diff --git a/asm.js b/asm.js index 11dbc8f..9cdb05e 100644 --- a/asm.js +++ b/asm.js @@ -47,14 +47,18 @@ class Tokenizer { } const State = Object.freeze({ - TOP: 0, - EXPORT: 1, - FUNC: 2, - RESULT: 3, - PARAM_NAME: 4, - PARAM_TYPE: 5, - LOCAL_NAME: 6, - LOCAL_TYPE: 7, + TOP: 0, + EXPORT: 1, + FUNC: 2, + RESULT: 3, + PARAM_NAME: 4, + PARAM_TYPE: 5, + LOCAL_NAME: 6, + LOCAL_TYPE: 7, + MEM_NAME: 8, + MEM_INITIAL: 9, + MEM_MAX: 10, + MEM_FLAGS: 11, }); const Action = Object.freeze({ @@ -65,6 +69,7 @@ const Action = Object.freeze({ PARAM: 4, SYMBOL: 5, LOCAL: 6, + MEM: 7, }); const types = { @@ -81,6 +86,12 @@ const opcodes = { "i32.mul": 0x6c, }; +const mem_flags = { + "max": 1, + "shared": 2, + "64": 4, +}; + class Parser { constructor() { this.tokens = []; @@ -92,6 +103,7 @@ class Parser { ".result": State.RESULT, ".param": State.PARAM_NAME, ".local": State.LOCAL_NAME, + ".mem": State.MEM_NAME, }; this.handlers = { [State.TOP]: (token) => this.token_top(token), @@ -102,6 +114,10 @@ class Parser { [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), + [State.MEM_NAME]: (token) => this.token_mem_name(token), + [State.MEM_INIT]: (token) => this.token_mem_init(token), + [State.MEM_MAX]: (token) => this.token_mem_max(token), + [State.MEM_FLAGS]: (token) => this.token_mem_flags(token), }; this.results = []; @@ -215,6 +231,67 @@ class Parser { } } + token_mem_name(token) { + if (token == LINE_END) { + console.error( + "ERROR: Unexpected newline in .mem: expected name"); + this.state = State.TOP; + } else { + this.mem = { flags: 0 }; + this.mem_name = token; + this.state = State.MEM_INIT; + } + } + + token_mem_init(token) { + if (token == LINE_END) { + console.error( + "ERROR: Unexpected newline in .mem: expected initial size"); + this.mem = undefined; + this.mem_name = undefined; + this.state = State.TOP; + } else { + this.mem.init = this.integer(token) ?? console.error( + `ERROR: Invalid initial size {token} in .mem`); + this.state = State.MEM_MAX; + } + } + + token_mem_max(token) { + if (token == LINE_END) { + return this.mem_action(); + } else { + this.mem.max = this.integer(token) ?? console.error( + `ERROR: Invalid maximum size {token} in .mem`); + this.mem.flags |= mem_flags.max; + this.state = State.MEM_FLAGS; + } + } + + token_mem_flags(token) { + if (token == LINE_END) { + return this.mem_action(); + } else { + for (const flag of token.split(",")) { + this.mem.flags |= mem_flags[flag] ?? console.error( + `ERROR: Invalid flag {flag} in .mem`); + } + this.state = State.TOP; + return this.mem_action(); + } + } + + mem_action() { + const action = { + type: Action.MEM, + mem: { [this.mem_name]: { ...this.mem } } + }; + this.mem = undefined; + this.mem_name = undefined; + this.state = State.TOP; + return action; + } + *handle(src) { let action; for (const token of this.tokenizer.handle(src)) { @@ -227,8 +304,9 @@ class Parser { const Section = Object.freeze({ TYPE: 0x01, FUNC: 0x03, + MEM: 0x05, EXPORT: 0x07, - CODE: 0x0a + CODE: 0x0a, }); export class Assembler { @@ -244,10 +322,12 @@ export class Assembler { [Action.PARAM]: (action) => this.action_param(action), [Action.SYMBOL]: (action) => this.action_symbol(action), [Action.LOCAL]: (action) => this.action_local(action), + [Action.MEM]: (action) => this.action_mem(action), }; this.exports = []; - this.funcs = {} + this.funcs = {}; + this.mems = {}; } action_append(action) { @@ -288,6 +368,10 @@ export class Assembler { func.body.push(index); } + action_mem(action) { + Object.assign(this.mems, action.mem); + } + push(chunk) { const text = this.decoder.decode(chunk, { stream: true }); for (const action of this.parser.handle(text)) @@ -325,6 +409,20 @@ export class Assembler { return [ func_count, ...Array(func_count).keys() ]; } + wasm_section_mem() { + const mems = Object.values(this.mems); + if (mems.length == 0) + return null; + + const contents = mems.map(({ init, max, flags }) => { + if (flags & mem_flags.max) + return [ flags, init, max ]; + else + return [ flags, init ]; + }); + return [ mems.length ].concat(...contents); + } + wasm_section_export() { const exports = Object.entries(this.exports); const contents = exports.map(([ name, { kind, index }]) => { @@ -360,12 +458,13 @@ export class Assembler { const template = [ [ Section.TYPE, () => this.wasm_section_type() ], [ Section.FUNC, () => this.wasm_section_func() ], + [ Section.MEM, () => this.wasm_section_mem() ], [ Section.EXPORT, () => this.wasm_section_export() ], [ Section.CODE, () => this.wasm_section_code() ], ]; const sections = template.map(([ code, generator ]) => { const body = generator(); - return [ code, body.length, ...body ]; + return body == null ? [] : [ code, body.length, ...body ]; }); return new Uint8Array(HEADER.concat(...sections));