diff --git a/asm.js b/asm.js index a6683fd..e6b2ba3 100644 --- a/asm.js +++ b/asm.js @@ -69,18 +69,21 @@ 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, - MEM_NAME: 8, - MEM_INITIAL: 9, - MEM_MAX: 10, - MEM_FLAGS: 11, + 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, + IMPORT_NAME: 12, + IMPORT_MOD: 13, + IMPORT_FIELD: 14 }); const Action = Object.freeze({ @@ -92,6 +95,7 @@ const Action = Object.freeze({ SYMBOL: 5, LOCAL: 6, MEM: 7, + IMPORT: 8, }); const types = { @@ -126,20 +130,24 @@ class Parser { ".param": State.PARAM_NAME, ".local": State.LOCAL_NAME, ".mem": State.MEM_NAME, + ".import": State.IMPORT_NAME, }; this.handlers = { - [State.TOP]: (token) => this.token_top(token), - [State.EXPORT]: (token) => this.token_export(token), - [State.FUNC]: (token) => this.token_func(token), - [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), - [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), + [State.TOP]: (token) => this.token_top(token), + [State.EXPORT]: (token) => this.token_export(token), + [State.FUNC]: (token) => this.token_func(token), + [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), + [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), + [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), }; this.results = []; @@ -303,6 +311,55 @@ class Parser { } } + token_import_name(token) { + if (token == LINE_END) { + console.error( + "ERROR: Unexpected end of line in .import: expected name"); + this.state = State.TOP; + } else { + this.import = { name: token }; + this.state = State.IMPORT_MOD; + } + } + + token_import_mod(token) { + if (token == LINE_END) { + console.error( + "ERROR: Unexpected end of line in .import: expected name"); + this.import = undefined; + this.state = State.TOP; + } else if (token.string == undefined) { + console.error( + `ERROR: Unexpected token {token} in .import: expected` + + " module string"); + this.import = undefined; + this.state = State.TOP; + } else { + this.import.mod = token.string; + this.state = State.IMPORT_FIELD; + } + } + + token_import_field(token) { + if (token == LINE_END) { + console.error( + "ERROR: Unexpected end of line in .import: expected name"); + this.import = undefined; + this.state = State.TOP; + } else if (token.string == undefined) { + console.error( + "ERROR: Unexpected token in .import: expected field string"); + this.import = undefined; + this.state = State.TOP; + } else { + this.import.field = token.string; + const action = { type: Action.IMPORT, import: this.import }; + this.import = undefined; + this.state = State.TOP; + return action; + } + } + mem_action() { const action = { type: Action.MEM, @@ -325,12 +382,18 @@ class Parser { const Section = Object.freeze({ TYPE: 0x01, + IMPORT: 0x02, FUNC: 0x03, MEM: 0x05, EXPORT: 0x07, CODE: 0x0a, }); +const Kind = Object.freeze({ + FUNC: 0x00, + MEM: 0x02, +}); + export class Assembler { constructor() { this.encoder = new TextEncoder("utf-8"); @@ -345,11 +408,13 @@ export class Assembler { [Action.SYMBOL]: (action) => this.action_symbol(action), [Action.LOCAL]: (action) => this.action_local(action), [Action.MEM]: (action) => this.action_mem(action), + [Action.IMPORT]: (action) => this.action_import(action), }; this.exports = []; this.funcs = {}; this.mems = {}; + this.imports = []; } action_append(action) { @@ -358,7 +423,7 @@ export class Assembler { action_export(action) { const index = Object.keys(this.funcs).indexOf(action.name); - this.exports[action.name] = { kind: 0x00, index }; + this.exports[action.name] = { kind: Kind.FUNC, index }; } action_func(action) { @@ -394,6 +459,19 @@ export class Assembler { Object.assign(this.mems, action.mem); } + action_import(action) { + const mem = this.mems[action.import.name]; + mem.imported = true; + this.imports.push({ + mod: action.import.mod, + field: action.import.field, + kind: Kind.MEM, + flags: mem.flags, + init: mem.init, + max: mem.max, + }) + } + push(chunk) { const text = this.decoder.decode(chunk, { stream: true }); for (const action of this.parser.handle(text)) @@ -426,22 +504,35 @@ export class Assembler { return [ contents.length ].concat(...contents); } + wasm_section_import() { + if (this.imports.length == 0) + return null; + const contents = this.imports.map((imp) => { + const mod_utf8 = this.encoder.encode(imp.mod); + const field_utf8 = this.encoder.encode(imp.field); + return [ + mod_utf8.length, + ...mod_utf8, + field_utf8.length, + ...field_utf8, + imp.kind, + ...this.mem_wasm(imp), + ]; + }); + return [ this.imports.length ].concat(...contents); + } + wasm_section_func() { const func_count = Object.entries(this.funcs).length; return [ func_count, ...Array(func_count).keys() ]; } wasm_section_mem() { - const mems = Object.values(this.mems); + const mems = Object.values(this.mems).filter( + ({imported}) => !imported); 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 ]; - }); + const contents = mems.map((mem) => this.mem_wasm(mem)); return [ mems.length ].concat(...contents); } @@ -479,6 +570,7 @@ export class Assembler { wasm() { const template = [ [ Section.TYPE, () => this.wasm_section_type() ], + [ Section.IMPORT, () => this.wasm_section_import() ], [ Section.FUNC, () => this.wasm_section_func() ], [ Section.MEM, () => this.wasm_section_mem() ], [ Section.EXPORT, () => this.wasm_section_export() ], @@ -491,4 +583,11 @@ export class Assembler { return new Uint8Array(HEADER.concat(...sections)); } + + mem_wasm({ flags, init, max }) { + if (flags & mem_flags.max) + return [ flags, init, max ]; + else + return [ flags, init ]; + } }