Compare commits
5 Commits
d3d0801236
...
77f6d57e1b
| Author | SHA1 | Date | |
|---|---|---|---|
|
77f6d57e1b
|
|||
|
510a74aa04
|
|||
|
75600d0568
|
|||
|
6a4877d52c
|
|||
|
554d918640
|
@@ -15,7 +15,7 @@ emulated in JavaScript.
|
|||||||
|
|
||||||
You'll need:
|
You'll need:
|
||||||
|
|
||||||
- [WABT](https://github.com/WebAssembly/wabt)
|
- [WABT](https://github.com/WebAssembly/wabt) (not for long mwahaha)
|
||||||
- [Guile](https://www.gnu.org/software/guile/) (or bring your own HTTP
|
- [Guile](https://www.gnu.org/software/guile/) (or bring your own HTTP
|
||||||
server -- see note below)
|
server -- see note below)
|
||||||
|
|
||||||
|
|||||||
373
asm.js
Normal file
373
asm.js
Normal file
@@ -0,0 +1,373 @@
|
|||||||
|
const HEADER = [
|
||||||
|
0x00, 0x61, 0x73, 0x6d,
|
||||||
|
0x01, 0x00, 0x00, 0x00
|
||||||
|
];
|
||||||
|
|
||||||
|
const LINE_END = "\n"
|
||||||
|
|
||||||
|
class Tokenizer {
|
||||||
|
constructor() {
|
||||||
|
this.delims = new Set([" ", "\r", "\n", "\t"]);
|
||||||
|
this.skips = new Set([" ", "\r", "\t"]);
|
||||||
|
this.comment_start = ";"
|
||||||
|
this.buffer = [];
|
||||||
|
this.comment = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
skip() {
|
||||||
|
const idx = this.buffer.findIndex((cp) => !this.skips.has(cp));
|
||||||
|
this.buffer = idx == -1 ? [] : this.buffer.slice(idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
next() {
|
||||||
|
this.skip();
|
||||||
|
if (this.buffer[0] == LINE_END)
|
||||||
|
return this.buffer.shift();
|
||||||
|
|
||||||
|
const idx = this.buffer.findIndex((cp) => this.delims.has(cp));
|
||||||
|
if (idx != -1) {
|
||||||
|
const token = this.buffer.slice(0, idx).join("");
|
||||||
|
this.buffer = this.buffer.slice(idx);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*handle(src) {
|
||||||
|
this.buffer.push(...src);
|
||||||
|
let token;
|
||||||
|
while (token = this.next()) {
|
||||||
|
if (token == this.comment_start)
|
||||||
|
this.comment = true;
|
||||||
|
else if (this.comment && token == LINE_END)
|
||||||
|
this.comment = false;
|
||||||
|
else if (!this.comment)
|
||||||
|
yield token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const State = Object.freeze({
|
||||||
|
TOP: 0,
|
||||||
|
EXPORT: 1,
|
||||||
|
FUNC: 2,
|
||||||
|
RESULT: 3,
|
||||||
|
PARAM_NAME: 4,
|
||||||
|
PARAM_TYPE: 5,
|
||||||
|
LOCAL_NAME: 6,
|
||||||
|
LOCAL_TYPE: 7,
|
||||||
|
});
|
||||||
|
|
||||||
|
const Action = Object.freeze({
|
||||||
|
APPEND: 0,
|
||||||
|
EXPORT: 1,
|
||||||
|
FUNC: 2,
|
||||||
|
RESULT: 3,
|
||||||
|
PARAM: 4,
|
||||||
|
SYMBOL: 5,
|
||||||
|
LOCAL: 6,
|
||||||
|
});
|
||||||
|
|
||||||
|
const types = {
|
||||||
|
"func": 0x60,
|
||||||
|
"i32": 0x7f,
|
||||||
|
};
|
||||||
|
|
||||||
|
const opcodes = {
|
||||||
|
"end": 0x0b,
|
||||||
|
"local.get": 0x20,
|
||||||
|
"local.set": 0x21,
|
||||||
|
"local.tee": 0x22,
|
||||||
|
"i32.const": 0x41,
|
||||||
|
"i32.mul": 0x6c,
|
||||||
|
};
|
||||||
|
|
||||||
|
class Parser {
|
||||||
|
constructor() {
|
||||||
|
this.tokens = [];
|
||||||
|
this.tokenizer = new Tokenizer();
|
||||||
|
this.state = State.TOP;
|
||||||
|
this.directives = {
|
||||||
|
".export": State.EXPORT,
|
||||||
|
".func": State.FUNC,
|
||||||
|
".result": State.RESULT,
|
||||||
|
".param": State.PARAM_NAME,
|
||||||
|
".local": State.LOCAL_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),
|
||||||
|
};
|
||||||
|
|
||||||
|
this.results = [];
|
||||||
|
this.params = {};
|
||||||
|
this.locals = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
integer(token) {
|
||||||
|
let base;
|
||||||
|
switch (token.slice(-1)) {
|
||||||
|
case "b": base = 2; break;
|
||||||
|
case "o": base = 8; break;
|
||||||
|
case "h": base = 16; break;
|
||||||
|
default: base = 10; break;
|
||||||
|
}
|
||||||
|
const x = parseInt(token, base);
|
||||||
|
return Number.isNaN(x) ? null : x;
|
||||||
|
}
|
||||||
|
|
||||||
|
translate_code(token) {
|
||||||
|
return opcodes[token] ?? this.integer(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
translate_type(token) {
|
||||||
|
return types[token];
|
||||||
|
}
|
||||||
|
|
||||||
|
token_top(token) {
|
||||||
|
if (token == LINE_END)
|
||||||
|
return;
|
||||||
|
let state;
|
||||||
|
if (state = this.directives[token]) {
|
||||||
|
this.state = state;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const code = this.translate_code(token);
|
||||||
|
if (code)
|
||||||
|
return { type: Action.APPEND, code };
|
||||||
|
else
|
||||||
|
return { type: Action.SYMBOL, symbol: token };
|
||||||
|
}
|
||||||
|
|
||||||
|
token_export(token) {
|
||||||
|
this.state = State.TOP;
|
||||||
|
return { type: Action.EXPORT, name: token };
|
||||||
|
}
|
||||||
|
|
||||||
|
token_func(token) {
|
||||||
|
this.state = State.TOP;
|
||||||
|
return { type: Action.FUNC, name: token };
|
||||||
|
}
|
||||||
|
|
||||||
|
token_result(token) {
|
||||||
|
if (token == LINE_END) {
|
||||||
|
const action = { type: Action.RESULT, results: this.results };
|
||||||
|
this.state = State.TOP;
|
||||||
|
this.results = [];
|
||||||
|
return action;
|
||||||
|
} else {
|
||||||
|
this.results.push(this.translate_type(token));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
token_param_name(token) {
|
||||||
|
if (token == LINE_END) {
|
||||||
|
const action = { type: Action.PARAM, params: this.params };
|
||||||
|
this.state = State.TOP;
|
||||||
|
this.params = {};
|
||||||
|
return action;
|
||||||
|
} else {
|
||||||
|
this.current_param = token;
|
||||||
|
this.state = State.PARAM_TYPE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
token_param_type(token) {
|
||||||
|
if (token == LINE_END) {
|
||||||
|
console.error(
|
||||||
|
"ERROR: Unexpected newline in .param: expected type");
|
||||||
|
this.state = State.TOP;
|
||||||
|
this.params = {};
|
||||||
|
} else {
|
||||||
|
this.params[this.current_param] = types[token];
|
||||||
|
this.current_param = undefined;
|
||||||
|
this.state = State.PARAM_NAME;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
token_local_name(token) {
|
||||||
|
if (token == LINE_END) {
|
||||||
|
const action = { type: Action.LOCAL, locals: this.locals };
|
||||||
|
this.state = State.TOP;
|
||||||
|
this.locals = {};
|
||||||
|
return action;
|
||||||
|
} else {
|
||||||
|
this.current_local = token;
|
||||||
|
this.state = State.LOCAL_TYPE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
token_local_type(token) {
|
||||||
|
if (token == LINE_END) {
|
||||||
|
console.error(
|
||||||
|
"ERROR: Unexpected newline in .local: expected type");
|
||||||
|
this.state = State.TOP;
|
||||||
|
this.locals = {};
|
||||||
|
} else {
|
||||||
|
this.locals[this.current_local] = types[token];
|
||||||
|
this.current_local = undefined;
|
||||||
|
this.state = State.LOCAL_NAME;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*handle(src) {
|
||||||
|
let action;
|
||||||
|
for (const token of this.tokenizer.handle(src)) {
|
||||||
|
if (action = this.handlers[this.state](token))
|
||||||
|
yield action;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Section = Object.freeze({
|
||||||
|
TYPE: 0x01,
|
||||||
|
FUNC: 0x03,
|
||||||
|
EXPORT: 0x07,
|
||||||
|
CODE: 0x0a
|
||||||
|
});
|
||||||
|
|
||||||
|
export class Assembler {
|
||||||
|
constructor() {
|
||||||
|
this.encoder = new TextEncoder("utf-8");
|
||||||
|
this.decoder = new TextDecoder("utf-8");
|
||||||
|
this.parser = new Parser();
|
||||||
|
this.handlers = {
|
||||||
|
[Action.APPEND]: (action) => this.action_append(action),
|
||||||
|
[Action.EXPORT]: (action) => this.action_export(action),
|
||||||
|
[Action.FUNC]: (action) => this.action_func(action),
|
||||||
|
[Action.RESULT]: (action) => this.action_result(action),
|
||||||
|
[Action.PARAM]: (action) => this.action_param(action),
|
||||||
|
[Action.SYMBOL]: (action) => this.action_symbol(action),
|
||||||
|
[Action.LOCAL]: (action) => this.action_local(action),
|
||||||
|
};
|
||||||
|
|
||||||
|
this.exports = [];
|
||||||
|
this.funcs = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
action_append(action) {
|
||||||
|
this.funcs[this.current_func].body.push(action.code);
|
||||||
|
}
|
||||||
|
|
||||||
|
action_export(action) {
|
||||||
|
const index = Object.keys(this.funcs).indexOf(action.name);
|
||||||
|
this.exports[action.name] = { kind: 0x00, index };
|
||||||
|
}
|
||||||
|
|
||||||
|
action_func(action) {
|
||||||
|
this.funcs[action.name] = {
|
||||||
|
params: {},
|
||||||
|
results: [],
|
||||||
|
locals: {},
|
||||||
|
body: [],
|
||||||
|
}
|
||||||
|
this.current_func = action.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
action_result(action) {
|
||||||
|
this.funcs[this.current_func].results.push(...action.results);
|
||||||
|
}
|
||||||
|
|
||||||
|
action_param(action) {
|
||||||
|
Object.assign(this.funcs[this.current_func].params, action.params);
|
||||||
|
}
|
||||||
|
|
||||||
|
action_local(action) {
|
||||||
|
Object.assign(this.funcs[this.current_func].locals, action.locals);
|
||||||
|
}
|
||||||
|
|
||||||
|
action_symbol(action) {
|
||||||
|
const func = this.funcs[this.current_func];
|
||||||
|
const index = this.lookup_param(func, action.symbol)
|
||||||
|
?? this.lookup_local(func, action.symbol);
|
||||||
|
func.body.push(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
push(chunk) {
|
||||||
|
const text = this.decoder.decode(chunk, { stream: true });
|
||||||
|
for (const action of this.parser.handle(text))
|
||||||
|
this.handlers[action.type](action);
|
||||||
|
}
|
||||||
|
|
||||||
|
lookup_param(func, symbol) {
|
||||||
|
const index = Object.keys(func.params).indexOf(symbol);
|
||||||
|
return index == -1 ? null : index;
|
||||||
|
}
|
||||||
|
|
||||||
|
lookup_local(func, symbol) {
|
||||||
|
const param_count = Object.entries(func.params).length;
|
||||||
|
const index = param_count + Object.keys(func.locals).indexOf(symbol);
|
||||||
|
return index == -1 ? null : index;
|
||||||
|
}
|
||||||
|
|
||||||
|
wasm_section_type() {
|
||||||
|
const funcs = Object.values(this.funcs);
|
||||||
|
const contents = funcs.map(({ params, results }) => {
|
||||||
|
const param_types = Object.values(params);
|
||||||
|
return [
|
||||||
|
types["func"],
|
||||||
|
param_types.length,
|
||||||
|
...param_types,
|
||||||
|
results.length,
|
||||||
|
...results,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
return [ contents.length ].concat(...contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
wasm_section_func() {
|
||||||
|
const func_count = Object.entries(this.funcs).length;
|
||||||
|
return [ func_count, ...Array(func_count).keys() ];
|
||||||
|
}
|
||||||
|
|
||||||
|
wasm_section_export() {
|
||||||
|
const exports = Object.entries(this.exports);
|
||||||
|
const contents = exports.map(([ name, { kind, index }]) => {
|
||||||
|
const name_utf8 = this.encoder.encode(name);
|
||||||
|
return [
|
||||||
|
name_utf8.length,
|
||||||
|
...name_utf8,
|
||||||
|
kind,
|
||||||
|
index,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
return [ exports.length ].concat(...contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
wasm_section_code() {
|
||||||
|
const funcs = Object.values(this.funcs);
|
||||||
|
const contents = funcs.map(({ body, locals }) => {
|
||||||
|
const local_types = Object.values(locals);
|
||||||
|
const local_count = local_types.length;
|
||||||
|
return [
|
||||||
|
body.length + local_count + 3,
|
||||||
|
local_count,
|
||||||
|
local_count,
|
||||||
|
...local_types,
|
||||||
|
...body,
|
||||||
|
opcodes["end"]
|
||||||
|
]
|
||||||
|
});
|
||||||
|
return [ contents.length ].concat(...contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
wasm() {
|
||||||
|
const template = [
|
||||||
|
[ Section.TYPE, () => this.wasm_section_type() ],
|
||||||
|
[ Section.FUNC, () => this.wasm_section_func() ],
|
||||||
|
[ 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 new Uint8Array(HEADER.concat(...sections));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user