From 77f6d57e1b183586fafec4010d2f39c9e734ca88 Mon Sep 17 00:00:00 2001 From: Camden Dixie O'Brien Date: Tue, 10 Mar 2026 00:37:05 +0000 Subject: [PATCH] Add support for locals --- asm.js | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 3 deletions(-) diff --git a/asm.js b/asm.js index 6a11618..11dbc8f 100644 --- a/asm.js +++ b/asm.js @@ -53,6 +53,8 @@ const State = Object.freeze({ RESULT: 3, PARAM_NAME: 4, PARAM_TYPE: 5, + LOCAL_NAME: 6, + LOCAL_TYPE: 7, }); const Action = Object.freeze({ @@ -62,6 +64,7 @@ const Action = Object.freeze({ RESULT: 3, PARAM: 4, SYMBOL: 5, + LOCAL: 6, }); const types = { @@ -72,6 +75,8 @@ const types = { const opcodes = { "end": 0x0b, "local.get": 0x20, + "local.set": 0x21, + "local.tee": 0x22, "i32.const": 0x41, "i32.mul": 0x6c, }; @@ -86,6 +91,7 @@ class Parser { ".func": State.FUNC, ".result": State.RESULT, ".param": State.PARAM_NAME, + ".local": State.LOCAL_NAME, }; this.handlers = { [State.TOP]: (token) => this.token_top(token), @@ -94,10 +100,13 @@ class Parser { [State.RESULT]: (token) => this.token_result(token), [State.PARAM_NAME]: (token) => this.token_param_name(token), [State.PARAM_TYPE]: (token) => this.token_param_type(token), + [State.LOCAL_NAME]: (token) => this.token_local_name(token), + [State.LOCAL_TYPE]: (token) => this.token_local_type(token), }; this.results = []; this.params = {}; + this.locals = {}; } integer(token) { @@ -181,6 +190,31 @@ class Parser { } } + token_local_name(token) { + if (token == LINE_END) { + const action = { type: Action.LOCAL, locals: this.locals }; + this.state = State.TOP; + this.locals = {}; + return action; + } else { + this.current_local = token; + this.state = State.LOCAL_TYPE; + } + } + + token_local_type(token) { + if (token == LINE_END) { + console.error( + "ERROR: Unexpected newline in .local: expected type"); + this.state = State.TOP; + this.locals = {}; + } else { + this.locals[this.current_local] = types[token]; + this.current_local = undefined; + this.state = State.LOCAL_NAME; + } + } + *handle(src) { let action; for (const token of this.tokenizer.handle(src)) { @@ -209,6 +243,7 @@ export class Assembler { [Action.RESULT]: (action) => this.action_result(action), [Action.PARAM]: (action) => this.action_param(action), [Action.SYMBOL]: (action) => this.action_symbol(action), + [Action.LOCAL]: (action) => this.action_local(action), }; this.exports = []; @@ -242,9 +277,14 @@ export class Assembler { Object.assign(this.funcs[this.current_func].params, action.params); } + action_local(action) { + Object.assign(this.funcs[this.current_func].locals, action.locals); + } + action_symbol(action) { const func = this.funcs[this.current_func]; - const index = Object.keys(func.params).indexOf(action.symbol); + const index = this.lookup_param(func, action.symbol) + ?? this.lookup_local(func, action.symbol); func.body.push(index); } @@ -254,6 +294,17 @@ export class Assembler { this.handlers[action.type](action); } + lookup_param(func, symbol) { + const index = Object.keys(func.params).indexOf(symbol); + return index == -1 ? null : index; + } + + lookup_local(func, symbol) { + const param_count = Object.entries(func.params).length; + const index = param_count + Object.keys(func.locals).indexOf(symbol); + return index == -1 ? null : index; + } + wasm_section_type() { const funcs = Object.values(this.funcs); const contents = funcs.map(({ params, results }) => { @@ -291,10 +342,13 @@ export class Assembler { wasm_section_code() { const funcs = Object.values(this.funcs); const contents = funcs.map(({ body, locals }) => { - const local_count = Object.entries(locals).length; + const local_types = Object.values(locals); + const local_count = local_types.length; return [ - body.length + 2, + body.length + local_count + 3, local_count, + local_count, + ...local_types, ...body, opcodes["end"] ]