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 }