Skip to content

Instantly share code, notes, and snippets.

@bhelx
Last active January 27, 2024 04:15
Show Gist options
  • Save bhelx/c3533d6e1d0ec6aa71da2fae51f09a2b to your computer and use it in GitHub Desktop.
Save bhelx/c3533d6e1d0ec6aa71da2fae51f09a2b to your computer and use it in GitHub Desktop.

Revisions

  1. bhelx revised this gist Jan 27, 2024. 1 changed file with 0 additions and 3 deletions.
    3 changes: 0 additions & 3 deletions wam.js
    Original file line number Diff line number Diff line change
    @@ -121,9 +121,6 @@ class Parser {
    }

    parseFunctionSection(sectionSize) {
    // var [numTypes, nextIndex] = this.readU32()
    // this.seek(nextIndex)
    // console.log({sectionSize, numTypes})
    this.seek(sectionSize);

    return {
  2. bhelx revised this gist Jan 27, 2024. 1 changed file with 36 additions and 49 deletions.
    85 changes: 36 additions & 49 deletions wam.js
    Original file line number Diff line number Diff line change
    @@ -9,18 +9,25 @@ class Parser {
    MAGIC_BYTES = Buffer.from([0x00, 0x61, 0x73, 0x6d]);
    MOD_VERSION = Buffer.from([0x01, 0x00, 0x00, 0x00]);


    constructor(data) {
    this.data = data;
    this.index = 0;
    }

    readBytes(n) {
    peekBytes(n) {
    return this.data.subarray(this.index, this.index + n);
    }

    readByte() {
    return this.data[this.index];
    consumeBytes(n) {
    let result = this.peekBytes(n)
    this.seek(n)
    return result
    }

    consumeByte() {
    const result = this.data[this.index];
    this.seek(1)
    return result
    }

    seek(n) {
    @@ -33,23 +40,22 @@ class Parser {
    return this.index >= this.data.length;
    }

    readU32() {
    const bytes = this.readBytes(MAX_NUMBER_OF_BYTE_U32);
    consumeU32() {
    const bytes = this.peekBytes(MAX_NUMBER_OF_BYTE_U32);
    const result = decodeUInt32(bytes);
    return [result.value, result.nextIndex];
    this.seek(result.nextIndex)
    return result.value
    }

    parseModule() {
    const magic = this.readBytes(4);
    const magic = this.consumeBytes(4);
    if (!magic.equals(this.MAGIC_BYTES)) {
    throw Error("Magic bytes not found: " + magic);
    }
    this.seek(4);
    const version = this.readBytes(4);
    const version = this.consumeBytes(4);
    if (!version.equals(this.MOD_VERSION)) {
    throw Error(`Module version did not magtch : ${version}`);
    }
    this.seek(4);
    const sections = [];
    while (!this.endOfFile()) {
    const section = this.parseSection();
    @@ -63,10 +69,8 @@ class Parser {
    }

    parseSection() {
    const id = this.readByte();
    this.seek(1);
    let [sectionSize, nextIndex] = this.readU32();
    this.seek(nextIndex);
    const id = this.consumeByte();
    let sectionSize = this.consumeU32();
    switch (id) {
    case 0x01:
    return this.parseTypeSection(sectionSize);
    @@ -82,33 +86,27 @@ class Parser {
    }

    parseTypeSection(sectionSize) {
    var [numTypes, nextIndex] = this.readU32();
    this.seek(nextIndex);
    var numTypes = this.consumeU32();
    let funcTypes = [];
    for (var i = 0; i < numTypes; i++) {
    var [headerByte, nextIndex] = this.readU32();
    this.seek(nextIndex);
    var headerByte = this.consumeU32();
    if (headerByte != 0x60) {
    throw Error("Expected 0x60 header byte got " + headerByte);
    }
    var [numFuncElements, nextIndex] = this.readU32();
    this.seek(nextIndex);
    var numFuncElements = this.consumeU32();
    const funcSig = {
    params: [],
    returns: [],
    };
    // params
    for (var j = 0; j < numFuncElements; j++) {
    var [valueType, nextIndex] = this.readU32();
    this.seek(nextIndex);
    var valueType = this.consumeU32();
    funcSig.params.push(valueType);
    }
    var [numFuncElements, nextIndex] = this.readU32();
    this.seek(nextIndex);
    var numFuncElements = this.consumeU32();
    // returns
    for (var j = 0; j < numFuncElements; j++) {
    var [valueType, nextIndex] = this.readU32();
    this.seek(nextIndex);
    var valueType = this.consumeU32();
    funcSig.returns.push(valueType);
    }
    funcTypes.push({
    @@ -134,19 +132,14 @@ class Parser {
    }

    parseExportSection(sectionSize) {
    var [numExports, nextIndex] = this.readU32();
    this.seek(nextIndex);
    var numExports = this.consumeU32();
    const exports = [];
    for (let i = 0; i < numExports; i++) {
    var [strLen, nextIndex] = this.readU32();
    this.seek(nextIndex);
    const bytes = this.readBytes(strLen);
    var strLen = this.consumeU32();
    const bytes = this.consumeBytes(strLen);
    const name = new TextDecoder().decode(bytes);
    this.seek(strLen);
    const type = this.readByte();
    this.seek(1);
    const idx = this.readByte();
    this.seek(1);
    const type = this.consumeByte();
    const idx = this.consumeByte();
    exports.push({
    name,
    desc: {
    @@ -163,30 +156,24 @@ class Parser {
    }

    parseCodeSection(sectionSize) {
    var [numCodes, nextIndex] = this.readU32();
    this.seek(nextIndex);
    var numCodes = this.consumeU32();

    const functions = [];
    for (let i = 0; i < numCodes; i++) {
    var [codeSize, nextIndex] = this.readU32();
    this.seek(nextIndex);
    var [funcIndex, nextIndex] = this.readU32();
    this.seek(nextIndex);
    var codeSize = this.consumeU32();
    var funcIndex = this.consumeU32();
    const end = this.index + codeSize - 1;
    const instructions = [];
    while (this.index < end) {
    const instr = { opcode: this.readByte(), operands: [] };
    this.seek(1);
    const instr = { opcode: this.consumeByte(), operands: [] };
    switch (instr.opcode) {
    case 0x20:
    instr.name = "local.get";
    instr.operands.push(this.readByte());
    this.seek(1);
    instr.operands.push(this.consumeByte());
    break;
    case 0x41:
    instr.name = "i32.const";
    instr.operands.push(this.readByte());
    this.seek(1);
    instr.operands.push(this.consumeByte());
    break;
    case 0x6a:
    instr.name = "i32.add";
  3. bhelx revised this gist Nov 13, 2022. No changes.
  4. bhelx revised this gist Nov 13, 2022. No changes.
  5. bhelx revised this gist Nov 13, 2022. No changes.
  6. bhelx created this gist Nov 13, 2022.
    12 changes: 12 additions & 0 deletions test.wat
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,12 @@
    (module
    (func
    (export "add41")
    (param $n i32)
    (result i32)

    i32.const 41
    local.get $n
    i32.add
    )
    )

    265 changes: 265 additions & 0 deletions wam.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,265 @@
    const {
    decodeUInt32,
    MAX_NUMBER_OF_BYTE_U32,
    } = require("@webassemblyjs/leb128");

    const fs = require("fs");

    class Parser {
    MAGIC_BYTES = Buffer.from([0x00, 0x61, 0x73, 0x6d]);
    MOD_VERSION = Buffer.from([0x01, 0x00, 0x00, 0x00]);


    constructor(data) {
    this.data = data;
    this.index = 0;
    }

    readBytes(n) {
    return this.data.subarray(this.index, this.index + n);
    }

    readByte() {
    return this.data[this.index];
    }

    seek(n) {
    if (this.index + n > this.data.length)
    throw Error("Tring to seek past EOF");
    this.index += n;
    }

    endOfFile() {
    return this.index >= this.data.length;
    }

    readU32() {
    const bytes = this.readBytes(MAX_NUMBER_OF_BYTE_U32);
    const result = decodeUInt32(bytes);
    return [result.value, result.nextIndex];
    }

    parseModule() {
    const magic = this.readBytes(4);
    if (!magic.equals(this.MAGIC_BYTES)) {
    throw Error("Magic bytes not found: " + magic);
    }
    this.seek(4);
    const version = this.readBytes(4);
    if (!version.equals(this.MOD_VERSION)) {
    throw Error(`Module version did not magtch : ${version}`);
    }
    this.seek(4);
    const sections = [];
    while (!this.endOfFile()) {
    const section = this.parseSection();
    sections.push(section);
    }

    return {
    type: "Module",
    sections,
    };
    }

    parseSection() {
    const id = this.readByte();
    this.seek(1);
    let [sectionSize, nextIndex] = this.readU32();
    this.seek(nextIndex);
    switch (id) {
    case 0x01:
    return this.parseTypeSection(sectionSize);
    case 0x03:
    return this.parseFunctionSection(sectionSize);
    case 0x07:
    return this.parseExportSection(sectionSize);
    case 0x0A:
    return this.parseCodeSection(sectionSize);
    default:
    throw Error("Unknown section id: " + id);
    }
    }

    parseTypeSection(sectionSize) {
    var [numTypes, nextIndex] = this.readU32();
    this.seek(nextIndex);
    let funcTypes = [];
    for (var i = 0; i < numTypes; i++) {
    var [headerByte, nextIndex] = this.readU32();
    this.seek(nextIndex);
    if (headerByte != 0x60) {
    throw Error("Expected 0x60 header byte got " + headerByte);
    }
    var [numFuncElements, nextIndex] = this.readU32();
    this.seek(nextIndex);
    const funcSig = {
    params: [],
    returns: [],
    };
    // params
    for (var j = 0; j < numFuncElements; j++) {
    var [valueType, nextIndex] = this.readU32();
    this.seek(nextIndex);
    funcSig.params.push(valueType);
    }
    var [numFuncElements, nextIndex] = this.readU32();
    this.seek(nextIndex);
    // returns
    for (var j = 0; j < numFuncElements; j++) {
    var [valueType, nextIndex] = this.readU32();
    this.seek(nextIndex);
    funcSig.returns.push(valueType);
    }
    funcTypes.push({
    funcSig,
    });
    }

    return {
    type: "TypeSection",
    funcTypes,
    };
    }

    parseFunctionSection(sectionSize) {
    // var [numTypes, nextIndex] = this.readU32()
    // this.seek(nextIndex)
    // console.log({sectionSize, numTypes})
    this.seek(sectionSize);

    return {
    type: "FunctionSection",
    };
    }

    parseExportSection(sectionSize) {
    var [numExports, nextIndex] = this.readU32();
    this.seek(nextIndex);
    const exports = [];
    for (let i = 0; i < numExports; i++) {
    var [strLen, nextIndex] = this.readU32();
    this.seek(nextIndex);
    const bytes = this.readBytes(strLen);
    const name = new TextDecoder().decode(bytes);
    this.seek(strLen);
    const type = this.readByte();
    this.seek(1);
    const idx = this.readByte();
    this.seek(1);
    exports.push({
    name,
    desc: {
    type,
    idx,
    },
    });
    }

    return {
    type: "ExportSection",
    exports,
    };
    }

    parseCodeSection(sectionSize) {
    var [numCodes, nextIndex] = this.readU32();
    this.seek(nextIndex);

    const functions = [];
    for (let i = 0; i < numCodes; i++) {
    var [codeSize, nextIndex] = this.readU32();
    this.seek(nextIndex);
    var [funcIndex, nextIndex] = this.readU32();
    this.seek(nextIndex);
    const end = this.index + codeSize - 1;
    const instructions = [];
    while (this.index < end) {
    const instr = { opcode: this.readByte(), operands: [] };
    this.seek(1);
    switch (instr.opcode) {
    case 0x20:
    instr.name = "local.get";
    instr.operands.push(this.readByte());
    this.seek(1);
    break;
    case 0x41:
    instr.name = "i32.const";
    instr.operands.push(this.readByte());
    this.seek(1);
    break;
    case 0x6a:
    instr.name = "i32.add";
    break;
    case 0x0b:
    instr.name = "end";
    break;
    default:
    throw Error("Opcode not supported yet: " + instr.opcode);
    }
    instructions.push(instr);
    }
    functions.push({
    funcIndex,
    instructions,
    });
    }

    return {
    type: "CodeSection",
    functions,
    };
    }
    }
    class WamMachine {
    constructor() {
    this.stack = [];
    }

    run(code, args) {
    let ret = undefined;
    code.forEach((c) => {
    switch (c.opcode) {
    case 0x20:
    this.stack.push(args[c.operands[0]]);
    break;
    case 0x41:
    this.stack.push(c.operands[0]);
    break;
    case 0x6a:
    let a = this.stack.pop();
    let b = this.stack.pop();
    this.stack.push(a + b);
    break;
    case 0x0b:
    ret = this.stack.pop();
    break;
    }
    });
    return ret;
    }
    }
    class WamModule {
    constructor(file) {
    const data = fs.readFileSync(file);
    this.module = new Parser(data).parseModule();
    this.machine = new WamMachine();
    this.exports = {};
    const exportSec = this.module.sections.find(
    (s) => s.type === "ExportSection"
    );
    const codeSec = this.module.sections.find((s) => s.type === "CodeSection");
    //const typeSec = this.module.sections.find(s => s.type === "TypeSection")
    exportSec.exports.forEach((fe) => {
    const instructions = codeSec.functions[fe.desc.idx].instructions;
    //const funcType = typSec.funcTypes[fe.desc.type]
    this.exports[fe.name] = (...args) => {
    return this.machine.run(instructions, args);
    };
    });
    }
    }

    const mod = new WamModule(process.argv[2]);
    const add41 = mod.exports.add41;
    console.log(add41(1)); // prints 42