From 098936108cb587e936ff215106829d88f102af5a Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 17 Jun 2026 11:58:41 +0100 Subject: [PATCH] Add `core:rexcode/wasm/module` --- core/rexcode/wasm/decoder.odin | 24 +- core/rexcode/wasm/encoder.odin | 9 +- core/rexcode/wasm/encoding_types.odin | 12 +- core/rexcode/wasm/module/module.odin | 151 ++++++++++ core/rexcode/wasm/module/parse.odin | 402 ++++++++++++++++++++++++++ core/rexcode/wasm/module/print.odin | 241 +++++++++++++++ core/rexcode/wasm/module/relocs.odin | 93 ++++++ core/rexcode/wasm/printer.odin | 94 +++--- 8 files changed, 944 insertions(+), 82 deletions(-) create mode 100644 core/rexcode/wasm/module/module.odin create mode 100644 core/rexcode/wasm/module/parse.odin create mode 100644 core/rexcode/wasm/module/print.odin create mode 100644 core/rexcode/wasm/module/relocs.odin diff --git a/core/rexcode/wasm/decoder.odin b/core/rexcode/wasm/decoder.odin index ea13ba5c3..6d1aa2fd1 100644 --- a/core/rexcode/wasm/decoder.odin +++ b/core/rexcode/wasm/decoder.odin @@ -19,8 +19,7 @@ import "base:runtime" // its immediates in declaration order, reconstructing Operands. // // WASM control flow is structured (branches carry relative label depths, not -// byte offsets), so there is no PC-relative label inference -- `label_defs` -// is part of the universal signature but left untouched. Object-file index +// byte offsets), so there is no PC-relative label inference. Object-file index // relocations *are* re-attached: when an input relocation lands on a decoded // index field, that operand is marked `symbolic` and carries the label id. // @@ -39,7 +38,6 @@ decode :: proc( relocs: []Relocation, instructions: ^[dynamic]Instruction, inst_info: ^[dynamic]Instruction_Info, - label_defs: ^[dynamic]Label_Definition, errors: ^[dynamic]Error, targets_allocator := context.allocator, ) -> (byte_count: u32, ok: bool) { @@ -157,6 +155,8 @@ decode_one :: proc( op.index = lid op.flags.symbolic = true op.size = 5 + } else if m == .CALL { + op.flags.symbolic = true } inst.ops[slot] = op slot += 1 @@ -229,15 +229,15 @@ decode_one :: proc( @(private="file") idx_kind_for :: #force_inline proc "contextless" (m: Mnemonic, which: int) -> Index_Kind { #partial switch m { - case .BR, .BR_IF: return .LABEL - case .CALL, .REF_FUNC: return .FUNC - case .CALL_INDIRECT: return which == 0 ? .TYPE : .TABLE - case .LOCAL_GET, .LOCAL_SET, .LOCAL_TEE: return .LOCAL - case .GLOBAL_GET, .GLOBAL_SET: return .GLOBAL - case .MEMORY_INIT, .DATA_DROP: return .DATA - case .TABLE_INIT: return which == 0 ? .ELEM : .TABLE - case .ELEM_DROP: return .ELEM - case .TABLE_COPY: return .TABLE + case .BR, .BR_IF: return .LABEL + case .CALL, .REF_FUNC: return .FUNC + case .CALL_INDIRECT: return which == 0 ? .TYPE : .TABLE + case .LOCAL_GET, .LOCAL_SET, .LOCAL_TEE: return .LOCAL + case .GLOBAL_GET, .GLOBAL_SET: return .GLOBAL + case .MEMORY_INIT, .DATA_DROP: return .DATA + case .TABLE_INIT: return which == 0 ? .ELEM : .TABLE + case .ELEM_DROP: return .ELEM + case .TABLE_COPY: return .TABLE case .TABLE_GROW, .TABLE_SIZE, .TABLE_FILL: return .TABLE } return .NONE diff --git a/core/rexcode/wasm/encoder.odin b/core/rexcode/wasm/encoder.odin index fd453f548..df9d63a1b 100644 --- a/core/rexcode/wasm/encoder.odin +++ b/core/rexcode/wasm/encoder.odin @@ -45,9 +45,8 @@ encode :: proc( ) -> (byte_count: u32, ok: bool) { errors_start := u32(len(errors)) - for i in 0..>= 7 - if v != 0 { b |= 0x80 } + if v != 0 { + b |= 0x80 + } code[offset^] = b offset^ += 1 if v == 0 { @@ -94,7 +96,9 @@ write_sleb :: #force_inline proc "contextless" (code: []u8, offset: ^u32, value: b := u8(v & 0x7F) v >>= 7 // arithmetic shift on signed value sign-extends done := (v == 0 && (b & 0x40) == 0) || (v == -1 && (b & 0x40) != 0) - if !done { b |= 0x80 } + if !done { + b |= 0x80 + } code[offset^] = b offset^ += 1 if done { @@ -109,7 +113,9 @@ write_uleb_padded5 :: #force_inline proc "contextless" (code: []u8, offset: ^u32 for i := 0; i < 5 && offset^ < u32(len(code)); i += 1 { b := u8(v & 0x7F) v >>= 7 - if i != 4 { b |= 0x80 } + if i != 4 { + b |= 0x80 + } code[offset^] = b offset^ += 1 } diff --git a/core/rexcode/wasm/module/module.odin b/core/rexcode/wasm/module/module.odin new file mode 100644 index 000000000..cd371721b --- /dev/null +++ b/core/rexcode/wasm/module/module.odin @@ -0,0 +1,151 @@ +package rexcode_wasm_module + +import "base:runtime" +import "core:rexcode/wasm" + +WASM_MAGIC :: u32(0x6d736100) // "\0asm" as a little-endian u32 +WASM_VERSION :: u32(1) + +Section_Id :: enum u8 { // Binary section ids (WebAssembly core spec ยง5.5.2). + CUSTOM = 0, + TYPE = 1, + IMPORT = 2, + FUNCTION = 3, + TABLE = 4, + MEMORY = 5, + GLOBAL = 6, + EXPORT = 7, + START = 8, + ELEMENT = 9, + CODE = 10, + DATA = 11, + DATA_COUNT = 12, +} + +Section :: struct { + id: Section_Id, + offset: u32, // file offset of the section *contents* + size: u32, // contents length in bytes + count: u32, // element count + name: string, // custom-section name (borrowed) +} + +External_Kind :: enum u8 { + FUNC = 0, + TABLE = 1, + MEMORY = 2, + GLOBAL = 3, +} + +@(rodata) +external_kind_string := [External_Kind]string{ + .FUNC = "func", + .TABLE = "table", + .MEMORY = "memory", + .GLOBAL = "global", +} + +Func_Type :: struct { + params: []wasm.Value_Type, + results: []wasm.Value_Type, +} + +Import :: struct { + kind: External_Kind, + module_name: string, // borrowed + field_name: string, // borrowed + index: u32, // typeidx for FUNC, 0 for other kinds +} + +Export :: struct { + kind: External_Kind, + name: string, // borrowed + index: u32, +} + +// A compressed run of declared locals (e.g. `3 x i32`) +Local_Group :: struct { + count: u32, + type: wasm.Value_Type, +} + +// A function in the module's function index space. +// Imported functions occupy the low indices, followed by the module-defined functions. +Function :: struct { + func_index: u32, + type_index: u32, + type: Func_Type, // resolved signature ({} if the type id was out of range) + imported: bool, + name: string, // export / name-section / import field (borrowed) + import_module: string, // borrowed, "" for defined functions + import_field: string, // borrowed, "" for defined functions + + // defined functions only: + locals: []Local_Group, + body_offset: u32, // file offset of the instruction stream + body_size: u32, // instruction-stream length in bytes +} + +Module :: struct { + version: u32, + sections: []Section, + types: []Func_Type, + imports: []Import, + functions: []Function, // whole function index space (imports + defined) + exports: []Export, + start: i64, // -1 if absent, else the start funcidx + + data: []u8, // borrowed reference to the whole file (body decode reads from it) + + allocator: runtime.Allocator, +} + +// ----------------------------------------------------------------------------- +// Small display helpers +// ----------------------------------------------------------------------------- + + +@(require_results) +section_name :: proc(id: Section_Id) -> string { + switch id { + case .CUSTOM: return "custom" + case .TYPE: return "type" + case .IMPORT: return "import" + case .FUNCTION: return "function" + case .TABLE: return "table" + case .MEMORY: return "memory" + case .GLOBAL: return "global" + case .EXPORT: return "export" + case .START: return "start" + case .ELEMENT: return "element" + case .CODE: return "code" + case .DATA: return "data" + case .DATA_COUNT: return "data.count" + } + return "unknown" +} + +@(require_results) +valtype_name :: proc(t: wasm.Value_Type) -> string { + switch t { + case .I32: return "i32" + case .I64: return "i64" + case .F32: return "f32" + case .F64: return "f64" + case .V128: return "v128" + case .FUNCREF: return "funcref" + case .EXTERNREF: return "externref" + } + return "?" +} + +@(require_results) +external_kind_name :: proc(k: External_Kind) -> string { + switch k { + case .FUNC: return "func" + case .TABLE: return "table" + case .MEMORY: return "memory" + case .GLOBAL: return "global" + } + return "?" +} diff --git a/core/rexcode/wasm/module/parse.odin b/core/rexcode/wasm/module/parse.odin new file mode 100644 index 000000000..44dfc3f89 --- /dev/null +++ b/core/rexcode/wasm/module/parse.odin @@ -0,0 +1,402 @@ +package rexcode_wasm_module + +import "base:runtime" +import "core:rexcode/wasm" + +Parse_Error :: enum { + NONE = 0, + TRUNCATED, + BAD_MAGIC, + BAD_TYPE_FORM, // a functype did not start with 0x60 + BAD_SECTION, // section contents extend past the section size + BAD_ULEB, // ULEB number didn't stop after 10 bytes +} + +Reader_Error :: union #shared_nil { + Parse_Error, + runtime.Allocator_Error, +} + +Reader :: struct { + data: []u8, + off: u32, +} + +@(require_results) +reader :: proc(data: []u8, off: u32) -> Reader { + return Reader{data = data, off = off} +} + +@(require_results) +rd_byte :: proc(r: ^Reader) -> (u8, Parse_Error) { + if r.off >= u32(len(r.data)) { + return 0, .TRUNCATED + } + b := r.data[r.off] + r.off += 1 + return b, .NONE +} + +@(require_results) +rd_u32le_block :: proc(r: ^Reader) -> (u32, Parse_Error) { + if r.off + 4 > u32(len(r.data)) { + return 0, .TRUNCATED + } + v := u32(r.data[r.off]) | + u32(r.data[r.off+1])<<8 | + u32(r.data[r.off+2])<<16 | + u32(r.data[r.off+3])<<24 + r.off += 4 + return v, .NONE +} + +@(require_results) +rd_uleb :: proc(r: ^Reader) -> (u64, Parse_Error) { + shift: uint = 0 + value: u64 = 0 + for _ in 0..<10 { + if r.off >= u32(len(r.data)) { + return 0, .TRUNCATED + } + b := r.data[r.off] + r.off += 1 + value |= u64(b & 0x7F) << shift + if b & 0x80 == 0 { + return value, .NONE + } + shift += 7 + } + return 0, .BAD_ULEB +} + +// Signed-LEB128 reader. +@(require_results) +rd_sleb :: proc(r: ^Reader) -> (i64, Parse_Error) { + shift: uint = 0 + value: i64 = 0 + b: u8 = 0 + for _ in 0..<10 { + if r.off >= u32(len(r.data)) { + return 0, .TRUNCATED + } + b = r.data[r.off] + r.off += 1 + value |= i64(b & 0x7F) << shift + shift += 7 + if b & 0x80 == 0 { + break + } + } + if shift < 64 && (b & 0x40) != 0 { + value |= -(i64(1) << shift) + } + return value, .NONE +} + + +@(require_results) +rd_u32 :: proc(r: ^Reader) -> (u32, Parse_Error) { + v, err := rd_uleb(r) + return u32(v), err +} + +@(require_results) +rd_name :: proc(r: ^Reader) -> (val: string, err: Parse_Error) { + n := rd_u32(r) or_return + if r.off + n > u32(len(r.data)) { + err = .TRUNCATED + return + } + val = string(r.data[r.off:][:n]) + r.off += n + return +} + +@(require_results) +rd_valtype_vec :: proc(r: ^Reader, allocator: runtime.Allocator) -> (out: []wasm.Value_Type, err: Reader_Error) { + n := rd_u32(r) or_return + + // now actually parse it + out = make([]wasm.Value_Type, int(n), allocator) or_return + for &v in out { + v = wasm.Value_Type(rd_byte(r) or_return) + } + return +} + +@(require_results) +rd_limits :: proc(r: ^Reader) -> (min: u64, max: Maybe(u64), err: Parse_Error) { + flags := rd_byte(r) or_return + min = rd_uleb(r) or_return + if flags & 0x01 != 0 { + max = rd_uleb(r) or_return + } + return +} + + +@(require_results) +parse :: proc(data: []u8, allocator := context.allocator) -> (m: Module, err: Reader_Error) { + context.allocator = allocator + + m.data = data + m.start = -1 + m.allocator = allocator + + r := reader(data, 0) + if (rd_u32le_block(&r) or_else 0) != WASM_MAGIC { + return m, .BAD_MAGIC + } + m.version = rd_u32le_block(&r) or_return + + secs: [dynamic]Section + for r.off < u32(len(data)) { + id := Section_Id(rd_byte(&r) or_return) + size := rd_u32(&r) or_return + content := r.off + if content + size > u32(len(data)) { + return m, .BAD_SECTION + } + + sec := Section{id = id, offset = content, size = size} + switch id { + case .CUSTOM: + sub := reader(data, content) + sec.name = rd_name(&sub) or_return + case .START: + // funcidx, no vector count + case .TYPE, .IMPORT, .FUNCTION, .TABLE, .MEMORY, .GLOBAL, + .EXPORT, .ELEMENT, .CODE, .DATA, .DATA_COUNT: + sub := reader(data, content) + sec.count = rd_u32(&sub) or_return + } + append(&secs, sec) or_return + r.off = content + size + } + m.sections = secs[:] + + + func_typeidx: []u32 + codes: []Code_Body + + for &sec in m.sections { + s := reader(data, sec.offset) + #partial switch sec.id { + case .TYPE: m.types = parse_types (&s, allocator) or_return + case .IMPORT: m.imports = parse_imports (&s, allocator) or_return + case .FUNCTION: func_typeidx = parse_function_section(&s, allocator) or_return + case .EXPORT: m.exports = parse_exports (&s, allocator) or_return + case .CODE: codes = parse_code (&s, allocator) or_return + case .START: m.start = i64(rd_u32(&s) or_return) + } + } + + build_functions(&m, func_typeidx, codes, allocator) or_return + apply_name_section(&m) + return +} + +@(require_results) +parse_types :: proc(r: ^Reader, allocator: runtime.Allocator) -> (out: []Func_Type, err: Reader_Error) { + n := rd_u32(r) or_return + out = make([]Func_Type, int(n), allocator) or_return + for &func in out { + form := rd_byte(r) or_return + if form != 0x60 { + return out, .BAD_TYPE_FORM + } + func.params = rd_valtype_vec(r, allocator) or_return + func.results = rd_valtype_vec(r, allocator) or_return + } + return +} + +@(require_results) +parse_imports :: proc(r: ^Reader, allocator: runtime.Allocator) -> (out: []Import, err: Reader_Error) { + n := rd_u32(r) or_return + out = make([]Import, int(n), allocator) or_return + for &imp in out { + imp.module_name = rd_name(r) or_return + imp.field_name = rd_name(r) or_return + imp.kind = External_Kind(rd_byte(r) or_return) + switch imp.kind { + case .FUNC: + imp.index = rd_u32(r) or_return + case .TABLE: + _ = rd_byte(r) or_return // reftype + _, _ = rd_limits(r) or_return + case .MEMORY: + _, _ = rd_limits(r) or_return + case .GLOBAL: + _ = rd_byte(r) or_return // valtype + _ = rd_byte(r) or_return // mutability + } + } + return +} + +@(require_results) +parse_function_section :: proc(r: ^Reader, allocator: runtime.Allocator) -> (out: []u32, err: Reader_Error) { + n := rd_u32(r) or_return + out = make([]u32, int(n), allocator) or_return + for &idx in out { + idx = rd_u32(r) or_return + } + return +} + +@(require_results) +parse_exports :: proc(r: ^Reader, allocator: runtime.Allocator) -> (out: []Export, err: Reader_Error) { + n := rd_u32(r) or_return + out = make([]Export, int(n), allocator) or_return + for &e in out { + e.name = rd_name(r) or_return + e.kind = External_Kind(rd_byte(r) or_return) + e.index = rd_u32(r) or_return + } + return +} + +Code_Body :: struct { + locals: []Local_Group, + body_offset: u32, + body_size: u32, +} + +@(require_results) +parse_code :: proc(r: ^Reader, allocator: runtime.Allocator) -> (out: []Code_Body, err: Reader_Error) { + n := rd_u32(r) or_return + out = make([]Code_Body, int(n), allocator) or_return + for &code_body in out { + total := rd_u32(r) or_return + body_start := r.off + body_end := body_start + total + + nl := rd_u32(r) or_return + locals := make([]Local_Group, int(nl), allocator) or_return + for &local in locals { + cnt := rd_u32(r) or_return + t := wasm.Value_Type(rd_byte(r) or_return) + local = Local_Group{count = cnt, type = t} + } + code_body = Code_Body{ + locals = locals, + body_offset = r.off, + body_size = body_end > r.off ? body_end - r.off : 0, + } + r.off = body_end // jump past the expression to the next entry + } + return +} + +@(require_results) +build_functions :: proc(m: ^Module, func_typeidx: []u32, codes: []Code_Body, allocator: runtime.Allocator) -> runtime.Allocator_Error { + num_imports := 0 + for imp in m.imports { + if imp.kind == .FUNC { + num_imports += 1 + } + } + total := num_imports + len(func_typeidx) + if total == 0 { + return nil + } + + funcs := make([]Function, total, allocator) or_return + + idx := 0 + for imp in m.imports { + (imp.kind == .FUNC) or_continue + f := Function{ + func_index = u32(idx), + type_index = imp.index, + imported = true, + name = imp.field_name, + import_module = imp.module_name, + import_field = imp.field_name, + } + if int(imp.index) < len(m.types) { + f.type = m.types[imp.index] + } + funcs[idx] = f + idx += 1 + } + + for tidx, i in func_typeidx { + fi := num_imports + i + f := Function{ + func_index = u32(fi), + type_index = tidx, + imported = false, + } + if int(tidx) < len(m.types) { + f.type = m.types[tidx] + } + if i < len(codes) { + c := &codes[i] + f.locals = c.locals + f.body_offset = c.body_offset + f.body_size = c.body_size + } + funcs[fi] = f + } + + for e in m.exports { + if e.kind == .FUNC && int(e.index) < total && funcs[e.index].name == "" { + funcs[e.index].name = e.name + } + } + + m.functions = funcs + return nil +} + +// Override function names with debug names from the "name" custom section's function-names subsection (id 1), if it exists. +apply_name_section :: proc(m: ^Module) { + for sec in m.sections { + if sec.id != .CUSTOM || sec.name != "name" { + continue + } + + r := reader(m.data, sec.offset) + _ = rd_name(&r) or_break // re-read the section name to position at the subsections + end := sec.offset + sec.size + + for r.off < end { + sub_id := rd_byte(&r) or_break + sub_size := rd_u32(&r) or_break + payload_end := r.off + sub_size + if sub_id == 1 { + count := rd_u32(&r) or_break + for _ in 0.. 0 { + strings.write_string(sb, "\n.import\n") + for imp, i in m.imports { + fmt.sbprintf(sb, " [%d] %s %q %q idx:%d\n", i, external_kind_string[imp.kind], imp.module_name, imp.field_name, imp.index) + } + } + + if len(m.exports) > 0 { + strings.write_string(sb, "\n.export\n") + for e, i in m.exports { + fmt.sbprintf(sb, " [%d] %s %q idx:%d\n", i, external_kind_string[e.kind], e.name, e.index) + } + } + + if len(m.types) > 0 { + strings.write_string(sb, "\n.") + strings.write_string(sb, section_name(.TYPE)) + strings.write_string(sb, "\n") + for t, i in m.types { + strings.write_string(sb, " [") + write_u64(sb, u64(i)) + strings.write_string(sb, "] ") + write_func_type(sb, t) + strings.write_byte(sb, '\n') + } + } + + + func_relocs: []wasm.Relocation + for rg in relocs_group { + if rg.target_section == .FUNCTION { + func_relocs = rg.relocs + break + } + } + + + strings.write_string(sb, "\nfunctions:\n") + for f in m.functions { + strings.write_string(sb, " [") + write_u64(sb, u64(f.func_index)) + strings.write_string(sb, "] ") + if f.name != "" { + strings.write_byte(sb, '$') + strings.write_quoted_string(sb, f.name) + strings.write_byte(sb, ' ') + } + write_func_type(sb, f.type) + + if f.imported { + strings.write_string(sb, " @ import ") + strings.write_quoted_string(sb, f.import_module) + strings.write_string(sb, " ") + strings.write_quoted_string(sb, f.import_field) + strings.write_string(sb, "\n") + continue + } + + strings.write_byte(sb, '\n') + write_locals(sb, f.locals) + write_body(sb, m, f, func_relocs, &label_names) + } +} + +// Disassemble and print one function body. Returns the empty string for +// imported functions (which have no body). +aprint_function :: proc(m: Module, f: Function, relocs: []wasm.Relocation, label_names: ^map[u32]string, allocator := context.allocator) -> string { + if f.imported || f.body_size == 0 { + return "" + } + body := m.data[f.body_offset:][:f.body_size] + + insts: [dynamic]wasm.Instruction + info: [dynamic]wasm.Instruction_Info + errs: [dynamic]wasm.Error + defer delete(insts) + defer delete(info) + defer delete(errs) + + wasm.decode(body, relocs, &insts, &info, &errs) + return wasm.aprint(insts[:], info[:], allocator=allocator, label_names=label_names) +} + +write_body :: proc(sb: ^strings.Builder, m: Module, f: Function, relocs: []wasm.Relocation, label_names: ^map[u32]string) { + if f.body_size == 0 { return } + text := aprint_function(m, f, relocs, label_names, context.temp_allocator) + for line in strings.split_lines_iterator(&text) { + if line == "" { + continue + } + strings.write_string(sb, " ") + strings.write_string(sb, line) + strings.write_byte(sb, '\n') + } +} + +write_locals :: proc(sb: ^strings.Builder, locals: []Local_Group) { + if len(locals) == 0 { return } + strings.write_string(sb, " locals:") + for g in locals { + strings.write_byte(sb, ' ') + if g.count > 1 { + write_u64(sb, u64(g.count)) + strings.write_string(sb, "x") + } + strings.write_string(sb, valtype_name(g.type)) + } + strings.write_byte(sb, '\n') +} + +write_func_type :: proc(sb: ^strings.Builder, t: Func_Type) { + strings.write_byte(sb, '(') + for p, i in t.params { + if i > 0 { strings.write_string(sb, ", ") } + strings.write_string(sb, valtype_name(p)) + } + strings.write_string(sb, ") -> ") + strings.write_byte(sb, '(') + for rt, i in t.results { + if i > 0 { strings.write_string(sb, ", ") } + strings.write_string(sb, valtype_name(rt)) + } + strings.write_byte(sb, ')') +} + +write_u64 :: proc(sb: ^strings.Builder, v: u64) { + if v == 0 { strings.write_byte(sb, '0'); return } + buf: [20]u8 + i := 0 + n := v + for n > 0 { buf[i] = '0' + u8(n % 10); n /= 10; i += 1 } + for j := i - 1; j >= 0; j -= 1 { strings.write_byte(sb, buf[j]) } +} + +write_padded :: proc(sb: ^strings.Builder, s: string, width: int) { + strings.write_string(sb, s) + for _ in len(s).. (reloc_groups: []Reloc_Group, err: Reader_Error) { + groups: [dynamic]Reloc_Group + groups.allocator = m.allocator + for sec in m.sections { + if !(sec.id == .CUSTOM && strings.has_prefix(sec.name, "reloc.")) { + continue + } + + r := reader(m.data[sec.offset:][:sec.size], 0) + _ = rd_name(&r) or_return // step past the custom-section name + target := Section_Id(rd_u32(&r) or_return) + count := rd_u32(&r) or_return + + out := make([]wasm.Relocation, int(count), m.allocator) + w := 0 + for _ in 0.. (wasm.Relocation_Type, bool) { + switch code { + case 0: return .FUNCTION_INDEX_LEB, true // R_WASM_FUNCTION_INDEX_LEB + case 1: return .TABLE_INDEX_SLEB, true // R_WASM_TABLE_INDEX_SLEB + case 2: return .TABLE_INDEX_I32, true // R_WASM_TABLE_INDEX_I32 + case 3: return .MEMORY_ADDR_LEB, true // R_WASM_MEMORY_ADDR_LEB + case 4: return .MEMORY_ADDR_SLEB, true // R_WASM_MEMORY_ADDR_SLEB + case 5: return .MEMORY_ADDR_I32, true // R_WASM_MEMORY_ADDR_I32 + case 6: return .TYPE_INDEX_LEB, true // R_WASM_TYPE_INDEX_LEB + case 7: return .GLOBAL_INDEX_LEB, true // R_WASM_GLOBAL_INDEX_LEB + case 20: return .TABLE_NUMBER_LEB, true // R_WASM_TABLE_NUMBER_LEB + } + return .NONE, false +} + +// MEMORY_ADDR_* (3,4,5) and the *_OFFSET_I32 (8,9) forms carry a trailing +// signed-LEB addend; the index-type relocations do not. +@(require_results) +reloc_has_addend :: proc(code: u8) -> bool { + switch code { + case 3, 4, 5, 8, 9: return true + } + return false +} + +@(require_results) +reloc_field_size :: proc(t: wasm.Relocation_Type) -> u8 { + #partial switch t { + case .TABLE_INDEX_I32, .MEMORY_ADDR_I32: + return 4 // 4-byte LE field + } + return 5 // 5-byte padded (S)LEB field +} diff --git a/core/rexcode/wasm/printer.odin b/core/rexcode/wasm/printer.odin index 8aa9b50ad..e449318c5 100644 --- a/core/rexcode/wasm/printer.odin +++ b/core/rexcode/wasm/printer.odin @@ -55,8 +55,6 @@ sbprint :: proc( sb: ^strings.Builder, instructions: []Instruction, inst_info: []Instruction_Info, - label_defs: []Label_Definition, - tokens: ^[dynamic]Token = nil, options: ^Print_Options = nil, label_names: ^map[u32]string = nil, ) { @@ -98,7 +96,7 @@ sbprint :: proc( write_decimal_u32(sb, u32(bb)) } case: - for slot in 0.. string { sb := strings.builder_make(allocator) - sbprint(&sb, instructions, inst_info, label_defs, tokens, options, label_names) + sbprint(&sb, instructions, inst_info, options, label_names) return strings.to_string(sb) } aprintln :: proc( - instructions: []Instruction, inst_info: []Instruction_Info, label_defs: []Label_Definition, - tokens: ^[dynamic]Token = nil, options: ^Print_Options = nil, label_names: ^map[u32]string = nil, + instructions: []Instruction, inst_info: []Instruction_Info, options: ^Print_Options = nil, label_names: ^map[u32]string = nil, allocator := context.allocator, ) -> string { sb := strings.builder_make(allocator) - sbprintln(&sb, instructions, inst_info, label_defs, tokens, options, label_names) + sbprintln(&sb, instructions, inst_info, options, label_names) return strings.to_string(sb) } tprint :: proc( - instructions: []Instruction, inst_info: []Instruction_Info, label_defs: []Label_Definition, - tokens: ^[dynamic]Token = nil, options: ^Print_Options = nil, label_names: ^map[u32]string = nil, + instructions: []Instruction, inst_info: []Instruction_Info, options: ^Print_Options = nil, label_names: ^map[u32]string = nil, ) -> string { sb := strings.builder_make(context.temp_allocator) - sbprint(&sb, instructions, inst_info, label_defs, tokens, options, label_names) + sbprint(&sb, instructions, inst_info, options, label_names) return strings.to_string(sb) } tprintln :: proc( - instructions: []Instruction, inst_info: []Instruction_Info, label_defs: []Label_Definition, - tokens: ^[dynamic]Token = nil, options: ^Print_Options = nil, label_names: ^map[u32]string = nil, + instructions: []Instruction, inst_info: []Instruction_Info, options: ^Print_Options = nil, label_names: ^map[u32]string = nil, ) -> string { sb := strings.builder_make(context.temp_allocator) - sbprintln(&sb, instructions, inst_info, label_defs, tokens, options, label_names) + sbprintln(&sb, instructions, inst_info, options, label_names) return strings.to_string(sb) } bprint :: proc( buf: []u8, - instructions: []Instruction, inst_info: []Instruction_Info, label_defs: []Label_Definition, - tokens: ^[dynamic]Token = nil, options: ^Print_Options = nil, label_names: ^map[u32]string = nil, + instructions: []Instruction, inst_info: []Instruction_Info, options: ^Print_Options = nil, label_names: ^map[u32]string = nil, ) -> string { sb := strings.builder_from_bytes(buf) - sbprint(&sb, instructions, inst_info, label_defs, tokens, options, label_names) + sbprint(&sb, instructions, inst_info, options, label_names) return strings.to_string(sb) } bprintln :: proc( buf: []u8, - instructions: []Instruction, inst_info: []Instruction_Info, label_defs: []Label_Definition, - tokens: ^[dynamic]Token = nil, options: ^Print_Options = nil, label_names: ^map[u32]string = nil, + instructions: []Instruction, inst_info: []Instruction_Info, options: ^Print_Options = nil, label_names: ^map[u32]string = nil, ) -> string { sb := strings.builder_from_bytes(buf) - sbprintln(&sb, instructions, inst_info, label_defs, tokens, options, label_names) + sbprintln(&sb, instructions, inst_info, options, label_names) return strings.to_string(sb) } fprint :: proc( fd: ^os.File, - instructions: []Instruction, inst_info: []Instruction_Info, label_defs: []Label_Definition, - tokens: ^[dynamic]Token = nil, options: ^Print_Options = nil, label_names: ^map[u32]string = nil, + instructions: []Instruction, inst_info: []Instruction_Info, options: ^Print_Options = nil, label_names: ^map[u32]string = nil, ) { sb := strings.builder_make(context.temp_allocator) - sbprint(&sb, instructions, inst_info, label_defs, tokens, options, label_names) + sbprint(&sb, instructions, inst_info, options, label_names) os.write_string(fd, strings.to_string(sb)) } fprintln :: proc( fd: ^os.File, - instructions: []Instruction, inst_info: []Instruction_Info, label_defs: []Label_Definition, - tokens: ^[dynamic]Token = nil, options: ^Print_Options = nil, label_names: ^map[u32]string = nil, + instructions: []Instruction, inst_info: []Instruction_Info, options: ^Print_Options = nil, label_names: ^map[u32]string = nil, ) { sb := strings.builder_make(context.temp_allocator) - sbprintln(&sb, instructions, inst_info, label_defs, tokens, options, label_names) + sbprintln(&sb, instructions, inst_info, options, label_names) os.write_string(fd, strings.to_string(sb)) } wprint :: proc( w: io.Writer, - instructions: []Instruction, inst_info: []Instruction_Info, label_defs: []Label_Definition, - tokens: ^[dynamic]Token = nil, options: ^Print_Options = nil, label_names: ^map[u32]string = nil, + instructions: []Instruction, inst_info: []Instruction_Info, options: ^Print_Options = nil, label_names: ^map[u32]string = nil, ) { sb := strings.builder_make(context.temp_allocator) - sbprint(&sb, instructions, inst_info, label_defs, tokens, options, label_names) + sbprint(&sb, instructions, inst_info, options, label_names) io.write_string(w, strings.to_string(sb)) } wprintln :: proc( w: io.Writer, - instructions: []Instruction, inst_info: []Instruction_Info, label_defs: []Label_Definition, - tokens: ^[dynamic]Token = nil, options: ^Print_Options = nil, label_names: ^map[u32]string = nil, + instructions: []Instruction, inst_info: []Instruction_Info, options: ^Print_Options = nil, label_names: ^map[u32]string = nil, ) { sb := strings.builder_make(context.temp_allocator) - sbprintln(&sb, instructions, inst_info, label_defs, tokens, options, label_names) + sbprintln(&sb, instructions, inst_info, options, label_names) io.write_string(w, strings.to_string(sb)) } @@ -247,7 +229,6 @@ wprintln :: proc( // Internal writers // ============================================================================= -@(private="file") write_mnemonic :: proc(sb: ^strings.Builder, m: Mnemonic, uppercase: bool) { name := MNEMONIC_NAMES[m] if name == "" { strings.write_string(sb, ""); return } @@ -265,7 +246,6 @@ write_mnemonic :: proc(sb: ^strings.Builder, m: Mnemonic, uppercase: bool) { } } -@(private="file") write_operand :: proc( sb: ^strings.Builder, op: ^Operand, @@ -293,19 +273,18 @@ write_operand :: proc( } case .MEMARG: - // WAT prints non-trivial memargs as `offset=N align=N` (omitting either - // when it is the natural default is a refinement; we print both). - strings.write_string(sb, "offset=") - write_decimal_u32(sb, op.memarg.offset) - strings.write_string(sb, " align=") + // WAT prints non-trivial memargs as `align=N offset=N` + // omitting either when it is the natural default is a refinement. + strings.write_string(sb, "align=") write_decimal_u32(sb, op.memarg.align) + strings.write_string(sb, " offset=") + write_decimal_u32(sb, op.memarg.offset) case .BLOCK_TYPE: write_block_type(sb, op.immediate) } } -@(private="file") write_block_type :: proc(sb: ^strings.Builder, v: i64) { switch Block_Type(v) { case .EMPTY: // no result annotation @@ -324,7 +303,6 @@ write_block_type :: proc(sb: ^strings.Builder, v: i64) { } } -@(private="file") write_heap_type :: proc(sb: ^strings.Builder, b: u8) { #partial switch Value_Type(b) { case .FUNCREF: strings.write_string(sb, "func") @@ -334,7 +312,6 @@ write_heap_type :: proc(sb: ^strings.Builder, b: u8) { } } -@(private="file") write_float :: proc(sb: ^strings.Builder, op: ^Operand) { buf: [40]u8 if op.size == 4 { @@ -348,7 +325,6 @@ write_float :: proc(sb: ^strings.Builder, op: ^Operand) { } } -@(private="file") write_label :: proc( sb: ^strings.Builder, label_id: u32, @@ -357,15 +333,15 @@ write_label :: proc( ) { if label_names != nil { if name, ok := label_names^[label_id]; ok { - strings.write_string(sb, name) + strings.write_string(sb, "$") + strings.write_quoted_string(sb, name) return } } - strings.write_string(sb, opts.label_prefix) + strings.write_string(sb, "$") write_decimal_u32(sb, label_id) } -@(private="file") write_decimal_u32 :: proc(sb: ^strings.Builder, v: u32) { if v == 0 { strings.write_byte(sb, '0') @@ -383,7 +359,6 @@ write_decimal_u32 :: proc(sb: ^strings.Builder, v: u32) { } } -@(private="file") write_signed_decimal :: proc(sb: ^strings.Builder, v: i64) { if v < 0 { strings.write_byte(sb, '-') @@ -393,7 +368,6 @@ write_signed_decimal :: proc(sb: ^strings.Builder, v: i64) { } } -@(private="file") write_decimal_u64 :: proc(sb: ^strings.Builder, v: u64) { if v == 0 { strings.write_byte(sb, '0')