zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

Ir.zig (22921B) - Raw


      1 const std = @import("std");
      2 const Allocator = std.mem.Allocator;
      3 const assert = std.debug.assert;
      4 const Interner = @import("Interner.zig");
      5 const Object = @import("Object.zig");
      6 
      7 const Ir = @This();
      8 
      9 interner: *Interner,
     10 decls: std.StringArrayHashMapUnmanaged(Decl),
     11 
     12 pub const Decl = struct {
     13     instructions: std.MultiArrayList(Inst),
     14     body: std.ArrayListUnmanaged(Ref),
     15     arena: std.heap.ArenaAllocator.State,
     16 
     17     pub fn deinit(decl: *Decl, gpa: Allocator) void {
     18         decl.instructions.deinit(gpa);
     19         decl.body.deinit(gpa);
     20         decl.arena.promote(gpa).deinit();
     21     }
     22 };
     23 
     24 pub const Builder = struct {
     25     gpa: Allocator,
     26     arena: std.heap.ArenaAllocator,
     27     interner: *Interner,
     28 
     29     decls: std.StringArrayHashMapUnmanaged(Decl) = .empty,
     30     instructions: std.MultiArrayList(Ir.Inst) = .{},
     31     body: std.ArrayListUnmanaged(Ref) = .empty,
     32     alloc_count: u32 = 0,
     33     arg_count: u32 = 0,
     34     current_label: Ref = undefined,
     35 
     36     pub fn deinit(b: *Builder) void {
     37         for (b.decls.values()) |*decl| {
     38             decl.deinit(b.gpa);
     39         }
     40         b.decls.deinit(b.gpa);
     41         b.arena.deinit();
     42         b.instructions.deinit(b.gpa);
     43         b.body.deinit(b.gpa);
     44         b.* = undefined;
     45     }
     46 
     47     pub fn finish(b: *Builder) Ir {
     48         return .{
     49             .interner = b.interner,
     50             .decls = b.decls.move(),
     51         };
     52     }
     53 
     54     pub fn startFn(b: *Builder) Allocator.Error!void {
     55         const entry = try b.makeLabel("entry");
     56         try b.body.append(b.gpa, entry);
     57         b.current_label = entry;
     58     }
     59 
     60     pub fn finishFn(b: *Builder, name: []const u8) !void {
     61         var duped_instructions = try b.instructions.clone(b.gpa);
     62         errdefer duped_instructions.deinit(b.gpa);
     63         var duped_body = try b.body.clone(b.gpa);
     64         errdefer duped_body.deinit(b.gpa);
     65 
     66         try b.decls.put(b.gpa, name, .{
     67             .instructions = duped_instructions,
     68             .body = duped_body,
     69             .arena = b.arena.state,
     70         });
     71         b.instructions.shrinkRetainingCapacity(0);
     72         b.body.shrinkRetainingCapacity(0);
     73         b.arena = std.heap.ArenaAllocator.init(b.gpa);
     74         b.alloc_count = 0;
     75         b.arg_count = 0;
     76     }
     77 
     78     pub fn startBlock(b: *Builder, label: Ref) !void {
     79         try b.body.append(b.gpa, label);
     80         b.current_label = label;
     81     }
     82 
     83     pub fn addArg(b: *Builder, ty: Interner.Ref) Allocator.Error!Ref {
     84         const ref: Ref = @enumFromInt(b.instructions.len);
     85         try b.instructions.append(b.gpa, .{ .tag = .arg, .data = .{ .none = {} }, .ty = ty });
     86         try b.body.insert(b.gpa, b.arg_count, ref);
     87         b.arg_count += 1;
     88         return ref;
     89     }
     90 
     91     pub fn addAlloc(b: *Builder, size: u32, @"align": u32) Allocator.Error!Ref {
     92         const ref: Ref = @enumFromInt(b.instructions.len);
     93         try b.instructions.append(b.gpa, .{
     94             .tag = .alloc,
     95             .data = .{ .alloc = .{ .size = size, .@"align" = @"align" } },
     96             .ty = .ptr,
     97         });
     98         try b.body.insert(b.gpa, b.alloc_count + b.arg_count + 1, ref);
     99         b.alloc_count += 1;
    100         return ref;
    101     }
    102 
    103     pub fn addInst(b: *Builder, tag: Ir.Inst.Tag, data: Ir.Inst.Data, ty: Interner.Ref) Allocator.Error!Ref {
    104         const ref: Ref = @enumFromInt(b.instructions.len);
    105         try b.instructions.append(b.gpa, .{ .tag = tag, .data = data, .ty = ty });
    106         try b.body.append(b.gpa, ref);
    107         return ref;
    108     }
    109 
    110     pub fn makeLabel(b: *Builder, name: [*:0]const u8) Allocator.Error!Ref {
    111         const ref: Ref = @enumFromInt(b.instructions.len);
    112         try b.instructions.append(b.gpa, .{ .tag = .label, .data = .{ .label = name }, .ty = .void });
    113         return ref;
    114     }
    115 
    116     pub fn addJump(b: *Builder, label: Ref) Allocator.Error!void {
    117         _ = try b.addInst(.jmp, .{ .un = label }, .noreturn);
    118     }
    119 
    120     pub fn addBranch(b: *Builder, cond: Ref, true_label: Ref, false_label: Ref) Allocator.Error!void {
    121         const branch = try b.arena.allocator().create(Ir.Inst.Branch);
    122         branch.* = .{
    123             .cond = cond,
    124             .then = true_label,
    125             .@"else" = false_label,
    126         };
    127         _ = try b.addInst(.branch, .{ .branch = branch }, .noreturn);
    128     }
    129 
    130     pub fn addSwitch(b: *Builder, target: Ref, values: []Interner.Ref, labels: []Ref, default: Ref) Allocator.Error!void {
    131         assert(values.len == labels.len);
    132         const a = b.arena.allocator();
    133         const @"switch" = try a.create(Ir.Inst.Switch);
    134         @"switch".* = .{
    135             .target = target,
    136             .cases_len = @intCast(values.len),
    137             .case_vals = (try a.dupe(Interner.Ref, values)).ptr,
    138             .case_labels = (try a.dupe(Ref, labels)).ptr,
    139             .default = default,
    140         };
    141         _ = try b.addInst(.@"switch", .{ .@"switch" = @"switch" }, .noreturn);
    142     }
    143 
    144     pub fn addStore(b: *Builder, ptr: Ref, val: Ref) Allocator.Error!void {
    145         _ = try b.addInst(.store, .{ .bin = .{ .lhs = ptr, .rhs = val } }, .void);
    146     }
    147 
    148     pub fn addConstant(b: *Builder, val: Interner.Ref, ty: Interner.Ref) Allocator.Error!Ref {
    149         const ref: Ref = @enumFromInt(b.instructions.len);
    150         try b.instructions.append(b.gpa, .{
    151             .tag = .constant,
    152             .data = .{ .constant = val },
    153             .ty = ty,
    154         });
    155         return ref;
    156     }
    157 
    158     pub fn addPhi(b: *Builder, inputs: []const Inst.Phi.Input, ty: Interner.Ref) Allocator.Error!Ref {
    159         const a = b.arena.allocator();
    160         const input_refs = try a.alloc(Ref, inputs.len * 2 + 1);
    161         input_refs[0] = @enumFromInt(inputs.len);
    162         @memcpy(input_refs[1..], std.mem.bytesAsSlice(Ref, std.mem.sliceAsBytes(inputs)));
    163 
    164         return b.addInst(.phi, .{ .phi = .{ .ptr = input_refs.ptr } }, ty);
    165     }
    166 
    167     pub fn addSelect(b: *Builder, cond: Ref, then: Ref, @"else": Ref, ty: Interner.Ref) Allocator.Error!Ref {
    168         const branch = try b.arena.allocator().create(Ir.Inst.Branch);
    169         branch.* = .{
    170             .cond = cond,
    171             .then = then,
    172             .@"else" = @"else",
    173         };
    174         return b.addInst(.select, .{ .branch = branch }, ty);
    175     }
    176 };
    177 
    178 pub const Renderer = struct {
    179     gpa: Allocator,
    180     obj: *Object,
    181     ir: *const Ir,
    182     errors: ErrorList = .{},
    183 
    184     pub const ErrorList = std.StringArrayHashMapUnmanaged([]const u8);
    185 
    186     pub const Error = Allocator.Error || error{LowerFail};
    187 
    188     pub fn deinit(r: *Renderer) void {
    189         for (r.errors.values()) |msg| r.gpa.free(msg);
    190         r.errors.deinit(r.gpa);
    191     }
    192 
    193     pub fn render(r: *Renderer) !void {
    194         switch (r.obj.target.cpu.arch) {
    195             .x86, .x86_64 => return @import("Ir/x86/Renderer.zig").render(r),
    196             else => unreachable,
    197         }
    198     }
    199 
    200     pub fn fail(
    201         r: *Renderer,
    202         name: []const u8,
    203         comptime format: []const u8,
    204         args: anytype,
    205     ) Error {
    206         try r.errors.ensureUnusedCapacity(r.gpa, 1);
    207         r.errors.putAssumeCapacity(name, try std.fmt.allocPrint(r.gpa, format, args));
    208         return error.LowerFail;
    209     }
    210 };
    211 
    212 pub fn render(
    213     ir: *const Ir,
    214     gpa: Allocator,
    215     target: std.Target,
    216     errors: ?*Renderer.ErrorList,
    217 ) !*Object {
    218     const obj = try Object.create(gpa, target);
    219     errdefer obj.deinit();
    220 
    221     var renderer: Renderer = .{
    222         .gpa = gpa,
    223         .obj = obj,
    224         .ir = ir,
    225     };
    226     defer {
    227         if (errors) |some| {
    228             some.* = renderer.errors.move();
    229         }
    230         renderer.deinit();
    231     }
    232 
    233     try renderer.render();
    234     return obj;
    235 }
    236 
    237 pub const Ref = enum(u32) { none = std.math.maxInt(u32), _ };
    238 
    239 pub const Inst = struct {
    240     tag: Tag,
    241     data: Data,
    242     ty: Interner.Ref,
    243 
    244     pub const Tag = enum {
    245         // data.constant
    246         // not included in blocks
    247         constant,
    248 
    249         // data.arg
    250         // not included in blocks
    251         arg,
    252         symbol,
    253 
    254         // data.label
    255         label,
    256 
    257         // data.block
    258         label_addr,
    259         jmp,
    260 
    261         // data.switch
    262         @"switch",
    263 
    264         // data.branch
    265         branch,
    266         select,
    267 
    268         // data.un
    269         jmp_val,
    270 
    271         // data.call
    272         call,
    273 
    274         // data.alloc
    275         alloc,
    276 
    277         // data.phi
    278         phi,
    279 
    280         // data.bin
    281         store,
    282         bit_or,
    283         bit_xor,
    284         bit_and,
    285         bit_shl,
    286         bit_shr,
    287         cmp_eq,
    288         cmp_ne,
    289         cmp_lt,
    290         cmp_lte,
    291         cmp_gt,
    292         cmp_gte,
    293         add,
    294         sub,
    295         mul,
    296         div,
    297         mod,
    298 
    299         // data.un
    300         ret,
    301         load,
    302         bit_not,
    303         negate,
    304         trunc,
    305         zext,
    306         sext,
    307     };
    308 
    309     pub const Data = union {
    310         constant: Interner.Ref,
    311         none: void,
    312         bin: struct {
    313             lhs: Ref,
    314             rhs: Ref,
    315         },
    316         un: Ref,
    317         arg: u32,
    318         alloc: struct {
    319             size: u32,
    320             @"align": u32,
    321         },
    322         @"switch": *Switch,
    323         call: *Call,
    324         label: [*:0]const u8,
    325         branch: *Branch,
    326         phi: Phi,
    327     };
    328 
    329     pub const Branch = struct {
    330         cond: Ref,
    331         then: Ref,
    332         @"else": Ref,
    333     };
    334 
    335     pub const Switch = struct {
    336         target: Ref,
    337         cases_len: u32,
    338         default: Ref,
    339         case_vals: [*]Interner.Ref,
    340         case_labels: [*]Ref,
    341     };
    342 
    343     pub const Call = struct {
    344         func: Ref,
    345         args_len: u32,
    346         args_ptr: [*]Ref,
    347 
    348         pub fn args(c: Call) []Ref {
    349             return c.args_ptr[0..c.args_len];
    350         }
    351     };
    352 
    353     pub const Phi = struct {
    354         ptr: [*]Ir.Ref,
    355 
    356         pub const Input = struct {
    357             label: Ir.Ref,
    358             value: Ir.Ref,
    359         };
    360 
    361         pub fn inputs(p: Phi) []Input {
    362             const len = @intFromEnum(p.ptr[0]) * 2;
    363             const slice = (p.ptr + 1)[0..len];
    364             return std.mem.bytesAsSlice(Input, std.mem.sliceAsBytes(slice));
    365         }
    366     };
    367 };
    368 
    369 pub fn deinit(ir: *Ir, gpa: std.mem.Allocator) void {
    370     for (ir.decls.values()) |*decl| {
    371         decl.deinit(gpa);
    372     }
    373     ir.decls.deinit(gpa);
    374     ir.* = undefined;
    375 }
    376 
    377 const TYPE = std.io.tty.Color.bright_magenta;
    378 const INST = std.io.tty.Color.bright_cyan;
    379 const REF = std.io.tty.Color.bright_blue;
    380 const LITERAL = std.io.tty.Color.bright_green;
    381 const ATTRIBUTE = std.io.tty.Color.bright_yellow;
    382 
    383 const RefMap = std.AutoArrayHashMap(Ref, void);
    384 
    385 pub fn dump(ir: *const Ir, gpa: Allocator, config: std.io.tty.Config, w: anytype) !void {
    386     for (ir.decls.keys(), ir.decls.values()) |name, *decl| {
    387         try ir.dumpDecl(decl, gpa, name, config, w);
    388     }
    389 }
    390 
    391 fn dumpDecl(ir: *const Ir, decl: *const Decl, gpa: Allocator, name: []const u8, config: std.io.tty.Config, w: anytype) !void {
    392     const tags = decl.instructions.items(.tag);
    393     const data = decl.instructions.items(.data);
    394 
    395     var ref_map = RefMap.init(gpa);
    396     defer ref_map.deinit();
    397 
    398     var label_map = RefMap.init(gpa);
    399     defer label_map.deinit();
    400 
    401     const ret_inst = decl.body.items[decl.body.items.len - 1];
    402     const ret_operand = data[@intFromEnum(ret_inst)].un;
    403     const ret_ty = decl.instructions.items(.ty)[@intFromEnum(ret_operand)];
    404     try ir.writeType(ret_ty, config, w);
    405     try config.setColor(w, REF);
    406     try w.print(" @{s}", .{name});
    407     try config.setColor(w, .reset);
    408     try w.writeAll("(");
    409 
    410     var arg_count: u32 = 0;
    411     while (true) : (arg_count += 1) {
    412         const ref = decl.body.items[arg_count];
    413         if (tags[@intFromEnum(ref)] != .arg) break;
    414         if (arg_count != 0) try w.writeAll(", ");
    415         try ref_map.put(ref, {});
    416         try ir.writeRef(decl, &ref_map, ref, config, w);
    417         try config.setColor(w, .reset);
    418     }
    419     try w.writeAll(") {\n");
    420     for (decl.body.items[arg_count..]) |ref| {
    421         switch (tags[@intFromEnum(ref)]) {
    422             .label => try label_map.put(ref, {}),
    423             else => {},
    424         }
    425     }
    426 
    427     for (decl.body.items[arg_count..]) |ref| {
    428         const i = @intFromEnum(ref);
    429         const tag = tags[i];
    430         switch (tag) {
    431             .arg, .constant, .symbol => unreachable,
    432             .label => {
    433                 const label_index = label_map.getIndex(ref).?;
    434                 try config.setColor(w, REF);
    435                 try w.print("{s}.{d}:\n", .{ data[i].label, label_index });
    436             },
    437             // .label_val => {
    438             //     const un = data[i].un;
    439             //     try w.print("    %{d} = label.{d}\n", .{ i, @intFromEnum(un) });
    440             // },
    441             .jmp => {
    442                 const un = data[i].un;
    443                 try config.setColor(w, INST);
    444                 try w.writeAll("    jmp ");
    445                 try writeLabel(decl, &label_map, un, config, w);
    446                 try w.writeByte('\n');
    447             },
    448             .branch => {
    449                 const br = data[i].branch;
    450                 try config.setColor(w, INST);
    451                 try w.writeAll("    branch ");
    452                 try ir.writeRef(decl, &ref_map, br.cond, config, w);
    453                 try config.setColor(w, .reset);
    454                 try w.writeAll(", ");
    455                 try writeLabel(decl, &label_map, br.then, config, w);
    456                 try config.setColor(w, .reset);
    457                 try w.writeAll(", ");
    458                 try writeLabel(decl, &label_map, br.@"else", config, w);
    459                 try w.writeByte('\n');
    460             },
    461             .select => {
    462                 const br = data[i].branch;
    463                 try ir.writeNewRef(decl, &ref_map, ref, config, w);
    464                 try w.writeAll("select ");
    465                 try ir.writeRef(decl, &ref_map, br.cond, config, w);
    466                 try config.setColor(w, .reset);
    467                 try w.writeAll(", ");
    468                 try ir.writeRef(decl, &ref_map, br.then, config, w);
    469                 try config.setColor(w, .reset);
    470                 try w.writeAll(", ");
    471                 try ir.writeRef(decl, &ref_map, br.@"else", config, w);
    472                 try w.writeByte('\n');
    473             },
    474             // .jmp_val => {
    475             //     const bin = data[i].bin;
    476             //     try w.print("    %{s} %{d} label.{d}\n", .{ @tagName(tag), @intFromEnum(bin.lhs), @intFromEnum(bin.rhs) });
    477             // },
    478             .@"switch" => {
    479                 const @"switch" = data[i].@"switch";
    480                 try config.setColor(w, INST);
    481                 try w.writeAll("    switch ");
    482                 try ir.writeRef(decl, &ref_map, @"switch".target, config, w);
    483                 try config.setColor(w, .reset);
    484                 try w.writeAll(" {");
    485                 for (@"switch".case_vals[0..@"switch".cases_len], @"switch".case_labels) |val_ref, label_ref| {
    486                     try w.writeAll("\n        ");
    487                     try ir.writeValue(val_ref, config, w);
    488                     try config.setColor(w, .reset);
    489                     try w.writeAll(" => ");
    490                     try writeLabel(decl, &label_map, label_ref, config, w);
    491                     try config.setColor(w, .reset);
    492                 }
    493                 try config.setColor(w, LITERAL);
    494                 try w.writeAll("\n        default ");
    495                 try config.setColor(w, .reset);
    496                 try w.writeAll("=> ");
    497                 try writeLabel(decl, &label_map, @"switch".default, config, w);
    498                 try config.setColor(w, .reset);
    499                 try w.writeAll("\n    }\n");
    500             },
    501             .call => {
    502                 const call = data[i].call;
    503                 try ir.writeNewRef(decl, &ref_map, ref, config, w);
    504                 try w.writeAll("call ");
    505                 try ir.writeRef(decl, &ref_map, call.func, config, w);
    506                 try config.setColor(w, .reset);
    507                 try w.writeAll("(");
    508                 for (call.args(), 0..) |arg, arg_i| {
    509                     if (arg_i != 0) try w.writeAll(", ");
    510                     try ir.writeRef(decl, &ref_map, arg, config, w);
    511                     try config.setColor(w, .reset);
    512                 }
    513                 try w.writeAll(")\n");
    514             },
    515             .alloc => {
    516                 const alloc = data[i].alloc;
    517                 try ir.writeNewRef(decl, &ref_map, ref, config, w);
    518                 try w.writeAll("alloc ");
    519                 try config.setColor(w, ATTRIBUTE);
    520                 try w.writeAll("size ");
    521                 try config.setColor(w, LITERAL);
    522                 try w.print("{d}", .{alloc.size});
    523                 try config.setColor(w, ATTRIBUTE);
    524                 try w.writeAll(" align ");
    525                 try config.setColor(w, LITERAL);
    526                 try w.print("{d}", .{alloc.@"align"});
    527                 try w.writeByte('\n');
    528             },
    529             .phi => {
    530                 try ir.writeNewRef(decl, &ref_map, ref, config, w);
    531                 try w.writeAll("phi");
    532                 try config.setColor(w, .reset);
    533                 try w.writeAll(" {");
    534                 for (data[i].phi.inputs()) |input| {
    535                     try w.writeAll("\n        ");
    536                     try writeLabel(decl, &label_map, input.label, config, w);
    537                     try config.setColor(w, .reset);
    538                     try w.writeAll(" => ");
    539                     try ir.writeRef(decl, &ref_map, input.value, config, w);
    540                     try config.setColor(w, .reset);
    541                 }
    542                 try config.setColor(w, .reset);
    543                 try w.writeAll("\n    }\n");
    544             },
    545             .store => {
    546                 const bin = data[i].bin;
    547                 try config.setColor(w, INST);
    548                 try w.writeAll("    store ");
    549                 try ir.writeRef(decl, &ref_map, bin.lhs, config, w);
    550                 try config.setColor(w, .reset);
    551                 try w.writeAll(", ");
    552                 try ir.writeRef(decl, &ref_map, bin.rhs, config, w);
    553                 try w.writeByte('\n');
    554             },
    555             .ret => {
    556                 try config.setColor(w, INST);
    557                 try w.writeAll("    ret ");
    558                 if (data[i].un != .none) try ir.writeRef(decl, &ref_map, data[i].un, config, w);
    559                 try w.writeByte('\n');
    560             },
    561             .load => {
    562                 try ir.writeNewRef(decl, &ref_map, ref, config, w);
    563                 try w.writeAll("load ");
    564                 try ir.writeRef(decl, &ref_map, data[i].un, config, w);
    565                 try w.writeByte('\n');
    566             },
    567             .bit_or,
    568             .bit_xor,
    569             .bit_and,
    570             .bit_shl,
    571             .bit_shr,
    572             .cmp_eq,
    573             .cmp_ne,
    574             .cmp_lt,
    575             .cmp_lte,
    576             .cmp_gt,
    577             .cmp_gte,
    578             .add,
    579             .sub,
    580             .mul,
    581             .div,
    582             .mod,
    583             => {
    584                 const bin = data[i].bin;
    585                 try ir.writeNewRef(decl, &ref_map, ref, config, w);
    586                 try w.print("{s} ", .{@tagName(tag)});
    587                 try ir.writeRef(decl, &ref_map, bin.lhs, config, w);
    588                 try config.setColor(w, .reset);
    589                 try w.writeAll(", ");
    590                 try ir.writeRef(decl, &ref_map, bin.rhs, config, w);
    591                 try w.writeByte('\n');
    592             },
    593             .bit_not,
    594             .negate,
    595             .trunc,
    596             .zext,
    597             .sext,
    598             => {
    599                 const un = data[i].un;
    600                 try ir.writeNewRef(decl, &ref_map, ref, config, w);
    601                 try w.print("{s} ", .{@tagName(tag)});
    602                 try ir.writeRef(decl, &ref_map, un, config, w);
    603                 try w.writeByte('\n');
    604             },
    605             .label_addr, .jmp_val => {},
    606         }
    607     }
    608     try config.setColor(w, .reset);
    609     try w.writeAll("}\n\n");
    610 }
    611 
    612 fn writeType(ir: Ir, ty_ref: Interner.Ref, config: std.io.tty.Config, w: anytype) !void {
    613     const ty = ir.interner.get(ty_ref);
    614     try config.setColor(w, TYPE);
    615     switch (ty) {
    616         .ptr_ty, .noreturn_ty, .void_ty, .func_ty => try w.writeAll(@tagName(ty)),
    617         .int_ty => |bits| try w.print("i{d}", .{bits}),
    618         .float_ty => |bits| try w.print("f{d}", .{bits}),
    619         .array_ty => |info| {
    620             try w.print("[{d} * ", .{info.len});
    621             try ir.writeType(info.child, .no_color, w);
    622             try w.writeByte(']');
    623         },
    624         .vector_ty => |info| {
    625             try w.print("<{d} * ", .{info.len});
    626             try ir.writeType(info.child, .no_color, w);
    627             try w.writeByte('>');
    628         },
    629         .record_ty => |elems| {
    630             // TODO collect into buffer and only print once
    631             try w.writeAll("{ ");
    632             for (elems, 0..) |elem, i| {
    633                 if (i != 0) try w.writeAll(", ");
    634                 try ir.writeType(elem, config, w);
    635             }
    636             try w.writeAll(" }");
    637         },
    638         else => unreachable, // not a type
    639     }
    640 }
    641 
    642 fn writeValue(ir: Ir, val: Interner.Ref, config: std.io.tty.Config, w: anytype) !void {
    643     try config.setColor(w, LITERAL);
    644     const key = ir.interner.get(val);
    645     switch (key) {
    646         .null => return w.writeAll("nullptr_t"),
    647         .int => |repr| switch (repr) {
    648             inline else => |x| return w.print("{d}", .{x}),
    649         },
    650         .float => |repr| switch (repr) {
    651             inline else => |x| return w.print("{d}", .{@as(f64, @floatCast(x))}),
    652         },
    653         .bytes => |b| return std.zig.stringEscape(b, "", .{}, w),
    654         else => unreachable, // not a value
    655     }
    656 }
    657 
    658 fn writeRef(ir: Ir, decl: *const Decl, ref_map: *RefMap, ref: Ref, config: std.io.tty.Config, w: anytype) !void {
    659     assert(ref != .none);
    660     const index = @intFromEnum(ref);
    661     const ty_ref = decl.instructions.items(.ty)[index];
    662     if (decl.instructions.items(.tag)[index] == .constant) {
    663         try ir.writeType(ty_ref, config, w);
    664         const v_ref = decl.instructions.items(.data)[index].constant;
    665         try w.writeByte(' ');
    666         try ir.writeValue(v_ref, config, w);
    667         return;
    668     } else if (decl.instructions.items(.tag)[index] == .symbol) {
    669         const name = decl.instructions.items(.data)[index].label;
    670         try ir.writeType(ty_ref, config, w);
    671         try config.setColor(w, REF);
    672         try w.print(" @{s}", .{name});
    673         return;
    674     }
    675     try ir.writeType(ty_ref, config, w);
    676     try config.setColor(w, REF);
    677     const ref_index = ref_map.getIndex(ref).?;
    678     try w.print(" %{d}", .{ref_index});
    679 }
    680 
    681 fn writeNewRef(ir: Ir, decl: *const Decl, ref_map: *RefMap, ref: Ref, config: std.io.tty.Config, w: anytype) !void {
    682     try ref_map.put(ref, {});
    683     try w.writeAll("    ");
    684     try ir.writeRef(decl, ref_map, ref, config, w);
    685     try config.setColor(w, .reset);
    686     try w.writeAll(" = ");
    687     try config.setColor(w, INST);
    688 }
    689 
    690 fn writeLabel(decl: *const Decl, label_map: *RefMap, ref: Ref, config: std.io.tty.Config, w: anytype) !void {
    691     assert(ref != .none);
    692     const index = @intFromEnum(ref);
    693     const label = decl.instructions.items(.data)[index].label;
    694     try config.setColor(w, REF);
    695     const label_index = label_map.getIndex(ref).?;
    696     try w.print("{s}.{d}", .{ label, label_index });
    697 }