WASM Instructions

After we have set up the toolchain to develop WASM in JS, it is time to learn how to do real coding with WASM Instructions, which are encoded by opcodes.

Let's write a starter WASM with I/O via module imports and exports.

Starter

chapter_wasm_instructions/starter.js

(async () => {
    const canvas = document.getElementById('starter_canvas');
    const hex_output = document.getElementById('starter_hex');
    const width = 512;
    const height = 512;
    canvas.width = width;
    canvas.height = height;
    canvas.style.border = '1px solid black';
    const ctx = canvas.getContext('2d');
    ctx.fillRect(0, 0, width, height);
    let imageData = ctx.getImageData(0, 0, width, height);
    let x = 0;
    let y = 0;
    let offset = 0;

    const check_boundary = () => {
        if (x < 0) { x = 0 }
        if (x > width - 1) { x = width - 1 }
        if (y < 0) { y = 0 }
        if (y > height - 1) { y = height - 1 }
        offset = 4 * (y * width + x);
        r = offset;
        g = offset + 1;
        b = offset + 2;
        a = offset + 3;
    }

    canvas.addEventListener('mousemove', (e) => {
        const rect = canvas.getBoundingClientRect();
        x = Math.floor(e.clientX - rect.left);
        y = Math.floor(e.clientY - rect.top);
        hexdump();
    }, false)

    canvas.addEventListener('mouseout', (e) => {
        x = 0;
        y = 0;
        offset = 0;
        hexdump();
    }, false)

    const hexdump = () => {
        const { data } = imageData;
        let output = '';
        check_boundary();
        for (let i = offset; i < offset + 0x100; i++) {
            if (i < data.length) {
                if (data[i] < 0x10) {
                    output += `0${data[i].toString(16)}`;
                }
                else {
                    output += `${data[i].toString(16)}`;
                }
                if ((i % 0x10) === 0x0f) {
                    output += '\n';
                }
                else {
                    output += ' ';
                }
            }
        }
        hex_output.innerHTML = `
x: ${parseInt(x)}
y: ${parseInt(y)}        
hexdump (offset: ${offset})

${output}
`;
    };

    const canvas_render = () => {
        ctx.putImageData(imageData, 0, 0);
    }

    const magic = [0x00, 0x61, 0x73, 0x6d];
    const version = [0x01, 0x00, 0x00, 0x00];
    const section_01 = [
        0x01, // type section 
        0x0d, // 13 bytes
        0x03, // number of functions
        0x60, // first func type (output)
        0x00, // no input
        0x00, // no output
        0x60, // second func type (f)
        0x00, // no input
        0x00, // no output
        0x60, // third func type (s)
        0x02, // takes two params 
        0x7f, 0x7f, // i32
        0x01, // return 1 result 
        0x7f  // i32
    ];

    const section_02 = [
        0x02, // import section
        0x0d, // 13 bytes
        0x01, // 1 import
        0x02, // 2 bytes
        0x6a, // j
        0x73, // s
        0x06, // 6 bytes
        0x6f, // o
        0x75, // u
        0x74, // t
        0x70, // p
        0x75, // u
        0x74, // t
        0x00, // no input
        0x00  // no output
    ]
    const section_03 = [
        0x03, // func section
        0x03, // 3 bytes
        0x02, // number of functions 
        0x01, // type of the first function (f)
        0x02  // type of the second function (s)
    ];
    const section_05 = [
        0x05, // memory section
        0x03, // 3 bytes
        0x01, // number of memory
        0x00, // min
        0x10  // max 16 pages of data
    ];
    const section_07 = [
        0x07, // export section
        0x16, // 22 bytes
        0x04, // number of exports
        0x01,
        0x6d, // "m"
        0x02,
        0x00,
        0x01,
        0x66, // "f"
        0x00,
        0x01, // func id
        0x01,
        0x73, // "s"
        0x00,
        0x02, // func id
        0x06, // 6 bytes
        0x6f, // o
        0x75, // u
        0x74, // t
        0x70, // p
        0x75, // u
        0x74, // t
        0x00,
        0x00  // func id 
    ];

    const section_0a_header = [
        0x0a, // code section 
        0x18, // 24 bytes
        0x02  // number of function bodies
    ]

    const section_0a_f = [
        0x0a, // the first func (f) 
        0x00, // number of local variables
        0x41,
        0x00, // address 0x00
        0x41,
        0x00, // 
        0x01,
        0x36, // store to the memory
        0x02, // align
        0x00, // offset
        0x0b  // end
    ]

    const section_0a_s = [
        0x0b, // 11 bytes the second func (s)
        0x00, // number of local variables
        0x20, // local.get 
        0x00, // 0
        0x20, // local.get
        0x01, // 1
        0x36, // i32.store 
        0x02, // align
        0x00, // offset
        0x20, // local.get
        0x01, // 1 (return the stored i32)
        0x0b  // opcode for end
    ]

    const section_0a = section_0a_header
        .concat(section_0a_f)
        .concat(section_0a_s);

    const wasm = new Uint8Array(
        magic.concat(version)
            .concat(section_01)
            .concat(section_02)
            .concat(section_03)
            .concat(section_05)
            .concat(section_07)
            .concat(section_0a)
    );

    const importObject = {
        js: {
            output: () => {
                canvas_render();
                hexdump();
            },
        }
    };

    const module = await WebAssembly.compile(wasm.buffer);
    const instance = await WebAssembly.instantiate(module, importObject);
    const { exports } = instance;
    const { f, s, m, output } = exports;
    console.log({ exports })
    const memory_data = new Uint8Array(m.buffer);

    for (let i = 0; i < imageData.data.length; i++) {
        imageData.data[i] = memory_data[i];
    }

    output();
})();



Draw on canvas

Mouse down to draw red dots on canvas.

chapter_wasm_instructions/draw.js

(async () => {
    const canvas = document.getElementById('draw_canvas');
    canvas.style.width = "100%";
    const hex_output = document.getElementById('draw_hex');
    const width = 512;
    const height = 512;
    canvas.width = width;
    canvas.height = height;
    canvas.style.border = '1px solid black';
    const ctx = canvas.getContext('2d');
    ctx.fillRect(0, 0, width, height);
    let imageData = ctx.getImageData(0, 0, width, height);
    let x = 0;
    let y = 0;
    let offset = 0;

    const check_boundary = () => {
        if (x < 0) { x = 0 }
        if (x > width - 1) { x = width - 1 }
        if (y < 0) { y = 0 }
        if (y > height - 1) { y = height - 1 }
        offset = 4 * (y * width + x);
    }

    let drawing = false;

    const start = (e) => {
        const rect = canvas.getBoundingClientRect();
        x = Math.floor(e.clientX - rect.left);
        y = Math.floor(e.clientY - rect.top);
        drawing = true;
        hexdump();
    };

    canvas.addEventListener('mousedown', start, false)
    canvas.addEventListener('touchstart', start, false)

    const end = (e) => {
        drawing = false;
        hexdump();
    }
    canvas.addEventListener('mouseup', end, false)
    canvas.addEventListener('touchend', end, false);

    const move = (e) => {
        e.preventDefault();
        const rect = canvas.getBoundingClientRect();
        const ratio = width / canvas.clientWidth;

        const { changedTouches } = e;
        if (changedTouches && changedTouches[0]) {
            x = Math.floor((changedTouches[0].pageX - canvas.offsetLeft) * ratio);
            y = Math.floor((changedTouches[0].pageY - canvas.offsetTop) * ratio);
        }
        else {
            x = Math.floor((e.clientX - rect.left) * ratio);
            y = Math.floor((e.clientY - rect.top) * ratio);
        }
        if (drawing) {
            draw();
            canvas_render();
        }
        hexdump();
    };

    canvas.addEventListener('mousemove', move, false);
    canvas.addEventListener('touchmove', move, false);

    canvas.addEventListener('mouseout', (e) => {
        x = 0;
        y = 0;
        offset = 0;
        drawing = false;
        hexdump();
    }, false)

    const hexdump = () => {
        const { data } = imageData;
        check_boundary();
        let output = '';
        for (let i = offset; i < offset + 0x100; i++) {
            if (i < data.length) {
                if (data[i] < 0x10) {
                    output += `0${data[i].toString(16)}`;
                }
                else {
                    output += `${data[i].toString(16)}`;
                }
                if ((i % 0x10) === 0x0f) {
                    output += '\n';
                }
                else {
                    output += ' ';
                }
            }
        }
        hex_output.innerHTML = `
x: ${parseInt(x)}
y: ${parseInt(y)}        
hexdump (offset: ${offset})

${output}
`;
    };

    const canvas_render = () => {
        for (let i = 0; i < imageData.data.length; i++) {
            imageData.data[i] = memory_data[i];
        }
        ctx.putImageData(imageData, 0, 0);
    }

    const magic = [0x00, 0x61, 0x73, 0x6d];
    const version = [0x01, 0x00, 0x00, 0x00];
    const section_01 = [
        0x01, // type section 
        0x0d, // 13 bytes
        0x03, // number of functions
        0x60, // first func type (output)
        0x00, // no input
        0x00, // no output
        0x60, // second func type (f)
        0x00, // no input
        0x00, // no output
        0x60, // third func type (s)
        0x02, // takes two params 
        0x7f, 0x7f, // i32
        0x01, // return 1 result 
        0x7f  // i32
    ];

    const section_02 = [
        0x02, // import section
        0x0d, // 13 bytes
        0x01, // 1 import
        0x02, // 2 bytes
        0x6a, // j
        0x73, // s
        0x06, // 6 bytes
        0x6f, // o
        0x75, // u
        0x74, // t
        0x70, // p
        0x75, // u
        0x74, // t
        0x00, // no input
        0x00  // no output
    ]
    const section_03 = [
        0x03, // func section
        0x03, // 3 bytes
        0x02, // number of functions 
        0x01, // type of the first function (f)
        0x02  // type of the second function (s)
    ];
    const section_05 = [
        0x05, // memory section
        0x03, // 3 bytes
        0x01, // number of memory
        0x00, // min
        0x10  // max 16 pages of data
    ];
    const section_07 = [
        0x07, // export section
        0x16, // 22 bytes
        0x04, // number of exports
        0x01, // 1 byte name
        0x6d, // "m"
        0x02, // memory 
        0x00, // memory id
        0x01, // 1 byte
        0x66, // "f"
        0x00, // function
        0x01, // func id
        0x01, // 1 byte
        0x73, // "s"
        0x00, // function 
        0x02, // func id
        0x06, // 6 bytes
        0x6f, // o
        0x75, // u
        0x74, // t
        0x70, // p
        0x75, // u
        0x74, // t
        0x00, // function
        0x00  // func id 
    ];

    const section_0a_header = [
        0x0a, // code section 
        0x18, // 24 bytes
        0x02  // number of function bodies
    ]

    const section_0a_f = [
        0x0a, // the first func (f) 
        0x00, // number of local variables
        0x41,
        0x02, // address 0x00
        0x41,
        0x00, // 0x00 // does nothing
        0x01,
        0x36, // store to the memory
        0x02, // align
        0x00, // offset
        0x0b  // end
    ]

    const section_0a_s = [
        0x0b, // 11 bytes the second func (s)
        0x00, // number of local variables
        0x20, // local.get 
        0x00, // 0
        0x20, // local.get
        0x01, // 1
        0x36, // i32.store 
        0x00, // align
        0x00, // offset
        0x20, // local.get
        0x01, // 1 (return the stored i32)
        0x0b  // opcode for end
    ]

    const section_0a = section_0a_header
        .concat(section_0a_f)
        .concat(section_0a_s);

    const wasm = new Uint8Array(
        magic.concat(version)
            .concat(section_01)
            .concat(section_02)
            .concat(section_03)
            .concat(section_05)
            .concat(section_07)
            .concat(section_0a)
    );

    console.log({ wasm });
    const importObject = {
        js: {
            output: () => {
                canvas_render();
                hexdump();
            },
        }
    };

    const module = await WebAssembly.compile(wasm.buffer);
    const instance = await WebAssembly.instantiate(module, importObject);
    const { exports } = instance;
    const { f, s, m, output } = exports;
    console.log({ exports })
    const memory_data = new Uint8Array(m.buffer);

    const draw = () => {
        check_boundary();
        s(offset, 0xff0000ff); // AABBGGRR
    }

    output();
})();