WASM Binary Format
Here is a hexdump function to help us read byte code in the browser:
chapter_wasm_binary/hexdump.js
const hexdump = ({ data, length, offset = 0 }) => {
let output = '';
for (let i = offset; i < offset + (length ? length : 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;
};
Module preamble
Every module starts with a preamble of a magic and version. Let's use JS to create an empty module from binary data.
chapter_wasm_binary/magic_version.js
(async () => {
const magic = [0x00, 0x61, 0x73, 0x6d];
const version = [0x01, 0x00, 0x00, 0x00];
const wasm = new Uint8Array(magic.concat(version));
const importObject = {};
const module = await WebAssembly.compile(wasm.buffer);
const instance = await WebAssembly.instantiate(module, importObject);
console.log({ module, instance });
document.getElementById('magic_version').innerHTML = `
WASM
hexdump({ data: wasm })
${hexdump({ data: wasm })}
${JSON.stringify({ module, instance }, null, 2)}
`
})();
As a reference here is the chapter_wasm_binary/magic_version.wat
(module)
$ wat2wasm magic_version.wat
$ hexdump -C magic_version.wasm
00000000 00 61 73 6d 01 00 00 00 |.asm....|
00000008
Sections
Each section consists of
- a one-byte section id,
- the
u32
size of the contents, in bytes, - the actual contents, whose structure is depended on the section id.
Every section is optional; an omitted section is equivalent to the section being present with empty contents.
Let's create modules from scratch by adding sections one by one.
(module (func))
chapter_wasm_binary/func.js
(async () => {
const magic = [0x00, 0x61, 0x73, 0x6d];
const version = [0x01, 0x00, 0x00, 0x00];
const section_01 = [
0x01, // type section
0x04, // 4 bytes
0x01, // 1 type
0x60, // func type
0x00, // no input
0x00 // no output
];
const section_03 = [
0x03, // func section
0x02, // 2 bytes
0x01, // number of functions
0x00 // type of the function
];
const section_0a = [
0x0a, // code section
0x04, // 4 bytes
0x01, // number of function bodies.
0x02, // 2 bytes
0x00, // number of local variables
0x0b // opcode for end
];
const wasm = new Uint8Array(
magic.concat(version)
.concat(section_01)
.concat(section_03)
.concat(section_0a)
);
const importObject = {};
const module = await WebAssembly.compile(wasm.buffer);
const instance = await WebAssembly.instantiate(module, importObject);
console.log({ module, instance });
document.getElementById('func').innerHTML = `
WASM:
hexdump({ data: wasm })
${hexdump({ data: wasm })}
${JSON.stringify({ module, instance }, null, 2)}
`
})();
As a reference here is the chapter_wasm_binary/func.wat
(module (func))
$ wat2wasm func.wat
$ hexdump -C func.wasm
00000000 00 61 73 6d 01 00 00 00 01 04 01 60 00 00 03 02 |.asm.......`....|
00000010 01 00 0a 04 01 02 00 0b |........|
00000018
(module (memory 1) (func))
chapter_wasm_binary/mem_func.js
(async () => {
const magic = [0x00, 0x61, 0x73, 0x6d];
const version = [0x01, 0x00, 0x00, 0x00];
const section_01 = [
0x01, // type section
0x04, // 4 bytes
0x01, // 1 type
0x60, // func type
0x00, // no input
0x00 // no output
];
const section_03 = [
0x03, // func section
0x02, // 2 bytes
0x01, // number of functions
0x00 // type of the function
];
const section_05 = [
0x05, // memory section
0x03, // 3 bytes
0x01, //
0x00, // min
0x01 // max
];
const section_0a = [
0x0a, // code section
0x04, // 4 bytes
0x01, // number of function bodies.
0x02, // 2 bytes
0x00, // number of local variables
0x0b // opcode for end
];
const wasm = new Uint8Array(
magic.concat(version)
.concat(section_01)
.concat(section_03)
.concat(section_05)
.concat(section_0a)
);
const importObject = {};
const module = await WebAssembly.compile(wasm.buffer);
const instance = await WebAssembly.instantiate(module, importObject);
console.log({ module, instance });
document.getElementById('mem_func').innerHTML = `
WASM:
hexdump({ data: wasm })
${hexdump({ data: wasm })}
${JSON.stringify({ module, instance }, null, 2)}
`
})();
As a reference here is the chapter_wasm_binary/mem_func.wat
(module (memory 1) (func))
$ wat2wasm mem_func.wat
$ hexdump -C mem_func.wasm
00000000 00 61 73 6d 01 00 00 00 01 04 01 60 00 00 03 02 |.asm.......`....|
00000010 01 00 05 03 01 00 01 0a 04 01 02 00 0b |.............|
0000001d
(module (memory (import "js" "mem") 1) (func))
chapter_wasm_binary/import_mem_func.js
(async () => {
const magic = [0x00, 0x61, 0x73, 0x6d];
const version = [0x01, 0x00, 0x00, 0x00];
const section_01 = [
0x01, // type section
0x04, // 4 bytes
0x01, // 1 type
0x60, // func type
0x00, // no input
0x00 // no output
];
const section_02 = [
0x02, // import section
0x0b, // 11 (0x0b) bytes
0x01, // number of imports
0x02, // module string length
0x6a, // j
0x73, // s
0x03, // field string length
0x6d, // m
0x65, // e
0x6d, // m
0x02, // memory type
0x00, // min
0x01 // max
];
const section_03 = [
0x03, // func section
0x02, // 2 bytes
0x01, // number of functions
0x00 // type of the function
];
const section_0a = [
0x0a, // code section
0x04, // 4 bytes
0x01, // number of function bodies.
0x02, // 2 bytes
0x00, // number of local variables
0x0b // opcode for end
];
const wasm = new Uint8Array(magic
.concat(version)
.concat(section_01)
.concat(section_02)
.concat(section_03)
.concat(section_0a));
const mem = new WebAssembly.Memory({ initial: 1, maximum: 1 });
const importObject = { js: { mem } };
const module = await WebAssembly.compile(wasm.buffer);
const instance = await WebAssembly.instantiate(module, importObject);
console.log({ module, instance });
document.getElementById('import_mem_func').innerHTML = `
WASM
hexdump({ data: wasm })
${hexdump({ data: wasm })}
${JSON.stringify({ module, instance }, null, 2)}
`
})();
As a reference here is the chapter_wasm_binary/import_mem_func.wat
(module (memory (import "js" "mem") 1) (func))
$ wat2wasm import_mem_func.wat
$ hexdump -C import_mem_func.wasm
00000000 00 61 73 6d 01 00 00 00 01 04 01 60 00 00 02 0b |.asm.......`....|
00000010 01 02 6a 73 03 6d 65 6d 02 00 01 03 02 01 00 0a |..js.mem........|
00000020 04 01 02 00 0b |.....|
00000025
Export memory and func
chapter_wasm_binary/export_mem_func.js
(async () => {
const magic = [0x00, 0x61, 0x73, 0x6d];
const version = [0x01, 0x00, 0x00, 0x00];
const section_01 = [
0x01, // type section
0x07, // 7 bytes
0x01, // number of functions
0x60, // function type
0x02, // takes two params
0x7f, 0x7f, // i32
0x01, // return 1 result
0x7f // i32
];
const section_03 = [
0x03, // func section
0x02, // 2 bytes
0x01, // number of functions
0x00 // type of the function
];
const section_05 = [
0x05, // memory section
0x03, // 3 bytes
0x01, // number of memory
0x00, // min
0x01 // max
];
const section_07 = [
0x07, // export section
0x09, // 9 bytes
0x02, // number of exports
0x01, // field length
0x6d, // "m"
0x02, // type: memory
0x00, // memory index
0x01, // field length
0x66, // "f"
0x00, // type: function
0x00 // function index
];
const section_0a = [
0x0a, // code section
0x0d, // 13 bytes
0x01, // number of function bodies.
0x0b, // 11 bytes
0x00, // number of local variables
0x20, // local.get
0x00, // 0
0x20, // local.get
0x01, // 1
0x36, // i32.store
0x02, // align 2 bytes
0x00, // offset
0x20, // local.get
0x01, // 1 (return the stored i32)
0x0b // opcode for end
];
const wasm = new Uint8Array(
magic.concat(version)
.concat(section_01)
.concat(section_03)
.concat(section_05)
.concat(section_07)
.concat(section_0a)
);
const importObject = {};
const module = await WebAssembly.compile(wasm.buffer);
const instance = await WebAssembly.instantiate(module, importObject);
const { exports } = instance;
console.log({ module, instance, exports });
document.getElementById('export_mem_func').innerHTML = `
WASM
hexdump({ data: wasm})
${hexdump({ data: wasm })}
hexdump({ data: wasm, length: 0x10})
${hexdump({ data: wasm, length: 0x10 })}
hexdump({ data: wasm, length: 0x10, offset: 0x10})
${hexdump({ data: wasm, length: 0x10, offset: 0x10 })}
hexdump({ data: (new Uint8Array(exports.m.buffer)), length: 0x10})
${hexdump({ data: (new Uint8Array(exports.m.buffer)), length: 0x10 })}
exports.f(0x08, 42)
${exports.f(0x08, 42)}
hexdump({ data: (new Uint8Array(exports.m.buffer)), length: 0x10})
${hexdump({ data: (new Uint8Array(exports.m.buffer)), length: 0x10 })}
${JSON.stringify({ module, instance }, null, 2)}
`
})();
As a reference here is the chapter_wasm_binary/export_mem_func.wat
(module
(memory 1)
(func (param i32 i32) (result i32)
local.get 0
local.get 1
i32.store
local.get 1
)
(export "m" (memory 0 ))
(export "f" (func 0 ))
)
$ wat2wasm export_mem_func.wat
$ hexdump -C export_mem_func.wasm
00000000 00 61 73 6d 01 00 00 00 01 07 01 60 02 7f 7f 01 |.asm.......`....|
00000010 7f 03 02 01 00 05 03 01 00 01 07 09 02 01 6d 02 |..............m.|
00000020 00 01 66 00 00 0a 0d 01 0b 00 20 00 20 01 36 02 |..f....... . .6.|
00000030 00 20 01 0b |. ..|
00000034