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);
}
})();