master
  1const std = @import("std");
  2const Allocator = std.mem.Allocator;
  3const assert = std.debug.assert;
  4const Interner = @import("Interner.zig");
  5const Object = @import("Object.zig");
  6
  7const Ir = @This();
  8
  9interner: *Interner,
 10decls: std.StringArrayHashMapUnmanaged(Decl),
 11
 12pub const Decl = struct {
 13    instructions: std.MultiArrayList(Inst),
 14    body: std.ArrayList(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
 24pub 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) = .empty,
 31    body: std.ArrayList(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
178pub 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
212pub 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
237pub const Ref = enum(u32) { none = std.math.maxInt(u32), _ };
238
239pub 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
369pub 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
377const TYPE = std.Io.tty.Color.bright_magenta;
378const INST = std.Io.tty.Color.bright_cyan;
379const REF = std.Io.tty.Color.bright_blue;
380const LITERAL = std.Io.tty.Color.bright_green;
381const ATTRIBUTE = std.Io.tty.Color.bright_yellow;
382
383const RefMap = std.AutoArrayHashMapUnmanaged(Ref, void);
384
385pub fn dump(ir: *const Ir, gpa: Allocator, config: std.Io.tty.Config, w: *std.Io.Writer) !void {
386    for (ir.decls.keys(), ir.decls.values()) |name, *decl| {
387        try ir.dumpDecl(decl, gpa, name, config, w);
388    }
389    try w.flush();
390}
391
392fn dumpDecl(ir: *const Ir, decl: *const Decl, gpa: Allocator, name: []const u8, config: std.Io.tty.Config, w: *std.Io.Writer) !void {
393    const tags = decl.instructions.items(.tag);
394    const data = decl.instructions.items(.data);
395
396    var ref_map: RefMap = .empty;
397    defer ref_map.deinit(gpa);
398
399    var label_map: RefMap = .empty;
400    defer label_map.deinit(gpa);
401
402    const ret_inst = decl.body.items[decl.body.items.len - 1];
403    const ret_operand = data[@intFromEnum(ret_inst)].un;
404    const ret_ty = decl.instructions.items(.ty)[@intFromEnum(ret_operand)];
405    try ir.writeType(ret_ty, config, w);
406    try config.setColor(w, REF);
407    try w.print(" @{s}", .{name});
408    try config.setColor(w, .reset);
409    try w.writeAll("(");
410
411    var arg_count: u32 = 0;
412    while (true) : (arg_count += 1) {
413        const ref = decl.body.items[arg_count];
414        if (tags[@intFromEnum(ref)] != .arg) break;
415        if (arg_count != 0) try w.writeAll(", ");
416        try ref_map.put(gpa, ref, {});
417        try ir.writeRef(decl, &ref_map, ref, config, w);
418        try config.setColor(w, .reset);
419    }
420    try w.writeAll(") {\n");
421    for (decl.body.items[arg_count..]) |ref| {
422        switch (tags[@intFromEnum(ref)]) {
423            .label => try label_map.put(gpa, ref, {}),
424            else => {},
425        }
426    }
427
428    for (decl.body.items[arg_count..]) |ref| {
429        const i = @intFromEnum(ref);
430        const tag = tags[i];
431        switch (tag) {
432            .arg, .constant, .symbol => unreachable,
433            .label => {
434                const label_index = label_map.getIndex(ref).?;
435                try config.setColor(w, REF);
436                try w.print("{s}.{d}:\n", .{ data[i].label, label_index });
437            },
438            // .label_val => {
439            //     const un = data[i].un;
440            //     try w.print("    %{d} = label.{d}\n", .{ i, @intFromEnum(un) });
441            // },
442            .jmp => {
443                const un = data[i].un;
444                try config.setColor(w, INST);
445                try w.writeAll("    jmp ");
446                try writeLabel(decl, &label_map, un, config, w);
447                try w.writeByte('\n');
448            },
449            .branch => {
450                const br = data[i].branch;
451                try config.setColor(w, INST);
452                try w.writeAll("    branch ");
453                try ir.writeRef(decl, &ref_map, br.cond, config, w);
454                try config.setColor(w, .reset);
455                try w.writeAll(", ");
456                try writeLabel(decl, &label_map, br.then, config, w);
457                try config.setColor(w, .reset);
458                try w.writeAll(", ");
459                try writeLabel(decl, &label_map, br.@"else", config, w);
460                try w.writeByte('\n');
461            },
462            .select => {
463                const br = data[i].branch;
464                try ir.writeNewRef(gpa, decl, &ref_map, ref, config, w);
465                try w.writeAll("select ");
466                try ir.writeRef(decl, &ref_map, br.cond, config, w);
467                try config.setColor(w, .reset);
468                try w.writeAll(", ");
469                try ir.writeRef(decl, &ref_map, br.then, config, w);
470                try config.setColor(w, .reset);
471                try w.writeAll(", ");
472                try ir.writeRef(decl, &ref_map, br.@"else", config, w);
473                try w.writeByte('\n');
474            },
475            // .jmp_val => {
476            //     const bin = data[i].bin;
477            //     try w.print("    %{s} %{d} label.{d}\n", .{ @tagName(tag), @intFromEnum(bin.lhs), @intFromEnum(bin.rhs) });
478            // },
479            .@"switch" => {
480                const @"switch" = data[i].@"switch";
481                try config.setColor(w, INST);
482                try w.writeAll("    switch ");
483                try ir.writeRef(decl, &ref_map, @"switch".target, config, w);
484                try config.setColor(w, .reset);
485                try w.writeAll(" {");
486                for (@"switch".case_vals[0..@"switch".cases_len], @"switch".case_labels) |val_ref, label_ref| {
487                    try w.writeAll("\n        ");
488                    try ir.writeValue(val_ref, config, w);
489                    try config.setColor(w, .reset);
490                    try w.writeAll(" => ");
491                    try writeLabel(decl, &label_map, label_ref, config, w);
492                    try config.setColor(w, .reset);
493                }
494                try config.setColor(w, LITERAL);
495                try w.writeAll("\n        default ");
496                try config.setColor(w, .reset);
497                try w.writeAll("=> ");
498                try writeLabel(decl, &label_map, @"switch".default, config, w);
499                try config.setColor(w, .reset);
500                try w.writeAll("\n    }\n");
501            },
502            .call => {
503                const call = data[i].call;
504                try ir.writeNewRef(gpa, decl, &ref_map, ref, config, w);
505                try w.writeAll("call ");
506                try ir.writeRef(decl, &ref_map, call.func, config, w);
507                try config.setColor(w, .reset);
508                try w.writeAll("(");
509                for (call.args(), 0..) |arg, arg_i| {
510                    if (arg_i != 0) try w.writeAll(", ");
511                    try ir.writeRef(decl, &ref_map, arg, config, w);
512                    try config.setColor(w, .reset);
513                }
514                try w.writeAll(")\n");
515            },
516            .alloc => {
517                const alloc = data[i].alloc;
518                try ir.writeNewRef(gpa, decl, &ref_map, ref, config, w);
519                try w.writeAll("alloc ");
520                try config.setColor(w, ATTRIBUTE);
521                try w.writeAll("size ");
522                try config.setColor(w, LITERAL);
523                try w.print("{d}", .{alloc.size});
524                try config.setColor(w, ATTRIBUTE);
525                try w.writeAll(" align ");
526                try config.setColor(w, LITERAL);
527                try w.print("{d}", .{alloc.@"align"});
528                try w.writeByte('\n');
529            },
530            .phi => {
531                try ir.writeNewRef(gpa, decl, &ref_map, ref, config, w);
532                try w.writeAll("phi");
533                try config.setColor(w, .reset);
534                try w.writeAll(" {");
535                for (data[i].phi.inputs()) |input| {
536                    try w.writeAll("\n        ");
537                    try writeLabel(decl, &label_map, input.label, config, w);
538                    try config.setColor(w, .reset);
539                    try w.writeAll(" => ");
540                    try ir.writeRef(decl, &ref_map, input.value, config, w);
541                    try config.setColor(w, .reset);
542                }
543                try config.setColor(w, .reset);
544                try w.writeAll("\n    }\n");
545            },
546            .store => {
547                const bin = data[i].bin;
548                try config.setColor(w, INST);
549                try w.writeAll("    store ");
550                try ir.writeRef(decl, &ref_map, bin.lhs, config, w);
551                try config.setColor(w, .reset);
552                try w.writeAll(", ");
553                try ir.writeRef(decl, &ref_map, bin.rhs, config, w);
554                try w.writeByte('\n');
555            },
556            .ret => {
557                try config.setColor(w, INST);
558                try w.writeAll("    ret ");
559                if (data[i].un != .none) try ir.writeRef(decl, &ref_map, data[i].un, config, w);
560                try w.writeByte('\n');
561            },
562            .load => {
563                try ir.writeNewRef(gpa, decl, &ref_map, ref, config, w);
564                try w.writeAll("load ");
565                try ir.writeRef(decl, &ref_map, data[i].un, config, w);
566                try w.writeByte('\n');
567            },
568            .bit_or,
569            .bit_xor,
570            .bit_and,
571            .bit_shl,
572            .bit_shr,
573            .cmp_eq,
574            .cmp_ne,
575            .cmp_lt,
576            .cmp_lte,
577            .cmp_gt,
578            .cmp_gte,
579            .add,
580            .sub,
581            .mul,
582            .div,
583            .mod,
584            => {
585                const bin = data[i].bin;
586                try ir.writeNewRef(gpa, decl, &ref_map, ref, config, w);
587                try w.print("{s} ", .{@tagName(tag)});
588                try ir.writeRef(decl, &ref_map, bin.lhs, config, w);
589                try config.setColor(w, .reset);
590                try w.writeAll(", ");
591                try ir.writeRef(decl, &ref_map, bin.rhs, config, w);
592                try w.writeByte('\n');
593            },
594            .bit_not,
595            .negate,
596            .trunc,
597            .zext,
598            .sext,
599            => {
600                const un = data[i].un;
601                try ir.writeNewRef(gpa, decl, &ref_map, ref, config, w);
602                try w.print("{s} ", .{@tagName(tag)});
603                try ir.writeRef(decl, &ref_map, un, config, w);
604                try w.writeByte('\n');
605            },
606            .label_addr, .jmp_val => {},
607        }
608    }
609    try config.setColor(w, .reset);
610    try w.writeAll("}\n\n");
611}
612
613fn writeType(ir: Ir, ty_ref: Interner.Ref, config: std.Io.tty.Config, w: *std.Io.Writer) !void {
614    const ty = ir.interner.get(ty_ref);
615    try config.setColor(w, TYPE);
616    switch (ty) {
617        .ptr_ty, .noreturn_ty, .void_ty, .func_ty => try w.writeAll(@tagName(ty)),
618        .int_ty => |bits| try w.print("i{d}", .{bits}),
619        .float_ty => |bits| try w.print("f{d}", .{bits}),
620        .array_ty => |info| {
621            try w.print("[{d} * ", .{info.len});
622            try ir.writeType(info.child, .no_color, w);
623            try w.writeByte(']');
624        },
625        .vector_ty => |info| {
626            try w.print("<{d} * ", .{info.len});
627            try ir.writeType(info.child, .no_color, w);
628            try w.writeByte('>');
629        },
630        .record_ty => |elems| {
631            // TODO collect into buffer and only print once
632            try w.writeAll("{ ");
633            for (elems, 0..) |elem, i| {
634                if (i != 0) try w.writeAll(", ");
635                try ir.writeType(elem, config, w);
636            }
637            try w.writeAll(" }");
638        },
639        else => unreachable, // not a type
640    }
641}
642
643fn writeValue(ir: Ir, val: Interner.Ref, config: std.Io.tty.Config, w: *std.Io.Writer) !void {
644    try config.setColor(w, LITERAL);
645    const key = ir.interner.get(val);
646    switch (key) {
647        .null => return w.writeAll("nullptr_t"),
648        .int => |repr| switch (repr) {
649            inline else => |x| return w.print("{d}", .{x}),
650        },
651        .float => |repr| switch (repr) {
652            inline else => |x| return w.print("{d}", .{@as(f64, @floatCast(x))}),
653        },
654        .bytes => |b| return std.zig.stringEscape(b, w),
655        else => unreachable, // not a value
656    }
657}
658
659fn writeRef(ir: Ir, decl: *const Decl, ref_map: *RefMap, ref: Ref, config: std.Io.tty.Config, w: *std.Io.Writer) !void {
660    assert(ref != .none);
661    const index = @intFromEnum(ref);
662    const ty_ref = decl.instructions.items(.ty)[index];
663    if (decl.instructions.items(.tag)[index] == .constant) {
664        try ir.writeType(ty_ref, config, w);
665        const v_ref = decl.instructions.items(.data)[index].constant;
666        try w.writeByte(' ');
667        try ir.writeValue(v_ref, config, w);
668        return;
669    } else if (decl.instructions.items(.tag)[index] == .symbol) {
670        const name = decl.instructions.items(.data)[index].label;
671        try ir.writeType(ty_ref, config, w);
672        try config.setColor(w, REF);
673        try w.print(" @{s}", .{name});
674        return;
675    }
676    try ir.writeType(ty_ref, config, w);
677    try config.setColor(w, REF);
678    const ref_index = ref_map.getIndex(ref).?;
679    try w.print(" %{d}", .{ref_index});
680}
681
682fn writeNewRef(ir: Ir, gpa: Allocator, decl: *const Decl, ref_map: *RefMap, ref: Ref, config: std.Io.tty.Config, w: *std.Io.Writer) !void {
683    try ref_map.put(gpa, ref, {});
684    try w.writeAll("    ");
685    try ir.writeRef(decl, ref_map, ref, config, w);
686    try config.setColor(w, .reset);
687    try w.writeAll(" = ");
688    try config.setColor(w, INST);
689}
690
691fn writeLabel(decl: *const Decl, label_map: *RefMap, ref: Ref, config: std.Io.tty.Config, w: *std.Io.Writer) !void {
692    assert(ref != .none);
693    const index = @intFromEnum(ref);
694    const label = decl.instructions.items(.data)[index].label;
695    try config.setColor(w, REF);
696    const label_index = label_map.getIndex(ref).?;
697    try w.print("{s}.{d}", .{ label, label_index });
698}