diff --git a/core/rexcode/wasm/module/module.odin b/core/rexcode/wasm/module/module.odin index 8e8be5ad5..e42c3b1d7 100644 --- a/core/rexcode/wasm/module/module.odin +++ b/core/rexcode/wasm/module/module.odin @@ -1,6 +1,7 @@ package rexcode_wasm_module import "base:runtime" +import "core:fmt" import "core:rexcode/wasm" WASM_MAGIC :: u32(0x6d736100) // "\0asm" as a little-endian u32 @@ -155,13 +156,8 @@ Custom_Section_Target_Features :: struct { } -// ----------------------------------------------------------------------------- -// Small display helpers -// ----------------------------------------------------------------------------- - - @(require_results) -section_name :: proc(id: Section_Id) -> string { +section_name :: #force_inline proc "contextless" (id: Section_Id) -> string { switch id { case .CUSTOM: return "custom" case .TYPE: return "type" @@ -181,7 +177,7 @@ section_name :: proc(id: Section_Id) -> string { } @(require_results) -valtype_name :: proc(t: wasm.Value_Type) -> string { +valtype_name :: #force_inline proc "contextless" (t: wasm.Value_Type) -> string { switch t { case .I32: return "i32" case .I64: return "i64" @@ -195,7 +191,7 @@ valtype_name :: proc(t: wasm.Value_Type) -> string { } @(require_results) -external_kind_name :: proc(k: External_Kind) -> string { +external_kind_name :: #force_inline proc "contextless" (k: External_Kind) -> string { switch k { case .FUNC: return "func" case .TABLE: return "table" @@ -204,3 +200,80 @@ external_kind_name :: proc(k: External_Kind) -> string { } return "?" } + + + +@(require_results) +module_name :: proc "contextless" (m: Module) -> (string, bool) { + for c in m.customs { + #partial switch v in c.variant { + case Custom_Section_Name: + if v.module_name != "" { + return v.module_name, true + } + } + } + return "", false +} + +@(require_results) +count_import_kind :: proc "contextless" (m: Module, k: External_Kind) -> u32 { + n: u32 = 0 + for imp in m.imports { + if imp.kind == k { + n += 1 + } + } + return n +} + +@(require_results) +block_sig :: proc "contextless" (m: Module, imm: i64) -> (params: int, results: int) { + if imm < 0 { + if imm == i64(wasm.Block_Type.EMPTY) { + return 0, 0 + } + return 0, 1 // single value-type result + } + if int(imm) < len(m.types) { + t := m.types[imm] + return len(t.params), len(t.results) + } + return 0, 0 +} + + + +// Stack arity (operands consumed, results produced) for non-control operators, +// plus the control operators that flow through the plain path. Block/loop/if +// are handled directly by `wat_fold_region`. +@(require_results) +instruction_arity :: proc(m: Module, inst: wasm.Instruction) -> (inputs: int, outputs: int) { + e := &wasm.ENCODING_TABLE[inst.mnemonic] + inputs, outputs = int(e.inputs), int(e.outputs) + if inputs < 0 || outputs < 0 { + #partial switch inst.mnemonic { + case .CALL, .RETURN_CALL: + if int(inst.ops[0].index) < len(m.functions) { + t := m.functions[inst.ops[0].index].type + return len(t.params), len(t.results) + } + return 0, 0 + case .CALL_INDIRECT, .RETURN_CALL_INDIRECT: + if int(inst.ops[0].index) < len(m.types) { + t := m.types[inst.ops[0].index] + return len(t.params) + 1, len(t.results) + } + return 1, 0 + + case .RETURN: + if int(inst.ops[0].index) < len(m.types) { + t := m.types[inst.ops[0].index] + return len(t.results), 0 + } + return 0, 0 + } + fmt.panicf("Unknown optional arity handling %v", inst.mnemonic) + } + return +} diff --git a/core/rexcode/wasm/module/print.odin b/core/rexcode/wasm/module/print.odin index ca5d9c58a..4195a02ab 100644 --- a/core/rexcode/wasm/module/print.odin +++ b/core/rexcode/wasm/module/print.odin @@ -124,12 +124,15 @@ sbprint_module :: proc(sb: ^strings.Builder, m: Module) { for i in 0.. string { + sb := strings.builder_make(allocator) + sbprint_wat(&sb, m, opts) + return strings.to_string(sb) +} + +// Result lives in the temp allocator. +tprint_wat :: proc(m: Module, opts := DEFAULT_WAT_OPTIONS) -> string { + sb := strings.builder_make(context.temp_allocator) + sbprint_wat(&sb, m, opts) + return strings.to_string(sb) +} + +sbprint_wat :: proc(sb: ^strings.Builder, m: Module, opts := DEFAULT_WAT_OPTIONS) { + unit := opts.indent_unit + if unit == "" { + unit = " " + } + + strings.write_string(sb, "(module") + defer strings.write_string(sb, ")\n") + + if name, ok := module_name(m); ok { + if wat_ident_ok(name) { + strings.write_string(sb, " $") + strings.write_string(sb, name) + } else { + fmt.sbprintf(sb, " (;%q;)", name) + } + } + strings.write_byte(sb, '\n') + + for t, i in m.types { + fmt.sbprintf(sb, "%s(type (;%d;) ", unit, i) + wat_write_functype(sb, t) + strings.write_string(sb, ")\n") + } + + wat_write_imports(sb, m, unit) + + for f in m.functions { + if f.imported { + continue + } + wat_write_function(sb, m, f, unit) + } + + wat_write_tables (sb, m, unit) + wat_write_memories(sb, m, unit) + wat_write_globals (sb, m, unit) + + for e in m.exports { + fmt.sbprintf(sb, "%s(export %q (%s %d))\n", unit, e.name, external_kind_name(e.kind), e.index) + } + + if m.start >= 0 { + fmt.sbprintf(sb, "%s(start %d)\n", unit, m.start) + } + + wat_write_elements(sb, m, unit) + wat_write_data (sb, m, unit) +} + +wat_write_functype :: proc(sb: ^strings.Builder, t: Func_Type) { + strings.write_string(sb, "(func") + defer strings.write_byte(sb, ')') + if len(t.params) > 0 { + strings.write_string(sb, " (param") + defer strings.write_byte(sb, ')') + for p in t.params { + strings.write_byte(sb, ' ') + strings.write_string(sb, valtype_name(p)) + } + } + if len(t.results) > 0 { + strings.write_string(sb, " (result") + defer strings.write_byte(sb, ')') + for rt in t.results { + strings.write_byte(sb, ' ') + strings.write_string(sb, valtype_name(rt)) + } + } +} + +wat_write_imports :: proc(sb: ^strings.Builder, m: Module, unit: string) -> Parse_Error { + sec: Section + { + found := false + for s in m.sections { + if s.id == .IMPORT { + sec = s + found = true + break + } + } + if !found { + return nil + } + } + + r := reader(m.data, sec.offset) + count := rd_u32(&r) or_return + + fi: u32 + ti: u32 + mi: u32 + gi: u32 + + for _ in 0.. 0 { + strings.write_string(sb, " (param") + defer strings.write_byte(sb, ')') + for p in f.type.params { + strings.write_byte(sb, ' ') + strings.write_string(sb, valtype_name(p)) + } + } + if len(f.type.results) > 0 { + strings.write_string(sb, " (result") + defer strings.write_byte(sb, ')') + for rt in f.type.results { + strings.write_byte(sb, ' ') + strings.write_string(sb, valtype_name(rt)) + } + } + if len(f.locals) > 0 { + strings.write_string(sb, " (local") + defer strings.write_byte(sb, ')') + for g in f.locals { + for _ in 0.. (out: [dynamic]WAT_Node, term: wasm.Mnemonic) { + stack: [dynamic]WAT_Node + defer delete(stack) + + for i^ < len(insts) { + inst := insts[i^] + #partial switch inst.mnemonic { + case .END, .ELSE: + i^ += 1 + append(&out, ..stack[:]) + return out, inst.mnemonic + + case .BLOCK, .LOOP: + _, results := block_sig(m, inst.ops[0].immediate) + head := render_head(inst) + i^ += 1 + body, _ := wat_fold_region(insts, m, i) + node := &WAT_Node{ + kind = .BLOCK if inst.mnemonic == .BLOCK else .LOOP, + head = head, + body = body, + results = results, + } + push_expr(&stack, &out, node, 0, results) + + case .IF: + _, results := block_sig(m, inst.ops[0].immediate) + head := render_head(inst) + i^ += 1 + then_body, t := wat_fold_region(insts, m, i) + else_body: [dynamic]WAT_Node + if t == .ELSE { + else_body, _ = wat_fold_region(insts, m, i) + } + node := &WAT_Node{ + kind = .IF, + head = head, + body = then_body, + els = else_body, + results = results, + } + push_expr(&stack, &out, node, 1, results) // 1 = the condition + + case: + op, res := instruction_arity(m, inst) + node := &WAT_Node{ + kind = .PLAIN, + head = render_head(inst), + results = res, + } + i^ += 1 + push_expr(&stack, &out, node, op, res) + } + } + + append(&out, ..stack[:]) + + return out, .END +} + +push_expr :: proc(stack, out: ^[dynamic]WAT_Node, node: ^WAT_Node, op, res: int) { + if op <= len(stack) { + start := len(stack) - op + append(&node.children, ..stack[start:]) + resize(stack, start) + append(stack, node^) + } else { + append(out, ..stack[:]) + clear(stack) + append(stack, node^) + } + if res == 0 { + append(out, ..stack[:]) + clear(stack) + } +} + +render_head :: proc(inst: wasm.Instruction) -> string { + for j in 0.. bool { + (n.kind == .PLAIN) or_return + for &c in n.children { + wat_node_is_simple(&c) or_return + } + return true +} + +wat_render :: proc(sb: ^strings.Builder, n: ^WAT_Node, level: int, unit: string) { + strings.write_byte(sb, '(') + defer strings.write_byte(sb, ')') + strings.write_string(sb, n.head) + + switch n.kind { + case .PLAIN: + if wat_node_is_simple(n) { + for &c in n.children { + strings.write_byte(sb, ' ') + wat_render(sb, &c, level, unit) + } + } else { + for &c in n.children { + strings.write_byte(sb, '\n') + indent_str(sb, unit, level + 1) + wat_render(sb, &c, level + 1, unit) + } + } + + case .BLOCK, .LOOP: + for &s in n.body { + strings.write_byte(sb, '\n') + indent_str(sb, unit, level + 1) + wat_render(sb, &s, level + 1, unit) + } + + case .IF: + for &c in n.children { + if wat_node_is_simple(&c) { + strings.write_byte(sb, ' ') + wat_render(sb, &c, level, unit) + } else { + strings.write_byte(sb, '\n') + indent_str(sb, unit, level + 1) + wat_render(sb, &c, level + 1, unit) + } + } + strings.write_byte(sb, '\n') + indent_str(sb, unit, level + 1) + { + strings.write_string(sb, "(then") + defer strings.write_byte(sb, ')') + for &s in n.body { + strings.write_byte(sb, '\n') + indent_str(sb, unit, level + 2) + wat_render(sb, &s, level + 2, unit) + } + } + if len(n.els) > 0 { + strings.write_byte(sb, '\n') + indent_str(sb, unit, level + 1) + strings.write_string(sb, "(else") + defer strings.write_byte(sb, ')') + for &s in n.els { + strings.write_byte(sb, '\n') + indent_str(sb, unit, level + 2) + wat_render(sb, &s, level + 2, unit) + } + } + } +} + +wat_write_tables :: proc(sb: ^strings.Builder, m: Module, unit: string) { + idx := count_import_kind(m, .TABLE) + for sec in m.sections { + if sec.id != .TABLE { + continue + } + r := reader(m.data, sec.offset) + count := rd_u32(&r) or_break + for _ in 0.. Parse_Error { + elem_idx := 0 + for sec in m.sections { + if sec.id != .ELEMENT { + continue + } + r := reader(m.data, sec.offset) + count := rd_u32(&r) or_break + for _ in 0.. Parse_Error { + data_idx := 0 + for sec in m.sections { + if sec.id != .DATA { + continue + } + r := reader(m.data, sec.offset) + count := rd_u32(&r) or_break + for _ in 0.. 0 { strings.write_byte(sb, ' ') } + wat_render(sb, &s, 0, unit) + } +} + +wat_write_id_or_comment :: proc(sb: ^strings.Builder, name: string, index: u32) { + if name != "" && wat_ident_ok(name) { + strings.write_string(sb, " $") + strings.write_string(sb, name) + } else { + fmt.sbprintf(sb, " (;%d;)", index) + } +} + +@(private) +indent_str :: proc(sb: ^strings.Builder, unit: string, level: int) { + for _ in 0.. bool { + if len(s) == 0 { + return false + } + #no_bounds_check for i in 0..', '?', '@', '\\', '^', '_', '`', '|', + '~': + // okay + case: + return false + } + } + return true +} + +wat_write_quoted_string :: proc(sb: ^strings.Builder, bytes: []u8) { + @(rodata, static) + HEX := "0123456789abcdef" + strings.write_byte(sb, '"') + for b in bytes { + switch b { + case '"': strings.write_string(sb, "\\\"") + case '\\': strings.write_string(sb, "\\\\") + case 0x20..<0x7F: strings.write_byte(sb, b) + case: + strings.write_byte(sb, '\\') + strings.write_byte(sb, HEX[b >> 4]) + strings.write_byte(sb, HEX[b & 0xF]) + } + } + strings.write_byte(sb, '"') +}