const std = @import("std"); const Value = @import("value.zig").Value; const Type = @import("type.zig").Type; const Module = @import("Module.zig"); const assert = std.debug.assert; const codegen = @import("codegen.zig"); const ast = std.zig.ast; /// These are in-memory, analyzed instructions. See `zir.Inst` for the representation /// of instructions that correspond to the ZIR text format. /// This struct owns the `Value` and `Type` memory. When the struct is deallocated, /// so are the `Value` and `Type`. The value of a constant must be copied into /// a memory location for the value to survive after a const instruction. pub const Inst = struct { tag: Tag, /// Each bit represents the index of an `Inst` parameter in the `args` field. /// If a bit is set, it marks the end of the lifetime of the corresponding /// instruction parameter. For example, 0b101 means that the first and /// third `Inst` parameters' lifetimes end after this instruction, and will /// not have any more following references. /// The most significant bit being set means that the instruction itself is /// never referenced, in other words its lifetime ends as soon as it finishes. /// If bit 15 (0b1xxx_xxxx_xxxx_xxxx) is set, it means this instruction itself is unreferenced. /// If bit 14 (0bx1xx_xxxx_xxxx_xxxx) is set, it means this is a special case and the /// lifetimes of operands are encoded elsewhere. deaths: DeathsInt = undefined, ty: Type, src: Module.LazySrcLoc, pub const DeathsInt = u16; pub const DeathsBitIndex = std.math.Log2Int(DeathsInt); pub const unreferenced_bit_index = @typeInfo(DeathsInt).Int.bits - 1; pub const deaths_bits = unreferenced_bit_index - 1; pub fn isUnused(self: Inst) bool { return (self.deaths & (1 << unreferenced_bit_index)) != 0; } pub fn operandDies(self: Inst, index: DeathsBitIndex) bool { assert(index < deaths_bits); return @truncate(u1, self.deaths >> index) != 0; } pub fn clearOperandDeath(self: *Inst, index: DeathsBitIndex) void { assert(index < deaths_bits); self.deaths &= ~(@as(DeathsInt, 1) << index); } pub fn specialOperandDeaths(self: Inst) bool { return (self.deaths & (1 << deaths_bits)) != 0; } pub const Tag = enum { add, addwrap, alloc, arg, assembly, bit_and, bitcast, bit_or, block, br, /// Same as `br` except the operand is a list of instructions to be treated as /// a flat block; that is there is only 1 break instruction from the block, and /// it is implied to be after the last instruction, and the last instruction is /// the break operand. /// This instruction exists for late-stage semantic analysis patch ups, to /// replace one br operand with multiple instructions, without moving anything else around. br_block_flat, breakpoint, br_void, call, cmp_lt, cmp_lte, cmp_eq, cmp_gte, cmp_gt, cmp_neq, condbr, constant, dbg_stmt, /// ?T => bool is_null, /// ?T => bool (inverted logic) is_non_null, /// *?T => bool is_null_ptr, /// *?T => bool (inverted logic) is_non_null_ptr, /// E!T => bool is_err, /// *E!T => bool is_err_ptr, /// E => u16 error_to_int, /// u16 => E int_to_error, bool_and, bool_or, /// Read a value from a pointer. load, /// A labeled block of code that loops forever. At the end of the body it is implied /// to repeat; no explicit "repeat" instruction terminates loop bodies. loop, ptrtoint, ref, ret, retvoid, varptr, /// Write a value to a pointer. LHS is pointer, RHS is value. store, sub, subwrap, unreach, mul, mulwrap, div, not, floatcast, intcast, /// ?T => T optional_payload, /// *?T => *T optional_payload_ptr, wrap_optional, /// E!T -> T unwrap_errunion_payload, /// E!T -> E unwrap_errunion_err, /// *(E!T) -> *T unwrap_errunion_payload_ptr, /// *(E!T) -> E unwrap_errunion_err_ptr, /// wrap from T to E!T wrap_errunion_payload, /// wrap from E to E!T wrap_errunion_err, xor, switchbr, /// Given a pointer to a struct and a field index, returns a pointer to the field. struct_field_ptr, pub fn Type(tag: Tag) type { return switch (tag) { .alloc, .retvoid, .unreach, .breakpoint, => NoOp, .ref, .ret, .bitcast, .not, .is_non_null, .is_non_null_ptr, .is_null, .is_null_ptr, .is_err, .is_err_ptr, .int_to_error, .error_to_int, .ptrtoint, .floatcast, .intcast, .load, .optional_payload, .optional_payload_ptr, .wrap_optional, .unwrap_errunion_payload, .unwrap_errunion_err, .unwrap_errunion_payload_ptr, .unwrap_errunion_err_ptr, .wrap_errunion_payload, .wrap_errunion_err, => UnOp, .add, .addwrap, .sub, .subwrap, .mul, .mulwrap, .div, .cmp_lt, .cmp_lte, .cmp_eq, .cmp_gte, .cmp_gt, .cmp_neq, .store, .bool_and, .bool_or, .bit_and, .bit_or, .xor, => BinOp, .arg => Arg, .assembly => Assembly, .block => Block, .br => Br, .br_block_flat => BrBlockFlat, .br_void => BrVoid, .call => Call, .condbr => CondBr, .constant => Constant, .loop => Loop, .varptr => VarPtr, .struct_field_ptr => StructFieldPtr, .switchbr => SwitchBr, .dbg_stmt => DbgStmt, }; } pub fn fromCmpOp(op: std.math.CompareOperator) Tag { return switch (op) { .lt => .cmp_lt, .lte => .cmp_lte, .eq => .cmp_eq, .gte => .cmp_gte, .gt => .cmp_gt, .neq => .cmp_neq, }; } }; /// Prefer `castTag` to this. pub fn cast(base: *Inst, comptime T: type) ?*T { if (@hasField(T, "base_tag")) { return base.castTag(T.base_tag); } inline for (@typeInfo(Tag).Enum.fields) |field| { const tag = @intToEnum(Tag, field.value); if (base.tag == tag) { if (T == tag.Type()) { return @fieldParentPtr(T, "base", base); } return null; } } unreachable; } pub fn castTag(base: *Inst, comptime tag: Tag) ?*tag.Type() { if (base.tag == tag) { return @fieldParentPtr(tag.Type(), "base", base); } return null; } pub fn Args(comptime T: type) type { return std.meta.fieldInfo(T, .args).field_type; } /// Returns `null` if runtime-known. pub fn value(base: *Inst) ?Value { if (base.ty.onePossibleValue()) |opv| return opv; const inst = base.castTag(.constant) orelse return null; return inst.val; } pub fn cmpOperator(base: *Inst) ?std.math.CompareOperator { return switch (base.tag) { .cmp_lt => .lt, .cmp_lte => .lte, .cmp_eq => .eq, .cmp_gte => .gte, .cmp_gt => .gt, .cmp_neq => .neq, else => null, }; } pub fn operandCount(base: *Inst) usize { inline for (@typeInfo(Tag).Enum.fields) |field| { const tag = @intToEnum(Tag, field.value); if (tag == base.tag) { return @fieldParentPtr(tag.Type(), "base", base).operandCount(); } } unreachable; } pub fn getOperand(base: *Inst, index: usize) ?*Inst { inline for (@typeInfo(Tag).Enum.fields) |field| { const tag = @intToEnum(Tag, field.value); if (tag == base.tag) { return @fieldParentPtr(tag.Type(), "base", base).getOperand(index); } } unreachable; } pub fn breakBlock(base: *Inst) ?*Block { return switch (base.tag) { .br => base.castTag(.br).?.block, .br_void => base.castTag(.br_void).?.block, .br_block_flat => base.castTag(.br_block_flat).?.block, else => null, }; } pub const NoOp = struct { base: Inst, pub fn operandCount(self: *const NoOp) usize { return 0; } pub fn getOperand(self: *const NoOp, index: usize) ?*Inst { return null; } }; pub const UnOp = struct { base: Inst, operand: *Inst, pub fn operandCount(self: *const UnOp) usize { return 1; } pub fn getOperand(self: *const UnOp, index: usize) ?*Inst { if (index == 0) return self.operand; return null; } }; pub const BinOp = struct { base: Inst, lhs: *Inst, rhs: *Inst, pub fn operandCount(self: *const BinOp) usize { return 2; } pub fn getOperand(self: *const BinOp, index: usize) ?*Inst { var i = index; if (i < 1) return self.lhs; i -= 1; if (i < 1) return self.rhs; i -= 1; return null; } }; pub const Arg = struct { pub const base_tag = Tag.arg; base: Inst, /// This exists to be emitted into debug info. name: [*:0]const u8, pub fn operandCount(self: *const Arg) usize { return 0; } pub fn getOperand(self: *const Arg, index: usize) ?*Inst { return null; } }; pub const Assembly = struct { pub const base_tag = Tag.assembly; base: Inst, asm_source: []const u8, is_volatile: bool, output: ?*Inst, output_name: ?[]const u8, inputs: []const []const u8, clobbers: []const []const u8, args: []const *Inst, pub fn operandCount(self: *const Assembly) usize { return self.args.len; } pub fn getOperand(self: *const Assembly, index: usize) ?*Inst { if (index < self.args.len) return self.args[index]; return null; } }; pub const Block = struct { pub const base_tag = Tag.block; base: Inst, body: Body, /// This memory is reserved for codegen code to do whatever it needs to here. codegen: codegen.BlockData = .{}, pub fn operandCount(self: *const Block) usize { return 0; } pub fn getOperand(self: *const Block, index: usize) ?*Inst { return null; } }; pub const convertable_br_size = std.math.max(@sizeOf(BrBlockFlat), @sizeOf(Br)); pub const convertable_br_align = std.math.max(@alignOf(BrBlockFlat), @alignOf(Br)); comptime { assert(@byteOffsetOf(BrBlockFlat, "base") == @byteOffsetOf(Br, "base")); } pub const BrBlockFlat = struct { pub const base_tag = Tag.br_block_flat; base: Inst, block: *Block, body: Body, pub fn operandCount(self: *const BrBlockFlat) usize { return 0; } pub fn getOperand(self: *const BrBlockFlat, index: usize) ?*Inst { return null; } }; pub const Br = struct { pub const base_tag = Tag.br; base: Inst, block: *Block, operand: *Inst, pub fn operandCount(self: *const Br) usize { return 1; } pub fn getOperand(self: *const Br, index: usize) ?*Inst { if (index == 0) return self.operand; return null; } }; pub const BrVoid = struct { pub const base_tag = Tag.br_void; base: Inst, block: *Block, pub fn operandCount(self: *const BrVoid) usize { return 0; } pub fn getOperand(self: *const BrVoid, index: usize) ?*Inst { return null; } }; pub const Call = struct { pub const base_tag = Tag.call; base: Inst, func: *Inst, args: []const *Inst, pub fn operandCount(self: *const Call) usize { return self.args.len + 1; } pub fn getOperand(self: *const Call, index: usize) ?*Inst { var i = index; if (i < 1) return self.func; i -= 1; if (i < self.args.len) return self.args[i]; i -= self.args.len; return null; } }; pub const CondBr = struct { pub const base_tag = Tag.condbr; base: Inst, condition: *Inst, then_body: Body, else_body: Body, /// Set of instructions whose lifetimes end at the start of one of the branches. /// The `then` branch is first: `deaths[0..then_death_count]`. /// The `else` branch is next: `(deaths + then_death_count)[0..else_death_count]`. deaths: [*]*Inst = undefined, then_death_count: u32 = 0, else_death_count: u32 = 0, pub fn operandCount(self: *const CondBr) usize { return 1; } pub fn getOperand(self: *const CondBr, index: usize) ?*Inst { var i = index; if (i < 1) return self.condition; i -= 1; return null; } pub fn thenDeaths(self: *const CondBr) []*Inst { return self.deaths[0..self.then_death_count]; } pub fn elseDeaths(self: *const CondBr) []*Inst { return (self.deaths + self.then_death_count)[0..self.else_death_count]; } }; pub const Constant = struct { pub const base_tag = Tag.constant; base: Inst, val: Value, pub fn operandCount(self: *const Constant) usize { return 0; } pub fn getOperand(self: *const Constant, index: usize) ?*Inst { return null; } }; pub const Loop = struct { pub const base_tag = Tag.loop; base: Inst, body: Body, pub fn operandCount(self: *const Loop) usize { return 0; } pub fn getOperand(self: *const Loop, index: usize) ?*Inst { return null; } }; pub const VarPtr = struct { pub const base_tag = Tag.varptr; base: Inst, variable: *Module.Var, pub fn operandCount(self: *const VarPtr) usize { return 0; } pub fn getOperand(self: *const VarPtr, index: usize) ?*Inst { return null; } }; pub const StructFieldPtr = struct { pub const base_tag = Tag.struct_field_ptr; base: Inst, struct_ptr: *Inst, field_index: usize, pub fn operandCount(self: *const StructFieldPtr) usize { return 1; } pub fn getOperand(self: *const StructFieldPtr, index: usize) ?*Inst { var i = index; if (i < 1) return self.struct_ptr; i -= 1; return null; } }; pub const SwitchBr = struct { pub const base_tag = Tag.switchbr; base: Inst, target: *Inst, cases: []Case, /// Set of instructions whose lifetimes end at the start of one of the cases. /// In same order as cases, deaths[0..case_0_count, case_0_count .. case_1_count, ... ]. deaths: [*]*Inst = undefined, else_index: u32 = 0, else_deaths: u32 = 0, else_body: Body, pub const Case = struct { item: Value, body: Body, index: u32 = 0, deaths: u32 = 0, }; pub fn operandCount(self: *const SwitchBr) usize { return 1; } pub fn getOperand(self: *const SwitchBr, index: usize) ?*Inst { var i = index; if (i < 1) return self.target; i -= 1; return null; } pub fn caseDeaths(self: *const SwitchBr, case_index: usize) []*Inst { const case = self.cases[case_index]; return (self.deaths + case.index)[0..case.deaths]; } pub fn elseDeaths(self: *const SwitchBr) []*Inst { return (self.deaths + self.else_index)[0..self.else_deaths]; } }; pub const DbgStmt = struct { pub const base_tag = Tag.dbg_stmt; base: Inst, byte_offset: u32, pub fn operandCount(self: *const DbgStmt) usize { return 0; } pub fn getOperand(self: *const DbgStmt, index: usize) ?*Inst { return null; } }; }; pub const Body = struct { instructions: []*Inst, }; /// For debugging purposes, prints a function representation to stderr. pub fn dumpFn(old_module: Module, module_fn: *Module.Fn) void { const allocator = old_module.gpa; var ctx: DumpTzir = .{ .allocator = allocator, .arena = std.heap.ArenaAllocator.init(allocator), .old_module = &old_module, .module_fn = module_fn, .indent = 2, .inst_table = DumpTzir.InstTable.init(allocator), .partial_inst_table = DumpTzir.InstTable.init(allocator), .const_table = DumpTzir.InstTable.init(allocator), }; defer ctx.inst_table.deinit(); defer ctx.partial_inst_table.deinit(); defer ctx.const_table.deinit(); defer ctx.arena.deinit(); switch (module_fn.state) { .queued => std.debug.print("(queued)", .{}), .inline_only => std.debug.print("(inline_only)", .{}), .in_progress => std.debug.print("(in_progress)", .{}), .sema_failure => std.debug.print("(sema_failure)", .{}), .dependency_failure => std.debug.print("(dependency_failure)", .{}), .success => { const writer = std.io.getStdErr().writer(); ctx.dump(module_fn.body, writer) catch @panic("failed to dump TZIR"); }, } } const DumpTzir = struct { allocator: *std.mem.Allocator, arena: std.heap.ArenaAllocator, old_module: *const Module, module_fn: *Module.Fn, indent: usize, inst_table: InstTable, partial_inst_table: InstTable, const_table: InstTable, next_index: usize = 0, next_partial_index: usize = 0, next_const_index: usize = 0, const InstTable = std.AutoArrayHashMap(*Inst, usize); /// TODO: Improve this code to include a stack of Body and store the instructions /// in there. Now we are putting all the instructions in a function local table, /// however instructions that are in a Body can be thown away when the Body ends. fn dump(dtz: *DumpTzir, body: Body, writer: std.fs.File.Writer) !void { // First pass to pre-populate the table so that we can show even invalid references. // Must iterate the same order we iterate the second time. // We also look for constants and put them in the const_table. try dtz.fetchInstsAndResolveConsts(body); std.debug.print("Module.Function(name={s}):\n", .{dtz.module_fn.owner_decl.name}); for (dtz.const_table.items()) |entry| { const constant = entry.key.castTag(.constant).?; try writer.print(" @{d}: {} = {};\n", .{ entry.value, constant.base.ty, constant.val, }); } return dtz.dumpBody(body, writer); } fn fetchInstsAndResolveConsts(dtz: *DumpTzir, body: Body) error{OutOfMemory}!void { for (body.instructions) |inst| { try dtz.inst_table.put(inst, dtz.next_index); dtz.next_index += 1; switch (inst.tag) { .alloc, .retvoid, .unreach, .breakpoint, .dbg_stmt, .arg, => {}, .ref, .ret, .bitcast, .not, .is_non_null, .is_non_null_ptr, .is_null, .is_null_ptr, .is_err, .is_err_ptr, .error_to_int, .int_to_error, .ptrtoint, .floatcast, .intcast, .load, .optional_payload, .optional_payload_ptr, .wrap_optional, .wrap_errunion_payload, .wrap_errunion_err, .unwrap_errunion_payload, .unwrap_errunion_err, .unwrap_errunion_payload_ptr, .unwrap_errunion_err_ptr, => { const un_op = inst.cast(Inst.UnOp).?; try dtz.findConst(un_op.operand); }, .add, .addwrap, .sub, .subwrap, .mul, .mulwrap, .div, .cmp_lt, .cmp_lte, .cmp_eq, .cmp_gte, .cmp_gt, .cmp_neq, .store, .bool_and, .bool_or, .bit_and, .bit_or, .xor, => { const bin_op = inst.cast(Inst.BinOp).?; try dtz.findConst(bin_op.lhs); try dtz.findConst(bin_op.rhs); }, .br => { const br = inst.castTag(.br).?; try dtz.findConst(&br.block.base); try dtz.findConst(br.operand); }, .br_block_flat => { const br_block_flat = inst.castTag(.br_block_flat).?; try dtz.findConst(&br_block_flat.block.base); try dtz.fetchInstsAndResolveConsts(br_block_flat.body); }, .br_void => { const br_void = inst.castTag(.br_void).?; try dtz.findConst(&br_void.block.base); }, .block => { const block = inst.castTag(.block).?; try dtz.fetchInstsAndResolveConsts(block.body); }, .condbr => { const condbr = inst.castTag(.condbr).?; try dtz.findConst(condbr.condition); try dtz.fetchInstsAndResolveConsts(condbr.then_body); try dtz.fetchInstsAndResolveConsts(condbr.else_body); }, .switchbr => { const switchbr = inst.castTag(.switchbr).?; try dtz.findConst(switchbr.target); try dtz.fetchInstsAndResolveConsts(switchbr.else_body); for (switchbr.cases) |case| { try dtz.fetchInstsAndResolveConsts(case.body); } }, .loop => { const loop = inst.castTag(.loop).?; try dtz.fetchInstsAndResolveConsts(loop.body); }, .call => { const call = inst.castTag(.call).?; try dtz.findConst(call.func); for (call.args) |arg| { try dtz.findConst(arg); } }, .struct_field_ptr => { const struct_field_ptr = inst.castTag(.struct_field_ptr).?; try dtz.findConst(struct_field_ptr.struct_ptr); }, // TODO fill out this debug printing .assembly, .constant, .varptr, => {}, } } } fn dumpBody(dtz: *DumpTzir, body: Body, writer: std.fs.File.Writer) (std.fs.File.WriteError || error{OutOfMemory})!void { for (body.instructions) |inst| { const my_index = dtz.next_partial_index; try dtz.partial_inst_table.put(inst, my_index); dtz.next_partial_index += 1; try writer.writeByteNTimes(' ', dtz.indent); try writer.print("%{d}: {} = {s}(", .{ my_index, inst.ty, @tagName(inst.tag), }); switch (inst.tag) { .alloc, .retvoid, .unreach, .breakpoint, .dbg_stmt, => try writer.writeAll(")\n"), .ref, .ret, .bitcast, .not, .is_non_null, .is_null, .is_non_null_ptr, .is_null_ptr, .is_err, .is_err_ptr, .error_to_int, .int_to_error, .ptrtoint, .floatcast, .intcast, .load, .optional_payload, .optional_payload_ptr, .wrap_optional, .wrap_errunion_err, .wrap_errunion_payload, .unwrap_errunion_err, .unwrap_errunion_payload, .unwrap_errunion_payload_ptr, .unwrap_errunion_err_ptr, => { const un_op = inst.cast(Inst.UnOp).?; const kinky = try dtz.writeInst(writer, un_op.operand); if (kinky != null) { try writer.writeAll(") // Instruction does not dominate all uses!\n"); } else { try writer.writeAll(")\n"); } }, .add, .addwrap, .sub, .subwrap, .mul, .mulwrap, .div, .cmp_lt, .cmp_lte, .cmp_eq, .cmp_gte, .cmp_gt, .cmp_neq, .store, .bool_and, .bool_or, .bit_and, .bit_or, .xor, => { const bin_op = inst.cast(Inst.BinOp).?; const lhs_kinky = try dtz.writeInst(writer, bin_op.lhs); try writer.writeAll(", "); const rhs_kinky = try dtz.writeInst(writer, bin_op.rhs); if (lhs_kinky != null or rhs_kinky != null) { try writer.writeAll(") // Instruction does not dominate all uses!"); if (lhs_kinky) |lhs| { try writer.print(" %{d}", .{lhs}); } if (rhs_kinky) |rhs| { try writer.print(" %{d}", .{rhs}); } try writer.writeAll("\n"); } else { try writer.writeAll(")\n"); } }, .arg => { const arg = inst.castTag(.arg).?; try writer.print("{s})\n", .{arg.name}); }, .br => { const br = inst.castTag(.br).?; const lhs_kinky = try dtz.writeInst(writer, &br.block.base); try writer.writeAll(", "); const rhs_kinky = try dtz.writeInst(writer, br.operand); if (lhs_kinky != null or rhs_kinky != null) { try writer.writeAll(") // Instruction does not dominate all uses!"); if (lhs_kinky) |lhs| { try writer.print(" %{d}", .{lhs}); } if (rhs_kinky) |rhs| { try writer.print(" %{d}", .{rhs}); } try writer.writeAll("\n"); } else { try writer.writeAll(")\n"); } }, .br_block_flat => { const br_block_flat = inst.castTag(.br_block_flat).?; const block_kinky = try dtz.writeInst(writer, &br_block_flat.block.base); if (block_kinky != null) { try writer.writeAll(", { // Instruction does not dominate all uses!\n"); } else { try writer.writeAll(", {\n"); } const old_indent = dtz.indent; dtz.indent += 2; try dtz.dumpBody(br_block_flat.body, writer); dtz.indent = old_indent; try writer.writeByteNTimes(' ', dtz.indent); try writer.writeAll("})\n"); }, .br_void => { const br_void = inst.castTag(.br_void).?; const kinky = try dtz.writeInst(writer, &br_void.block.base); if (kinky) |_| { try writer.writeAll(") // Instruction does not dominate all uses!\n"); } else { try writer.writeAll(")\n"); } }, .block => { const block = inst.castTag(.block).?; try writer.writeAll("{\n"); const old_indent = dtz.indent; dtz.indent += 2; try dtz.dumpBody(block.body, writer); dtz.indent = old_indent; try writer.writeByteNTimes(' ', dtz.indent); try writer.writeAll("})\n"); }, .condbr => { const condbr = inst.castTag(.condbr).?; const condition_kinky = try dtz.writeInst(writer, condbr.condition); if (condition_kinky != null) { try writer.writeAll(", { // Instruction does not dominate all uses!\n"); } else { try writer.writeAll(", {\n"); } const old_indent = dtz.indent; dtz.indent += 2; try dtz.dumpBody(condbr.then_body, writer); try writer.writeByteNTimes(' ', old_indent); try writer.writeAll("}, {\n"); try dtz.dumpBody(condbr.else_body, writer); dtz.indent = old_indent; try writer.writeByteNTimes(' ', old_indent); try writer.writeAll("})\n"); }, .switchbr => { const switchbr = inst.castTag(.switchbr).?; const condition_kinky = try dtz.writeInst(writer, switchbr.target); if (condition_kinky != null) { try writer.writeAll(", { // Instruction does not dominate all uses!\n"); } else { try writer.writeAll(", {\n"); } const old_indent = dtz.indent; if (switchbr.else_body.instructions.len != 0) { dtz.indent += 2; try dtz.dumpBody(switchbr.else_body, writer); try writer.writeByteNTimes(' ', old_indent); try writer.writeAll("}, {\n"); dtz.indent = old_indent; } for (switchbr.cases) |case| { dtz.indent += 2; try dtz.dumpBody(case.body, writer); try writer.writeByteNTimes(' ', old_indent); try writer.writeAll("}, {\n"); dtz.indent = old_indent; } try writer.writeByteNTimes(' ', old_indent); try writer.writeAll("})\n"); }, .loop => { const loop = inst.castTag(.loop).?; try writer.writeAll("{\n"); const old_indent = dtz.indent; dtz.indent += 2; try dtz.dumpBody(loop.body, writer); dtz.indent = old_indent; try writer.writeByteNTimes(' ', dtz.indent); try writer.writeAll("})\n"); }, .call => { const call = inst.castTag(.call).?; const args_kinky = try dtz.allocator.alloc(?usize, call.args.len); defer dtz.allocator.free(args_kinky); std.mem.set(?usize, args_kinky, null); var any_kinky_args = false; const func_kinky = try dtz.writeInst(writer, call.func); for (call.args) |arg, i| { try writer.writeAll(", "); args_kinky[i] = try dtz.writeInst(writer, arg); any_kinky_args = any_kinky_args or args_kinky[i] != null; } if (func_kinky != null or any_kinky_args) { try writer.writeAll(") // Instruction does not dominate all uses!"); if (func_kinky) |func_index| { try writer.print(" %{d}", .{func_index}); } for (args_kinky) |arg_kinky| { if (arg_kinky) |arg_index| { try writer.print(" %{d}", .{arg_index}); } } try writer.writeAll("\n"); } else { try writer.writeAll(")\n"); } }, .struct_field_ptr => { const struct_field_ptr = inst.castTag(.struct_field_ptr).?; const kinky = try dtz.writeInst(writer, struct_field_ptr.struct_ptr); if (kinky != null) { try writer.print("{d}) // Instruction does not dominate all uses!\n", .{ struct_field_ptr.field_index, }); } else { try writer.print("{d})\n", .{struct_field_ptr.field_index}); } }, // TODO fill out this debug printing .assembly, .constant, .varptr, => { try writer.writeAll("!TODO!)\n"); }, } } } fn writeInst(dtz: *DumpTzir, writer: std.fs.File.Writer, inst: *Inst) !?usize { if (dtz.partial_inst_table.get(inst)) |operand_index| { try writer.print("%{d}", .{operand_index}); return null; } else if (dtz.const_table.get(inst)) |operand_index| { try writer.print("@{d}", .{operand_index}); return null; } else if (dtz.inst_table.get(inst)) |operand_index| { try writer.print("%{d}", .{operand_index}); return operand_index; } else { try writer.writeAll("!BADREF!"); return null; } } fn findConst(dtz: *DumpTzir, operand: *Inst) !void { if (operand.tag == .constant) { try dtz.const_table.put(operand, dtz.next_const_index); dtz.next_const_index += 1; } } };