mirror of
https://github.com/odin-lang/Odin.git
synced 2026-07-17 13:11:07 +00:00
Add print_wat.odin
This commit is contained in:
@@ -130,6 +130,7 @@ Function :: struct {
|
||||
blocks: []Block,
|
||||
name: string,
|
||||
signature: Type_Ref, // a FUNCTION type in Module.types
|
||||
id: Id, // not used by all formats
|
||||
}
|
||||
|
||||
// A module-level mutable/immutable value.
|
||||
|
||||
@@ -37,6 +37,6 @@ Error_Code :: enum u8 {
|
||||
Error :: struct #packed {
|
||||
location: u32,
|
||||
code: Error_Code,
|
||||
_: [3]u8,
|
||||
_: [3]u8 `fmt:"-"`,
|
||||
}
|
||||
#assert(size_of(Error) == 8)
|
||||
|
||||
@@ -98,7 +98,6 @@ Module :: struct {
|
||||
using base: ir.Module, // functions, globals, symbols, dataflow (= .STACK), target
|
||||
|
||||
version: u32, // WASM_VERSION if 0 on encode
|
||||
name: string, // Module's name
|
||||
|
||||
// --- container sections (the WASM-specific data the ir core has no slot for) ---
|
||||
func_types: []Func_Type, // the type section; ir.Function.signature indexes here
|
||||
@@ -202,3 +201,28 @@ section_name :: #force_inline proc "contextless" (id: Section_Id) -> string {
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
|
||||
@(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)
|
||||
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
|
||||
}
|
||||
@@ -69,7 +69,7 @@ decode :: proc(
|
||||
return
|
||||
}
|
||||
|
||||
@(private="file", require_results)
|
||||
@(private, require_results)
|
||||
parse_error_code :: proc "contextless" (err: Reader_Error) -> Error_Code {
|
||||
#partial switch e in err {
|
||||
case Parse_Error:
|
||||
@@ -168,12 +168,12 @@ Reader :: struct {
|
||||
off: u32,
|
||||
}
|
||||
|
||||
@(private="file", require_results)
|
||||
@(private, require_results)
|
||||
reader :: proc "contextless" (data: []u8, off: u32) -> Reader {
|
||||
return Reader{data = data, off = off}
|
||||
}
|
||||
|
||||
@(private="file", require_results)
|
||||
@(private, require_results)
|
||||
rd_byte :: proc "contextless" (r: ^Reader) -> (u8, Parse_Error) {
|
||||
if r.off >= u32(len(r.data)) { return 0, .TRUNCATED }
|
||||
b := r.data[r.off]
|
||||
@@ -181,7 +181,7 @@ rd_byte :: proc "contextless" (r: ^Reader) -> (u8, Parse_Error) {
|
||||
return b, .NONE
|
||||
}
|
||||
|
||||
@(private="file", require_results)
|
||||
@(private, require_results)
|
||||
rd_u32le_block :: proc "contextless" (r: ^Reader) -> (u32, Parse_Error) {
|
||||
if r.off + 4 > u32(len(r.data)) { return 0, .TRUNCATED }
|
||||
v := u32(r.data[r.off]) |
|
||||
@@ -192,7 +192,7 @@ rd_u32le_block :: proc "contextless" (r: ^Reader) -> (u32, Parse_Error) {
|
||||
return v, .NONE
|
||||
}
|
||||
|
||||
@(private="file", require_results)
|
||||
@(private, require_results)
|
||||
rd_uleb :: proc "contextless" (r: ^Reader) -> (u64, Parse_Error) {
|
||||
shift: uint = 0
|
||||
value: u64 = 0
|
||||
@@ -207,7 +207,7 @@ rd_uleb :: proc "contextless" (r: ^Reader) -> (u64, Parse_Error) {
|
||||
return 0, .BAD_ULEB
|
||||
}
|
||||
|
||||
@(private="file", require_results)
|
||||
@(private, require_results)
|
||||
rd_sleb :: proc "contextless" (r: ^Reader) -> (i64, Parse_Error) {
|
||||
shift: uint = 0
|
||||
value: i64 = 0
|
||||
@@ -226,13 +226,13 @@ rd_sleb :: proc "contextless" (r: ^Reader) -> (i64, Parse_Error) {
|
||||
return value, .NONE
|
||||
}
|
||||
|
||||
@(private="file", require_results)
|
||||
@(private, require_results)
|
||||
rd_u32 :: proc "contextless" (r: ^Reader) -> (u32, Parse_Error) {
|
||||
v, err := rd_uleb(r)
|
||||
return u32(v), err
|
||||
}
|
||||
|
||||
@(private="file", require_results)
|
||||
@(private, require_results)
|
||||
rd_name :: proc "contextless" (r: ^Reader) -> (val: string, err: Parse_Error) {
|
||||
n := rd_u32(r) or_return
|
||||
if r.off + n > u32(len(r.data)) { err = .TRUNCATED; return }
|
||||
@@ -241,7 +241,7 @@ rd_name :: proc "contextless" (r: ^Reader) -> (val: string, err: Parse_Error) {
|
||||
return
|
||||
}
|
||||
|
||||
@(private="file", require_results)
|
||||
@(private, require_results)
|
||||
rd_valtype_vec :: proc(r: ^Reader, allocator: runtime.Allocator) -> (out: []Value_Type, err: Reader_Error) {
|
||||
n := rd_u32(r) or_return
|
||||
out = make([]Value_Type, int(n), allocator) or_return
|
||||
@@ -251,7 +251,7 @@ rd_valtype_vec :: proc(r: ^Reader, allocator: runtime.Allocator) -> (out: []Valu
|
||||
return
|
||||
}
|
||||
|
||||
@(private="file", require_results)
|
||||
@(private, require_results)
|
||||
rd_limits :: proc "contextless" (r: ^Reader) -> (min: u64, max: Maybe(u64), err: Parse_Error) {
|
||||
flags := rd_byte(r) or_return
|
||||
min = rd_uleb(r) or_return
|
||||
@@ -265,7 +265,7 @@ rd_limits :: proc "contextless" (r: ^Reader) -> (min: u64, max: Maybe(u64), err:
|
||||
// Structured section parsers
|
||||
// =============================================================================
|
||||
|
||||
@(private="file", require_results)
|
||||
@(private, 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
|
||||
@@ -278,7 +278,7 @@ parse_types :: proc(r: ^Reader, allocator: runtime.Allocator) -> (out: []Func_Ty
|
||||
return
|
||||
}
|
||||
|
||||
@(private="file", require_results)
|
||||
@(private, 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
|
||||
@@ -302,7 +302,7 @@ parse_imports :: proc(r: ^Reader, allocator: runtime.Allocator) -> (out: []Impor
|
||||
return
|
||||
}
|
||||
|
||||
@(private="file", require_results)
|
||||
@(private, 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
|
||||
@@ -312,7 +312,7 @@ parse_function_section :: proc(r: ^Reader, allocator: runtime.Allocator) -> (out
|
||||
return
|
||||
}
|
||||
|
||||
@(private="file", require_results)
|
||||
@(private, 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
|
||||
@@ -326,20 +326,20 @@ parse_exports :: proc(r: ^Reader, allocator: runtime.Allocator) -> (out: []Expor
|
||||
|
||||
// One code entry's locals (still compressed as `count x type` groups) and the
|
||||
// file span of its body `expr`.
|
||||
@(private="file")
|
||||
@(private)
|
||||
Code_Body :: struct {
|
||||
locals: []Local_Group,
|
||||
body_offset: u32, // file offset of the instruction stream
|
||||
body_size: u32, // instruction-stream length in bytes
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
@(private)
|
||||
Local_Group :: struct {
|
||||
count: u32,
|
||||
type: Value_Type,
|
||||
}
|
||||
|
||||
@(private="file", require_results)
|
||||
@(private, 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
|
||||
@@ -364,7 +364,7 @@ parse_code :: proc(r: ^Reader, allocator: runtime.Allocator) -> (out: []Code_Bod
|
||||
}
|
||||
|
||||
|
||||
@(private="file", require_results)
|
||||
@(private, require_results)
|
||||
parse_custom_sections :: proc(m: ^Module, allocator: runtime.Allocator) -> Reader_Error {
|
||||
custom_count := 0
|
||||
for &sec in m.sections {
|
||||
@@ -465,7 +465,7 @@ parse_custom_sections :: proc(m: ^Module, allocator: runtime.Allocator) -> Reade
|
||||
// Function index space (imports ++ defined) with eagerly-decoded bodies
|
||||
// =============================================================================
|
||||
|
||||
@(private="file", require_results)
|
||||
@(private, require_results)
|
||||
build_functions :: proc(
|
||||
m: ^Module,
|
||||
func_typeidx: []u32,
|
||||
@@ -530,6 +530,7 @@ build_functions :: proc(
|
||||
blocks := make([]Block, 1, allocator) or_return
|
||||
blocks[0] = Block{id = ID_NONE, ops = ops}
|
||||
f.blocks = blocks
|
||||
f.id = Id(fi)
|
||||
}
|
||||
funcs[fi] = f
|
||||
}
|
||||
@@ -550,7 +551,7 @@ build_functions :: proc(
|
||||
// be relative to the body start (so `decode_ops` -- whose pc starts at 0 --
|
||||
// matches them). Returns nil when there are none (the common, linked-module
|
||||
// case), so no allocation happens.
|
||||
@(private="file", require_results)
|
||||
@(private, require_results)
|
||||
relocs_for_body :: proc(all: []Relocation, body_off_in_sec, size: u32, allocator: runtime.Allocator) -> []Relocation {
|
||||
if len(all) == 0 { return nil }
|
||||
lo := body_off_in_sec
|
||||
@@ -571,7 +572,7 @@ relocs_for_body :: proc(all: []Relocation, body_off_in_sec, size: u32, allocator
|
||||
// Relocations (object-file `reloc.*` custom sections)
|
||||
// =============================================================================
|
||||
|
||||
@(private="file", require_results)
|
||||
@(private, require_results)
|
||||
parse_relocations :: proc(m: Module, allocator: runtime.Allocator) -> (groups_out: []Reloc_Group, err: Reader_Error) {
|
||||
groups: [dynamic]Reloc_Group
|
||||
groups.allocator = allocator
|
||||
@@ -611,7 +612,7 @@ parse_relocations :: proc(m: Module, allocator: runtime.Allocator) -> (groups_ou
|
||||
|
||||
// Override function names with the "name" custom section's function-name
|
||||
// subsection (id 1) when present -- these are the authoritative debug names.
|
||||
@(private="file")
|
||||
@(private)
|
||||
apply_name_section :: proc(m: ^Module) {
|
||||
for sec in m.sections {
|
||||
if sec.id != .CUSTOM || sec.name != "name" { continue }
|
||||
|
||||
547
core/rexcode/ir/wasm/print_wat.odin
Normal file
547
core/rexcode/ir/wasm/print_wat.odin
Normal file
@@ -0,0 +1,547 @@
|
||||
package rexcode_wasm
|
||||
|
||||
import "base:runtime"
|
||||
import "core:fmt"
|
||||
import "core:io"
|
||||
import "core:os"
|
||||
import "core:strings"
|
||||
|
||||
// =============================================================================
|
||||
// WebAssembly TEXT (WAT) PRINTER
|
||||
// =============================================================================
|
||||
//
|
||||
// `print.odin` emits a compact, custom *disassembly* of a parsed `Module`.
|
||||
// This file emits the standard WebAssembly text format (`.wat`): the nested
|
||||
// s-expression module that `wat2wasm` consumes and that `wasm2wat` produces.
|
||||
//
|
||||
// (module
|
||||
// (type (;0;) (func (param i32 i32) (result i32)))
|
||||
// (func $add (type 0) (param i32 i32) (result i32)
|
||||
// (i32.add
|
||||
// (local.get 0)
|
||||
// (local.get 1)))
|
||||
// (export "add" (func 0)))
|
||||
//
|
||||
// Folding
|
||||
// -------
|
||||
// A WASM code section is a *linear* stack-machine stream; WAT bodies are
|
||||
// *folded* operator trees. Reconstructing the trees needs each instruction's
|
||||
// stack arity (operands consumed / results produced), which the linear stream
|
||||
// does not carry, so it is recovered here:
|
||||
// * fixed for the numeric / memory / variable / parametric / reference /
|
||||
// bulk-memory operators (`instruction_arity`),
|
||||
// * computed from the module's own type table for `call` (callee signature),
|
||||
// `call_indirect` (the referenced type), and `block`/`loop`/`if` results.
|
||||
//
|
||||
// The folder is the algorithm `wat2wasm`/`wasm2wat` use: keep a stack of
|
||||
// partially built operand trees; an operator with N operands claims the top N
|
||||
// trees as children; an operator that yields no value (a "statement", e.g. a
|
||||
// store / drop / void call) flushes the stack so nothing is reordered. Where
|
||||
// folding would be unsound (not enough operands contiguously on top, e.g.
|
||||
// across a side-effecting statement) it degrades to flat folded exprs such as
|
||||
// `(i32.add)` whose operands are the preceding sibling exprs -- still valid,
|
||||
// still correct, just less nested. SIMD / atomic operators are conservatively
|
||||
// treated as statements (rendered flat) rather than risk an incorrect tree.
|
||||
//
|
||||
// Index operands print numerically (`call 3`, `local.get 0`): the decoder
|
||||
// marks `call` targets `symbolic` (which would print `$3`); that flag is
|
||||
// cleared per-instruction so references always resolve. Names from the "name"
|
||||
// custom section are attached to *definitions* as `$name` (or kept as an index
|
||||
// comment when the text would not be a legal WAT identifier).
|
||||
//
|
||||
// Coverage: TYPE, IMPORT (all four kinds), FUNCTION/CODE, TABLE, MEMORY,
|
||||
// GLOBAL, EXPORT, START, ELEMENT (active table-0 funcref form), DATA.
|
||||
|
||||
WAT_Options :: struct {
|
||||
indent_unit: string, // one indentation step; default " "
|
||||
}
|
||||
|
||||
DEFAULT_WAT_OPTIONS :: WAT_Options{
|
||||
indent_unit = " ",
|
||||
}
|
||||
|
||||
print_wat :: proc(m: Module, file: ^os.File, opts := DEFAULT_WAT_OPTIONS) {
|
||||
sb := strings.builder_make(context.allocator)
|
||||
defer strings.builder_destroy(&sb)
|
||||
sbprint_wat(&sb, m, opts)
|
||||
os.write_string(file, strings.to_string(sb))
|
||||
}
|
||||
|
||||
fprint_wat :: proc(w: io.Writer, m: Module, opts := DEFAULT_WAT_OPTIONS) {
|
||||
sb := strings.builder_make(context.temp_allocator)
|
||||
sbprint_wat(&sb, m, opts)
|
||||
io.write_string(w, strings.to_string(sb))
|
||||
}
|
||||
|
||||
// Allocates the result with `allocator` (caller owns it).
|
||||
aprint_wat :: proc(m: Module, opts := DEFAULT_WAT_OPTIONS, allocator := context.allocator) -> 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.func_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)
|
||||
|
||||
func_relocs := relocations_for_section(m.relocations, .FUNCTION)
|
||||
|
||||
for f in m.functions {
|
||||
if len(f.blocks) == 0 {
|
||||
// assume this is the equivalent of an imported block
|
||||
continue
|
||||
}
|
||||
wat_write_function(sb, m, func_relocs, 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..<count {
|
||||
mod := rd_name(&r) or_return
|
||||
fld := rd_name(&r) or_return
|
||||
kb := rd_byte(&r) or_return
|
||||
|
||||
fmt.sbprintf(sb, "%s(import %q %q (", unit, mod, fld)
|
||||
defer strings.write_string(sb, "))\n")
|
||||
|
||||
switch External_Kind(kb) {
|
||||
case .FUNC:
|
||||
tidx, _ := rd_u32(&r)
|
||||
strings.write_string(sb, "func")
|
||||
name := ""
|
||||
if int(fi) < len(m.functions) {
|
||||
name = m.functions[fi].name
|
||||
}
|
||||
wat_write_id_or_comment(sb, name, Id(fi))
|
||||
fmt.sbprintf(sb, " (type %d)", tidx)
|
||||
fi += 1
|
||||
case .TABLE:
|
||||
rt, _ := rd_byte(&r)
|
||||
min, max, _ := rd_limits(&r)
|
||||
fmt.sbprintf(sb, "table (;%d;) %d", ti, min)
|
||||
if mx, ok := max.?; ok {
|
||||
fmt.sbprintf(sb, " %d", mx)
|
||||
}
|
||||
fmt.sbprintf(sb, " %s", valtype_name(Value_Type(rt)))
|
||||
ti += 1
|
||||
case .MEMORY:
|
||||
min, max, _ := rd_limits(&r)
|
||||
fmt.sbprintf(sb, "memory (;%d;) %d", mi, min)
|
||||
if mx, ok := max.?; ok {
|
||||
fmt.sbprintf(sb, " %d", mx)
|
||||
}
|
||||
mi += 1
|
||||
case .GLOBAL:
|
||||
vt, _ := rd_byte(&r)
|
||||
mut, _ := rd_byte(&r)
|
||||
fmt.sbprintf(sb, "global (;%d;) ", gi)
|
||||
if mut == 1 {
|
||||
fmt.sbprintf(sb, "(mut %s)", valtype_name(Value_Type(vt)))
|
||||
} else {
|
||||
strings.write_string(sb, valtype_name(Value_Type(vt)))
|
||||
}
|
||||
gi += 1
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
wat_write_function :: proc(sb: ^strings.Builder, m: Module, func_relocs: []Relocation, f: Function, unit: string) {
|
||||
strings.write_string(sb, unit)
|
||||
strings.write_string(sb, "(func")
|
||||
defer strings.write_string(sb, ")\n")
|
||||
wat_write_id_or_comment(sb, f.name, f.id)
|
||||
fmt.sbprintf(sb, " (type %d)", f.signature)
|
||||
|
||||
type := &m.func_types[f.signature]
|
||||
|
||||
if len(type.params) > 0 {
|
||||
strings.write_string(sb, " (param")
|
||||
defer strings.write_byte(sb, ')')
|
||||
for p in type.params {
|
||||
strings.write_byte(sb, ' ')
|
||||
strings.write_string(sb, valtype_name(p))
|
||||
}
|
||||
}
|
||||
if len(type.results) > 0 {
|
||||
strings.write_string(sb, " (result")
|
||||
defer strings.write_byte(sb, ')')
|
||||
for rt in type.results {
|
||||
strings.write_byte(sb, ' ')
|
||||
strings.write_string(sb, valtype_name(rt))
|
||||
}
|
||||
}
|
||||
locals := m.function_locals[f.id]
|
||||
if len(locals) > 0 {
|
||||
strings.write_string(sb, "\n")
|
||||
strings.write_string(sb, " (local")
|
||||
defer strings.write_byte(sb, ')')
|
||||
for g in locals {
|
||||
strings.write_byte(sb, ' ')
|
||||
strings.write_string(sb, valtype_name(g))
|
||||
}
|
||||
}
|
||||
strings.write_byte(sb, '\n')
|
||||
|
||||
opts := DEFAULT_PRINT_OPTIONS
|
||||
opts.indent = " "
|
||||
assert(len(f.blocks) <= 1)
|
||||
for blk in f.blocks {
|
||||
for &op, i in blk.ops {
|
||||
if i+1 == len(blk.ops) && Opcode(op.opcode) == .END {
|
||||
// TODO(bill): Should this be rendered or NOT?
|
||||
// break
|
||||
}
|
||||
write_operation(sb, &op, &opts)
|
||||
strings.write_string(sb, opts.separator)
|
||||
}
|
||||
}
|
||||
|
||||
strings.write_string(sb, 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..<count {
|
||||
rt := rd_byte(&r) or_break
|
||||
min, max := rd_limits(&r) or_break
|
||||
fmt.sbprintf(sb, "%s(table (;%d;) %d", unit, idx, min)
|
||||
defer fmt.sbprintf(sb, " %s)\n", valtype_name(Value_Type(rt)))
|
||||
if mx, ok := max.?; ok {
|
||||
fmt.sbprintf(sb, " %d", mx)
|
||||
}
|
||||
idx += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wat_write_memories :: proc(sb: ^strings.Builder, m: Module, unit: string) {
|
||||
idx := count_import_kind(m, .MEMORY)
|
||||
for sec in m.sections {
|
||||
if sec.id != .MEMORY {
|
||||
continue
|
||||
}
|
||||
r := reader(m.data, sec.offset)
|
||||
count := rd_u32(&r) or_break
|
||||
for _ in 0..<count {
|
||||
min, max := rd_limits(&r) or_break
|
||||
fmt.sbprintf(sb, "%s(memory (;%d;) %d", unit, idx, min)
|
||||
defer strings.write_string(sb, ")\n")
|
||||
if mx, ok := max.?; ok {
|
||||
fmt.sbprintf(sb, " %d", mx)
|
||||
}
|
||||
idx += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wat_write_globals :: proc(sb: ^strings.Builder, m: Module, unit: string) {
|
||||
idx := count_import_kind(m, .GLOBAL)
|
||||
for sec in m.sections {
|
||||
if sec.id != .GLOBAL {
|
||||
continue
|
||||
}
|
||||
relocs := relocations_for_section(m.relocations, sec.id)
|
||||
|
||||
r := reader(m.data, sec.offset)
|
||||
count := rd_u32(&r) or_break
|
||||
for _ in 0..<count {
|
||||
vt := rd_byte(&r) or_break
|
||||
mut := rd_byte(&r) or_break
|
||||
|
||||
fmt.sbprintf(sb, "%s(global (;%d;) ", unit, idx)
|
||||
defer strings.write_string(sb, ")\n")
|
||||
|
||||
if mut == 1 {
|
||||
fmt.sbprintf(sb, "(mut %s) ", valtype_name(Value_Type(vt)))
|
||||
} else {
|
||||
fmt.sbprintf(sb, "%s ", valtype_name(Value_Type(vt)))
|
||||
}
|
||||
wat_write_const_expr(sb, m, relocs, m.data, &r.off, unit, context.temp_allocator)
|
||||
idx += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wat_write_elements :: proc(sb: ^strings.Builder, m: Module, unit: string) -> Parse_Error {
|
||||
elem_idx := 0
|
||||
for sec in m.sections {
|
||||
if sec.id != .ELEMENT {
|
||||
continue
|
||||
}
|
||||
|
||||
relocs := relocations_for_section(m.relocations, sec.id)
|
||||
|
||||
r := reader(m.data, sec.offset)
|
||||
count := rd_u32(&r) or_break
|
||||
for _ in 0..<count {
|
||||
flags := rd_u32(&r) or_break
|
||||
if flags != 0 {
|
||||
fmt.sbprintf(sb, "%s(; elem segment %d: unsupported flags=%d ;)\n", unit, elem_idx, flags)
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.sbprintf(sb, "%s(elem (;%d;) ", unit, elem_idx)
|
||||
defer strings.write_string(sb, ")\n")
|
||||
|
||||
wat_write_const_expr(sb, m, relocs, m.data, &r.off, unit, context.temp_allocator)
|
||||
strings.write_string(sb, " func")
|
||||
n := rd_u32(&r) or_break
|
||||
for _ in 0..<n {
|
||||
fidx := rd_u32(&r) or_return
|
||||
fmt.sbprintf(sb, " %d", fidx)
|
||||
}
|
||||
elem_idx += 1
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
wat_write_data :: proc(sb: ^strings.Builder, m: Module, unit: string) -> 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..<count {
|
||||
kind := rd_u32(&r) or_return
|
||||
|
||||
fmt.sbprintf(sb, "%s(data (;%d;)", unit, data_idx)
|
||||
defer strings.write_string(sb, ")\n")
|
||||
|
||||
memidx: u32 = 0
|
||||
switch kind {
|
||||
case 2:
|
||||
// memidx + expr + bytes
|
||||
memidx, _ = rd_u32(&r)
|
||||
fallthrough
|
||||
case 0:
|
||||
// expr + bytes
|
||||
if memidx != 0 {
|
||||
fmt.sbprintf(sb, " (memory %d)", memidx)
|
||||
}
|
||||
strings.write_byte(sb, ' ')
|
||||
relocs := relocations_for_section(m.relocations, sec.id)
|
||||
wat_write_const_expr(sb, m, relocs, m.data, &r.off, unit, context.temp_allocator)
|
||||
case 1:
|
||||
// bytes
|
||||
case:
|
||||
fmt.sbprintf(sb, "%s(; data segment %d: unsupported kind=%d ;)\n", unit, data_idx, kind)
|
||||
return nil
|
||||
}
|
||||
|
||||
size := rd_u32(&r) or_return
|
||||
strings.write_byte(sb, ' ')
|
||||
wat_write_quoted_string(sb, m.data[r.off:][:size])
|
||||
r.off += size
|
||||
|
||||
data_idx += 1
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode and fold a constant expression at `off^` (advancing past its `end`).
|
||||
wat_write_const_expr :: proc(sb: ^strings.Builder, m: Module, relocs: []Relocation, data: []u8, off: ^u32, unit: string, allocator: runtime.Allocator) {
|
||||
context.allocator = context.temp_allocator
|
||||
|
||||
ops: [dynamic]Operation
|
||||
ops.allocator = allocator
|
||||
defer delete(ops)
|
||||
|
||||
for off^ < u32(len(data)) {
|
||||
op, next := decode_one(data, relocs, off^, allocator) or_break
|
||||
off^ = next
|
||||
if Opcode(op.opcode) == .END {
|
||||
break
|
||||
}
|
||||
append(&ops, op)
|
||||
}
|
||||
sbprint(sb, ops[:])
|
||||
|
||||
// i := 0
|
||||
// stmts, _ := wat_fold_region(exprs[:], m, &i)
|
||||
// for &s, k in stmts {
|
||||
// if k > 0 { strings.write_byte(sb, ' ') }
|
||||
// wat_render(sb, &s, 0, unit)
|
||||
// }
|
||||
}
|
||||
|
||||
wat_write_id_or_comment :: proc(sb: ^strings.Builder, name: string, index: Id) {
|
||||
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..<level {
|
||||
strings.write_string(sb, unit)
|
||||
}
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
wat_ident_ok :: proc(s: string) -> bool {
|
||||
if len(s) == 0 {
|
||||
return false
|
||||
}
|
||||
#no_bounds_check for i in 0..<len(s) {
|
||||
c := s[i]
|
||||
switch c {
|
||||
case '0'..='9', 'A'..='Z', 'a'..='z':
|
||||
// okay
|
||||
case '!', '#', '$', '%', '&', '\'', '*', '+', '-', '.', '/',
|
||||
':', '<', '=', '>', '?', '@', '\\', '^', '_', '`', '|',
|
||||
'~':
|
||||
// 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, '"')
|
||||
}
|
||||
|
||||
|
||||
@(require_results)
|
||||
valtype_name :: #force_inline proc "contextless" (t: 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 :: #force_inline proc "contextless" (k: External_Kind) -> string {
|
||||
switch k {
|
||||
case .FUNC: return "func"
|
||||
case .TABLE: return "table"
|
||||
case .MEMORY: return "memory"
|
||||
case .GLOBAL: return "global"
|
||||
}
|
||||
return "?"
|
||||
}
|
||||
@@ -54,7 +54,9 @@ Reloc_Group :: struct {
|
||||
@(require_results)
|
||||
relocations_for_section :: proc "contextless" (groups: []Reloc_Group, id: Section_Id) -> []Relocation {
|
||||
for g in groups {
|
||||
if g.target_section == id { return g.relocs }
|
||||
if g.target_section == id {
|
||||
return g.relocs
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -86,7 +88,8 @@ reloc_type_from_wire :: proc "contextless" (code: u8) -> (Relocation_Type, bool)
|
||||
@(require_results)
|
||||
reloc_has_addend :: proc "contextless" (code: u8) -> bool {
|
||||
switch code {
|
||||
case 3, 4, 5, 8, 9: return true
|
||||
case 3, 4, 5, 8, 9:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user