Files
spl/stage2/ir2vm.spl
T

1369 lines
42 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Expect retcode = 0
// IR -> VM 机器码(移植自 stage1/spl_ir2vm.c)。
// 依赖:IR(ir.spl)、IR 类型表(type_table.spl)、布局(layout.spl)、
// 属性(attr.spl)、字节码容器(mcode.spl)。
//
// extern_native 用「按函数 ref 下标的 isize 数组」代替 stage1 的 map:
// 默认 -1 表示非 extern,>=0 表示 native 索引(语义等价)。
type .{ .Vec } = @import("utils/vec.spl");
type .{ .ByteView } = @import("utils/byte_view.spl");
type .{ .ByteBuffer } = @import("utils/byte_buffer.spl");
type TypeTable = @import("./type_table.spl");
type LayoutM = @import("./layout.spl");
type .{ .Layout } = @import("./layout.spl");
type AttrM = @import("./attr.spl");
type .{ .Attr, .AttrArg } = @import("./attr.spl");
type IrM = @import("./ir.spl");
type .{ .Ir, .IrFunc, .IrEntry } = @import("./ir.spl");
type Mcode = @import("./mcode.spl");
type .{ .Prog, .Op, .VmKind, .VmIns, .VmFunc } = @import("./mcode.spl");
type Mem = @import("sys/mem.spl");
type Ir2Vm = @self;
ir: *Ir,
ty: *TypeTable,
attr: *Attr,
layout: *Layout,
extern_native: Vec, // isize
prog: Prog,
// ---- 类型/布局查询 ----
fn info_of(ctx: *Ir2Vm, tid: usize): *LayoutM.LayoutInfo {
ret LayoutM.info_of(ctx.layout, tid);
}
fn ir_type_size(ctx: *Ir2Vm, tid: usize): usize {
var info = info_of(ctx, tid);
if info == null { ret 0; }
ret (info.size_bits + 7) / 8;
}
fn ir_type_align(ctx: *Ir2Vm, tid: usize): usize {
var info = info_of(ctx, tid);
if info == null { ret 1; }
var a = info.align_bits / 8;
if a == 0 { a = 1; }
ret a;
}
fn ir_is_aggregate(ctx: *Ir2Vm, tid: usize): bool {
var te = TypeTable.node(ctx.ty, tid);
if te == null { ret false; }
match te.node {
.Array => { ret true; },
.Struct => { ret true; },
.Union => { ret true; },
.Enum => { ret true; },
.Shape => { ret true; },
_ => { ret false; },
}
ret false;
}
fn fn_ret_type(ctx: *Ir2Vm, fn_tid: usize): usize {
var te = TypeTable.node(ctx.ty, fn_tid);
if te == null { ret 0; }
match te.node {
.Fn(f) => { ret f.ret_type; },
_ => { ret 0; },
}
ret 0;
}
fn fn_nargs(ctx: *Ir2Vm, fn_tid: usize): usize {
var te = TypeTable.node(ctx.ty, fn_tid);
if te == null { ret 0; }
match te.node {
.Fn(f) => { ret f.params.len; },
_ => { ret 0; },
}
ret 0;
}
fn ir_agg_field_tid(ctx: *Ir2Vm, tid: usize, idx: usize): usize {
var te = TypeTable.node(ctx.ty, tid);
if te == null { ret 0; }
match te.node {
.Enum(agg) => {
if idx == 0 { ret agg.tag_type; }
var vidx = idx - 1;
if vidx < agg.variants.len {
var m = TypeTable.member_at(&agg.variants, vidx);
if m != null { ret m.type_id; }
}
ret 0;
},
.Struct(agg) => {
if idx < agg.variants.len {
var m = TypeTable.member_at(&agg.variants, idx);
if m != null { ret m.type_id; }
}
ret 0;
},
.Union(agg) => {
if idx < agg.variants.len {
var m = TypeTable.member_at(&agg.variants, idx);
if m != null { ret m.type_id; }
}
ret 0;
},
.Array(a) => { ret a.element; },
_ => { ret 0; },
}
ret 0;
}
fn ir_agg_field_off(ctx: *Ir2Vm, tid: usize, idx: usize): usize {
var te = TypeTable.node(ctx.ty, tid);
if te == null { ret 0; }
match te.node {
.Array(a) => { ret idx * ir_type_size(ctx, a.element); },
_ => {},
}
var info = info_of(ctx, tid);
if info == null || idx >= info.field_bit_offsets.len { ret 0; }
ret usize_at(&info.field_bit_offsets, idx) / 8;
}
fn vm_kind_of(ctx: *Ir2Vm, tid: usize): VmKind {
var te = TypeTable.node(ctx.ty, tid);
if te == null { ret .Void; }
match te.node {
.Unit => { ret .Void; },
.Bool => { ret .Bool; },
.Int(x) => {
if x.bits == 0 { ret .I32; }
if x.bits == 8 { if x.is_signed { ret .I8; } ret .U8; }
if x.bits == 16 { if x.is_signed { ret .I16; } ret .U16; }
if x.bits == 32 { if x.is_signed { ret .I32; } ret .U32; }
if x.is_signed { ret .I64; }
ret .U64;
},
.Float(x) => { if x.bits == 32 { ret .F32; } ret .F64; },
_ => { ret .Ptr; },
}
ret .Ptr;
}
fn tid_is_signed(ctx: *Ir2Vm, tid: usize): bool {
var te = TypeTable.node(ctx.ty, tid);
if te == null { ret false; }
match te.node {
.Int(x) => { ret x.is_signed; },
_ => { ret false; },
}
ret false;
}
// 节点的「值类型」;0 表示无结果(控制/存储类)。
fn ir_result_tid(ctx: *Ir2Vm, n: *IrEntry): usize {
match n.body {
.Arith(a) => { ret a.tid; },
.Cmp(c) => { ret c.tid; },
.Cast(c) => { ret c.to_tid; },
.MemAlloc(m) => { ret TypeTable.make_ptr(ctx.ty, m.tid); },
.MemGlobalAlloc(m) => { ret m.tid; },
.MemLoad(m) => { ret m.tid; },
.MemOffset(m) => { ret TypeTable.make_ptr(ctx.ty, m.tid); },
.MemFieldPtr(m) => {
var field = ir_agg_field_tid(ctx, m.tid, m.field_idx);
ret TypeTable.make_ptr(ctx.ty, field);
},
.Const(c) => { ret c.tid; },
.TypeOp(t) => { ret t.tid; },
.AggConstruct(a) => { ret a.tid; },
.AggExtract(a) => { ret a.field_tid; },
.AggInsert(a) => { ret a.tid; },
.Select(s) => { ret s.tid; },
.Call(c) => { ret c.tid; },
.Param(p) => { ret p.tid; },
_ => { ret 0; },
}
ret 0;
}
// ---- 小工具 ----
// 内部热路径:假定索引有效,直接取址(省去 Vec.get/valid)。
fn usize_at(v: *Vec, i: usize): usize {
ret (v.data as *usize)[i];
}
fn push_usize(v: *Vec, x: usize) {
ignore_bool(Vec.push_raw(v, &x as *u8));
}
fn fnode(f: *IrFunc, i: usize): *IrEntry {
ret (f.nodes.data as *IrEntry) + i;
}
fn align_up(x: usize, a: usize): usize {
if a <= 1 { ret x; }
ret (x + a - 1) / a * a;
}
fn const_usize(n: *IrEntry): usize {
if n == null { ret 0; }
match n.body {
.Const(c) => {
match c.lit {
.Int(v) => { ret v; },
.Char(v) => { ret v as usize; },
.Bool(v) => { if v { ret 1; } ret 0; },
.Ptr(v) => { ret v; },
.Fn(v) => { ret v; },
.Float(v) => { ret 0; },
}
},
_ => { ret 0; },
}
ret 0;
}
fn const_fn(n: *IrEntry): usize {
if n == null { ret 0; }
match n.body {
.Const(c) => {
match c.lit {
.Fn(v) => { ret v; },
_ => { ret 0; },
}
},
_ => { ret 0; },
}
ret 0;
}
// ---- extern ----
fn fn_check_extern(ctx: *Ir2Vm, f: *IrFunc): bool {
if ctx.attr == null || f == null { ret false; }
var i: usize = 0;
while i < f.attrs.len {
var a = AttrM.node(ctx.attr, usize_at(&f.attrs, i));
if a != null {
var info = AttrM.info_at(ctx.attr, a.info_index);
if info != null && ByteView.eq(info.name, ByteView.from_slice("extern")) {
a.consumed_mask = a.consumed_mask | (1 as u32 << 2);
ret true;
}
}
i = i + 1;
}
ret false;
}
fn extern_get(ctx: *Ir2Vm, ref: usize): isize {
if ref == 0 || ref >= ctx.extern_native.len { ret -1; }
match Vec.get(&ctx.extern_native, ref) {
.Ok(ptr) => { ret (ptr as *isize).*; },
.Err(error) => { ret -1; },
}
ret -1;
}
fn extern_set(ctx: *Ir2Vm, ref: usize, value: isize) {
if ref >= ctx.extern_native.len { ret; }
match Vec.get(&ctx.extern_native, ref) {
.Ok(ptr) => { (ptr as *isize).* = value; },
.Err(error) => {},
}
}
// ---- 帧布局 ----
type Slot = struct {
result_off: usize,
data_off: usize,
}
type FrameLayout = struct {
slots: Vec, // Slot
args_bytes: usize,
frame_bytes: usize,
}
fn slot_at(frame: *FrameLayout, i: usize): *Slot {
ret (frame.slots.data as *Slot) + i;
}
fn slot_result(frame: *FrameLayout, i: usize): usize {
var s = slot_at(frame, i);
if s == null { ret 0; }
ret s.result_off;
}
fn slot_data(frame: *FrameLayout, i: usize): usize {
var s = slot_at(frame, i);
if s == null { ret 0; }
ret s.data_off;
}
fn node_data_size(ctx: *Ir2Vm, f: *IrFunc, node: *IrEntry): usize {
match node.body {
.MemAlloc(m) => {
var count: usize = 1;
if m.count < f.nodes.len {
var cn = fnode(f, m.count);
count = const_usize(cn);
}
if count == 0 { count = 1; }
var sz = ir_type_size(ctx, m.tid);
if sz == 0 { sz = 1; }
ret sz * count;
},
.AggConstruct(a) => {
var sz = ir_type_size(ctx, a.tid);
if sz == 0 { ret 8; }
ret sz;
},
.AggInsert(a) => {
var sz = ir_type_size(ctx, a.tid);
if sz == 0 { ret 8; }
ret sz;
},
.AggExtract(a) => {
if ir_is_aggregate(ctx, a.field_tid) { ret ir_type_size(ctx, a.field_tid); }
ret 0;
},
.MemLoad(m) => {
if ir_is_aggregate(ctx, m.tid) { ret ir_type_size(ctx, m.tid); }
ret 0;
},
.Panic(p) => { ret @sizeof(usize) * 4; },
.Call(c) => {
if ir_is_aggregate(ctx, c.tid) { ret ir_type_size(ctx, c.tid); }
ret 0;
},
_ => { ret 0; },
}
ret 0;
}
fn frame_build(fl: *FrameLayout, ctx: *Ir2Vm, fid: usize) {
fl.args_bytes = 0;
fl.frame_bytes = 0;
var f = IrM.func(ctx.ir, fid);
if f == null {
fl.slots = Vec.blank(@sizeof(Slot), @alignof(Slot));
ret;
}
var n = f.nodes.len;
match Vec.with_capacity(@sizeof(Slot), @alignof(Slot), n) {
.Ok(v) => { fl.slots = v; },
.Err(error) => { fl.slots = Vec.blank(@sizeof(Slot), @alignof(Slot)); },
}
var i: usize = 0;
while i < n {
var blank: Slot = .{ .result_off = 0, .data_off = 0 };
match Vec.push(&fl.slots, &blank as *u8) { .Ok => {}, .Err(error) => {} }
i = i + 1;
}
var nargs = fn_nargs(ctx, f.fn_tid);
var hidden_sret: usize = 0;
if ir_is_aggregate(ctx, fn_ret_type(ctx, f.fn_tid)) { hidden_sret = 1; }
fl.args_bytes = (nargs + hidden_sret) * @sizeof(usize);
var off = fl.args_bytes;
i = 1;
while i < n {
var node = fnode(f, i);
if ir_result_tid(ctx, node) != 0 {
var s = slot_at(fl, i);
s.result_off = off;
off = off + @sizeof(usize);
}
var extra = node_data_size(ctx, f, node);
if extra != 0 {
off = align_up(off, 8);
var s2 = slot_at(fl, i);
s2.data_off = off;
off = off + align_up(extra, 8);
}
i = i + 1;
}
fl.frame_bytes = align_up(off, 8);
}
fn frame_drop(fl: *FrameLayout) {
Vec.drop(&fl.slots);
}
// ---- cgen ----
type FixKind = enum {
Jmp,
Fnaddr,
}
type Fix = struct {
kind: FixKind,
ins_idx: usize,
node_target: usize,
fn_target: usize,
}
type Cgen = @self;
ctx: *Ir2Vm,
fid: usize,
irf: *IrFunc,
frame: FrameLayout,
node_ip: Vec, // usize
fixups: *Vec,
terminated: bool,
fn emit(cg: *Cgen, op: Op, ty: VmKind, imm: usize) {
ignore_usize(Mcode.add_instr(&cg.ctx.prog, op, ty, imm));
}
fn ip_of(cg: *Cgen): usize {
ret cg.ctx.prog.insns.len;
}
fn ins_ptr(prog: *Prog, i: usize): *VmIns {
ret (prog.insns.data as *VmIns) + i;
}
fn func_ptr(prog: *Prog, i: usize): *VmFunc {
ret (prog.funcs.data as *VmFunc) + i;
}
fn cg_laddr(cg: *Cgen, off: usize) { emit(cg, .Laddr, .Void, off); }
fn cg_load(cg: *Cgen, k: VmKind) { emit(cg, .Load, k, 0); }
fn cg_store(cg: *Cgen, k: VmKind) { emit(cg, .Store, k, 0); }
fn cg_push(cg: *Cgen, k: VmKind, v: usize) { emit(cg, .Push, k, v); }
fn cg_op(cg: *Cgen, op: Op, k: VmKind) { emit(cg, op, k, 0); }
fn cg_swap(cg: *Cgen) { emit(cg, .Swap, .Void, 0); }
fn cg_gaddr(cg: *Cgen, idx: usize) { emit(cg, .Gaddr, .Void, idx); }
fn cg_load_slot(cg: *Cgen, ref: usize, k: VmKind) {
cg_laddr(cg, slot_result(&cg.frame, ref));
cg_load(cg, k);
}
fn cg_store_slot(cg: *Cgen, ref: usize, k: VmKind) {
cg_laddr(cg, slot_result(&cg.frame, ref));
cg_swap(cg);
cg_store(cg, k);
}
fn cg_store_at(cg: *Cgen, addr: usize, k: VmKind) {
cg_laddr(cg, addr);
cg_swap(cg);
cg_store(cg, k);
}
type DataLoc = struct {
indirect: bool,
off: usize,
base: usize,
}
fn direct_data(off: usize): DataLoc {
ret DataLoc { .indirect = false, .off = off, .base = 0 };
}
fn direct_data_base(off: usize, base: usize): DataLoc {
ret DataLoc { .indirect = false, .off = off, .base = base };
}
fn ptr_slot(slot_off: usize): DataLoc {
ret DataLoc { .indirect = true, .off = slot_off, .base = 0 };
}
fn ptr_slot_base(slot_off: usize, base: usize): DataLoc {
ret DataLoc { .indirect = true, .off = slot_off, .base = base };
}
fn cg_push_addr(cg: *Cgen, loc: DataLoc, extra: usize) {
var delta = loc.base + extra;
if loc.indirect {
cg_laddr(cg, loc.off);
cg_load(cg, .Ptr);
if delta != 0 {
cg_push(cg, .U64, delta);
cg_op(cg, .Add, .Ptr);
}
} else {
cg_laddr(cg, loc.off + delta);
}
}
fn cg_copy_agg(cg: *Cgen, src: DataLoc, dst: DataLoc, tid: usize) {
var sz = ir_type_size(cg.ctx, tid);
var done: usize = 0;
while sz - done >= 8 {
cg_push_addr(cg, dst, done);
cg_push_addr(cg, src, done);
cg_load(cg, .U64);
cg_store(cg, .U64);
done = done + 8;
}
while done < sz {
cg_push_addr(cg, dst, done);
cg_push_addr(cg, src, done);
cg_load(cg, .U8);
cg_store(cg, .U8);
done = done + 1;
}
}
fn cg_add_fix(cg: *Cgen, ins_idx: usize, kind: FixKind, node_target: usize, fn_target: usize) {
var fx = Fix {
.kind = kind,
.ins_idx = ins_idx,
.node_target = node_target,
.fn_target = fn_target,
};
match Vec.push(cg.fixups, &fx as *u8) { .Ok => {}, .Err(error) => {} }
}
fn arith_tid(n: *IrEntry): usize {
match n.body {
.Arith(a) => { ret a.tid; },
_ => { ret 0; },
}
ret 0;
}
fn cg_binop(cg: *Cgen, ref: usize, n: *IrEntry, op: Op) {
match n.body {
.Arith(a) => {
var k = vm_kind_of(cg.ctx, a.tid);
cg_load_slot(cg, a.left, k);
cg_load_slot(cg, a.right, k);
cg_op(cg, op, k);
cg_store_slot(cg, ref, k);
},
_ => {},
}
}
fn cg_unop(cg: *Cgen, ref: usize, n: *IrEntry, op: Op) {
match n.body {
.Arith(a) => {
var k = vm_kind_of(cg.ctx, a.tid);
cg_load_slot(cg, a.left, k);
cg_op(cg, op, k);
cg_store_slot(cg, ref, k);
},
_ => {},
}
}
fn cg_cmp(cg: *Cgen, ref: usize, n: *IrEntry, op: Op) {
match n.body {
.Cmp(c) => {
var k = vm_kind_of(cg.ctx, c.tid);
cg_load_slot(cg, c.a, k);
cg_load_slot(cg, c.b, k);
cg_op(cg, op, k);
cg_store_slot(cg, ref, .Bool);
},
_ => {},
}
}
fn cg_const(cg: *Cgen, ref: usize, n: *IrEntry) {
var ctx = cg.ctx;
var tid: usize = 0;
match n.body { .Const(c) => { tid = c.tid; }, _ => { ret; } }
var te = TypeTable.node(ctx.ty, tid);
if te == null { ret; }
match te.node {
.Fn(f) => {
var cref = const_fn(n);
var nat_idx: isize = -1;
if cref > 0 { nat_idx = extern_get(ctx, cref); }
if nat_idx >= 0 {
cg_push(cg, .U64, nat_idx as usize);
} else {
var idx = ip_of(cg);
cg_push(cg, .Ptr, 0);
cg_add_fix(cg, idx, .Fnaddr, 0, cref);
}
cg_store_slot(cg, ref, .Ptr);
ret;
},
.Unit => { ret; },
_ => {},
}
var k = vm_kind_of(ctx, tid);
var imm: usize = 0;
match te.node {
.Bool => {
match n.body {
.Const(c) => {
match c.lit { .Bool(v) => { if v { imm = 1; } }, _ => {} }
},
_ => {},
}
},
.Int(x) => { imm = const_usize(n); },
.Float => {
var fv: f64 = 0.0;
match n.body {
.Const(c) => {
match c.lit { .Float(v) => { fv = v; }, _ => {} }
},
_ => {},
}
Mem.copy(&imm as *any, &fv as *any, 8);
},
.Ptr => { imm = const_usize(n); },
_ => {},
}
cg_push(cg, k, imm);
cg_store_slot(cg, ref, k);
}
fn cg_cast(cg: *Cgen, ref: usize, n: *IrEntry) {
var ctx = cg.ctx;
match n.body {
.Cast(c) => {
var ft = TypeTable.node(ctx.ty, c.from_tid);
var tt = TypeTable.node(ctx.ty, c.to_tid);
if ft == null || tt == null { ret; }
var fk = vm_kind_of(ctx, c.from_tid);
var tk = vm_kind_of(ctx, c.to_tid);
cg_load_slot(cg, c.val, fk);
var f_int = false;
var f_bits: usize = 0;
var f_signed = false;
match ft.node {
.Int(x) => { f_int = true; f_bits = x.bits; f_signed = x.is_signed; },
_ => {},
}
var t_int = false;
var t_bits: usize = 0;
match tt.node {
.Int(x) => { t_int = true; t_bits = x.bits; },
_ => {},
}
var t_bool = false;
match tt.node { .Bool => { t_bool = true; }, _ => {} }
var f_bool = false;
match ft.node { .Bool => { f_bool = true; }, _ => {} }
if f_int && t_int {
if f_bits > t_bits {
emit(cg, .Trunc, .Void, t_bits);
} else if f_bits < t_bits {
if f_signed { emit(cg, .Sext, .Void, f_bits); }
else { emit(cg, .Zext, .Void, f_bits); }
}
} else if f_bool && t_int {
emit(cg, .Zext, .Void, 1);
}
ignore_bool(t_bool);
cg_store_slot(cg, ref, tk);
},
_ => {},
}
}
fn cg_mem(cg: *Cgen, ref: usize, n: *IrEntry) {
var ctx = cg.ctx;
match n.body {
.MemAlloc(m) => {
cg_laddr(cg, slot_data(&cg.frame, ref));
cg_store_slot(cg, ref, .Ptr);
},
.MemGlobalAlloc(m) => {
var idx: usize = 0;
if m.const_node < cg.irf.nodes.len {
var cn = fnode(cg.irf, m.const_node);
var pv: usize = 0;
var ctid: usize = 0;
match cn.body {
.Const(c) => {
ctid = c.tid;
match c.lit { .Ptr(v) => { pv = v; }, _ => {} }
},
_ => {},
}
if pv != 0 {
var size = ir_type_size(ctx, ctid);
idx = Mcode.add_data(&ctx.prog, pv, size);
}
}
if idx > 0 { emit(cg, .Gaddr, .Void, idx); }
else { cg_push(cg, .Ptr, 0); }
cg_store_slot(cg, ref, .Ptr);
},
.MemLoad(m) => {
var tid = m.tid;
if ir_is_aggregate(ctx, tid) {
var data = slot_data(&cg.frame, ref);
cg_copy_agg(cg, ptr_slot(slot_result(&cg.frame, m.ptr)), direct_data(data), tid);
cg_laddr(cg, data);
cg_store_slot(cg, ref, .Ptr);
} else {
var k = vm_kind_of(ctx, tid);
cg_load_slot(cg, m.ptr, .Ptr);
cg_load(cg, k);
cg_store_slot(cg, ref, k);
}
},
.MemStore(m) => {
var tid = m.tid;
if ir_is_aggregate(ctx, tid) {
cg_copy_agg(cg, ptr_slot(slot_result(&cg.frame, m.val)),
ptr_slot(slot_result(&cg.frame, m.ptr)), tid);
} else {
var k = vm_kind_of(ctx, tid);
cg_load_slot(cg, m.ptr, .Ptr);
cg_load_slot(cg, m.val, k);
cg_store(cg, k);
}
},
.MemOffset(m) => {
var elem_size = ir_type_size(ctx, m.tid);
if elem_size == 0 { elem_size = 1; }
cg_load_slot(cg, m.ptr, .Ptr);
cg_load_slot(cg, m.offset, .U64);
cg_push(cg, .U64, elem_size);
cg_op(cg, .Mul, .U64);
cg_op(cg, .Add, .Ptr);
cg_store_slot(cg, ref, .Ptr);
},
.MemFieldPtr(m) => {
var off = ir_agg_field_off(ctx, m.tid, m.field_idx);
cg_load_slot(cg, m.agg, .Ptr);
if off != 0 {
cg_push(cg, .U64, off);
cg_op(cg, .Add, .Ptr);
}
cg_store_slot(cg, ref, .Ptr);
},
_ => {},
}
}
fn cg_agg(cg: *Cgen, ref: usize, n: *IrEntry) {
var ctx = cg.ctx;
match n.body {
.AggConstruct(a) => {
var base = slot_data(&cg.frame, ref);
var agg_t = a.tid;
var i: usize = 0;
while i < a.fields.len {
var fref = usize_at(&a.fields, i);
var ftid = ir_agg_field_tid(ctx, agg_t, i);
if ftid != 0 {
var foff = ir_agg_field_off(ctx, agg_t, i);
if ir_is_aggregate(ctx, ftid) {
cg_copy_agg(cg, ptr_slot(slot_result(&cg.frame, fref)),
direct_data_base(base, foff), ftid);
} else {
var fk = vm_kind_of(ctx, ftid);
cg_load_slot(cg, fref, fk);
cg_store_at(cg, base + foff, fk);
}
}
i = i + 1;
}
cg_laddr(cg, base);
cg_store_slot(cg, ref, .Ptr);
},
.AggExtract(a) => {
var agg_t = a.tid;
var ftid = a.field_tid;
var foff = ir_agg_field_off(ctx, agg_t, a.field_idx);
if ir_is_aggregate(ctx, ftid) {
var data = slot_data(&cg.frame, ref);
cg_copy_agg(cg, ptr_slot_base(slot_result(&cg.frame, a.val), foff),
direct_data(data), ftid);
cg_laddr(cg, data);
cg_store_slot(cg, ref, .Ptr);
} else {
var fk = vm_kind_of(ctx, ftid);
cg_load_slot(cg, a.val, .Ptr);
if foff != 0 {
cg_push(cg, .U64, foff);
cg_op(cg, .Add, .Ptr);
}
cg_load(cg, fk);
cg_store_slot(cg, ref, fk);
}
},
.AggInsert(a) => {
var base = slot_data(&cg.frame, ref);
var agg_t = a.tid;
cg_copy_agg(cg, ptr_slot(slot_result(&cg.frame, a.agg)), direct_data(base), agg_t);
var foff = ir_agg_field_off(ctx, agg_t, a.field_idx);
var ftid = ir_agg_field_tid(ctx, agg_t, a.field_idx);
if ftid != 0 {
if ir_is_aggregate(ctx, ftid) {
cg_copy_agg(cg, ptr_slot(slot_result(&cg.frame, a.field)),
direct_data_base(base, foff), ftid);
} else {
var fk = vm_kind_of(ctx, ftid);
cg_load_slot(cg, a.field, fk);
cg_store_at(cg, base + foff, fk);
}
}
cg_laddr(cg, base);
cg_store_slot(cg, ref, .Ptr);
},
_ => {},
}
}
fn cg_typeop(cg: *Cgen, ref: usize, n: *IrEntry) {
var ctx = cg.ctx;
var v: usize = 0;
match n.body {
.TypeOp(t) => {
match n.kind {
.TypeSizeof => { v = ir_type_size(ctx, t.tid); },
.TypeBitsizeof => {
var info = info_of(ctx, t.tid);
if info != null { v = info.size_bits; }
},
.TypeAlignof => { v = ir_type_align(ctx, t.tid); },
.TypeOffsetof => {
var idx: usize = 0;
if t.field_idx < cg.irf.nodes.len {
idx = const_usize(fnode(cg.irf, t.field_idx));
}
v = ir_agg_field_off(ctx, t.tid, idx);
},
.TypeFieldCount => {
var te = TypeTable.node(ctx.ty, t.tid);
if te != null {
match te.node {
.Struct(agg) => { v = agg.variants.len; },
.Union(agg) => { v = agg.variants.len; },
.Enum(agg) => { v = agg.variants.len; },
.Shape(agg) => { v = agg.variants.len; },
_ => {},
}
}
},
_ => { ret; },
}
},
_ => { ret; },
}
cg_push(cg, .U64, v);
cg_store_slot(cg, ref, .U64);
}
fn cg_control(cg: *Cgen, ref: usize, n: *IrEntry) {
var ctx = cg.ctx;
match n.body {
.Param(p) => {
var k = vm_kind_of(ctx, p.tid);
var idx: usize = 0;
if p.idx < cg.irf.nodes.len {
idx = const_usize(fnode(cg.irf, p.idx));
}
var shift: usize = 0;
if ir_is_aggregate(ctx, fn_ret_type(ctx, cg.irf.fn_tid)) { shift = 1; }
cg_laddr(cg, (idx + shift) * @sizeof(usize));
cg_load(cg, k);
cg_store_slot(cg, ref, k);
},
.Ret(r) => {
var ret_tid = r.tid;
if ir_is_aggregate(ctx, ret_tid) {
if r.val != 0 {
var sz = ir_type_size(ctx, ret_tid);
cg_laddr(cg, 0);
cg_load(cg, .Ptr);
cg_load_slot(cg, r.val, .Ptr);
cg_push(cg, .U64, sz);
var nat = Mcode.get_or_add_native(&ctx.prog, ByteView.from_slice("vm_memcpy"));
cg_push(cg, .U64, nat as usize);
emit(cg, .Ncall, .Void, 3);
emit(cg, .Drop, .Void, 0);
}
emit(cg, .Ret, .Void, 0);
} else {
var k = vm_kind_of(ctx, ret_tid);
if @enum2int(k) != @enum2int(VmKind.Void) && r.val != 0 {
cg_load_slot(cg, r.val, k);
}
emit(cg, .Ret, k, 0);
}
},
.Br(b) => {
cg_load_slot(cg, b.cond, .Bool);
var bz_idx = ip_of(cg);
emit(cg, .Bz, .Void, 0);
cg_add_fix(cg, bz_idx, .Jmp, b.false_label, 0);
var jmp_idx = ip_of(cg);
emit(cg, .Jmp, .Void, 0);
cg_add_fix(cg, jmp_idx, .Jmp, b.true_label, 0);
},
.Jmp(j) => {
var idx = ip_of(cg);
emit(cg, .Jmp, .Void, 0);
cg_add_fix(cg, idx, .Jmp, j.label, 0);
},
.Call(cl) => {
var callee: usize = 0;
if cl.func < cg.irf.nodes.len {
callee = const_fn(fnode(cg.irf, cl.func));
}
var nat_idx: isize = -1;
if callee > 0 { nat_idx = extern_get(ctx, callee); }
var nargs = cl.params.len;
if nat_idx >= 0 {
var i: usize = 0;
while i < nargs {
var pref = usize_at(&cl.params, i);
var pn = fnode(cg.irf, pref);
var pk = vm_kind_of(ctx, ir_result_tid(ctx, pn));
cg_load_slot(cg, pref, pk);
i = i + 1;
}
cg_push(cg, .U64, nat_idx as usize);
emit(cg, .Ncall, .Void, nargs);
var rk = vm_kind_of(ctx, cl.tid);
if @enum2int(rk) == @enum2int(VmKind.Void) {
emit(cg, .Drop, .Void, 0);
} else {
cg_store_slot(cg, ref, rk);
}
} else {
var ret_t = cl.tid;
var ret_is_sret = ir_is_aggregate(ctx, ret_t);
var sret_off: usize = 0;
if ret_is_sret {
sret_off = slot_data(&cg.frame, ref);
cg_laddr(cg, sret_off);
}
var i: usize = 0;
while i < nargs {
var pref = usize_at(&cl.params, i);
var pn = fnode(cg.irf, pref);
var pk = vm_kind_of(ctx, ir_result_tid(ctx, pn));
cg_load_slot(cg, pref, pk);
i = i + 1;
}
cg_load_slot(cg, cl.func, .Ptr);
var actual = nargs;
if ret_is_sret { actual = actual + 1; }
emit(cg, .Call, .Void, actual);
if ret_is_sret {
cg_laddr(cg, sret_off);
cg_store_slot(cg, ref, .Ptr);
} else {
var rk = vm_kind_of(ctx, ret_t);
if @enum2int(rk) != @enum2int(VmKind.Void) {
cg_store_slot(cg, ref, rk);
}
}
}
},
_ => {},
}
}
fn cg_node(cg: *Cgen, ref: usize, n: *IrEntry) {
var ctx = cg.ctx;
match n.kind {
.TypeConst => { cg_const(cg, ref, n); },
.ArithAdd => { cg_binop(cg, ref, n, .Add); },
.ArithSub => { cg_binop(cg, ref, n, .Sub); },
.ArithMul => { cg_binop(cg, ref, n, .Mul); },
.ArithDiv => {
if tid_is_signed(ctx, arith_tid(n)) { cg_binop(cg, ref, n, .DivS); }
else { cg_binop(cg, ref, n, .DivU); }
},
.ArithRem => {
if tid_is_signed(ctx, arith_tid(n)) { cg_binop(cg, ref, n, .RemS); }
else { cg_binop(cg, ref, n, .RemU); }
},
.ArithAnd => { cg_binop(cg, ref, n, .And); },
.ArithOr => { cg_binop(cg, ref, n, .Or); },
.ArithXor => { cg_binop(cg, ref, n, .Xor); },
.ArithShl => { cg_binop(cg, ref, n, .Shl); },
.ArithShr => {
if tid_is_signed(ctx, arith_tid(n)) { cg_binop(cg, ref, n, .ShrS); }
else { cg_binop(cg, ref, n, .ShrU); }
},
.ArithNeg => { cg_unop(cg, ref, n, .Neg); },
.ArithNot => {
var tid = arith_tid(n);
var te = TypeTable.node(ctx.ty, tid);
var is_bool = false;
if te != null {
match te.node { .Bool => { is_bool = true; }, _ => {} }
}
if is_bool {
match n.body { .Arith(a) => {
cg_load_slot(cg, a.left, .Bool);
}, _ => {} }
cg_push(cg, .Bool, 0);
cg_op(cg, .Eq, .Bool);
cg_store_slot(cg, ref, .Bool);
} else {
cg_unop(cg, ref, n, .Not);
}
},
.ArithAbs => {},
.CmpEq => { cg_cmp(cg, ref, n, .Eq); },
.CmpNe => { cg_cmp(cg, ref, n, .Ne); },
.CmpLt => {
var k = cmp_tid(n);
if tid_is_signed(ctx, k) { cg_cmp(cg, ref, n, .Slt); } else { cg_cmp(cg, ref, n, .Ult); }
},
.CmpLe => {
var k = cmp_tid(n);
if tid_is_signed(ctx, k) { cg_cmp(cg, ref, n, .Sle); } else { cg_cmp(cg, ref, n, .Ule); }
},
.CmpGt => {
var k = cmp_tid(n);
if tid_is_signed(ctx, k) { cg_cmp(cg, ref, n, .Sgt); } else { cg_cmp(cg, ref, n, .Ugt); }
},
.CmpGe => {
var k = cmp_tid(n);
if tid_is_signed(ctx, k) { cg_cmp(cg, ref, n, .Sge); } else { cg_cmp(cg, ref, n, .Uge); }
},
.CastTrunc => { cg_cast(cg, ref, n); },
.CastZext => { cg_cast(cg, ref, n); },
.CastSext => { cg_cast(cg, ref, n); },
.CastFext => { cg_cast(cg, ref, n); },
.CastFtrunc => { cg_cast(cg, ref, n); },
.CastBitcast => { cg_cast(cg, ref, n); },
.CastPtr2int => { cg_cast(cg, ref, n); },
.CastInt2ptr => { cg_cast(cg, ref, n); },
.CastBool2int => { cg_cast(cg, ref, n); },
.CaseInt2float => { cg_cast(cg, ref, n); },
.CaseFloat2int => { cg_cast(cg, ref, n); },
.MemAlloca => { cg_mem(cg, ref, n); },
.MemGlobalAlloc => { cg_mem(cg, ref, n); },
.MemLoad => { cg_mem(cg, ref, n); },
.MemStore => { cg_mem(cg, ref, n); },
.MemOffset => { cg_mem(cg, ref, n); },
.MemFieldPtr => { cg_mem(cg, ref, n); },
.AggConstruct => { cg_agg(cg, ref, n); },
.AggExtract => { cg_agg(cg, ref, n); },
.AggInsert => { cg_agg(cg, ref, n); },
.TypeSizeof => { cg_typeop(cg, ref, n); },
.TypeBitsizeof => { cg_typeop(cg, ref, n); },
.TypeAlignof => { cg_typeop(cg, ref, n); },
.TypeOffsetof => { cg_typeop(cg, ref, n); },
.TypeFieldCount => { cg_typeop(cg, ref, n); },
.ControlParam => { cg_control(cg, ref, n); },
.ControlRet => { cg_control(cg, ref, n); },
.ControlBr => { cg_control(cg, ref, n); },
.ControlJmp => { cg_control(cg, ref, n); },
.ControlCall => { cg_control(cg, ref, n); },
.ControlUnreachable => { emit(cg, .Halt, .Void, 1); },
.ControlTrap => { emit(cg, .Halt, .Void, 1); },
.DbgPanic => {
var data = slot_data(&cg.frame, ref);
var msg = ByteView.empty();
match n.body { .Panic(p) => { msg = p.msg; }, _ => {} }
var msg_idx = Mcode.add_data(&ctx.prog, msg.ptr as usize, msg.len);
var fmt = ByteView.from_slice("%s");
var fmt_idx = Mcode.add_data(&ctx.prog, fmt.ptr as usize, 2);
if msg_idx <= 0 || fmt_idx <= 0 { ret; }
cg_gaddr(cg, fmt_idx);
cg_store_at(cg, data + 0, .Ptr);
cg_push(cg, .U64, 2);
cg_store_at(cg, data + 8, .U64);
cg_gaddr(cg, msg_idx);
cg_store_at(cg, data + 16, .Ptr);
cg_push(cg, .U64, msg.len);
cg_store_at(cg, data + 24, .U64);
cg_laddr(cg, data + 0);
cg_laddr(cg, data + 16);
var nat = Mcode.get_or_add_native(&ctx.prog, ByteView.from_slice("vm_printf"));
cg_push(cg, .U64, nat as usize);
emit(cg, .Ncall, .Void, 2);
emit(cg, .Drop, .Void, 0);
},
.DbgBreakpoint => { emit(cg, .Bk, .Void, 0); },
_ => {},
}
}
fn cmp_tid(n: *IrEntry): usize {
match n.body {
.Cmp(c) => { ret c.tid; },
_ => { ret 0; },
}
ret 0;
}
// ---- 翻译 ----
fn translate_function(ctx: *Ir2Vm, fid: usize, all_fixups: *Vec): usize {
var irf = IrM.func(ctx.ir, fid);
if irf == null { ret 0; }
var local_fixups = Vec.blank(@sizeof(Fix), @alignof(Fix));
var cg: Cgen = undefined;
cg.ctx = ctx;
cg.fid = fid;
cg.irf = irf;
cg.frame.slots = Vec.blank(@sizeof(Slot), @alignof(Slot));
cg.frame.args_bytes = 0;
cg.frame.frame_bytes = 0;
match Vec.with_capacity(@sizeof(usize), @alignof(usize), irf.nodes.len) {
.Ok(v) => { cg.node_ip = v; },
.Err(error) => { cg.node_ip = Vec.blank(@sizeof(usize), @alignof(usize)); },
}
cg.fixups = &local_fixups;
cg.terminated = false;
frame_build(&cg.frame, ctx, fid);
var func_ip = ip_of(&cg);
ignore_usize(Mcode.add_func(&ctx.prog, irf.name, fn_nargs(ctx, irf.fn_tid), func_ip));
var local_slots = (cg.frame.frame_bytes - cg.frame.args_bytes + 7) / 8;
emit(&cg, .Alloc, .Void, local_slots);
var nnodes = irf.nodes.len;
var i: usize = 0;
while i < nnodes {
push_usize(&cg.node_ip, 0);
i = i + 1;
}
i = 1;
while i < nnodes {
(cg.node_ip.data as *usize)[i] = ip_of(&cg);
cg_node(&cg, i, fnode(irf, i));
i = i + 1;
}
i = 0;
while i < local_fixups.len {
match Vec.get(&local_fixups, i) {
.Ok(ptr) => {
var fx = ptr as *Fix;
if @enum2int(fx.kind) == @enum2int(FixKind.Jmp) {
var ins = ins_ptr(&ctx.prog, fx.ins_idx);
if ins != null {
var target_ip: usize = 0;
if fx.node_target < nnodes {
target_ip = usize_at(&cg.node_ip, fx.node_target);
}
ins.imm = target_ip - fx.ins_idx - 1;
}
} else if @enum2int(fx.kind) == @enum2int(FixKind.Fnaddr) {
match Vec.push(all_fixups, ptr as *u8) { .Ok => {}, .Err(error) => {} }
}
},
.Err(error) => {},
}
i = i + 1;
}
Vec.drop(&cg.node_ip);
Vec.drop(&local_fixups);
frame_drop(&cg.frame);
ret func_ip;
}
// ---- 生命周期 / 入口 ----
// 就地初始化(避免返回含 Prog 的大聚合;对应 stage1 spl_ir2vm_init)。
fn init(ctx: *Ir2Vm, ir: *Ir, ty: *TypeTable, layout: *Layout, attr: *Attr) {
ctx.ir = ir;
ctx.ty = ty;
ctx.attr = attr;
ctx.layout = layout;
ctx.extern_native = Vec.blank(@sizeof(isize), @alignof(isize));
match Mcode.new() {
.Ok(v) => { ctx.prog = v; },
.Err(error) => { @assert(false, "ir2vm: prog init failed"); },
}
}
fn drop(ctx: *Ir2Vm) {
Mcode.drop(&ctx.prog);
Vec.drop(&ctx.extern_native);
}
fn run_func(ctx: *Ir2Vm, ref: usize): i32 {
var all_fixups = Vec.blank(@sizeof(Fix), @alignof(Fix));
defer { Vec.drop(&all_fixups); }
ignore_usize(translate_function(ctx, ref, &all_fixups));
ret 0;
}
fn run(ctx: *Ir2Vm): i32 {
var n = ctx.ir.funcs.len;
if n <= 1 { ret 0; }
// Pass 1:登记 extern 函数 -> native 索引。
var i: usize = 0;
while i < n {
var v: isize = -1;
match Vec.push(&ctx.extern_native, &v as *u8) { .Ok => {}, .Err(error) => {} }
i = i + 1;
}
i = 1;
while i < n {
var irf = IrM.func(ctx.ir, i);
if irf != null && fn_check_extern(ctx, irf) {
var nat = Mcode.get_or_add_native(&ctx.prog, irf.name);
extern_set(ctx, i, nat);
}
i = i + 1;
}
// 哨兵:保证真实函数入口地址非零。
ignore_usize(Mcode.add_instr(&ctx.prog, .Halt, .Void, 0));
// 预分配指令容量(按 IR 节点数粗略估计),减少逐条 append 的扩容/拷贝。
var est: usize = 16;
i = 1;
while i < n {
var irf = IrM.func(ctx.ir, i);
if irf != null { est = est + irf.nodes.len * 8 + 32; }
i = i + 1;
}
match Vec.reserve(&ctx.prog.insns, est) { .Ok => {}, .Err(error) => {} }
// Pass 2:翻译非 extern 函数。
var fid_ip = Vec.blank(@sizeof(usize), @alignof(usize));
i = 0;
while i < n {
push_usize(&fid_ip, 0);
i = i + 1;
}
var all_fixups = Vec.blank(@sizeof(Fix), @alignof(Fix));
defer { Vec.drop(&all_fixups); }
i = 1;
while i < n {
if extern_get(ctx, i) < 0 {
var ip = translate_function(ctx, i, &all_fixups);
(fid_ip.data as *usize)[i] = ip;
}
i = i + 1;
}
// FNADDR 回填。
i = 0;
while i < all_fixups.len {
match Vec.get(&all_fixups, i) {
.Ok(ptr) => {
var fx = ptr as *Fix;
if @enum2int(fx.kind) == @enum2int(FixKind.Fnaddr) {
var ins = ins_ptr(&ctx.prog, fx.ins_idx);
if ins != null {
var target: usize = 0;
if fx.fn_target > 0 && fx.fn_target < n {
target = usize_at(&fid_ip, fx.fn_target);
}
ins.imm = target;
}
}
},
.Err(error) => {},
}
i = i + 1;
}
// 每个函数的 ninsns = 下一个入口 - 本入口。
i = 0;
while i < ctx.prog.funcs.len {
var f = func_ptr(&ctx.prog, i);
if f != null {
var next = ctx.prog.insns.len;
if i + 1 < ctx.prog.funcs.len {
var nf = func_ptr(&ctx.prog, i + 1);
if nf != null { next = nf.address; }
}
f.ninsns = next - f.address;
}
i = i + 1;
}
Vec.drop(&fid_ip);
ret 0;
}
fn dump(ctx: *Ir2Vm, out: *ByteBuffer): bool {
ret Mcode.dump(&ctx.prog, out);
}
// 把翻译结果写成 .sir(二进制)。
fn store(ctx: *Ir2Vm, path: ByteView): bool {
match Mcode.store(&ctx.prog, path) {
.Ok => { ret true; },
.Err(error) => { ret false; },
}
ret false;
}
// ---- 工具 ----
fn ignore_usize(v: usize) {}
fn ignore_bool(b: bool) {}
// ---- 测试 ----
fn main() {
var ir = IrM.new();
defer { IrM.drop(&ir); }
var tt: TypeTable = undefined;
match TypeTable.new() {
.Ok(v) => { tt = v; },
.Err(error) => { @assert(false, "tt init"); ret; },
}
defer { TypeTable.drop(&tt); }
var layout: Layout = undefined;
match LayoutM.new() {
.Ok(v) => { layout = v; },
.Err(error) => { @assert(false, "layout init"); ret; },
}
defer { LayoutM.drop(&layout); }
var attr: Attr = undefined;
match AttrM.new() {
.Ok(v) => { attr = v; },
.Err(error) => { @assert(false, "attr init"); ret; },
}
defer { AttrM.drop(&attr); }
var ctx: Ir2Vm = undefined;
init(&ctx, &ir, &tt, &layout, &attr);
defer { drop(&ctx); }
var rc = run(&ctx);
@assert(rc == 0, "run empty ir");
// 只有哨兵函数:无指令。
@assert(ctx.prog.insns.len == 0, "empty ir yields no insns");
}