Implement .mem directive

This commit is contained in:
2026-03-10 15:33:41 +00:00
parent 77f6d57e1b
commit 5a3084dd16

121
asm.js
View File

@@ -47,14 +47,18 @@ class Tokenizer {
} }
const State = Object.freeze({ const State = Object.freeze({
TOP: 0, TOP: 0,
EXPORT: 1, EXPORT: 1,
FUNC: 2, FUNC: 2,
RESULT: 3, RESULT: 3,
PARAM_NAME: 4, PARAM_NAME: 4,
PARAM_TYPE: 5, PARAM_TYPE: 5,
LOCAL_NAME: 6, LOCAL_NAME: 6,
LOCAL_TYPE: 7, LOCAL_TYPE: 7,
MEM_NAME: 8,
MEM_INITIAL: 9,
MEM_MAX: 10,
MEM_FLAGS: 11,
}); });
const Action = Object.freeze({ const Action = Object.freeze({
@@ -65,6 +69,7 @@ const Action = Object.freeze({
PARAM: 4, PARAM: 4,
SYMBOL: 5, SYMBOL: 5,
LOCAL: 6, LOCAL: 6,
MEM: 7,
}); });
const types = { const types = {
@@ -81,6 +86,12 @@ const opcodes = {
"i32.mul": 0x6c, "i32.mul": 0x6c,
}; };
const mem_flags = {
"max": 1,
"shared": 2,
"64": 4,
};
class Parser { class Parser {
constructor() { constructor() {
this.tokens = []; this.tokens = [];
@@ -92,6 +103,7 @@ class Parser {
".result": State.RESULT, ".result": State.RESULT,
".param": State.PARAM_NAME, ".param": State.PARAM_NAME,
".local": State.LOCAL_NAME, ".local": State.LOCAL_NAME,
".mem": State.MEM_NAME,
}; };
this.handlers = { this.handlers = {
[State.TOP]: (token) => this.token_top(token), [State.TOP]: (token) => this.token_top(token),
@@ -102,6 +114,10 @@ class Parser {
[State.PARAM_TYPE]: (token) => this.token_param_type(token), [State.PARAM_TYPE]: (token) => this.token_param_type(token),
[State.LOCAL_NAME]: (token) => this.token_local_name(token), [State.LOCAL_NAME]: (token) => this.token_local_name(token),
[State.LOCAL_TYPE]: (token) => this.token_local_type(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 = []; 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) { *handle(src) {
let action; let action;
for (const token of this.tokenizer.handle(src)) { for (const token of this.tokenizer.handle(src)) {
@@ -227,8 +304,9 @@ class Parser {
const Section = Object.freeze({ const Section = Object.freeze({
TYPE: 0x01, TYPE: 0x01,
FUNC: 0x03, FUNC: 0x03,
MEM: 0x05,
EXPORT: 0x07, EXPORT: 0x07,
CODE: 0x0a CODE: 0x0a,
}); });
export class Assembler { export class Assembler {
@@ -244,10 +322,12 @@ export class Assembler {
[Action.PARAM]: (action) => this.action_param(action), [Action.PARAM]: (action) => this.action_param(action),
[Action.SYMBOL]: (action) => this.action_symbol(action), [Action.SYMBOL]: (action) => this.action_symbol(action),
[Action.LOCAL]: (action) => this.action_local(action), [Action.LOCAL]: (action) => this.action_local(action),
[Action.MEM]: (action) => this.action_mem(action),
}; };
this.exports = []; this.exports = [];
this.funcs = {} this.funcs = {};
this.mems = {};
} }
action_append(action) { action_append(action) {
@@ -288,6 +368,10 @@ export class Assembler {
func.body.push(index); func.body.push(index);
} }
action_mem(action) {
Object.assign(this.mems, action.mem);
}
push(chunk) { push(chunk) {
const text = this.decoder.decode(chunk, { stream: true }); const text = this.decoder.decode(chunk, { stream: true });
for (const action of this.parser.handle(text)) for (const action of this.parser.handle(text))
@@ -325,6 +409,20 @@ export class Assembler {
return [ func_count, ...Array(func_count).keys() ]; 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() { wasm_section_export() {
const exports = Object.entries(this.exports); const exports = Object.entries(this.exports);
const contents = exports.map(([ name, { kind, index }]) => { const contents = exports.map(([ name, { kind, index }]) => {
@@ -360,12 +458,13 @@ export class Assembler {
const template = [ const template = [
[ Section.TYPE, () => this.wasm_section_type() ], [ Section.TYPE, () => this.wasm_section_type() ],
[ Section.FUNC, () => this.wasm_section_func() ], [ Section.FUNC, () => this.wasm_section_func() ],
[ Section.MEM, () => this.wasm_section_mem() ],
[ Section.EXPORT, () => this.wasm_section_export() ], [ Section.EXPORT, () => this.wasm_section_export() ],
[ Section.CODE, () => this.wasm_section_code() ], [ Section.CODE, () => this.wasm_section_code() ],
]; ];
const sections = template.map(([ code, generator ]) => { const sections = template.map(([ code, generator ]) => {
const body = generator(); const body = generator();
return [ code, body.length, ...body ]; return body == null ? [] : [ code, body.length, ...body ];
}); });
return new Uint8Array(HEADER.concat(...sections)); return new Uint8Array(HEADER.concat(...sections));