WASM Binary Format

Reference

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