From 1452ffe6155f47347d4cb8661002ddc8513455df Mon Sep 17 00:00:00 2001 From: Camden Dixie O'Brien Date: Sun, 15 Mar 2026 12:14:35 +0000 Subject: [PATCH] Implement .table and .elem --- asm.js | 145 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 144 insertions(+), 1 deletion(-) diff --git a/asm.js b/asm.js index 5036c66..5f4a556 100644 --- a/asm.js +++ b/asm.js @@ -100,6 +100,10 @@ const State = Object.freeze({ TYPE_NAME: 28, TYPE_PARAM: 29, TYPE_RESULT: 30, + TABLE_NAME: 31, + TABLE_SIZE: 32, + ELEM_TABLE: 33, + ELEM_ELEM: 34, }); const Action = Object.freeze({ @@ -122,6 +126,8 @@ const Action = Object.freeze({ EXIT: 17, ELSE: 18, TYPE: 19, + TABLE: 20, + ELEM: 21, }); const types = { @@ -214,6 +220,8 @@ class Parser { ".align": State.ALIGN, ".def": State.DEF_NAME, ".type": State.TYPE_NAME, + ".table": State.TABLE_NAME, + ".elem": State.ELEM_TABLE, }; this.blocks = new Set(["block", "loop", "if"]); this.handlers = { @@ -248,6 +256,10 @@ class Parser { [State.TYPE_NAME]: (token) => this.token_type_name(token), [State.TYPE_PARAM]: (token) => this.token_type_param(token), [State.TYPE_RESULT]: (token) => this.token_type_result(token), + [State.TABLE_NAME]: (token) => this.token_table_name(token), + [State.TABLE_SIZE]: (token) => this.token_table_size(token), + [State.ELEM_TABLE]: (token) => this.token_elem_table(token), + [State.ELEM_ELEM]: (token) => this.token_elem_elem(token), }; this.results = []; @@ -740,6 +752,73 @@ class Parser { this.type.results.push(type); } + token_table_name(token) { + if (token == LINE_END) { + console.error( + "ERROR: Unexpected end of line in .table, expected name"); + this.state = State.TOP; + return; + } + + this.table = { name: token }; + this.state = State.TABLE_SIZE; + } + + token_table_size(token) { + if (token == LINE_END) { + console.error( + "ERROR: Unexpected end of line in .table, expected size"); + this.table = undefined; + this.state = State.TOP; + return; + } + + const size = this.integer(token); + if (size == null) { + console.error( + `ERROR: Unexpected token ${token} in .table, expected size`); + this.table = undefined; + this.state = State.TOP; + return; + } + + this.table.size = size; + const action = { type: Action.TABLE, table: this.table }; + this.table = undefined; + this.state = State.TOP; + return action; + } + + token_elem_table(token) { + if (token == LINE_END) { + console.error( + "ERROR: Unexpected end of line in .elem, expected " + + "table name"); + this.state = State.TOP; + return; + } + + this.elem = { table: token }; + this.state = State.ELEM_ELEM; + } + + token_elem_elem(token) { + if (token == LINE_END) { + console.error( + "ERROR: Unexpected end of line in .elem, expected " + + "element name"); + this.state = State.TOP; + this.elem = undefined; + return; + } + + this.elem.elem = token; + const action = { type: Action.ELEM, elem: this.elem }; + this.elem = undefined + this.state = State.TOP; + return action; + } + mem_action() { const action = { type: Action.MEM, @@ -771,9 +850,11 @@ const Section = Object.freeze({ TYPE: 0x01, IMPORT: 0x02, FUNC: 0x03, + TABLE: 0x04, MEM: 0x05, GLOBAL: 0x06, EXPORT: 0x07, + ELEM: 0x09, CODE: 0x0a, DATA: 0x0b, }); @@ -808,6 +889,8 @@ export class Assembler { [Action.EXIT]: (action) => this.action_exit(action), [Action.ELSE]: (action) => this.action_else(action), [Action.TYPE]: (action) => this.action_type(action), + [Action.TABLE]: (action) => this.action_table(action), + [Action.ELEM]: (action) => this.action_elem(action), }; this.exports = []; @@ -821,6 +904,7 @@ export class Assembler { this.blocks = []; this.types = []; this.type_bindings = {}; + this.tables = {}; } action_append(action) { @@ -959,6 +1043,23 @@ export class Assembler { this.type_bindings[action.the_type.name] = index; } + action_table(action) { + this.tables[action.table.name] = { + size: action.table.size, + elems: [], + }; + } + + action_elem(action) { + const table = this.tables[action.elem.table]; + const index = Object.keys(this.funcs).indexOf(action.elem.elem); + if (index == -1) { + console.error(`ERROR: ${action.elem.elem}: no such function`); + return; + } + table.elems.push(index); + } + push(chunk) { const text = this.decoder.decode(chunk, { stream: true }); for (const action of this.parser.handle(text)) { @@ -1009,7 +1110,6 @@ export class Assembler { } leb128(x) { - const orig = x; const bytes = []; while (true) { const b = x & 0x7f; @@ -1022,6 +1122,18 @@ export class Assembler { } } + uleb128(x) { + const bytes = []; + do { + const b = x & 0x7f; + x >>= 7; + if (x != 0) + b |= 0x80; + bytes.push(b); + } while (x != 0); + return bytes; + } + wasm_section_type() { if (this.types.length == 0) return null; return [ this.types.length ].concat(...this.types); @@ -1052,6 +1164,17 @@ export class Assembler { return [ count, ...types ]; } + wasm_section_table() { + const sizes = Object.values(this.tables).map(({size}) => size); + if (sizes.length == 0) return null; + const contents = sizes.map((size) => [ + types.funcref, + 0x00, + this.uleb128(size), + ]); + return [ sizes.length, contents ].flat(Infinity) + } + wasm_section_mem() { const mems = Object.values(this.mems).filter( ({imported}) => !imported); @@ -1085,6 +1208,24 @@ export class Assembler { return [ exports.length ].concat(...contents); } + wasm_section_elem() { + const table_elems = Object.values(this.tables).map( + ({elems}) => elems); + const count = table_elems.flat().length; + if (count == 0) return null; + const contents = table_elems.flatMap((elems, table) => + elems.flatMap((fn, index) => [ + table == 0 ? 0 : [ 2, table ], + opcodes["i32.const"], + index, + opcodes["end"], + 1, + fn, + ]) + ); + return [ count, contents ].flat(); + } + wasm_section_code() { const funcs = Object.values(this.funcs); if (funcs.length == 0) return null; @@ -1134,9 +1275,11 @@ export class Assembler { [ Section.TYPE, () => this.wasm_section_type() ], [ Section.IMPORT, () => this.wasm_section_import() ], [ Section.FUNC, () => this.wasm_section_func() ], + [ Section.TABLE, () => this.wasm_section_table() ], [ Section.MEM, () => this.wasm_section_mem() ], [ Section.GLOBAL, () => this.wasm_section_global() ], [ Section.EXPORT, () => this.wasm_section_export() ], + [ Section.ELEM, () => this.wasm_section_elem() ], [ Section.CODE, () => this.wasm_section_code() ], [ Section.DATA, () => this.wasm_section_data() ], ];