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}