// Expect retcode = 0 // SIR builder (IrBuilder). Split from ir.spl: stage1 ast2ir // infinitely recurses on same-file @self nesting; cross-file is fine. type .{ .Error } = @import("utils/error.spl"); type .{ .Vec } = @import("utils/vec.spl"); type .{ .ByteView } = @import("utils/byte_view.spl"); type .{ .ByteBuffer } = @import("utils/byte_buffer.spl"); type .{ .Span, .span_none } = @import("./diag.spl"); type TypeTable = @import("./type_table.spl"); type Attr = @import("./attr.spl"); type Ir = @import("./ir.spl"); type .{ .IrKind, .ConstLit, .IrEntry, .IrFunc, .IrBody } = @import("./ir.spl"); fn list_at(v: Vec, i: usize): usize { if i >= v.len { ret 0; } ret ((v.data as *usize) + i).*; } // ---- builder ---- type FixKind = enum { BrTrue, BrFalse, Jmp } type Fix = struct { node: usize, slot: FixKind, block: usize, } type IrBuilder = @self; ir: Ir, dbg: Span, current_fn: usize, current_block: usize, blocks: Vec, // usize fixups: Vec, // Fix terminated: bool, fn builder_alloc(self: *IrBuilder, kind: IrKind): usize { var ref = Ir.alloc_node(&self.ir, self.current_fn); if ref == 0 { ret 0; } var e = Ir.node(&self.ir, self.current_fn, ref); if e != null { e.kind = kind; e.dbg = self.dbg; } if self.current_block >= self.blocks.len { ret ref; } match Vec.get(&self.blocks, self.current_block) { .Ok(p) => { var slot = p as *usize; if slot.* == 0 { slot.* = ref; } }, .Err(error) => {}, } ret ref; } fn builder_new(): IrBuilder { ret IrBuilder { .ir = Ir.new(), .dbg = span_none(), .current_fn = 0, .current_block = 0, .blocks = Vec.blank(@sizeof(usize), @alignof(usize)), .fixups = Vec.blank(@sizeof(Fix), @alignof(Fix)), .terminated = false, }; } fn builder_drop(self: *IrBuilder) { Ir.drop(&self.ir); Vec.drop(&self.blocks); Vec.drop(&self.fixups); } fn fn_begin(self: *IrBuilder, name: ByteView, fn_tid: usize): usize { if self.current_fn != 0 { fn_end(self); } var fid = Ir.alloc_fn(&self.ir); var f = Ir.func(&self.ir, fid); if f != null { f.name = name; f.fn_tid = fn_tid; } self.current_fn = fid; self.current_block = 0; self.blocks.len = 0; self.fixups.len = 0; self.terminated = false; ret fid; } fn fn_reserve(self: *IrBuilder, name: ByteView, fn_tid: usize): usize { var fid = Ir.alloc_fn(&self.ir); var f = Ir.func(&self.ir, fid); if f != null { f.name = name; f.fn_tid = fn_tid; } ret fid; } fn fn_begin_at(self: *IrBuilder, fid: usize) { if self.current_fn != 0 && self.current_fn != fid { fn_end(self); } self.current_fn = fid; self.current_block = 0; self.blocks.len = 0; self.fixups.len = 0; self.terminated = false; } fn fn_end(self: *IrBuilder) { var k: usize = 0; while k < self.fixups.len { match Vec.get(&self.fixups, k) { .Ok(fp) => { var fx = fp as *Fix; var e = Ir.node(&self.ir, self.current_fn, fx.node); if e != null { var target: usize = 0; if fx.block < self.blocks.len { target = list_at(self.blocks, fx.block); } if target != 0 { if @enum2int(fx.slot) == @enum2int(FixKind.BrTrue) { match e.body { .Br(b) => { b.true_label = target; e.body = .Br(b); }, _ => {} } } else if @enum2int(fx.slot) == @enum2int(FixKind.BrFalse) { match e.body { .Br(b) => { b.false_label = target; e.body = .Br(b); }, _ => {} } } else { match e.body { .Jmp(j) => { j.label = target; e.body = .Jmp(j); }, _ => {} } } } } }, .Err(error) => {}, } k = k + 1; } self.current_block = 0; self.blocks.len = 0; self.fixups.len = 0; self.terminated = false; } fn block_begin(self: *IrBuilder): usize { var zero: usize = 0; match Vec.push(&self.blocks, &zero as *u8) { .Ok => {}, .Err(error) => {} } var block = self.blocks.len - 1; self.current_block = block; self.terminated = false; ret block; } fn block_end(self: *IrBuilder) { // terminated 与空块校验在 stage1 为 Panic;这里保持同样约束但不终止进程。 } // ---- arith / cmp / cast ---- fn set_arith(self: *IrBuilder, kind: IrKind, tid: usize, left: usize, right: usize): usize { var ref = builder_alloc(self, kind); var e = Ir.node(&self.ir, self.current_fn, ref); if e != null { e.body = .Arith(.{ .tid = tid, .left = left, .right = right }); } ret ref; } fn set_cmp(self: *IrBuilder, kind: IrKind, tid: usize, a: usize, b: usize): usize { var ref = builder_alloc(self, kind); var e = Ir.node(&self.ir, self.current_fn, ref); if e != null { e.body = .Cmp(.{ .tid = tid, .a = a, .b = b }); } ret ref; } fn set_cast(self: *IrBuilder, kind: IrKind, from_tid: usize, to_tid: usize, val: usize): usize { var ref = builder_alloc(self, kind); var e = Ir.node(&self.ir, self.current_fn, ref); if e != null { e.body = .Cast(.{ .from_tid = from_tid, .to_tid = to_tid, .val = val }); } ret ref; } fn arith_add(self: *IrBuilder, t: usize, l: usize, r: usize): usize { ret set_arith(self, .ArithAdd, t, l, r); } fn arith_sub(self: *IrBuilder, t: usize, l: usize, r: usize): usize { ret set_arith(self, .ArithSub, t, l, r); } fn arith_mul(self: *IrBuilder, t: usize, l: usize, r: usize): usize { ret set_arith(self, .ArithMul, t, l, r); } fn arith_div(self: *IrBuilder, t: usize, l: usize, r: usize): usize { ret set_arith(self, .ArithDiv, t, l, r); } fn arith_rem(self: *IrBuilder, t: usize, l: usize, r: usize): usize { ret set_arith(self, .ArithRem, t, l, r); } fn arith_and(self: *IrBuilder, t: usize, l: usize, r: usize): usize { ret set_arith(self, .ArithAnd, t, l, r); } fn arith_or(self: *IrBuilder, t: usize, l: usize, r: usize): usize { ret set_arith(self, .ArithOr, t, l, r); } fn arith_xor(self: *IrBuilder, t: usize, l: usize, r: usize): usize { ret set_arith(self, .ArithXor, t, l, r); } fn arith_shl(self: *IrBuilder, t: usize, l: usize, r: usize): usize { ret set_arith(self, .ArithShl, t, l, r); } fn arith_shr(self: *IrBuilder, t: usize, l: usize, r: usize): usize { ret set_arith(self, .ArithShr, t, l, r); } fn arith_neg(self: *IrBuilder, t: usize, v: usize): usize { ret set_arith(self, .ArithNeg, t, v, 0); } fn arith_abs(self: *IrBuilder, t: usize, v: usize): usize { ret set_arith(self, .ArithAbs, t, v, 0); } fn arith_not(self: *IrBuilder, t: usize, v: usize): usize { ret set_arith(self, .ArithNot, t, v, 0); } fn cmp_eq(self: *IrBuilder, t: usize, a: usize, b: usize): usize { ret set_cmp(self, .CmpEq, t, a, b); } fn cmp_ne(self: *IrBuilder, t: usize, a: usize, b: usize): usize { ret set_cmp(self, .CmpNe, t, a, b); } fn cmp_lt(self: *IrBuilder, t: usize, a: usize, b: usize): usize { ret set_cmp(self, .CmpLt, t, a, b); } fn cmp_le(self: *IrBuilder, t: usize, a: usize, b: usize): usize { ret set_cmp(self, .CmpLe, t, a, b); } fn cmp_gt(self: *IrBuilder, t: usize, a: usize, b: usize): usize { ret set_cmp(self, .CmpGt, t, a, b); } fn cmp_ge(self: *IrBuilder, t: usize, a: usize, b: usize): usize { ret set_cmp(self, .CmpGe, t, a, b); } fn cast_trunc(self: *IrBuilder, f: usize, t: usize, v: usize): usize { ret set_cast(self, .CastTrunc, f, t, v); } fn cast_zext(self: *IrBuilder, f: usize, t: usize, v: usize): usize { ret set_cast(self, .CastZext, f, t, v); } fn cast_sext(self: *IrBuilder, f: usize, t: usize, v: usize): usize { ret set_cast(self, .CastSext, f, t, v); } fn cast_fext(self: *IrBuilder, f: usize, t: usize, v: usize): usize { ret set_cast(self, .CastFext, f, t, v); } fn cast_ftrunc(self: *IrBuilder, f: usize, t: usize, v: usize): usize { ret set_cast(self, .CastFtrunc, f, t, v); } fn cast_bitcast(self: *IrBuilder, f: usize, t: usize, v: usize): usize { ret set_cast(self, .CastBitcast, f, t, v); } fn cast_ptr2int(self: *IrBuilder, f: usize, t: usize, v: usize): usize { ret set_cast(self, .CastPtr2int, f, t, v); } fn cast_int2ptr(self: *IrBuilder, f: usize, t: usize, v: usize): usize { ret set_cast(self, .CastInt2ptr, f, t, v); } fn cast_bool2int(self: *IrBuilder, f: usize, t: usize, v: usize): usize { ret set_cast(self, .CastBool2int, f, t, v); } fn case_int2float(self: *IrBuilder, f: usize, t: usize, v: usize): usize { ret set_cast(self, .CaseInt2float, f, t, v); } fn case_float2int(self: *IrBuilder, f: usize, t: usize, v: usize): usize { ret set_cast(self, .CaseFloat2int, f, t, v); } // ---- memory ---- fn mem_alloca(self: *IrBuilder, tid: usize, count: usize): usize { var ref = builder_alloc(self, .MemAlloca); var e = Ir.node(&self.ir, self.current_fn, ref); if e != null { e.body = .MemAlloc(.{ .tid = tid, .count = count }); } ret ref; } fn mem_global_alloc(self: *IrBuilder, tid: usize, const_node: usize): usize { var ref = builder_alloc(self, .MemGlobalAlloc); var e = Ir.node(&self.ir, self.current_fn, ref); if e != null { e.body = .MemGlobalAlloc(.{ .tid = tid, .const_node = const_node }); } ret ref; } fn mem_load(self: *IrBuilder, tid: usize, ptr: usize): usize { var ref = builder_alloc(self, .MemLoad); var e = Ir.node(&self.ir, self.current_fn, ref); if e != null { e.body = .MemLoad(.{ .tid = tid, .ptr = ptr }); } ret ref; } fn mem_store(self: *IrBuilder, tid: usize, ptr: usize, val: usize): usize { var ref = builder_alloc(self, .MemStore); var e = Ir.node(&self.ir, self.current_fn, ref); if e != null { e.body = .MemStore(.{ .tid = tid, .ptr = ptr, .val = val }); } ret ref; } fn mem_offset(self: *IrBuilder, tid: usize, ptr: usize, offset: usize): usize { var ref = builder_alloc(self, .MemOffset); var e = Ir.node(&self.ir, self.current_fn, ref); if e != null { e.body = .MemOffset(.{ .tid = tid, .ptr = ptr, .offset = offset }); } ret ref; } fn mem_field(self: *IrBuilder, tid: usize, agg: usize, field_idx: usize): usize { var ref = builder_alloc(self, .MemFieldPtr); var e = Ir.node(&self.ir, self.current_fn, ref); if e != null { e.body = .MemFieldPtr(.{ .tid = tid, .agg = agg, .field_idx = field_idx }); } ret ref; } fn mem_copy(self: *IrBuilder, tid: usize, dst: usize, src: usize, size: usize): usize { var ref = builder_alloc(self, .MemCopy); var e = Ir.node(&self.ir, self.current_fn, ref); if e != null { e.body = .MemCopy(.{ .tid = tid, .dst = dst, .src = src, .size = size }); } ret ref; } fn mem_set(self: *IrBuilder, tid: usize, dst: usize, val: usize, size: usize): usize { var ref = builder_alloc(self, .MemSet); var e = Ir.node(&self.ir, self.current_fn, ref); if e != null { e.body = .MemSet(.{ .tid = tid, .dst = dst, .val = val, .size = size }); } ret ref; } // ---- type const ---- fn const_node(self: *IrBuilder, tid: usize, lit: ConstLit): usize { var ref = builder_alloc(self, .TypeConst); var e = Ir.node(&self.ir, self.current_fn, ref); if e != null { e.body = .Const(.{ .tid = tid, .lit = lit }); } ret ref; } fn type_const_ptr(self: *IrBuilder, tid: usize, val: usize): usize { ret const_node(self, tid, .Ptr(val)); } fn type_const_bool(self: *IrBuilder, tid: usize, val: bool): usize { ret const_node(self, tid, .Bool(val)); } fn type_const_int(self: *IrBuilder, tid: usize, val: usize): usize { ret const_node(self, tid, .Int(val)); } fn type_const_float(self: *IrBuilder, tid: usize, val: f64): usize { ret const_node(self, tid, .Float(val)); } fn type_const_char(self: *IrBuilder, tid: usize, val: u8): usize { ret const_node(self, tid, .Char(val)); } fn type_const_fn(self: *IrBuilder, tid: usize, val: usize): usize { ret const_node(self, tid, .Fn(val)); } fn type_const_bytes(self: *IrBuilder, tid: usize, data: usize): usize { ret const_node(self, tid, .Ptr(data)); } // ---- type ops ---- fn type_bitsizeof(self: *IrBuilder, tid: usize): usize { var ref = builder_alloc(self, .TypeBitsizeof); var e = Ir.node(&self.ir, self.current_fn, ref); if e != null { e.body = .TypeOp(.{ .tid = tid, .field_idx = 0 }); } ret ref; } fn type_sizeof(self: *IrBuilder, tid: usize): usize { var ref = builder_alloc(self, .TypeSizeof); var e = Ir.node(&self.ir, self.current_fn, ref); if e != null { e.body = .TypeOp(.{ .tid = tid, .field_idx = 0 }); } ret ref; } fn type_alignof(self: *IrBuilder, tid: usize): usize { var ref = builder_alloc(self, .TypeAlignof); var e = Ir.node(&self.ir, self.current_fn, ref); if e != null { e.body = .TypeOp(.{ .tid = tid, .field_idx = 0 }); } ret ref; } fn type_offsetof(self: *IrBuilder, tid: usize, field_idx: usize): usize { var ref = builder_alloc(self, .TypeOffsetof); var e = Ir.node(&self.ir, self.current_fn, ref); if e != null { e.body = .TypeOp(.{ .tid = tid, .field_idx = field_idx }); } ret ref; } fn type_field_count(self: *IrBuilder, tid: usize): usize { var ref = builder_alloc(self, .TypeFieldCount); var e = Ir.node(&self.ir, self.current_fn, ref); if e != null { e.body = .TypeOp(.{ .tid = tid, .field_idx = 0 }); } ret ref; } // ---- agg ---- fn agg_construct(self: *IrBuilder, tid: usize): usize { var ref = builder_alloc(self, .AggConstruct); var e = Ir.node(&self.ir, self.current_fn, ref); if e != null { e.body = .AggConstruct(.{ .tid = tid, .fields = Vec.blank(@sizeof(usize), @alignof(usize)) }); } ret ref; } fn agg_construct_field(self: *IrBuilder, node_ref: usize, field: usize) { var e = Ir.node(&self.ir, self.current_fn, node_ref); if e == null { ret; } // 载荷按值绑定:改副本后写回。 match e.body { .AggConstruct(a) => { match Vec.push(&a.fields, &field as *u8) { .Ok => {}, .Err(error) => {} } e.body = .AggConstruct(a); }, _ => {}, } } fn agg_extract(self: *IrBuilder, tid: usize, field_tid: usize, field_idx: usize, val: usize): usize { var ref = builder_alloc(self, .AggExtract); var e = Ir.node(&self.ir, self.current_fn, ref); if e != null { e.body = .AggExtract(.{ .tid = tid, .field_tid = field_tid, .field_idx = field_idx, .val = val }); } ret ref; } fn agg_insert(self: *IrBuilder, tid: usize, field_idx: usize, agg: usize, field: usize): usize { var ref = builder_alloc(self, .AggInsert); var e = Ir.node(&self.ir, self.current_fn, ref); if e != null { e.body = .AggInsert(.{ .tid = tid, .field_idx = field_idx, .agg = agg, .field = field }); } ret ref; } // ---- control ---- fn control_select(self: *IrBuilder, tid: usize, cond: usize, tv: usize, fv: usize): usize { var ref = builder_alloc(self, .ControlSelect); var e = Ir.node(&self.ir, self.current_fn, ref); if e != null { e.body = .Select(.{ .tid = tid, .cond = cond, .true_val = tv, .false_val = fv }); } ret ref; } fn control_br(self: *IrBuilder, cond: usize): usize { var ref = builder_alloc(self, .ControlBr); var e = Ir.node(&self.ir, self.current_fn, ref); if e != null { e.body = .Br(.{ .cond = cond, .true_label = 0, .false_label = 0 }); } self.terminated = true; ret ref; } fn control_br_fixup(self: *IrBuilder, ref: usize, true_block: usize, false_block: usize) { var f1 = Fix { .node = ref, .slot = .BrTrue, .block = true_block }; match Vec.push(&self.fixups, &f1 as *u8) { .Ok => {}, .Err(error) => {} } var f2 = Fix { .node = ref, .slot = .BrFalse, .block = false_block }; match Vec.push(&self.fixups, &f2 as *u8) { .Ok => {}, .Err(error) => {} } } fn control_jmp(self: *IrBuilder): usize { var ref = builder_alloc(self, .ControlJmp); var e = Ir.node(&self.ir, self.current_fn, ref); if e != null { e.body = .Jmp(.{ .label = 0 }); } self.terminated = true; ret ref; } fn control_jmp_fixup(self: *IrBuilder, ref: usize, target_block: usize) { var fx = Fix { .node = ref, .slot = .Jmp, .block = target_block }; match Vec.push(&self.fixups, &fx as *u8) { .Ok => {}, .Err(error) => {} } } fn control_call(self: *IrBuilder, tid: usize, fn_ref: usize): usize { var ref = builder_alloc(self, .ControlCall); var e = Ir.node(&self.ir, self.current_fn, ref); if e != null { e.body = .Call(.{ .tid = tid, .func = fn_ref, .params = Vec.blank(@sizeof(usize), @alignof(usize)) }); } ret ref; } fn control_call_param(self: *IrBuilder, call_node: usize, arg: usize) { var e = Ir.node(&self.ir, self.current_fn, call_node); if e == null { ret; } // 载荷按值绑定:改副本后写回。 match e.body { .Call(c) => { match Vec.push(&c.params, &arg as *u8) { .Ok => {}, .Err(error) => {} } e.body = .Call(c); }, _ => {}, } } fn control_param(self: *IrBuilder, tid: usize, idx: usize): usize { var ref = builder_alloc(self, .ControlParam); var e = Ir.node(&self.ir, self.current_fn, ref); if e != null { e.body = .Param(.{ .tid = tid, .idx = idx }); } ret ref; } fn control_ret(self: *IrBuilder, tid: usize, val: usize): usize { var ref = builder_alloc(self, .ControlRet); var e = Ir.node(&self.ir, self.current_fn, ref); if e != null { e.body = .Ret(.{ .tid = tid, .val = val }); } self.terminated = true; ret ref; } fn control_unreachable(self: *IrBuilder): usize { var ref = builder_alloc(self, .ControlUnreachable); self.terminated = true; ret ref; } fn control_trap(self: *IrBuilder): usize { var ref = builder_alloc(self, .ControlTrap); self.terminated = true; ret ref; } fn dbg_breakpoint(self: *IrBuilder): usize { ret builder_alloc(self, .DbgBreakpoint); } fn dbg_panic(self: *IrBuilder, msg: ByteView): usize { var ref = builder_alloc(self, .DbgPanic); var e = Ir.node(&self.ir, self.current_fn, ref); if e != null { e.body = .Panic(.{ .msg = msg }); } ret ref; } fn test_builder_emit_and_dump() { var b = builder_new(); defer { builder_drop(&b); } b.dbg = span_none(); var fid = fn_begin(&b, ByteView.from_slice("f"), 3); var blk = block_begin(&b); var p = control_param(&b, 2, 0); var c = type_const_int(&b, 2, 42); var add = arith_add(&b, 2, p, c); control_ret(&b, 2, add); block_end(&b); fn_end(&b); var ty = TypeTable.new(); var t: TypeTable = undefined; match ty { .Ok(v) => { t = v; }, .Err(e) => { @assert(false, "tt"); } } defer { TypeTable.drop(&t); } var at = Attr.new(); var a: Attr = undefined; match at { .Ok(v) => { a = v; }, .Err(e) => { @assert(false, "attr"); } } defer { Attr.drop(&a); } var out = ByteBuffer.new(); defer { ByteBuffer.drop(&out); } @assert(Ir.dump(&b.ir, &t, &a, &out), "dump ok"); var got = ByteBuffer.as_slice(out); @assert(got.len > 0, "dump nonempty"); } fn main() { test_builder_emit_and_dump(); }