diff --git a/asm.js b/asm.js index e6b2ba3..9882baa 100644 --- a/asm.js +++ b/asm.js @@ -83,7 +83,10 @@ const State = Object.freeze({ MEM_FLAGS: 11, IMPORT_NAME: 12, IMPORT_MOD: 13, - IMPORT_FIELD: 14 + IMPORT_FIELD: 14, + GLOBAL_NAME: 15, + GLOBAL_TYPE: 16, + GLOBAL_INIT: 17, }); const Action = Object.freeze({ @@ -96,6 +99,7 @@ const Action = Object.freeze({ LOCAL: 6, MEM: 7, IMPORT: 8, + GLOBAL: 9, }); const types = { @@ -104,12 +108,14 @@ const types = { }; const opcodes = { - "end": 0x0b, - "local.get": 0x20, - "local.set": 0x21, - "local.tee": 0x22, - "i32.const": 0x41, - "i32.mul": 0x6c, + "end": 0x0b, + "local.get": 0x20, + "local.set": 0x21, + "local.tee": 0x22, + "global.get": 0x23, + "global.set": 0x24, + "i32.const": 0x41, + "i32.mul": 0x6c, }; const mem_flags = { @@ -118,6 +124,10 @@ const mem_flags = { "64": 4, }; +const const_opcodes = { + [types["i32"]]: opcodes["i32.const"], +}; + class Parser { constructor() { this.tokens = []; @@ -131,6 +141,7 @@ class Parser { ".local": State.LOCAL_NAME, ".mem": State.MEM_NAME, ".import": State.IMPORT_NAME, + ".global": State.GLOBAL_NAME, }; this.handlers = { [State.TOP]: (token) => this.token_top(token), @@ -148,7 +159,10 @@ class Parser { [State.IMPORT_NAME]: (token) => this.token_import_name(token), [State.IMPORT_MOD]: (token) => this.token_import_mod(token), [State.IMPORT_FIELD]: (token) => this.token_import_field(token), - }; + [State.GLOBAL_NAME]: (token) => this.token_global_name(token), + [State.GLOBAL_TYPE]: (token) => this.token_global_type(token), + [State.GLOBAL_INIT]: (token) => this.token_global_init(token), + }; this.results = []; this.params = {}; @@ -360,6 +374,57 @@ class Parser { } } + token_global_name(token) { + if (token == LINE_END) { + console.error( + "ERROR: Unexpected end of line in .global: expected name"); + this.state = State.TOP; + } else { + this.global = {}; + this.global_name = token; + this.state = State.GLOBAL_TYPE; + } + } + + token_global_type(token) { + if (token == LINE_END) { + console.error( + "ERROR: Unexpected newline in .global: expected type"); + this.global = undefined; + this.global_name = undefined; + this.state = State.TOP; + } else { + this.global.type = types[token] ?? console.error( + `ERROR: Unexpected token {token} in .global: expected type`); + this.state = State.GLOBAL_INIT; + } + } + + token_global_init(token) { + if (token == LINE_END) { + console.error( + "ERROR: Unexpected newline in .global: expected" + + " initial value"); + this.global = undefined; + this.global_name = undefined; + this.state = State.TOP; + } else { + const value = this.integer(token) ?? console.error( + `ERROR: Unexpected token {token} in .global: expected` + + " initial value"); + const const_opcode = const_opcodes[this.global.type]; + this.global.init = [ const_opcode, value, opcodes["end"] ]; + const action = { + type: Action.GLOBAL, + global: { [this.global_name]: this.global } + }; + this.global = undefined; + this.global_name = undefined; + this.state = State.TOP; + return action; + } + } + mem_action() { const action = { type: Action.MEM, @@ -385,6 +450,7 @@ const Section = Object.freeze({ IMPORT: 0x02, FUNC: 0x03, MEM: 0x05, + GLOBAL: 0x06, EXPORT: 0x07, CODE: 0x0a, }); @@ -409,12 +475,14 @@ export class Assembler { [Action.LOCAL]: (action) => this.action_local(action), [Action.MEM]: (action) => this.action_mem(action), [Action.IMPORT]: (action) => this.action_import(action), + [Action.GLOBAL]: (action) => this.action_global(action), }; this.exports = []; this.funcs = {}; this.mems = {}; this.imports = []; + this.globals = {}; } action_append(action) { @@ -451,7 +519,12 @@ export class Assembler { action_symbol(action) { const func = this.funcs[this.current_func]; const index = this.lookup_param(func, action.symbol) - ?? this.lookup_local(func, action.symbol); + ?? this.lookup_local(func, action.symbol) + ?? this.lookup_global(action.symbol); + if (index == null) { + console.error(`ERROR: Unable to resolve symbol {action.symbol}`); + index = 0; + } func.body.push(index); } @@ -472,6 +545,10 @@ export class Assembler { }) } + action_global(action) { + Object.assign(this.globals, action.global); + } + push(chunk) { const text = this.decoder.decode(chunk, { stream: true }); for (const action of this.parser.handle(text)) @@ -489,6 +566,11 @@ export class Assembler { return index == -1 ? null : index; } + lookup_global(symbol) { + const index = Object.keys(this.globals).indexOf(symbol); + return index == -1 ? null : index; + } + wasm_section_type() { const funcs = Object.values(this.funcs); const contents = funcs.map(({ params, results }) => { @@ -536,6 +618,15 @@ export class Assembler { return [ mems.length ].concat(...contents); } + wasm_section_global() { + const globals = Object.values(this.globals); + if (globals.length == 0) + return null; + const contents = globals.map( + ({ type, init }) => [ type, 1, ...init ]); + return [ globals.length ].concat(...contents); + } + wasm_section_export() { const exports = Object.entries(this.exports); const contents = exports.map(([ name, { kind, index }]) => { @@ -555,14 +646,23 @@ export class Assembler { const contents = funcs.map(({ body, locals }) => { const local_types = Object.values(locals); const local_count = local_types.length; - return [ - body.length + local_count + 3, - local_count, - local_count, - ...local_types, - ...body, - opcodes["end"] - ] + if (local_count == 0) { + return [ + body.length + 2, + 0, + ...body, + opcodes["end"] + ] + } else { + return [ + body.length + local_count + 3, + local_count, + local_count, + ...local_types, + ...body, + opcodes["end"] + ]; + } }); return [ contents.length ].concat(...contents); } @@ -573,6 +673,7 @@ export class Assembler { [ Section.IMPORT, () => this.wasm_section_import() ], [ Section.FUNC, () => this.wasm_section_func() ], [ Section.MEM, () => this.wasm_section_mem() ], + [ Section.GLOBAL, () => this.wasm_section_global() ], [ Section.EXPORT, () => this.wasm_section_export() ], [ Section.CODE, () => this.wasm_section_code() ], ];