Assemble kernel on the client #1

Merged
cdo merged 72 commits from client-side-assembler into main 2026-03-18 15:21:33 +00:00
Showing only changes of commit 118e6af896 - Show all commits

107
asm.js
View File

@@ -83,7 +83,10 @@ const State = Object.freeze({
MEM_FLAGS: 11, MEM_FLAGS: 11,
IMPORT_NAME: 12, IMPORT_NAME: 12,
IMPORT_MOD: 13, IMPORT_MOD: 13,
IMPORT_FIELD: 14 IMPORT_FIELD: 14,
GLOBAL_NAME: 15,
GLOBAL_TYPE: 16,
GLOBAL_INIT: 17,
}); });
const Action = Object.freeze({ const Action = Object.freeze({
@@ -96,6 +99,7 @@ const Action = Object.freeze({
LOCAL: 6, LOCAL: 6,
MEM: 7, MEM: 7,
IMPORT: 8, IMPORT: 8,
GLOBAL: 9,
}); });
const types = { const types = {
@@ -108,6 +112,8 @@ const opcodes = {
"local.get": 0x20, "local.get": 0x20,
"local.set": 0x21, "local.set": 0x21,
"local.tee": 0x22, "local.tee": 0x22,
"global.get": 0x23,
"global.set": 0x24,
"i32.const": 0x41, "i32.const": 0x41,
"i32.mul": 0x6c, "i32.mul": 0x6c,
}; };
@@ -118,6 +124,10 @@ const mem_flags = {
"64": 4, "64": 4,
}; };
const const_opcodes = {
[types["i32"]]: opcodes["i32.const"],
};
class Parser { class Parser {
constructor() { constructor() {
this.tokens = []; this.tokens = [];
@@ -131,6 +141,7 @@ class Parser {
".local": State.LOCAL_NAME, ".local": State.LOCAL_NAME,
".mem": State.MEM_NAME, ".mem": State.MEM_NAME,
".import": State.IMPORT_NAME, ".import": State.IMPORT_NAME,
".global": State.GLOBAL_NAME,
}; };
this.handlers = { this.handlers = {
[State.TOP]: (token) => this.token_top(token), [State.TOP]: (token) => this.token_top(token),
@@ -148,6 +159,9 @@ class Parser {
[State.IMPORT_NAME]: (token) => this.token_import_name(token), [State.IMPORT_NAME]: (token) => this.token_import_name(token),
[State.IMPORT_MOD]: (token) => this.token_import_mod(token), [State.IMPORT_MOD]: (token) => this.token_import_mod(token),
[State.IMPORT_FIELD]: (token) => this.token_import_field(token), [State.IMPORT_FIELD]: (token) => this.token_import_field(token),
[State.GLOBAL_NAME]: (token) => this.token_global_name(token),
[State.GLOBAL_TYPE]: (token) => this.token_global_type(token),
[State.GLOBAL_INIT]: (token) => this.token_global_init(token),
}; };
this.results = []; this.results = [];
@@ -360,6 +374,57 @@ class Parser {
} }
} }
token_global_name(token) {
if (token == LINE_END) {
console.error(
"ERROR: Unexpected end of line in .global: expected name");
this.state = State.TOP;
} else {
this.global = {};
this.global_name = token;
this.state = State.GLOBAL_TYPE;
}
}
token_global_type(token) {
if (token == LINE_END) {
console.error(
"ERROR: Unexpected newline in .global: expected type");
this.global = undefined;
this.global_name = undefined;
this.state = State.TOP;
} else {
this.global.type = types[token] ?? console.error(
`ERROR: Unexpected token {token} in .global: expected type`);
this.state = State.GLOBAL_INIT;
}
}
token_global_init(token) {
if (token == LINE_END) {
console.error(
"ERROR: Unexpected newline in .global: expected"
+ " initial value");
this.global = undefined;
this.global_name = undefined;
this.state = State.TOP;
} else {
const value = this.integer(token) ?? console.error(
`ERROR: Unexpected token {token} in .global: expected`
+ " initial value");
const const_opcode = const_opcodes[this.global.type];
this.global.init = [ const_opcode, value, opcodes["end"] ];
const action = {
type: Action.GLOBAL,
global: { [this.global_name]: this.global }
};
this.global = undefined;
this.global_name = undefined;
this.state = State.TOP;
return action;
}
}
mem_action() { mem_action() {
const action = { const action = {
type: Action.MEM, type: Action.MEM,
@@ -385,6 +450,7 @@ const Section = Object.freeze({
IMPORT: 0x02, IMPORT: 0x02,
FUNC: 0x03, FUNC: 0x03,
MEM: 0x05, MEM: 0x05,
GLOBAL: 0x06,
EXPORT: 0x07, EXPORT: 0x07,
CODE: 0x0a, CODE: 0x0a,
}); });
@@ -409,12 +475,14 @@ export class Assembler {
[Action.LOCAL]: (action) => this.action_local(action), [Action.LOCAL]: (action) => this.action_local(action),
[Action.MEM]: (action) => this.action_mem(action), [Action.MEM]: (action) => this.action_mem(action),
[Action.IMPORT]: (action) => this.action_import(action), [Action.IMPORT]: (action) => this.action_import(action),
[Action.GLOBAL]: (action) => this.action_global(action),
}; };
this.exports = []; this.exports = [];
this.funcs = {}; this.funcs = {};
this.mems = {}; this.mems = {};
this.imports = []; this.imports = [];
this.globals = {};
} }
action_append(action) { action_append(action) {
@@ -451,7 +519,12 @@ export class Assembler {
action_symbol(action) { action_symbol(action) {
const func = this.funcs[this.current_func]; const func = this.funcs[this.current_func];
const index = this.lookup_param(func, action.symbol) const index = this.lookup_param(func, action.symbol)
?? this.lookup_local(func, action.symbol); ?? this.lookup_local(func, action.symbol)
?? this.lookup_global(action.symbol);
if (index == null) {
console.error(`ERROR: Unable to resolve symbol {action.symbol}`);
index = 0;
}
func.body.push(index); func.body.push(index);
} }
@@ -472,6 +545,10 @@ export class Assembler {
}) })
} }
action_global(action) {
Object.assign(this.globals, action.global);
}
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))
@@ -489,6 +566,11 @@ export class Assembler {
return index == -1 ? null : index; return index == -1 ? null : index;
} }
lookup_global(symbol) {
const index = Object.keys(this.globals).indexOf(symbol);
return index == -1 ? null : index;
}
wasm_section_type() { wasm_section_type() {
const funcs = Object.values(this.funcs); const funcs = Object.values(this.funcs);
const contents = funcs.map(({ params, results }) => { const contents = funcs.map(({ params, results }) => {
@@ -536,6 +618,15 @@ export class Assembler {
return [ mems.length ].concat(...contents); return [ mems.length ].concat(...contents);
} }
wasm_section_global() {
const globals = Object.values(this.globals);
if (globals.length == 0)
return null;
const contents = globals.map(
({ type, init }) => [ type, 1, ...init ]);
return [ globals.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 }]) => {
@@ -555,6 +646,14 @@ export class Assembler {
const contents = funcs.map(({ body, locals }) => { const contents = funcs.map(({ body, locals }) => {
const local_types = Object.values(locals); const local_types = Object.values(locals);
const local_count = local_types.length; const local_count = local_types.length;
if (local_count == 0) {
return [
body.length + 2,
0,
...body,
opcodes["end"]
]
} else {
return [ return [
body.length + local_count + 3, body.length + local_count + 3,
local_count, local_count,
@@ -562,7 +661,8 @@ export class Assembler {
...local_types, ...local_types,
...body, ...body,
opcodes["end"] opcodes["end"]
] ];
}
}); });
return [ contents.length ].concat(...contents); return [ contents.length ].concat(...contents);
} }
@@ -573,6 +673,7 @@ export class Assembler {
[ Section.IMPORT, () => this.wasm_section_import() ], [ Section.IMPORT, () => this.wasm_section_import() ],
[ Section.FUNC, () => this.wasm_section_func() ], [ Section.FUNC, () => this.wasm_section_func() ],
[ Section.MEM, () => this.wasm_section_mem() ], [ Section.MEM, () => this.wasm_section_mem() ],
[ Section.GLOBAL, () => this.wasm_section_global() ],
[ Section.EXPORT, () => this.wasm_section_export() ], [ Section.EXPORT, () => this.wasm_section_export() ],
[ Section.CODE, () => this.wasm_section_code() ], [ Section.CODE, () => this.wasm_section_code() ],
]; ];