Compare commits
11 Commits
5369a0969e
...
902404cb10
| Author | SHA1 | Date | |
|---|---|---|---|
|
902404cb10
|
|||
|
d4718f1106
|
|||
|
33f5a4be06
|
|||
|
e2429b2b03
|
|||
|
2972030d0a
|
|||
|
2c3e5f46da
|
|||
|
93f3dd1f41
|
|||
|
cfa4fa7d4f
|
|||
|
94cee7d258
|
|||
|
092d870a9c
|
|||
|
6db71ee382
|
257
asm.js
257
asm.js
@@ -87,6 +87,14 @@ const State = Object.freeze({
|
|||||||
GLOBAL_NAME: 15,
|
GLOBAL_NAME: 15,
|
||||||
GLOBAL_TYPE: 16,
|
GLOBAL_TYPE: 16,
|
||||||
GLOBAL_INIT: 17,
|
GLOBAL_INIT: 17,
|
||||||
|
AT_MEM: 18,
|
||||||
|
AT_ADDR: 19,
|
||||||
|
BYTE: 20,
|
||||||
|
WORD: 21,
|
||||||
|
UTF8: 22,
|
||||||
|
ALIGN: 23,
|
||||||
|
DEF_NAME: 24,
|
||||||
|
DEF_VALUE: 25,
|
||||||
});
|
});
|
||||||
|
|
||||||
const Action = Object.freeze({
|
const Action = Object.freeze({
|
||||||
@@ -100,6 +108,10 @@ const Action = Object.freeze({
|
|||||||
MEM: 7,
|
MEM: 7,
|
||||||
IMPORT: 8,
|
IMPORT: 8,
|
||||||
GLOBAL: 9,
|
GLOBAL: 9,
|
||||||
|
AT: 10,
|
||||||
|
DATA: 11,
|
||||||
|
ALIGN: 12,
|
||||||
|
DEF: 13,
|
||||||
});
|
});
|
||||||
|
|
||||||
const types = {
|
const types = {
|
||||||
@@ -129,7 +141,8 @@ const const_opcodes = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
class Parser {
|
class Parser {
|
||||||
constructor() {
|
constructor(encoder) {
|
||||||
|
this.encoder = encoder;
|
||||||
this.tokens = [];
|
this.tokens = [];
|
||||||
this.tokenizer = new Tokenizer();
|
this.tokenizer = new Tokenizer();
|
||||||
this.state = State.TOP;
|
this.state = State.TOP;
|
||||||
@@ -142,6 +155,12 @@ class Parser {
|
|||||||
".mem": State.MEM_NAME,
|
".mem": State.MEM_NAME,
|
||||||
".import": State.IMPORT_NAME,
|
".import": State.IMPORT_NAME,
|
||||||
".global": State.GLOBAL_NAME,
|
".global": State.GLOBAL_NAME,
|
||||||
|
".at": State.AT_MEM,
|
||||||
|
".byte": State.BYTE,
|
||||||
|
".word": State.WORD,
|
||||||
|
".utf8": State.UTF8,
|
||||||
|
".align": State.ALIGN,
|
||||||
|
".def": State.DEF_NAME,
|
||||||
};
|
};
|
||||||
this.handlers = {
|
this.handlers = {
|
||||||
[State.TOP]: (token) => this.token_top(token),
|
[State.TOP]: (token) => this.token_top(token),
|
||||||
@@ -162,7 +181,15 @@ class Parser {
|
|||||||
[State.GLOBAL_NAME]: (token) => this.token_global_name(token),
|
[State.GLOBAL_NAME]: (token) => this.token_global_name(token),
|
||||||
[State.GLOBAL_TYPE]: (token) => this.token_global_type(token),
|
[State.GLOBAL_TYPE]: (token) => this.token_global_type(token),
|
||||||
[State.GLOBAL_INIT]: (token) => this.token_global_init(token),
|
[State.GLOBAL_INIT]: (token) => this.token_global_init(token),
|
||||||
};
|
[State.AT_MEM]: (token) => this.token_at_mem(token),
|
||||||
|
[State.AT_ADDR]: (token) => this.token_at_addr(token),
|
||||||
|
[State.BYTE]: (token) => this.token_byte(token),
|
||||||
|
[State.WORD]: (token) => this.token_word(token),
|
||||||
|
[State.UTF8]: (token) => this.token_utf8(token),
|
||||||
|
[State.ALIGN]: (token) => this.token_align(token),
|
||||||
|
[State.DEF_NAME]: (token) => this.token_def_name(token),
|
||||||
|
[State.DEF_VALUE]: (token) => this.token_def_value(token),
|
||||||
|
};
|
||||||
|
|
||||||
this.results = [];
|
this.results = [];
|
||||||
this.params = {};
|
this.params = {};
|
||||||
@@ -296,7 +323,7 @@ class Parser {
|
|||||||
this.state = State.TOP;
|
this.state = State.TOP;
|
||||||
} else {
|
} else {
|
||||||
this.mem.init = this.integer(token) ?? console.error(
|
this.mem.init = this.integer(token) ?? console.error(
|
||||||
`ERROR: Invalid initial size {token} in .mem`);
|
`ERROR: Invalid initial size ${token} in .mem`);
|
||||||
this.state = State.MEM_MAX;
|
this.state = State.MEM_MAX;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -306,7 +333,7 @@ class Parser {
|
|||||||
return this.mem_action();
|
return this.mem_action();
|
||||||
} else {
|
} else {
|
||||||
this.mem.max = this.integer(token) ?? console.error(
|
this.mem.max = this.integer(token) ?? console.error(
|
||||||
`ERROR: Invalid maximum size {token} in .mem`);
|
`ERROR: Invalid maximum size ${token} in .mem`);
|
||||||
this.mem.flags |= mem_flags.max;
|
this.mem.flags |= mem_flags.max;
|
||||||
this.state = State.MEM_FLAGS;
|
this.state = State.MEM_FLAGS;
|
||||||
}
|
}
|
||||||
@@ -318,7 +345,7 @@ class Parser {
|
|||||||
} else {
|
} else {
|
||||||
for (const flag of token.split(",")) {
|
for (const flag of token.split(",")) {
|
||||||
this.mem.flags |= mem_flags[flag] ?? console.error(
|
this.mem.flags |= mem_flags[flag] ?? console.error(
|
||||||
`ERROR: Invalid flag {flag} in .mem`);
|
`ERROR: Invalid flag ${flag} in .mem`);
|
||||||
}
|
}
|
||||||
this.state = State.TOP;
|
this.state = State.TOP;
|
||||||
return this.mem_action();
|
return this.mem_action();
|
||||||
@@ -344,7 +371,7 @@ class Parser {
|
|||||||
this.state = State.TOP;
|
this.state = State.TOP;
|
||||||
} else if (token.string == undefined) {
|
} else if (token.string == undefined) {
|
||||||
console.error(
|
console.error(
|
||||||
`ERROR: Unexpected token {token} in .import: expected`
|
`ERROR: Unexpected token ${token} in .import: expected`
|
||||||
+ " module string");
|
+ " module string");
|
||||||
this.import = undefined;
|
this.import = undefined;
|
||||||
this.state = State.TOP;
|
this.state = State.TOP;
|
||||||
@@ -395,7 +422,8 @@ class Parser {
|
|||||||
this.state = State.TOP;
|
this.state = State.TOP;
|
||||||
} else {
|
} else {
|
||||||
this.global.type = types[token] ?? console.error(
|
this.global.type = types[token] ?? console.error(
|
||||||
`ERROR: Unexpected token {token} in .global: expected type`);
|
`ERROR: Unexpected token ${token} in .global: `
|
||||||
|
+ "expected type");
|
||||||
this.state = State.GLOBAL_INIT;
|
this.state = State.GLOBAL_INIT;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -410,7 +438,7 @@ class Parser {
|
|||||||
this.state = State.TOP;
|
this.state = State.TOP;
|
||||||
} else {
|
} else {
|
||||||
const value = this.integer(token) ?? console.error(
|
const value = this.integer(token) ?? console.error(
|
||||||
`ERROR: Unexpected token {token} in .global: expected`
|
`ERROR: Unexpected token ${token} in .global: expected`
|
||||||
+ " initial value");
|
+ " initial value");
|
||||||
const const_opcode = const_opcodes[this.global.type];
|
const const_opcode = const_opcodes[this.global.type];
|
||||||
this.global.init = [ const_opcode, value, opcodes["end"] ];
|
this.global.init = [ const_opcode, value, opcodes["end"] ];
|
||||||
@@ -425,6 +453,127 @@ class Parser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
token_at_mem(token) {
|
||||||
|
this.at = { mem: token };
|
||||||
|
this.state = State.AT_ADDR;
|
||||||
|
}
|
||||||
|
|
||||||
|
token_at_addr(token) {
|
||||||
|
const value = this.integer(token);
|
||||||
|
if (value == null) {
|
||||||
|
console.error(
|
||||||
|
`ERROR: Unexpected token ${token} in .mem: `
|
||||||
|
+ "expected address");
|
||||||
|
this.at = undefined;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.at.addr = value;
|
||||||
|
const action = { type: Action.AT, at: this.at };
|
||||||
|
this.at = undefined;
|
||||||
|
this.state = State.TOP;
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
|
||||||
|
token_byte(token) {
|
||||||
|
if (token == LINE_END) {
|
||||||
|
this.state = State.TOP;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const action = { type: Action.DATA, size: 1 };
|
||||||
|
const value = this.integer(token);
|
||||||
|
if (value == null) {
|
||||||
|
action.symbol = token;
|
||||||
|
} else {
|
||||||
|
if (value > 0xff)
|
||||||
|
console.error(`WARNING: Value ${token} is truncated`);
|
||||||
|
action.value = [ value & 0xff ];
|
||||||
|
}
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
|
||||||
|
token_word(token) {
|
||||||
|
if (token == LINE_END) {
|
||||||
|
this.state = State.TOP;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const action = { type: Action.DATA, size: 2 };
|
||||||
|
const value = this.integer(token);
|
||||||
|
if (value == null) {
|
||||||
|
action.symbol = token;
|
||||||
|
} else {
|
||||||
|
if (value > 0xffff)
|
||||||
|
console.error(`WARNING: Value ${token} is truncated`);
|
||||||
|
action.value = [ value & 0xff, (value >> 8) & 0xff ];
|
||||||
|
}
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
|
||||||
|
token_utf8(token) {
|
||||||
|
if (token == LINE_END) {
|
||||||
|
this.state = State.TOP;
|
||||||
|
return;
|
||||||
|
} else if (token.string == undefined) {
|
||||||
|
console.error(
|
||||||
|
`ERROR: Unexpected token ${token}, expected string`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const value = this.encoder.encode(token.string);
|
||||||
|
const action = { type: Action.DATA, size: value.length, value };
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
|
||||||
|
token_align(token) {
|
||||||
|
const action = { type: Action.ALIGN };
|
||||||
|
if (token == LINE_END) {
|
||||||
|
action.alignment = 4;
|
||||||
|
} else {
|
||||||
|
action.alignment = this.integer(token);
|
||||||
|
if (action.alignment == null) {
|
||||||
|
console.error(
|
||||||
|
`ERROR: Unexpected token ${token}, expected alignment`);
|
||||||
|
this.state = State.TOP;
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.state = State.TOP
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
|
||||||
|
token_def_name(token) {
|
||||||
|
if (token == LINE_END) {
|
||||||
|
console.error("ERROR: Unexpected end of line, expected name");
|
||||||
|
this.state = State.TOP;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.def_name = token;
|
||||||
|
this.state = State.DEF_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
token_def_value(token) {
|
||||||
|
if (token == LINE_END) {
|
||||||
|
console.error("ERROR: Unexpected end of line, expected value");
|
||||||
|
this.def_name = undefined;
|
||||||
|
this.state = State.TOP;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const value = this.integer(token);
|
||||||
|
if (value == null) {
|
||||||
|
console.error(
|
||||||
|
`ERROR: Unexpected token ${token}, expected value`);
|
||||||
|
this.def_name = undefined;
|
||||||
|
this.state = State.TOP;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const action = {
|
||||||
|
type: Action.DEF,
|
||||||
|
def: { name: this.def_name, value },
|
||||||
|
};
|
||||||
|
this.def_name = undefined;
|
||||||
|
this.state = State.TOP;
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
|
||||||
mem_action() {
|
mem_action() {
|
||||||
const action = {
|
const action = {
|
||||||
type: Action.MEM,
|
type: Action.MEM,
|
||||||
@@ -453,6 +602,7 @@ const Section = Object.freeze({
|
|||||||
GLOBAL: 0x06,
|
GLOBAL: 0x06,
|
||||||
EXPORT: 0x07,
|
EXPORT: 0x07,
|
||||||
CODE: 0x0a,
|
CODE: 0x0a,
|
||||||
|
DATA: 0x0b,
|
||||||
});
|
});
|
||||||
|
|
||||||
const Kind = Object.freeze({
|
const Kind = Object.freeze({
|
||||||
@@ -464,7 +614,7 @@ export class Assembler {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.encoder = new TextEncoder("utf-8");
|
this.encoder = new TextEncoder("utf-8");
|
||||||
this.decoder = new TextDecoder("utf-8");
|
this.decoder = new TextDecoder("utf-8");
|
||||||
this.parser = new Parser();
|
this.parser = new Parser(this.encoder);
|
||||||
this.handlers = {
|
this.handlers = {
|
||||||
[Action.APPEND]: (action) => this.action_append(action),
|
[Action.APPEND]: (action) => this.action_append(action),
|
||||||
[Action.EXPORT]: (action) => this.action_export(action),
|
[Action.EXPORT]: (action) => this.action_export(action),
|
||||||
@@ -476,6 +626,10 @@ export class Assembler {
|
|||||||
[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),
|
[Action.GLOBAL]: (action) => this.action_global(action),
|
||||||
|
[Action.AT]: (action) => this.action_at(action),
|
||||||
|
[Action.DATA]: (action) => this.action_data(action),
|
||||||
|
[Action.ALIGN]: (action) => this.action_align(action),
|
||||||
|
[Action.DEF]: (action) => this.action_def(action),
|
||||||
};
|
};
|
||||||
|
|
||||||
this.exports = [];
|
this.exports = [];
|
||||||
@@ -483,6 +637,9 @@ export class Assembler {
|
|||||||
this.mems = {};
|
this.mems = {};
|
||||||
this.imports = [];
|
this.imports = [];
|
||||||
this.globals = {};
|
this.globals = {};
|
||||||
|
this.pos = { mem: 0, addr: 0 };
|
||||||
|
this.data = [];
|
||||||
|
this.defs = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
action_append(action) {
|
action_append(action) {
|
||||||
@@ -518,14 +675,16 @@ 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 value = this.lookup_param(func, action.symbol)
|
||||||
?? this.lookup_local(func, action.symbol)
|
?? this.lookup_local(func, action.symbol)
|
||||||
?? this.lookup_global(action.symbol);
|
?? this.lookup_global(action.symbol)
|
||||||
if (index == null) {
|
?? this.lookup_def(action.symbol);
|
||||||
console.error(`ERROR: Unable to resolve symbol {action.symbol}`);
|
if (value == null) {
|
||||||
index = 0;
|
console.error(
|
||||||
|
`ERROR: Unable to resolve symbol ${action.symbol}`);
|
||||||
|
value = 0;
|
||||||
}
|
}
|
||||||
func.body.push(index);
|
func.body.push(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
action_mem(action) {
|
action_mem(action) {
|
||||||
@@ -549,6 +708,39 @@ export class Assembler {
|
|||||||
Object.assign(this.globals, action.global);
|
Object.assign(this.globals, action.global);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
action_at(action) {
|
||||||
|
const mem = Object.keys(this.mems).indexOf(action.at.mem);
|
||||||
|
if (mem == -1) {
|
||||||
|
console.error(`ERROR: No memory named ${action.at.mem}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.pos.mem = mem;
|
||||||
|
this.pos.addr = action.at.addr;
|
||||||
|
this.data.push({ loc: { ...this.pos }, data: [] })
|
||||||
|
}
|
||||||
|
|
||||||
|
action_data(action) {
|
||||||
|
const data = this.data.at(-1).data;
|
||||||
|
const value = action.value != null
|
||||||
|
? action.value
|
||||||
|
: this.le_bytes(this.lookup_def(action.symbol), action.size);
|
||||||
|
data.push(...value);
|
||||||
|
this.pos.addr += action.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
action_align(action) {
|
||||||
|
const alignment = action.alignment;
|
||||||
|
const data = this.data.at(-1).data;
|
||||||
|
while (this.pos.addr % alignment != 0) {
|
||||||
|
data.push(0);
|
||||||
|
++this.pos.addr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
action_def(action) {
|
||||||
|
this.defs[action.def.name] = action.def.value;
|
||||||
|
}
|
||||||
|
|
||||||
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))
|
||||||
@@ -562,8 +754,8 @@ export class Assembler {
|
|||||||
|
|
||||||
lookup_local(func, symbol) {
|
lookup_local(func, symbol) {
|
||||||
const param_count = Object.entries(func.params).length;
|
const param_count = Object.entries(func.params).length;
|
||||||
const index = param_count + Object.keys(func.locals).indexOf(symbol);
|
const index = Object.keys(func.locals).indexOf(symbol);
|
||||||
return index == -1 ? null : index;
|
return index == -1 ? null : param_count + index;
|
||||||
}
|
}
|
||||||
|
|
||||||
lookup_global(symbol) {
|
lookup_global(symbol) {
|
||||||
@@ -571,6 +763,21 @@ export class Assembler {
|
|||||||
return index == -1 ? null : index;
|
return index == -1 ? null : index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lookup_def(symbol) {
|
||||||
|
return this.defs[symbol];
|
||||||
|
}
|
||||||
|
|
||||||
|
le_bytes(value, count) {
|
||||||
|
let bytes = []
|
||||||
|
while (value != 0 && bytes.length < count) {
|
||||||
|
bytes.push(value & 0xff);
|
||||||
|
value >>= 8;
|
||||||
|
}
|
||||||
|
while (bytes.length < count)
|
||||||
|
bytes.push(0);
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
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 }) => {
|
||||||
@@ -667,6 +874,21 @@ export class Assembler {
|
|||||||
return [ contents.length ].concat(...contents);
|
return [ contents.length ].concat(...contents);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wasm_section_data() {
|
||||||
|
if (this.data.length == 0) return null;
|
||||||
|
const contents = this.data.map(({ loc, data }) => {
|
||||||
|
return [
|
||||||
|
...(loc.mem == 0 ? [ 0 ] : [ 2, loc.mem ]),
|
||||||
|
opcodes["i32.const"],
|
||||||
|
loc.addr,
|
||||||
|
opcodes["end"],
|
||||||
|
data.length,
|
||||||
|
...data,
|
||||||
|
]
|
||||||
|
});
|
||||||
|
return [ contents.length ].concat(...contents);
|
||||||
|
}
|
||||||
|
|
||||||
wasm() {
|
wasm() {
|
||||||
const template = [
|
const template = [
|
||||||
[ Section.TYPE, () => this.wasm_section_type() ],
|
[ Section.TYPE, () => this.wasm_section_type() ],
|
||||||
@@ -676,6 +898,7 @@ export class Assembler {
|
|||||||
[ Section.GLOBAL, () => this.wasm_section_global() ],
|
[ 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() ],
|
||||||
|
[ Section.DATA, () => this.wasm_section_data() ],
|
||||||
];
|
];
|
||||||
const sections = template.map(([ code, generator ]) => {
|
const sections = template.map(([ code, generator ]) => {
|
||||||
const body = generator();
|
const body = generator();
|
||||||
|
|||||||
Reference in New Issue
Block a user