mirror of
https://github.com/odin-lang/Odin.git
synced 2026-07-22 15:32:35 +00:00
224 lines
6.1 KiB
Odin
224 lines
6.1 KiB
Odin
// rexcode · Brendan Punsky (dotbmp@github), original author
|
|
// Ginger Bill (gingerBill@github)
|
|
|
|
package rexcode_wasm
|
|
|
|
import "core:math/bits"
|
|
|
|
// =============================================================================
|
|
// SECTION: Encoder (Module -> WASM instruction byte stream)
|
|
// =============================================================================
|
|
//
|
|
// Variable-length, byte-oriented, LEB128-heavy. Encoding is a single forward
|
|
// pass: each operation writes its opcode (a byte, or a prefix byte plus an
|
|
// unsigned-LEB sub-opcode) followed by its immediates, advancing a byte cursor.
|
|
//
|
|
// WASM has no PC-relative branches (control flow uses structured label depths),
|
|
// so there is no second resolution pass -- which is exactly why the ir verbs
|
|
// (docs/ir_design.md §4) drop the ISA `label_defs`/`resolve`/`base_address`.
|
|
// Relocations *are* produced, for symbolic index references (see op_label), and
|
|
// returned for a linker to patch; symbolic indices are laid down as fixed-width
|
|
// 5-byte LEB placeholders so the patched value always fits.
|
|
//
|
|
// The reusable core is `encode_ops` (an operation stream = a WASM `expr`). The
|
|
// container `encode` verb (write.odin) wraps those bodies in the CODE-section
|
|
// framing and emits the surrounding sections, producing a whole `.wasm` file;
|
|
// this file owns only the instruction-stream level.
|
|
|
|
MAX_OPCODE_SIZE :: 3 // prefix byte + two-byte unsigned-LEB sub-opcode (SIMD reaches 0x113)
|
|
|
|
@(require_results)
|
|
encode_max_code_size :: #force_inline proc "contextless" (n: int) -> int {
|
|
// Worst case per op without a br_table: a 3-byte opcode plus the largest
|
|
// single immediate, v128.const's 16 raw bytes. br_table is unbounded in its
|
|
// target count; callers encoding those should size from the target totals.
|
|
return n * 24
|
|
}
|
|
@(require_results)
|
|
encode_max_relocation_count :: #force_inline proc "contextless" (n: int) -> int {
|
|
return n
|
|
}
|
|
|
|
// encode_ops: the reusable instruction-stream encoder (a WASM `expr`).
|
|
encode_ops :: proc(ops: []Operation, code: []u8, relocs: ^[dynamic]Relocation, errors: ^[dynamic]Error) -> (byte_count: u32, ok: bool) {
|
|
errors_start := u32(len(errors))
|
|
for &op, i in ops {
|
|
n := encode_operation(&op, byte_count, u16(i), code, relocs, errors) or_return
|
|
byte_count += n
|
|
}
|
|
ok = u32(len(errors)) == errors_start
|
|
return
|
|
}
|
|
|
|
encode_operation :: proc(
|
|
op: ^Operation,
|
|
pc: u32,
|
|
op_index: u16,
|
|
code: []u8,
|
|
relocs: ^[dynamic]Relocation,
|
|
errors: ^[dynamic]Error,
|
|
) -> (size: u32, ok: bool) {
|
|
opcode := Opcode(op.opcode)
|
|
if opcode == .INVALID {
|
|
append(errors, Error{location = u32(op_index), code = .INVALID_OPCODE})
|
|
return
|
|
}
|
|
form := encoding_form(opcode)
|
|
|
|
need := encoded_size(op, form)
|
|
if pc + need > u32(len(code)) {
|
|
append(errors, Error{location = u32(op_index), code = .BUFFER_OVERFLOW})
|
|
return
|
|
}
|
|
|
|
off := pc
|
|
|
|
// Opcode (and prefix sub-opcode).
|
|
if form.prefix == PREFIX_NONE {
|
|
code[off] = u8(form.opcode)
|
|
off += 1
|
|
} else {
|
|
code[off] = form.prefix
|
|
off += 1
|
|
write_uleb(code, &off, u64(form.opcode))
|
|
}
|
|
|
|
// Immediates, walked in declaration order with an operand cursor.
|
|
opi := 0
|
|
for k in form.imm {
|
|
switch k {
|
|
case .NONE:
|
|
// nothing
|
|
|
|
case .BLOCKTYPE, .I32, .I64:
|
|
write_sleb(code, &off, op.operands[opi].imm)
|
|
opi += 1
|
|
|
|
case .F32:
|
|
write_u32_block(code, &off, u32(op.operands[opi].imm))
|
|
opi += 1
|
|
|
|
case .F64:
|
|
write_u64_block(code, &off, u64(op.operands[opi].imm))
|
|
opi += 1
|
|
|
|
case .IDX:
|
|
o := op.operands[opi]
|
|
if operand_symbolic(o) {
|
|
append(relocs, Relocation{
|
|
offset = off, label_id = operand_index(o), addend = 0,
|
|
type = reloc_type_for(operand_index_kind(o)), size = 5, inst_idx = op_index,
|
|
})
|
|
write_uleb_padded5(code, &off, u64(operand_index(o)))
|
|
} else {
|
|
write_uleb(code, &off, u64(operand_index(o)))
|
|
}
|
|
opi += 1
|
|
|
|
case .MEMARG:
|
|
ma := operand_memarg(op.operands[opi])
|
|
// NOTE(bill): stored as log2 even though the spec text reads otherwise.
|
|
align := bits.log2(u64(ma.align))
|
|
write_uleb(code, &off, align)
|
|
write_uleb(code, &off, u64(ma.offset))
|
|
opi += 1
|
|
|
|
case .REFTYPE:
|
|
code[off] = u8(op.operands[opi].imm)
|
|
off += 1
|
|
opi += 1
|
|
|
|
case .BR_TABLE:
|
|
// operands = [default, case0, case1, ...] -- every entry a label depth.
|
|
cases := op.operands[opi + 1:]
|
|
write_uleb(code, &off, u64(len(cases)))
|
|
for c in cases {
|
|
write_uleb(code, &off, u64(operand_index(c)))
|
|
}
|
|
write_uleb(code, &off, u64(operand_index(op.operands[opi]))) // default
|
|
opi = len(op.operands)
|
|
|
|
case .ZERO_BYTE:
|
|
code[off] = 0x00
|
|
off += 1
|
|
|
|
case .LANE:
|
|
code[off] = u8(op.operands[opi].imm)
|
|
off += 1
|
|
opi += 1
|
|
|
|
case .LANES16:
|
|
bytes := operand_v128(op.operands[opi], op.operands[opi + 1])
|
|
for bb in bytes {
|
|
code[off] = bb
|
|
off += 1
|
|
}
|
|
opi += 2
|
|
}
|
|
}
|
|
|
|
return off - pc, true
|
|
}
|
|
|
|
@(private="file")
|
|
encoded_size :: proc(op: ^Operation, form: ^Encoding) -> u32 {
|
|
size: u32 = 1
|
|
if form.prefix != PREFIX_NONE {
|
|
size += uleb_size(u64(form.opcode))
|
|
}
|
|
opi := 0
|
|
for k in form.imm {
|
|
switch k {
|
|
case .NONE:
|
|
case .BLOCKTYPE, .I32, .I64:
|
|
size += sleb_size(op.operands[opi].imm)
|
|
opi += 1
|
|
case .F32:
|
|
size += 4
|
|
opi += 1
|
|
case .F64:
|
|
size += 8
|
|
opi += 1
|
|
case .IDX:
|
|
o := op.operands[opi]
|
|
size += operand_symbolic(o) ? 5 : uleb_size(u64(operand_index(o)))
|
|
opi += 1
|
|
case .MEMARG:
|
|
ma := operand_memarg(op.operands[opi])
|
|
size += uleb_size(bits.log2(u64(ma.align))) + uleb_size(u64(ma.offset))
|
|
opi += 1
|
|
case .REFTYPE:
|
|
size += 1
|
|
opi += 1
|
|
case .BR_TABLE:
|
|
cases := op.operands[opi + 1:]
|
|
size += uleb_size(u64(len(cases)))
|
|
for c in cases {
|
|
size += uleb_size(u64(operand_index(c)))
|
|
}
|
|
size += uleb_size(u64(operand_index(op.operands[opi])))
|
|
opi = len(op.operands)
|
|
case .ZERO_BYTE:
|
|
size += 1
|
|
case .LANE:
|
|
size += 1
|
|
opi += 1
|
|
case .LANES16:
|
|
size += 16
|
|
opi += 2
|
|
}
|
|
}
|
|
return size
|
|
}
|
|
|
|
@(private="file")
|
|
reloc_type_for :: #force_inline proc "contextless" (k: Index_Kind) -> Relocation_Type {
|
|
#partial switch k {
|
|
case .FUNC: return .FUNCTION_INDEX_LEB
|
|
case .TYPE: return .TYPE_INDEX_LEB
|
|
case .GLOBAL: return .GLOBAL_INDEX_LEB
|
|
case .TABLE: return .TABLE_NUMBER_LEB
|
|
}
|
|
return .FUNCTION_INDEX_LEB
|
|
}
|