Programming with ArrayBuffer

Let's start from the bottom and program from scratch with ArrayBuffer.

An array buffer of 16 bytes

chapter_bytes/js/16_bytes.js

{
    const buffer = new ArrayBuffer(0x10);
    const u8 = new Uint8Array(buffer);
    const data = u8.map((d, i) => i);
    const hex = (data) => {
        return data.reduce((x, y) => (
            y < 0x10 ? (`${x}0${y.toString(16)} `) : (`${x}${y.toString(16)} `)), ''
        );
    };

    document.getElementById('16_bytes').innerHTML = `
Data:
${hex(data)}
`;
}


An array buffer of 256 bytes

chapter_bytes/js/256_bytes.js

{
    const buffer = new ArrayBuffer(0x100);
    const u8 = new Uint8Array(buffer);
    const data = u8.map((d, i) => i);
    const hex = (data) => {
        return data.reduce((x, y) => {
            if (y < 0x10) {
                return `${x}0${y.toString(16)} `;
            }
            else {
                if ((y % 0x10) === 0) {
                    return `${x}\n${y.toString(16)} `
                }
                else {
                    return `${x}${y.toString(16)} `;
                }
            }
        }, ''
        );
    };

    document.getElementById('256_bytes').innerHTML = `
Data:
${hex(data)}
`;
}

These are all the possible values a byte can represent.



Empty WASM of 8 bytes

chapter_bytes/wat/empty.wat

(module)
$ wat2wasm empty.wat
$ hexdump -C empty.wasm
00000000  00 61 73 6d 01 00 00 00                           |.asm....|
00000008

chapter_bytes/js/8_bytes_empty_wasm.js

(async () => {
    const buffer = new ArrayBuffer(0x08);
    const u8 = new Uint8Array(buffer);
    const wasm_bytes = [0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00];
    const data = u8.map((d, i) => wasm_bytes[i]);

    const importObject = {};

    const wasm = await WebAssembly.instantiate(data.buffer, importObject);
    console.log({ wasm });
    const { instance } = wasm;
    const hex = (data) => {
        return data.reduce((x, y) => (
            y < 0x10 ? (`${x}0${y.toString(16)} `) : (`${x}${y.toString(16)} `)), ''
        );
    };

    document.getElementById('8_bytes_empty_wasm').innerHTML = `
Data:
${hex(data)}
wasm:
${JSON.stringify(wasm, null, 2)}
`;
})();


42 WASM

Let's see if we can "hand write" bytecode WASM in ArrayBuffer for a simple WASM that gives us the answer.

chapter_bytes/wat/42.wat

(module (func (export "42") (result i32) i32.const 42))
$ wat2wasm 42.wat
$ hexdump -C 42.wasm
00000000  00 61 73 6d 01 00 00 00  01 05 01 60 00 01 7f 03  |.asm.......`....|
00000010  02 01 00 07 06 01 02 34  32 00 00 0a 06 01 04 00  |.......42.......|
00000020  41 2a 0b                                          |A*.|
00000023

Now we can hard code those bytes in following JS.

chapter_bytes/js/42_wasm.js

(async () => {
    const wasm_bytes = [
        0x00, 0x61, 0x73, 0x6d,
        0x01, 0x00, 0x00, 0x00,
        0x01, 0x05, 0x01, 0x60,
        0x00, 0x01, 0x7f, 0x03,
        0x02, 0x01, 0x00, 0x07,
        0x06, 0x01, 0x02, 0x34,
        0x32, 0x00, 0x00, 0x0a,
        0x06, 0x01, 0x04, 0x00,
        0x41, 0x2a, 0x0b
    ];

    const buffer = new ArrayBuffer(wasm_bytes.length);
    const u8 = new Uint8Array(buffer);
    const data = u8.map((d, i) => wasm_bytes[i]);

    const importObject = {};

    const wasm = await WebAssembly.instantiate(data.buffer, importObject);
    console.log({ wasm });
    const { instance, module } = wasm;
    const { exports } = instance;
    const answer = exports["42"];
    const hex = (data) => {
        let output = '';
        for (let i = 0; i < data.length; i++) {
            if (data[i] < 0x10) {
                output += `0${data[i].toString(16)}`;
            }
            else {
                output += `${data[i].toString(16)}`;
            }
            if ((i % 0x10) === 0x0f) {
                output += '\n';
            }
            else {
                output += ' ';
            }
        }
        return output;
    };
    document.getElementById('42_wasm').innerHTML = `
Data:
${hex(data)}

wasm:
${JSON.stringify(wasm, null, 2)}

answer: 
${answer}

answer(): 
${answer()}
`;
})();


Memory

Now let's see how WebAssembly.Memory works.

We add a page of memory to the empty wasm.

chapter_bytes/wat/memory_empty.wat

(module (memory (import "js" "mem") 1)) ;; 1 page = 64KB
$ wat2wasm memory_empty.wat
$ hexdump -C memory_empty.wasm
00000000  00 61 73 6d 01 00 00 00  02 0b 01 02 6a 73 03 6d  |.asm........js.m|
00000010  65 6d 02 00 01                                    |em...|
00000015

chapter_bytes/js/single_page_memory_empty_wasm.js

(async () => {
    const wasm_bytes = [
        0x00, 0x61, 0x73, 0x6d,
        0x01, 0x00, 0x00, 0x00,
        0x02, 0x0b, 0x01, 0x02,
        0x6a, 0x73, 0x03, 0x6d,
        0x65, 0x6d, 0x02, 0x00,
        0x01
    ];

    const buffer = new ArrayBuffer(wasm_bytes.length);
    const u8 = new Uint8Array(buffer);
    const data = u8.map((d, i) => wasm_bytes[i]);
    const mem = new WebAssembly.Memory({ initial: 1, maximum: 1 });
    const importObject = { js: { mem } };

    window.wasm = await WebAssembly.instantiate(data.buffer, importObject);
    console.log({ wasm });
    const { instance, module } = wasm;
    console.log({ instance, module })
    const hex = (data) => {
        let output = '';
        for (let i = 0; i < data.length; i++) {
            if (data[i] < 0x10) {
                output += `0${data[i].toString(16)}`;
            }
            else {
                output += `${data[i].toString(16)}`;
            }
            if ((i % 0x10) === 0x0f) {
                output += '\n';
            }
            else {
                output += ' ';
            }
        }
        return output;
    };

    document.getElementById('single_page_memory_empty_wasm').innerHTML = `
Data:
${hex(data)}
wasm:
${JSON.stringify(wasm, null, 2)}
`;
})();


42 Memory

Now let's store 42 into the memory and then load it.

chapter_bytes/wat/42_memory.wat

(module 
  (memory (import "js" "mem") 1)
  (func (export "store") i32.const 0 i32.const 42 i32.store)
  (func (export "load") (result i32) i32.const 0 i32.load)
)
$ wat2wasm 42_memory.wat
$ hexdump -C 42_memory.wasm
00000000  00 61 73 6d 01 00 00 00  01 08 02 60 00 00 60 00  |.asm.......`..`.|
00000010  01 7f 02 0b 01 02 6a 73  03 6d 65 6d 02 00 01 03  |......js.mem....|
00000020  03 02 00 01 07 10 02 05  73 74 6f 72 65 00 00 04  |........store...|
00000030  6c 6f 61 64 00 01 0a 13  02 09 00 41 00 41 2a 36  |load.......A.A*6|
00000040  02 00 0b 07 00 41 00 28  02 00 0b                 |.....A.(...|
0000004b

chapter_bytes/js/42_memory_wasm.js

(async () => {
    const wasm_bytes = [
        0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
        0x01, 0x08, 0x02, 0x60, 0x00, 0x00, 0x60, 0x00,
        0x01, 0x7f, 0x02, 0x0b, 0x01, 0x02, 0x6a, 0x73,
        0x03, 0x6d, 0x65, 0x6d, 0x02, 0x00, 0x01, 0x03,
        0x03, 0x02, 0x00, 0x01, 0x07, 0x10, 0x02, 0x05,
        0x73, 0x74, 0x6f, 0x72, 0x65, 0x00, 0x00, 0x04,
        0x6c, 0x6f, 0x61, 0x64, 0x00, 0x01, 0x0a, 0x13,
        0x02, 0x09, 0x00, 0x41, 0x00, 0x41, 0x2a, 0x36,
        0x02, 0x00, 0x0b, 0x07, 0x00, 0x41, 0x00, 0x28,
        0x02, 0x00, 0x0b
    ];

    const buffer = new ArrayBuffer(wasm_bytes.length);
    const u8 = new Uint8Array(buffer);
    const data = u8.map((d, i) => wasm_bytes[i]);
    const mem = new WebAssembly.Memory({ initial: 1, maximum: 1 });
    const importObject = { js: { mem } };

    const wasm = await WebAssembly.instantiate(data.buffer, importObject);

    const { instance, module } = wasm;
    const { exports } = instance;
    const { store, load } = exports;

    console.log({ wasm })
    console.log({ instance, module, exports })
    const hex = (data) => {
        let output = '';
        for (let i = 0; i < data.length; i++) {
            if (data[i] < 0x10) {
                output += `0${data[i].toString(16)}`;
            }
            else {
                output += `${data[i].toString(16)}`;
            }
            if ((i % 0x10) === 0x0f) {
                output += '\n';
            }
            else {
                output += ' ';
            }
        }
        return output;
    };


    document.getElementById('42_memory_wasm').innerHTML += `
Data:
${hex(data)}
wasm:
${JSON.stringify(wasm, null, 2)}
store: ${store}
load: ${load}

load() : ${load()}
store() : ${store()}
load() : ${load()}
`;
})();


store and hex

chapter_bytes/wat/hex_memory_wasm.wat

(module 
  (memory (import "js" "mem") 1)
  (import "js" "hex" (func (param i32) ))
  (func (export "store") (param i32 i32) (result i32) 
    local.get 0
    local.get 1
    i32.store
    local.get 0
    i32.load)
  (export "hex" (func 0))
)
$ wat2wasm hex_memory_wasm.wat
$ hexdump -C hex_memory_wasm.wasm
00000000  00 61 73 6d 01 00 00 00  01 0b 02 60 01 7f 00 60  |.asm.......`...`|
00000010  02 7f 7f 01 7f 02 14 02  02 6a 73 03 6d 65 6d 02  |.........js.mem.|
00000020  00 01 02 6a 73 03 68 65  78 00 00 03 02 01 01 07  |...js.hex.......|
00000030  0f 02 05 73 74 6f 72 65  00 01 03 68 65 78 00 00  |...store...hex..|
00000040  0a 10 01 0e 00 20 00 20  01 36 02 00 20 00 28 02  |..... . .6.. .(.|
00000050  00 0b                                             |..|
00000052

chapter_bytes/js/hex_memory_wasm.js

(async () => {
    const wasm_bytes = [
        0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
        0x01, 0x0b, 0x02, 0x60, 0x01, 0x7f, 0x00, 0x60,
        0x02, 0x7f, 0x7f, 0x01, 0x7f, 0x02, 0x14, 0x02,
        0x02, 0x6a, 0x73, 0x03, 0x6d, 0x65, 0x6d, 0x02,
        0x00, 0x01, 0x02, 0x6a, 0x73, 0x03, 0x68, 0x65,
        0x78, 0x00, 0x00, 0x03, 0x02, 0x01, 0x01, 0x07,
        0x0f, 0x02, 0x05, 0x73, 0x74, 0x6f, 0x72, 0x65,
        0x00, 0x01, 0x03, 0x68, 0x65, 0x78, 0x00, 0x00,
        0x0a, 0x10, 0x01, 0x0e, 0x00, 0x20, 0x00, 0x20,
        0x01, 0x36, 0x02, 0x00, 0x20, 0x00, 0x28, 0x02,
        0x00, 0x0b
    ];

    const buffer = new ArrayBuffer(wasm_bytes.length);
    const u8 = new Uint8Array(buffer);
    const data = u8.map((d, i) => wasm_bytes[i]);
    const mem = new WebAssembly.Memory({ initial: 1, maximum: 1 });
    const importObject = {
        js: {
            mem,
            hex: (length = 0) => {
                const data = new Uint8Array(mem.buffer);
                let output = '';
                for (let i = 0; i < length; i++) {
                    if (data[i] < 0x10) {
                        output += `0${data[i].toString(16)}`;
                    }
                    else {
                        output += `${data[i].toString(16)}`;
                    }
                    if ((i % 0x10) === 0x0f) {
                        output += '\n';
                    }
                    else {
                        output += ' ';
                    }
                }
                document.getElementById('hex_memory_wasm').innerHTML += `
hex(${length}):
${output}`;
            }
        }
    };

    const wasm = await WebAssembly.instantiate(data.buffer, importObject);

    const { instance, module } = wasm;
    const { exports } = instance;
    const { store, hex } = exports;

    console.log({ wasm })
    console.log({ instance, module, exports })

    document.getElementById('hex_memory_wasm').innerHTML += `
wasm:
${JSON.stringify(wasm, null, 2)}
store: ${store}
store(0, 42) : ${store(0, 42)}
`;
    hex(0x10);
    document.getElementById('hex_memory_wasm').innerHTML += `
store(0xf, 42) : ${store(0xf, 42)}
`
    hex(0x10);

    for (let i = 0; i < 257; i++) {
        store(i, i)
        hex(i);
    }
})();