mirror of
https://github.com/odin-lang/Odin.git
synced 2026-07-17 13:11:07 +00:00
Add module parsing
This commit is contained in:
@@ -20,9 +20,11 @@ import "base:runtime"
|
||||
// relocations *are* re-attached: when an input relocation lands on a decoded
|
||||
// index field, that operand is marked symbolic and carries the label id.
|
||||
|
||||
// decode: parse a WASM `expr` byte stream into a single-function, single-block
|
||||
// Module (dataflow = .STACK). The reusable stream decoder is `decode_ops`.
|
||||
decode :: proc(
|
||||
// decode_expr: parse a bare WASM `expr` byte stream (one instruction stream,
|
||||
// no container) into a single-function, single-block Module (dataflow = .STACK).
|
||||
// The full-container module verb is `decode` (parse.odin); the reusable
|
||||
// stream-level decoder both share is `decode_ops`.
|
||||
decode_expr :: proc(
|
||||
data: []u8,
|
||||
m: ^Module,
|
||||
errors: ^[dynamic]Error,
|
||||
@@ -109,7 +111,6 @@ decode_one :: proc(
|
||||
|
||||
form := encoding_form(m)
|
||||
operands := make([dynamic]Operand, allocator)
|
||||
defer if !ok { delete(operands) }
|
||||
|
||||
for k, ki in form.imm {
|
||||
switch k {
|
||||
@@ -166,15 +167,15 @@ decode_one :: proc(
|
||||
|
||||
case .BR_TABLE:
|
||||
count := int(read_uleb(data, &off) or_return)
|
||||
reserve(&operands, len(operands)+count+1)
|
||||
operands_default_index := len(operands)
|
||||
for _ in 0..<count {
|
||||
default_index := len(operands)
|
||||
reserve(&operands, count+1)
|
||||
append(&operands, Operand{}) // dummy for default
|
||||
for i in 0 ..< int(count) {
|
||||
t := read_uleb(data, &off) or_return
|
||||
append(&operands, op_labelidx(u32(t)))
|
||||
}
|
||||
def := read_uleb(data, &off) or_return
|
||||
// default first
|
||||
inject_at(&operands, operands_default_index, op_labelidx(u32(def)))
|
||||
operands[default_index] = op_labelidx(u32(def)) // default first
|
||||
|
||||
case .ZERO_BYTE:
|
||||
if off >= u32(len(data)) {
|
||||
|
||||
@@ -55,6 +55,17 @@ External_Kind :: enum u8 {
|
||||
GLOBAL = 3,
|
||||
}
|
||||
|
||||
// A parsed binary section header: where its *contents* live in the file and how
|
||||
// many entries it declares. Kept so sections the ir core does not model
|
||||
// structurally (table/memory/global/element/data) stay re-readable from `data`.
|
||||
Section :: struct {
|
||||
id: Section_Id,
|
||||
offset: u32, // file offset of the section contents
|
||||
size: u32, // contents length in bytes
|
||||
count: u32, // declared element count (0 for CUSTOM / START)
|
||||
name: string, // custom-section name (borrowed from `data`)
|
||||
}
|
||||
|
||||
// A WASM function signature: value-typed params and results.
|
||||
Func_Type :: struct {
|
||||
params: []Value_Type,
|
||||
@@ -76,10 +87,18 @@ Export :: struct {
|
||||
|
||||
// The WASM module -- the unit the verbs operate on. Embeds the ir core; adds the
|
||||
// container metadata parsed from / emitted to the binary sections.
|
||||
//
|
||||
// After `decode`, `base.functions` spans the whole WASM function index space:
|
||||
// imported functions occupy the low indices (their `blocks` are empty -- an
|
||||
// import has no body) followed by the module-defined functions (each a single
|
||||
// `ir.Block` whose ops are the decoded body). `ir.Function.signature` is the
|
||||
// function's typeidx (an index into `func_types`, by convention), and its
|
||||
// `name` is resolved from the export and "name" custom sections.
|
||||
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
|
||||
@@ -91,6 +110,11 @@ Module :: struct {
|
||||
// A WASM function's declared locals (the code section's local groups), kept
|
||||
// parallel to base.functions since ir.Function has no locals slot.
|
||||
function_locals: [][]Value_Type,
|
||||
|
||||
// --- preserved binary framing (so nothing decoded is lost / is re-readable) ---
|
||||
sections: []Section, // every section header, in file order
|
||||
relocations: []Reloc_Group, // object-file relocations, grouped by target section
|
||||
data: []u8, // borrowed whole-file bytes (table/memory/... re-readable)
|
||||
}
|
||||
|
||||
// A freshly-made WASM module declares STACK dataflow so the shared verbs and the
|
||||
|
||||
539
core/rexcode/ir/wasm/parse.odin
Normal file
539
core/rexcode/ir/wasm/parse.odin
Normal file
@@ -0,0 +1,539 @@
|
||||
// rexcode · Brendan Punsky (dotbmp@github), original author
|
||||
// Ginger Bill (gingerBill@github)
|
||||
|
||||
package rexcode_wasm
|
||||
|
||||
import "base:runtime"
|
||||
import "core:strings"
|
||||
|
||||
// =============================================================================
|
||||
// SECTION: Container decode (a whole .wasm binary module -> ir Module)
|
||||
// =============================================================================
|
||||
//
|
||||
// `decode_ops` / `decode_expr` (decoder.odin) turn a single WASM `expr` -- one
|
||||
// instruction stream -- into ir.Operations. That is only the *code* of one
|
||||
// function; a real `.wasm` file is a container: an 8-byte header (`\0asm`, a
|
||||
// version) followed by a sequence of length-prefixed sections (type / import /
|
||||
// function / table / memory / global / export / start / element / code / data,
|
||||
// plus custom sections). This file is the missing outer layer: `decode` reads
|
||||
// that whole container and populates the full `wasm.Module`.
|
||||
//
|
||||
// How the container maps onto the ir core:
|
||||
//
|
||||
// * TYPE -> `func_types`.
|
||||
// * FUNCTION -> the typeidx per defined function (folded into signatures).
|
||||
// * CODE -> for *each* defined function, the locals go to
|
||||
// `function_locals` and the body `expr` is decoded (via the
|
||||
// shared `decode_ops`) into a single `ir.Block` of Operations
|
||||
// under an `ir.Function`. This is the plural fix: the old
|
||||
// `decode` produced one function; a module has many.
|
||||
// * IMPORT -> `imports`; imported functions also occupy the low function
|
||||
// indices (with empty bodies) so the funcidx space is intact.
|
||||
// * EXPORT -> `exports`, and names attached to the referenced functions.
|
||||
// * START -> `start`.
|
||||
// * reloc.* -> `relocations`; the CODE group is threaded into body decode
|
||||
// so relocatable index fields decode as symbolic refs.
|
||||
//
|
||||
// TABLE / MEMORY / GLOBAL / ELEMENT / DATA are recorded as `sections` and left
|
||||
// re-readable from `data` (the shared core has no structural slot for them and
|
||||
// there is, as yet, no container *emitter* -- the codec's symmetric unit is the
|
||||
// instruction stream, mirroring the original which only parsed the container).
|
||||
//
|
||||
// Structural problems surface as an `ir.Error` (BUFFER_TOO_SHORT for a
|
||||
// truncation, MALFORMED_MODULE otherwise) and `ok = false`; per-instruction
|
||||
// body errors are pushed by `decode_ops` as usual.
|
||||
|
||||
// decode: parse a whole `.wasm` binary module into `m`. Returns the number of
|
||||
// bytes consumed (the file length on success) and whether it fully succeeded.
|
||||
decode :: proc(
|
||||
data: []u8,
|
||||
m: ^Module,
|
||||
errors: ^[dynamic]Error,
|
||||
allocator := context.allocator,
|
||||
) -> (byte_count: u32, ok: bool) {
|
||||
context.allocator = allocator
|
||||
errors_start := u32(len(errors))
|
||||
|
||||
m.base.dataflow = .STACK
|
||||
m.version = WASM_VERSION
|
||||
m.start = -1
|
||||
m.data = data
|
||||
|
||||
if err := parse_container(data, m, errors, allocator); err != nil {
|
||||
append(errors, Error{location = 0, code = parse_error_code(err)})
|
||||
return 0, false
|
||||
}
|
||||
|
||||
byte_count = u32(len(data))
|
||||
ok = u32(len(errors)) == errors_start
|
||||
return
|
||||
}
|
||||
|
||||
@(private="file", require_results)
|
||||
parse_error_code :: proc "contextless" (err: Reader_Error) -> Error_Code {
|
||||
#partial switch e in err {
|
||||
case Parse_Error:
|
||||
if e == .TRUNCATED {
|
||||
return .BUFFER_TOO_SHORT
|
||||
}
|
||||
}
|
||||
return .MALFORMED_MODULE // allocation failure / anything else
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Container walk
|
||||
// =============================================================================
|
||||
|
||||
parse_container :: proc(data: []u8, m: ^Module, errors: ^[dynamic]Error, allocator: runtime.Allocator) -> Reader_Error {
|
||||
r := reader(data, 0)
|
||||
if (rd_u32le_block(&r) or_else 0) != WASM_MAGIC {
|
||||
return .BAD_MAGIC
|
||||
}
|
||||
m.version = rd_u32le_block(&r) or_return
|
||||
|
||||
// Pass 1: collect every section header (id, content offset, size, count).
|
||||
secs: [dynamic]Section
|
||||
secs.allocator = allocator
|
||||
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 .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 only, 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[:]
|
||||
|
||||
// Pass 2: parse the structured sections.
|
||||
func_typeidx: []u32
|
||||
codes: []Code_Body
|
||||
for &sec in m.sections {
|
||||
s := reader(data, sec.offset)
|
||||
#partial switch sec.id {
|
||||
case .TYPE: m.func_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)
|
||||
}
|
||||
}
|
||||
|
||||
// Relocations must be parsed before the bodies: the CODE group is threaded
|
||||
// into body decode so relocatable index fields become symbolic refs.
|
||||
m.relocations = parse_relocations(m^, allocator) or_return
|
||||
|
||||
build_functions(m, func_typeidx, codes, errors, allocator) or_return
|
||||
apply_name_section(m)
|
||||
return nil
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Byte reader (container-level, distinct from leb.odin's stream primitives:
|
||||
// it tracks position and reports structural errors -- TRUNCATED / BAD_*)
|
||||
// =============================================================================
|
||||
|
||||
Parse_Error :: enum u8 {
|
||||
NONE = 0,
|
||||
TRUNCATED, // read past the end of the buffer
|
||||
BAD_MAGIC, // not a `\0asm` module
|
||||
BAD_TYPE_FORM, // a functype did not start with 0x60
|
||||
BAD_SECTION, // section contents extend past the buffer
|
||||
BAD_ULEB, // a ULEB did not terminate within 10 bytes
|
||||
}
|
||||
|
||||
Reader_Error :: union #shared_nil {
|
||||
Parse_Error,
|
||||
runtime.Allocator_Error,
|
||||
}
|
||||
|
||||
Reader :: struct {
|
||||
data: []u8,
|
||||
off: u32,
|
||||
}
|
||||
|
||||
@(private="file", require_results)
|
||||
reader :: proc "contextless" (data: []u8, off: u32) -> Reader {
|
||||
return Reader{data = data, off = off}
|
||||
}
|
||||
|
||||
@(private="file", 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]
|
||||
r.off += 1
|
||||
return b, .NONE
|
||||
}
|
||||
|
||||
@(private="file", 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]) |
|
||||
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
|
||||
}
|
||||
|
||||
@(private="file", require_results)
|
||||
rd_uleb :: proc "contextless" (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
|
||||
}
|
||||
|
||||
@(private="file", require_results)
|
||||
rd_sleb :: proc "contextless" (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
|
||||
}
|
||||
|
||||
@(private="file", require_results)
|
||||
rd_u32 :: proc "contextless" (r: ^Reader) -> (u32, Parse_Error) {
|
||||
v, err := rd_uleb(r)
|
||||
return u32(v), err
|
||||
}
|
||||
|
||||
@(private="file", 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 }
|
||||
val = string(r.data[r.off:][:n])
|
||||
r.off += n
|
||||
return
|
||||
}
|
||||
|
||||
@(private="file", 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
|
||||
for &v in out {
|
||||
v = Value_Type(rd_byte(r) or_return)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@(private="file", 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
|
||||
if flags & 0x01 != 0 {
|
||||
max = rd_uleb(r) or_return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Structured section parsers
|
||||
// =============================================================================
|
||||
|
||||
@(private="file", 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 &ft in out {
|
||||
form := rd_byte(r) or_return
|
||||
if form != 0x60 { return out, .BAD_TYPE_FORM }
|
||||
ft.params = rd_valtype_vec(r, allocator) or_return
|
||||
ft.results = rd_valtype_vec(r, allocator) or_return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@(private="file", 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
|
||||
}
|
||||
|
||||
@(private="file", 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
|
||||
}
|
||||
|
||||
@(private="file", 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
|
||||
}
|
||||
|
||||
// One code entry's locals (still compressed as `count x type` groups) and the
|
||||
// file span of its body `expr`.
|
||||
@(private="file")
|
||||
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")
|
||||
Local_Group :: struct {
|
||||
count: u32,
|
||||
type: Value_Type,
|
||||
}
|
||||
|
||||
@(private="file", 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 &cb in out {
|
||||
total := rd_u32(r) or_return
|
||||
body_end := r.off + total
|
||||
|
||||
nl := rd_u32(r) or_return
|
||||
locals := make([]Local_Group, int(nl), allocator) or_return
|
||||
for &g in locals {
|
||||
g.count = rd_u32(r) or_return
|
||||
g.type = Value_Type(rd_byte(r) or_return)
|
||||
}
|
||||
cb = 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 expr to the next entry
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Function index space (imports ++ defined) with eagerly-decoded bodies
|
||||
// =============================================================================
|
||||
|
||||
@(private="file", require_results)
|
||||
build_functions :: proc(
|
||||
m: ^Module,
|
||||
func_typeidx: []u32,
|
||||
codes: []Code_Body,
|
||||
errors: ^[dynamic]Error,
|
||||
allocator: runtime.Allocator,
|
||||
) -> Reader_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
|
||||
locals := make([][]Value_Type, total, allocator) or_return
|
||||
|
||||
// CODE-section file offset, to rebase its relocations to body-relative.
|
||||
code_off: u32 = 0
|
||||
for s in m.sections {
|
||||
if s.id == .CODE { code_off = s.offset; break }
|
||||
}
|
||||
code_relocs := relocations_for_section(m.relocations, .CODE)
|
||||
|
||||
// Imported functions: low indices, no body.
|
||||
idx := 0
|
||||
for imp in m.imports {
|
||||
(imp.kind == .FUNC) or_continue
|
||||
funcs[idx] = Function{
|
||||
name = imp.field_name,
|
||||
signature = Type_Ref(imp.index), // typeidx into func_types
|
||||
}
|
||||
idx += 1
|
||||
}
|
||||
|
||||
// Defined functions: decode each body into a single ir.Block.
|
||||
for tidx, i in func_typeidx {
|
||||
fi := num_imports + i
|
||||
f := Function{signature = Type_Ref(tidx)}
|
||||
|
||||
if i < len(codes) {
|
||||
cb := codes[i]
|
||||
|
||||
// Expand the compressed local groups to a flat value-type list.
|
||||
nlocals := 0
|
||||
for g in cb.locals { nlocals += int(g.count) }
|
||||
flat := make([]Value_Type, nlocals, allocator) or_return
|
||||
w := 0
|
||||
for g in cb.locals {
|
||||
for _ in 0 ..< int(g.count) { flat[w] = g.type; w += 1 }
|
||||
}
|
||||
locals[fi] = flat
|
||||
|
||||
// Decode the body, threading the (rebased) CODE relocations.
|
||||
body := m.data[cb.body_offset:][:cb.body_size]
|
||||
body_rel := cb.body_offset - code_off
|
||||
body_relocs := relocs_for_body(code_relocs, body_rel, cb.body_size, allocator)
|
||||
|
||||
ops, _, _ := decode_ops(body, body_relocs, errors, allocator)
|
||||
blocks := make([]Block, 1, allocator) or_return
|
||||
blocks[0] = Block{id = ID_NONE, ops = ops}
|
||||
f.blocks = blocks
|
||||
}
|
||||
funcs[fi] = f
|
||||
}
|
||||
|
||||
// Names from the export section (kept if the name section overrides later).
|
||||
for e in m.exports {
|
||||
if e.kind == .FUNC && int(e.index) < total && funcs[e.index].name == "" {
|
||||
funcs[e.index].name = e.name
|
||||
}
|
||||
}
|
||||
|
||||
m.base.functions = funcs
|
||||
m.function_locals = locals
|
||||
return nil
|
||||
}
|
||||
|
||||
// Filter a section's relocations down to one body and rebase their offsets to
|
||||
// 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)
|
||||
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
|
||||
hi := body_off_in_sec + size
|
||||
out: [dynamic]Relocation
|
||||
out.allocator = allocator
|
||||
for rr in all {
|
||||
if rr.offset >= lo && rr.offset < hi {
|
||||
r2 := rr
|
||||
r2.offset = rr.offset - lo
|
||||
append(&out, r2)
|
||||
}
|
||||
}
|
||||
return out[:]
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Relocations (object-file `reloc.*` custom sections)
|
||||
// =============================================================================
|
||||
|
||||
@(private="file", require_results)
|
||||
parse_relocations :: proc(m: Module, allocator: runtime.Allocator) -> (groups_out: []Reloc_Group, err: Reader_Error) {
|
||||
groups: [dynamic]Reloc_Group
|
||||
groups.allocator = 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([]Relocation, int(count), allocator) or_return
|
||||
w := 0
|
||||
for _ in 0 ..< count {
|
||||
code := rd_byte(&r) or_return
|
||||
offset := rd_u32(&r) or_return // field offset 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] = 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
|
||||
}
|
||||
groups_out = groups[:]
|
||||
return
|
||||
}
|
||||
|
||||
// Override function names with the "name" custom section's function-name
|
||||
// subsection (id 1) when present -- these are the authoritative debug names.
|
||||
@(private="file")
|
||||
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 reach 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 { // function names
|
||||
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.base.functions) {
|
||||
m.base.functions[fidx].name = name
|
||||
}
|
||||
}
|
||||
}
|
||||
r.off = payload_end // skip subsections we do not interpret
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -41,3 +41,62 @@ Relocation :: struct #packed {
|
||||
inst_idx: u16,
|
||||
}
|
||||
#assert(size_of(Relocation) == 16)
|
||||
|
||||
// A set of relocations that apply to one section (parsed from a `reloc.<name>`
|
||||
// custom section). Grouped by the section they target so the decoder can hand
|
||||
// the CODE-section group to each function body it decodes.
|
||||
Reloc_Group :: struct {
|
||||
target_section: Section_Id,
|
||||
relocs: []Relocation,
|
||||
}
|
||||
|
||||
// The relocations (if any) that patch fields in the given section.
|
||||
@(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 }
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Object-file (tool-conventions) relocation wire format
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// Decode a relocation type byte from a `reloc.*` custom section. `ok` is false
|
||||
// for a type this codec does not model (the entry is then skipped).
|
||||
@(require_results)
|
||||
reloc_type_from_wire :: proc "contextless" (code: u8) -> (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 forms (8,9) carry a trailing
|
||||
// signed-LEB addend; the index-type relocations do not.
|
||||
@(require_results)
|
||||
reloc_has_addend :: proc "contextless" (code: u8) -> bool {
|
||||
switch code {
|
||||
case 3, 4, 5, 8, 9: return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// On-wire field width of a relocation, in bytes.
|
||||
@(require_results)
|
||||
reloc_field_size :: proc "contextless" (t: 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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user