diff --git a/asm.js b/asm.js index a21bb52..39dc49d 100644 --- a/asm.js +++ b/asm.js @@ -78,7 +78,7 @@ const State = Object.freeze({ LOCAL_NAME: 6, LOCAL_TYPE: 7, MEM_NAME: 8, - MEM_INITIAL: 9, + MEM_INIT: 9, MEM_MAX: 10, MEM_FLAGS: 11, IMPORT_NAME: 12, @@ -95,6 +95,8 @@ const State = Object.freeze({ ALIGN: 23, DEF_NAME: 24, DEF_VALUE: 25, + BLOCK_NAME: 26, + BLOCK_TYPE: 27, }); const Action = Object.freeze({ @@ -113,21 +115,32 @@ const Action = Object.freeze({ ALIGN: 12, DEF: 13, LABEL: 14, + ENTER: 16, + EXIT: 17, + ELSE: 18, }); const types = { + "void": 0x40, "func": 0x60, "i32": 0x7f, }; const opcodes = { + "block": 0x02, + "loop": 0x03, + "if": 0x04, + "else": 0x05, "end": 0x0b, + "br": 0x0c, + "br_if": 0x0d, "local.get": 0x20, "local.set": 0x21, "local.tee": 0x22, "global.get": 0x23, "global.set": 0x24, "i32.const": 0x41, + "i32.gt_u": 0x4b, "i32.mul": 0x6c, }; @@ -163,6 +176,7 @@ class Parser { ".align": State.ALIGN, ".def": State.DEF_NAME, }; + this.blocks = new Set(["block", "loop", "if"]); this.handlers = { [State.TOP]: (token) => this.token_top(token), [State.EXPORT]: (token) => this.token_export(token), @@ -190,6 +204,8 @@ class Parser { [State.ALIGN]: (token) => this.token_align(token), [State.DEF_NAME]: (token) => this.token_def_name(token), [State.DEF_VALUE]: (token) => this.token_def_value(token), + [State.BLOCK_NAME]: (token) => this.token_block_name(token), + [State.BLOCK_TYPE]: (token) => this.token_block_type(token), }; this.results = []; @@ -220,18 +236,34 @@ class Parser { token_top(token) { if (token == LINE_END) return; - let state; - if (state = this.directives[token]) { + const state = this.directives[token]; + if (state) { this.state = state; return; } + if (token.endsWith(":")) return { type: Action.LABEL, name: token.slice(0, -1) }; - const code = this.translate_code(token); - if (code) - return { type: Action.APPEND, code }; - else - return { type: Action.SYMBOL, symbol: token }; + + const opcode = opcodes[token]; + + if (this.blocks.has(token)) { + this.state = State.BLOCK_NAME; + this.block = { code: opcode }; + return; + } + if (token == "else") + return { type: Action.ELSE } + if (token == "end") + return { type: Action.EXIT }; + + if (opcode) + return { type: Action.APPEND, opcode }; + const literal = this.integer(token); + if (literal) + return { type: Action.APPEND, literal }; + + return { type: Action.SYMBOL, symbol: token }; } token_export(token) { @@ -574,6 +606,36 @@ class Parser { return action; } + token_block_name(token) { + if (token == LINE_END) { + this.block.type = types["void"]; + const action = { type: Action.ENTER, block: this.block }; + this.state = State.TOP; + this.block = undefined; + return action; + } else { + this.block.label = token; + this.state = State.BLOCK_TYPE; + return; + } + } + + token_block_type(token) { + if (token == LINE_END) { + this.block.type = types["void"]; + } else { + this.block.type = types[token]; + if (this.block.type == undefined) { + console.error( + `ERROR: Unexpected token ${token}, expected type`); + } + } + const action = { type: Action.ENTER, block: this.block }; + this.state = State.TOP; + this.block = undefined; + return action; + } + mem_action() { const action = { type: Action.MEM, @@ -588,8 +650,9 @@ class Parser { *handle(src) { let action; for (const token of this.tokenizer.handle(src)) { - if (action = this.handlers[this.state](token)) + if (action = this.handlers[this.state](token)) { yield action; + } } } } @@ -631,6 +694,9 @@ export class Assembler { [Action.ALIGN]: (action) => this.action_align(action), [Action.DEF]: (action) => this.action_def(action), [Action.LABEL]: (action) => this.action_label(action), + [Action.ENTER]: (action) => this.action_enter(action), + [Action.EXIT]: (action) => this.action_exit(action), + [Action.ELSE]: (action) => this.action_else(action), }; this.exports = []; @@ -641,10 +707,14 @@ export class Assembler { this.pos = { mem: 0, addr: 0 }; this.data = []; this.defs = {}; + this.blocks = []; } action_append(action) { - this.funcs[this.current_func].body.push(action.code); + const code = action.opcode != undefined + ? [ action.opcode ] + : this.leb128(action.literal); + this.funcs[this.current_func].body.push(...code); } action_export(action) { @@ -676,14 +746,14 @@ export class Assembler { action_symbol(action) { const func = this.funcs[this.current_func]; - const value = this.lookup_param(func, action.symbol) + const value = this.lookup_block(action.symbol) + ?? this.lookup_param(func, action.symbol) ?? this.lookup_local(func, action.symbol) ?? this.lookup_global(action.symbol) ?? this.lookup_def(action.symbol); if (value == null) { console.error( `ERROR: Unable to resolve symbol ${action.symbol}`); - value = 0; } func.body.push(value); } @@ -748,12 +818,34 @@ export class Assembler { this.defs[action.name] = this.pos.addr; } + action_enter(action) { + this.funcs[this.current_func].body.push(action.block.code); + this.funcs[this.current_func].body.push(action.block.type); + this.blocks.push(action.block.label); + } + + action_exit(action) { + this.funcs[this.current_func].body.push(opcodes.end); + this.blocks.pop(); + } + + action_else(action) { + this.blocks.pop(); + this.funcs[this.current_func].body.push(opcodes.else); + this.blocks.push(undefined); + } + push(chunk) { const text = this.decoder.decode(chunk, { stream: true }); for (const action of this.parser.handle(text)) this.handlers[action.type](action); } + lookup_block(symbol) { + const index = this.blocks.toReversed().indexOf(symbol); + return index == -1 ? null : index; + } + lookup_param(func, symbol) { const index = Object.keys(func.params).indexOf(symbol); return index == -1 ? null : index; @@ -921,4 +1013,18 @@ export class Assembler { else return [ flags, init ]; } + + leb128(x) { + const orig = x; + const bytes = []; + while (true) { + const b = x & 0x7f; + x >>= 7; + if (x == 0 && (b & 0x40) == 0 || x == -1 && (b & 0x40) != 0) { + bytes.push(b); + return bytes; + } + bytes.push(b | 0x80); + } + } }