// Expect retcode = 0 // SIR 中间表示:节点用 arena 序号引用(0=error),按函数分桶;公共字段在 IrEntry, // kind 专属载荷在 IrBody ADT(kind 与载荷一一对应)。builder 负责发射与跳转回填。 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 .{ .append_usize, .append_i64 } = @import("utils/fmt.spl"); type .{ .Span, .span_none } = @import("./diag.spl"); type TypeTable = @import("./type_table.spl"); type Attr = @import("./attr.spl"); type IrKind = enum { ArithAdd, ArithSub, ArithMul, ArithDiv, ArithRem, ArithNeg, ArithAbs, ArithAnd, ArithOr, ArithXor, ArithShl, ArithShr, ArithNot, CmpEq, CmpNe, CmpLt, CmpLe, CmpGt, CmpGe, CastTrunc, CastZext, CastSext, CastFext, CastFtrunc, CastBitcast, CastPtr2int, CastInt2ptr, CastBool2int, CaseInt2float, CaseFloat2int, MemAlloca, MemGlobalAlloc, MemLoad, MemStore, MemOffset, MemFieldPtr, MemCopy, MemSet, TypeConst, TypeBitsizeof, TypeSizeof, TypeAlignof, TypeOffsetof, TypeFieldCount, AggConstruct, AggExtract, AggInsert, ControlSelect, ControlBr, ControlJmp, ControlCall, ControlParam, ControlRet, ControlUnreachable, ControlTrap, DbgBreakpoint, DbgPanic, } type ConstLit = enum { Bool: bool, Ptr: usize, Int: usize, Float: f64, Char: u8, Fn: usize, Global: usize, // 全局变量下标(Ir.globals);值 = 指向该全局的 *T } // 模块级全局变量:编译期分配的静态存储,引用时是一个 *T 指针常量(同 LLVM GlobalVariable)。 type Global = struct { tid: usize, zero: bool, // true = 零初始化(BSS,不携带字节) data: Vec, // u8 初始字节(zero=false 时使用) } type IrBody = enum { None, Arith: struct { tid: usize, left: usize, right: usize }, Cmp: struct { tid: usize, a: usize, b: usize }, Cast: struct { from_tid: usize, to_tid: usize, val: usize }, MemAlloc: struct { tid: usize, count: usize }, MemGlobalAlloc: struct { tid: usize, const_node: usize }, MemLoad: struct { tid: usize, ptr: usize }, MemStore: struct { tid: usize, ptr: usize, val: usize }, MemOffset: struct { tid: usize, ptr: usize, offset: usize }, MemFieldPtr: struct { tid: usize, agg: usize, field_idx: usize }, MemCopy: struct { tid: usize, dst: usize, src: usize, size: usize }, MemSet: struct { tid: usize, dst: usize, val: usize, size: usize }, Const: struct { tid: usize, lit: ConstLit }, TypeOp: struct { tid: usize, field_idx: usize }, AggConstruct: struct { tid: usize, fields: Vec }, AggExtract: struct { tid: usize, field_tid: usize, field_idx: usize, val: usize }, AggInsert: struct { tid: usize, field_idx: usize, agg: usize, field: usize }, Select: struct { tid: usize, cond: usize, true_val: usize, false_val: usize }, Br: struct { cond: usize, true_label: usize, false_label: usize }, Jmp: struct { label: usize }, Call: struct { tid: usize, func: usize, params: Vec }, Param: struct { tid: usize, idx: usize }, Ret: struct { tid: usize, val: usize }, Panic: struct { msg: ByteView }, } type IrEntry = struct { kind: IrKind, dbg: Span, attrs: Vec, // attr id body: IrBody, } type IrFunc = struct { name: ByteView, attrs: Vec, // attr id fn_tid: usize, nodes: Vec, // IrEntry } type Ir = @self; funcs: Vec, // IrFunc globals: Vec, // Global fn new_entry(): IrEntry { ret IrEntry { .kind = .ArithAdd, .dbg = span_none(), .attrs = Vec.blank(@sizeof(usize), @alignof(usize)), .body = .None, }; } fn new(): Ir { var ir = Ir { .funcs = Vec.blank(@sizeof(IrFunc), @alignof(IrFunc)), .globals = Vec.blank(@sizeof(Global), @alignof(Global)) }; var sentinel = IrFunc { .name = ByteView.empty(), .attrs = Vec.blank(@sizeof(usize), @alignof(usize)), .fn_tid = 0, .nodes = Vec.blank(@sizeof(IrEntry), @alignof(IrEntry)), }; var sn = new_entry(); Vec.push(&sentinel.nodes, &sn as *u8); Vec.push(&ir.funcs, &sentinel as *u8); ret ir; } fn drop_body(body: IrBody) { match body { .AggConstruct(a) => { Vec.drop(&a.fields); }, .Call(c) => { Vec.drop(&c.params); }, _ => {}, } } fn drop(self: *Ir) { var i: usize = 0; while i < self.funcs.len { var f = (self.funcs.data as *IrFunc) + i; var j: usize = 0; while j < f.nodes.len { var e = (f.nodes.data as *IrEntry) + j; Vec.drop(&e.attrs); drop_body(e.body); j = j + 1; } Vec.drop(&f.attrs); Vec.drop(&f.nodes); i = i + 1; } Vec.drop(&self.funcs); var g: usize = 0; while g < self.globals.len { var ge = (self.globals.data as *Global) + g; Vec.drop(&ge.data); g = g + 1; } Vec.drop(&self.globals); } // 追加一个全局变量;返回其下标(gidx)。bytes 的所有权转移进表。 fn add_global(self: *Ir, tid: usize, zero: bool, bytes: Vec): usize { var g = Global { .tid = tid, .zero = zero, .data = bytes }; match Vec.push(&self.globals, &g as *u8) { .Ok => {}, .Err(error) => { ret 0; } } ret self.globals.len - 1; } fn global_at(self: *Ir, gidx: usize): *Global { if gidx >= self.globals.len { ret null; } ret (self.globals.data as *Global) + gidx; } fn alloc_fn(self: *Ir): usize { var f = IrFunc { .name = ByteView.empty(), .attrs = Vec.blank(@sizeof(usize), @alignof(usize)), .fn_tid = 0, .nodes = Vec.blank(@sizeof(IrEntry), @alignof(IrEntry)), }; var sn = new_entry(); match Vec.push(&f.nodes, &sn as *u8) { .Ok => {}, .Err(error) => { ret 0; } } match Vec.push(&self.funcs, &f as *u8) { .Ok => {}, .Err(error) => { ret 0; } } ret self.funcs.len - 1; } fn alloc_node(self: *Ir, fn_id: usize): usize { if fn_id == 0 || fn_id >= self.funcs.len { ret 0; } var f = (self.funcs.data as *IrFunc) + fn_id; var n = new_entry(); match Vec.push(&f.nodes, &n as *u8) { .Ok => {}, .Err(error) => { ret 0; } } ret f.nodes.len - 1; } fn func(self: *Ir, fn_id: usize): *IrFunc { if fn_id == 0 || fn_id >= self.funcs.len { ret null; } ret (self.funcs.data as *IrFunc) + fn_id; } fn node(self: *Ir, fn_id: usize, node_id: usize): *IrEntry { var f = func(self, fn_id); if f == null { ret null; } if node_id == 0 || node_id >= f.nodes.len { ret null; } ret (f.nodes.data as *IrEntry) + node_id; } fn kind_name(kind: IrKind): []u8 { match kind { .ArithAdd => { ret "arith.add"; }, .ArithSub => { ret "arith.sub"; }, .ArithMul => { ret "arith.mul"; }, .ArithDiv => { ret "arith.div"; }, .ArithRem => { ret "arith.rem"; }, .ArithNeg => { ret "arith.neg"; }, .ArithAbs => { ret "arith.abs"; }, .ArithAnd => { ret "arith.and"; }, .ArithOr => { ret "arith.or"; }, .ArithXor => { ret "arith.xor"; }, .ArithShl => { ret "arith.shl"; }, .ArithShr => { ret "arith.shr"; }, .ArithNot => { ret "arith.not"; }, .CmpEq => { ret "cmp.eq"; }, .CmpNe => { ret "cmp.ne"; }, .CmpLt => { ret "cmp.lt"; }, .CmpLe => { ret "cmp.le"; }, .CmpGt => { ret "cmp.gt"; }, .CmpGe => { ret "cmp.ge"; }, .CastTrunc => { ret "cast.trunc"; }, .CastZext => { ret "cast.zext"; }, .CastSext => { ret "cast.sext"; }, .CastFext => { ret "cast.fext"; }, .CastFtrunc => { ret "cast.ftrunc"; }, .CastBitcast => { ret "cast.bitcast"; }, .CastPtr2int => { ret "cast.ptr2int"; }, .CastInt2ptr => { ret "cast.int2ptr"; }, .CastBool2int => { ret "cast.bool2int"; }, .CaseInt2float => { ret "case.int2float"; }, .CaseFloat2int => { ret "case.float2int"; }, .MemAlloca => { ret "mem.alloca"; }, .MemGlobalAlloc => { ret "mem.global_alloc"; }, .MemLoad => { ret "mem.load"; }, .MemStore => { ret "mem.store"; }, .MemOffset => { ret "mem.offset"; }, .MemFieldPtr => { ret "mem.field"; }, .MemCopy => { ret "mem.copy"; }, .MemSet => { ret "mem.set"; }, .TypeConst => { ret "type.const"; }, .TypeBitsizeof => { ret "type.bitsizeof"; }, .TypeSizeof => { ret "type.sizeof"; }, .TypeAlignof => { ret "type.alignof"; }, .TypeOffsetof => { ret "type.offsetof"; }, .TypeFieldCount => { ret "type.field_count"; }, .AggConstruct => { ret "agg.construct"; }, .AggExtract => { ret "agg.extract"; }, .AggInsert => { ret "agg.insert"; }, .ControlSelect => { ret "control.select"; }, .ControlBr => { ret "control.br"; }, .ControlJmp => { ret "control.jmp"; }, .ControlCall => { ret "control.call"; }, .ControlParam => { ret "control.param"; }, .ControlRet => { ret "control.ret"; }, .ControlUnreachable => { ret "control.unreachable"; }, .ControlTrap => { ret "control.trap"; }, .DbgBreakpoint => { ret "dbg.breakpoint"; }, .DbgPanic => { ret "dbg.panic"; }, } ret "?"; } fn out_str(out: *ByteBuffer, s: []u8): bool { match ByteBuffer.append(out, s) { .Ok => { ret true; }, .Err(error) => { ret false; } } ret false; } fn out_u(out: *ByteBuffer, v: usize): bool { match append_usize(out, v, 10, false) { .Ok => { ret true; }, .Err(error) => { ret false; } } ret false; } fn out_i(out: *ByteBuffer, v: i64): bool { match append_i64(out, v, 10, false) { .Ok => { ret true; }, .Err(error) => { ret false; } } ret false; } fn dump_type(out: *ByteBuffer, ty: *TypeTable, tid: usize): bool { if !out_str(out, "(") { ret false; } if !TypeTable.pure_dump(ty, tid, out) { ret false; } ret out_str(out, ")"); } fn dump_ref(out: *ByteBuffer, ref: usize): bool { if ref == 0 { ret out_str(out, " _"); } if !out_str(out, " %") { ret false; } ret out_u(out, ref); } fn dump_attrs(out: *ByteBuffer, attr: *Attr, ids: Vec): bool { if ids.len == 0 { ret true; } if !out_str(out, " attrs=[") { ret false; } var i: usize = 0; while i < ids.len { if i > 0 && !out_str(out, ", ") { ret false; } var id = list_at(ids, i); if !Attr.dump_id(attr, id, out) { ret false; } i = i + 1; } ret out_str(out, "]"); } fn list_at(v: Vec, i: usize): usize { if i >= v.len { ret 0; } ret ((v.data as *usize) + i).*; } fn dump_body(out: *ByteBuffer, ty: *TypeTable, e: *IrEntry): bool { match e.body { .None => { ret true; }, .Arith(a) => { if !dump_type(out, ty, a.tid) { ret false; } if !dump_ref(out, a.left) { ret false; } // 一元 arith(neg/abs/not)不打印 right。 match e.kind { .ArithNeg, .ArithAbs, .ArithNot => { ret true; }, _ => {}, } ret dump_ref(out, a.right); }, .Cmp(c) => { if !dump_type(out, ty, c.tid) { ret false; } if !dump_ref(out, c.a) { ret false; } ret dump_ref(out, c.b); }, .Cast(c) => { if !out_str(out, " ") { ret false; } if !TypeTable.pure_dump(ty, c.from_tid, out) { ret false; } if !out_str(out, " -> ") { ret false; } if !TypeTable.pure_dump(ty, c.to_tid, out) { ret false; } ret dump_ref(out, c.val); }, .MemAlloc(m) => { if !dump_type(out, ty, m.tid) { ret false; } ret dump_ref(out, m.count); }, .MemGlobalAlloc(m) => { if !dump_type(out, ty, m.tid) { ret false; } if !out_str(out, " g#") { ret false; } ret out_u(out, m.const_node); }, .MemLoad(m) => { if !dump_type(out, ty, m.tid) { ret false; } ret dump_ref(out, m.ptr); }, .MemStore(m) => { if !dump_type(out, ty, m.tid) { ret false; } if !dump_ref(out, m.ptr) { ret false; } ret dump_ref(out, m.val); }, .MemOffset(m) => { if !dump_type(out, ty, m.tid) { ret false; } if !dump_ref(out, m.ptr) { ret false; } ret dump_ref(out, m.offset); }, .MemFieldPtr(m) => { if !dump_type(out, ty, m.tid) { ret false; } if !dump_ref(out, m.agg) { ret false; } if !out_str(out, " #") { ret false; } ret out_u(out, m.field_idx); }, .MemCopy(m) => { if !dump_type(out, ty, m.tid) { ret false; } if !dump_ref(out, m.dst) { ret false; } if !dump_ref(out, m.src) { ret false; } ret dump_ref(out, m.size); }, .MemSet(m) => { if !dump_type(out, ty, m.tid) { ret false; } if !dump_ref(out, m.dst) { ret false; } if !dump_ref(out, m.val) { ret false; } ret dump_ref(out, m.size); }, .Const(c) => { if !dump_type(out, ty, c.tid) { ret false; } match c.lit { .Global(g) => { if !out_str(out, " global#") { ret false; } ret out_u(out, g); }, _ => {}, } var te = TypeTable.node(ty, c.tid); var is_float = false; var is_fn = false; var is_u8 = false; var is_arr = false; if te != null { match te.node { .Float(x) => { is_float = true; }, .Fn(x) => { is_fn = true; }, .Array(x) => { is_arr = true; }, .Int(x) => { if x.bits == 8 { is_u8 = true; } }, _ => {}, } } if is_float { ret out_str(out, " float=?"); } if is_arr { ret out_str(out, " bytes"); } if is_fn { if !out_str(out, " fn#") { ret false; } match c.lit { .Fn(v) => { ret out_u(out, v); }, _ => { ret out_str(out, "?"); } } } if is_u8 { var cv: usize = 0; match c.lit { .Char(v) => { cv = v as usize; }, .Int(v) => { cv = v; }, _ => {}, } if !out_str(out, " char='") { ret false; } match ByteBuffer.append_byte(out, cv as u8) { .Ok => {}, .Err(error) => { ret false; } } ret out_str(out, "'"); } // 其他常量统一按 int_lit 打印(union 别名)。 var iv: usize = 0; match c.lit { .Int(v) => { iv = v; }, .Bool(v) => { if v { iv = 1; } }, .Ptr(v) => { iv = v; }, .Char(v) => { iv = v as usize; }, .Fn(v) => { iv = v; }, .Float(v) => {}, } if !out_str(out, " int=") { ret false; } ret out_u(out, iv); }, .TypeOp(t) => { if !dump_type(out, ty, t.tid) { ret false; } if @enum2int(e.kind) == @enum2int(IrKind.TypeOffsetof) { ret dump_ref(out, t.field_idx); } ret true; }, .AggConstruct(a) => { if !dump_type(out, ty, a.tid) { ret false; } var i: usize = 0; while i < a.fields.len { if !dump_ref(out, list_at(a.fields, i)) { ret false; } i = i + 1; } ret true; }, .AggExtract(a) => { if !dump_type(out, ty, a.tid) { ret false; } if !out_str(out, " #") { ret false; } if !out_u(out, a.field_idx) { ret false; } ret dump_ref(out, a.val); }, .AggInsert(a) => { if !dump_type(out, ty, a.tid) { ret false; } if !out_str(out, " #") { ret false; } if !out_u(out, a.field_idx) { ret false; } if !dump_ref(out, a.agg) { ret false; } ret dump_ref(out, a.field); }, .Select(s) => { if !dump_type(out, ty, s.tid) { ret false; } if !dump_ref(out, s.cond) { ret false; } if !dump_ref(out, s.true_val) { ret false; } ret dump_ref(out, s.false_val); }, .Br(b) => { if !dump_ref(out, b.cond) { ret false; } if !dump_ref(out, b.true_label) { ret false; } ret dump_ref(out, b.false_label); }, .Jmp(j) => { ret dump_ref(out, j.label); }, .Call(c) => { if !dump_type(out, ty, c.tid) { ret false; } if !dump_ref(out, c.func) { ret false; } var i: usize = 0; while i < c.params.len { if !dump_ref(out, list_at(c.params, i)) { ret false; } i = i + 1; } ret true; }, .Param(p) => { if !dump_type(out, ty, p.tid) { ret false; } ret dump_ref(out, p.idx); }, .Ret(r) => { if !dump_type(out, ty, r.tid) { ret false; } ret dump_ref(out, r.val); }, .Panic(p) => { if !out_str(out, " \"") { ret false; } if !out_str(out, ByteView.as_slice(p.msg)) { ret false; } ret out_str(out, "\""); }, } ret false; } fn dump(self: *Ir, ty: *TypeTable, attr: *Attr, out: *ByteBuffer): bool { var i: usize = 1; while i < self.funcs.len { var f = func(self, i); if f == null { i = i + 1; continue; } if !out_str(out, "func ") { ret false; } if f.name.len == 0 { if !out_str(out, "?") { ret false; } } else { if !out_str(out, ByteView.as_slice(f.name)) { ret false; } } if !out_str(out, " %") { ret false; } if !out_u(out, i) { ret false; } if !out_str(out, " tid = ") { ret false; } if !TypeTable.pure_dump(ty, f.fn_tid, out) { ret false; } if !dump_attrs(out, attr, f.attrs) { ret false; } if !out_str(out, "\n") { ret false; } var j: usize = 1; while j < f.nodes.len { var e = node(self, i, j); if e == null { j = j + 1; continue; } if !out_str(out, " #") { ret false; } if !out_u(out, j) { ret false; } if !out_str(out, " ") { ret false; } if !out_str(out, kind_name(e.kind)) { ret false; } if !dump_body(out, ty, e) { ret false; } if !dump_attrs(out, attr, e.attrs) { ret false; } if !out_str(out, "\n") { ret false; } j = j + 1; } i = i + 1; } ret true; } fn test_kinds() { @assert(ByteView.eq(ByteView.from_slice(kind_name(.ArithAdd)), ByteView.from_slice("arith.add")), "arith.add name"); @assert(ByteView.eq(ByteView.from_slice(kind_name(.ControlCall)), ByteView.from_slice("control.call")), "control.call name"); @assert(ByteView.eq(ByteView.from_slice(kind_name(.DbgPanic)), ByteView.from_slice("dbg.panic")), "dbg.panic name"); } fn test_alloc_and_node() { var ir = new(); defer { drop(&ir); } var fid = alloc_fn(&ir); @assert(fid == 1, "first fn is 1"); var nid = alloc_node(&ir, fid); @assert(nid == 1, "first node is 1"); var e = node(&ir, fid, nid); @assert(e != null, "node lookup"); e.kind = .ArithAdd; e.body = .Arith(.{ .tid = 2, .left = 1, .right = 1 }); var f = func(&ir, fid); @assert(f != null && f.nodes.len == 2, "fn nodes"); } fn main() { test_kinds(); test_alloc_and_node(); }