// This module converts the Wasm definitions to the WAT // WebAssembly text format for easier debugging and inspection. import chalk from "chalk"; import { Blocktype, Data, DatamodeActive, Elem, Export, Func, FuncType, Global, GlobalType, Import, Instr, Limits, Mem, MemArg, Module, Start, Table, TableType, ValType, } from "./defs"; const identity = (s: string) => s; class Formatter { print: (chunk: string) => void; indentation: number; wordsInSexpr: number[]; freshLinebreak: boolean; color: boolean; constructor(print: (chunk: string) => void, color = true) { this.print = print; this.indentation = 0; this.wordsInSexpr = []; this.freshLinebreak = false; this.color = color; } linebreak() { this.print("\n"); this.print(" ".repeat(this.indentation)); this.freshLinebreak = true; } breakIndent() { this.indentation++; this.linebreak(); } breakDedent() { this.indentation--; if (this.indentation < 0) { throw new Error( "Cannot dedent from 0 indents, there are more dedents than indents" ); } this.linebreak(); } sexpr(f: () => void) { this.startSexpr(); f(); this.endSexpr(); } keyword(word: string) { this.word(word, chalk.blue); } type(word: string | number) { this.word(word, chalk.green); } word(word: string | number, color: (s: string) => string = identity) { const last = this.wordsInSexpr.length - 1; if (this.wordsInSexpr[last] > 0 && !this.freshLinebreak) { // The first word hugs the left parenthesis. this.print(" "); } this.freshLinebreak = false; if (this.color) { this.print(color(String(word))); } else { this.print(String(word)); } this.wordsInSexpr[last]++; } startSexpr() { this.word("("); this.wordsInSexpr.push(0); } endSexpr() { this.print(")"); this.freshLinebreak = false; this.wordsInSexpr.pop(); } } export function writeModuleWatToString(module: Module, color = true): string { const parts: string[] = []; const writer = (s: string) => parts.push(s); printModule(module, new Formatter(writer, color)); return parts.join(""); } export function writeModuleWat(module: Module, f: Formatter) { printModule(module, f); } // base function printString(s: string, f: Formatter) { // TODO: escaping f.word(`"${s}"`); } function printBinaryString(buf: Uint8Array, f: Formatter) { f.word(`"${buf.toString()}"`); } function printId(id: string | undefined, f: Formatter) { if (id) { f.word(`$${id}`); } } // types function printValType(type: ValType, f: Formatter) { f.type(type); } function printFuncType(type: FuncType, f: Formatter) { f.sexpr(() => { f.keyword("func"); f.sexpr(() => { f.keyword("param"); type.params.forEach((param) => printValType(param, f)); }); f.sexpr(() => { f.keyword("result"); type.returns.forEach((type) => printValType(type, f)); }); }); } function printLimits(limits: Limits, f: Formatter) { f.word(limits.min); f.word(limits.max); } function printTableType(type: TableType, f: Formatter) { printLimits(type.limits, f); printValType(type.reftype, f); } function printGlobalType(type: GlobalType, f: Formatter) { if (type.mut === "const") { printValType(type.type, f); } else { f.sexpr(() => { f.keyword("mut"); printValType(type.type, f); }); } } // instrs function printBlockType(type: Blocktype, f: Formatter) { f.sexpr(() => { f.keyword("type"); if (type.kind === "typeidx") { f.type(type.idx); } else if (type.type !== undefined) { printValType(type.type, f); } }); } function printMemarg(arg: MemArg, f: Formatter) { if (arg.align !== undefined) { f.word(`align=${arg.align}`); } if (arg.offset /*0->false*/) { `offset=${arg.offset}`; } } /** * Print a list of instructions with one extra indentation. * Start: indented start of first instr * End: start of next line */ function printInstrBlock(instrs: Instr[], f: Formatter) { instrs.forEach((nested, i) => { printInstr(nested, f); if (i !== instrs.length - 1) { f.linebreak(); } }); f.breakDedent(); } function printInstr(instr: Instr, f: Formatter) { switch (instr.kind) { case "block": case "loop": f.word(instr.kind); printBlockType(instr.type, f); f.breakIndent(); printInstrBlock(instr.instrs, f); f.word("end"); break; case "if": if (instr.else.length === 0) { f.word(instr.kind); printBlockType(instr.type, f); f.breakIndent(); printInstrBlock(instr.then, f); f.word("end"); } else { f.word(instr.kind); printBlockType(instr.type, f); f.breakIndent(); printInstrBlock(instr.then, f); f.word("else"); f.breakIndent(); printInstrBlock(instr.else, f); f.word("end"); } break; case "unreachable": case "nop": case "return": case "ref.is_null": case "drop": case "memory.size": case "memory.grow": case "memory.fill": case "memory.copy": // numeric i32 case "i32.clz": case "i32.ctz": case "i32.popcnt": case "i32.add": case "i32.sub": case "i32.mul": case "i32.div_s": case "i32.div_u": case "i32.rem_s": case "i32.rem_u": case "i32.and": case "i32.or": case "i32.xor": case "i32.shl": case "i32.shr_s": case "i32.shr_u": case "i32.rotl": case "i32.rotr": // numeric i64 case "i64.clz": case "i64.ctz": case "i64.popcnt": case "i64.add": case "i64.sub": case "i64.mul": case "i64.div_s": case "i64.div_u": case "i64.rem_s": case "i64.rem_u": case "i64.and": case "i64.or": case "i64.xor": case "i64.shl": case "i64.shr_s": case "i64.shr_u": case "i64.rotl": case "i64.rotr": // numeric f32 case "f32.abs": case "f32.neg": case "f32.ceil": case "f32.floor": case "f32.trunc": case "f32.nearest": case "f32.sqrt": case "f32.add": case "f32.sub": case "f32.mul": case "f32.div": case "f32.min": case "f32.max": case "f32.copysign": // numeric f64 case "f64.abs": case "f64.neg": case "f64.ceil": case "f64.floor": case "f64.trunc": case "f64.nearest": case "f64.sqrt": case "f64.add": case "f64.sub": case "f64.mul": case "f64.div": case "f64.min": case "f64.max": case "f64.copysign": // more numeric i32 case "i32.eqz": case "i32.eq": case "i32.ne": case "i32.lt_s": case "i32.lt_u": case "i32.gt_s": case "i32.gt_u": case "i32.le_s": case "i32.le_u": case "i32.ge_s": case "i32.ge_u": // more numeric i64 case "i64.eqz": case "i64.eq": case "i64.ne": case "i64.lt_s": case "i64.lt_u": case "i64.gt_s": case "i64.gt_u": case "i64.le_s": case "i64.le_u": case "i64.ge_s": case "i64.ge_u": // more numeric f32 case "f32.eq": case "f32.ne": case "f32.lt": case "f32.gt": case "f32.le": case "f32.ge": // more numeric f64 case "f64.eq": case "f64.ne": case "f64.lt": case "f64.gt": case "f64.le": case "f64.ge": // more numeric case "i32.wrap_i64": case "i32.trunc_f32_s": case "i32.trunc_f32_u": case "i32.trunc_f64_u": case "i32.trunc_f64_s": case "i32.trunc_sat_f32_s": case "i32.trunc_sat_f32_u": case "i32.trunc_sat_f64_u": case "i32.trunc_sat_f64_s": case "i64.extend_i32_s": case "i64.extend_i32_u": case "i64.trunc_f32_s": case "i64.trunc_f32_u": case "i64.trunc_f64_u": case "i64.trunc_f64_s": case "i64.trunc_sat_f32_s": case "i64.trunc_sat_f32_u": case "i64.trunc_sat_f64_u": case "i64.trunc_sat_f64_s": case "f32.convert_i32_s": case "f32.convert_i32_u": case "f32.convert_i64_s": case "f32.convert_i64_u": case "f32.demote_f64": case "f64.convert_i32_s": case "f64.convert_i32_u": case "f64.convert_i64_s": case "f64.convert_i64_u": case "f64.promote_f32": case "i32.reinterpret_f32": case "i64.reinterpret_f64": case "f32.reinterpret_i32": case "f64.reinterpret_i64": // int extend case "i32.extend8_s": case "i32.extend16_s": case "i64.extend8_s": case "i64.extend16_s": case "i64.extend32_s": f.word(instr.kind); break; case "br": case "br_if": f.word(instr.kind); f.word(instr.label); break; case "br_table": f.word(instr.kind); instr.labels.forEach((label) => f.word(label)); f.word(instr.label); break; case "call": f.word(instr.kind); f.word(instr.func); break; case "call_indirect": f.word(instr.kind); f.word(instr.table); f.word(instr.type); break; case "i32.const": case "i64.const": case "f32.const": case "f64.const": case "ref.null": case "ref.func": case "local.get": case "local.set": case "local.tee": case "global.get": case "global.set": case "table.get": case "table.set": case "table.size": case "table.grow": case "table.fill": case "elem.drop": case "memory.init": case "data.drop": f.word(instr.kind); f.word(instr.imm); break; case "select": f.word(instr.kind); instr.type?.forEach((type) => printValType(type, f)); break; case "table.copy": case "table.init": f.word(instr.kind); f.word(instr.imm1); f.word(instr.imm2); break; case "i32.load": case "i64.load": case "f32.load": case "f64.load": case "i32.load8_s": case "i32.load8_u": case "i32.load16_s": case "i32.load16_u": case "i64.load8_s": case "i64.load8_u": case "i64.load16_s": case "i64.load16_u": case "i64.load32_s": case "i64.load32_u": case "i32.store": case "i64.store": case "f32.store": case "f64.store": case "i32.store8": case "i32.store16": case "i64.store8": case "i64.store16": case "i64.store32": case "v128.load": case "v128.store": f.word(instr.kind); printMemarg(instr.imm, f); break; } } // modules function printType(type: FuncType, f: Formatter) { f.sexpr(() => { f.keyword("type"); printFuncType(type, f); }); } function printImport(import_: Import, f: Formatter) { f.sexpr(() => { f.keyword("import"); printString(import_.module, f); printString(import_.name, f); const desc = import_.desc; f.sexpr(() => { f.word(desc.kind); switch (desc.kind) { case "func": f.sexpr(() => { f.keyword("type"); f.type(desc.type); }); break; case "table": printTableType(desc.type, f); break; case "memory": printLimits(desc.type, f); break; case "global": printGlobalType(desc.type, f); break; } }); }); } function printFunction(func: Func, f: Formatter) { f.sexpr(() => { f.keyword("func"); printId(func._name, f); f.sexpr(() => { f.keyword("type"); f.type(func.type); }); if (func.locals.length > 0 || func.body.length > 0) { f.breakIndent(); } if (func.locals.length > 0) { f.sexpr(() => { f.keyword("local"); func.locals.forEach((local) => printValType(local, f)); }); f.linebreak(); } if (func.body.length > 0) { printInstrBlock(func.body, f); } }); } function printTable(table: Table, f: Formatter) { f.sexpr(() => { f.keyword("table"); printId(table._name, f); printTableType(table.type, f); }); } function printMem(mem: Mem, f: Formatter) { f.sexpr(() => { f.keyword("memory"); printId(mem._name, f); printLimits(mem.type, f); }); } function printGlobal(global: Global, f: Formatter) { f.sexpr(() => { f.keyword("global"); printId(global._name, f); printGlobalType(global.type, f); f.breakIndent(); printInstrBlock(global.init, f); }); } function printExport(export_: Export, f: Formatter) { const desc = export_.desc; f.sexpr(() => { f.keyword("export"); printString(export_.name, f); f.sexpr(() => { f.word(desc.kind); f.word(desc.idx); }); }); } function printStart(start: Start, f: Formatter) { f.sexpr(() => { f.keyword("start"); f.word(start.func); }); } function printElem(_elem: Elem, f: Formatter) { todo(); } function printData(data: Data, f: Formatter) { let mode = data.mode; f.sexpr(() => { f.keyword("data"); printId(data._name, f); if (mode.kind === "active") { const active: DatamodeActive = mode; if (active.memory !== 0) { f.sexpr(() => { f.keyword("memory"); f.word(active.memory); }); } f.sexpr(() => { if (active.offset.length === 1) { printInstr(active.offset[0], f); } else { f.keyword("offset"); f.linebreak(); printInstrBlock(active.offset, f); } }); } printBinaryString(data.init, f); }); } function printModule(module: Module, f: Formatter) { f.sexpr(() => { f.keyword("module"); printId(module._name, f); f.breakIndent(); let hasAnything = false; const breakIfAny = () => { if (hasAnything) { f.linebreak(); } hasAnything = true; }; module.types.forEach((type) => { breakIfAny(); printType(type, f); }); module.imports.forEach((import_) => { breakIfAny(); printImport(import_, f); }); module.funcs.forEach((func) => { breakIfAny(); printFunction(func, f); }); module.tables.forEach((table) => { breakIfAny(); printTable(table, f); }); module.mems.forEach((mem) => { breakIfAny(); printMem(mem, f); }); module.globals.forEach((global) => { breakIfAny(); printGlobal(global, f); }); module.exports.forEach((export_) => { breakIfAny(); printExport(export_, f); }); if (module.start) { breakIfAny(); printStart(module.start, f); } module.elems.forEach((elem) => { breakIfAny(); printElem(elem, f); }); module.datas.forEach((data) => { breakIfAny(); printData(data, f); }); f.breakDedent(); }); f.linebreak(); } function todo(): never { throw new Error("todo"); }