mirror of
https://github.com/odin-lang/Odin.git
synced 2026-07-22 15:32:35 +00:00
156 lines
5.9 KiB
Odin
156 lines
5.9 KiB
Odin
// rexcode · Brendan Punsky (dotbmp@github), original author
|
|
// Ginger Bill (gingerBill@github)
|
|
|
|
package rexcode_wasm
|
|
|
|
import "base:runtime"
|
|
|
|
// =============================================================================
|
|
// SECTION: Builder (constructing ir.Operations for a WASM function body)
|
|
// =============================================================================
|
|
//
|
|
// The IR leaf is `ir.Operation` -- an opcode (u16), a variable-arity `[]Operand`
|
|
// (caller-owned, unlike the old ISA `Instruction`'s inline [2]Operand), and an
|
|
// optional typed `Result` (always `.id == ID_NONE` here: WASM is a stack machine
|
|
// and names no results). So every builder that carries operands must allocate
|
|
// their backing store; the `Builder` below owns that store and accumulates ops
|
|
// into the current block, mirroring `spirv.Builder`.
|
|
|
|
// Operation flags derived from the opcode's ENCODING_TABLE form, plus the
|
|
// terminator bit for the ops that end a stack-IR region.
|
|
@(require_results)
|
|
op_flags_for :: proc "contextless" (opcode: Opcode) -> Operation_Flags {
|
|
e := ENCODING_TABLE[opcode]
|
|
f: Operation_Flags
|
|
f.control = e.flags.control
|
|
f.memory = e.flags.memory
|
|
#partial switch opcode {
|
|
case .RETURN, .UNREACHABLE,
|
|
.BR, .BR_TABLE,
|
|
.RETURN_CALL, .RETURN_CALL_INDIRECT:
|
|
f.terminator = true
|
|
}
|
|
return f
|
|
}
|
|
|
|
// The stack-IR result: WASM defines no SSA value, so every operation's result is
|
|
// empty. (Kept as a helper so the intent is explicit at every call site.)
|
|
@(require_results)
|
|
no_result :: #force_inline proc "contextless" () -> Result {
|
|
return Result{id = ID_NONE, type = TYPE_NONE}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Stateless construction (caller owns `operands`)
|
|
// -----------------------------------------------------------------------------
|
|
|
|
// Build an Operation over an already-owned operand slice (no allocation).
|
|
@(require_results)
|
|
operation :: #force_inline proc "contextless" (opcode: Opcode, operands: []Operand = nil) -> Operation {
|
|
return Operation{
|
|
opcode = u16(opcode),
|
|
operands = operands,
|
|
result = no_result(),
|
|
flags = op_flags_for(opcode),
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Builder
|
|
// -----------------------------------------------------------------------------
|
|
|
|
Builder :: struct {
|
|
alloc: runtime.Allocator,
|
|
ops: [dynamic]Operation, // current block
|
|
}
|
|
|
|
@(require_results)
|
|
builder_make :: proc(allocator := context.allocator) -> Builder {
|
|
b: Builder
|
|
b.alloc = allocator
|
|
b.ops.allocator = allocator
|
|
return b
|
|
}
|
|
|
|
// Detach the accumulated operations as a block body (and reset for the next).
|
|
@(require_results)
|
|
take_block :: proc(b: ^Builder, label: Id = ID_NONE) -> Block {
|
|
blk := Block{id = label, ops = b.ops[:]}
|
|
b.ops = nil
|
|
b.ops.allocator = b.alloc
|
|
return blk
|
|
}
|
|
|
|
// Stable per-operation operand backing.
|
|
@(require_results)
|
|
opbuf :: proc(b: ^Builder, ops: ..Operand) -> []Operand {
|
|
if len(ops) == 0 {
|
|
return nil
|
|
}
|
|
buf := make([]Operand, len(ops), b.alloc)
|
|
copy(buf, ops)
|
|
return buf
|
|
}
|
|
|
|
// Append an operation with the given operands, allocating their backing.
|
|
emit :: proc(b: ^Builder, opcode: Opcode, operands: ..Operand) {
|
|
append(&b.ops, operation(opcode, opbuf(b, ..operands)))
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Convenience emitters (a representative set; `emit` covers the rest)
|
|
// -----------------------------------------------------------------------------
|
|
|
|
emit_none :: proc(b: ^Builder, opcode: Opcode) { emit(b, opcode) }
|
|
|
|
emit_i32 :: proc(b: ^Builder, v: i32) { emit(b, .I32_CONST, op_i32(v)) }
|
|
emit_i64 :: proc(b: ^Builder, v: i64) { emit(b, .I64_CONST, op_i64(v)) }
|
|
emit_f32 :: proc(b: ^Builder, v: f32) { emit(b, .F32_CONST, op_f32(v)) }
|
|
emit_f64 :: proc(b: ^Builder, v: f64) { emit(b, .F64_CONST, op_f64(v)) }
|
|
|
|
emit_local_get :: proc(b: ^Builder, n: u32) { emit(b, .LOCAL_GET, op_local(n)) }
|
|
emit_local_set :: proc(b: ^Builder, n: u32) { emit(b, .LOCAL_SET, op_local(n)) }
|
|
emit_local_tee :: proc(b: ^Builder, n: u32) { emit(b, .LOCAL_TEE, op_local(n)) }
|
|
emit_global_get :: proc(b: ^Builder, n: u32) { emit(b, .GLOBAL_GET, op_global(n)) }
|
|
emit_global_set :: proc(b: ^Builder, n: u32) { emit(b, .GLOBAL_SET, op_global(n)) }
|
|
|
|
emit_call :: proc(b: ^Builder, funcidx: u32) { emit(b, .CALL, op_func(funcidx)) }
|
|
emit_call_indirect :: proc(b: ^Builder, typeidx: u32, tableidx: u32 = 0) {
|
|
emit(b, .CALL_INDIRECT, op_typeidx(typeidx), op_table(tableidx))
|
|
}
|
|
|
|
emit_block :: proc(b: ^Builder, bt: Block_Type = .EMPTY) { emit(b, .BLOCK, op_blocktype(bt)) }
|
|
emit_loop :: proc(b: ^Builder, bt: Block_Type = .EMPTY) { emit(b, .LOOP, op_blocktype(bt)) }
|
|
emit_if :: proc(b: ^Builder, bt: Block_Type = .EMPTY) { emit(b, .IF, op_blocktype(bt)) }
|
|
emit_else :: proc(b: ^Builder) { emit(b, .ELSE) }
|
|
emit_end :: proc(b: ^Builder) { emit(b, .END) }
|
|
|
|
emit_br :: proc(b: ^Builder, depth: u32) { emit(b, .BR, op_labelidx(depth)) }
|
|
emit_br_if :: proc(b: ^Builder, depth: u32) { emit(b, .BR_IF, op_labelidx(depth)) }
|
|
|
|
// br_table: operands are [default, case0, case1, ...], every entry a label depth.
|
|
emit_br_table :: proc(b: ^Builder, targets: []u32, default_depth: u32) {
|
|
buf := make([]Operand, len(targets)+1, b.alloc)
|
|
buf[0] = op_labelidx(default_depth)
|
|
for t, i in targets {
|
|
buf[i+1] = op_labelidx(t)
|
|
}
|
|
append(&b.ops, operation(.BR_TABLE, buf))
|
|
}
|
|
|
|
emit_return :: proc(b: ^Builder) { emit(b, .RETURN) }
|
|
emit_unreachable :: proc(b: ^Builder) { emit(b, .UNREACHABLE) }
|
|
|
|
emit_load :: proc(b: ^Builder, opcode: Opcode, ma: Memarg) { emit(b, opcode, op_memarg(ma)) }
|
|
emit_store :: proc(b: ^Builder, opcode: Opcode, ma: Memarg) { emit(b, opcode, op_memarg(ma)) }
|
|
|
|
// v128.const / i8x16.shuffle: the 16-byte immediate as two ATTRIBUTE halves.
|
|
emit_v128_const :: proc(b: ^Builder, value: [16]u8) {
|
|
lo, hi := op_v128(value)
|
|
emit(b, .V128_CONST, lo, hi)
|
|
}
|
|
emit_shuffle :: proc(b: ^Builder, lanes: [16]u8) {
|
|
lo, hi := op_v128(lanes)
|
|
emit(b, .I8X16_SHUFFLE, lo, hi)
|
|
}
|