Add core:rexcode/wasm/module

This commit is contained in:
gingerBill
2026-06-17 11:58:41 +01:00
parent 0d9ad7259b
commit 098936108c
8 changed files with 944 additions and 82 deletions

View File

@@ -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

View File

@@ -45,9 +45,8 @@ encode :: proc(
) -> (byte_count: u32, ok: bool) {
errors_start := u32(len(errors))
for i in 0..<u32(len(instructions)) {
inst := &instructions[i]
n := encode_one(inst, byte_count, u16(i), code, relocs, errors) or_return
for &inst, i in instructions {
n := encode_one(&inst, byte_count, u16(i), code, relocs, errors) or_return
inst.length = u8(min(n, 255))
byte_count += n
}
@@ -56,11 +55,7 @@ encode :: proc(
return
}
// =============================================================================
// Internal
// =============================================================================
@(private="file")
encode_one :: #force_inline proc(
inst: ^Instruction,
pc: u32,

View File

@@ -78,7 +78,9 @@ write_uleb :: #force_inline proc "contextless" (code: []u8, offset: ^u32, value:
for {
b := u8(v & 0x7F)
v >>= 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
}

View File

@@ -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 "?"
}

View File

@@ -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..<count {
fidx := rd_u32(&r) or_break
name := rd_name(&r) or_break
if int(fidx) < len(m.functions) {
m.functions[fidx].name = name
}
}
}
// skip any subsection we do not need to interpret
r.off = payload_end
}
return
}
}
module_destroy :: proc(m: ^Module) {
for t in m.types {
delete(t.params, m.allocator)
delete(t.results, m.allocator)
}
for f in m.functions {
if !f.imported {
delete(f.locals, m.allocator)
}
}
delete(m.sections, m.allocator)
delete(m.types, m.allocator)
delete(m.imports, m.allocator)
delete(m.functions, m.allocator)
delete(m.exports, m.allocator)
m^ = {}
}

View File

@@ -0,0 +1,241 @@
package rexcode_wasm_module
import "core:strings"
import "core:os"
import "core:fmt"
import wasm "../"
print_module :: proc(m: Module) {
sb := strings.builder_make(context.allocator)
defer strings.builder_destroy(&sb)
sbprint_module(&sb, m)
s := strings.to_string(sb)
os.write_string(os.stdout, s)
}
sbprint_module :: proc(sb: ^strings.Builder, m: Module) {
strings.write_string(sb, "WebAssembly Module, Version: ")
write_u64(sb, u64(m.version))
strings.write_byte(sb, '\n')
label_names: map[u32]string
defer delete(label_names)
for f in m.functions {
if f.name != "" {
label_names[f.func_index] = f.name
}
}
relocs_group, _ := parse_relocations(m, m.allocator)
defer delete(relocs_group, m.allocator)
// sections
for sec in m.sections {
strings.write_string(sb, ".")
write_padded(sb, section_name(sec.id), 12)
#partial switch sec.id {
case .CUSTOM:
strings.write_string(sb, " \"")
strings.write_string(sb, sec.name)
strings.write_byte(sb, '"')
case .START:
// do nothing
case:
strings.write_string(sb, " (")
write_u64(sb, u64(sec.count))
strings.write_string(sb, " entries)")
}
strings.write_byte(sb, '\n')
data := m.data[sec.offset:][:sec.size]
section_printing: #partial switch sec.id {
case .DATA:
r := reader(data, 0)
count := rd_u32(&r) or_break section_printing
assert(count == sec.count)
for i in 0..<sec.count {
fmt.sbprintf(sb, " [%d]\n", i)
kind := rd_u32(&r) or_break section_printing
switch kind {
case 2: // memidx + expr + []byte
memidx := rd_u32(&r) or_break section_printing
fmt.sbprintf(sb, " memidx:%d\n", memidx)
fallthrough
case 0: // expr + []byte
relocs: []wasm.Relocation
for rg in relocs_group {
if rg.target_section == sec.id {
relocs = rg.relocs
break
}
}
for r.off < u32(len(r.data)) {
inst, info, next := wasm.decode_one(r.data[r.off:], relocs=relocs, pc=0, targets_allocator=context.temp_allocator) or_break section_printing
r.off += next
if inst.mnemonic == .END {
break
}
wasm.sbprint(sb, {inst}, {info}, nil, &label_names)
}
size := rd_u32(&r) or_break section_printing
fmt.sbprintf(sb, " %q\n", r.data[r.off:][:size])
case 1: // []byte
fmt.sbprintf(sb, " %q\n", r.data[r.off:])
}
}
case .MEMORY:
r := reader(data, 0)
count := rd_u32(&r) or_break section_printing
assert(count == sec.count)
for i in 0..<sec.count {
fmt.sbprintf(sb, " [%d]\n", i)
min, max := rd_limits(&r) or_break section_printing
if max == nil {
fmt.sbprintf(sb, " limits: %v..inf\n", min)
} else {
fmt.sbprintf(sb, " limits: %v..%v\n", min, max)
}
}
}
}
if len(m.imports) > 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)..<width { strings.write_byte(sb, ' ') }
}

View File

@@ -0,0 +1,93 @@
package rexcode_wasm_module
import "base:runtime"
import "core:strings"
import wasm "core:rexcode/wasm"
Reloc_Group :: struct {
target_section: Section_Id, // index of the section these apply to
relocs: []wasm.Relocation,
}
@(require_results)
parse_relocations :: proc(m: Module, allocator: runtime.Allocator) -> (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..<count {
code := rd_byte(&r) or_return
offset := rd_u32(&r) or_return // offset of the field within target_section
index := rd_u32(&r) or_return // symbol / target index
addend: i32 = 0
if reloc_has_addend(code) {
addend = i32(rd_sleb(&r) or_return)
}
t := reloc_type_from_wire(code) or_continue
out[w] = wasm.Relocation{
offset = offset,
label_id = index,
addend = addend,
type = t,
size = reloc_field_size(t),
}
w += 1
}
append(&groups, Reloc_Group{target_section = target, relocs = out[:w]}) or_return
}
reloc_groups = groups[:]
return
}
relocations_destroy :: proc(groups: []Reloc_Group, allocator: runtime.Allocator) {
context.allocator = allocator
for g in groups { delete(g.relocs) }
delete(groups)
}
@(require_results)
reloc_type_from_wire :: proc(code: u8) -> (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
}

View File

@@ -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..<int(inst.operand_count) {
for slot in 0..<inst.operand_count {
strings.write_byte(sb, ' ')
write_operand(sb, &inst.ops[slot], inst.mnemonic, label_names, opts)
}
@@ -106,20 +104,16 @@ sbprint :: proc(
strings.write_string(sb, opts.separator)
}
_ = tokens
_ = label_defs
}
sbprintln :: 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,
) {
sbprint(sb, instructions, inst_info, label_defs, tokens, options, label_names)
sbprint(sb, instructions, inst_info, options, label_names)
strings.write_byte(sb, '\n')
}
@@ -128,118 +122,106 @@ sbprintln :: proc(
// =============================================================================
print :: 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,
) {
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(os.stdout, strings.to_string(sb))
}
println :: 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,
) {
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(os.stdout, strings.to_string(sb))
}
aprint :: 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)
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')