mirror of
https://github.com/ziglang/zig.git
synced 2025-02-10 22:50:18 +00:00
602 lines
20 KiB
Zig
Vendored
602 lines
20 KiB
Zig
Vendored
const std = @import("std");
|
|
const assert = std.debug.assert;
|
|
const Allocator = std.mem.Allocator;
|
|
const Compilation = @import("Compilation.zig");
|
|
const Interner = @import("Interner.zig");
|
|
const StringId = @import("StringInterner.zig").StringId;
|
|
const Value = @import("Value.zig");
|
|
|
|
const Ir = @This();
|
|
|
|
pool: Interner,
|
|
strings: []const u8,
|
|
// decls: std.StringArrayHashMapUnmanaged(Decl),
|
|
|
|
// pub const Decl = struct {
|
|
instructions: std.MultiArrayList(Inst),
|
|
body: std.ArrayListUnmanaged(Ref),
|
|
arena: std.heap.ArenaAllocator.State,
|
|
// };
|
|
|
|
pub const Builder = struct {
|
|
gpa: Allocator,
|
|
arena: std.heap.ArenaAllocator,
|
|
instructions: std.MultiArrayList(Ir.Inst) = .{},
|
|
body: std.ArrayListUnmanaged(Ref) = .{},
|
|
alloc_count: u32 = 0,
|
|
arg_count: u32 = 0,
|
|
pool: Interner = .{},
|
|
current_label: Ref = undefined,
|
|
|
|
pub fn deinit(b: *Builder) void {
|
|
b.arena.deinit();
|
|
b.instructions.deinit(b.gpa);
|
|
b.body.deinit(b.gpa);
|
|
b.pool.deinit(b.gpa);
|
|
b.* = undefined;
|
|
}
|
|
|
|
pub fn startFn(b: *Builder) Allocator.Error!void {
|
|
b.alloc_count = 0;
|
|
b.arg_count = 0;
|
|
b.instructions.len = 0;
|
|
b.body.items.len = 0;
|
|
const entry = try b.makeLabel("entry");
|
|
try b.body.append(b.gpa, entry);
|
|
b.current_label = entry;
|
|
}
|
|
|
|
pub fn startBlock(b: *Builder, label: Ref) !void {
|
|
try b.body.append(b.gpa, label);
|
|
b.current_label = label;
|
|
}
|
|
|
|
pub fn addArg(b: *Builder, ty: Interner.Ref) Allocator.Error!Ref {
|
|
const ref: Ref = @enumFromInt(b.instructions.len);
|
|
try b.instructions.append(b.gpa, .{ .tag = .arg, .data = .{ .none = {} }, .ty = ty });
|
|
try b.body.insert(b.gpa, b.arg_count, ref);
|
|
b.arg_count += 1;
|
|
return ref;
|
|
}
|
|
|
|
pub fn addAlloc(b: *Builder, size: u32, @"align": u32) Allocator.Error!Ref {
|
|
const ref: Ref = @enumFromInt(b.instructions.len);
|
|
try b.instructions.append(b.gpa, .{
|
|
.tag = .alloc,
|
|
.data = .{ .alloc = .{ .size = size, .@"align" = @"align" } },
|
|
.ty = .ptr,
|
|
});
|
|
try b.body.insert(b.gpa, b.alloc_count + b.arg_count + 1, ref);
|
|
b.alloc_count += 1;
|
|
return ref;
|
|
}
|
|
|
|
pub fn addInst(b: *Builder, tag: Ir.Inst.Tag, data: Ir.Inst.Data, ty: Interner.Ref) Allocator.Error!Ref {
|
|
const ref: Ref = @enumFromInt(b.instructions.len);
|
|
try b.instructions.append(b.gpa, .{ .tag = tag, .data = data, .ty = ty });
|
|
try b.body.append(b.gpa, ref);
|
|
return ref;
|
|
}
|
|
|
|
pub fn makeLabel(b: *Builder, name: [*:0]const u8) Allocator.Error!Ref {
|
|
const ref: Ref = @enumFromInt(b.instructions.len);
|
|
try b.instructions.append(b.gpa, .{ .tag = .label, .data = .{ .label = name }, .ty = .void });
|
|
return ref;
|
|
}
|
|
|
|
pub fn addJump(b: *Builder, label: Ref) Allocator.Error!void {
|
|
_ = try b.addInst(.jmp, .{ .un = label }, .noreturn);
|
|
}
|
|
|
|
pub fn addBranch(b: *Builder, cond: Ref, true_label: Ref, false_label: Ref) Allocator.Error!void {
|
|
const branch = try b.arena.allocator().create(Ir.Inst.Branch);
|
|
branch.* = .{
|
|
.cond = cond,
|
|
.then = true_label,
|
|
.@"else" = false_label,
|
|
};
|
|
_ = try b.addInst(.branch, .{ .branch = branch }, .noreturn);
|
|
}
|
|
|
|
pub fn addSwitch(b: *Builder, target: Ref, values: []Interner.Ref, labels: []Ref, default: Ref) Allocator.Error!void {
|
|
assert(values.len == labels.len);
|
|
const a = b.arena.allocator();
|
|
const @"switch" = try a.create(Ir.Inst.Switch);
|
|
@"switch".* = .{
|
|
.target = target,
|
|
.cases_len = @intCast(values.len),
|
|
.case_vals = (try a.dupe(Interner.Ref, values)).ptr,
|
|
.case_labels = (try a.dupe(Ref, labels)).ptr,
|
|
.default = default,
|
|
};
|
|
_ = try b.addInst(.@"switch", .{ .@"switch" = @"switch" }, .noreturn);
|
|
}
|
|
|
|
pub fn addStore(b: *Builder, ptr: Ref, val: Ref) Allocator.Error!void {
|
|
_ = try b.addInst(.store, .{ .bin = .{ .lhs = ptr, .rhs = val } }, .void);
|
|
}
|
|
|
|
pub fn addConstant(b: *Builder, val: Value, ty: Interner.Ref) Allocator.Error!Ref {
|
|
const ref: Ref = @enumFromInt(b.instructions.len);
|
|
const key: Interner.Key = .{
|
|
.value = val,
|
|
};
|
|
const val_ref = try b.pool.put(b.gpa, key);
|
|
try b.instructions.append(b.gpa, .{ .tag = .constant, .data = .{
|
|
.constant = val_ref,
|
|
}, .ty = ty });
|
|
return ref;
|
|
}
|
|
|
|
pub fn addPhi(b: *Builder, inputs: []const Inst.Phi.Input, ty: Interner.Ref) Allocator.Error!Ref {
|
|
const a = b.arena.allocator();
|
|
const input_refs = try a.alloc(Ref, inputs.len * 2 + 1);
|
|
input_refs[0] = @enumFromInt(inputs.len);
|
|
std.mem.copy(Ref, input_refs[1..], std.mem.bytesAsSlice(Ref, std.mem.sliceAsBytes(inputs)));
|
|
|
|
return b.addInst(.phi, .{ .phi = .{ .ptr = input_refs.ptr } }, ty);
|
|
}
|
|
|
|
pub fn addSelect(b: *Builder, cond: Ref, then: Ref, @"else": Ref, ty: Interner.Ref) Allocator.Error!Ref {
|
|
const branch = try b.arena.allocator().create(Ir.Inst.Branch);
|
|
branch.* = .{
|
|
.cond = cond,
|
|
.then = then,
|
|
.@"else" = @"else",
|
|
};
|
|
return b.addInst(.select, .{ .branch = branch }, ty);
|
|
}
|
|
};
|
|
|
|
pub const Ref = enum(u32) { none = std.math.maxInt(u32), _ };
|
|
|
|
pub const Inst = struct {
|
|
tag: Tag,
|
|
data: Data,
|
|
ty: Interner.Ref,
|
|
|
|
pub const Tag = enum {
|
|
// data.constant
|
|
// not included in blocks
|
|
constant,
|
|
|
|
// data.arg
|
|
// not included in blocks
|
|
arg,
|
|
symbol,
|
|
|
|
// data.label
|
|
label,
|
|
|
|
// data.block
|
|
label_addr,
|
|
jmp,
|
|
|
|
// data.switch
|
|
@"switch",
|
|
|
|
// data.branch
|
|
branch,
|
|
select,
|
|
|
|
// data.un
|
|
jmp_val,
|
|
|
|
// data.call
|
|
call,
|
|
|
|
// data.alloc
|
|
alloc,
|
|
|
|
// data.phi
|
|
phi,
|
|
|
|
// data.bin
|
|
store,
|
|
bit_or,
|
|
bit_xor,
|
|
bit_and,
|
|
bit_shl,
|
|
bit_shr,
|
|
cmp_eq,
|
|
cmp_ne,
|
|
cmp_lt,
|
|
cmp_lte,
|
|
cmp_gt,
|
|
cmp_gte,
|
|
add,
|
|
sub,
|
|
mul,
|
|
div,
|
|
mod,
|
|
|
|
// data.un
|
|
ret,
|
|
load,
|
|
bit_not,
|
|
negate,
|
|
trunc,
|
|
zext,
|
|
sext,
|
|
};
|
|
|
|
pub const Data = union {
|
|
constant: Interner.Ref,
|
|
none: void,
|
|
bin: struct {
|
|
lhs: Ref,
|
|
rhs: Ref,
|
|
},
|
|
un: Ref,
|
|
arg: u32,
|
|
alloc: struct {
|
|
size: u32,
|
|
@"align": u32,
|
|
},
|
|
@"switch": *Switch,
|
|
call: *Call,
|
|
label: [*:0]const u8,
|
|
branch: *Branch,
|
|
phi: Phi,
|
|
};
|
|
|
|
pub const Branch = struct {
|
|
cond: Ref,
|
|
then: Ref,
|
|
@"else": Ref,
|
|
};
|
|
|
|
pub const Switch = struct {
|
|
target: Ref,
|
|
cases_len: u32,
|
|
default: Ref,
|
|
case_vals: [*]Interner.Ref,
|
|
case_labels: [*]Ref,
|
|
};
|
|
|
|
pub const Call = struct {
|
|
func: Ref,
|
|
args_len: u32,
|
|
args_ptr: [*]Ref,
|
|
|
|
pub fn args(c: Call) []Ref {
|
|
return c.args_ptr[0..c.args_len];
|
|
}
|
|
};
|
|
|
|
pub const Phi = struct {
|
|
ptr: [*]Ir.Ref,
|
|
|
|
pub const Input = struct {
|
|
label: Ir.Ref,
|
|
value: Ir.Ref,
|
|
};
|
|
|
|
pub fn inputs(p: Phi) []Input {
|
|
const len = @intFromEnum(p.ptr[0]) * 2;
|
|
const slice = (p.ptr + 1)[0..len];
|
|
return std.mem.bytesAsSlice(Input, std.mem.sliceAsBytes(slice));
|
|
}
|
|
};
|
|
};
|
|
|
|
pub fn deinit(ir: *Ir, gpa: std.mem.Allocator) void {
|
|
ir.arena.promote(gpa).deinit();
|
|
ir.instructions.deinit(gpa);
|
|
ir.* = undefined;
|
|
}
|
|
|
|
const util = @import("util.zig");
|
|
const TYPE = util.Color.purple;
|
|
const INST = util.Color.cyan;
|
|
const REF = util.Color.blue;
|
|
const LITERAL = util.Color.green;
|
|
const ATTRIBUTE = util.Color.yellow;
|
|
|
|
const RefMap = std.AutoArrayHashMap(Ref, void);
|
|
|
|
pub fn dump(ir: Ir, gpa: Allocator, name: []const u8, color: bool, w: anytype) !void {
|
|
const tags = ir.instructions.items(.tag);
|
|
const data = ir.instructions.items(.data);
|
|
|
|
var ref_map = RefMap.init(gpa);
|
|
defer ref_map.deinit();
|
|
|
|
var label_map = RefMap.init(gpa);
|
|
defer label_map.deinit();
|
|
|
|
const ret_inst = ir.body.items[ir.body.items.len - 1];
|
|
const ret_operand = data[@intFromEnum(ret_inst)].un;
|
|
const ret_ty = ir.instructions.items(.ty)[@intFromEnum(ret_operand)];
|
|
try ir.writeType(ret_ty, color, w);
|
|
if (color) util.setColor(REF, w);
|
|
try w.print(" @{s}", .{name});
|
|
if (color) util.setColor(.reset, w);
|
|
try w.writeAll("(");
|
|
|
|
var arg_count: u32 = 0;
|
|
while (true) : (arg_count += 1) {
|
|
const ref = ir.body.items[arg_count];
|
|
if (tags[@intFromEnum(ref)] != .arg) break;
|
|
if (arg_count != 0) try w.writeAll(", ");
|
|
try ref_map.put(ref, {});
|
|
try ir.writeRef(&ref_map, ref, color, w);
|
|
if (color) util.setColor(.reset, w);
|
|
}
|
|
try w.writeAll(") {\n");
|
|
for (ir.body.items[arg_count..]) |ref| {
|
|
switch (tags[@intFromEnum(ref)]) {
|
|
.label => try label_map.put(ref, {}),
|
|
else => {},
|
|
}
|
|
}
|
|
|
|
for (ir.body.items[arg_count..]) |ref| {
|
|
const i = @intFromEnum(ref);
|
|
const tag = tags[i];
|
|
switch (tag) {
|
|
.arg, .constant, .symbol => unreachable,
|
|
.label => {
|
|
const label_index = label_map.getIndex(ref).?;
|
|
if (color) util.setColor(REF, w);
|
|
try w.print("{s}.{d}:\n", .{ data[i].label, label_index });
|
|
},
|
|
// .label_val => {
|
|
// const un = data[i].un;
|
|
// try w.print(" %{d} = label.{d}\n", .{ i, @intFromEnum(un) });
|
|
// },
|
|
.jmp => {
|
|
const un = data[i].un;
|
|
if (color) util.setColor(INST, w);
|
|
try w.writeAll(" jmp ");
|
|
try ir.writeLabel(&label_map, un, color, w);
|
|
try w.writeByte('\n');
|
|
},
|
|
.branch => {
|
|
const br = data[i].branch;
|
|
if (color) util.setColor(INST, w);
|
|
try w.writeAll(" branch ");
|
|
try ir.writeRef(&ref_map, br.cond, color, w);
|
|
if (color) util.setColor(.reset, w);
|
|
try w.writeAll(", ");
|
|
try ir.writeLabel(&label_map, br.then, color, w);
|
|
if (color) util.setColor(.reset, w);
|
|
try w.writeAll(", ");
|
|
try ir.writeLabel(&label_map, br.@"else", color, w);
|
|
try w.writeByte('\n');
|
|
},
|
|
.select => {
|
|
const br = data[i].branch;
|
|
try ir.writeNewRef(&ref_map, ref, color, w);
|
|
try w.writeAll("select ");
|
|
try ir.writeRef(&ref_map, br.cond, color, w);
|
|
if (color) util.setColor(.reset, w);
|
|
try w.writeAll(", ");
|
|
try ir.writeRef(&ref_map, br.then, color, w);
|
|
if (color) util.setColor(.reset, w);
|
|
try w.writeAll(", ");
|
|
try ir.writeRef(&ref_map, br.@"else", color, w);
|
|
try w.writeByte('\n');
|
|
},
|
|
// .jmp_val => {
|
|
// const bin = data[i].bin;
|
|
// try w.print(" %{s} %{d} label.{d}\n", .{ @tagName(tag), @intFromEnum(bin.lhs), @intFromEnum(bin.rhs) });
|
|
// },
|
|
.@"switch" => {
|
|
const @"switch" = data[i].@"switch";
|
|
if (color) util.setColor(INST, w);
|
|
try w.writeAll(" switch ");
|
|
try ir.writeRef(&ref_map, @"switch".target, color, w);
|
|
if (color) util.setColor(.reset, w);
|
|
try w.writeAll(" {");
|
|
for (@"switch".case_vals[0..@"switch".cases_len], @"switch".case_labels) |val_ref, label_ref| {
|
|
try w.writeAll("\n ");
|
|
try ir.writeValue(val_ref, color, w);
|
|
if (color) util.setColor(.reset, w);
|
|
try w.writeAll(" => ");
|
|
try ir.writeLabel(&label_map, label_ref, color, w);
|
|
if (color) util.setColor(.reset, w);
|
|
}
|
|
if (color) util.setColor(LITERAL, w);
|
|
try w.writeAll("\n default ");
|
|
if (color) util.setColor(.reset, w);
|
|
try w.writeAll("=> ");
|
|
try ir.writeLabel(&label_map, @"switch".default, color, w);
|
|
if (color) util.setColor(.reset, w);
|
|
try w.writeAll("\n }\n");
|
|
},
|
|
.call => {
|
|
const call = data[i].call;
|
|
try ir.writeNewRef(&ref_map, ref, color, w);
|
|
try w.writeAll("call ");
|
|
try ir.writeRef(&ref_map, call.func, color, w);
|
|
if (color) util.setColor(.reset, w);
|
|
try w.writeAll("(");
|
|
for (call.args(), 0..) |arg, arg_i| {
|
|
if (arg_i != 0) try w.writeAll(", ");
|
|
try ir.writeRef(&ref_map, arg, color, w);
|
|
if (color) util.setColor(.reset, w);
|
|
}
|
|
try w.writeAll(")\n");
|
|
},
|
|
.alloc => {
|
|
const alloc = data[i].alloc;
|
|
try ir.writeNewRef(&ref_map, ref, color, w);
|
|
try w.writeAll("alloc ");
|
|
if (color) util.setColor(ATTRIBUTE, w);
|
|
try w.writeAll("size ");
|
|
if (color) util.setColor(LITERAL, w);
|
|
try w.print("{d}", .{alloc.size});
|
|
if (color) util.setColor(ATTRIBUTE, w);
|
|
try w.writeAll(" align ");
|
|
if (color) util.setColor(LITERAL, w);
|
|
try w.print("{d}", .{alloc.@"align"});
|
|
try w.writeByte('\n');
|
|
},
|
|
.phi => {
|
|
try ir.writeNewRef(&ref_map, ref, color, w);
|
|
try w.writeAll("phi");
|
|
if (color) util.setColor(.reset, w);
|
|
try w.writeAll(" {");
|
|
for (data[i].phi.inputs()) |input| {
|
|
try w.writeAll("\n ");
|
|
try ir.writeLabel(&label_map, input.label, color, w);
|
|
if (color) util.setColor(.reset, w);
|
|
try w.writeAll(" => ");
|
|
try ir.writeRef(&ref_map, input.value, color, w);
|
|
if (color) util.setColor(.reset, w);
|
|
}
|
|
if (color) util.setColor(.reset, w);
|
|
try w.writeAll("\n }\n");
|
|
},
|
|
.store => {
|
|
const bin = data[i].bin;
|
|
if (color) util.setColor(INST, w);
|
|
try w.writeAll(" store ");
|
|
try ir.writeRef(&ref_map, bin.lhs, color, w);
|
|
if (color) util.setColor(.reset, w);
|
|
try w.writeAll(", ");
|
|
try ir.writeRef(&ref_map, bin.rhs, color, w);
|
|
try w.writeByte('\n');
|
|
},
|
|
.ret => {
|
|
if (color) util.setColor(INST, w);
|
|
try w.writeAll(" ret ");
|
|
if (data[i].un != .none) try ir.writeRef(&ref_map, data[i].un, color, w);
|
|
try w.writeByte('\n');
|
|
},
|
|
.load => {
|
|
try ir.writeNewRef(&ref_map, ref, color, w);
|
|
try w.writeAll("load ");
|
|
try ir.writeRef(&ref_map, data[i].un, color, w);
|
|
try w.writeByte('\n');
|
|
},
|
|
.bit_or,
|
|
.bit_xor,
|
|
.bit_and,
|
|
.bit_shl,
|
|
.bit_shr,
|
|
.cmp_eq,
|
|
.cmp_ne,
|
|
.cmp_lt,
|
|
.cmp_lte,
|
|
.cmp_gt,
|
|
.cmp_gte,
|
|
.add,
|
|
.sub,
|
|
.mul,
|
|
.div,
|
|
.mod,
|
|
=> {
|
|
const bin = data[i].bin;
|
|
try ir.writeNewRef(&ref_map, ref, color, w);
|
|
try w.print("{s} ", .{@tagName(tag)});
|
|
try ir.writeRef(&ref_map, bin.lhs, color, w);
|
|
if (color) util.setColor(.reset, w);
|
|
try w.writeAll(", ");
|
|
try ir.writeRef(&ref_map, bin.rhs, color, w);
|
|
try w.writeByte('\n');
|
|
},
|
|
.bit_not,
|
|
.negate,
|
|
.trunc,
|
|
.zext,
|
|
.sext,
|
|
=> {
|
|
const un = data[i].un;
|
|
try ir.writeNewRef(&ref_map, ref, color, w);
|
|
try w.print("{s} ", .{@tagName(tag)});
|
|
try ir.writeRef(&ref_map, un, color, w);
|
|
try w.writeByte('\n');
|
|
},
|
|
.label_addr, .jmp_val => {},
|
|
}
|
|
}
|
|
if (color) util.setColor(.reset, w);
|
|
try w.writeAll("}\n\n");
|
|
}
|
|
|
|
fn writeType(ir: Ir, ty_ref: Interner.Ref, color: bool, w: anytype) !void {
|
|
const ty = ir.pool.get(ty_ref);
|
|
if (color) util.setColor(TYPE, w);
|
|
switch (ty) {
|
|
.value => unreachable,
|
|
.ptr, .noreturn, .void, .func => try w.writeAll(@tagName(ty)),
|
|
.int => |bits| try w.print("i{d}", .{bits}),
|
|
.float => |bits| try w.print("f{d}", .{bits}),
|
|
.array => |info| {
|
|
try w.print("[{d} * ", .{info.len});
|
|
try ir.writeType(info.child, false, w);
|
|
try w.writeByte(']');
|
|
},
|
|
.vector => |info| {
|
|
try w.print("<{d} * ", .{info.len});
|
|
try ir.writeType(info.child, false, w);
|
|
try w.writeByte('>');
|
|
},
|
|
.record => |info| {
|
|
// TODO collect into buffer and only print once
|
|
try w.writeAll("{ ");
|
|
for (info.elements, 0..) |elem, i| {
|
|
if (i != 0) try w.writeAll(", ");
|
|
try ir.writeType(elem, color, w);
|
|
}
|
|
try w.writeAll(" }");
|
|
},
|
|
}
|
|
}
|
|
|
|
fn writeValue(ir: Ir, val_ref: Interner.Ref, color: bool, w: anytype) !void {
|
|
const v = ir.pool.get(val_ref).value;
|
|
if (color) util.setColor(LITERAL, w);
|
|
switch (v.tag) {
|
|
.unavailable => try w.writeAll(" unavailable"),
|
|
.int => try w.print("{d}", .{v.data.int}),
|
|
.bytes => try w.print("\"{s}\"", .{v.data.bytes.slice(ir.strings)}),
|
|
// std.fmt does @as instead of @floatCast
|
|
.float => try w.print("{d}", .{@as(f64, @floatCast(v.data.float))}),
|
|
else => try w.print("({s})", .{@tagName(v.tag)}),
|
|
}
|
|
}
|
|
|
|
fn writeRef(ir: Ir, ref_map: *RefMap, ref: Ref, color: bool, w: anytype) !void {
|
|
assert(ref != .none);
|
|
const index = @intFromEnum(ref);
|
|
const ty_ref = ir.instructions.items(.ty)[index];
|
|
if (ir.instructions.items(.tag)[index] == .constant) {
|
|
try ir.writeType(ty_ref, color, w);
|
|
const v_ref = ir.instructions.items(.data)[index].constant;
|
|
try w.writeByte(' ');
|
|
try ir.writeValue(v_ref, color, w);
|
|
return;
|
|
} else if (ir.instructions.items(.tag)[index] == .symbol) {
|
|
const name = ir.instructions.items(.data)[index].label;
|
|
try ir.writeType(ty_ref, color, w);
|
|
if (color) util.setColor(REF, w);
|
|
try w.print(" @{s}", .{name});
|
|
return;
|
|
}
|
|
try ir.writeType(ty_ref, color, w);
|
|
if (color) util.setColor(REF, w);
|
|
const ref_index = ref_map.getIndex(ref).?;
|
|
try w.print(" %{d}", .{ref_index});
|
|
}
|
|
|
|
fn writeNewRef(ir: Ir, ref_map: *RefMap, ref: Ref, color: bool, w: anytype) !void {
|
|
try ref_map.put(ref, {});
|
|
try w.writeAll(" ");
|
|
try ir.writeRef(ref_map, ref, color, w);
|
|
if (color) util.setColor(.reset, w);
|
|
try w.writeAll(" = ");
|
|
if (color) util.setColor(INST, w);
|
|
}
|
|
|
|
fn writeLabel(ir: Ir, label_map: *RefMap, ref: Ref, color: bool, w: anytype) !void {
|
|
assert(ref != .none);
|
|
const index = @intFromEnum(ref);
|
|
const label = ir.instructions.items(.data)[index].label;
|
|
if (color) util.setColor(REF, w);
|
|
const label_index = label_map.getIndex(ref).?;
|
|
try w.print("{s}.{d}", .{ label, label_index });
|
|
}
|