Files
sx/src/ir/print.zig
2026-03-01 22:38:41 +02:00

540 lines
24 KiB
Zig

const std = @import("std");
const types = @import("types.zig");
const inst_mod = @import("inst.zig");
const mod_mod = @import("module.zig");
const TypeId = types.TypeId;
const TypeTable = types.TypeTable;
const StringId = types.StringId;
const Ref = inst_mod.Ref;
const BlockId = inst_mod.BlockId;
const FuncId = inst_mod.FuncId;
const GlobalId = inst_mod.GlobalId;
const Inst = inst_mod.Inst;
const Op = inst_mod.Op;
const Function = inst_mod.Function;
const Block = inst_mod.Block;
const Global = inst_mod.Global;
const ConstantValue = inst_mod.ConstantValue;
const Module = mod_mod.Module;
const Writer = *std.Io.Writer;
// ── Public API ──────────────────────────────────────────────────────────
pub fn printModule(module: *const Module, writer: Writer) !void {
// Print globals
for (module.globals.items, 0..) |global, i| {
try printGlobal(&global, @intCast(i), module, writer);
}
if (module.globals.items.len > 0 and module.functions.items.len > 0) {
try writer.writeByte('\n');
}
// Print functions
for (module.functions.items, 0..) |*func, i| {
if (i > 0) try writer.writeByte('\n');
try printFunction(func, @intCast(i), module, writer);
}
}
pub fn printFunction(func: *const Function, func_idx: u32, module: *const Module, writer: Writer) !void {
const tt = &module.types;
// Signature
if (func.is_extern) try writer.writeAll("extern ");
if (func.is_comptime) try writer.writeAll("comptime ");
try writer.writeAll("func @");
try writer.writeAll(tt.getString(func.name));
try writer.writeByte('(');
for (func.params, 0..) |param, i| {
if (i > 0) try writer.writeAll(", ");
const pname = tt.getString(param.name);
if (pname.len > 0) {
try writer.writeAll(pname);
try writer.writeAll(": ");
}
try writeType(param.ty, tt, writer);
}
try writer.writeAll(") -> ");
try writeType(func.ret, tt, writer);
if (func.is_extern) {
try writer.writeAll(";\n");
return;
}
try writer.writeAll(" {\n");
// Blocks — each block tracks its own first_ref from emission order
_ = func_idx;
for (func.blocks.items, 0..) |*block, bi| {
var ref_counter: u32 = block.first_ref;
try printBlock(block, @intCast(bi), tt, &ref_counter, writer);
}
try writer.writeAll("}\n");
}
fn printGlobal(global: *const Global, _: u32, module: *const Module, writer: Writer) !void {
const tt = &module.types;
if (global.is_extern) try writer.writeAll("extern ");
if (global.is_const) try writer.writeAll("const ") else try writer.writeAll("global ");
try writer.writeAll("@");
try writer.writeAll(tt.getString(global.name));
try writer.writeAll(": ");
try writeType(global.ty, tt, writer);
if (global.init_val) |init| {
try writer.writeAll(" = ");
try writeConstant(init, writer);
}
if (global.comptime_func) |fid| {
try writer.print(" = #run @{d}", .{fid.index()});
}
try writer.writeAll(";\n");
}
fn printBlock(block: *const Block, block_idx: u32, tt: *const TypeTable, ref_counter: *u32, writer: Writer) !void {
// Block header
try writer.writeAll(" ");
const name = tt.getString(block.name);
if (name.len > 0) {
try writer.writeAll(name);
} else {
try writer.print("bb{d}", .{block_idx});
}
if (block.params.len > 0) {
try writer.writeByte('(');
for (block.params, 0..) |pty, i| {
if (i > 0) try writer.writeAll(", ");
try writeType(pty, tt, writer);
}
try writer.writeByte(')');
}
try writer.writeAll(":\n");
// Instructions
for (block.insts.items) |*instruction| {
try printInst(instruction, ref_counter.*, tt, writer);
ref_counter.* += 1;
}
}
fn printInst(instruction: *const Inst, ref_idx: u32, tt: *const TypeTable, writer: Writer) !void {
const op = instruction.op;
const ty = instruction.ty;
// Check if this is a void/terminator instruction (no result)
const has_result = !isVoidOp(op);
try writer.writeAll(" ");
if (has_result) {
try writer.print("%{d} = ", .{ref_idx});
}
switch (op) {
// ── Constants ───────────────────────────────────────────
.const_int => |v| try writer.print("const {d} : ", .{v}),
.const_float => |v| try writer.print("const {d:.6} : ", .{v}),
.const_bool => |v| try writer.print("const {s} : ", .{if (v) "true" else "false"}),
.const_string => |sid| {
try writer.writeAll("const \"");
try writer.writeAll(tt.getString(sid));
try writer.writeAll("\" : ");
},
.const_null => try writer.writeAll("const null : "),
.const_undef => try writer.writeAll("const undef : "),
// ── Arithmetic ──────────────────────────────────────────
.add => |b| try writer.print("add %{d}, %{d} : ", .{ b.lhs.index(), b.rhs.index() }),
.sub => |b| try writer.print("sub %{d}, %{d} : ", .{ b.lhs.index(), b.rhs.index() }),
.mul => |b| try writer.print("mul %{d}, %{d} : ", .{ b.lhs.index(), b.rhs.index() }),
.div => |b| try writer.print("div %{d}, %{d} : ", .{ b.lhs.index(), b.rhs.index() }),
.mod => |b| try writer.print("mod %{d}, %{d} : ", .{ b.lhs.index(), b.rhs.index() }),
.neg => |u| try writer.print("neg %{d} : ", .{u.operand.index()}),
// ── Bitwise ─────────────────────────────────────────────
.bit_and => |b| try writer.print("bit_and %{d}, %{d} : ", .{ b.lhs.index(), b.rhs.index() }),
.bit_or => |b| try writer.print("bit_or %{d}, %{d} : ", .{ b.lhs.index(), b.rhs.index() }),
.bit_xor => |b| try writer.print("bit_xor %{d}, %{d} : ", .{ b.lhs.index(), b.rhs.index() }),
.bit_not => |u| try writer.print("bit_not %{d} : ", .{u.operand.index()}),
.shl => |b| try writer.print("shl %{d}, %{d} : ", .{ b.lhs.index(), b.rhs.index() }),
.shr => |b| try writer.print("shr %{d}, %{d} : ", .{ b.lhs.index(), b.rhs.index() }),
// ── Comparison ──────────────────────────────────────────
.cmp_eq => |b| try writer.print("cmp_eq %{d}, %{d} : ", .{ b.lhs.index(), b.rhs.index() }),
.cmp_ne => |b| try writer.print("cmp_ne %{d}, %{d} : ", .{ b.lhs.index(), b.rhs.index() }),
.cmp_lt => |b| try writer.print("cmp_lt %{d}, %{d} : ", .{ b.lhs.index(), b.rhs.index() }),
.cmp_le => |b| try writer.print("cmp_le %{d}, %{d} : ", .{ b.lhs.index(), b.rhs.index() }),
.cmp_gt => |b| try writer.print("cmp_gt %{d}, %{d} : ", .{ b.lhs.index(), b.rhs.index() }),
.cmp_ge => |b| try writer.print("cmp_ge %{d}, %{d} : ", .{ b.lhs.index(), b.rhs.index() }),
.str_eq => |b| try writer.print("str_eq %{d}, %{d} : ", .{ b.lhs.index(), b.rhs.index() }),
.str_ne => |b| try writer.print("str_ne %{d}, %{d} : ", .{ b.lhs.index(), b.rhs.index() }),
// ── Logical ─────────────────────────────────────────────
.bool_and => |b| try writer.print("bool_and %{d}, %{d} : ", .{ b.lhs.index(), b.rhs.index() }),
.bool_or => |b| try writer.print("bool_or %{d}, %{d} : ", .{ b.lhs.index(), b.rhs.index() }),
.bool_not => |u| try writer.print("bool_not %{d} : ", .{u.operand.index()}),
// ── Conversions ─────────────────────────────────────────
.widen => |c| {
try writer.print("widen %{d} : ", .{c.operand.index()});
try writeType(c.from, tt, writer);
try writer.writeAll(" -> ");
try writeType(c.to, tt, writer);
try writer.writeByte('\n');
return;
},
.narrow => |c| {
try writer.print("narrow %{d} : ", .{c.operand.index()});
try writeType(c.from, tt, writer);
try writer.writeAll(" -> ");
try writeType(c.to, tt, writer);
try writer.writeByte('\n');
return;
},
.bitcast => |c| {
try writer.print("bitcast %{d} : ", .{c.operand.index()});
try writeType(c.from, tt, writer);
try writer.writeAll(" -> ");
try writeType(c.to, tt, writer);
try writer.writeByte('\n');
return;
},
.int_to_float => |c| {
try writer.print("int_to_float %{d} : ", .{c.operand.index()});
try writeType(c.from, tt, writer);
try writer.writeAll(" -> ");
try writeType(c.to, tt, writer);
try writer.writeByte('\n');
return;
},
.float_to_int => |c| {
try writer.print("float_to_int %{d} : ", .{c.operand.index()});
try writeType(c.from, tt, writer);
try writer.writeAll(" -> ");
try writeType(c.to, tt, writer);
try writer.writeByte('\n');
return;
},
// ── Memory ──────────────────────────────────────────────
.alloca => |aty| {
try writer.writeAll("alloca ");
try writeType(aty, tt, writer);
try writer.writeAll(" : ");
},
.load => |u| try writer.print("load %{d} : ", .{u.operand.index()}),
.store => |s| {
try writer.print("store %{d}, %{d}\n", .{ s.ptr.index(), s.val.index() });
return;
},
.heap_alloc => |u| try writer.print("heap_alloc %{d} : ", .{u.operand.index()}),
.heap_free => |u| {
try writer.print("heap_free %{d}\n", .{u.operand.index()});
return;
},
// ── Struct ops ──────────────────────────────────────────
.struct_init => |agg| {
try writer.writeAll("struct_init [");
for (agg.fields, 0..) |f, i| {
if (i > 0) try writer.writeAll(", ");
try writer.print("%{d}", .{f.index()});
}
try writer.writeAll("] : ");
},
.struct_get => |fa| try writer.print("struct_get %{d}, {d} : ", .{ fa.base.index(), fa.field_index }),
.struct_gep => |fa| try writer.print("struct_gep %{d}, {d} : ", .{ fa.base.index(), fa.field_index }),
// ── Enum ops ────────────────────────────────────────────
.enum_init => |ei| {
if (ei.payload.isNone()) {
try writer.print("enum_init tag={d} : ", .{ei.tag});
} else {
try writer.print("enum_init tag={d}, payload=%{d} : ", .{ ei.tag, ei.payload.index() });
}
},
.enum_tag => |u| try writer.print("enum_tag %{d} : ", .{u.operand.index()}),
.enum_payload => |fa| try writer.print("enum_payload %{d}, {d} : ", .{ fa.base.index(), fa.field_index }),
// ── Union ops ───────────────────────────────────────────
.union_get => |fa| try writer.print("union_get %{d}, {d} : ", .{ fa.base.index(), fa.field_index }),
.union_gep => |fa| try writer.print("union_gep %{d}, {d} : ", .{ fa.base.index(), fa.field_index }),
// ── Array/Slice ops ─────────────────────────────────────
.index_get => |b| try writer.print("index_get %{d}[%{d}] : ", .{ b.lhs.index(), b.rhs.index() }),
.index_gep => |b| try writer.print("index_gep %{d}[%{d}] : ", .{ b.lhs.index(), b.rhs.index() }),
.length => |u| try writer.print("length %{d} : ", .{u.operand.index()}),
.data_ptr => |u| try writer.print("data_ptr %{d} : ", .{u.operand.index()}),
.subslice => |s| try writer.print("subslice %{d}[%{d}..%{d}] : ", .{ s.base.index(), s.lo.index(), s.hi.index() }),
.array_to_slice => |u| try writer.print("array_to_slice %{d} : ", .{u.operand.index()}),
// ── Tuple ops ───────────────────────────────────────────
.tuple_init => |agg| {
try writer.writeAll("tuple_init [");
for (agg.fields, 0..) |f, i| {
if (i > 0) try writer.writeAll(", ");
try writer.print("%{d}", .{f.index()});
}
try writer.writeAll("] : ");
},
.tuple_get => |fa| try writer.print("tuple_get %{d}, {d} : ", .{ fa.base.index(), fa.field_index }),
// ── Optional ops ────────────────────────────────────────
.optional_wrap => |u| try writer.print("optional_wrap %{d} : ", .{u.operand.index()}),
.optional_unwrap => |u| try writer.print("optional_unwrap %{d} : ", .{u.operand.index()}),
.optional_has_value => |u| try writer.print("optional_has_value %{d} : ", .{u.operand.index()}),
.optional_coalesce => |b| try writer.print("optional_coalesce %{d}, %{d} : ", .{ b.lhs.index(), b.rhs.index() }),
// ── Pointer ops ─────────────────────────────────────────
.addr_of => |u| try writer.print("addr_of %{d} : ", .{u.operand.index()}),
.deref => |u| try writer.print("deref %{d} : ", .{u.operand.index()}),
// ── Vector ops ──────────────────────────────────────────
.vec_splat => |u| try writer.print("vec_splat %{d} : ", .{u.operand.index()}),
.vec_extract => |b| try writer.print("vec_extract %{d}[%{d}] : ", .{ b.lhs.index(), b.rhs.index() }),
.vec_insert => |t| try writer.print("vec_insert %{d}[%{d}] = %{d} : ", .{ t.a.index(), t.b.index(), t.c.index() }),
// ── Calls ───────────────────────────────────────────────
.call => |c| {
try writer.print("call @{d}(", .{c.callee.index()});
try writeArgs(c.args, writer);
try writer.writeAll(") : ");
},
.call_indirect => |c| {
try writer.print("call_indirect %{d}(", .{c.callee.index()});
try writeArgs(c.args, writer);
try writer.writeAll(") : ");
},
.call_closure => |c| {
try writer.print("call_closure %{d}(", .{c.callee.index()});
try writeArgs(c.args, writer);
try writer.writeAll(") : ");
},
.call_builtin => |c| {
try writer.print("call_builtin {s}(", .{@tagName(c.builtin)});
try writeArgs(c.args, writer);
try writer.writeAll(") : ");
},
// ── Protocol ────────────────────────────────────────────
.protocol_call_dynamic => |c| {
try writer.print("protocol_call_dynamic %{d}.{d}(", .{ c.receiver.index(), c.method_index });
try writeArgs(c.args, writer);
try writer.writeAll(") : ");
},
.protocol_erase => |pe| {
try writer.print("protocol_erase %{d} -> ", .{pe.concrete.index()});
try writeType(pe.protocol_type, tt, writer);
try writer.writeByte('\n');
return;
},
// ── Closure ─────────────────────────────────────────────
.closure_create => |cc| {
try writer.print("closure_create @{d}", .{cc.func.index()});
if (!cc.env.isNone()) {
try writer.print(", env=%{d}", .{cc.env.index()});
}
try writer.writeAll(" : ");
},
// ── Context ─────────────────────────────────────────────
.context_load => |co| {
try writer.writeAll("context_load .");
try writer.writeAll(tt.getString(co.field));
try writer.writeAll(" : ");
},
.context_store => |co| {
try writer.writeAll("context_store .");
try writer.writeAll(tt.getString(co.field));
try writer.print(", %{d}\n", .{co.value.index()});
return;
},
.context_save => {
try writer.writeAll("context_save : ");
},
.context_restore => |u| {
try writer.print("context_restore %{d}\n", .{u.operand.index()});
return;
},
// ── Globals ─────────────────────────────────────────────
.global_get => |gid| try writer.print("global_get @{d} : ", .{gid.index()}),
.func_ref => |fid| try writer.print("func_ref @{d} : ", .{@intFromEnum(fid)}),
.global_set => |gs| {
try writer.print("global_set @{d}, %{d}\n", .{ gs.global.index(), gs.value.index() });
return;
},
// ── Block params ────────────────────────────────────────
.block_param => |bp| try writer.print("block_param bb{d}[{d}] : ", .{ bp.block.index(), bp.param_index }),
// ── Any ─────────────────────────────────────────────────
.box_any => |ba| try writer.print("box_any %{d} : ", .{ba.operand.index()}),
.unbox_any => |u| try writer.print("unbox_any %{d} : ", .{u.operand.index()}),
// ── Reflection ──────────────────────────────────────────
.field_name_get => |fr| try writer.print("field_name_get T{d}[%{d}] : ", .{ fr.struct_type.index(), fr.index.index() }),
.field_value_get => |fr| try writer.print("field_value_get %{d}, T{d}[%{d}] : ", .{ fr.base.index(), fr.struct_type.index(), fr.index.index() }),
// ── Terminators ─────────────────────────────────────────
.br => |b| {
try writer.print("br bb{d}", .{b.target.index()});
if (b.args.len > 0) {
try writer.writeByte('(');
try writeArgs(b.args, writer);
try writer.writeByte(')');
}
try writer.writeByte('\n');
return;
},
.cond_br => |cb| {
try writer.print("cond_br %{d}, bb{d}", .{ cb.cond.index(), cb.then_target.index() });
if (cb.then_args.len > 0) {
try writer.writeByte('(');
try writeArgs(cb.then_args, writer);
try writer.writeByte(')');
}
try writer.print(", bb{d}", .{cb.else_target.index()});
if (cb.else_args.len > 0) {
try writer.writeByte('(');
try writeArgs(cb.else_args, writer);
try writer.writeByte(')');
}
try writer.writeByte('\n');
return;
},
.switch_br => |sb| {
try writer.print("switch_br %{d} [", .{sb.operand.index()});
for (sb.cases, 0..) |case, i| {
if (i > 0) try writer.writeAll(", ");
try writer.print("{d} -> bb{d}", .{ case.value, case.target.index() });
}
try writer.print("] default bb{d}\n", .{sb.default.index()});
return;
},
.ret => |u| {
try writer.print("ret %{d}\n", .{u.operand.index()});
return;
},
.ret_void => {
try writer.writeAll("ret void\n");
return;
},
.@"unreachable" => {
try writer.writeAll("unreachable\n");
return;
},
// ── Misc ────────────────────────────────────────────────
.placeholder => |sid| {
try writer.writeAll("placeholder \"");
try writer.writeAll(tt.getString(sid));
try writer.writeAll("\" : ");
},
}
// Default: print the result type
try writeType(ty, tt, writer);
try writer.writeByte('\n');
}
// ── Helpers ─────────────────────────────────────────────────────────────
fn writeType(id: TypeId, tt: *const TypeTable, writer: Writer) !void {
// Fast path for builtins
if (id.isBuiltin()) {
try writer.writeAll(tt.typeName(id));
return;
}
// Composite types — format recursively
const info = tt.get(id);
switch (info) {
.@"struct" => |s| try writer.writeAll(tt.getString(s.name)),
.@"enum" => |e| try writer.writeAll(tt.getString(e.name)),
.@"union" => |u| try writer.writeAll(tt.getString(u.name)),
.tagged_union => |u| try writer.writeAll(tt.getString(u.name)),
.protocol => |p| try writer.writeAll(tt.getString(p.name)),
.pointer => |p| {
try writer.writeByte('*');
try writeType(p.pointee, tt, writer);
},
.many_pointer => |p| {
try writer.writeAll("[*]");
try writeType(p.element, tt, writer);
},
.slice => |s| {
try writer.writeAll("[]");
try writeType(s.element, tt, writer);
},
.array => |a| {
try writer.print("[{d}]", .{a.length});
try writeType(a.element, tt, writer);
},
.optional => |o| {
try writer.writeByte('?');
try writeType(o.child, tt, writer);
},
.vector => |v| {
try writer.print("Vector({d}, ", .{v.length});
try writeType(v.element, tt, writer);
try writer.writeByte(')');
},
.function => |f| {
try writer.writeByte('(');
for (f.params, 0..) |p, i| {
if (i > 0) try writer.writeAll(", ");
try writeType(p, tt, writer);
}
try writer.writeAll(") -> ");
try writeType(f.ret, tt, writer);
},
.closure => |c| {
try writer.writeAll("closure(");
for (c.params, 0..) |p, i| {
if (i > 0) try writer.writeAll(", ");
try writeType(p, tt, writer);
}
try writer.writeAll(") -> ");
try writeType(c.ret, tt, writer);
},
.tuple => |t| {
try writer.writeByte('(');
for (t.fields, 0..) |f, i| {
if (i > 0) try writer.writeAll(", ");
try writeType(f, tt, writer);
}
try writer.writeByte(')');
},
else => try writer.writeAll(tt.typeName(id)),
}
}
fn writeArgs(args: []const Ref, writer: Writer) !void {
for (args, 0..) |arg, i| {
if (i > 0) try writer.writeAll(", ");
try writer.print("%{d}", .{arg.index()});
}
}
fn writeConstant(val: ConstantValue, writer: Writer) !void {
switch (val) {
.int => |v| try writer.print("{d}", .{v}),
.float => |v| try writer.print("{d:.6}", .{v}),
.boolean => |v| try writer.writeAll(if (v) "true" else "false"),
.string => try writer.writeAll("\"...\""),
.null_val => try writer.writeAll("null"),
.undef => try writer.writeAll("undef"),
.zeroinit => try writer.writeAll("zeroinit"),
.aggregate => try writer.writeAll("{...}"),
}
}
fn isVoidOp(op: Op) bool {
return switch (op) {
.store, .heap_free, .context_store, .context_restore, .global_set, .br, .cond_br, .switch_br, .ret, .ret_void, .@"unreachable" => true,
else => false,
};
}