authorgravatar for xtex@astrafall.orgxtex <xtex@astrafall.org> 2026-08-07 15:12:14+08:00
committergravatar for xtex@astrafall.orgxtex <xtex@astrafall.org> 2026-08-09 11:10:26+08:00
log726f539fe5975afee1ad857bff0c59032f146516
tree25f4b59d88a2be7783e31508fa131b849983410c
parent054f867dc0fa3867f2ff951ca26afd92202c0718
signaturebadge-check Signed by SSH key SHA256:IEYEjkZlkUTr5U9GiDAmZU/4eZus2t2RsxusyhQqwao

loongarch: scaffold a self-hosted LoongArch backend

Signed-off-by: xtex <xtex@astrafall.org>

11 files changed, 8705 insertions(+), 2 deletions(-)

CMakeLists.txt+9
......@@ -359,6 +359,15 @@ set(ZIG_STAGE2_SOURCES
359359 src/codegen/llvm.zig
360360 src/codegen/llvm/bindings.zig
361361 src/codegen/loongarch/abi.zig
362 src/codegen/loongarch/encoding.zig
363 src/codegen/loongarch/decode_tree.zon
364 src/codegen/loongarch/inst_formats.zon
365 src/codegen/loongarch/Assemble.zig
366 src/codegen/loongarch/Disassemble.zig
367 src/codegen/loongarch/Mir.zig
368 src/codegen/loongarch/bits.zig
369 src/codegen/loongarch/Select.zig
370 src/codegen/loongarch.zig
362371 src/codegen/s390x/abi.zig
363372 src/crash_report.zig
364373 src/dev.zig
lib/std/lang.zig+4
......@@ -1313,6 +1313,9 @@ pub const CompilerBackend = enum(u64) {
13131313 /// The reference implementation self-hosted compiler of Zig, using the
13141314 /// powerpc backend.
13151315 stage2_powerpc = 12,
1316 /// The reference implementation self-hosted compiler of Zig, using the
1317 /// loongarch backend.
1318 stage2_loongarch = 13,
13161319
13171320 _,
13181321};
......@@ -1340,6 +1343,7 @@ pub const panic: type = p: {
13401343 break :p root.panic;
13411344 }
13421345 break :p switch (builtin.zig_backend) {
1346 .stage2_loongarch,
13431347 .stage2_powerpc,
13441348 .stage2_riscv64,
13451349 => std.debug.simple_panic,
src/Zcu.zig+4
......@@ -4725,6 +4725,10 @@ pub fn callconvSupported(zcu: *Zcu, cc: std.lang.CallingConvention) union(enum)
47254725 .spirv_task, .spirv_mesh => target.os.tag == .vulkan,
47264726 else => false,
47274727 },
4728 .stage2_loongarch => switch (cc) {
4729 .loongarch64_lp64, .loongarch32_ilp32, .naked => true,
4730 else => false,
4731 },
47284732 };
47294733 if (!backend_ok) return .{ .bad_backend = backend };
47304734 return .ok;
src/codegen.zig+11-1
......@@ -23,6 +23,7 @@ const Alignment = InternPool.Alignment;
2323const dev = @import("dev.zig");
2424
2525pub const aarch64 = @import("codegen/aarch64.zig");
26pub const loongarch = @import("codegen/loongarch.zig");
2627
2728pub const Error = link.Error;
2829
......@@ -33,6 +34,7 @@ fn devFeatureForBackend(backend: std.lang.CompilerBackend) dev.Feature {
3334 .stage2_arm => .arm_backend,
3435 .stage2_c => .c_backend,
3536 .stage2_llvm => .llvm_backend,
37 .stage2_loongarch => .loongarch_backend,
3638 .stage2_powerpc => unreachable,
3739 .stage2_riscv64 => .riscv64_backend,
3840 .stage2_sparc64 => .sparc64_backend,
......@@ -51,6 +53,7 @@ fn importBackend(comptime backend: std.lang.CompilerBackend) type {
5153 .stage2_arm => unreachable,
5254 .stage2_c => @import("codegen/c.zig"),
5355 .stage2_llvm => @import("codegen/llvm.zig"),
56 .stage2_loongarch => loongarch,
5457 .stage2_powerpc => unreachable,
5558 .stage2_riscv64 => @import("codegen/riscv64/CodeGen.zig"),
5659 .stage2_sparc64 => @import("codegen/sparc64/CodeGen.zig"),
......@@ -71,6 +74,7 @@ pub fn legalizeFeatures(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) ?*co
7174 .stage2_wasm,
7275 .stage2_x86_64,
7376 .stage2_aarch64,
77 .stage2_loongarch,
7478 .stage2_x86,
7579 .stage2_riscv64,
7680 .stage2_sparc64,
......@@ -87,7 +91,7 @@ pub fn wantsLiveness(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) bool {
8791 const target = &zcu.navFileScope(nav_index).mod.?.resolved_target.result;
8892 return switch (target_util.zigBackend(target, zcu.comp.config.use_llvm)) {
8993 else => true,
90 .stage2_aarch64 => false,
94 .stage2_aarch64, .stage2_loongarch => false,
9195 };
9296}
9397
......@@ -96,6 +100,7 @@ pub fn wantsLiveness(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) bool {
96100/// union of all MIR types. The active tag is known from the backend in use; see `AnyMir.tag`.
97101pub const AnyMir = union {
98102 aarch64: if (dev.env.supports(.aarch64_backend)) @import("codegen/aarch64/Mir.zig") else noreturn,
103 loongarch: if (dev.env.supports(.loongarch_backend)) @import("codegen/loongarch/Mir.zig") else noreturn,
99104 riscv64: if (dev.env.supports(.riscv64_backend)) @import("codegen/riscv64/Mir.zig") else noreturn,
100105 sparc64: if (dev.env.supports(.sparc64_backend)) @import("codegen/sparc64/Mir.zig") else noreturn,
101106 x86_64: if (dev.env.supports(.x86_64_backend)) @import("codegen/x86_64/Mir.zig") else noreturn,
......@@ -106,6 +111,7 @@ pub const AnyMir = union {
106111 pub inline fn tag(comptime backend: std.lang.CompilerBackend) []const u8 {
107112 return switch (backend) {
108113 .stage2_aarch64 => "aarch64",
114 .stage2_loongarch => "loongarch",
109115 .stage2_riscv64 => "riscv64",
110116 .stage2_sparc64 => "sparc64",
111117 .stage2_x86_64 => "x86_64",
......@@ -122,6 +128,7 @@ pub const AnyMir = union {
122128 switch (backend) {
123129 else => unreachable,
124130 inline .stage2_aarch64,
131 .stage2_loongarch,
125132 .stage2_riscv64,
126133 .stage2_sparc64,
127134 .stage2_x86_64,
......@@ -151,6 +158,7 @@ pub fn generateFunction(
151158 switch (target_util.zigBackend(target, false)) {
152159 else => unreachable,
153160 inline .stage2_aarch64,
161 .stage2_loongarch,
154162 .stage2_riscv64,
155163 .stage2_sparc64,
156164 .stage2_x86_64,
......@@ -194,6 +202,7 @@ pub fn emitFunction(
194202 switch (target_util.zigBackend(target, zcu.comp.config.use_llvm)) {
195203 else => unreachable,
196204 inline .stage2_aarch64,
205 .stage2_loongarch,
197206 .stage2_riscv64,
198207 .stage2_sparc64,
199208 .stage2_x86_64,
......@@ -1292,4 +1301,5 @@ pub fn flattenType(items_buf: []FlattenedItem, ty: Type, zcu: *Zcu, opts: struct
12921301
12931302test {
12941303 _ = aarch64;
1304 _ = loongarch;
12951305}
src/codegen/loongarch.zig created+163
......@@ -0,0 +1,163 @@
1pub const Mir = @import("loongarch/Mir.zig");
2const Select = @import("loongarch/Select.zig");
3const bits = @import("loongarch/bits.zig");
4pub const Disassemble = @import("loongarch/Disassemble.zig");
5pub const encoding = @import("loongarch/encoding.zig");
6
7test {
8 _ = bits;
9 _ = Disassemble;
10}
11
12pub fn legalizeFeatures(_: *const std.Target) ?*const Air.Legalize.Features {
13 return comptime &.initMany(&.{
14 .expand_bit_cast_safe,
15 .expand_int_cast_safe,
16 .expand_int_from_float_safe,
17 .expand_int_from_float_optimized_safe,
18 .expand_add_safe,
19 .expand_sub_safe,
20 .expand_mul_safe,
21 .expand_packed_load,
22 .expand_packed_store,
23 .expand_packed_agg_field_val,
24 .expand_packed_aggregate_init,
25 .soft_f16,
26 .soft_f32,
27 .soft_f64,
28 .soft_f80,
29 });
30}
31
32pub fn generate(
33 _: *link.File,
34 pt: Zcu.PerThread,
35 func_index: InternPool.Index,
36 air: *const Air,
37 liveness: *const ?Air.Liveness,
38) !Mir {
39 const zcu = pt.zcu;
40 const gpa = zcu.gpa;
41 const ip = &zcu.intern_pool;
42 const func = zcu.funcInfo(func_index);
43 const func_zir = func.zir_body_inst.resolveFull(ip).?;
44 const file = zcu.fileByIndex(func_zir.file);
45 const named_params_len = file.zir.?.getParamBody(func_zir.inst).len;
46 const func_type = ip.indexToKey(func.ty).func_type;
47 assert(liveness.* == null);
48
49 // Initialize ISel
50 const mod = zcu.navFileScope(func.owner_nav).mod.?;
51 var isel: Select = .{
52 .pt = pt,
53 .target = &mod.resolved_target.result,
54 .opt_mode = mod.optimize_mode,
55 .air = air.*,
56 .nav_index = zcu.funcInfo(func_index).owner_nav,
57 };
58 defer isel.deinit();
59 assert(try isel.active_blocks.fetchPut(gpa, Select.Block.main, .{ .target_label = 0 }) == null);
60 defer isel.active_blocks.entries.items(.value)[0].deinit(&isel);
61
62 const air_main_body = air.getMainBody();
63
64 // Calculate parameter & return hints and layouts
65 var cc_it1: Select.CallAbiIterator = .{
66 .cc = &func_type.cc,
67 .isel = &isel,
68 .stack_pointer = .fp,
69 };
70 var cc_it2 = cc_it1;
71
72 switch (func_type.cc) {
73 // naked functions cannot have any arguments
74 // Otherwise, SP will always be moved to allocate space for the saved FP and _start will be broken.
75 .naked => {},
76 // FP is required to load byval arguments passed on stack now
77 // TODO: use FP only when necessary
78 else => isel.saved_registers.insert(.fp),
79 }
80
81 const ret_layout_vi: ?Select.Value.Index = ret: {
82 const ret_vi1 = try cc_it1.resolve(.fromInterned(func_type.return_type), true) orelse break :ret null;
83 const ret_vi2 = try cc_it2.resolve(.fromInterned(func_type.return_type), true) orelse unreachable;
84 ret_vi2.deref(&isel);
85 tracking_log.debug("{f} <- %main", .{ret_vi1});
86 try isel.live_values.putNoClobber(gpa, Select.Block.main, ret_vi1);
87 break :ret ret_vi2;
88 };
89
90 var arg_layouts: std.ArrayList(Select.Value.Index) = .empty;
91 defer arg_layouts.deinit(gpa);
92 for (air_main_body) |air_inst_index| {
93 if (air.instructions.items(.tag)[@backingInt(air_inst_index)] != .arg) break;
94 const arg = air.instructions.items(.data)[@backingInt(air_inst_index)].arg;
95 const param_ty = arg.ty.toType();
96 if (arg.zir_param_index >= named_params_len)
97 assert(func_type.is_var_args);
98 const param_vi1 = try cc_it1.resolve(param_ty, false) orelse unreachable;
99 const param_vi2 = try cc_it2.resolve(param_ty, false) orelse unreachable;
100 tracking_log.debug("{f} <- %{d}", .{ param_vi1, @backingInt(air_inst_index) });
101 try isel.live_values.putNoClobber(gpa, air_inst_index, param_vi1);
102 try arg_layouts.append(gpa, param_vi2);
103 }
104 if (arg_layouts.items.len != 0)
105 isel.arg_layouts = try arg_layouts.toOwnedSlice(gpa);
106
107 // Analyze
108 try isel.analyze(air_main_body);
109 try isel.finishAnalysis();
110 isel.verify(false);
111
112 // Generate body
113 assert(isel.instructions.items.len == 0);
114 try isel.body(air_main_body);
115 if (isel.live_values.fetchRemove(Select.Block.main)) |ret_vi| {
116 defer ret_vi.value.deref(&isel);
117
118 switch (ret_vi.value.parent(&isel)) {
119 .none, .value => {},
120 .address => |ret_addr_vi| {
121 tracking_log.debug("live-in by-ref return address", .{});
122 try ret_addr_vi.defLiveIn(&isel, ret_layout_vi.?.parent(&isel).address, .{});
123 },
124 .constant => unreachable,
125 }
126 }
127
128 // Generate prologue and epilogue
129 const prologue = isel.instructions.items.len;
130 const epilogue = try isel.layout(cc_it1, mod);
131
132 // Verification
133 isel.verify(true);
134 try isel.verifyTargetFeatures();
135
136 // Finalization
137 const instructions = try isel.instructions.toOwnedSlice(gpa);
138 var mir: Mir = .{
139 .prologue = instructions[prologue..epilogue],
140 .body = instructions[0..prologue],
141 .epilogue = instructions[epilogue..],
142 .nav_relocs = &.{},
143 .uav_relocs = &.{},
144 .lazy_relocs = &.{},
145 .global_relocs = &.{},
146 .internal_relocs = &.{},
147 };
148 errdefer mir.deinit(gpa);
149 mir.nav_relocs = try isel.nav_relocs.toOwnedSlice(gpa);
150 mir.uav_relocs = try isel.uav_relocs.toOwnedSlice(gpa);
151 mir.lazy_relocs = try isel.lazy_relocs.toOwnedSlice(gpa);
152 mir.global_relocs = try isel.global_relocs.toOwnedSlice(gpa);
153 mir.internal_relocs = try isel.internal_relocs.toOwnedSlice(gpa);
154 return mir;
155}
156
157const Air = @import("../Air.zig");
158const assert = std.debug.assert;
159const InternPool = @import("../InternPool.zig");
160const link = @import("../link.zig");
161const std = @import("std");
162const tracking_log = std.log.scoped(.tracking);
163const Zcu = @import("../Zcu.zig");
src/codegen/loongarch/Assemble.zig created+255
......@@ -0,0 +1,255 @@
1source: []const u8,
2args: std.StringHashMapUnmanaged(Operand) = .empty,
3
4pub const Operand = union(enum) {
5 register: Register,
6 signed_imm: i64,
7 unsigned_imm: u64,
8};
9
10pub fn deinit(as: *Assemble, gpa: std.mem.Allocator) void {
11 as.args.deinit(gpa);
12}
13
14pub fn nextLine(as: *Assemble) []const u8 {
15 const line_len = std.mem.findScalar(u8, as.source, '\n') orelse {
16 const line = as.source;
17 as.source = "";
18 return line;
19 };
20 const line = as.source[0..line_len];
21 as.source = as.source[line_len + 1 ..];
22 return line;
23}
24
25pub fn parseLine(as: *Assemble, orig_line: []const u8) !?Instruction {
26 var line = orig_line;
27
28 // strip comment
29 if (std.mem.find(u8, line, "//")) |comment_i| line = line[comment_i..];
30
31 var token_it = std.mem.tokenizeAny(u8, line, " \t");
32 if (token_it.next()) |mnemonic_str| {
33 log.debug("- '{s}'", .{line});
34 log.debug(" - mnemonic: {s}", .{mnemonic_str});
35 var op_it: OperandIterator = .init(as, token_it.rest());
36 const instruction = parseInstruction(mnemonic_str, &op_it) orelse return error.InvalidSyntax;
37 if (!op_it.isEnd()) {
38 log.debug("find unrecognized operands", .{});
39 return error.InvalidSyntax;
40 }
41 return instruction;
42 } else return null;
43}
44
45const OperandIterator = struct {
46 as: *Assemble,
47 iter: std.mem.SplitIterator(u8, .scalar),
48
49 fn init(as: *Assemble, ops: []const u8) OperandIterator {
50 log.debug(" - operands: {s}", .{ops});
51 return .{
52 .as = as,
53 .iter = std.mem.splitScalar(u8, ops, ','),
54 };
55 }
56
57 fn isEnd(it: *OperandIterator) bool {
58 return it.iter.peek() == null;
59 }
60
61 fn next(it: *OperandIterator) ?[]const u8 {
62 if (it.iter.next()) |op| {
63 const res = std.mem.trim(u8, op, " \t");
64 log.debug(" - {s}", .{res});
65 return res;
66 }
67 return null;
68 }
69
70 fn tryResolveArg(it: *OperandIterator, tmpl: []const u8) !?*Operand {
71 if (tmpl.len < 2)
72 return null;
73 if (tmpl[0] == '%' and tmpl[1] == '[' and tmpl[tmpl.len - 1] == ']') {
74 const arg_name = tmpl[2..][0 .. tmpl.len - 3];
75 if (it.as.args.getPtr(arg_name)) |arg_op|
76 return arg_op;
77 }
78 return null;
79 }
80
81 fn nextReg(it: *OperandIterator) ?Register {
82 if (it.next()) |name| {
83 return if (try it.tryResolveArg(name)) |arg_op|
84 switch (arg_op.*) {
85 .register => |reg| reg,
86 else => null,
87 }
88 else
89 Register.parse(name);
90 }
91 return null;
92 }
93
94 fn nextImm(it: *OperandIterator, T: type) ?T {
95 if (it.next()) |imm_str| {
96 return if (try it.tryResolveArg(imm_str)) |arg_op|
97 switch (arg_op.*) {
98 inline .signed_imm, .unsigned_imm => |imm| if (std.math.cast(T, imm)) |imm_cast|
99 imm_cast
100 else
101 null,
102 else => null,
103 }
104 else
105 std.fmt.parseInt(T, imm_str, 0) catch null;
106 }
107 return null;
108 }
109};
110
111fn parseInstruction(mnemonic: []const u8, ops: *OperandIterator) ?Instruction {
112 @setEvalBranchQuota(3_000);
113
114 // find override matchers
115 inline for (@typeInfo(matcher_overrides).@"struct".decl_names) |decl| {
116 if (mnemonicEql(decl, mnemonic)) {
117 const matcher = @field(matcher_overrides, decl);
118 return switch (@typeInfo(@TypeOf(matcher))) {
119 .@"fn" => matcher(ops),
120 .enum_literal => defaultMatcher(@field(Mnemonic, decl), ops),
121 .null => return null,
122 else => unreachable,
123 };
124 }
125 }
126
127 // find default matchers
128 inline for (@typeInfo(@TypeOf(inst_formats.instructions)).@"struct".field_names) |decl| {
129 if (@hasDecl(matcher_overrides, decl)) continue;
130 if (mnemonicEql(decl, mnemonic))
131 return defaultMatcher(@field(Mnemonic, decl), ops);
132 }
133
134 log.debug(" unmatched mnemonic", .{});
135 return null;
136}
137
138fn mnemonicEql(mnemonic: []const u8, rhs: []const u8) bool {
139 if (mnemonic.len != rhs.len) return false;
140 for (mnemonic, rhs) |l, r| {
141 assert(!std.ascii.isUpper(l));
142 if (l != std.ascii.toLower(r)) return false;
143 }
144 return true;
145}
146
147fn defaultMatcher(comptime mnemonic: Mnemonic, ops: *OperandIterator) ?Instruction {
148 const inst_info = @field(inst_formats.instructions, @tagName(mnemonic));
149 const format = if (@hasField(@TypeOf(inst_info), "orig_format") and !@hasField(@TypeOf(inst_info), "orig_name"))
150 inst_info.orig_format
151 else
152 inst_info.format;
153 // TODO check features
154 return defaultMatcherFormat(@tagName(format), inst_info.word, ops);
155}
156
157fn defaultMatcherFormat(comptime format: []const u8, word: u32, ops: *OperandIterator) ?Instruction {
158 const format_info = @field(inst_formats.formats, format);
159 const encodeFn = @field(encoding.Instruction, "encode" ++ format);
160 const EncodeArgs = std.meta.ArgsTuple(@TypeOf(encodeFn));
161 var encode_args: EncodeArgs = undefined;
162 encode_args[0] = word;
163 inline for (format_info.slots, 1..) |slot, slot_i| {
164 const Slot = @TypeOf(slot);
165 if (@hasField(Slot, "reg")) {
166 const class = slot.reg.class;
167 const reg = ops.nextReg() orelse return null;
168 if (reg.class() != switch (class) {
169 .int => .int,
170 .fp, .lsx, .lasx => .fp,
171 .fcc => .fcc,
172 .lbt_scratch => .int,
173 else => unreachable,
174 })
175 return null;
176 encode_args[slot_i] = reg;
177 } else if (@hasField(Slot, "imm")) {
178 const signedness = @field(std.builtin.Signedness, @tagName(slot.imm.signedness));
179 const ImmValue = @Int(signedness, slot.imm.length);
180 encode_args[slot_i] = ops.nextImm(ImmValue) orelse return null;
181 } else {
182 @compileLog("Current slot:", slot);
183 @compileError("Invalid operand slot info");
184 }
185 }
186 return @call(.always_inline, encodeFn, encode_args);
187}
188
189const matcher_overrides = struct {
190 const b = null;
191 const bl = null;
192 const beqz = null;
193 const bnez = null;
194 const bceqz = null;
195 const bcnez = null;
196 const bgt = null;
197 const bgtu = null;
198 const ble = null;
199 const bleu = null;
200
201 const @"xxx.unknown.1" = null;
202 const csrrd = null;
203 const csrwr = null;
204 const gcsrrd = null;
205 const gcsrwr = null;
206 const csrxchg = null;
207 const cacop = null;
208 const invtlb = null;
209 const tlbinv = null;
210 const preld = null;
211 const preldx = null;
212 const dbcl = null;
213 const ertn = null;
214 const pcaddi = null;
215 const @"ext.w.b" = null;
216 const @"ext.w.h" = null;
217 const @"ldptr.w" = null;
218 const @"ldptr.d" = null;
219 const @"stptr.w" = null;
220 const @"stptr.d" = null;
221 const @"bitrev.w" = null;
222 const @"bitrev.d" = null;
223 const @"bitrev.4b" = null;
224 const @"bitrev.8b" = null;
225 const @"asrtle.d" = null;
226 const @"asrtgt.d" = null;
227 const @"lu32i.d" = null;
228 const lu52i = null;
229 const @"alsl.w" = null;
230 const @"alsl.wu" = null;
231 const @"alsl.d" = null;
232 const @"bytepick.w" = null;
233 const @"bytepick.d" = null;
234
235 pub fn move(ops: *OperandIterator) ?Instruction {
236 const rd = ops.nextReg() orelse return null;
237 const rj = ops.nextReg() orelse return null;
238 return .ori(rd, rj, 0);
239 }
240
241 pub fn nop(_: *OperandIterator) ?Instruction {
242 return .andi(.zero, .zero, 0);
243 }
244};
245
246const Assemble = @This();
247const assert = std.debug.assert;
248const encoding = @import("encoding.zig");
249const bits = @import("bits.zig");
250const Instruction = encoding.Instruction;
251const Mnemonic = encoding.Mnemonic;
252const Register = bits.Register;
253const std = @import("std");
254const log = std.log.scoped(.@"asm");
255const inst_formats = @import("inst_formats.zon");
src/codegen/loongarch/Disassemble.zig created+220
......@@ -0,0 +1,220 @@
1const encoding = @import("encoding.zig");
2const Mnemonic = encoding.Mnemonic;
3const Instruction = encoding.Instruction;
4const bits = @import("bits.zig");
5const Register = bits.Register;
6const Disassemble = @This();
7
8const decode_tree = @import("decode_tree.zon");
9const inst_formats = @import("inst_formats.zon");
10
11mnemonic_operands_separator: []const u8 = " ",
12operands_separator: []const u8 = ", ",
13enable_aliases: bool = false,
14preferred_style: Style = .manual,
15
16pub const Style = enum {
17 /// Encoding style, used by loongson-community/loongarch-opcodes.
18 ///
19 /// Output operands are not post-processed, sorted with slot offset.
20 encoding,
21 /// Manual style, used by the official manual and assembly code.
22 ///
23 /// Output operands are post-processed.
24 manual,
25};
26
27pub fn printInstruction(dis: *const Disassemble, inst: Instruction, writer: *std.Io.Writer) std.Io.Writer.Error!void {
28 @setEvalBranchQuota(3000);
29 const mnemonic = decodeMnemonic(inst) orelse return try writer.print("(UNKNOWN: 0x{x:0>8})", .{inst.word});
30
31 inline for (@typeInfo(Mnemonic).@"enum".field_names) |mnemonic_field| try_mnemonic: {
32 if (@field(Mnemonic, mnemonic_field) != mnemonic) break :try_mnemonic;
33
34 const inst_info = @field(inst_formats.instructions, mnemonic_field);
35 const InstInfo = @TypeOf(inst_info);
36
37 switch (dis.preferred_style) {
38 .encoding => {
39 try writer.writeAll(mnemonic_field);
40 const format = inst_info.format;
41 if (format != .EMPTY) try writer.writeAll(dis.mnemonic_operands_separator);
42 try dis.printOperands(@field(inst_formats.formats, @tagName(format)), inst, writer);
43 },
44 .manual => {
45 try writer.writeAll(if (@hasField(InstInfo, "orig_name")) inst_info.orig_name else mnemonic_field);
46 const format = if (@hasField(InstInfo, "orig_format")) inst_info.orig_format else inst_info.format;
47 if (format != .EMPTY) try writer.writeAll(dis.mnemonic_operands_separator);
48 try dis.printOperands(@field(inst_formats.formats, @tagName(format)), inst, writer);
49 },
50 }
51 return;
52 }
53}
54
55pub fn printInstructionAlloc(dis: *const Disassemble, inst: Instruction, gpa: std.mem.Allocator) (std.Io.Writer.Error || std.mem.Allocator.Error)![]u8 {
56 var writer: std.Io.Writer.Allocating = .init(gpa);
57 defer writer.deinit();
58 try dis.printInstruction(inst, &writer.writer);
59 return try writer.toOwnedSlice();
60}
61
62test printInstruction {
63 const dis: Disassemble = .{};
64
65 const testDisasm = struct {
66 fn testDisasm(expected: []const u8, inst: u32) !void {
67 const assembly = try dis.printInstructionAlloc(.{ .word = inst }, std.testing.allocator);
68 defer std.testing.allocator.free(assembly);
69 try std.testing.expectEqualStrings(expected, assembly);
70 }
71 }.testDisasm;
72
73 try testDisasm("fcmp.caf.s $fcc0, $f1, $f2", 0x0c100820);
74 try testDisasm("ertn", 0x06483800);
75 try testDisasm("addi.d $r8, $r0, 0xa", 0x02c02808);
76}
77
78pub fn fmtInstruction(dis: Disassemble, inst: Instruction) struct {
79 dis: Disassemble,
80 inst: Instruction,
81
82 pub fn format(data: @This(), w: *std.Io.Writer) std.Io.Writer.Error!void {
83 try data.dis.printInstruction(data.inst, w);
84 }
85} {
86 return .{ .dis = dis, .inst = inst };
87}
88
89pub fn decodeMnemonic(inst: Instruction) ?Mnemonic {
90 return decodeMnemonicWithTree(decode_tree, inst);
91}
92
93/// Decodes mnemonics with a node in the decode tree.
94fn decodeMnemonicWithTree(comptime tree: anytype, inst: Instruction) ?Mnemonic {
95 const Tree = @TypeOf(tree);
96 if (@hasField(Tree, "instruction")) {
97 return @field(Mnemonic, @tagName(tree.instruction));
98 } else if (@hasField(Tree, "mask")) {
99 const value = inst.word & tree.mask;
100 inline for (tree.cases) |case| try_case: {
101 if (@hasField(@TypeOf(case), "value")) {
102 if (value != case.value) break :try_case;
103 }
104 const then = case.then;
105
106 if (@hasField(@TypeOf(then), "instruction")) {
107 // manually inline here to reduce decoder functions of leaf nodes
108 return @field(Mnemonic, @tagName(then.instruction));
109 } else return decodeMnemonicWithTree(then, inst);
110 }
111 return null;
112 } else {
113 @compileLog("Current decode-tree node:", tree);
114 @compileError("Invalid decode-tree node");
115 }
116}
117
118test decodeMnemonic {
119 try std.testing.expectEqual(Mnemonic.@"fcmp.caf.s", decodeMnemonic(.{ .word = 0x0c100820 }).?);
120 try std.testing.expectEqual(Mnemonic.eret, decodeMnemonic(.{ .word = 0x06483800 }).?);
121 try std.testing.expectEqual(Mnemonic.@"addi.d", decodeMnemonic(.{ .word = 0x02c02808 }).?);
122}
123
124pub fn printOperands(dis: *const Disassemble, comptime format: anytype, inst: Instruction, writer: *std.Io.Writer) std.Io.Writer.Error!void {
125 const word = inst.word;
126 inline for (format.slots, 0..) |slot, slot_i| {
127 if (slot_i != 0) try writer.writeAll(dis.operands_separator);
128
129 const Slot = @TypeOf(slot);
130 if (@hasField(Slot, "reg")) {
131 const location = slot.reg.location;
132 const class = slot.reg.class;
133
134 const reg: u5 = if (class == .fcc)
135 @as(u3, @truncate(word >> location))
136 else
137 @as(u5, @truncate(word >> location));
138
139 try dis.printRegister(writer, class, reg);
140 } else if (@hasField(Slot, "imm")) {
141 const signedness = @field(std.builtin.Signedness, @tagName(slot.imm.signedness));
142 const ImmValue = @Int(signedness, slot.imm.length);
143 const UnsignedImmValue = @Int(.unsigned, slot.imm.length);
144 const Imm32 = @Int(signedness, 32);
145 // extend to 32-bit so postprocess won't overflow
146 var value: Imm32 = @as(ImmValue, @bitCast(@as(UnsignedImmValue, @truncate(word >> slot.imm.location))));
147
148 if (@hasField(@TypeOf(slot.imm), "post_proc")) {
149 const postproc = slot.imm.post_proc;
150 const PostProc = @TypeOf(postproc);
151 if (@hasField(PostProc, "shl")) value <<= postproc.shl;
152 if (@hasField(PostProc, "add")) value += postproc.add;
153 }
154
155 if (signedness == .unsigned) {
156 try writer.print("0x{x}", .{value});
157 } else {
158 if (value >= 0)
159 try writer.print("0x{x}", .{value})
160 else
161 try writer.print("-0x{x}", .{@abs(value)});
162 }
163 } else {
164 @compileLog("Current slot:", slot);
165 @compileError("Invalid operand slot info");
166 }
167 }
168}
169
170fn printRegister(dis: *const Disassemble, writer: *std.Io.Writer, comptime class: anytype, orig_reg: u5) std.Io.Writer.Error!void {
171 var reg = orig_reg;
172 const reg_prefix = prefix: {
173 if (dis.enable_aliases) {
174 switch (class) {
175 .int => switch (reg) {
176 1 => return try writer.print("$ra", .{}),
177 3 => return try writer.print("$sp", .{}),
178 4...11 => {
179 reg -= 4;
180 break :prefix "a";
181 },
182 12...20 => {
183 reg -= 12;
184 break :prefix "t";
185 },
186 22 => return try writer.print("$fp", .{}),
187 23...31 => {
188 reg -= 23;
189 break :prefix "s";
190 },
191 else => {},
192 },
193 .fp => switch (reg) {
194 0...7 => break :prefix "fa",
195 8...23 => {
196 reg -= 8;
197 break :prefix "ft";
198 },
199 24...31 => {
200 reg -= 24;
201 break :prefix "fs";
202 },
203 },
204 else => {},
205 }
206 }
207 break :prefix switch (class) {
208 .int => "r",
209 .fp => "f",
210 .fcc => "fcc",
211 .lsx => "v",
212 .lasx => "x",
213 else => unreachable,
214 };
215 };
216
217 try writer.print("${s}{d}", .{ reg_prefix, reg });
218}
219
220const std = @import("std");
src/codegen/loongarch/Mir.zig created+275
......@@ -0,0 +1,275 @@
1const Mir = @This();
2const Instruction = @import("encoding.zig").Instruction;
3const Disassemble = @import("Disassemble.zig");
4
5prologue: []const Instruction,
6body: []const Instruction,
7epilogue: []const Instruction,
8nav_relocs: []const Reloc.Nav,
9uav_relocs: []const Reloc.Uav,
10lazy_relocs: []const Reloc.Lazy,
11global_relocs: []const Reloc.Global,
12internal_relocs: []const Reloc.Internal,
13
14pub const Reloc = struct {
15 label: u32,
16 addend: i64 align(@alignOf(u32)) = 0,
17
18 pub const Nav = struct {
19 nav: InternPool.Nav.Index,
20 reloc: Reloc,
21 };
22
23 pub const Uav = struct {
24 uav: InternPool.Key.Ptr.BaseAddr.Uav,
25 reloc: Reloc,
26 };
27
28 pub const Lazy = struct {
29 symbol: link.File.LazySymbol,
30 reloc: Reloc,
31 };
32
33 pub const Global = struct {
34 name: [*:0]const u8,
35 reloc: Reloc,
36 };
37
38 pub const Literal = struct {
39 label: u32,
40 };
41
42 pub const Internal = struct {
43 // Target MIR index
44 target: usize = 0,
45 label: u32,
46 };
47};
48
49pub fn deinit(mir: *Mir, gpa: std.mem.Allocator) void {
50 assert(mir.body.ptr + mir.body.len == mir.prologue.ptr);
51 assert(mir.prologue.ptr + mir.prologue.len == mir.epilogue.ptr);
52 gpa.free(mir.body.ptr[0 .. mir.body.len + mir.prologue.len + mir.epilogue.len]);
53 gpa.free(mir.nav_relocs);
54 gpa.free(mir.uav_relocs);
55 gpa.free(mir.lazy_relocs);
56 gpa.free(mir.global_relocs);
57 gpa.free(mir.internal_relocs);
58 mir.* = undefined;
59}
60
61pub fn emit(
62 mir: Mir,
63 lf: *link.File,
64 pt: Zcu.PerThread,
65 func_index: InternPool.Index,
66 atom_index: link.File.AtomId,
67 w: *std.Io.Writer,
68 debug_output: link.File.DebugInfoOutput,
69) !void {
70 _ = debug_output;
71 const zcu = pt.zcu;
72 const ip = &zcu.intern_pool;
73 const func = zcu.funcInfo(func_index);
74 const nav = ip.getNav(func.owner_nav);
75 mir_log.debug("{f}:", .{nav.fqn.fmt(ip)});
76
77 const code_len = mir.prologue.len + mir.body.len + mir.epilogue.len;
78 try w.rebase(w.end, @sizeOf(Instruction) * code_len);
79 emitInstructionsBackward(w, mir.prologue) catch unreachable;
80 emitInstructionsBackward(w, mir.body) catch unreachable;
81 const body_end: u32 = @intCast(w.end);
82 emitInstructionsBackward(w, mir.epilogue) catch unreachable;
83 mir_log.debug("", .{});
84
85 for (mir.nav_relocs) |nav_reloc| emitReloc(
86 lf,
87 zcu,
88 atom_index,
89 try @import("../../codegen.zig").genNavRef(
90 lf,
91 pt,
92 nav_reloc.nav,
93 ),
94 mir.body[nav_reloc.reloc.label],
95 body_end - @sizeOf(Instruction) * (1 + nav_reloc.reloc.label),
96 nav_reloc.reloc.addend,
97 ) catch |err|
98 return zcu.codegenFail(func.owner_nav, "emit reloc failed: {t}", .{err});
99 for (mir.uav_relocs) |uav_reloc| emitReloc(
100 lf,
101 zcu,
102 atom_index,
103 try lf.lowerUav(
104 pt,
105 uav_reloc.uav.val,
106 ZigType.fromInterned(uav_reloc.uav.orig_ty).ptrAlignment(zcu),
107 ),
108 mir.body[uav_reloc.reloc.label],
109 body_end - @sizeOf(Instruction) * (1 + uav_reloc.reloc.label),
110 uav_reloc.reloc.addend,
111 ) catch |err|
112 return zcu.codegenFail(func.owner_nav, "emit reloc failed: {t}", .{err});
113 for (mir.lazy_relocs) |lazy_reloc| emitReloc(
114 lf,
115 zcu,
116 atom_index,
117 if (lf.cast(.elf)) |ef|
118 @fromBackingInt(ef.zigObjectPtr().?.getOrCreateMetadataForLazySymbol(ef, pt, lazy_reloc.symbol) catch |err|
119 return zcu.codegenFail(func.owner_nav, "{s} creating lazy symbol", .{@errorName(err)}))
120 else if (lf.cast(.elf2)) |elf|
121 elf.lazySymbol(lazy_reloc.symbol) catch |err|
122 return zcu.codegenFail(func.owner_nav, "emit lazy symbol: {t}", .{err})
123 else
124 return zcu.codegenFail(func.owner_nav, "external symbols unimplemented for {s}", .{@tagName(lf.tag)}),
125 mir.body[lazy_reloc.reloc.label],
126 body_end - @sizeOf(Instruction) * (1 + lazy_reloc.reloc.label),
127 lazy_reloc.reloc.addend,
128 ) catch |err|
129 return zcu.codegenFail(func.owner_nav, "emit reloc failed: {t}", .{err});
130 for (mir.global_relocs) |global_reloc| emitReloc(
131 lf,
132 zcu,
133 atom_index,
134 if (lf.cast(.elf)) |ef|
135 @fromBackingInt(try ef.getGlobalSymbol(std.mem.span(global_reloc.name), null))
136 else if (lf.cast(.elf2)) |elf| elf.externSymbol(.{
137 .name = std.mem.span(global_reloc.name),
138 .lib_name = null,
139 .type = .FUNC,
140 }) catch |err|
141 return zcu.codegenFail(func.owner_nav, "emit global symbol failed: {t}", .{err}) else return zcu.codegenFail(func.owner_nav, "external symbols unimplemented for {s}", .{@tagName(lf.tag)}),
142 mir.body[global_reloc.reloc.label],
143 body_end - @sizeOf(Instruction) * (1 + global_reloc.reloc.label),
144 global_reloc.reloc.addend,
145 ) catch |err|
146 return zcu.codegenFail(func.owner_nav, "emit reloc failed: {t}", .{err});
147
148 const func_nav = try @import("../../codegen.zig").genNavRef(
149 lf,
150 pt,
151 func.owner_nav,
152 );
153 for (mir.internal_relocs) |internal_reloc| emitReloc(
154 lf,
155 zcu,
156 atom_index,
157 func_nav,
158 mir.body[internal_reloc.label],
159 body_end - @sizeOf(Instruction) * (1 + internal_reloc.label),
160 @sizeOf(Instruction) * (@as(i64, @intCast(mir.prologue.len + mir.body.len - internal_reloc.target))),
161 ) catch |err|
162 return zcu.codegenFail(func.owner_nav, "emit reloc failed: {t}", .{err});
163}
164
165fn emitInstructionsForward(w: *std.Io.Writer, instructions: []const Instruction) !void {
166 for (instructions) |instruction| try emitInstruction(w, instruction);
167}
168fn emitInstructionsBackward(w: *std.Io.Writer, instructions: []const Instruction) !void {
169 var instruction_index = instructions.len;
170 while (instruction_index > 0) {
171 instruction_index -= 1;
172 try emitInstruction(w, instructions[instruction_index]);
173 }
174}
175fn emitInstruction(w: *std.Io.Writer, instruction: Instruction) !void {
176 mir_log.debug(" {f}", .{(Disassemble{}).fmtInstruction(instruction)});
177 try w.writeInt(@FieldType(Instruction, "word"), instruction.word, .little);
178}
179
180fn emitReloc(
181 lf: *link.File,
182 zcu: *Zcu,
183 atom_index: link.File.AtomId,
184 sym_index: link.File.SymbolId,
185 instruction: Instruction,
186 offset: u32,
187 addend: i64,
188) !void {
189 const mnemonic = Disassemble.decodeMnemonic(instruction) orelse {
190 mir_log.debug("cannot decode instruction 0x{x}", .{instruction.word});
191 unreachable;
192 };
193 switch (mnemonic) {
194 else => {
195 mir_log.debug("unimplemented reloc on {t}", .{mnemonic});
196 unreachable;
197 },
198 .pcaddu18i => if (lf.cast(.elf2)) |ef| {
199 try ef.addReloc(atom_index, offset, sym_index, addend, .{ .LARCH = .CALL36 });
200 } else if (lf.cast(.elf)) |ef| {
201 const zo = ef.zigObjectPtr().?;
202 const atom = zo.symbol(@backingInt(atom_index)).atom(ef).?;
203 try atom.addReloc(zcu.gpa, .{
204 .r_offset = offset,
205 .r_info = @as(u64, @backingInt(sym_index)) << 32 | @backingInt(std.elf.R_LARCH.CALL36),
206 .r_addend = @bitCast(addend),
207 }, zo);
208 } else unreachable,
209 .b, .bl => if (lf.cast(.elf2)) |ef| {
210 try ef.addReloc(atom_index, offset, sym_index, addend, .{ .LARCH = .B26 });
211 } else if (lf.cast(.elf)) |ef| {
212 const zo = ef.zigObjectPtr().?;
213 const atom = zo.symbol(@backingInt(atom_index)).atom(ef).?;
214 try atom.addReloc(zcu.gpa, .{
215 .r_offset = offset,
216 .r_info = @as(u64, @backingInt(sym_index)) << 32 | @backingInt(std.elf.R_LARCH.B26),
217 .r_addend = @bitCast(addend),
218 }, zo);
219 } else unreachable,
220 .beq, .bne, .ble, .bgt, .bleu, .bgtu => if (lf.cast(.elf2)) |ef| {
221 try ef.addReloc(atom_index, offset, sym_index, addend, .{ .LARCH = .B16 });
222 } else if (lf.cast(.elf)) |ef| {
223 const zo = ef.zigObjectPtr().?;
224 const atom = zo.symbol(@backingInt(atom_index)).atom(ef).?;
225 try atom.addReloc(zcu.gpa, .{
226 .r_offset = offset,
227 .r_info = @as(u64, @backingInt(sym_index)) << 32 | @backingInt(std.elf.R_LARCH.B16),
228 .r_addend = @bitCast(addend),
229 }, zo);
230 } else unreachable,
231 .beqz, .bnez, .bceqz, .bcnez => if (lf.cast(.elf2)) |ef| {
232 try ef.addReloc(atom_index, offset, sym_index, addend, .{ .LARCH = .B21 });
233 } else if (lf.cast(.elf)) |ef| {
234 const zo = ef.zigObjectPtr().?;
235 const atom = zo.symbol(@backingInt(atom_index)).atom(ef).?;
236 try atom.addReloc(zcu.gpa, .{
237 .r_offset = offset,
238 .r_info = @as(u64, @backingInt(sym_index)) << 32 | @backingInt(std.elf.R_LARCH.B21),
239 .r_addend = @bitCast(addend),
240 }, zo);
241 } else unreachable,
242 .pcalau12i => if (lf.cast(.elf2)) |ef| {
243 try ef.addReloc(atom_index, offset, sym_index, addend, .{ .LARCH = .PCALA_HI20 });
244 } else if (lf.cast(.elf)) |ef| {
245 const zo = ef.zigObjectPtr().?;
246 const atom = zo.symbol(@backingInt(atom_index)).atom(ef).?;
247 try atom.addReloc(zcu.gpa, .{
248 .r_offset = offset,
249 .r_info = @as(u64, @backingInt(sym_index)) << 32 | @backingInt(std.elf.R_LARCH.PCALA_HI20),
250 .r_addend = @bitCast(addend),
251 }, zo);
252 } else unreachable,
253 .@"addi.d" => if (lf.cast(.elf2)) |ef| {
254 try ef.addReloc(atom_index, offset, sym_index, addend, .{ .LARCH = .PCALA_LO12 });
255 } else if (lf.cast(.elf)) |ef| {
256 const zo = ef.zigObjectPtr().?;
257 const atom = zo.symbol(@backingInt(atom_index)).atom(ef).?;
258 try atom.addReloc(zcu.gpa, .{
259 .r_offset = offset,
260 .r_info = @as(u64, @backingInt(sym_index)) << 32 | @backingInt(std.elf.R_LARCH.PCALA_LO12),
261 .r_addend = @bitCast(addend),
262 }, zo);
263 } else unreachable,
264 }
265}
266
267const Air = @import("../../Air.zig");
268const assert = std.debug.assert;
269const mir_log = std.log.scoped(.mir);
270const InternPool = @import("../../InternPool.zig");
271const link = @import("../../link.zig");
272const std = @import("std");
273const target_util = @import("../../target.zig");
274const Zcu = @import("../../Zcu.zig");
275const ZigType = @import("../../Type.zig");
src/codegen/loongarch/Select.zig created+7518
......@@ -0,0 +1,7518 @@
1const Register = @import("bits.zig").Register;
2const encoding = @import("encoding.zig");
3const Instruction = encoding.Instruction;
4const Mir = @import("Mir.zig");
5const Assemble = @import("Assemble.zig");
6const Disassemble = @import("Disassemble.zig");
7
8const verify_target_features = false;
9const assume_memmove_no_overlap = true;
10/// https://github.com/ziglang/zig/issues/11307
11/// Enabling this flag generates "break 0xAA" for unimplemented things.
12const debug_trap_unimplemented_code = false;
13/// Saves AIR index to $r21 for debugging.
14const debug_r21_as_air = false;
15
16pt: Zcu.PerThread,
17target: *const std.Target,
18opt_mode: std.builtin.OptimizeMode,
19air: Air,
20nav_index: InternPool.Nav.Index,
21
22// WIP MIR
23saved_registers: RegisterSet = .empty,
24instructions: std.ArrayList(Instruction) = .empty,
25nav_relocs: std.ArrayList(Mir.Reloc.Nav) = .empty,
26uav_relocs: std.ArrayList(Mir.Reloc.Uav) = .empty,
27lazy_relocs: std.ArrayList(Mir.Reloc.Lazy) = .empty,
28global_relocs: std.ArrayList(Mir.Reloc.Global) = .empty,
29internal_relocs: std.ArrayList(Mir.Reloc.Internal) = .empty,
30
31// Stack Frame
32returns: bool = false,
33stack_size: u24 = 0,
34stack_align: InternPool.Alignment = .@"16",
35/// Relocations for reading incoming registers.
36///
37/// The instruction must be `ori rd, rj, 0`.
38/// These relocations are applied in `Select.layout`,
39/// and the instruction may be replaced with `ld.[w/d] rd, sp, ?`
40/// if `rj` is spilled to stack.
41///
42/// See `Select.ldIncoming`.
43layout_relocs: std.ArrayList(usize) = .empty,
44
45// Value Tracking
46live_registers: LiveRegisters = .initFill(.free),
47live_values: std.AutoHashMapUnmanaged(Air.Inst.Index, Value.Index) = .empty,
48values: std.ArrayList(Value) = .empty,
49value_types: std.ArrayList(ZigType) = .empty,
50
51// Calling Convention
52arg_layouts: []const Value.Index = &.{},
53
54// Analysis
55/// Definition order of AIR instructions.
56def_order: std.AutoArrayHashMapUnmanaged(Air.Inst.Index, void) = .empty,
57/// Stack of active blocks. Value is undefined during analysis.
58active_blocks: std.AutoArrayHashMapUnmanaged(Air.Inst.Index, Block) = .empty,
59/// Loops. The last entry is Loop.invalid, which is added in `finishAnalysis`.
60loops: std.AutoArrayHashMapUnmanaged(Air.Inst.Index, Loop) = .empty,
61/// Stack of active loops.
62active_loops: std.ArrayList(Loop.Index) = .empty,
63/// Loop liveness
64loop_outer_live: struct {
65 /// Pairs of loops and AIRs that is used in the loop body but is defined
66 /// earlier than the loop entry.
67 /// Populated during analysis phase, in analyseUse.
68 ///
69 /// Includes only references where the loop and the AIR are in the same upper loop.
70 /// For example, in the following structure:
71 /// %1 arg
72 /// %2 arg
73 /// %3 arg
74 /// %4 loop (loop 0)
75 /// %5 add %1 %2
76 /// %6 loop (loop 1)
77 /// %7 add %3 %5
78 /// %8 add %2 %5
79 /// Only (loop 0, %1), (loop 0, %2), (loop 0, %3), (loop 1, %5) will be recorded, because,
80 /// although %2 and %3 are used in loop 1, they are in the outer layer of loop 0, not loop 1.
81 set: std.AutoArrayHashMapUnmanaged(struct { Loop.Index, Air.Inst.Index }, void) = .empty,
82 /// List representation of `loop_live.set`, for faster indexing.
83 list: std.ArrayList(Air.Inst.Index) = .empty,
84} = .{},
85
86pub const RegisterSet = std.enums.EnumSet(Register);
87pub const LiveRegisters = std.enums.EnumArray(Register, Value.Index);
88
89pub const Block = struct {
90 snapshot: LocationSnapshot = .empty,
91 target_label: u32,
92
93 pub const main: Air.Inst.Index = @fromBackingInt(
94 std.math.maxInt(@typeInfo(Air.Inst.Index).@"enum".tag_type),
95 );
96
97 pub fn deinit(target_block: *Block, isel: *Select) void {
98 target_block.snapshot.deinit(isel);
99 }
100
101 fn branch(target_block: *Block, isel: *Select) !void {
102 if (isel.instructions.items.len > target_block.target_label) {
103 try isel.internal_relocs.append(isel.pt.zcu.gpa, .{
104 .label = @intCast(isel.instructions.items.len),
105 .target = target_block.target_label,
106 });
107 try isel.emit(.b(0, 0));
108 }
109 try target_block.snapshot.merge(isel);
110 }
111};
112
113pub const Loop = struct {
114 def_order: u32,
115 outer_live: u32,
116 repeat_list: u32,
117 /// Used during code selection. Location snapshot before entering loop bodyies.
118 /// Cleared after leaving the loop body.
119 snapshot: LocationSnapshot = .empty,
120 /// Used during code selection. Registers that are written during a loop body.
121 /// See Select.markRegWritten.
122 /// After leaving a loop, written register set is copied to the outer loop.
123 written_regs: RegisterSet = .empty,
124
125 pub const invalid: Air.Inst.Index = @fromBackingInt(
126 std.math.maxInt(@typeInfo(Air.Inst.Index).@"enum".tag_type),
127 );
128
129 pub const Index = enum(u32) {
130 _,
131
132 fn inst(li: Loop.Index, isel: *Select) Air.Inst.Index {
133 return isel.loops.keys()[@backingInt(li)];
134 }
135
136 fn get(li: Loop.Index, isel: *Select) *Loop {
137 return &isel.loops.values()[@backingInt(li)];
138 }
139 };
140
141 pub const empty_list: u32 = std.math.maxInt(u32);
142
143 fn branch(target_loop: *Loop, isel: *Select) !void {
144 try isel.instructions.ensureUnusedCapacity(isel.pt.zcu.gpa, 1);
145 const repeat_list_tail = target_loop.repeat_list;
146 target_loop.repeat_list = @intCast(isel.instructions.items.len);
147 isel.instructions.appendAssumeCapacity(@bitCast(repeat_list_tail));
148 try target_loop.snapshot.merge(isel);
149 }
150};
151
152pub fn deinit(isel: *Select) void {
153 const gpa = isel.pt.zcu.gpa;
154
155 isel.instructions.deinit(gpa);
156 isel.nav_relocs.deinit(gpa);
157 isel.uav_relocs.deinit(gpa);
158 isel.lazy_relocs.deinit(gpa);
159 isel.global_relocs.deinit(gpa);
160 isel.internal_relocs.deinit(gpa);
161
162 isel.layout_relocs.deinit(gpa);
163
164 isel.live_values.deinit(gpa);
165 isel.values.deinit(gpa);
166 isel.value_types.deinit(gpa);
167
168 if (isel.arg_layouts.len != 0) gpa.free(isel.arg_layouts);
169
170 isel.def_order.deinit(gpa);
171 isel.active_blocks.deinit(gpa);
172 isel.loops.deinit(gpa);
173 isel.active_loops.deinit(gpa);
174 isel.loop_outer_live.set.deinit(gpa);
175 isel.loop_outer_live.list.deinit(gpa);
176
177 isel.* = undefined;
178}
179
180/// A node in the value tree.
181pub const Value = struct {
182 refs: u32,
183 flags: Flags,
184 offset_from_parent: u64,
185 parent_payload: Parent.Payload,
186 location_payload: LocationInfo.Payload,
187 parts: Value.Index,
188
189 /// Must be at least 16 to compute call ABI.
190 /// Must be at least 16, the largest hardware alignment.
191 pub const max_parts = 16;
192 pub const PartsLen = std.math.IntFittingRange(0, Value.max_parts);
193
194 comptime {
195 if (!std.debug.runtime_safety) assert(@sizeOf(Value) == 32);
196 }
197
198 pub const Flags = packed struct(u32) {
199 alignment: InternPool.Alignment,
200 parent_tag: Parent.Tag,
201 location_tag: LocationInfo.Tag,
202 parts_len_minus_one: std.math.IntFittingRange(0, Value.max_parts - 1),
203 splitted: bool,
204 unused: u17 = 0,
205 };
206
207 pub const Parent = union(enum(u2)) {
208 none: void,
209 value: Value.Index,
210 constant: Constant,
211 /// Dereferencing. Only used for layout values at ABI boundaries.
212 address: Value.Index,
213
214 pub const Tag = @typeInfo(Parent).@"union".tag_type.?;
215 pub const Payload = Payload: {
216 const info = @typeInfo(Parent).@"union";
217 break :Payload @Union(.auto, null, info.field_names, info.field_types[0..], &@splat(.{}));
218 };
219 };
220
221 pub const LocationInfo = union(enum(u2)) {
222 /// Small values that fit into a register
223 small: struct {
224 flags: packed struct {
225 /// Byte-size of the part
226 size: u6,
227 /// Way in which the unused bits are filled
228 /// For subtrees whose root has Parent.address, immutable after initialization
229 extension: Extension,
230 /// Register access modifier
231 hint_modifier: Register.Modifier,
232 /// Preferred register, maybe ignore, $zero = unset
233 hint_register: Register,
234 /// The current expected location
235 location_tag: Location.Tag,
236 },
237 location_payload: Location.Payload,
238 },
239 /// Large values that can only be stored in stack slots
240 large: struct {
241 /// Byte-size of the part
242 size: u32,
243 /// The current expected location
244 /// Well-shaped values are always in pcs extended, ill-shaped are garbage extended
245 stack_slot: Indirect,
246 },
247 /// Extreme values that are too large to be materialized in stack slots
248 extreme: struct {
249 size: u64,
250 },
251
252 pub const Tag = @typeInfo(LocationInfo).@"union".tag_type.?;
253 pub const Payload = Payload: {
254 const info = @typeInfo(LocationInfo).@"union";
255 break :Payload @Union(.auto, null, info.field_names, info.field_types[0..], &@splat(.{}));
256 };
257 };
258
259 pub const Location = union(enum(u1)) {
260 register: Register.Alias,
261 stack_slot: Indirect,
262
263 pub const unallocated: Location = .{ .register = .zero };
264
265 pub inline fn isUnallocated(loc: Location) bool {
266 return switch (loc) {
267 .register => |ra| ra.reg == Register.zero,
268 else => false,
269 };
270 }
271
272 fn tryLock(loc: Location, isel: *Select) RegLock {
273 return if (loc.asRegister()) |reg| isel.tryLockReg(reg) else .empty;
274 }
275
276 pub fn asRegisterAlias(loc: Location) ?Register.Alias {
277 return switch (loc) {
278 .register => |ra| if (ra.reg == Register.zero) null else ra,
279 else => null,
280 };
281 }
282
283 pub fn asRegister(loc: Location) ?Register {
284 return if (loc.asRegisterAlias()) |ra| ra.reg else null;
285 }
286
287 pub fn asStackSlot(loc: Location) ?Indirect {
288 return switch (loc) {
289 .stack_slot => |stack_slot| stack_slot,
290 else => null,
291 };
292 }
293
294 pub fn format(loc: Location, w: *std.Io.Writer) std.Io.Writer.Error!void {
295 if (loc.isUnallocated()) return w.writeAll("unallocated");
296 switch (loc) {
297 inline else => |loc_pl| try loc_pl.format(w),
298 }
299 }
300
301 pub fn markRegWritten(loc: Location, isel: *Select) void {
302 if (loc.asRegister()) |loc_reg| isel.markRegWritten(loc_reg);
303 }
304
305 pub const Tag = @typeInfo(Location).@"union".tag_type.?;
306 pub const Payload = Payload: {
307 const info = @typeInfo(Location).@"union";
308 break :Payload @Union(.auto, null, info.field_names, info.field_types[0..], &@splat(.{}));
309 };
310 };
311
312 // TODO far indirect
313 pub const Indirect = packed struct(u32) {
314 base: Register,
315 offset: i25,
316
317 pub const unallocated: Indirect = .{ .base = .zero, .offset = 0 };
318
319 pub fn withOffset(ind: Indirect, offset: i25) Indirect {
320 return .{
321 .base = ind.base,
322 .offset = ind.offset + offset,
323 };
324 }
325
326 pub fn format(self: Indirect, w: *std.Io.Writer) std.Io.Writer.Error!void {
327 try w.print("[${t}, #{s}0x{x}]", .{
328 self.base,
329 if (self.offset < 0) "-" else "",
330 @abs(self.offset),
331 });
332 }
333 };
334
335 pub const Extension = enum(u2) {
336 garbage,
337 sign_ext,
338 zero_ext,
339
340 pub fn fromSignedness(signedness: std.builtin.Signedness) Extension {
341 return switch (signedness) {
342 .signed => .sign_ext,
343 .unsigned => .zero_ext,
344 };
345 }
346
347 fn signednessForLoad(fill_mode: Extension) std.builtin.Signedness {
348 return switch (fill_mode) {
349 .garbage, .zero_ext => .unsigned,
350 .sign_ext => .signed,
351 };
352 }
353
354 pub fn mix(a: Extension, b: Extension) Extension {
355 if (a == b) return a;
356 return .garbage;
357 }
358
359 fn pcsMode(isel: *Select, ty: ZigType) Extension {
360 const zcu = isel.pt.zcu;
361 const int_info = switch (ty.zigTypeTag(zcu)) {
362 .bool => ZigType.u1.intInfo(zcu),
363 .int, .@"enum", .error_set => ty.intInfo(zcu),
364 else => return .garbage,
365 };
366 return switch (int_info.bits) {
367 32 => .sign_ext,
368 else => .fromSignedness(int_info.signedness),
369 };
370 }
371 };
372
373 pub const Index = enum(u32) {
374 allocating = std.math.maxInt(u32) - 1,
375 free = std.math.maxInt(u32) - 0,
376 _,
377
378 fn get(vi: Value.Index, isel: *Select) *Value {
379 return &isel.values.items[@backingInt(vi)];
380 }
381
382 fn typeOf(vi: Value.Index, isel: *Select) ?ZigType {
383 const ty = isel.value_types.items[@backingInt(vi)];
384 if (ty.ip_index == .none) return null;
385 return ty;
386 }
387
388 pub fn format(vi: Value.Index, w: *std.Io.Writer) std.Io.Writer.Error!void {
389 return switch (vi) {
390 _ => w.print("${d}", .{@backingInt(vi)}),
391 .allocating => w.writeAll("(allocating)"),
392 .free => w.writeAll("(free)"),
393 };
394 }
395
396 fn setAlignment(vi: Value.Index, isel: *Select, new_alignment: InternPool.Alignment) void {
397 vi.get(isel).flags.alignment = new_alignment;
398 }
399
400 pub fn alignment(vi: Value.Index, isel: *Select) InternPool.Alignment {
401 return vi.get(isel).flags.alignment;
402 }
403
404 pub fn setParent(vi: Value.Index, isel: *Select, new_parent: Parent) void {
405 const value = vi.get(isel);
406 if (value.refs > 0) {
407 switch (value.flags.parent_tag) {
408 .none, .constant => {},
409 inline .address, .value => |tag| @field(value.parent_payload, @tagName(tag)).deref(isel),
410 }
411 switch (new_parent) {
412 .none => unreachable,
413 .constant => {},
414 .address, .value => |parent_vi| _ = parent_vi.ref(isel),
415 }
416 }
417 value.flags.parent_tag = new_parent;
418 value.parent_payload = switch (new_parent) {
419 .none => unreachable,
420 inline else => |payload, tag| @unionInit(Parent.Payload, @tagName(tag), payload),
421 };
422 }
423
424 pub fn parent(vi: Value.Index, isel: *Select) Parent {
425 const value = vi.get(isel);
426 return switch (value.flags.parent_tag) {
427 inline else => |tag| @unionInit(
428 Parent,
429 @tagName(tag),
430 @field(value.parent_payload, @tagName(tag)),
431 ),
432 };
433 }
434
435 pub fn parentValue(vi: Value.Index, isel: *Select) ?Value.Index {
436 const value = vi.get(isel);
437 return switch (value.flags.parent_tag) {
438 .value => value.parent_payload.value,
439 else => null,
440 };
441 }
442
443 pub fn valueRoot(initial_vi: Value.Index, isel: *Select) struct { u64, Value.Index } {
444 var offset: u64 = 0;
445 var vi = initial_vi;
446 parent: switch (vi.parent(isel)) {
447 else => return .{ offset, vi },
448 .value => |parent_vi| {
449 offset += vi.get(isel).offset_from_parent;
450 vi = parent_vi;
451 continue :parent parent_vi.parent(isel);
452 },
453 }
454 }
455
456 pub fn locationInfo(vi: Value.Index, isel: *Select) LocationInfo {
457 const value = vi.get(isel);
458 return switch (value.flags.location_tag) {
459 inline else => |tag| @unionInit(
460 LocationInfo,
461 @tagName(tag),
462 @field(value.location_payload, @tagName(tag)),
463 ),
464 };
465 }
466
467 pub fn isSmall(vi: Value.Index, isel: *Select) bool {
468 return vi.get(isel).flags.location_tag == .small;
469 }
470
471 pub fn setSmallLocation(vi: Value.Index, isel: *Select, new_location: Location) void {
472 const value = vi.get(isel);
473 value.location_payload.small.flags.location_tag = new_location;
474 value.location_payload.small.location_payload = switch (new_location) {
475 inline else => |payload, tag| @unionInit(Location.Payload, @tagName(tag), payload),
476 };
477 }
478
479 pub fn smallLocation(vi: Value.Index, isel: *Select) Location {
480 const value = vi.get(isel);
481 return switch (value.location_payload.small.flags.location_tag) {
482 inline else => |tag| @unionInit(
483 Location,
484 @tagName(tag),
485 @field(value.location_payload.small.location_payload, @tagName(tag)),
486 ),
487 };
488 }
489
490 pub fn positionInParent(vi: Value.Index, isel: *Select) struct { u64, u64 } {
491 return .{ vi.get(isel).offset_from_parent, vi.size(isel) };
492 }
493
494 pub fn offsetIn(initial_vi: Value.Index, isel: *Select, ancestor_vi: Value.Index) u64 {
495 if (initial_vi == ancestor_vi) return 0;
496 var offset: u64 = 0;
497 var vi = initial_vi;
498 parent: switch (vi.parent(isel)) {
499 else => unreachable, // ancestor_vi is not an ancestor of initial_vi
500 .value => |parent_vi| {
501 offset += vi.get(isel).offset_from_parent;
502 if (parent_vi != ancestor_vi) {
503 vi = parent_vi;
504 continue :parent parent_vi.parent(isel);
505 } else return offset;
506 },
507 }
508 }
509
510 pub fn size(vi: Value.Index, isel: *Select) u64 {
511 return switch (vi.locationInfo(isel)) {
512 .small => |loc| loc.flags.size,
513 inline else => |loc| loc.size,
514 };
515 }
516
517 pub fn bitSize(vi: Value.Index, isel: *Select) u64 {
518 if (vi.typeOf(isel)) |init_ty| bit_size: {
519 const zcu = isel.pt.zcu;
520 var ty = init_ty;
521 check_ty: while (true) {
522 switch (ty.zigTypeTag(zcu)) {
523 else => {},
524 .error_union => break :bit_size,
525 .@"struct", .@"union" => if (ty.containerLayout(zcu) != .@"packed") break :bit_size,
526 .pointer, .optional => if (!ty.isPtrAtRuntime(zcu)) break :bit_size,
527 .array, .vector => {
528 ty = ty.childType(zcu);
529 continue :check_ty;
530 },
531 }
532 break :check_ty;
533 }
534 return init_ty.bitSize(zcu);
535 }
536 return vi.size(isel) * 8;
537 }
538
539 fn setExtension(vi: Value.Index, isel: *Select, new_mode: Extension) void {
540 const value = vi.get(isel);
541 if (value.flags.location_tag == .small)
542 value.location_payload.small.flags.extension = new_mode;
543 }
544
545 /// For values on stack, unused bits are the highest ((size * 8) - bit_size) bits.
546 /// For values on registers, unused bits are the highest (ra_width - bit_size) bits.
547 /// That is, for a u3 (3b, 1B) stored in LA64 GPR (64b, 8B), the unused bits to be filled
548 /// are reg[3..63] instead of reg[3..7].
549 pub fn extension(vi: Value.Index, isel: *Select) Extension {
550 const value = vi.get(isel);
551 return switch (value.flags.location_tag) {
552 .small => value.location_payload.small.flags.extension,
553 .large, .extreme => if (vi.typeOf(isel)) |ty| .pcsMode(isel, ty) else .garbage,
554 };
555 }
556
557 fn setHintModifier(vi: Value.Index, isel: *Select, new_modifier: Register.Modifier) void {
558 vi.get(isel).location_payload.small.flags.hint_modifier = new_modifier;
559 }
560
561 pub fn hintModifier(vi: Value.Index, isel: *Select) Register.Modifier {
562 return switch (vi.locationInfo(isel)) {
563 .small => |loc| loc.flags.hint_modifier,
564 .large, .extreme => .undef,
565 };
566 }
567
568 fn setHintRegister(vi: Value.Index, isel: *Select, new_hint: Register) void {
569 vi.get(isel).location_payload.small.flags.hint_register = new_hint;
570 }
571
572 pub fn hintRegister(vi: Value.Index, isel: *Select) ?Register {
573 return switch (vi.locationInfo(isel)) {
574 .small => |loc| switch (loc.flags.hint_register) {
575 Register.zero => null,
576 else => |hint_reg| hint_reg,
577 },
578 .large, .extreme => null,
579 };
580 }
581
582 pub fn hintRegisterAlias(vi: Value.Index, isel: *Select) ?Register.Alias {
583 return switch (vi.locationInfo(isel)) {
584 .small => |loc| switch (loc.flags.hint_register) {
585 Register.zero => null,
586 else => |hint_reg| .{ .mod = vi.hintModifier(isel), .reg = hint_reg },
587 },
588 .large, .extreme => null,
589 };
590 }
591
592 pub fn location(vi: Value.Index, isel: *Select) ?Location {
593 return switch (vi.locationInfo(isel)) {
594 .small => |loc| if (loc.flags.location_tag == .register and loc.location_payload.register.reg == Register.zero)
595 null
596 else switch (loc.flags.location_tag) {
597 inline else => |tag| @unionInit(
598 Location,
599 @tagName(tag),
600 @field(loc.location_payload, @tagName(tag)),
601 ),
602 },
603 .large => |loc| if (loc.stack_slot == Indirect.unallocated)
604 null
605 else
606 .{ .stack_slot = loc.stack_slot },
607 .extreme => null,
608 };
609 }
610
611 pub fn register(vi: Value.Index, isel: *Select) ?Register.Alias {
612 return switch (vi.location(isel) orelse return null) {
613 .register => |ra| ra,
614 .stack_slot => null,
615 };
616 }
617
618 pub fn stackSlot(vi: Value.Index, isel: *Select) ?Indirect {
619 return switch (vi.location(isel) orelse return null) {
620 .register => null,
621 .stack_slot => |slot| slot,
622 };
623 }
624
625 /// Takes the expected location. Registers are free.
626 fn takeLocation(vi: Value.Index, isel: *Select) ?Location {
627 const value = vi.get(isel);
628 return switch (value.flags.location_tag) {
629 .small => loc: {
630 const loc = vi.smallLocation(isel);
631 if (loc.isUnallocated()) break :loc null;
632 if (loc.asRegister()) |reg| {
633 const live_vi = isel.live_registers.getPtr(reg);
634 assert(live_vi.* == vi);
635 live_vi.* = .free;
636 }
637 vi.setSmallLocation(isel, .unallocated);
638 break :loc loc;
639 },
640 .large => loc: {
641 const stack_slot = value.location_payload.large.stack_slot;
642 if (stack_slot == Indirect.unallocated) break :loc null;
643 value.location_payload.large.stack_slot = .unallocated;
644 break :loc .{ .stack_slot = stack_slot };
645 },
646 .extreme => null,
647 };
648 }
649
650 /// Takes the expected location. Registers are free and marked written.
651 fn takeLocationMarkWritten(vi: Value.Index, isel: *Select) ?Location {
652 const maybe_loc = vi.takeLocation(isel);
653 if (maybe_loc) |loc| loc.markRegWritten(isel);
654 return maybe_loc;
655 }
656
657 fn setStackSlot(vi: Value.Index, isel: *Select, new_slot: Indirect) void {
658 const value = vi.get(isel);
659 return switch (value.flags.location_tag) {
660 .small => vi.setSmallLocation(isel, .{ .stack_slot = new_slot }),
661 .large => value.location_payload.large.stack_slot = new_slot,
662 .extreme => unreachable,
663 };
664 }
665
666 pub fn isUsed(vi: Value.Index, isel: *Select) bool {
667 return vi.valueRoot(isel)[1].parent(isel) != .none or vi.hasLocationRecursive(isel);
668 }
669
670 fn hasLocationRecursive(vi: Value.Index, isel: *Select) bool {
671 if (vi.location(isel) != null) return true;
672 var part_it = vi.parts(isel);
673 if (part_it.only() == null)
674 while (part_it.next()) |part_vi|
675 if (part_vi.hasLocationRecursive(isel)) return true;
676 return false;
677 }
678
679 fn setParts(vi: Value.Index, isel: *Select, parts_len: Value.PartsLen) void {
680 assert(parts_len > 1);
681 const value = vi.get(isel);
682 assert(value.flags.parts_len_minus_one == 0);
683 value.parts = @fromBackingInt(@intCast(isel.values.items.len));
684 value.flags.parts_len_minus_one = @intCast(parts_len - 1);
685 }
686
687 fn addPart(vi: Value.Index, isel: *Select, part_offset: u64, part_size: u64, maybe_ty: ?ZigType) Value.Index {
688 const part_vi = isel.initValueAdvanced(
689 vi.alignment(isel),
690 part_offset,
691 part_size,
692 maybe_ty,
693 );
694 if (maybe_ty) |ty|
695 tracking_log.debug("{f} <- {f}[{d}] ({d}B, {f})", .{ part_vi, vi, part_offset, part_size, isel.fmtType(ty) })
696 else
697 tracking_log.debug("{f} <- {f}[{d}] ({d}B, untyped)", .{ part_vi, vi, part_offset, part_size });
698 part_vi.setParent(isel, .{ .value = vi });
699 return part_vi;
700 }
701
702 fn addIntPart(vi: Value.Index, isel: *Select, part_offset: u64, part_size: u64, part_bit_size: u9) !Value.Index {
703 const part_vi = isel.initValueAdvanced(vi.alignment(isel), part_offset, part_size, try isel.pt.intType(.unsigned, part_bit_size));
704 tracking_log.debug("{f} <- {f}[{d}] ({d}B, {d}b)", .{ part_vi, vi, part_offset, part_size, part_bit_size });
705 part_vi.setParent(isel, .{ .value = vi });
706 return part_vi;
707 }
708
709 pub fn parts(vi: Value.Index, isel: *Select) Value.PartIterator {
710 const value = vi.get(isel);
711 return switch (value.flags.parts_len_minus_one) {
712 0 => .initOne(vi),
713 else => |parts_len_minus_one| .{
714 .vi = value.parts,
715 .remaining = @as(Value.PartsLen, parts_len_minus_one) + 1,
716 },
717 };
718 }
719
720 pub fn hasParts(vi: Value.Index, isel: *Select) bool {
721 return vi.get(isel).flags.parts_len_minus_one != 0;
722 }
723
724 fn partAtOffset(vi: Value.Index, isel: *Select, offset: u64) Value.Index {
725 const SearchPartIndex = std.math.IntFittingRange(0, Value.max_parts * 2 - 1);
726 const value = vi.get(isel);
727 var last: SearchPartIndex = value.flags.parts_len_minus_one;
728 if (last == 0) return vi;
729 var first: SearchPartIndex = 0;
730 last += 1;
731 while (true) {
732 const mid = (first + last) / 2;
733 const mid_vi: Value.Index = @fromBackingInt(@backingInt(value.parts) + mid);
734 if (mid == first) return mid_vi;
735 if (offset < mid_vi.get(isel).offset_from_parent) last = mid else first = mid;
736 }
737 }
738
739 fn partExact(vi: Value.Index, isel: *Select, offset: u64, part_size: u64) !Value.Index {
740 try vi.split(isel, false);
741 const part_vi = vi.partAtOffset(isel, offset);
742 if (part_vi.offsetIn(isel, vi) != offset or part_vi.size(isel) != part_size) {
743 isel.dumpValues(.all);
744 tracking_log.debug("{f}.partExact({}, {}) selected {f}", .{ vi, offset, part_size, part_vi });
745 unreachable;
746 }
747 return part_vi;
748 }
749
750 fn partExactRecursive(vi: Value.Index, isel: *Select, init_offset: u64, part_size: u64) !Value.Index {
751 if (init_offset == 0 and vi.size(isel) == part_size) return vi;
752 var part_vi = vi;
753 var offset = init_offset;
754 while (true) {
755 try part_vi.split(isel, false);
756 const subpart_vi = part_vi.partAtOffset(isel, offset);
757 if (subpart_vi == part_vi) {
758 isel.dumpValues(.all);
759 tracking_log.debug("{f}.partExactRecursive({}, {}) selected {f}", .{ vi, init_offset, part_size, part_vi });
760 unreachable;
761 }
762 const subpart_offset = subpart_vi.get(isel).offset_from_parent;
763 offset -= subpart_offset;
764 if (offset == 0 and subpart_vi.size(isel) == part_size) return subpart_vi;
765 part_vi = subpart_vi;
766 }
767 }
768
769 fn partAtLargerThan(vi: Value.Index, isel: *Select, offset: u64, part_size: u64) !Value.Index {
770 try vi.split(isel, false);
771 const part_vi = vi.partAtOffset(isel, offset);
772 if (part_vi.offsetIn(isel, vi) != offset or part_vi.size(isel) < part_size) {
773 isel.dumpValues(.all);
774 tracking_log.debug("{f}.partAtLargerThan({}, {}) selected {f}", .{ vi, offset, part_size, part_vi });
775 unreachable;
776 }
777 return part_vi;
778 }
779
780 fn walk(vi: Value.Index, isel: *Select, opts: Walk.Options) Walk {
781 return .{ .isel = isel, .root_vi = vi, .next_vi = vi, .opts = opts };
782 }
783
784 fn ref(initial_vi: Value.Index, isel: *Select) Value.Index {
785 var vi = initial_vi;
786 while (true) {
787 const refs = &vi.get(isel).refs;
788 refs.* += 1;
789 if (refs.* > 1) return initial_vi;
790 switch (vi.parent(isel)) {
791 .none, .constant => {},
792 .address, .value => |parent_vi| {
793 vi = parent_vi;
794 continue;
795 },
796 }
797 return initial_vi;
798 }
799 }
800
801 pub fn deref(initial_vi: Value.Index, isel: *Select) void {
802 var vi = initial_vi;
803 while (true) {
804 const refs = &vi.get(isel).refs;
805 refs.* -= 1;
806 if (refs.* > 0) return;
807 switch (vi.parent(isel)) {
808 .none, .constant => {},
809 .address, .value => |parent_vi| {
810 vi = parent_vi;
811 continue;
812 },
813 }
814 return;
815 }
816 }
817
818 /// Allocates a stack slot for this value, not updating the value location.
819 fn allocStackSlot(vi: Value.Index, isel: *Select) Indirect {
820 const offset = vi.alignment(isel).forward(isel.stack_size);
821 isel.stack_size = @intCast(offset + vi.size(isel));
822 tracking_log.debug("[sp, #0x{x}] -> allocated for {f}", .{ @abs(offset), vi });
823 return .{
824 .base = .sp,
825 .offset = @intCast(offset),
826 };
827 }
828
829 /// Allocates a register for this value, not updating the value location.
830 fn allocRegister(vi: Value.Index, isel: *Select) !?Register.Alias {
831 // Try to allocate hint register
832 if (vi.hintRegister(isel)) |hint_reg| {
833 const live_vi = isel.live_registers.getPtr(hint_reg);
834 if (live_vi.* == .free) {
835 live_vi.* = .allocating;
836 isel.saved_registers.insert(hint_reg);
837 return .{ .reg = hint_reg, .mod = vi.hintModifier(isel) };
838 }
839 }
840 // Try to allocate a register
841 const value = vi.get(isel);
842 switch (value.flags.location_tag) {
843 .small => {
844 const reg_mod = vi.hintModifier(isel);
845 const reg = try isel.allocReg(reg_mod.class());
846 return .{ .reg = reg, .mod = reg_mod };
847 },
848 .large, .extreme => return null,
849 }
850 }
851
852 fn reextend(vi: Value.Index, isel: *Select, new_ext: Extension) !void {
853 if (!vi.isSmall(isel)) return;
854 return vi.reextendAdvanced(isel, vi.bitSize(isel), null, new_ext);
855 }
856
857 fn reextendToGarbage(vi: Value.Index, isel: *Select) !void {
858 if (!vi.isSmall(isel)) return;
859 return vi.reextendAdvanced(isel, vi.bitSize(isel), null, .garbage);
860 }
861
862 fn reextendToPcs(vi: Value.Index, isel: *Select) !void {
863 if (!vi.isSmall(isel)) return;
864 const ty = vi.typeOf(isel) orelse unreachable; // cannot reextend ill-shaped values to PCS mode
865 return vi.reextendAdvanced(isel, vi.bitSize(isel), null, .pcsMode(isel, ty));
866 }
867
868 fn reextendAdvanced(
869 vi: Value.Index,
870 isel: *Select,
871 old_bits: u64,
872 override_old_ext: ?Extension,
873 new_ext: Extension,
874 ) !void {
875 if (vi.location(isel) == null) return;
876 const value = vi.get(isel);
877 const old_ext = override_old_ext orelse vi.extension(isel);
878 const bit_size = vi.bitSize(isel);
879 if (bit_size == 0) return;
880 const vi_bits = vi.size(isel) * 8;
881 const old_unused_bits = vi_bits - @min(old_bits, vi_bits);
882 const new_unused_bits = vi_bits - bit_size;
883 const dst_ext, const src_ext = if (bit_size == old_bits)
884 .{ old_ext, new_ext }
885 else if (bit_size < old_bits)
886 .{ .garbage, new_ext }
887 else ext_config: {
888 // To cast an ABI int to a wider one, signedness of the int must be specified
889 // in new_ext, so bits that are previously unused but now used can be properly
890 // re-filled.
891 if (old_ext != .garbage)
892 break :ext_config .{ old_ext, new_ext }
893 else
894 break :ext_config .{ .zero_ext, new_ext };
895 };
896 const unused_bits = @max(new_unused_bits, old_unused_bits);
897 if (dst_ext == src_ext and bit_size <= old_bits) return;
898 tracking_log.debug("{f}: {t} ({t}) -> {t} ({t}), {d}b -> {d}b", .{ vi, src_ext, new_ext, dst_ext, old_ext, old_bits, bit_size });
899
900 // avoid setting extension to .garbage to reduce MIR for sequences like
901 // zero_ext -> garbage -> zero_ext
902 if (dst_ext == .garbage) return;
903 if (value.flags.location_tag == .small)
904 value.location_payload.small.flags.extension = new_ext;
905 if (vi_bits <= isel.gprBits()) {
906 const vi_mat = try vi.mat(isel, .{ .pref = .only_reg });
907 try isel.fillUnusedBits(
908 vi_mat.reg(),
909 vi_mat.reg(),
910 dst_ext,
911 src_ext,
912 @intCast(vi_mat.ra().mod.bitSize(isel.target) - vi_bits + unused_bits),
913 );
914 try vi_mat.finish(isel);
915 } else {
916 const unused_bytes = std.math.divCeil(u64, unused_bits, 8) catch unreachable;
917 assert(unused_bytes <= isel.gprSize()); // TODO larger extending
918 const used_bytes = vi.size(isel) - unused_bytes;
919
920 var hit = false;
921 var walker = vi.walk(isel, .{});
922 while (walker.next()) |part_vi| {
923 const part_offset = part_vi.offsetIn(isel, vi);
924 const part_size = part_vi.size(isel);
925 const part_end = part_offset + part_size;
926 if (part_end <= used_bytes) continue;
927 if (part_size > isel.gprSize()) continue;
928
929 walker.skipChildren(part_vi);
930
931 const part_mat = try part_vi.mat(isel, .{ .pref = .only_reg });
932 try isel.fillUnusedBits(
933 part_mat.reg(),
934 part_mat.reg(),
935 dst_ext,
936 src_ext,
937 @intCast(unused_bits - ((vi.size(isel) - part_end) * 8)),
938 );
939 try part_mat.finish(isel);
940 if (hit) unreachable; // TODO
941 hit = true;
942 }
943 }
944 }
945
946 /// Defines ancestors by combining their children
947 fn defChildren(def_vi: Value.Index, isel: *Select) !void {
948 if (def_vi.parentValue(isel)) |parent_vi|
949 try parent_vi.defChildren(isel);
950 assert(def_vi.hasParts(isel));
951 if (def_vi.location(isel) == null) return;
952 wip_mir_log.debug(" | # merge children -> {f}", .{def_vi});
953 const def_bit_size = def_vi.bitSize(isel);
954
955 // If def_vi fits into a register, reextend def_vi
956 var reextend_parts = true;
957 if (def_vi.isSmall(isel)) {
958 const maybe_mixed_ext = mix_ext: {
959 var maybe_mixed_ext: ?Extension = null;
960 var part_it = def_vi.parts(isel);
961 while (part_it.next()) |part_vi| {
962 const part_offset, const part_size = part_vi.positionInParent(isel);
963 if ((part_offset + part_size) * 8 > def_bit_size) {
964 if (maybe_mixed_ext) |mixed_ext|
965 maybe_mixed_ext = mixed_ext.mix(part_vi.extension(isel))
966 else
967 maybe_mixed_ext = part_vi.extension(isel);
968 }
969 }
970 break :mix_ext maybe_mixed_ext;
971 };
972 if (maybe_mixed_ext) |mixed_ext| {
973 try def_vi.reextend(isel, mixed_ext);
974 reextend_parts = false;
975 }
976 }
977
978 const def_loc = def_vi.takeLocationMarkWritten(isel).?;
979 const def_reg_lock = def_loc.tryLock(isel);
980 defer def_reg_lock.unlock(isel);
981 const def_ext = def_vi.extension(isel);
982 var part_it = def_vi.parts(isel);
983 while (part_it.next()) |part_vi| {
984 const part_offset, const part_size = part_vi.positionInParent(isel);
985 const part_mat = try part_vi.mat(isel, .{});
986 try isel.moveLoc(def_loc, part_offset, part_mat.loc(), 0, part_size, .preserved);
987 try part_mat.finish(isel);
988 if (reextend_parts)
989 try part_vi.reextend(isel, def_ext);
990 }
991 }
992
993 /// Defines descendants by deriving from their parents
994 fn defParent(def_vi: Value.Index, isel: *Select) !void {
995 if (def_vi.hasParts(isel)) {
996 // DFS descendants
997 var part_it = def_vi.parts(isel);
998 while (part_it.next()) |part_vi| try part_vi.defParent(isel);
999 }
1000 wip_mir_log.debug(" | # derive parent -> {f}", .{def_vi});
1001 const parent_vi = def_vi.parentValue(isel).?;
1002 try def_vi.reextendAdvanced(isel, parent_vi.bitSize(isel), null, parent_vi.extension(isel));
1003 const def_loc = def_vi.takeLocationMarkWritten(isel) orelse return;
1004 const def_offset, const def_size = def_vi.positionInParent(isel);
1005 const parent_mat = try parent_vi.mat(isel, .{});
1006 try isel.moveLoc(def_loc, 0, parent_mat.loc(), def_offset, def_size, .none);
1007 try parent_mat.finish(isel);
1008 }
1009
1010 /// Defines ancestors and descendants
1011 fn collectDefs(vi: Value.Index, isel: *Select) !void {
1012 if (vi.parentValue(isel)) |parent_vi|
1013 try parent_vi.defChildren(isel);
1014 if (vi.hasParts(isel)) {
1015 var part_it = vi.parts(isel);
1016 while (part_it.next()) |part_vi| try part_vi.defParent(isel);
1017 }
1018 }
1019
1020 /// Defines a value with a location.
1021 /// Returned location must be free-ed by caller.
1022 /// Extension unchanged.
1023 fn def(vi: Value.Index, isel: *Select) error{ AlreadyReported, OutOfMemory }!?Location {
1024 try vi.collectDefs(isel);
1025 return vi.takeLocationMarkWritten(isel);
1026 }
1027
1028 /// Defines a value with a register.
1029 /// Returned registers are free-ed.
1030 /// Extension unchanged.
1031 fn defReg(vi: Value.Index, isel: *Select) !?Register.Alias {
1032 const value = vi.get(isel);
1033 assert(value.flags.location_tag == .small); // must fit into a register
1034 try vi.collectDefs(isel);
1035
1036 const loc = vi.takeLocationMarkWritten(isel) orelse return null;
1037 switch (loc) {
1038 .register => |ra| return ra,
1039 .stack_slot => |stack| {
1040 const reg_mod = vi.hintModifier(isel);
1041 const reg = try isel.allocRegForWrite(reg_mod.class());
1042 defer isel.freeReg(reg);
1043 const ra: Register.Alias = .{ .mod = reg_mod, .reg = reg };
1044 try isel.storeReg(reg, vi.size(isel), stack.base, stack.offset);
1045 return ra;
1046 },
1047 }
1048 }
1049
1050 /// Defines a value with a register.
1051 /// Returned registers are free-ed.
1052 /// Extension unchanged.
1053 fn defRegMod(vi: Value.Index, isel: *Select, mod: Register.Modifier) !?Register {
1054 assert(mod != .undef);
1055 const loc = try vi.defReg(isel) orelse return null;
1056 if (loc.mod == mod) return loc.reg;
1057 const new_reg = try isel.allocRegForWrite(mod.class());
1058 try isel.moveReg(
1059 loc,
1060 0,
1061 .{ .reg = new_reg, .mod = mod },
1062 0,
1063 @min(loc.mod.bitSize(isel.target), mod.bitSize(isel.target)),
1064 .none,
1065 );
1066 return new_reg;
1067 }
1068
1069 /// Defines a value with a stack slot.
1070 /// Reextended in PCS mode.
1071 fn defStack(vi: Value.Index, isel: *Select) !?Indirect {
1072 try vi.reextendToPcs(isel);
1073 try vi.collectDefs(isel);
1074 const loc = vi.takeLocationMarkWritten(isel) orelse return null;
1075 switch (loc) {
1076 .register => |ra| {
1077 const stack_slot = vi.allocStackSlot(isel);
1078 try isel.loadReg(ra.reg, vi.size(isel), vi.extension(isel).signednessForLoad(), stack_slot.base, stack_slot.offset);
1079 return stack_slot;
1080 },
1081 .stack_slot => |stack| return stack,
1082 }
1083 }
1084
1085 /// Defines a value with undefined bytes.
1086 fn defUndef(vi: Value.Index, isel: *Select) !void {
1087 try vi.reextendToGarbage(isel);
1088 try vi.collectDefs(isel);
1089 const loc = vi.takeLocationMarkWritten(isel) orelse return;
1090 wip_mir_log.debug(" | # undef -> {f}", .{vi});
1091 try isel.moveUndef(loc, vi.size(isel));
1092 }
1093
1094 /// Defines a value by loading from memory.
1095 /// Reextended to PCS mode.
1096 ///
1097 /// Returns true if vi has a location.
1098 fn defLoad(
1099 vi: Value.Index,
1100 isel: *Select,
1101 base_reg: Register,
1102 offset: u64,
1103 opts: MemoryAccessOptions,
1104 ) !bool {
1105 try vi.reextendToPcs(isel);
1106 try vi.collectDefs(isel);
1107 const loc = vi.takeLocationMarkWritten(isel) orelse return false;
1108 wip_mir_log.debug(" | # load {f} <- [${t}, #{d}] ({d}B)", .{ vi, base_reg, offset, vi.size(isel) });
1109 _ = opts;
1110
1111 try isel.moveLoc(
1112 loc,
1113 0,
1114 .{ .stack_slot = .{ .base = base_reg, .offset = 0 } },
1115 offset,
1116 vi.size(isel),
1117 .none,
1118 );
1119 return true;
1120 }
1121
1122 /// Defines a value by copying another value.
1123 /// PCS aware.
1124 fn defMove(dst_vi: Value.Index, isel: *Select, src_ref: Air.Inst.Ref) !void {
1125 try dst_vi.defCopy(isel, try isel.use(src_ref));
1126 }
1127
1128 /// Defines a value by copying another value.
1129 /// PCS aware.
1130 fn defCopy(dst_vi: Value.Index, isel: *Select, src_vi: Value.Index) !void {
1131 try dst_vi.collectDefs(isel);
1132 wip_mir_log.debug(" | # copy {f} <- {f}", .{ dst_vi, src_vi });
1133 const copy_size = @min(dst_vi.size(isel), src_vi.size(isel));
1134
1135 // select reextension strategy
1136 const ext_strat: enum { dst_to_src, src_to_dst } = ext_strat: {
1137 const dst_has_loc = dst_vi.location(isel) != null;
1138 const src_has_loc = src_vi.location(isel) != null;
1139 if (dst_has_loc and !src_has_loc and src_vi.isSmall(isel)) break :ext_strat .src_to_dst;
1140 if (src_has_loc and !dst_has_loc) break :ext_strat .dst_to_src;
1141 break :ext_strat .dst_to_src; // random choice
1142 };
1143
1144 // reextend dst
1145 if (ext_strat == .dst_to_src) {
1146 try dst_vi.reextendAdvanced(
1147 isel,
1148 dst_vi.bitSize(isel),
1149 null,
1150 src_vi.extension(isel),
1151 );
1152 }
1153
1154 // do copy
1155 {
1156 const loc = dst_vi.takeLocation(isel) orelse return;
1157 const src_mat = try src_vi.mat(isel, .{
1158 .size = @intCast(copy_size),
1159 .pref = switch (loc) {
1160 .register => .prefer_reg,
1161 .stack_slot => .prefer_stack,
1162 },
1163 .hint_ra = loc.asRegisterAlias() orelse .zero,
1164 .hint_stack = loc.asStackSlot() orelse .unallocated,
1165 });
1166 const src_loc = src_mat.loc();
1167 if (!std.meta.eql(loc, src_loc)) {
1168 loc.markRegWritten(isel);
1169 try isel.moveLoc(loc, 0, src_mat.loc(), 0, copy_size, .none);
1170 }
1171 try src_mat.finish(isel);
1172 }
1173
1174 // reextend src
1175 if (ext_strat == .src_to_dst) {
1176 try src_vi.reextend(isel, dst_vi.extension(isel));
1177 }
1178 }
1179
1180 /// Defines a value in a certain layout, commonly used near basic block boundaries.
1181 /// Reextends to PCS mode.
1182 pub fn defLiveIn(def_vi: Value.Index, isel: *Select, layout_vi: Value.Index, opts: struct {
1183 /// Whether registers should be freed.
1184 fill_regs: bool = true,
1185 }) !void {
1186 wip_mir_log.debug(" | # live in {f}, layout={f}", .{ def_vi, layout_vi });
1187 assert(def_vi.size(isel) == layout_vi.size(isel));
1188 const gpa = isel.pt.zcu.gpa;
1189
1190 var maybe_def_addr_mat: ?Value.Mat = null;
1191 switch (def_vi.parent(isel)) {
1192 .none => {},
1193 .value => |parent_vi| try parent_vi.defChildren(isel),
1194 .address => |def_addr_vi| {
1195 switch (layout_vi.parent(isel)) {
1196 .address => |layout_addr_vi| {
1197 try def_addr_vi.defLiveIn(isel, layout_addr_vi, opts);
1198 },
1199 .none, .value => {
1200 maybe_def_addr_mat = try def_vi.parent(isel).address.matIntRegZeroExt(isel);
1201 },
1202 .constant => unreachable,
1203 }
1204 },
1205 .constant => unreachable,
1206 }
1207
1208 // TODO optimize this O(n^2)
1209 var def_walk = def_vi.walk(isel, .{});
1210 while (def_walk.next()) |def_part_vi| {
1211 const part_offset = def_part_vi.offsetIn(isel, def_vi);
1212 const part_size = def_part_vi.size(isel);
1213 const part_end_plus1 = part_offset + part_size;
1214
1215 var layout_walk = layout_vi.walk(isel, .{});
1216 var layout_parts: std.ArrayList(struct {
1217 vi: Value.Index,
1218 offset: u64,
1219 end_plus1: u64,
1220 }) = .empty;
1221 defer layout_parts.deinit(gpa);
1222 var maybe_mixed_layout_ext: ?Extension = null;
1223 while (layout_walk.next()) |layout_part_vi| {
1224 if (layout_part_vi.location(isel) == null and layout_part_vi.hintRegister(isel) == null) continue;
1225 const layout_part_offset = layout_part_vi.offsetIn(isel, layout_vi);
1226 const layout_part_size = layout_part_vi.size(isel);
1227 const layout_part_end_plus1 = layout_part_offset + layout_part_size;
1228 if (layout_part_end_plus1 <= part_offset or
1229 layout_part_offset >= part_end_plus1) continue;
1230
1231 try layout_parts.append(gpa, .{
1232 .vi = layout_part_vi,
1233 .offset = layout_part_offset,
1234 .end_plus1 = layout_part_end_plus1,
1235 });
1236
1237 const layout_part_ext = layout_part_vi.extension(isel);
1238 if (maybe_mixed_layout_ext) |mixed_layout_ext| {
1239 maybe_mixed_layout_ext = mixed_layout_ext.mix(layout_part_ext);
1240 } else {
1241 maybe_mixed_layout_ext = layout_part_ext;
1242 }
1243 }
1244 if (maybe_mixed_layout_ext) |mixed_layout_ext| {
1245 try def_part_vi.reextend(isel, mixed_layout_ext);
1246 } else unreachable;
1247
1248 const def_part_loc = if (maybe_def_addr_mat == null or def_part_vi != def_vi) def_part_loc: {
1249 break :def_part_loc def_part_vi.takeLocationMarkWritten(isel) orelse continue;
1250 } else def_part_loc: {
1251 break :def_part_loc maybe_def_addr_mat.?.loc();
1252 };
1253 const def_part_lock = def_part_loc.tryLock(isel);
1254 defer def_part_lock.unlock(isel);
1255
1256 for (layout_parts.items) |layout_part| {
1257 const dst_offset = layout_part.offset -| part_offset;
1258 const src_offset = part_offset -| layout_part.offset;
1259
1260 const mat_size = @min(part_end_plus1, layout_part.end_plus1) - @max(part_offset, layout_part.offset);
1261 assert(mat_size != 0);
1262 const src_loc: Location = if (layout_part.vi.location(isel)) |loc|
1263 loc
1264 else if (layout_part.vi.hintRegisterAlias(isel)) |hint_ra|
1265 .{ .register = hint_ra }
1266 else
1267 unreachable;
1268 if (opts.fill_regs) {
1269 if (src_loc.asRegister()) |src_reg|
1270 _ = try isel.fillReg(src_reg);
1271 }
1272 // TODO: replace reextending def_part_vi to .zero_ext with moveLoc .wipe when applicable
1273 try isel.moveLoc(def_part_loc, dst_offset, src_loc, src_offset, mat_size, .preserved);
1274 }
1275 }
1276 if (maybe_def_addr_mat) |def_addr_mat| try def_addr_mat.finish(isel);
1277 }
1278
1279 const MemoryAccessOptions = struct {
1280 // TODO unimplemented, remove?
1281 @"volatile": bool = false,
1282 };
1283
1284 const MatOptions = struct {
1285 /// Offset of materialized part
1286 offset: u64 = 0,
1287 /// Size, coerced to [0, part size - offset]
1288 size: u32 = std.math.maxInt(u32),
1289 /// Location preference
1290 pref: LocPreference = .none,
1291 reg_mod: Register.Modifier = .undef,
1292 /// Expected extension mode
1293 extension: Extension = .garbage,
1294 hint_ra: Register.Alias = .zero,
1295 hint_stack: Indirect = .unallocated,
1296
1297 const LocPreference = enum {
1298 none,
1299 /// Loads value to a register if possible, otherwise returns a stack slot
1300 prefer_reg,
1301 /// Loads value to a register, asserts the value fitting into a register
1302 only_reg,
1303 /// If there isn't an exisiting location, allocate a stack slot
1304 prefer_stack,
1305 /// Stores value to a stack slot
1306 only_stack,
1307 };
1308 };
1309
1310 /// Materializes a value
1311 fn mat(vi: Value.Index, isel: *Select, opts: MatOptions) Mat.Error!Mat {
1312 // try vi.split(isel, true);
1313 const mat_size = @min(opts.size, @as(u32, @intCast(vi.size(isel) - opts.offset)));
1314 const loc_pref = if (opts.extension == .garbage)
1315 opts.pref
1316 else switch (opts.pref) {
1317 .none, .prefer_reg, .prefer_stack => .prefer_reg,
1318 .only_reg, .only_stack => |loc_pref| loc_pref,
1319 };
1320 var maybe_prev_loc: ?Location = null;
1321 const loc: Location, var full = loc: {
1322 // Try to reuse existing location
1323 if (vi.location(isel)) |loc| {
1324 maybe_prev_loc = loc;
1325 switch (loc) {
1326 .register => |loc_ra| if (opts.offset == 0 and (opts.reg_mod == .undef or opts.reg_mod == loc_ra.mod)) {
1327 switch (loc_pref) {
1328 .none, .prefer_reg, .only_reg, .prefer_stack => break :loc .{ loc, false },
1329 .only_stack => {},
1330 }
1331 },
1332 .stack_slot => switch (loc_pref) {
1333 .none, .prefer_stack, .only_stack => break :loc .{ loc, true },
1334 .prefer_reg, .only_reg => {},
1335 },
1336 }
1337 }
1338 if (loc_pref != .only_stack and loc_pref != .prefer_stack) {
1339 // Try to allocate hint RA
1340 if (opts.hint_ra.reg != Register.zero) {
1341 if (isel.live_registers.get(opts.hint_ra.reg) == .free) {
1342 isel.saved_registers.insert(opts.hint_ra.reg);
1343 break :loc .{ .{ .register = opts.hint_ra }, false };
1344 }
1345 }
1346 // Try to allocate a register
1347 if (opts.reg_mod == .undef or opts.reg_mod == vi.hintModifier(isel)) {
1348 if (try vi.allocRegister(isel)) |ra|
1349 break :loc .{ .{ .register = ra }, false };
1350 } else try_alloc: {
1351 const reg = isel.allocReg(opts.reg_mod.class()) catch break :try_alloc;
1352 break :loc .{ .{ .register = .{ .reg = reg, .mod = opts.reg_mod } }, false };
1353 }
1354 }
1355 // Use existing stack slot if cannot mat into regs
1356 switch (loc_pref) {
1357 .none, .prefer_stack, .only_stack => {},
1358 .prefer_reg => if (maybe_prev_loc) |loc| break :loc .{ loc, true },
1359 .only_reg => unreachable, // too large to fit in registers
1360 }
1361 // Use hint stack slot
1362 if (false) {
1363 // TODO needs stack slot tracking
1364 if (opts.hint_stack != .unallocated) {
1365 break :loc .{ .{ .stack_slot = opts.hint_stack }, false };
1366 }
1367 }
1368 // Allocate on stack
1369 break :loc .{ .{ .stack_slot = vi.allocStackSlot(isel) }, true };
1370 };
1371 if (maybe_prev_loc) |prev_loc| {
1372 if (std.meta.eql(loc, prev_loc)) {
1373 if (opts.extension != .garbage) {
1374 try vi.reextendAdvanced(isel, vi.bitSize(isel), null, opts.extension);
1375 }
1376 _ = vi.takeLocation(isel);
1377 }
1378 }
1379 if (loc.asRegister()) |reg| {
1380 const live_vi = isel.live_registers.getPtr(reg);
1381 switch (live_vi.*) {
1382 _ => unreachable,
1383 .allocating => {},
1384 .free => live_vi.* = .allocating,
1385 }
1386 full = opts.offset == 0 and mat_size == vi.size(isel);
1387 }
1388 if (full) {
1389 tracking_log.debug("{f}[{d}..{d}] -> {f}[...] (mat, {t})", .{ vi, opts.offset, opts.offset + mat_size - 1, loc, opts.extension });
1390 } else {
1391 tracking_log.debug("{f}[{d}..{d}] -> {f} (mat, {t})", .{ vi, opts.offset, opts.offset + mat_size - 1, loc, opts.extension });
1392 }
1393 return .{
1394 .vi = vi,
1395 .location = loc,
1396 .offset = opts.offset,
1397 .size = mat_size,
1398 .extension = opts.extension,
1399 .full = full,
1400 };
1401 }
1402
1403 fn matReg(vi: Value.Index, isel: *Select) !Mat {
1404 return vi.mat(isel, .{ .pref = .only_reg });
1405 }
1406
1407 fn matRegMod(vi: Value.Index, isel: *Select, mod: Register.Modifier) !Mat {
1408 return vi.mat(isel, .{ .pref = .only_reg, .reg_mod = mod });
1409 }
1410
1411 fn matIntRegZeroExt(vi: Value.Index, isel: *Select) !Mat {
1412 return vi.mat(isel, .{
1413 .pref = .only_reg,
1414 .reg_mod = .integer,
1415 .extension = .zero_ext,
1416 });
1417 }
1418
1419 /// Moves the address of vi, plus offset, to ptr_reg
1420 fn matAddress(vi: Value.Index, isel: *Select, ptr_reg: Register, offset: u64) !void {
1421 wip_mir_log.debug(" | # address ${t} <- (&{f} + {d})", .{ ptr_reg, vi, offset });
1422 const offset_from_root, const root_vi = vi.valueRoot(isel);
1423 const total_root_offset = offset_from_root + offset;
1424 switch (root_vi.parent(isel)) {
1425 .none => {
1426 const value_mat = try vi.mat(isel, .{ .pref = .only_stack });
1427 const value_stack = value_mat.loc().stack_slot;
1428 try isel.addImm(ptr_reg, value_stack.base, @as(i65, value_stack.offset) + offset);
1429 try value_mat.finish(isel);
1430 },
1431 .address => |addr_vi| {
1432 const addr_mat = try addr_vi.mat(isel, .{
1433 .pref = .only_reg,
1434 .hint_ra = .{ .mod = .integer, .reg = ptr_reg },
1435 });
1436 try isel.addImm(ptr_reg, addr_mat.reg(), total_root_offset);
1437 try addr_mat.finish(isel);
1438 },
1439 .value => unreachable,
1440 .constant => |constant| {
1441 const pt = isel.pt;
1442 const zcu = pt.zcu;
1443
1444 try isel.uav_relocs.append(zcu.gpa, .{
1445 .uav = .{
1446 .val = constant.toIntern(),
1447 .orig_ty = (try pt.singleConstPtrType(constant.typeOf(zcu))).toIntern(),
1448 },
1449 .reloc = .{
1450 .label = @intCast(isel.instructions.items.len),
1451 .addend = @intCast(total_root_offset),
1452 },
1453 });
1454 try isel.emit(.@"addi.d"(ptr_reg, ptr_reg, 0));
1455 try isel.uav_relocs.append(zcu.gpa, .{
1456 .uav = .{
1457 .val = constant.toIntern(),
1458 .orig_ty = (try pt.singleConstPtrType(constant.typeOf(zcu))).toIntern(),
1459 },
1460 .reloc = .{
1461 .label = @intCast(isel.instructions.items.len),
1462 .addend = @intCast(total_root_offset),
1463 },
1464 });
1465 try isel.emit(.pcalau12i(ptr_reg, 0));
1466 },
1467 }
1468 }
1469
1470 /// Stores a value to memory.
1471 fn matStore(
1472 vi: Value.Index,
1473 isel: *Select,
1474 base_reg: Register,
1475 offset: u64,
1476 opts: MemoryAccessOptions,
1477 ) !void {
1478 wip_mir_log.debug(" | # store {f} -> [${t}, #{d}]", .{ vi, base_reg, offset });
1479 _ = opts;
1480
1481 const hint_stack: Indirect = if (std.math.cast(@FieldType(Indirect, "offset"), offset)) |stack_off|
1482 .{ .base = base_reg, .offset = stack_off }
1483 else
1484 .unallocated;
1485 const value_mat = try vi.mat(isel, .{ .hint_stack = hint_stack });
1486 try isel.moveLoc(
1487 .{ .stack_slot = .{ .base = base_reg, .offset = 0 } },
1488 offset,
1489 value_mat.loc(),
1490 0,
1491 vi.size(isel),
1492 .none,
1493 );
1494 try value_mat.finish(isel);
1495 }
1496
1497 /// Stores a value in a certain layout, commonly used near basic block boundaries.
1498 /// Reextends to PCS mode.
1499 fn matLiveOut(
1500 vi: Value.Index,
1501 isel: *Select,
1502 layout_vi: Value.Index,
1503 opts: struct {
1504 mode: enum { param, ret },
1505 },
1506 ) !void {
1507 wip_mir_log.debug(" | # live out {f}, layout={f}, opts: regs={t}", .{ vi, layout_vi, opts.mode });
1508
1509 wip_mir_log.debug(" | # live out {f}: fill registers", .{vi});
1510 switch (opts.mode) {
1511 .param => {
1512 var layout_walk = layout_vi.walk(isel, .{});
1513 while (layout_walk.next()) |part_vi| {
1514 if (part_vi.hintRegister(isel)) |part_reg| {
1515 _ = try isel.fillReg(part_reg);
1516 }
1517 }
1518 },
1519 .ret => {
1520 var layout_walk = layout_vi.walk(isel, .{});
1521 while (layout_walk.next()) |part_vi| {
1522 if (part_vi.hintRegister(isel)) |part_reg| {
1523 assert(try isel.forgetReg(part_reg));
1524 _ = isel.lockReg(part_reg);
1525 }
1526 }
1527 },
1528 }
1529
1530 wip_mir_log.debug(" | # live out {f}: move values", .{vi});
1531 var layout_walk = layout_vi.walk(isel, .{});
1532 while (layout_walk.next()) |part_vi| {
1533 if (part_vi.hintRegisterAlias(isel)) |part_ra| {
1534 const part_offset = part_vi.offsetIn(isel, layout_vi);
1535 const part_size = part_vi.size(isel);
1536
1537 if (opts.mode == .ret) isel.freeReg(part_ra.reg);
1538 const value_mat = try vi.mat(isel, .{
1539 .hint_ra = part_ra,
1540 .offset = part_offset,
1541 .size = @intCast(part_size),
1542 .extension = part_vi.extension(isel),
1543 });
1544 try isel.moveLoc(.{ .register = part_ra }, 0, value_mat.loc(), 0, part_size, .none);
1545 try value_mat.finish(isel);
1546 }
1547
1548 if (part_vi.location(isel)) |layout_part_loc| {
1549 const layout_part_stack = layout_part_loc.asStackSlot().?;
1550 const part_offset = part_vi.offsetIn(isel, layout_vi);
1551 const part_size = part_vi.size(isel);
1552
1553 const value_mat = try vi.mat(isel, .{
1554 .hint_stack = layout_part_stack,
1555 .offset = part_offset,
1556 .size = @intCast(part_size),
1557 .extension = part_vi.extension(isel),
1558 });
1559 try isel.moveLoc(.{ .stack_slot = layout_part_stack }, 0, value_mat.loc(), 0, part_size, .none);
1560 try value_mat.finish(isel);
1561 }
1562 }
1563 }
1564
1565 /// Moves the expected location to another location.
1566 fn moveTo(vi: Value.Index, isel: *Select, src_loc: Location) !void {
1567 if (src_loc.asRegister()) |src_reg| _ = try isel.fillReg(src_reg);
1568 tracking_log.debug("{f} -> {f} (move to)", .{ vi, src_loc });
1569 if (vi.takeLocationMarkWritten(isel)) |dst_loc|
1570 try isel.moveLoc(dst_loc, 0, src_loc, 0, vi.size(isel), .none);
1571 if (vi.isSmall(isel)) {
1572 vi.setSmallLocation(isel, src_loc);
1573 if (src_loc.asRegister()) |src_reg| {
1574 const src_live_vi = isel.live_registers.getPtr(src_reg);
1575 assert(src_live_vi.* == .free);
1576 src_live_vi.* = vi;
1577 }
1578 } else {
1579 switch (src_loc) {
1580 .register => unreachable, // large values cannot be moved into a register
1581 .stack_slot => |src_stack| vi.setStackSlot(isel, src_stack),
1582 }
1583 }
1584 }
1585
1586 pub fn isSplitted(vi: Value.Index, isel: *Select) bool {
1587 const value = vi.get(isel);
1588 return value.flags.parts_len_minus_one != 0 or value.flags.splitted;
1589 }
1590
1591 pub fn split(vi: Value.Index, isel: *Select, force: bool) !void {
1592 const zcu = isel.pt.zcu;
1593 const ip = &zcu.intern_pool;
1594
1595 const value1 = vi.get(isel);
1596 if (value1.flags.splitted and !force) return;
1597 value1.flags.splitted = true;
1598 if (value1.flags.parts_len_minus_one != 0) return;
1599 var ty = vi.typeOf(isel) orelse {
1600 if (force)
1601 return vi.splitBlindly(isel)
1602 else
1603 return;
1604 };
1605
1606 try isel.values.ensureUnusedCapacity(zcu.gpa, Value.max_parts);
1607 try isel.value_types.ensureUnusedCapacity(zcu.gpa, Value.max_parts);
1608 const value = vi.get(isel);
1609 type_key: switch (ip.indexToKey(ty.toIntern())) {
1610 else => return isel.fail("unimplemented Value.split({f})", .{isel.fmtType(ty)}),
1611 .int_type => |int_type| {
1612 const gpr_size = isel.gprSize();
1613 const gpr_bits = isel.gprBits();
1614 const parts_len = std.math.divCeil(u16, int_type.bits, gpr_bits) catch unreachable;
1615 if (parts_len == 1) break :type_key;
1616 vi.setParts(isel, @intCast(parts_len));
1617 for (0..parts_len) |part_index|
1618 _ = try vi.addIntPart(
1619 isel,
1620 part_index * gpr_size,
1621 gpr_size,
1622 @intCast(@min(int_type.bits - (part_index * gpr_bits), gpr_bits)),
1623 );
1624 },
1625 .ptr_type => |ptr_type| switch (ptr_type.flags.size) {
1626 .one, .many, .c => break :type_key,
1627 .slice => {
1628 const ptr_size = isel.gprSize();
1629 vi.setParts(isel, 2);
1630 _ = vi.addPart(isel, 0, ptr_size, ty.slicePtrFieldType(zcu));
1631 _ = vi.addPart(isel, ptr_size, ptr_size, .usize);
1632 },
1633 },
1634 .opt_type => |child_type| if (ty.optionalReprIsPayload(zcu)) {
1635 ty = .fromInterned(child_type);
1636 continue :type_key ip.indexToKey(child_type);
1637 } else {
1638 const child_ty: ZigType = .fromInterned(child_type);
1639 const child_size = child_ty.abiSize(zcu);
1640 vi.setParts(isel, 2);
1641 _ = vi.addPart(isel, 0, child_size, child_ty);
1642 _ = vi.addPart(isel, child_size, 1, .bool);
1643 },
1644 .array_type => |array_type| {
1645 const full_len = array_type.lenIncludingSentinel();
1646 const child_ty: ZigType = .fromInterned(array_type.child);
1647 const child_size = child_ty.abiSize(zcu);
1648 const aligned_size = child_ty.abiAlignment(zcu).forward(child_size);
1649 if (full_len == 1) {
1650 continue :type_key ip.indexToKey(child_ty.ip_index);
1651 } else if (full_len <= Value.max_parts) {
1652 vi.setParts(isel, @intCast(full_len));
1653 for (0..@intCast(full_len)) |part_i| {
1654 _ = vi.addPart(
1655 isel,
1656 @intCast(part_i * aligned_size),
1657 child_size,
1658 child_ty,
1659 );
1660 }
1661 } else {
1662 // Construct a tree with minimum nodes and depth
1663 // Minimum number of direct/indirect intermediate nodes to contain full_len leaf nodes
1664 const min_intermediate_nodes = (std.math.divCeil(u64, full_len - 1, Value.max_parts - 1) catch unreachable) - 1;
1665 assert(min_intermediate_nodes >= 1);
1666 // Number of direct intermediate children
1667 const intermediate_children = @min(Value.max_parts, min_intermediate_nodes);
1668 // Number of direct leaf children
1669 const leaf_children = @as(u64, Value.max_parts) - intermediate_children;
1670 // Number of indirect leaf children
1671 const indirect_leaf_children = full_len - leaf_children;
1672 // Length of each intermediate children
1673 const group_len = indirect_leaf_children / intermediate_children;
1674 const group_tail = indirect_leaf_children % intermediate_children;
1675 const tail_group_len = group_len + group_tail;
1676 const group_size = group_len * child_size;
1677 const tail_group_size = tail_group_len * child_size;
1678 const group_aligned_size = group_len * aligned_size;
1679 const group_ty: ZigType = if (intermediate_children == 1) undefined else try isel.pt.arrayType(.{
1680 .child = child_ty.ip_index,
1681 .len = group_len,
1682 });
1683 const tail_group_ty = if (array_type.sentinel == .none) try isel.pt.arrayType(.{
1684 .child = child_ty.ip_index,
1685 .len = tail_group_len,
1686 }) else try isel.pt.arrayType(.{
1687 .child = child_ty.ip_index,
1688 .len = tail_group_len - 1,
1689 .sentinel = array_type.sentinel,
1690 });
1691
1692 vi.setParts(isel, Value.max_parts);
1693 for (0..@intCast(leaf_children)) |part_i| {
1694 _ = vi.addPart(
1695 isel,
1696 @intCast(part_i * aligned_size),
1697 child_size,
1698 child_ty,
1699 );
1700 }
1701 const leaf_offset = leaf_children * aligned_size;
1702 for (0..@intCast(intermediate_children - 1)) |part_i| {
1703 _ = vi.addPart(
1704 isel,
1705 @intCast(leaf_offset + (part_i * group_aligned_size)),
1706 group_size,
1707 group_ty,
1708 );
1709 }
1710 _ = vi.addPart(
1711 isel,
1712 @intCast(leaf_offset + ((intermediate_children - 1) * group_aligned_size)),
1713 tail_group_size,
1714 tail_group_ty,
1715 );
1716 }
1717 },
1718 .anyframe_type => unreachable,
1719 .error_union_type => |error_union_type| {
1720 const payload_ty: ZigType = .fromInterned(error_union_type.payload_type);
1721 const error_set_offset = codegen.errUnionErrorOffset(payload_ty, zcu);
1722 const payload_offset = codegen.errUnionPayloadOffset(payload_ty, zcu);
1723
1724 var fields: [2]SplitStructField = undefined;
1725 var part_len: usize = 0;
1726 for (0..2) |field_index| {
1727 const field_name: enum { error_set, payload } = switch (field_index) {
1728 0 => if (error_set_offset < payload_offset) .error_set else .payload,
1729 1 => if (error_set_offset < payload_offset) .payload else .error_set,
1730 else => unreachable,
1731 };
1732 const field_ty: ZigType, const field_begin = switch (field_name) {
1733 .error_set => .{ .fromInterned(error_union_type.error_set_type), error_set_offset },
1734 .payload => .{ payload_ty, payload_offset },
1735 };
1736 const field_size = field_ty.abiSize(zcu);
1737 if (field_size == 0) continue;
1738
1739 fields[part_len] = .{ .offset = field_begin, .size = field_size };
1740 part_len += 1;
1741 }
1742
1743 try vi.splitStruct(isel, fields[0..part_len], .{
1744 .ty_size = vi.size(isel),
1745 .ty_alignment = vi.alignment(isel),
1746 .combine = false,
1747 });
1748 },
1749 .simple_type => |simple_type| switch (simple_type) {
1750 .f16, .f32, .f64, .f128, .c_longdouble => return isel.fail("Value.FieldPartIterator.next({f})", .{isel.fmtType(ty)}),
1751 .f80 => continue :type_key .{ .int_type = .{ .signedness = .unsigned, .bits = 80 } },
1752 .usize,
1753 .isize,
1754 .c_char,
1755 .c_short,
1756 .c_ushort,
1757 .c_int,
1758 .c_uint,
1759 .c_long,
1760 .c_ulong,
1761 .c_longlong,
1762 .c_ulonglong,
1763 => continue :type_key .{ .int_type = ty.intInfo(zcu) },
1764 .anyopaque,
1765 .void,
1766 .type,
1767 .comptime_int,
1768 .comptime_float,
1769 .noreturn,
1770 .null,
1771 .undefined,
1772 .enum_literal,
1773 .adhoc_inferred_error_set,
1774 .generic_poison,
1775 => unreachable,
1776 .bool => continue :type_key .{ .int_type = .{ .signedness = .unsigned, .bits = 1 } },
1777 .anyerror => continue :type_key .{ .int_type = .{
1778 .signedness = .unsigned,
1779 .bits = zcu.errorSetBits(),
1780 } },
1781 },
1782 .struct_type => {
1783 const loaded_struct = ip.loadStructType(ty.toIntern());
1784 switch (loaded_struct.layout) {
1785 .auto, .@"extern" => {},
1786 .@"packed" => {
1787 ty = .fromInterned(loaded_struct.packed_backing_int_type);
1788 continue :type_key ip.indexToKey(loaded_struct.packed_backing_int_type);
1789 },
1790 }
1791
1792 var field_end: u64 = 0;
1793 var field_it = loaded_struct.iterateRuntimeOrder(ip);
1794 var fields: []SplitStructField = try zcu.gpa.alloc(SplitStructField, loaded_struct.field_types.len);
1795 defer zcu.gpa.free(fields);
1796 var part_len: usize = 0;
1797 while (field_it.next()) |field_index| {
1798 const field_ty: ZigType = .fromInterned(loaded_struct.field_types.get(ip)[field_index]);
1799 const field_begin = switch (loaded_struct.field_aligns.getOrNone(ip, field_index)) {
1800 .none => field_ty.abiAlignment(zcu),
1801 else => |field_align| field_align,
1802 }.forward(field_end);
1803 const field_size = field_ty.abiSize(zcu);
1804 if (field_size == 0) continue;
1805 field_end = field_begin + field_size;
1806
1807 fields[part_len] = .{ .offset = field_begin, .size = field_size, .ty = field_ty };
1808 part_len += 1;
1809 }
1810
1811 try vi.splitStruct(isel, fields[0..part_len], .{
1812 .ty_size = vi.size(isel),
1813 .ty_alignment = vi.alignment(isel),
1814 .combine = true,
1815 });
1816 },
1817 .tuple_type => |tuple_type| {
1818 var field_end: u64 = 0;
1819 var fields: []SplitStructField = try zcu.gpa.alloc(SplitStructField, tuple_type.types.len);
1820 defer zcu.gpa.free(fields);
1821 var part_len: usize = 0;
1822
1823 for (tuple_type.types.get(ip), tuple_type.values.get(ip)) |field_type, field_value| {
1824 if (field_value != .none) continue;
1825 const field_ty: ZigType = .fromInterned(field_type);
1826 const field_begin = field_ty.abiAlignment(zcu).forward(field_end);
1827 const field_size = field_ty.abiSize(zcu);
1828 if (field_size == 0) continue;
1829 field_end = field_begin + field_size;
1830
1831 fields[part_len] = .{ .offset = field_begin, .size = field_size, .ty = field_ty };
1832 part_len += 1;
1833 }
1834
1835 try vi.splitStruct(isel, fields[0..part_len], .{
1836 .ty_size = vi.size(isel),
1837 .ty_alignment = vi.alignment(isel),
1838 .combine = true,
1839 });
1840 },
1841 .union_type => {
1842 const loaded_union = ip.loadUnionType(ty.toIntern());
1843 switch (loaded_union.layout) {
1844 .auto, .@"extern" => {},
1845 .@"packed" => continue :type_key .{ .int_type = .{
1846 .signedness = .unsigned,
1847 .bits = @intCast(ty.bitSize(zcu)),
1848 } },
1849 }
1850
1851 const union_layout = ZigType.getUnionLayout(loaded_union, zcu);
1852 const tag_offset = union_layout.tagOffset();
1853 const payload_offset = union_layout.payloadOffset();
1854
1855 var field_end: u64 = 0;
1856 var fields: [2]SplitStructField = undefined;
1857 var part_len: usize = 0;
1858
1859 for (0..2) |field_index| {
1860 const field_name: enum { tag, payload } = switch (field_index) {
1861 0 => if (tag_offset < payload_offset) .tag else .payload,
1862 1 => if (tag_offset < payload_offset) .payload else .tag,
1863 else => unreachable,
1864 };
1865 const field_size, const field_begin = switch (field_name) {
1866 .tag => .{ union_layout.tag_size, tag_offset },
1867 .payload => .{ union_layout.payload_size, payload_offset },
1868 };
1869 if (field_size == 0) continue;
1870 field_end = field_begin + field_size;
1871
1872 fields[part_len] = .{ .offset = field_begin, .size = field_size };
1873 part_len += 1;
1874 }
1875
1876 try vi.splitStruct(isel, fields[0..part_len], .{
1877 .ty_size = vi.size(isel),
1878 .ty_alignment = vi.alignment(isel),
1879 .combine = false,
1880 });
1881 },
1882 .opaque_type, .func_type => continue :type_key .{ .simple_type = .anyopaque },
1883 .enum_type => continue :type_key ip.indexToKey(ip.loadEnumType(ty.toIntern()).int_tag_type),
1884 .error_set_type,
1885 .inferred_error_set_type,
1886 => continue :type_key .{ .simple_type = .anyerror },
1887 }
1888
1889 if (force and value.flags.parts_len_minus_one == 0) try vi.splitBlindly(isel);
1890 }
1891
1892 pub fn splitBlindly(vi: Value.Index, isel: *Select) !void {
1893 const value = vi.get(isel);
1894 value.flags.splitted = true;
1895 if (value.flags.parts_len_minus_one != 0) return;
1896
1897 return isel.fail("splitBlindly unimplemented", .{});
1898 }
1899
1900 const SplitStructField = struct {
1901 offset: u64,
1902 size: u64,
1903 ty: ZigType = .void,
1904 };
1905
1906 const SplitStructOpts = struct {
1907 ty_size: u64,
1908 ty_alignment: InternPool.Alignment,
1909 combine: bool,
1910 };
1911
1912 fn splitStruct(vi: Value.Index, isel: *Select, fields: []SplitStructField, opts: SplitStructOpts) !void {
1913 const min_part_log2_stride: u5 = switch (opts.ty_size) {
1914 0...4 => 0,
1915 5...8 => 2,
1916 9...16 => 3,
1917 else => 4,
1918 };
1919 if (fields.len > Value.max_parts and
1920 (std.math.divCeil(u64, opts.ty_size, @as(u64, 1) << min_part_log2_stride) catch unreachable) > Value.max_parts)
1921 {
1922 // fast path for structs with too many parts
1923 return;
1924 }
1925
1926 // split parts with combination
1927 const Part = struct {
1928 offset: u64,
1929 size: u64,
1930 ty: ZigType,
1931 vi: Value.Index,
1932 subparts: Value.PartsLen,
1933 };
1934 var new_parts: [Value.max_parts]Part = undefined;
1935 var parts_len: Value.PartsLen = 0;
1936 var field_end: u64 = 0;
1937 for (fields) |*struct_field| {
1938 const field_ty = struct_field.ty;
1939 const field_begin = struct_field.offset;
1940 const field_size = struct_field.size;
1941 field_end = field_begin + field_size;
1942 if (opts.combine and parts_len > 0) combine: {
1943 const prev_part = &new_parts[parts_len - 1];
1944 const combined_size = field_end - prev_part.offset;
1945 if (combined_size > @as(u64, 1) << @min(
1946 min_part_log2_stride,
1947 opts.ty_alignment.toLog2Units(),
1948 @ctz(prev_part.offset),
1949 )) break :combine;
1950 prev_part.size = combined_size;
1951 prev_part.ty = undefined;
1952 prev_part.subparts += 1;
1953 continue;
1954 }
1955 if (parts_len == Value.max_parts) return;
1956 new_parts[parts_len] = .{
1957 .offset = field_begin,
1958 .size = field_size,
1959 .ty = field_ty,
1960 .vi = undefined,
1961 .subparts = 1,
1962 };
1963 parts_len += 1;
1964 }
1965 if (parts_len <= 1) return;
1966 vi.setParts(isel, parts_len);
1967 for (new_parts[0..parts_len]) |*part| {
1968 part.vi = vi.addPart(
1969 isel,
1970 part.offset,
1971 part.size,
1972 if (part.subparts == 1 and part.ty.ip_index != .void_type) part.ty else null,
1973 );
1974 }
1975 const last_part = new_parts[parts_len - 1];
1976 const remaining_size = opts.ty_size - last_part.offset - last_part.size;
1977 if (remaining_size != 0)
1978 _ = vi.addPart(isel, last_part.offset, remaining_size, null);
1979
1980 // split combined parts
1981 var part_index: Value.PartsLen = 0;
1982 for (fields) |*struct_field| {
1983 const field_ty = struct_field.ty;
1984 const field_begin = struct_field.offset;
1985 const field_size = struct_field.size;
1986
1987 var new_part = &new_parts[part_index];
1988 while (new_part.offset + new_part.size <= field_begin) {
1989 part_index += 1;
1990 new_part = &new_parts[part_index];
1991 }
1992 if (new_part.subparts == 1) continue;
1993 if (!new_part.vi.hasParts(isel))
1994 new_part.vi.setParts(isel, new_part.subparts);
1995 _ = new_part.vi.addPart(
1996 isel,
1997 field_begin - new_part.offset,
1998 field_size,
1999 if (field_ty.ip_index != .void_type) field_ty else null,
2000 );
2001 }
2002 }
2003 };
2004
2005 pub const PartIterator = struct {
2006 vi: Value.Index,
2007 remaining: Value.PartsLen,
2008
2009 fn initOne(vi: Value.Index) PartIterator {
2010 return .{ .vi = vi, .remaining = 1 };
2011 }
2012
2013 pub fn next(it: *PartIterator) ?Value.Index {
2014 if (it.remaining == 0) return null;
2015 it.remaining -= 1;
2016 defer it.vi = @fromBackingInt(@backingInt(it.vi) + 1);
2017 return it.vi;
2018 }
2019
2020 pub fn peek(it: PartIterator) ?Value.Index {
2021 var it_mut = it;
2022 return it_mut.next();
2023 }
2024
2025 pub fn only(it: PartIterator) ?Value.Index {
2026 return if (it.remaining == 1) it.vi else null;
2027 }
2028 };
2029
2030 const Mat = struct {
2031 vi: Value.Index,
2032 /// Position of the materialized part
2033 offset: u64,
2034 /// Size of the materialized part
2035 size: u32,
2036 /// Expected live-in extension mode
2037 extension: Extension,
2038 /// Register are locked.
2039 location: Location,
2040 /// Whether the location stores the whole value or the materialized part
2041 full: bool,
2042
2043 comptime {
2044 if (!std.debug.runtime_safety) assert(@sizeOf(Mat) <= 32);
2045 }
2046
2047 const Error = error{ OutOfMemory, AlreadyReported };
2048
2049 pub fn ra(mat: Value.Mat) Register.Alias {
2050 return mat.location.register;
2051 }
2052
2053 pub fn reg(mat: Value.Mat) Register {
2054 return mat.location.register.reg;
2055 }
2056
2057 pub fn loc(mat: Value.Mat) Location {
2058 return switch (mat.location) {
2059 .register => |loc_ra| .{ .register = loc_ra },
2060 .stack_slot => |stack_slot| if (mat.full)
2061 .{ .stack_slot = stack_slot.withOffset(@intCast(mat.offset)) }
2062 else
2063 .{ .stack_slot = stack_slot },
2064 };
2065 }
2066
2067 fn finish(mat: Value.Mat, isel: *Select) Mat.Error!void {
2068 const vi = mat.vi;
2069 const value = vi.get(isel);
2070 tracking_log.debug("{f}[{d}..{d}] <- {f} (mat finish)", .{ vi, mat.offset, mat.offset + mat.size - 1, mat.loc() });
2071
2072 if (mat.location.asRegister()) |mat_reg|
2073 isel.freeReg(mat_reg);
2074
2075 const offset_from_root, const root_vi = vi.valueRoot(isel);
2076 switch (root_vi.parent(isel)) {
2077 .none => {
2078 // Try to set the location as expected
2079 if (mat.full and vi.location(isel) == null) {
2080 switch (value.flags.location_tag) {
2081 .extreme => unreachable,
2082 .small => {
2083 vi.setSmallLocation(isel, mat.location);
2084 vi.setExtension(isel, mat.extension);
2085 if (mat.location.asRegister()) |loc_reg|
2086 isel.live_registers.set(loc_reg, vi);
2087 return;
2088 },
2089 .large => switch (mat.location) {
2090 .stack_slot => |stack_slot| {
2091 value.location_payload.large.stack_slot = stack_slot;
2092 try vi.reextendAdvanced(isel, vi.bitSize(isel), mat.extension, vi.extension(isel));
2093 return;
2094 },
2095 else => {},
2096 },
2097 }
2098 }
2099
2100 // Initialize a location and copy
2101 if (vi.location(isel) == null) {
2102 switch (value.flags.location_tag) {
2103 .extreme => unreachable,
2104 .small => {
2105 const new_ra = (try vi.allocRegister(isel)).?;
2106 vi.setSmallLocation(isel, .{ .register = new_ra });
2107 isel.live_registers.set(new_ra.reg, vi);
2108 },
2109 .large => value.location_payload.large.stack_slot = vi.allocStackSlot(isel),
2110 }
2111 }
2112 switch (value.flags.location_tag) {
2113 .extreme => unreachable,
2114 .small => {},
2115 .large => {
2116 try vi.reextendAdvanced(isel, vi.bitSize(isel), mat.extension, vi.extension(isel));
2117 },
2118 }
2119 const vi_loc = vi.location(isel).?;
2120 const maybe_loc_reg = vi_loc.asRegister();
2121 if (maybe_loc_reg) |loc_reg| {
2122 const loc_live = isel.live_registers.getPtr(loc_reg);
2123 assert(loc_live.* == vi);
2124 loc_live.* = .allocating;
2125 }
2126 vi_loc.markRegWritten(isel);
2127 try isel.moveLoc(
2128 mat.location,
2129 if (mat.full) mat.offset else 0,
2130 vi_loc,
2131 mat.offset,
2132 mat.size,
2133 .preserved,
2134 );
2135 if (maybe_loc_reg) |loc_reg| {
2136 const loc_live = isel.live_registers.getPtr(loc_reg);
2137 assert(loc_live.* == .allocating);
2138 loc_live.* = vi;
2139 }
2140 },
2141 .value => unreachable,
2142 .address => |addr_vi| {
2143 try vi.reextendAdvanced(isel, vi.bitSize(isel), mat.extension, vi.extension(isel));
2144
2145 // reextend
2146 reextend: {
2147 const dst_ext = vi.extension(isel);
2148 const src_ext = mat.extension;
2149 if (dst_ext == src_ext or dst_ext == .garbage) break :reextend;
2150
2151 const bit_size = vi.bitSize(isel);
2152 if (bit_size == 0) break :reextend;
2153
2154 switch (mat.location) {
2155 .register => |loc_ra| {
2156 const offset_fixup = if (mat.full) 0 else mat.offset;
2157 const reg_bits = loc_ra.mod.bitSize(isel.target);
2158 const unused_bits = reg_bits - @min(bit_size - (offset_fixup * 8), reg_bits);
2159 try isel.fillUnusedBits(loc_ra.reg, loc_ra.reg, dst_ext, src_ext, @intCast(unused_bits));
2160 },
2161 .stack_slot => |stack| {
2162 const total_size = vi.size(isel);
2163 const unused_bits = (total_size * 8) - bit_size;
2164 const reg_mod: Register.Modifier = if (vi.isSmall(isel)) vi.hintModifier(isel) else .integer;
2165 const reg_class = reg_mod.class();
2166 const reg_size = reg_mod.byteSize(isel.target);
2167 const reg_alignment: InternPool.Alignment = .fromByteUnits(reg_size);
2168 const base_offset = @as(i65, stack.offset) - (if (mat.full) 0 else mat.offset);
2169
2170 var offset = reg_alignment.backward(bit_size / 8);
2171 const tmp_reg = try isel.allocRegForWrite(reg_class);
2172 defer isel.freeReg(tmp_reg);
2173 while (offset < total_size) {
2174 const part_size = @min(reg_size, total_size - offset);
2175 defer offset += part_size;
2176
2177 try isel.storeReg(tmp_reg, part_size, stack.base, base_offset + offset);
2178 try isel.fillUnusedBits(tmp_reg, tmp_reg, dst_ext, src_ext, @intCast(unused_bits));
2179 try isel.loadReg(tmp_reg, part_size, vi.extension(isel).signednessForLoad(), stack.base, base_offset + offset);
2180 }
2181 },
2182 }
2183 }
2184
2185 const addr_mat = try addr_vi.matIntRegZeroExt(isel);
2186 assert(addr_mat.ra().mod == .integer);
2187 try isel.moveLoc(
2188 mat.location,
2189 if (mat.full) mat.offset else 0,
2190 .{ .stack_slot = .{ .base = addr_mat.reg(), .offset = 0 } },
2191 offset_from_root + mat.offset,
2192 mat.size,
2193 .none,
2194 );
2195 try addr_mat.finish(isel);
2196 },
2197 .constant => |constant| {
2198 const mat_loc = mat.loc();
2199 mat_loc.markRegWritten(isel);
2200 try isel.moveConstant(mat_loc, constant, offset_from_root + mat.offset, mat.size);
2201 },
2202 }
2203 }
2204 };
2205
2206 /// DFS iterator over a sub-tree.
2207 const Walk = struct {
2208 isel: *Select,
2209 root_vi: Value.Index,
2210 next_vi: Value.Index,
2211 opts: Options,
2212
2213 const Options = packed struct {
2214 /// Reversed order
2215 reverse: bool = true,
2216 /// Whether to include root nodes
2217 root: bool = true,
2218 /// Whether to include intermdiate nodes
2219 /// (i.e. nodes that are not leaf vertexes)
2220 intermdiate: bool = true,
2221 /// Whether to include leaf vertexes
2222 leaves: bool = true,
2223 };
2224
2225 pub fn next(it: *Walk) ?Value.Index {
2226 const isel = it.isel;
2227 const opts = it.opts;
2228 while (it.next_vi != .free) {
2229 const node_vi = it.next_vi;
2230
2231 // find next node
2232 next_node: {
2233 // go to the first child
2234 if (node_vi.hasParts(isel)) {
2235 it.next_vi = if (!opts.reverse)
2236 node_vi.get(isel).parts
2237 else last_child: {
2238 const node_value = node_vi.get(isel);
2239 break :last_child @fromBackingInt(@backingInt(node_value.parts) + node_value.flags.parts_len_minus_one);
2240 };
2241 break :next_node;
2242 }
2243 if (node_vi.parentValue(isel) != null) {
2244 var iter_vi = node_vi;
2245 while (true) {
2246 // go to the next sibling
2247 const parent_vi = iter_vi.get(isel).parent_payload.value;
2248 const parent_value = parent_vi.get(isel);
2249 if (!opts.reverse) {
2250 const last_sibling = @backingInt(parent_value.parts) + parent_value.flags.parts_len_minus_one;
2251 if (@backingInt(iter_vi) < last_sibling) {
2252 it.next_vi = @fromBackingInt(@backingInt(iter_vi) + 1);
2253 break :next_node;
2254 }
2255 } else {
2256 if (@backingInt(iter_vi) > @backingInt(parent_value.parts)) {
2257 it.next_vi = @fromBackingInt(@backingInt(iter_vi) - 1);
2258 break :next_node;
2259 }
2260 }
2261 // return to ancestor's sibling
2262 if (parent_value.flags.parent_tag == .value)
2263 iter_vi = parent_vi
2264 else
2265 break;
2266 }
2267 }
2268 it.next_vi = .free;
2269 }
2270
2271 // filter nodes
2272 if (!it.opts.root and node_vi == it.root_vi) continue;
2273 if (!it.opts.intermdiate and node_vi.hasParts(isel)) continue;
2274 if (!it.opts.leaves and !node_vi.hasParts(isel)) continue;
2275 return node_vi;
2276 }
2277 return null;
2278 }
2279
2280 pub fn skipChildren(it: *Walk, current_vi: Value.Index) void {
2281 const isel = it.isel;
2282 const current_value = current_vi.get(isel);
2283 if (current_value.flags.parts_len_minus_one != 0) {
2284 const last_part = @backingInt(current_value.parts) + current_value.flags.parts_len_minus_one;
2285 it.next_vi = @fromBackingInt(last_part);
2286 _ = it.next();
2287 }
2288 }
2289
2290 pub fn peek(it: Walk) ?Value.Index {
2291 var it_mut = it;
2292 return it_mut.next();
2293 }
2294 };
2295};
2296
2297fn fail(isel: *Select, comptime format: []const u8, args: anytype) error{ OutOfMemory, AlreadyReported } {
2298 @branchHint(.cold);
2299 wip_mir_log.debug("codegen error: " ++ format, args);
2300 return isel.pt.zcu.codegenFail(isel.nav_index, format, args);
2301}
2302
2303fn failUnimplemented(isel: *Select, comptime format: []const u8, args: anytype) error{ OutOfMemory, AlreadyReported }!void {
2304 @branchHint(.cold);
2305 if (debug_trap_unimplemented_code) {
2306 const gpa = isel.pt.zcu.gpa;
2307
2308 const msg = try std.fmt.allocPrintSentinel(gpa, format, args, 0);
2309 defer gpa.free(msg);
2310 wip_mir_log.err("{s}", .{msg});
2311 try isel.emit(.@"break"(0xaa));
2312 try isel.moveDebugString(.r22, msg);
2313 } else return isel.fail(format, args);
2314}
2315
2316fn moveDebugString(isel: *Select, reg: Register, msg: [:0]const u8) error{ OutOfMemory, AlreadyReported }!void {
2317 @branchHint(.cold);
2318 assert(debug_trap_unimplemented_code);
2319
2320 const pt = isel.pt;
2321 const zcu = pt.zcu;
2322 const ip = &zcu.intern_pool;
2323 const gpa = zcu.gpa;
2324
2325 const msg_ty = try pt.arrayType(.{
2326 .len = msg.len,
2327 .child = .u8_type,
2328 .sentinel = .zero_u8,
2329 });
2330 const msg_str = try ip.getOrPutString(gpa, zcu.comp.io, pt.tid, msg, .maybe_embedded_nulls);
2331 const msg_val = try pt.intern(.{ .aggregate = .{
2332 .ty = msg_ty.ip_index,
2333 .storage = .{ .bytes = msg_str },
2334 } });
2335 const msg_ptr = try pt.intern(.{ .ptr = .{
2336 .ty = .manyptr_const_u8_sentinel_0_type,
2337 .base_addr = .{ .uav = .{
2338 .val = msg_val,
2339 .orig_ty = .manyptr_const_u8_sentinel_0_type,
2340 } },
2341 .byte_offset = 0,
2342 } });
2343 try isel.moveConstant(
2344 .{ .register = .{ .reg = reg, .mod = .integer } },
2345 .fromInterned(msg_ptr),
2346 0,
2347 isel.gprSize(),
2348 );
2349}
2350
2351pub fn analyze(isel: *Select, air_body: []const Air.Inst.Index) !void {
2352 const zcu = isel.pt.zcu;
2353 const ip = &zcu.intern_pool;
2354 const gpa = zcu.gpa;
2355 const air_tags = isel.air.instructions.items(.tag);
2356 const air_data = isel.air.instructions.items(.data);
2357 const initial_def_order_len = isel.def_order.count();
2358
2359 for (air_body) |air_inst_index| {
2360 switch (air_tags[@backingInt(air_inst_index)]) {
2361 else => |air_tag| return isel.fail("unimplemented analyze for {t}", .{air_tag}),
2362 .arg,
2363 .ret_addr,
2364 .frame_addr,
2365 .err_return_trace,
2366 .save_err_return_trace_index,
2367 .runtime_nav_ptr,
2368 .c_va_start,
2369 => {
2370 try isel.def_order.putNoClobber(gpa, air_inst_index, {});
2371 },
2372 .add,
2373 .add_safe,
2374 .add_optimized,
2375 .add_wrap,
2376 .add_sat,
2377 .sub,
2378 .sub_safe,
2379 .sub_optimized,
2380 .sub_wrap,
2381 .sub_sat,
2382 .mul,
2383 .mul_safe,
2384 .mul_optimized,
2385 .mul_wrap,
2386 .mul_sat,
2387 .div_float,
2388 .div_float_optimized,
2389 .div_trunc,
2390 .div_trunc_optimized,
2391 .div_floor,
2392 .div_floor_optimized,
2393 .div_exact,
2394 .div_exact_optimized,
2395 .rem,
2396 .rem_optimized,
2397 .mod,
2398 .mod_optimized,
2399 .max,
2400 .min,
2401 .bit_and,
2402 .bit_or,
2403 .shr,
2404 .shr_exact,
2405 .shl,
2406 .shl_exact,
2407 .shl_sat,
2408 .xor,
2409 .cmp_lt,
2410 .cmp_lt_optimized,
2411 .cmp_lte,
2412 .cmp_lte_optimized,
2413 .cmp_eq,
2414 .cmp_eq_optimized,
2415 .cmp_gte,
2416 .cmp_gte_optimized,
2417 .cmp_gt,
2418 .cmp_gt_optimized,
2419 .cmp_neq,
2420 .cmp_neq_optimized,
2421 .array_elem_val,
2422 .slice_elem_val,
2423 .ptr_elem_val,
2424 => {
2425 const bin_op = air_data[@backingInt(air_inst_index)].bin_op;
2426
2427 try isel.analyzeUse(bin_op.lhs);
2428 try isel.analyzeUse(bin_op.rhs);
2429 try isel.def_order.putNoClobber(gpa, air_inst_index, {});
2430 },
2431 .ptr_add,
2432 .ptr_sub,
2433 .add_with_overflow,
2434 .sub_with_overflow,
2435 .mul_with_overflow,
2436 .shl_with_overflow,
2437 .slice,
2438 .slice_elem_ptr,
2439 .ptr_elem_ptr,
2440 => {
2441 const ty_pl = air_data[@backingInt(air_inst_index)].ty_pl;
2442 const bin_op = isel.air.extraData(Air.Bin, ty_pl.payload).data;
2443
2444 try isel.analyzeUse(bin_op.lhs);
2445 try isel.analyzeUse(bin_op.rhs);
2446 try isel.def_order.putNoClobber(gpa, air_inst_index, {});
2447 },
2448 .alloc => {
2449 const ty = air_data[@backingInt(air_inst_index)].ty;
2450
2451 isel.stack_align = isel.stack_align.maxStrict(ty.ptrAlignment(zcu));
2452 try isel.def_order.putNoClobber(gpa, air_inst_index, {});
2453 },
2454 .inferred_alloc,
2455 .inferred_alloc_comptime,
2456 .wasm_memory_size,
2457 .wasm_memory_grow,
2458 .work_item_id,
2459 .work_group_size,
2460 .work_group_id,
2461 => unreachable,
2462 .ret, .ret_safe, .ret_load => {
2463 const un_op = air_data[@backingInt(air_inst_index)].un_op;
2464 isel.returns = true;
2465
2466 assert(isel.active_blocks.keys()[0] == Block.main);
2467
2468 try isel.analyzeUse(un_op);
2469 },
2470 .ret_ptr => {
2471 const ty = air_data[@backingInt(air_inst_index)].ty;
2472
2473 if (isel.live_values.get(Block.main)) |ret_vi| {
2474 switch (ret_vi.parent(isel)) {
2475 .none => isel.stack_align = isel.stack_align.maxStrict(ty.ptrAlignment(zcu)),
2476 .value, .constant => unreachable,
2477 .address => |address_vi| try isel.live_values.putNoClobber(gpa, air_inst_index, address_vi.ref(isel)),
2478 }
2479 if (ret_vi.stackSlot(isel) != null)
2480 isel.stack_align = isel.stack_align.maxStrict(ty.ptrAlignment(zcu));
2481 }
2482 try isel.def_order.putNoClobber(gpa, air_inst_index, {});
2483 },
2484 .assembly => {
2485 const ty_pl = air_data[@backingInt(air_inst_index)].ty_pl;
2486 const extra = isel.air.extraData(Air.Asm, ty_pl.payload);
2487 const operands: []const Air.Inst.Ref = @ptrCast(isel.air.extra.items[extra.end..][0 .. extra.data.flags.outputs_len + extra.data.inputs_len]);
2488
2489 for (operands) |operand| if (operand != .none) try isel.analyzeUse(operand);
2490 if (ty_pl.ty != .void_type) try isel.def_order.putNoClobber(gpa, air_inst_index, {});
2491 },
2492 .not,
2493 .clz,
2494 .ctz,
2495 .popcount,
2496 .byte_swap,
2497 .bit_reverse,
2498 .abs,
2499 .load,
2500 .fptrunc,
2501 .fpext,
2502 .int_cast,
2503 .int_cast_safe,
2504 .trunc,
2505 .optional_payload,
2506 .optional_payload_ptr,
2507 .optional_payload_ptr_set,
2508 .wrap_optional,
2509 .unwrap_errunion_payload,
2510 .unwrap_errunion_err,
2511 .unwrap_errunion_payload_ptr,
2512 .unwrap_errunion_err_ptr,
2513 .errunion_payload_ptr_set,
2514 .wrap_errunion_payload,
2515 .wrap_errunion_err,
2516 .struct_field_ptr_index_0,
2517 .struct_field_ptr_index_1,
2518 .struct_field_ptr_index_2,
2519 .struct_field_ptr_index_3,
2520 .get_union_tag,
2521 .ptr_slice_len_ptr,
2522 .ptr_slice_ptr_ptr,
2523 .array_to_slice,
2524 .int_from_float,
2525 .int_from_float_optimized,
2526 .int_from_float_safe,
2527 .int_from_float_optimized_safe,
2528 .float_from_int,
2529 .splat,
2530 .error_set_has_value,
2531 .addrspace_cast,
2532 .c_va_arg,
2533 .c_va_copy,
2534 .bit_cast,
2535 .ptr_cast,
2536 .ptr_from_int,
2537 .int_from_ptr,
2538 .error_cast,
2539 .error_from_int,
2540 .int_from_error,
2541 .union_from_enum,
2542 => {
2543 const ty_op = air_data[@backingInt(air_inst_index)].ty_op;
2544
2545 try isel.analyzeUse(ty_op.operand);
2546 try isel.def_order.putNoClobber(gpa, air_inst_index, {});
2547 },
2548 .loop => {
2549 const ty_pl = air_data[@backingInt(air_inst_index)].ty_pl;
2550 const extra = isel.air.extraData(Air.Block, ty_pl.payload);
2551
2552 try isel.active_loops.append(gpa, @fromBackingInt(@intCast(isel.loops.count())));
2553 try isel.loops.putNoClobber(gpa, air_inst_index, .{
2554 .def_order = @intCast(isel.def_order.count()),
2555 .outer_live = 0,
2556 .repeat_list = undefined,
2557 });
2558 try isel.analyze(@ptrCast(isel.air.extra.items[extra.end..][0..extra.data.body_len]));
2559 assert(isel.active_loops.pop().?.inst(isel) == air_inst_index);
2560 },
2561 .repeat, .trap, .unreach => {},
2562 .br => {
2563 const br = air_data[@backingInt(air_inst_index)].br;
2564 try isel.analyzeUse(br.operand);
2565 },
2566 .breakpoint, .dbg_stmt, .dbg_empty_stmt, .dbg_var_ptr, .dbg_var_val, .dbg_arg_inline, .c_va_end => {},
2567 .sqrt,
2568 .sin,
2569 .cos,
2570 .tan,
2571 .exp,
2572 .exp2,
2573 .log,
2574 .log2,
2575 .log10,
2576 .floor,
2577 .ceil,
2578 .round,
2579 .trunc_float,
2580 .neg,
2581 .neg_optimized,
2582 .is_null,
2583 .is_non_null,
2584 .is_null_ptr,
2585 .is_non_null_ptr,
2586 .is_err,
2587 .is_non_err,
2588 .is_err_ptr,
2589 .is_non_err_ptr,
2590 .is_named_enum_value,
2591 .tag_name,
2592 .error_name,
2593 => {
2594 const un_op = air_data[@backingInt(air_inst_index)].un_op;
2595
2596 try isel.analyzeUse(un_op);
2597 try isel.def_order.putNoClobber(gpa, air_inst_index, {});
2598 },
2599 .cmp_vector, .cmp_vector_optimized => {
2600 const ty_pl = air_data[@backingInt(air_inst_index)].ty_pl;
2601 const extra = isel.air.extraData(Air.VectorCmp, ty_pl.payload).data;
2602
2603 try isel.analyzeUse(extra.lhs);
2604 try isel.analyzeUse(extra.rhs);
2605 try isel.def_order.putNoClobber(gpa, air_inst_index, {});
2606 },
2607 .store,
2608 .store_safe,
2609 .set_union_tag,
2610 .memset,
2611 .memset_safe,
2612 .memcpy,
2613 .memmove,
2614 .atomic_store_unordered,
2615 .atomic_store_monotonic,
2616 .atomic_store_release,
2617 .atomic_store_seq_cst,
2618 => {
2619 const bin_op = air_data[@backingInt(air_inst_index)].bin_op;
2620
2621 try isel.analyzeUse(bin_op.lhs);
2622 try isel.analyzeUse(bin_op.rhs);
2623 },
2624 .struct_field_ptr, .agg_field_val => {
2625 const ty_pl = air_data[@backingInt(air_inst_index)].ty_pl;
2626 const extra = isel.air.extraData(Air.StructField, ty_pl.payload).data;
2627
2628 try isel.analyzeUse(extra.struct_operand);
2629 try isel.def_order.putNoClobber(gpa, air_inst_index, {});
2630 },
2631 .aggregate_init => {
2632 const ty_pl = air_data[@backingInt(air_inst_index)].ty_pl;
2633 const elements: []const Air.Inst.Ref = @ptrCast(isel.air.extra.items[ty_pl.payload..][0..@intCast(ty_pl.ty.toType().arrayLen(zcu))]);
2634
2635 for (elements) |element| try isel.analyzeUse(element);
2636 try isel.def_order.putNoClobber(gpa, air_inst_index, {});
2637 },
2638 .union_init => {
2639 const ty_pl = air_data[@backingInt(air_inst_index)].ty_pl;
2640 const extra = isel.air.extraData(Air.UnionInit, ty_pl.payload).data;
2641
2642 try isel.analyzeUse(extra.init);
2643 try isel.def_order.putNoClobber(gpa, air_inst_index, {});
2644 },
2645 .prefetch => {
2646 const prefetch = air_data[@backingInt(air_inst_index)].prefetch;
2647 try isel.analyzeUse(prefetch.ptr);
2648 },
2649 .field_parent_ptr => {
2650 const ty_pl = air_data[@backingInt(air_inst_index)].ty_pl;
2651 const extra = isel.air.extraData(Air.FieldParentPtr, ty_pl.payload).data;
2652
2653 try isel.analyzeUse(extra.field_ptr);
2654 try isel.def_order.putNoClobber(gpa, air_inst_index, {});
2655 },
2656 .set_err_return_trace => {
2657 const un_op = air_data[@backingInt(air_inst_index)].un_op;
2658 try isel.analyzeUse(un_op);
2659 },
2660 inline .block, .dbg_inline_block => |air_tag| {
2661 const ty_pl = air_data[@backingInt(air_inst_index)].ty_pl;
2662 const extra = isel.air.extraData(switch (air_tag) {
2663 else => comptime unreachable,
2664 .block => Air.Block,
2665 .dbg_inline_block => Air.DbgInlineBlock,
2666 }, ty_pl.payload);
2667 const result_ty = ty_pl.ty.toInterned().?;
2668
2669 if (result_ty == .noreturn_type) {
2670 try isel.analyze(@ptrCast(isel.air.extra.items[extra.end..][0..extra.data.body_len]));
2671 break;
2672 }
2673
2674 assert(!(try isel.active_blocks.getOrPut(gpa, air_inst_index)).found_existing);
2675 try isel.analyze(@ptrCast(isel.air.extra.items[extra.end..][0..extra.data.body_len]));
2676 const block_entry = isel.active_blocks.pop().?;
2677 assert(block_entry.key == air_inst_index);
2678
2679 if (result_ty != .void_type) try isel.def_order.putNoClobber(gpa, air_inst_index, {});
2680 },
2681 .call,
2682 .call_always_tail,
2683 .call_never_tail,
2684 .call_never_inline,
2685 => {
2686 const pl_op = air_data[@backingInt(air_inst_index)].pl_op;
2687 const extra = isel.air.extraData(Air.Call, pl_op.payload);
2688 const args: []const Air.Inst.Ref = @ptrCast(isel.air.extra.items[extra.end..][0..extra.data.args_len]);
2689 isel.saved_registers.insert(.ra);
2690 const callee_ty = isel.air.typeOf(pl_op.operand, ip);
2691 const func_info = switch (ip.indexToKey(callee_ty.toIntern())) {
2692 else => unreachable,
2693 .func_type => |func_type| func_type,
2694 .ptr_type => |ptr_type| ip.indexToKey(ptr_type.child).func_type,
2695 };
2696
2697 try isel.analyzeUse(pl_op.operand);
2698 var cc_it: CallAbiIterator = .{ .isel = isel, .cc = &func_info.cc };
2699
2700 const ret_ty = isel.air.typeOfIndex(air_inst_index, ip);
2701 if (try cc_it.resolve(ret_ty, true)) |ret_vi| {
2702 tracking_log.debug("{f} <- %{d} (call return)", .{ ret_vi, @backingInt(air_inst_index) });
2703 switch (ret_vi.parent(isel)) {
2704 .none => {},
2705 .value, .constant => unreachable,
2706 .address => |address_vi| {
2707 defer address_vi.deref(isel);
2708 const ret_value = ret_vi.get(isel);
2709 ret_value.flags.parent_tag = .none;
2710 ret_value.parent_payload = .{ .none = {} };
2711 },
2712 }
2713 try isel.live_values.putNoClobber(gpa, air_inst_index, ret_vi);
2714
2715 try isel.def_order.putNoClobber(gpa, air_inst_index, {});
2716 }
2717
2718 for (args) |arg| {
2719 {
2720 const restore_values_len = isel.values.items.len;
2721 defer isel.values.shrinkRetainingCapacity(restore_values_len);
2722 defer isel.value_types.shrinkRetainingCapacity(restore_values_len);
2723
2724 const param_ty = isel.air.typeOf(arg, ip);
2725 const param_vi = try cc_it.resolve(param_ty, false) orelse continue;
2726 defer param_vi.deref(isel);
2727
2728 const passed_vi = switch (param_vi.parent(isel)) {
2729 .none => param_vi,
2730 .value, .constant => unreachable,
2731 .address => |address_vi| address_vi,
2732 };
2733 if (passed_vi.stackSlot(isel)) |stack_slot| {
2734 assert(stack_slot.base == Register.sp);
2735 isel.stack_size = @max(
2736 isel.stack_size,
2737 stack_slot.offset + @as(u24, @intCast(passed_vi.size(isel))),
2738 );
2739 }
2740 }
2741
2742 try isel.analyzeUse(arg);
2743 }
2744 },
2745 .cond_br => {
2746 const pl_op = air_data[@backingInt(air_inst_index)].pl_op;
2747 const extra = isel.air.extraData(Air.CondBr, pl_op.payload);
2748
2749 try isel.analyzeUse(pl_op.operand);
2750
2751 try isel.analyze(@ptrCast(isel.air.extra.items[extra.end..][0..extra.data.then_body_len]));
2752 try isel.analyze(@ptrCast(isel.air.extra.items[extra.end + extra.data.then_body_len ..][0..extra.data.else_body_len]));
2753 },
2754 .switch_br => {
2755 const switch_br = isel.air.unwrapSwitch(air_inst_index);
2756
2757 try isel.analyzeUse(switch_br.operand);
2758
2759 var cases_it = switch_br.iterateCases();
2760 while (cases_it.next()) |case| try isel.analyze(case.body);
2761 if (switch_br.else_body_len > 0) try isel.analyze(cases_it.elseBody());
2762 },
2763 .loop_switch_br => {
2764 const switch_br = isel.air.unwrapSwitch(air_inst_index);
2765
2766 try isel.active_loops.append(gpa, @fromBackingInt(@intCast(isel.loops.count())));
2767 try isel.loops.putNoClobber(gpa, air_inst_index, .{
2768 .def_order = @intCast(isel.def_order.count()),
2769 .outer_live = 0,
2770 .repeat_list = undefined,
2771 });
2772
2773 var cases_it = switch_br.iterateCases();
2774 while (cases_it.next()) |case| try isel.analyze(case.body);
2775 if (switch_br.else_body_len > 0) try isel.analyze(cases_it.elseBody());
2776
2777 assert(isel.active_loops.pop().?.inst(isel) == air_inst_index);
2778 },
2779 .switch_dispatch => {
2780 const br = air_data[@backingInt(air_inst_index)].br;
2781 try isel.analyzeUse(br.operand);
2782 },
2783 .slice_ptr => {
2784 const ty_op = air_data[@backingInt(air_inst_index)].ty_op;
2785
2786 try isel.analyzeUse(ty_op.operand);
2787 try isel.def_order.putNoClobber(gpa, air_inst_index, {});
2788
2789 const slice_vi = try isel.use(ty_op.operand);
2790 const ptr_part_vi = try slice_vi.partExact(isel, 0, 8);
2791 try isel.live_values.putNoClobber(gpa, air_inst_index, ptr_part_vi.ref(isel));
2792 },
2793 .slice_len => {
2794 const ty_op = air_data[@backingInt(air_inst_index)].ty_op;
2795
2796 try isel.analyzeUse(ty_op.operand);
2797 try isel.def_order.putNoClobber(gpa, air_inst_index, {});
2798
2799 const slice_vi = try isel.use(ty_op.operand);
2800 const len_part_vi = try slice_vi.partExact(isel, 8, 8);
2801 try isel.live_values.putNoClobber(gpa, air_inst_index, len_part_vi.ref(isel));
2802 },
2803 .reduce, .reduce_optimized => {
2804 const reduce = air_data[@backingInt(air_inst_index)].reduce;
2805
2806 try isel.analyzeUse(reduce.operand);
2807 try isel.def_order.putNoClobber(gpa, air_inst_index, {});
2808 },
2809 .shuffle_one => {
2810 const extra = isel.air.unwrapShuffleOne(zcu, air_inst_index);
2811
2812 try isel.analyzeUse(extra.operand);
2813 try isel.def_order.putNoClobber(gpa, air_inst_index, {});
2814 },
2815 .shuffle_two => {
2816 const extra = isel.air.unwrapShuffleTwo(zcu, air_inst_index);
2817
2818 try isel.analyzeUse(extra.operand_a);
2819 try isel.analyzeUse(extra.operand_b);
2820 try isel.def_order.putNoClobber(gpa, air_inst_index, {});
2821 },
2822 .@"try", .try_cold => {
2823 const pl_op = air_data[@backingInt(air_inst_index)].pl_op;
2824 const extra = isel.air.extraData(Air.Try, pl_op.payload);
2825
2826 try isel.analyzeUse(pl_op.operand);
2827 try isel.analyze(@ptrCast(isel.air.extra.items[extra.end..][0..extra.data.body_len]));
2828 try isel.def_order.putNoClobber(gpa, air_inst_index, {});
2829 },
2830 .try_ptr, .try_ptr_cold => {
2831 const ty_pl = air_data[@backingInt(air_inst_index)].ty_pl;
2832 const extra = isel.air.extraData(Air.TryPtr, ty_pl.payload);
2833
2834 try isel.analyzeUse(extra.data.ptr);
2835 try isel.analyze(@ptrCast(isel.air.extra.items[extra.end..][0..extra.data.body_len]));
2836 try isel.def_order.putNoClobber(gpa, air_inst_index, {});
2837 },
2838 .cmpxchg_weak, .cmpxchg_strong => {
2839 const ty_pl = air_data[@backingInt(air_inst_index)].ty_pl;
2840 const extra = isel.air.extraData(Air.Cmpxchg, ty_pl.payload).data;
2841
2842 try isel.analyzeUse(extra.ptr);
2843 try isel.analyzeUse(extra.expected_value);
2844 try isel.analyzeUse(extra.new_value);
2845 try isel.def_order.putNoClobber(gpa, air_inst_index, {});
2846 },
2847 .atomic_load => {
2848 const atomic_load = air_data[@backingInt(air_inst_index)].atomic_load;
2849
2850 try isel.analyzeUse(atomic_load.ptr);
2851 try isel.def_order.putNoClobber(gpa, air_inst_index, {});
2852 },
2853 .atomic_rmw => {
2854 const pl_op = air_data[@backingInt(air_inst_index)].pl_op;
2855 const extra = isel.air.extraData(Air.AtomicRmw, pl_op.payload).data;
2856
2857 try isel.analyzeUse(extra.operand);
2858 try isel.def_order.putNoClobber(gpa, air_inst_index, {});
2859 },
2860 }
2861 }
2862 isel.def_order.shrinkRetainingCapacity(initial_def_order_len);
2863}
2864
2865fn analyzeUse(isel: *Select, air_ref: Air.Inst.Ref) !void {
2866 const air_inst_index = air_ref.toIndex() orelse return;
2867 const def_order_index = isel.def_order.getIndex(air_inst_index).?;
2868
2869 // Loop liveness
2870 var active_loop_index = isel.active_loops.items.len;
2871 while (active_loop_index > 0) {
2872 const prev_active_loop_index = active_loop_index - 1;
2873 const active_loop = isel.active_loops.items[prev_active_loop_index];
2874 if (def_order_index >= active_loop.get(isel).def_order) break;
2875 active_loop_index = prev_active_loop_index;
2876 }
2877 if (active_loop_index < isel.active_loops.items.len) {
2878 const active_loop = isel.active_loops.items[active_loop_index];
2879 const loop_live_gop =
2880 try isel.loop_outer_live.set.getOrPut(isel.pt.zcu.gpa, .{ active_loop, air_inst_index });
2881 if (!loop_live_gop.found_existing) active_loop.get(isel).outer_live += 1;
2882 }
2883}
2884
2885pub fn finishAnalysis(isel: *Select) !void {
2886 const gpa = isel.pt.zcu.gpa;
2887
2888 // Loop liveness
2889 if (isel.loops.count() > 0) {
2890 try isel.loops.ensureUnusedCapacity(gpa, 1);
2891
2892 const loop_live_len: u32 = @intCast(isel.loop_outer_live.set.count());
2893 if (loop_live_len > 0) {
2894 try isel.loop_outer_live.list.resize(gpa, loop_live_len);
2895
2896 // prefix sum
2897 const loops = isel.loops.values();
2898 for (loops[1..], loops[0 .. loops.len - 1]) |*loop, prev_loop| loop.outer_live += prev_loop.outer_live;
2899 assert(loops[loops.len - 1].outer_live == loop_live_len);
2900
2901 for (isel.loop_outer_live.set.keys()) |entry| {
2902 const loop, const inst = entry;
2903 const loop_live = &loop.get(isel).outer_live;
2904 loop_live.* -= 1;
2905 isel.loop_outer_live.list.items[loop_live.*] = inst;
2906 }
2907 assert(loops[0].outer_live == 0);
2908 }
2909
2910 const invalid_gop = isel.loops.getOrPutAssumeCapacity(Loop.invalid);
2911 assert(!invalid_gop.found_existing);
2912 invalid_gop.value_ptr.* = .{
2913 .def_order = undefined,
2914 .outer_live = loop_live_len,
2915 .repeat_list = undefined,
2916 };
2917 }
2918
2919 assert(isel.active_blocks.count() == 1 and isel.active_blocks.keys()[0] == Select.Block.main);
2920 assert(isel.active_loops.items.len == 0);
2921}
2922
2923pub fn verify(isel: *Select, check_values: bool) void {
2924 if (!std.debug.runtime_safety) return;
2925 assert(isel.active_blocks.count() == 1 and isel.active_blocks.keys()[0] == Select.Block.main);
2926 assert(isel.active_loops.items.len == 0);
2927 assert(isel.values.items.len == isel.value_types.items.len);
2928
2929 // Verify register state
2930 var live_reg_it = isel.live_registers.iterator();
2931 while (live_reg_it.next()) |live_reg_entry| switch (live_reg_entry.value.*) {
2932 _ => {
2933 tracking_log.err("{f}: still using ${t}", .{ live_reg_entry.value.*, live_reg_entry.key });
2934 isel.dumpValues(.all);
2935 unreachable;
2936 },
2937 .allocating, .free => {},
2938 };
2939
2940 // Check values state
2941 if (!check_values) return;
2942 for (isel.values.items, 0..) |value, vi_i| {
2943 const vi: Value.Index = @fromBackingInt(@as(@typeInfo(Value.Index).@"enum".tag_type, @intCast(vi_i)));
2944 if (value.refs != 0) {
2945 tracking_log.err("{f}: still referenced", .{vi});
2946 isel.dumpValues(.all);
2947 unreachable;
2948 }
2949 if (value.flags.parent_tag == .none and value.offset_from_parent != 0) {
2950 tracking_log.err("{f}: values without none cannot have offset from parent", .{vi});
2951 isel.dumpValues(.all);
2952 unreachable;
2953 }
2954 // Stack slot locations are allowed because layout values use them
2955 if (vi.register(isel) != null) {
2956 tracking_log.err("{f}: still has a location", .{vi});
2957 isel.dumpValues(.all);
2958 unreachable;
2959 }
2960 }
2961}
2962
2963pub fn body(isel: *Select, air_body: []const Air.Inst.Index) error{ OutOfMemory, AlreadyReported }!void {
2964 const zcu = isel.pt.zcu;
2965 const ip = &zcu.intern_pool;
2966 const gpa = zcu.gpa;
2967
2968 {
2969 var live_reg_it = isel.live_registers.iterator();
2970 while (live_reg_it.next()) |live_reg_entry| switch (live_reg_entry.value.*) {
2971 .allocating => {
2972 tracking_log.err("${t} is allocated", .{live_reg_entry.key});
2973 isel.dumpValues(.all);
2974 unreachable;
2975 },
2976 _, .free => {},
2977 };
2978 }
2979
2980 var air: struct {
2981 isel: *Select,
2982 tag_items: []const Air.Inst.Tag,
2983 data_items: []const Air.Inst.Data,
2984 body: []const Air.Inst.Index,
2985 body_index: u32,
2986 inst_index: Air.Inst.Index,
2987
2988 fn tag(it: *@This(), inst_index: Air.Inst.Index) Air.Inst.Tag {
2989 return it.tag_items[@backingInt(inst_index)];
2990 }
2991
2992 fn data(it: *@This(), inst_index: Air.Inst.Index) Air.Inst.Data {
2993 return it.data_items[@backingInt(inst_index)];
2994 }
2995
2996 fn next(it: *@This()) ?Air.Inst.Tag {
2997 if (it.body_index == 0) {
2998 @branchHint(.unlikely);
2999 return null;
3000 }
3001 it.body_index -= 1;
3002 it.inst_index = it.body[it.body_index];
3003 wip_mir_log.debug("{f}", .{it.fmtAir(it.inst_index)});
3004 if (@import("builtin").mode == .debug) {
3005 if (it.isel.live_values.get(it.inst_index)) |def_vi| {
3006 wip_mir_log.debug(" <- {f}", .{it.isel.fmtValue(def_vi)});
3007 }
3008 }
3009 return it.tag(it.inst_index);
3010 }
3011
3012 fn fmtAir(it: @This(), inst: Air.Inst.Index) struct {
3013 isel: *Select,
3014 inst: Air.Inst.Index,
3015 pub fn format(fmt_air: @This(), writer: *std.Io.Writer) std.Io.Writer.Error!void {
3016 fmt_air.isel.air.writeInst(writer, fmt_air.inst, fmt_air.isel.pt, null);
3017 }
3018 } {
3019 return .{ .isel = it.isel, .inst = inst };
3020 }
3021 } = .{
3022 .isel = isel,
3023 .tag_items = isel.air.instructions.items(.tag),
3024 .data_items = isel.air.instructions.items(.data),
3025 .body = air_body,
3026 .body_index = @intCast(air_body.len),
3027 .inst_index = undefined,
3028 };
3029 while (air.next()) |air_tag| {
3030 switch (air_tag) {
3031 else => if (debug_trap_unimplemented_code) {
3032 if (isel.live_values.fetchRemove(air.inst_index)) |vi| {
3033 vi.value.deref(isel);
3034 isel.wipeLocationDfs(vi.value);
3035 }
3036 try isel.failUnimplemented("unimplemented select for {s}", .{@tagName(air_tag)});
3037 } else return isel.fail("unimplemented select for {s}", .{@tagName(air_tag)}),
3038
3039 // Misc
3040 .unreach => {},
3041 .trap, .breakpoint => try isel.emit(.@"break"(0)),
3042
3043 // Arguments & return
3044 .arg => {
3045 const arg_vi = isel.live_values.fetchRemove(air.inst_index).?.value;
3046 defer arg_vi.deref(isel);
3047 const layout_vi = isel.arg_layouts[@backingInt(air.inst_index)];
3048 layout_vi.deref(isel);
3049 switch (layout_vi.parent(isel)) {
3050 .none => try arg_vi.defLiveIn(isel, layout_vi, .{}),
3051 .value, .constant => unreachable,
3052 .address => |layout_addr_vi| {
3053 switch (arg_vi.parent(isel)) {
3054 else => unreachable,
3055 .address => |arg_addr_vi| {
3056 try arg_addr_vi.defLiveIn(isel, layout_addr_vi, .{});
3057 },
3058 }
3059 },
3060 }
3061 },
3062 .ret, .ret_safe => {
3063 assert(isel.active_blocks.keys()[0] == Block.main);
3064 try isel.active_blocks.values()[0].branch(isel);
3065 if (isel.live_values.get(Block.main)) |ret_vi| {
3066 const un_op = air.data(air.inst_index).un_op;
3067 const src_vi = try isel.use(un_op);
3068 switch (ret_vi.parent(isel)) {
3069 .none => try src_vi.matLiveOut(isel, ret_vi, .{ .mode = .ret }),
3070 .value, .constant => unreachable,
3071 .address => |addr_vi| {
3072 const addr_mat = try addr_vi.matIntRegZeroExt(isel);
3073 try src_vi.matStore(isel, addr_mat.reg(), 0, .{});
3074 try addr_mat.finish(isel);
3075 },
3076 }
3077 }
3078 },
3079 .ret_load => {
3080 const un_op = air.data(air.inst_index).un_op;
3081 const ptr_ty = isel.air.typeOf(un_op, ip);
3082 const ptr_info = ptr_ty.ptrInfo(zcu);
3083 if (ptr_info.packed_offset.host_size > 0) return isel.fail("packed load ret_load", .{});
3084
3085 assert(isel.active_blocks.keys()[0] == Block.main);
3086 try isel.active_blocks.values()[0].branch(isel);
3087 if (isel.live_values.get(Block.main)) |layout_vi| switch (layout_vi.parent(isel)) {
3088 .none => {
3089 const ptr_vi = try isel.use(un_op);
3090 const ret_ty = ptr_ty.childType(zcu);
3091 const ret_vi = try isel.initValue(ret_ty);
3092 ret_vi.setParent(isel, .{ .address = ptr_vi });
3093 try ret_vi.matLiveOut(isel, layout_vi, .{ .mode = .ret });
3094 },
3095 .value, .constant => unreachable,
3096 .address => {},
3097 };
3098 },
3099
3100 // Frame addresses
3101 .ret_addr => if (isel.live_values.fetchRemove(air.inst_index)) |addr_vi| unused: {
3102 defer addr_vi.value.deref(isel);
3103 const addr_reg = try addr_vi.value.defRegMod(isel, .integer) orelse break :unused;
3104 try isel.ldIncoming(addr_reg, .ra);
3105 },
3106 .frame_addr => if (isel.live_values.fetchRemove(air.inst_index)) |addr_vi| unused: {
3107 defer addr_vi.value.deref(isel);
3108 const addr_reg = try addr_vi.value.defRegMod(isel, .integer) orelse break :unused;
3109 isel.saved_registers.insert(.fp);
3110 try isel.emit(.ori(addr_reg, .fp, 0));
3111 },
3112
3113 // Debugging
3114 .dbg_stmt, .dbg_var_ptr, .dbg_var_val, .dbg_arg_inline => {},
3115 .dbg_empty_stmt => try isel.emit(.andi(.r0, .r0, 0)),
3116
3117 // Control-flows
3118 .dbg_inline_block => {
3119 const ty_pl = air.data(air.inst_index).ty_pl;
3120 const extra = isel.air.extraData(Air.DbgInlineBlock, ty_pl.payload);
3121 try isel.block(air.inst_index, ty_pl.ty.toType(), @ptrCast(
3122 isel.air.extra.items[extra.end..][0..extra.data.body_len],
3123 ));
3124 },
3125 .block => {
3126 const ty_pl = air.data(air.inst_index).ty_pl;
3127 const extra = isel.air.extraData(Air.Block, ty_pl.payload);
3128 try isel.block(air.inst_index, ty_pl.ty.toType(), @ptrCast(
3129 isel.air.extra.items[extra.end..][0..extra.data.body_len],
3130 ));
3131 },
3132 .loop => {
3133 const ty_pl = air.data(air.inst_index).ty_pl;
3134 const extra = isel.air.extraData(Air.Block, ty_pl.payload);
3135 const loops = isel.loops.values();
3136 const loop_index = isel.loops.getIndex(air.inst_index).?;
3137 const loop = &loops[loop_index];
3138
3139 tracking_log.debug("{f}", .{isel.fmtLoopLive(air.inst_index)});
3140 loop.snapshot = try isel.takeLocationSnapshot();
3141 tracking_log.debug("loop snapshot taken:\n{f}", .{loop.snapshot});
3142 loop.repeat_list = Loop.empty_list;
3143
3144 try isel.active_loops.append(gpa, @fromBackingInt(@intCast(loop_index)));
3145 try isel.body(@ptrCast(isel.air.extra.items[extra.end..][0..extra.data.body_len]));
3146 assert(isel.active_loops.pop().?.inst(isel) == air.inst_index);
3147
3148 tracking_log.debug("loop %{d}: merge snapshot after loop body", .{@backingInt(air.inst_index)});
3149 try loop.snapshot.merge(isel);
3150 loop.snapshot.deinit(isel);
3151 loop.snapshot = .empty;
3152
3153 tracking_log.debug("loop %{d}: kill registers written in loop body", .{@backingInt(air.inst_index)});
3154 try isel.fillRegsBatch(loop.written_regs, false);
3155 // copy written registers to outer loops
3156 isel.markRegsWritten(loop.written_regs);
3157
3158 // relocate branches
3159 var repeat_label = loop.repeat_list;
3160 assert(repeat_label != Loop.empty_list);
3161 while (repeat_label != Loop.empty_list) {
3162 const instruction = &isel.instructions.items[repeat_label];
3163 const next_repeat_label = instruction.*;
3164 instruction.* = .b(0, 0);
3165 try isel.internal_relocs.append(gpa, .{
3166 .label = repeat_label,
3167 .target = isel.instructions.items.len,
3168 });
3169 repeat_label = @bitCast(next_repeat_label);
3170 }
3171 },
3172 .repeat => {
3173 const repeat = air.data(air.inst_index).repeat;
3174 try isel.loops.getPtr(repeat.loop_inst).?.branch(isel);
3175 },
3176 .br => {
3177 const br = air.data(air.inst_index).br;
3178 try isel.active_blocks.getPtr(br.block_inst).?.branch(isel);
3179 if (isel.live_values.get(br.block_inst)) |dst_vi| try dst_vi.defMove(isel, br.operand);
3180 },
3181 .cond_br => {
3182 const pl_op = air.data(air.inst_index).pl_op;
3183 const extra = isel.air.extraData(Air.CondBr, pl_op.payload);
3184
3185 try isel.body(@ptrCast(isel.air.extra.items[extra.end + extra.data.then_body_len ..][0..extra.data.else_body_len]));
3186 const else_label = isel.instructions.items.len;
3187 var else_snapshot = try isel.takeLocationSnapshot();
3188 defer else_snapshot.deinit(isel);
3189 tracking_log.debug("if-body snapshot taken:\n{f}", .{else_snapshot});
3190 try isel.body(@ptrCast(isel.air.extra.items[extra.end..][0..extra.data.then_body_len]));
3191 try else_snapshot.merge(isel);
3192
3193 const cond_vi = try isel.use(pl_op.operand);
3194 const cond_mat = try cond_vi.mat(isel, .{
3195 .pref = .only_reg,
3196 .extension = .zero_ext,
3197 });
3198 try isel.internal_relocs.append(gpa, .{
3199 .label = @intCast(isel.instructions.items.len),
3200 .target = else_label,
3201 });
3202 try isel.emit(.beqz(cond_mat.reg(), 0, 0));
3203 try cond_mat.finish(isel);
3204 },
3205 .switch_br, .loop_switch_br => {
3206 // TODO loop switch br and switch dispatch
3207 if (air_tag == .loop_switch_br) try isel.failUnimplemented("TODO loop_switch_br", .{});
3208 const switch_br = isel.air.unwrapSwitch(air.inst_index);
3209
3210 var final_case = true;
3211 if (switch_br.else_body_len > 0) {
3212 var cases_it = switch_br.iterateCases();
3213 while (cases_it.next()) |_| {}
3214 try isel.body(cases_it.elseBody());
3215 assert(final_case);
3216 final_case = false;
3217 }
3218 var cases_it = switch_br.iterateCases();
3219 while (cases_it.next()) |case| {
3220 wip_mir_log.debug(" case {d}:", .{case.idx});
3221
3222 const next_label = isel.instructions.items.len;
3223 var next_snapshot = try isel.takeLocationSnapshot();
3224 defer next_snapshot.deinit(isel);
3225 tracking_log.debug("switch case snapshot taken:\n{f}", .{next_snapshot});
3226 try isel.body(case.body);
3227 try next_snapshot.merge(isel);
3228 if (final_case) {
3229 final_case = false;
3230 continue;
3231 }
3232
3233 const case_label = isel.instructions.items.len;
3234
3235 var cond_vi = try isel.use(switch_br.operand);
3236 const cond_mat = try cond_vi.mat(isel, .{
3237 .pref = .only_reg,
3238 .reg_mod = .integer,
3239 .extension = .zero_ext,
3240 });
3241
3242 try isel.internal_relocs.append(gpa, .{
3243 .label = @intCast(isel.instructions.items.len),
3244 .target = next_label,
3245 });
3246 try isel.emit(.b(0, 0));
3247
3248 var case_range_index = case.ranges.len;
3249 while (case_range_index > 0) {
3250 case_range_index -= 1;
3251 try isel.failUnimplemented("TODO switch_br range", .{});
3252 }
3253 var case_item_index = case.items.len;
3254 while (case_item_index > 0) {
3255 case_item_index -= 1;
3256
3257 const item_val: Constant = .fromInterned(case.items[case_item_index].toInterned().?);
3258 var item_bigint_space: Constant.BigIntSpace = undefined;
3259 const item_bigint = item_val.toBigInt(&item_bigint_space, zcu);
3260 const item_int: i64 = if (item_bigint.positive) @bitCast(
3261 item_bigint.toInt(u64) catch
3262 return isel.fail("too big case item: {f}", .{isel.fmtConstant(item_val)}),
3263 ) else item_bigint.toInt(i64) catch
3264 return isel.fail("too big case item: {f}", .{isel.fmtConstant(item_val)});
3265
3266 const item_reg = try isel.allocRegForWrite(.int);
3267 defer isel.freeReg(item_reg);
3268
3269 try isel.internal_relocs.append(gpa, .{
3270 .label = @intCast(isel.instructions.items.len),
3271 .target = case_label,
3272 });
3273 try isel.emit(.beq(cond_mat.reg(), item_reg, 0));
3274 try isel.moveIntImm(item_reg, @bitCast(item_int));
3275 }
3276
3277 try cond_mat.finish(isel);
3278 }
3279 },
3280
3281 // Procedure call
3282 .call => {
3283 const pl_op = air.data(air.inst_index).pl_op;
3284 const extra = isel.air.extraData(Air.Call, pl_op.payload);
3285 const args: []const Air.Inst.Ref = @ptrCast(isel.air.extra.items[extra.end..][0..extra.data.args_len]);
3286 const callee_ty = isel.air.typeOf(pl_op.operand, ip);
3287 const func_info = switch (ip.indexToKey(callee_ty.toIntern())) {
3288 else => unreachable,
3289 .func_type => |func_type| func_type,
3290 .ptr_type => |ptr_type| ip.indexToKey(ptr_type.child).func_type,
3291 };
3292
3293 var cc_it: CallAbiIterator = .{ .isel = isel, .cc = &func_info.cc };
3294
3295 // return
3296 try call.prepareReturn(isel);
3297 const ret_ty = isel.air.typeOfIndex(air.inst_index, ip);
3298 const maybe_def_ret_vi = isel.live_values.fetchRemove(air.inst_index);
3299 const ret_vi = try cc_it.resolve(ret_ty, true) orelse .free;
3300 defer if (ret_vi != .free) ret_vi.deref(isel);
3301
3302 var def_ret_stack: Value.Indirect = .unallocated;
3303 if (maybe_def_ret_vi) |def_ret_vi| {
3304 defer def_ret_vi.value.deref(isel);
3305 assert(ret_vi != .free);
3306 switch (ret_vi.parent(isel)) {
3307 else => {
3308 try def_ret_vi.value.defLiveIn(isel, ret_vi, .{});
3309 },
3310 .address => {
3311 def_ret_stack = try def_ret_vi.value.defStack(isel) orelse ret_vi.allocStackSlot(isel);
3312 },
3313 }
3314 }
3315 try call.finishReturn(isel);
3316
3317 // call
3318 try call.prepareCallee(isel);
3319 if (pl_op.operand.toInterned()) |ct_callee| {
3320 try isel.emit(.jirl(.ra, .ra, 0));
3321 try isel.nav_relocs.append(gpa, switch (ip.indexToKey(ct_callee)) {
3322 else => unreachable,
3323 inline .@"extern", .func => |func| .{
3324 .nav = func.owner_nav,
3325 .reloc = .{ .label = @intCast(isel.instructions.items.len) },
3326 },
3327 .ptr => |ptr| .{
3328 .nav = ptr.base_addr.nav,
3329 .reloc = .{
3330 .label = @intCast(isel.instructions.items.len),
3331 .addend = @intCast(ptr.byte_offset),
3332 },
3333 },
3334 });
3335 try isel.emit(.pcaddu18i(.ra, 0));
3336 } else {
3337 const callee_vi = try isel.use(pl_op.operand);
3338 const callee_mat = try callee_vi.matIntRegZeroExt(isel);
3339 try isel.emit(.jirl(.ra, callee_mat.reg(), 0));
3340 try callee_mat.finish(isel);
3341 }
3342 try call.finishCallee(isel);
3343
3344 // params
3345 try call.prepareParams(isel);
3346 if (ret_vi != .free) switch (ret_vi.parent(isel)) {
3347 else => {},
3348 .address => |addr_vi| try call.paramAddress(isel, def_ret_stack, addr_vi),
3349 };
3350 for (args) |arg| {
3351 const param_ty = isel.air.typeOf(arg, ip);
3352 const param_vi = try cc_it.resolve(param_ty, false) orelse continue;
3353 defer param_vi.deref(isel);
3354 const arg_vi = try isel.use(arg);
3355 try call.paramLiveOut(isel, arg_vi, param_vi);
3356 }
3357 try call.finishParams(isel);
3358 },
3359
3360 // Stack allocation
3361 .alloc, .ret_ptr => if (isel.live_values.fetchRemove(air.inst_index)) |ptr_vi| unused: {
3362 defer ptr_vi.value.deref(isel);
3363 switch (air_tag) {
3364 else => unreachable,
3365 .alloc => {},
3366 .ret_ptr => if (isel.live_values.get(Block.main)) |ret_vi| switch (ret_vi.parent(isel)) {
3367 .none => {},
3368 .value, .constant => unreachable,
3369 .address => break :unused,
3370 },
3371 }
3372 const ptr_reg = try ptr_vi.value.defRegMod(isel, .integer) orelse break :unused;
3373
3374 const ty = air.data(air.inst_index).ty;
3375 const slot_size = ty.childType(zcu).abiSize(zcu);
3376 const slot_align = ty.ptrAlignment(zcu);
3377 const slot_offset = slot_align.forward(isel.stack_size);
3378 isel.stack_size = @intCast(slot_offset + slot_size);
3379
3380 try isel.addImm(ptr_reg, .sp, slot_offset);
3381 },
3382 .inferred_alloc, .inferred_alloc_comptime => unreachable,
3383
3384 // Assembly
3385 .assembly => {
3386 const unwrapped_asm = isel.air.unwrapAsm(air.inst_index);
3387 const inputs = unwrapped_asm.inputs;
3388
3389 var as: Assemble = .{ .source = unwrapped_asm.source };
3390 defer as.deinit(gpa);
3391
3392 var it = unwrapped_asm.iterateOutputs();
3393 while (it.next()) |output| {
3394 const constraint = output.constraint;
3395 const name = output.name;
3396
3397 switch (output.operand) {
3398 else => return isel.fail("invalid constraint: '{s}'", .{constraint}),
3399 .none => {
3400 const output_reg = output_reg: {
3401 if (std.mem.startsWith(u8, constraint, "={") and std.mem.endsWith(u8, constraint, "}")) {
3402 const output_reg = Register.parse(constraint["={".len .. constraint.len - "}".len]) orelse
3403 return isel.fail("invalid constraint: '{s}'", .{constraint});
3404 assert(try isel.fillReg(output_reg));
3405 isel.markRegWritten(output_reg);
3406 if (isel.live_values.fetchRemove(air.inst_index)) |output_vi| {
3407 defer output_vi.value.deref(isel);
3408 try output_vi.value.reextendToPcs(isel);
3409 if (try output_vi.value.def(isel)) |output_loc|
3410 try isel.moveLoc(
3411 .{ .register = .{ .mod = .integer, .reg = output_reg } },
3412 0,
3413 output_loc,
3414 0,
3415 output_vi.value.size(isel),
3416 .none,
3417 );
3418 }
3419 break :output_reg output_reg;
3420 } else if (std.mem.eql(u8, constraint, "=r")) {
3421 if (isel.live_values.fetchRemove(air.inst_index)) |output_vi| {
3422 defer output_vi.value.deref(isel);
3423 try output_vi.value.reextendToPcs(isel);
3424 break :output_reg try output_vi.value.defRegMod(isel, .integer) orelse try isel.allocRegForWrite(.int);
3425 } else break :output_reg try isel.allocRegForWrite(.int);
3426 } else return isel.fail("invalid constraint: '{s}'", .{constraint});
3427 };
3428 if (!std.mem.eql(u8, name, "_")) {
3429 const arg_gop = try as.args.getOrPut(gpa, name);
3430 if (arg_gop.found_existing) return isel.fail("duplicate output name: '{s}'", .{name});
3431 arg_gop.value_ptr.* = .{ .register = output_reg };
3432 }
3433 },
3434 }
3435 }
3436
3437 const clobbers_val: Constant = .fromInterned(unwrapped_asm.clobbers);
3438 const clobbers_ty = clobbers_val.typeOf(zcu);
3439 var clobbers_bigint_buf: Constant.BigIntSpace = undefined;
3440 const clobbers_bigint = clobbers_val.toBigInt(&clobbers_bigint_buf, zcu);
3441 var clobbered_regs: RegisterSet = .empty;
3442 for (0..clobbers_ty.structFieldCount(zcu)) |field_index| {
3443 assert(clobbers_ty.fieldType(field_index, zcu).toIntern() == .bool_type);
3444 const limb_bits = @bitSizeOf(std.math.big.Limb);
3445 if (field_index / limb_bits >= clobbers_bigint.limbs.len) continue; // field is false
3446 switch (@as(u1, @truncate(clobbers_bigint.limbs[field_index / limb_bits] >> @intCast(field_index % limb_bits)))) {
3447 0 => continue, // field is false
3448 1 => {}, // field is true
3449 }
3450
3451 const clobber_name = clobbers_ty.structFieldName(field_index, zcu).toSlice(ip).?;
3452 if (std.mem.eql(u8, clobber_name, "memory")) continue;
3453 if (std.mem.startsWith(u8, clobber_name, "fcsr")) continue;
3454 const clobber_reg = Register.parse(clobber_name) orelse
3455 return isel.fail("unable to parse clobber: '{s}'", .{clobber_name});
3456 if (clobbered_regs.contains(clobber_reg))
3457 return isel.fail("clobbered twice: '{t}'", .{clobber_reg});
3458 clobbered_regs.insert(clobber_reg);
3459 }
3460 try isel.fillRegsBatch(clobbered_regs, true);
3461 isel.markRegsWritten(clobbered_regs);
3462
3463 const InputMat = union(enum(u1)) {
3464 reg: Register.Alias,
3465 mat: Value.Mat,
3466 };
3467 const input_mats = try gpa.alloc(InputMat, inputs.len);
3468 defer gpa.free(input_mats);
3469 var index: u32 = 0;
3470 it = unwrapped_asm.iterateInputs();
3471 while (it.next()) |input| : (index += 1) {
3472 const constraint = input.constraint;
3473 const name = input.name;
3474 const input_mat = &input_mats[index];
3475
3476 const input_vi = try isel.use(input.operand);
3477 try input_vi.reextendToPcs(isel);
3478
3479 // TODO support X constraint
3480 if (std.mem.startsWith(u8, constraint, "{") and std.mem.endsWith(u8, constraint, "}")) {
3481 const input_reg = Register.parse(constraint["{".len .. constraint.len - "}".len]) orelse
3482 return isel.fail("invalid constraint: '{s}'", .{constraint});
3483 input_mat.* = .{ .reg = .{ .mod = .integer, .reg = input_reg } };
3484 } else if (std.mem.eql(u8, constraint, "r")) {
3485 const input_value_mat = try input_vi.mat(isel, .{
3486 .pref = .only_reg,
3487 .reg_mod = .integer,
3488 .extension = if (input_vi.typeOf(isel)) |input_ty|
3489 .pcsMode(isel, input_ty)
3490 else
3491 .zero_ext,
3492 });
3493 input_mat.* = .{ .mat = input_value_mat };
3494 } else if (std.mem.eql(u8, name, "_")) {
3495 input_mat.* = .{ .reg = .zero };
3496 } else return isel.fail("invalid constraint: '{s}'", .{constraint});
3497
3498 if (!std.mem.eql(u8, name, "_")) {
3499 const arg_gop = try as.args.getOrPut(gpa, name);
3500 if (arg_gop.found_existing) return isel.fail("duplicate input name: '{s}'", .{name});
3501 arg_gop.value_ptr.* = .{ .register = switch (input_mat.*) {
3502 .reg => |input_ra| input_ra.reg,
3503 .mat => |input_val_mat| input_val_mat.reg(),
3504 } };
3505 }
3506 }
3507
3508 const asm_start = isel.instructions.items.len;
3509 while (instruction: {
3510 const line = as.nextLine();
3511 break :instruction as.parseLine(line) catch |err| switch (err) {
3512 error.InvalidSyntax => {
3513 if (debug_trap_unimplemented_code) {
3514 wip_mir_log.err("unable to assemble: '{s}'", .{std.mem.trim(
3515 u8,
3516 line,
3517 &std.ascii.whitespace,
3518 )});
3519 break :instruction Instruction.@"break"(0xaa);
3520 } else return isel.fail("unable to assemble: '{s}'", .{std.mem.trim(
3521 u8,
3522 line,
3523 &std.ascii.whitespace,
3524 )});
3525 },
3526 };
3527 }) |instruction| try isel.emit(instruction);
3528 std.mem.reverse(Instruction, isel.instructions.items[asm_start..]);
3529
3530 it = unwrapped_asm.iterateInputs();
3531 index = 0;
3532 while (it.next()) |input| : (index += 1) {
3533 const input_mat = &input_mats[index];
3534 const input_vi = try isel.use(input.operand);
3535 switch (input_mat.*) {
3536 .reg => |input_ra| {
3537 const input_val_mat = try input_vi.mat(isel, .{
3538 .pref = .prefer_reg,
3539 .hint_ra = input_ra,
3540 });
3541 const input_val_loc = input_val_mat.loc();
3542 const dst_loc: Value.Location = .{ .register = input_ra };
3543 if (!std.meta.eql(input_val_loc, dst_loc)) {
3544 dst_loc.markRegWritten(isel);
3545 try isel.moveLoc(dst_loc, 0, input_val_loc, 0, input_ra.mod.byteSize(isel.target), .none);
3546 }
3547 try input_val_mat.finish(isel);
3548 },
3549 .mat => |input_val_mat| try input_val_mat.finish(isel),
3550 }
3551 }
3552
3553 var clobber_regs_it = clobbered_regs.iterator();
3554 while (clobber_regs_it.next()) |clobber_reg| isel.freeReg(clobber_reg);
3555 },
3556
3557 // Arithmetic
3558 .add, .add_safe, .add_optimized, .add_wrap, .sub, .sub_safe, .sub_optimized, .sub_wrap => if (isel.live_values.fetchRemove(air.inst_index)) |res_vi| {
3559 defer res_vi.value.deref(isel);
3560
3561 const bin_op = air.data(air.inst_index).bin_op;
3562 const ty = isel.air.typeOf(bin_op.lhs, ip);
3563 if (!ty.isRuntimeFloat()) try isel.addOrSubtract(ty, res_vi.value, switch (air_tag) {
3564 else => unreachable,
3565 .add, .add_safe, .add_wrap => .add,
3566 .sub, .sub_safe, .sub_wrap => .sub,
3567 }, try isel.use(bin_op.lhs), try isel.use(bin_op.rhs), .{
3568 .overflow = switch (air_tag) {
3569 else => unreachable,
3570 .add, .sub => .@"unreachable",
3571 .add_safe, .sub_safe => .{ .panic = .integer_overflow },
3572 .add_wrap, .sub_wrap => .wrap,
3573 },
3574 }) else return isel.fail("unimplemented float", .{});
3575 },
3576 .not => if (isel.live_values.fetchRemove(air.inst_index)) |res_vi| unused: {
3577 defer res_vi.value.deref(isel);
3578
3579 const ty_op = air.data(air.inst_index).ty_op;
3580 const src_vi = try isel.use(ty_op.operand);
3581 const ty = ty_op.ty.toType();
3582 switch (ty.zigTypeTag(zcu)) {
3583 .bool => {
3584 // boolean not
3585 try res_vi.value.reextend(isel, .zero_ext);
3586 const res_reg = try res_vi.value.defRegMod(isel, .integer) orelse break :unused;
3587 // TODO optimize fcc path
3588 const src_mat = try src_vi.matIntRegZeroExt(isel);
3589 const src_reg = src_mat.reg();
3590 try isel.emit(.xori(res_reg, src_reg, 1));
3591 try src_mat.finish(isel);
3592 },
3593 .int => {
3594 // bitwise not
3595 var res_walk = res_vi.value.walk(isel, .{});
3596 const gpr_size = isel.gprSize();
3597 while (res_walk.next()) |res_part_vi| {
3598 if (res_part_vi.size(isel) > gpr_size) continue;
3599 res_walk.skipChildren(res_part_vi);
3600 const res_part_ra = try res_part_vi.defReg(isel) orelse continue;
3601 const src_part_mat = try src_vi.mat(isel, .{
3602 .offset = res_part_vi.offsetIn(isel, res_vi.value),
3603 .size = @intCast(res_part_vi.size(isel)),
3604 .pref = .only_reg,
3605 .reg_mod = res_part_ra.mod,
3606 });
3607 const src_part_reg = src_part_mat.reg();
3608 switch (res_part_ra.mod) {
3609 .undef => unreachable,
3610 .integer => try isel.emit(.nor(res_part_ra.reg, src_part_reg, .zero)),
3611 else => return isel.fail("unimplemented not {t}", .{res_part_ra.mod}),
3612 }
3613 try src_part_mat.finish(isel);
3614 }
3615 },
3616 else => |ty_tag| return isel.fail("unimplemented not on {t}", .{ty_tag}),
3617 }
3618 },
3619 .trunc => if (isel.live_values.fetchRemove(air.inst_index)) |res_vi| {
3620 defer res_vi.value.deref(isel);
3621
3622 const ty_op = air.data(air.inst_index).ty_op;
3623 const src_vi = try isel.use(ty_op.operand);
3624 const src_ty = ty_op.ty.toType();
3625 const src_bits = src_ty.bitSize(zcu);
3626 try res_vi.value.reextendAdvanced(
3627 isel,
3628 src_bits,
3629 src_vi.extension(isel),
3630 res_vi.value.extension(isel),
3631 );
3632 try res_vi.value.defCopy(isel, src_vi);
3633 },
3634 .div_trunc, .div_trunc_optimized, .div_floor, .div_floor_optimized, .div_exact, .div_exact_optimized => if (isel.live_values.fetchRemove(air.inst_index)) |res_vi| unused: {
3635 defer res_vi.value.deref(isel);
3636
3637 const bin_op = air.data(air.inst_index).bin_op;
3638 const ty = isel.air.typeOf(bin_op.lhs, ip);
3639 if (!ty.isRuntimeFloat()) {
3640 if (!ty.isAbiInt(zcu)) return isel.fail("bad {t} {f}", .{ air_tag, isel.fmtType(ty) });
3641 const int_info = ty.intInfo(zcu);
3642 switch (int_info.bits) {
3643 0 => unreachable,
3644 1...64 => |bits| {
3645 const res_reg = try res_vi.value.defRegMod(isel, .integer) orelse break :unused;
3646 const lhs_vi = try isel.use(bin_op.lhs);
3647 const rhs_vi = try isel.use(bin_op.rhs);
3648 const mat_opts: Value.Index.MatOptions = .{
3649 .pref = .only_reg,
3650 .reg_mod = .integer,
3651 .extension = ext_mode: {
3652 if (bits == 32 and isel.hasCpuFeature(.@"64bit") and isel.hasCpuFeature(.div32)) {
3653 break :ext_mode .garbage;
3654 }
3655 break :ext_mode .fromSignedness(int_info.signedness);
3656 },
3657 };
3658 const lhs_mat = try lhs_vi.mat(isel, mat_opts);
3659 const rhs_mat = try rhs_vi.mat(isel, mat_opts);
3660 const lhs_reg = lhs_mat.reg();
3661 const rhs_reg = rhs_mat.reg();
3662
3663 switch (bits) {
3664 else => unreachable,
3665 1...32 => try isel.emit(switch (int_info.signedness) {
3666 .signed => .@"div.w"(res_reg, lhs_reg, rhs_reg),
3667 .unsigned => .@"div.wu"(res_reg, lhs_reg, rhs_reg),
3668 }),
3669 33...64 => if (isel.hasCpuFeature(.@"64bit")) {
3670 try isel.emit(switch (int_info.signedness) {
3671 .signed => .@"div.d"(res_reg, lhs_reg, rhs_reg),
3672 .unsigned => .@"div.du"(res_reg, lhs_reg, rhs_reg),
3673 });
3674 } else return isel.fail("unimplemented 64bit division on LA32", .{}),
3675 }
3676 try rhs_mat.finish(isel);
3677 try lhs_mat.finish(isel);
3678 },
3679 else => try isel.failUnimplemented("too big {t} {f}", .{ air_tag, isel.fmtType(ty) }),
3680 }
3681 } else try isel.failUnimplemented("unimplemented float div", .{});
3682 },
3683 .bit_cast,
3684 .ptr_cast,
3685 .ptr_from_int,
3686 .int_from_ptr,
3687 .error_cast,
3688 .error_from_int,
3689 .int_from_error,
3690 .union_from_enum,
3691 => if (isel.live_values.fetchRemove(air.inst_index)) |dst_vi| unused: {
3692 defer dst_vi.value.deref(isel);
3693 const ty_op = air.data(air.inst_index).ty_op;
3694 const dst_ty = ty_op.ty.toType();
3695 const dst_tag = dst_ty.zigTypeTag(zcu);
3696 const src_ty = isel.air.typeOf(ty_op.operand, ip);
3697 const src_tag = src_ty.zigTypeTag(zcu);
3698
3699 if ((dst_tag == .bool or dst_ty.isAbiInt(zcu)) and (src_tag == .bool or src_ty.isAbiInt(zcu))) {
3700 const dst_int_info: std.builtin.Type.Int = if (dst_tag == .bool) .{ .signedness = .unsigned, .bits = 1 } else dst_ty.intInfo(zcu);
3701 const src_int_info: std.builtin.Type.Int = if (src_tag == .bool) .{ .signedness = .unsigned, .bits = 1 } else src_ty.intInfo(zcu);
3702 assert(dst_int_info.bits == src_int_info.bits);
3703 if (dst_tag != .@"struct" and src_tag != .@"struct") {
3704 try dst_vi.value.defMove(isel, ty_op.operand);
3705 } else switch (dst_int_info.bits) {
3706 0 => unreachable,
3707 1...31, 33...63 => |bits| {
3708 try dst_vi.value.reextendToGarbage(isel);
3709 const dst_reg = try dst_vi.value.defRegMod(isel, .integer) orelse break :unused;
3710 const src_vi = try isel.use(ty_op.operand);
3711 const src_mat = try src_vi.matReg(isel);
3712 try isel.fillUnusedBits(
3713 dst_reg,
3714 src_mat.reg(),
3715 .fromSignedness(dst_int_info.signedness),
3716 .fromSignedness(src_int_info.signedness),
3717 @intCast(bits),
3718 );
3719 try src_mat.finish(isel);
3720 },
3721 32, 64 => try dst_vi.value.defMove(isel, ty_op.operand),
3722 else => return isel.fail("unimplemented {t} {f} {f}", .{ air_tag, isel.fmtType(dst_ty), isel.fmtType(src_ty) }),
3723 }
3724 } else if ((dst_ty.isPtrAtRuntime(zcu) or dst_ty.isAbiInt(zcu)) and (src_ty.isPtrAtRuntime(zcu) or src_ty.isAbiInt(zcu))) {
3725 try dst_vi.value.defMove(isel, ty_op.operand);
3726 } else if (dst_ty.isSliceAtRuntime(zcu) and src_ty.isSliceAtRuntime(zcu)) {
3727 try dst_vi.value.defMove(isel, ty_op.operand);
3728 } else if (dst_tag == .error_union and src_tag == .error_union) {
3729 assert(dst_ty.errorUnionSet(zcu).hasRuntimeBits(zcu) ==
3730 src_ty.errorUnionSet(zcu).hasRuntimeBits(zcu));
3731 if (dst_ty.errorUnionPayload(zcu).toIntern() == src_ty.errorUnionPayload(zcu).toIntern()) {
3732 try dst_vi.value.defMove(isel, ty_op.operand);
3733 } else return isel.fail("bad {t} {f} {f}", .{ air_tag, isel.fmtType(dst_ty), isel.fmtType(src_ty) });
3734 } else if (dst_tag == .float and src_tag == .float) {
3735 assert(dst_ty.floatBits(isel.target) == src_ty.floatBits(isel.target));
3736 try dst_vi.value.defMove(isel, ty_op.operand);
3737 } else if (dst_ty.isAbiInt(zcu) and src_tag == .float) {
3738 const dst_int_info = dst_ty.intInfo(zcu);
3739 assert(dst_int_info.bits == src_ty.floatBits(isel.target));
3740
3741 try dst_vi.value.reextendToGarbage(isel);
3742 const dst_reg = try dst_vi.value.defRegMod(isel, .fromFloating(dst_int_info.bits)) orelse break :unused;
3743 const src_vi = try isel.use(ty_op.operand);
3744 const src_mat = try src_vi.matReg(isel);
3745 const src_reg = src_mat.reg();
3746 try isel.emit(switch (dst_int_info.bits) {
3747 else => unreachable,
3748 32 => .@"movfr2gr.s"(dst_reg, src_reg),
3749 64 => .@"movfr2gr.d"(dst_reg, src_reg),
3750 });
3751 try src_mat.finish(isel);
3752 } else if (dst_tag == .float and src_ty.isAbiInt(zcu)) {
3753 const src_int_info = src_ty.intInfo(zcu);
3754 assert(dst_ty.floatBits(isel.target) == src_int_info.bits);
3755
3756 try dst_vi.value.reextendToGarbage(isel);
3757 const dst_reg = try dst_vi.value.defRegMod(isel, .fromFloating(src_int_info.bits)) orelse break :unused;
3758 const src_vi = try isel.use(ty_op.operand);
3759 const src_mat = try src_vi.matReg(isel);
3760 const src_reg = src_mat.reg();
3761 try isel.emit(switch (src_int_info.bits) {
3762 else => unreachable,
3763 32 => .@"movgr2fr.w"(dst_reg, src_reg),
3764 64 => .@"movfr2gr.d"(dst_reg, src_reg),
3765 });
3766 try src_mat.finish(isel);
3767 } else if (dst_ty.isAbiInt(zcu) and src_tag == .array and src_ty.childType(zcu).isAbiInt(zcu)) {
3768 const dst_int_info = dst_ty.intInfo(zcu);
3769 const src_child_int_info = src_ty.childType(zcu).intInfo(zcu);
3770 const src_len = src_ty.arrayLenIncludingSentinel(zcu);
3771 assert(dst_int_info.bits == src_child_int_info.bits * src_len);
3772 const src_child_size = src_ty.childType(zcu).abiSize(zcu);
3773 if (8 * src_child_size == src_child_int_info.bits) {
3774 const src_vi = try isel.use(ty_op.operand);
3775 try dst_vi.value.defCopy(isel, src_vi);
3776 } else return isel.fail("bad {t} {f} {f}", .{ air_tag, isel.fmtType(dst_ty), isel.fmtType(src_ty) });
3777 } else if (dst_tag == .array and dst_ty.childType(zcu).isAbiInt(zcu) and src_ty.isAbiInt(zcu)) {
3778 const dst_child_int_info = dst_ty.childType(zcu).intInfo(zcu);
3779 const src_int_info = src_ty.intInfo(zcu);
3780 const dst_len = dst_ty.arrayLenIncludingSentinel(zcu);
3781 assert(dst_child_int_info.bits * dst_len == src_int_info.bits);
3782 const dst_child_size = dst_ty.childType(zcu).abiSize(zcu);
3783 if (8 * dst_child_size == dst_child_int_info.bits) {
3784 const src_vi = try isel.use(ty_op.operand);
3785 try dst_vi.value.defCopy(isel, src_vi);
3786 } else return isel.fail("bad {t} {f} {f}", .{ air_tag, isel.fmtType(dst_ty), isel.fmtType(src_ty) });
3787 } else if (dst_tag == .array and dst_ty.childType(zcu).isAbiInt(zcu) and
3788 src_tag == .array and src_ty.childType(zcu).isAbiInt(zcu))
3789 {
3790 const dst_child_int_info = dst_ty.childType(zcu).intInfo(zcu);
3791 const dst_len = dst_ty.arrayLenIncludingSentinel(zcu);
3792 const src_child_int_info = src_ty.childType(zcu).intInfo(zcu);
3793 const src_len = src_ty.arrayLenIncludingSentinel(zcu);
3794 assert(dst_child_int_info.bits * dst_len == src_child_int_info.bits * src_len);
3795 const dst_child_size = dst_ty.childType(zcu).abiSize(zcu);
3796 const src_child_size = src_ty.childType(zcu).abiSize(zcu);
3797 if (8 * dst_child_size == dst_child_int_info.bits and 8 * src_child_size == src_child_int_info.bits) {
3798 const src_vi = try isel.use(ty_op.operand);
3799 try dst_vi.value.defCopy(isel, src_vi);
3800 } else return isel.fail("bad {t} {f} {f}", .{ air_tag, isel.fmtType(dst_ty), isel.fmtType(src_ty) });
3801 } else return isel.fail("unimplemented {t} {f} {f}", .{ air_tag, isel.fmtType(dst_ty), isel.fmtType(src_ty) });
3802 },
3803 .bit_and, .bit_or, .xor => if (isel.live_values.fetchRemove(air.inst_index)) |res_vi| {
3804 defer res_vi.value.deref(isel);
3805
3806 const bin_op = air.data(air.inst_index).bin_op;
3807
3808 const lhs_vi = try isel.use(bin_op.lhs);
3809 const rhs_vi = try isel.use(bin_op.rhs);
3810
3811 const lhs_ext_mode = lhs_vi.extension(isel);
3812 const rhs_ext_mode = rhs_vi.extension(isel);
3813 try res_vi.value.reextend(isel, res_ext_mode: switch (air_tag) {
3814 else => unreachable,
3815 .bit_and => {
3816 if (lhs_ext_mode == rhs_ext_mode) break :res_ext_mode lhs_ext_mode;
3817 if (lhs_ext_mode == .zero_ext or rhs_ext_mode == .zero_ext) break :res_ext_mode .zero_ext;
3818 break :res_ext_mode .garbage;
3819 },
3820 .bit_or => if (lhs_ext_mode == rhs_ext_mode) lhs_ext_mode else .garbage,
3821 .xor => .garbage,
3822 });
3823
3824 var res_walk = res_vi.value.walk(isel, .{});
3825 const gpr_size = isel.gprSize();
3826 while (res_walk.next()) |res_part_vi| {
3827 if (res_part_vi.size(isel) > gpr_size) continue;
3828 res_walk.skipChildren(res_part_vi);
3829 const part_offset = res_part_vi.offsetIn(isel, res_vi.value);
3830 const part_size = res_part_vi.size(isel);
3831 // TODO implement vectors
3832 const res_part_ra = try res_part_vi.defReg(isel) orelse continue;
3833 const res_part_reg = res_part_ra.reg;
3834 const lhs_part_mat = try lhs_vi.mat(isel, .{
3835 .offset = part_offset,
3836 .size = @intCast(part_size),
3837 .pref = .only_reg,
3838 .reg_mod = res_part_ra.mod,
3839 });
3840 const lhs_part_reg = lhs_part_mat.reg();
3841 const rhs_part_mat = try lhs_vi.mat(isel, .{
3842 .offset = part_offset,
3843 .size = @intCast(part_size),
3844 .pref = .only_reg,
3845 .reg_mod = res_part_ra.mod,
3846 });
3847 const rhs_part_reg = rhs_part_mat.reg();
3848
3849 try isel.emit(switch (air_tag) {
3850 else => unreachable,
3851 .bit_and => .@"and"(res_part_reg, lhs_part_reg, rhs_part_reg),
3852 .bit_or => .@"or"(res_part_reg, lhs_part_reg, rhs_part_reg),
3853 .xor => .xor(res_part_reg, lhs_part_reg, rhs_part_reg),
3854 });
3855 try rhs_part_mat.finish(isel);
3856 try lhs_part_mat.finish(isel);
3857 }
3858 },
3859 .cmp_lt, .cmp_lte, .cmp_eq, .cmp_gte, .cmp_gt, .cmp_neq => if (isel.live_values.fetchRemove(air.inst_index)) |res_vi| unused: {
3860 defer res_vi.value.deref(isel);
3861
3862 const bin_op = air.data(air.inst_index).bin_op;
3863 const ty = isel.air.typeOf(bin_op.lhs, ip);
3864 const lhs_vi = try isel.use(bin_op.lhs);
3865 const rhs_vi = try isel.use(bin_op.rhs);
3866
3867 switch (ip.indexToKey(ty.toIntern())) {
3868 else => {},
3869 .opt_type => |payload_ty| switch (air_tag) {
3870 else => unreachable,
3871 .cmp_eq, .cmp_neq => if (!ty.optionalReprIsPayload(zcu)) {
3872 const payload_size = ZigType.abiSize(.fromInterned(payload_ty), zcu);
3873 try res_vi.value.reextendToGarbage(isel);
3874 const res_reg = try res_vi.value.defRegMod(isel, .integer) orelse break :unused;
3875
3876 const cmp_label = isel.instructions.items.len;
3877 try isel.cmp(
3878 res_reg,
3879 .fromInterned(payload_ty),
3880 try lhs_vi.partExact(isel, 0, payload_size),
3881 air_tag.toCmpOp().?,
3882 try rhs_vi.partExact(isel, 0, payload_size),
3883 );
3884 const lhs_tag_mat = try lhs_vi.mat(isel, .{
3885 .offset = payload_size,
3886 .size = 1,
3887 .pref = .only_reg,
3888 .reg_mod = .integer,
3889 .extension = .zero_ext,
3890 });
3891 const rhs_tag_mat = try rhs_vi.mat(isel, .{
3892 .offset = payload_size,
3893 .size = 1,
3894 .pref = .only_reg,
3895 .reg_mod = .integer,
3896 .extension = .zero_ext,
3897 });
3898 try isel.internal_relocs.append(gpa, .{
3899 .label = @intCast(isel.instructions.items.len),
3900 .target = cmp_label,
3901 });
3902 try isel.emit(.beqz(lhs_tag_mat.reg(), 0, 0));
3903 try isel.internal_relocs.append(gpa, .{
3904 .label = @intCast(isel.instructions.items.len),
3905 .target = cmp_label,
3906 });
3907 try isel.emit(.beqz(res_reg, 0, 0));
3908
3909 try isel.emit(.xori(res_reg, res_reg, 1));
3910 try isel.emit(.xor(res_reg, lhs_tag_mat.reg(), rhs_tag_mat.reg()));
3911 try rhs_tag_mat.finish(isel);
3912 try lhs_tag_mat.finish(isel);
3913 break :unused;
3914 },
3915 },
3916 }
3917
3918 // TODO optimize fcc path
3919 try res_vi.value.reextendToPcs(isel);
3920 try isel.cmp(
3921 try res_vi.value.defRegMod(isel, .integer) orelse break :unused,
3922 ty,
3923 lhs_vi,
3924 air_tag.toCmpOp().?,
3925 rhs_vi,
3926 );
3927 },
3928 .store, .store_safe, .atomic_store_unordered => unused: {
3929 const bin_op = air.data(air.inst_index).bin_op;
3930 const ptr_ty = isel.air.typeOf(bin_op.lhs, ip);
3931 const ptr_info = ptr_ty.ptrInfo(zcu);
3932 if (ptr_info.packed_offset.host_size > 0) return isel.fail("packed store", .{});
3933 if (bin_op.rhs.toInterned()) |rhs_val| if (ip.isUndef(rhs_val)) break :unused;
3934
3935 const src_vi = try isel.use(bin_op.rhs);
3936 const ptr_vi = try isel.use(bin_op.lhs);
3937 const ptr_mat = try ptr_vi.matReg(isel);
3938 try src_vi.matStore(isel, ptr_mat.reg(), 0, .{
3939 .@"volatile" = ptr_info.flags.is_volatile,
3940 });
3941 try ptr_mat.finish(isel);
3942 },
3943 .load => {
3944 const ty_op = air.data(air.inst_index).ty_op;
3945 const ptr_ty = isel.air.typeOf(ty_op.operand, ip);
3946 const ptr_info = ptr_ty.ptrInfo(zcu);
3947 if (ptr_info.packed_offset.host_size > 0) return isel.fail("packed load", .{});
3948
3949 if (ptr_info.flags.is_volatile) _ = try isel.use(air.inst_index.toRef());
3950 if (isel.live_values.fetchRemove(air.inst_index)) |dst_vi| {
3951 defer dst_vi.value.deref(isel);
3952
3953 // TODO unaligned loads
3954 assert(isel.target.cpu.has(.loongarch, .ual));
3955 const ptr_vi = try isel.use(ty_op.operand);
3956 const ptr_mat = try ptr_vi.matIntRegZeroExt(isel);
3957 _ = try dst_vi.value.defLoad(isel, ptr_mat.reg(), 0, .{
3958 .@"volatile" = ptr_info.flags.is_volatile,
3959 });
3960 try ptr_mat.finish(isel);
3961 }
3962 },
3963 .int_cast => if (isel.live_values.fetchRemove(air.inst_index)) |dst_vi| {
3964 defer dst_vi.value.deref(isel);
3965
3966 const ty_op = air.data(air.inst_index).ty_op;
3967 const dst_ty = ty_op.ty.toType();
3968 const dst_int_info = dst_ty.intInfo(zcu);
3969 const src_ty = isel.air.typeOf(ty_op.operand, ip);
3970 const src_int_info = src_ty.intInfo(zcu);
3971
3972 if (dst_int_info.bits == src_int_info.bits) {
3973 try dst_vi.value.defMove(isel, ty_op.operand);
3974 } else {
3975 const src_vi = try isel.use(ty_op.operand);
3976 try dst_vi.value.reextendAdvanced(isel, src_int_info.bits, null, src_vi.extension(isel));
3977 try dst_vi.value.defCopy(isel, src_vi);
3978 }
3979 },
3980 .is_null, .is_non_null => if (isel.live_values.fetchRemove(air.inst_index)) |is_vi| unused: {
3981 defer is_vi.value.deref(isel);
3982 const is_reg = try is_vi.value.defRegMod(isel, .integer) orelse break :unused;
3983
3984 const un_op = air.data(air.inst_index).un_op;
3985 const opt_ty = isel.air.typeOf(un_op, ip);
3986 const payload_ty = opt_ty.optionalChild(zcu);
3987 const payload_size = payload_ty.abiSize(zcu);
3988 const has_value_offset, const has_value_size = if (!opt_ty.optionalReprIsPayload(zcu))
3989 .{ payload_size, 1 }
3990 else if (payload_ty.isSlice(zcu))
3991 .{ 0, 8 }
3992 else
3993 .{ 0, @as(u32, @intCast(payload_size)) };
3994
3995 const opt_vi = try isel.use(un_op);
3996 const has_value_mat = try opt_vi.mat(isel, .{
3997 .offset = has_value_offset,
3998 .size = has_value_size,
3999 .pref = .only_reg,
4000 .reg_mod = .integer,
4001 .extension = .zero_ext,
4002 .hint_ra = .{ .reg = is_reg, .mod = .integer },
4003 });
4004 const has_value_reg = has_value_mat.reg();
4005 try isel.emit(switch (air_tag) {
4006 else => unreachable,
4007 .is_null => .sltui(is_reg, has_value_reg, 1),
4008 .is_non_null => .sltu(is_reg, .zero, has_value_reg),
4009 });
4010 try has_value_mat.finish(isel);
4011 },
4012 .is_err, .is_non_err => if (isel.live_values.fetchRemove(air.inst_index)) |is_vi| unused: {
4013 defer is_vi.value.deref(isel);
4014 const is_reg = try is_vi.value.defRegMod(isel, .integer) orelse break :unused;
4015
4016 const un_op = air.data(air.inst_index).un_op;
4017 const error_union_ty = isel.air.typeOf(un_op, ip);
4018 const error_union_info = ip.indexToKey(error_union_ty.toIntern()).error_union_type;
4019 const error_set_ty: ZigType = .fromInterned(error_union_info.error_set_type);
4020 const payload_ty: ZigType = .fromInterned(error_union_info.payload_type);
4021 const error_set_offset = codegen.errUnionErrorOffset(payload_ty, zcu);
4022 const error_set_size = error_set_ty.abiSize(zcu);
4023
4024 const error_union_vi = try isel.use(un_op);
4025 const error_set_mat = try error_union_vi.mat(isel, .{
4026 .offset = error_set_offset,
4027 .size = @intCast(error_set_size),
4028 .pref = .only_reg,
4029 .reg_mod = .integer,
4030 .hint_ra = .{ .reg = is_reg, .mod = .integer },
4031 });
4032 try isel.emit(switch (air_tag) {
4033 else => unreachable,
4034 .is_err => .sltu(is_reg, .zero, is_reg),
4035 .is_non_err => .sltui(is_reg, is_reg, 1),
4036 });
4037 try error_set_mat.finish(isel);
4038 },
4039 .max, .min => if (isel.live_values.fetchRemove(air.inst_index)) |res_vi| unused: {
4040 defer res_vi.value.deref(isel);
4041
4042 const bin_op = air.data(air.inst_index).bin_op;
4043 const ty = isel.air.typeOf(bin_op.lhs, ip);
4044 if (!ty.isRuntimeFloat()) {
4045 if (!ty.isAbiInt(zcu)) return isel.fail("bad {t} {f}", .{ air_tag, isel.fmtType(ty) });
4046 const int_info = ty.intInfo(zcu);
4047 if (int_info.bits > 64) return isel.fail("too big {t} {f}", .{ air_tag, isel.fmtType(ty) });
4048
4049 try res_vi.value.reextendToGarbage(isel);
4050 const res_reg = try res_vi.value.defRegMod(isel, .integer) orelse break :unused;
4051 const lhs_vi = try isel.use(bin_op.lhs);
4052 // TODO: relax LHS and RHS requirements to "not garbage filled"
4053 const lhs_mat = try lhs_vi.matIntRegZeroExt(isel);
4054 const lhs_reg = lhs_mat.reg();
4055 const rhs_vi = try isel.use(bin_op.rhs);
4056 const rhs_mat = try rhs_vi.matIntRegZeroExt(isel);
4057 const rhs_reg = rhs_mat.reg();
4058
4059 const tmp_reg = try isel.allocRegForWrite(.int);
4060 defer isel.freeReg(tmp_reg);
4061 const cond_reg = try isel.allocRegForWrite(.int);
4062 defer isel.freeReg(cond_reg);
4063
4064 try isel.emit(.@"or"(res_reg, res_reg, tmp_reg));
4065 try isel.emit(.maskeqz(res_reg, lhs_reg, cond_reg));
4066 try isel.emit(.masknez(tmp_reg, rhs_reg, cond_reg));
4067 switch (air_tag) {
4068 else => unreachable,
4069 .min => try isel.emit(.sltu(cond_reg, lhs_reg, rhs_reg)),
4070 .max => try isel.emit(.sltu(cond_reg, rhs_reg, lhs_reg)),
4071 }
4072
4073 try rhs_mat.finish(isel);
4074 try lhs_mat.finish(isel);
4075 } else switch (ty.floatBits(isel.target)) {
4076 else => unreachable,
4077 32, 64 => return isel.fail("TODO float min/max", .{}),
4078 }
4079 },
4080 .slice => if (isel.live_values.fetchRemove(air.inst_index)) |slice_vi| {
4081 defer slice_vi.value.deref(isel);
4082 const ty_pl = air.data(air.inst_index).ty_pl;
4083 const bin_op = isel.air.extraData(Air.Bin, ty_pl.payload).data;
4084 const gpr_size = isel.gprSize();
4085 const ptr_part_vi = try slice_vi.value.partExact(isel, 0, gpr_size);
4086 try ptr_part_vi.defMove(isel, bin_op.lhs);
4087 const len_part_vi = try slice_vi.value.partExact(isel, gpr_size, gpr_size);
4088 try len_part_vi.defMove(isel, bin_op.rhs);
4089 },
4090 .slice_ptr => if (isel.live_values.fetchRemove(air.inst_index)) |ptr_vi| {
4091 defer ptr_vi.value.deref(isel);
4092 const ty_op = air.data(air.inst_index).ty_op;
4093 const gpr_size = isel.gprSize();
4094 const slice_vi = try isel.use(ty_op.operand);
4095 const ptr_part_vi = try slice_vi.partExact(isel, 0, gpr_size);
4096 try ptr_vi.value.defCopy(isel, ptr_part_vi);
4097 },
4098 .slice_len => if (isel.live_values.fetchRemove(air.inst_index)) |len_vi| {
4099 defer len_vi.value.deref(isel);
4100 const ty_op = air.data(air.inst_index).ty_op;
4101 const gpr_size = isel.gprSize();
4102 const slice_vi = try isel.use(ty_op.operand);
4103 const len_part_vi = try slice_vi.partExact(isel, gpr_size, gpr_size);
4104 try len_vi.value.defCopy(isel, len_part_vi);
4105 },
4106 .ptr_slice_ptr_ptr => if (isel.live_values.fetchRemove(air.inst_index)) |dst_vi| {
4107 defer dst_vi.value.deref(isel);
4108 const ty_op = air.data(air.inst_index).ty_op;
4109 try dst_vi.value.defMove(isel, ty_op.operand);
4110 },
4111 .ptr_slice_len_ptr => if (isel.live_values.fetchRemove(air.inst_index)) |dst_vi| unused: {
4112 defer dst_vi.value.deref(isel);
4113 const ty_op = air.data(air.inst_index).ty_op;
4114 const dst_reg = try dst_vi.value.defRegMod(isel, .integer) orelse break :unused;
4115 const src_vi = try isel.use(ty_op.operand);
4116 const src_mat = try src_vi.matIntRegZeroExt(isel);
4117 const src_reg = src_mat.reg();
4118 switch (isel.gprSize()) {
4119 else => unreachable,
4120 4 => try isel.emit(.@"addi.w"(dst_reg, src_reg, 4)),
4121 8 => try isel.emit(.@"addi.d"(dst_reg, src_reg, 8)),
4122 }
4123 try src_mat.finish(isel);
4124 },
4125 .slice_elem_val => if (isel.live_values.fetchRemove(air.inst_index)) |elem_vi| unused: {
4126 defer elem_vi.value.deref(isel);
4127
4128 const bin_op = air.data(air.inst_index).bin_op;
4129 const slice_ty = isel.air.typeOf(bin_op.lhs, ip);
4130 const ptr_info = slice_ty.ptrInfo(zcu);
4131 const elem_size = elem_vi.value.size(isel);
4132
4133 const elem_ptr_reg = try isel.allocRegForWrite(.int);
4134 defer isel.freeReg(elem_ptr_reg);
4135
4136 if (!try elem_vi.value.defLoad(isel, elem_ptr_reg, 0, .{
4137 .@"volatile" = ptr_info.flags.is_volatile,
4138 })) break :unused;
4139
4140 const slice_vi = try isel.use(bin_op.lhs);
4141 const base_ptr_mat = try slice_vi.mat(isel, .{
4142 .offset = 0,
4143 .size = isel.gprSize(),
4144 .pref = .only_reg,
4145 .reg_mod = .integer,
4146 });
4147 const index_vi = try isel.use(bin_op.rhs);
4148 try isel.elemPtr(elem_ptr_reg, base_ptr_mat.reg(), .add, elem_size, index_vi);
4149 try base_ptr_mat.finish(isel);
4150 },
4151 .slice_elem_ptr => if (isel.live_values.fetchRemove(air.inst_index)) |elem_ptr_vi| unused: {
4152 defer elem_ptr_vi.value.deref(isel);
4153 const elem_ptr_reg = try elem_ptr_vi.value.defRegMod(isel, .integer) orelse break :unused;
4154
4155 const ty_pl = air.data(air.inst_index).ty_pl;
4156 const bin_op = isel.air.extraData(Air.Bin, ty_pl.payload).data;
4157 const elem_size = ty_pl.ty.toType().childType(zcu).abiSize(zcu);
4158
4159 const slice_vi = try isel.use(bin_op.lhs);
4160 const base_ptr_mat = try slice_vi.mat(isel, .{
4161 .offset = 0,
4162 .size = isel.gprSize(),
4163 .pref = .only_reg,
4164 .reg_mod = .integer,
4165 });
4166 const index_vi = try isel.use(bin_op.rhs);
4167 try isel.elemPtr(elem_ptr_reg, base_ptr_mat.reg(), .add, elem_size, index_vi);
4168 try base_ptr_mat.finish(isel);
4169 },
4170 .ptr_add, .ptr_sub => if (isel.live_values.fetchRemove(air.inst_index)) |res_vi| unused: {
4171 defer res_vi.value.deref(isel);
4172 const res_reg = try res_vi.value.defRegMod(isel, .integer) orelse break :unused;
4173
4174 const ty_pl = air.data(air.inst_index).ty_pl;
4175 const bin_op = isel.air.extraData(Air.Bin, ty_pl.payload).data;
4176 const elem_size = ty_pl.ty.toType().childType(zcu).abiSize(zcu);
4177
4178 const base_vi = try isel.use(bin_op.lhs);
4179 const base_ptr_mat = try base_vi.mat(isel, .{
4180 .offset = 0,
4181 .size = isel.gprSize(),
4182 .pref = .only_reg,
4183 .reg_mod = .integer,
4184 });
4185 const index_vi = try isel.use(bin_op.rhs);
4186 try isel.elemPtr(res_reg, base_ptr_mat.reg(), switch (air_tag) {
4187 else => unreachable,
4188 .ptr_add => .add,
4189 .ptr_sub => .sub,
4190 }, elem_size, index_vi);
4191 try base_ptr_mat.finish(isel);
4192 },
4193 .ptr_elem_ptr => if (isel.live_values.fetchRemove(air.inst_index)) |elem_ptr_vi| unused: {
4194 defer elem_ptr_vi.value.deref(isel);
4195 const elem_ptr_reg = try elem_ptr_vi.value.defRegMod(isel, .integer) orelse break :unused;
4196
4197 const ty_pl = air.data(air.inst_index).ty_pl;
4198 const bin_op = isel.air.extraData(Air.Bin, ty_pl.payload).data;
4199 const elem_size = ty_pl.ty.toType().childType(zcu).abiSize(zcu);
4200
4201 const base_vi = try isel.use(bin_op.lhs);
4202 const base_mat = try base_vi.matIntRegZeroExt(isel);
4203 const index_vi = try isel.use(bin_op.rhs);
4204 try isel.elemPtr(elem_ptr_reg, base_mat.reg(), .add, elem_size, index_vi);
4205 try base_mat.finish(isel);
4206 },
4207 .array_to_slice => if (isel.live_values.fetchRemove(air.inst_index)) |slice_vi| {
4208 defer slice_vi.value.deref(isel);
4209 const ty_op = air.data(air.inst_index).ty_op;
4210 const gpr_size = isel.gprSize();
4211 const array_len = isel.air.typeOf(ty_op.operand, ip).childType(zcu).arrayLen(zcu);
4212
4213 const len_part_vi = try slice_vi.value.partExact(isel, gpr_size, gpr_size);
4214 if (try len_part_vi.defRegMod(isel, .integer)) |len_reg|
4215 try isel.moveIntImm(len_reg, @bitCast(array_len));
4216
4217 const ptr_part_vi = try slice_vi.value.partExact(isel, 0, gpr_size);
4218 try ptr_part_vi.defMove(isel, ty_op.operand);
4219 },
4220 .@"try", .try_cold => {
4221 const pl_op = air.data(air.inst_index).pl_op;
4222 const extra = isel.air.extraData(Air.Try, pl_op.payload);
4223 const error_union_ty = isel.air.typeOf(pl_op.operand, ip);
4224 const error_union_info = ip.indexToKey(error_union_ty.toIntern()).error_union_type;
4225 const payload_ty: ZigType = .fromInterned(error_union_info.payload_type);
4226
4227 const error_union_vi = try isel.use(pl_op.operand);
4228 if (isel.live_values.fetchRemove(air.inst_index)) |payload_vi| {
4229 defer payload_vi.value.deref(isel);
4230
4231 const payload_part_vi = try error_union_vi.partExact(
4232 isel,
4233 codegen.errUnionPayloadOffset(payload_ty, zcu),
4234 payload_vi.value.size(isel),
4235 );
4236 try payload_vi.value.defCopy(isel, payload_part_vi);
4237 }
4238
4239 const cont_label = isel.instructions.items.len;
4240 var cont_snapshot = try isel.takeLocationSnapshot();
4241 defer cont_snapshot.deinit(isel);
4242 tracking_log.debug("try-continue snapshot taken:\n{f}", .{cont_snapshot});
4243 try isel.body(@ptrCast(isel.air.extra.items[extra.end..][0..extra.data.body_len]));
4244 try cont_snapshot.merge(isel);
4245
4246 const error_set_part_vi = try error_union_vi.partExact(
4247 isel,
4248 codegen.errUnionErrorOffset(payload_ty, zcu),
4249 ZigType.fromInterned(error_union_info.error_set_type).abiSize(zcu),
4250 );
4251 const error_set_part_mat = try error_set_part_vi.matIntRegZeroExt(isel);
4252 try isel.internal_relocs.append(gpa, .{
4253 .label = @intCast(isel.instructions.items.len),
4254 .target = cont_label,
4255 });
4256 try isel.emit(.beqz(error_set_part_mat.reg(), 0, 0));
4257 try error_set_part_mat.finish(isel);
4258 },
4259 .try_ptr, .try_ptr_cold => {
4260 const unwrapped_try = isel.air.unwrapTryPtr(air.inst_index);
4261 const error_union_ty = isel.air.typeOf(unwrapped_try.error_union_ptr, ip).childType(zcu);
4262 const error_union_info = ip.indexToKey(error_union_ty.toIntern()).error_union_type;
4263 const payload_ty: ZigType = .fromInterned(error_union_info.payload_type);
4264
4265 const error_union_ptr_vi = try isel.use(unwrapped_try.error_union_ptr);
4266 if (isel.live_values.fetchRemove(air.inst_index)) |payload_ptr_vi| unused: {
4267 defer payload_ptr_vi.value.deref(isel);
4268
4269 const payload_offset = codegen.errUnionPayloadOffset(unwrapped_try.error_union_payload_ptr_ty.toType().childType(zcu), zcu);
4270 if (payload_offset == 0) {
4271 try payload_ptr_vi.value.defMove(isel, unwrapped_try.error_union_ptr);
4272 } else {
4273 const payload_ptr_reg = try payload_ptr_vi.value.defRegMod(isel, .integer) orelse break :unused;
4274 const error_union_ptr_mat = try error_union_ptr_vi.matIntRegZeroExt(isel);
4275 try isel.addImm(payload_ptr_reg, error_union_ptr_mat.reg(), payload_offset);
4276 try error_union_ptr_mat.finish(isel);
4277 }
4278 }
4279
4280 const cont_label = isel.instructions.items.len;
4281 var cont_snapshot = try isel.takeLocationSnapshot();
4282 defer cont_snapshot.deinit(isel);
4283 tracking_log.debug("try_ptr-continue snapshot taken:\n{f}", .{cont_snapshot});
4284 try isel.body(unwrapped_try.else_body);
4285 try cont_snapshot.merge(isel);
4286
4287 const tmp_reg = try isel.allocRegForWrite(.int);
4288 defer isel.freeReg(tmp_reg);
4289
4290 try isel.internal_relocs.append(gpa, .{
4291 .label = @intCast(isel.instructions.items.len),
4292 .target = cont_label,
4293 });
4294 try isel.emit(.beqz(tmp_reg, 0, 0));
4295
4296 const error_union_ptr_mat = try error_union_ptr_vi.matIntRegZeroExt(isel);
4297 try isel.loadReg(
4298 tmp_reg,
4299 ZigType.fromInterned(error_union_info.error_set_type).abiSize(zcu),
4300 .unsigned,
4301 error_union_ptr_mat.reg(),
4302 codegen.errUnionErrorOffset(payload_ty, zcu),
4303 );
4304 try error_union_ptr_mat.finish(isel);
4305 },
4306 .aggregate_init => if (isel.live_values.fetchRemove(air.inst_index)) |agg_vi| {
4307 defer agg_vi.value.deref(isel);
4308
4309 const ty_pl = air.data(air.inst_index).ty_pl;
4310 const agg_ty = ty_pl.ty.toType();
4311 switch (ip.indexToKey(agg_ty.toIntern())) {
4312 .array_type => |array_type| {
4313 const elem_ty = ZigType.fromInterned(array_type.child);
4314 const elem_size = elem_ty.abiSize(zcu);
4315 const elems: []const Air.Inst.Ref =
4316 @ptrCast(isel.air.extra.items[ty_pl.payload..][0..@intCast(array_type.len)]);
4317 var elem_offset: u64 = 0;
4318
4319 try agg_vi.value.split(isel, false);
4320 for (elems) |elem| {
4321 const agg_part_vi = try agg_vi.value.partExactRecursive(isel, elem_offset, elem_size);
4322 try agg_part_vi.defMove(isel, elem);
4323 elem_offset += elem_size;
4324 }
4325 switch (array_type.sentinel) {
4326 .none => {},
4327 else => |sentinel| {
4328 const agg_part_vi = try agg_vi.value.partExactRecursive(isel, elem_offset, elem_size);
4329 try agg_part_vi.defMove(isel, .fromIntern(sentinel));
4330 },
4331 }
4332 },
4333 .struct_type => {
4334 const loaded_struct = ip.loadStructType(agg_ty.toIntern());
4335 const elems: []const Air.Inst.Ref =
4336 @ptrCast(isel.air.extra.items[ty_pl.payload..][0..loaded_struct.field_types.len]);
4337 var field_offset: u64 = 0;
4338 var field_it = loaded_struct.iterateRuntimeOrder(ip);
4339 while (field_it.next()) |field_index| {
4340 const field_ty: ZigType = .fromInterned(loaded_struct.field_types.get(ip)[field_index]);
4341 field_offset = loaded_struct.field_offsets.get(ip)[field_index];
4342 const field_size = field_ty.abiSize(zcu);
4343 if (field_size == 0) continue;
4344 const agg_part_vi = try agg_vi.value.partExactRecursive(isel, field_offset, field_size);
4345 try agg_part_vi.defMove(isel, elems[field_index]);
4346 field_offset += field_size;
4347 }
4348 assert(loaded_struct.alignment.forward(field_offset) == agg_vi.value.size(isel));
4349 },
4350 .tuple_type => |tuple_type| {
4351 const elems: []const Air.Inst.Ref =
4352 @ptrCast(isel.air.extra.items[ty_pl.payload..][0..tuple_type.types.len]);
4353 var tuple_align: InternPool.Alignment = .@"1";
4354 var field_offset: u64 = 0;
4355 for (
4356 tuple_type.types.get(ip),
4357 tuple_type.values.get(ip),
4358 elems,
4359 ) |field_ty_index, field_val, elem| {
4360 if (field_val != .none) continue;
4361 const field_ty: ZigType = .fromInterned(field_ty_index);
4362 const field_align = field_ty.abiAlignment(zcu);
4363 tuple_align = tuple_align.maxStrict(field_align);
4364 field_offset = field_align.forward(field_offset);
4365 const field_size = field_ty.abiSize(zcu);
4366 if (field_size == 0) continue;
4367 const agg_part_vi = try agg_vi.value.partExactRecursive(isel, field_offset, field_size);
4368 try agg_part_vi.defMove(isel, elem);
4369 field_offset += field_size;
4370 }
4371 assert(tuple_align.forward(field_offset) == agg_vi.value.size(isel));
4372 },
4373 .vector_type => try isel.failUnimplemented("agg init vector", .{}),
4374 else => unreachable,
4375 }
4376 },
4377 .struct_field_ptr => if (isel.live_values.fetchRemove(air.inst_index)) |dst_vi| unused: {
4378 defer dst_vi.value.deref(isel);
4379 const ty_pl = air.data(air.inst_index).ty_pl;
4380 const extra = isel.air.extraData(Air.StructField, ty_pl.payload).data;
4381 switch (codegen.fieldOffset(
4382 isel.air.typeOf(extra.struct_operand, ip),
4383 ty_pl.ty.toType(),
4384 extra.field_index,
4385 zcu,
4386 )) {
4387 0 => try dst_vi.value.defMove(isel, extra.struct_operand),
4388 else => |field_offset| {
4389 const dst_reg = try dst_vi.value.defRegMod(isel, .integer) orelse break :unused;
4390 const src_vi = try isel.use(extra.struct_operand);
4391 const src_mat = try src_vi.matIntRegZeroExt(isel);
4392 try isel.addImm(dst_reg, src_mat.reg(), field_offset);
4393 try src_mat.finish(isel);
4394 },
4395 }
4396 },
4397 .struct_field_ptr_index_0,
4398 .struct_field_ptr_index_1,
4399 .struct_field_ptr_index_2,
4400 .struct_field_ptr_index_3,
4401 => if (isel.live_values.fetchRemove(air.inst_index)) |dst_vi| unused: {
4402 defer dst_vi.value.deref(isel);
4403 const ty_op = air.data(air.inst_index).ty_op;
4404 switch (codegen.fieldOffset(
4405 isel.air.typeOf(ty_op.operand, ip),
4406 ty_op.ty.toType(),
4407 switch (air_tag) {
4408 else => unreachable,
4409 .struct_field_ptr_index_0 => 0,
4410 .struct_field_ptr_index_1 => 1,
4411 .struct_field_ptr_index_2 => 2,
4412 .struct_field_ptr_index_3 => 3,
4413 },
4414 zcu,
4415 )) {
4416 0 => try dst_vi.value.defMove(isel, ty_op.operand),
4417 else => |field_offset| {
4418 const dst_reg = try dst_vi.value.defRegMod(isel, .integer) orelse break :unused;
4419 const src_vi = try isel.use(ty_op.operand);
4420 const src_mat = try src_vi.matIntRegZeroExt(isel);
4421 try isel.addImm(dst_reg, src_mat.reg(), field_offset);
4422 try src_mat.finish(isel);
4423 },
4424 }
4425 },
4426 .agg_field_val => if (isel.live_values.fetchRemove(air.inst_index)) |field_vi| {
4427 defer field_vi.value.deref(isel);
4428
4429 const ty_pl = air.data(air.inst_index).ty_pl;
4430 const extra = isel.air.extraData(Air.StructField, ty_pl.payload).data;
4431 const agg_ty = isel.air.typeOf(extra.struct_operand, ip);
4432 const field_ty = ty_pl.ty.toType();
4433
4434 const field_bit_offset, const field_bit_size, const is_packed = switch (agg_ty.containerLayout(zcu)) {
4435 .auto, .@"extern" => .{
4436 8 * agg_ty.structFieldOffset(extra.field_index, zcu),
4437 8 * field_ty.abiSize(zcu),
4438 false,
4439 },
4440 .@"packed" => .{
4441 if (zcu.typeToPackedStruct(agg_ty)) |loaded_struct|
4442 zcu.structPackedFieldBitOffset(loaded_struct, extra.field_index)
4443 else
4444 0,
4445 field_ty.bitSize(zcu),
4446 true,
4447 },
4448 };
4449 if (is_packed) return isel.fail("packed field of {f}", .{
4450 isel.fmtType(agg_ty),
4451 });
4452
4453 const agg_vi = try isel.use(extra.struct_operand);
4454 switch (agg_ty.zigTypeTag(zcu)) {
4455 else => unreachable,
4456 .@"struct" => {
4457 const agg_part_vi = try agg_vi.partExactRecursive(
4458 isel,
4459 @divExact(field_bit_offset, 8),
4460 @divExact(field_bit_size, 8),
4461 );
4462 try field_vi.value.defCopy(isel, agg_part_vi);
4463 },
4464 .@"union" => {
4465 const agg_part_vi = try agg_vi.partAtLargerThan(
4466 isel,
4467 @divExact(field_bit_offset, 8),
4468 @divExact(field_bit_size, 8),
4469 );
4470 try field_vi.value.defCopy(isel, agg_part_vi);
4471 },
4472 }
4473 },
4474 .union_init => if (isel.live_values.fetchRemove(air.inst_index)) |union_vi| {
4475 defer union_vi.value.deref(isel);
4476
4477 const ty_pl = air.data(air.inst_index).ty_pl;
4478 const extra = isel.air.extraData(Air.UnionInit, ty_pl.payload).data;
4479 const union_ty = ty_pl.ty.toType();
4480 const loaded_union = ip.loadUnionType(union_ty.toIntern());
4481 const union_layout = ZigType.getUnionLayout(loaded_union, zcu);
4482
4483 if (union_layout.tag_size > 0) unused_tag: {
4484 const loaded_tag = ip.loadEnumType(loaded_union.enum_tag_type);
4485 const tag_vi = try union_vi.value.partExact(
4486 isel,
4487 union_layout.tagOffset(),
4488 union_layout.tag_size,
4489 );
4490 if (tag_vi.extension(isel) == .sign_ext)
4491 try tag_vi.reextendToGarbage(isel);
4492 const tag_reg = try tag_vi.defRegMod(isel, .integer) orelse break :unused_tag;
4493 const tag_val: i64 = switch (loaded_tag.field_values.len) {
4494 0 => extra.field_index,
4495 else => switch (ip.indexToKey(loaded_tag.field_values.get(ip)[extra.field_index]).int.storage) {
4496 .u64 => |imm| @bitCast(imm),
4497 .i64 => |imm| imm,
4498 else => unreachable,
4499 },
4500 };
4501 try isel.moveIntImm(tag_reg, tag_val);
4502 }
4503 const payload_vi = try union_vi.value.partExact(
4504 isel,
4505 union_layout.payloadOffset(),
4506 union_layout.payload_size,
4507 );
4508 try payload_vi.defMove(isel, extra.init);
4509 },
4510 .set_union_tag => {
4511 const bin_op = air.data(air.inst_index).bin_op;
4512 const union_ty = isel.air.typeOf(bin_op.lhs, ip).childType(zcu);
4513 const union_layout = union_ty.unionGetLayout(zcu);
4514 const tag_vi = try isel.use(bin_op.rhs);
4515 const union_ptr_vi = try isel.use(bin_op.lhs);
4516 const union_ptr_mat = try union_ptr_vi.matIntRegZeroExt(isel);
4517 try tag_vi.matStore(isel, union_ptr_mat.reg(), union_layout.tagOffset(), .{});
4518 try union_ptr_mat.finish(isel);
4519 },
4520 .get_union_tag => if (isel.live_values.fetchRemove(air.inst_index)) |tag_vi| {
4521 defer tag_vi.value.deref(isel);
4522 const ty_op = air.data(air.inst_index).ty_op;
4523 const union_ty = isel.air.typeOf(ty_op.operand, ip);
4524 const union_layout = union_ty.unionGetLayout(zcu);
4525 const union_vi = try isel.use(ty_op.operand);
4526 const tag_part_vi = try union_vi.partExact(isel, union_layout.tagOffset(), union_layout.tag_size);
4527 try tag_vi.value.defCopy(isel, tag_part_vi);
4528 },
4529 .optional_payload => if (isel.live_values.fetchRemove(air.inst_index)) |payload_vi| unused: {
4530 defer payload_vi.value.deref(isel);
4531
4532 const ty_op = air.data(air.inst_index).ty_op;
4533 const opt_ty = isel.air.typeOf(ty_op.operand, ip);
4534 if (opt_ty.optionalReprIsPayload(zcu)) {
4535 try payload_vi.value.defMove(isel, ty_op.operand);
4536 break :unused;
4537 }
4538
4539 const opt_vi = try isel.use(ty_op.operand);
4540 const payload_part_vi = try opt_vi.partExact(isel, 0, payload_vi.value.size(isel));
4541 try payload_vi.value.defCopy(isel, payload_part_vi);
4542 },
4543 .optional_payload_ptr => if (isel.live_values.fetchRemove(air.inst_index)) |payload_ptr_vi| {
4544 defer payload_ptr_vi.value.deref(isel);
4545 const ty_op = air.data(air.inst_index).ty_op;
4546 try payload_ptr_vi.value.defMove(isel, ty_op.operand);
4547 },
4548 .wrap_optional => if (isel.live_values.fetchRemove(air.inst_index)) |opt_vi| unused: {
4549 defer opt_vi.value.deref(isel);
4550
4551 const ty_op = air.data(air.inst_index).ty_op;
4552 if (ty_op.ty.toType().optionalReprIsPayload(zcu)) {
4553 try opt_vi.value.defMove(isel, ty_op.operand);
4554 break :unused;
4555 }
4556
4557 const payload_size = isel.air.typeOf(ty_op.operand, ip).abiSize(zcu);
4558
4559 const payload_part_vi = try opt_vi.value.partExact(isel, 0, payload_size);
4560 const has_value_part_vi = try opt_vi.value.partExact(isel, payload_size, 1);
4561 try payload_part_vi.defMove(isel, ty_op.operand);
4562 const maybe_has_value_part_reg = try has_value_part_vi.defRegMod(isel, .integer);
4563 if (maybe_has_value_part_reg) |has_value_part_reg|
4564 try isel.emit(.ori(has_value_part_reg, .zero, 0));
4565 },
4566 .field_parent_ptr => if (isel.live_values.fetchRemove(air.inst_index)) |dst_vi| unused: {
4567 defer dst_vi.value.deref(isel);
4568 const ty_pl = air.data(air.inst_index).ty_pl;
4569 const extra = isel.air.extraData(Air.FieldParentPtr, ty_pl.payload).data;
4570 switch (codegen.fieldOffset(
4571 ty_pl.ty.toType(),
4572 isel.air.typeOf(extra.field_ptr, ip),
4573 extra.field_index,
4574 zcu,
4575 )) {
4576 0 => try dst_vi.value.defMove(isel, extra.field_ptr),
4577 else => |field_offset| {
4578 const dst_reg = try dst_vi.value.defRegMod(isel, .integer) orelse break :unused;
4579 const src_vi = try isel.use(extra.field_ptr);
4580 const src_mat = try src_vi.matIntRegZeroExt(isel);
4581 try isel.addImm(dst_reg, src_mat.reg(), -@as(i65, field_offset));
4582 try src_mat.finish(isel);
4583 },
4584 }
4585 },
4586 .unwrap_errunion_payload => if (isel.live_values.fetchRemove(air.inst_index)) |payload_vi| {
4587 defer payload_vi.value.deref(isel);
4588
4589 const ty_op = air.data(air.inst_index).ty_op;
4590 const error_union_vi = try isel.use(ty_op.operand);
4591 try payload_vi.value.defCopy(
4592 isel,
4593 try error_union_vi.partExact(
4594 isel,
4595 codegen.errUnionPayloadOffset(ty_op.ty.toType(), zcu),
4596 payload_vi.value.size(isel),
4597 ),
4598 );
4599 },
4600 .unwrap_errunion_err => if (isel.live_values.fetchRemove(air.inst_index)) |error_set_vi| {
4601 defer error_set_vi.value.deref(isel);
4602
4603 const ty_op = air.data(air.inst_index).ty_op;
4604 const error_union_ty = isel.air.typeOf(ty_op.operand, ip);
4605 const error_union_vi = try isel.use(ty_op.operand);
4606 try error_set_vi.value.defCopy(
4607 isel,
4608 try error_union_vi.partExact(
4609 isel,
4610 codegen.errUnionErrorOffset(error_union_ty.errorUnionPayload(zcu), zcu),
4611 error_set_vi.value.size(isel),
4612 ),
4613 );
4614 },
4615 .wrap_errunion_payload => if (isel.live_values.fetchRemove(air.inst_index)) |error_union_vi| {
4616 defer error_union_vi.value.deref(isel);
4617
4618 const ty_op = air.data(air.inst_index).ty_op;
4619 const error_union_ty = ty_op.ty.toType();
4620 const error_union_info = ip.indexToKey(error_union_ty.toIntern()).error_union_type;
4621 const error_set_ty: ZigType = .fromInterned(error_union_info.error_set_type);
4622 const payload_ty: ZigType = .fromInterned(error_union_info.payload_type);
4623 const error_set_offset = codegen.errUnionErrorOffset(payload_ty, zcu);
4624 const payload_offset = codegen.errUnionPayloadOffset(payload_ty, zcu);
4625 const error_set_size = error_set_ty.abiSize(zcu);
4626 const payload_size = payload_ty.abiSize(zcu);
4627
4628 try error_union_vi.value.collectDefs(isel);
4629
4630 if (payload_size > 0) {
4631 const payload_part_vi = try error_union_vi.value.partExact(isel, payload_offset, payload_size);
4632 try payload_part_vi.defMove(isel, ty_op.operand);
4633 }
4634 const error_set_part_vi = try error_union_vi.value.partExact(isel, error_set_offset, error_set_size);
4635 if (try error_set_part_vi.defRegMod(isel, .integer)) |error_set_part_reg|
4636 try isel.emit(.ori(error_set_part_reg, .zero, 0));
4637 },
4638 .wrap_errunion_err => if (isel.live_values.fetchRemove(air.inst_index)) |error_union_vi| {
4639 defer error_union_vi.value.deref(isel);
4640
4641 const ty_op = air.data(air.inst_index).ty_op;
4642 const error_union_ty = ty_op.ty.toType();
4643 const error_union_info = ip.indexToKey(error_union_ty.toIntern()).error_union_type;
4644 const error_set_ty: ZigType = .fromInterned(error_union_info.error_set_type);
4645 const payload_ty: ZigType = .fromInterned(error_union_info.payload_type);
4646 const error_set_offset = codegen.errUnionErrorOffset(payload_ty, zcu);
4647 const payload_offset = codegen.errUnionPayloadOffset(payload_ty, zcu);
4648 const error_set_size = error_set_ty.abiSize(zcu);
4649 const payload_size = payload_ty.abiSize(zcu);
4650
4651 const error_set_part_vi = try error_union_vi.value.partExact(isel, error_set_offset, error_set_size);
4652 try error_set_part_vi.defMove(isel, ty_op.operand);
4653 if (payload_size > 0) {
4654 const payload_part_vi = try error_union_vi.value.partExact(isel, payload_offset, payload_size);
4655 try payload_part_vi.defUndef(isel);
4656 }
4657 },
4658 .errunion_payload_ptr_set => if (isel.live_values.fetchRemove(air.inst_index)) |payload_ptr_vi| unused: {
4659 defer payload_ptr_vi.value.deref(isel);
4660 const ty_op = air.data(air.inst_index).ty_op;
4661 const payload_ty = ty_op.ty.toType().childType(zcu);
4662 const eu_ty = isel.air.typeOf(ty_op.operand, ip).childType(zcu);
4663 const error_set_size = eu_ty.errorUnionSet(zcu).abiSize(zcu);
4664
4665 const eu_ptr_vi = try isel.use(ty_op.operand);
4666 const error_union_ptr_mat = try eu_ptr_vi.matIntRegZeroExt(isel);
4667 if (error_set_size != 0) {
4668 try isel.storeReg(
4669 .zero,
4670 error_set_size,
4671 error_union_ptr_mat.reg(),
4672 codegen.errUnionErrorOffset(payload_ty, zcu),
4673 );
4674 }
4675 const payload_offset = codegen.errUnionPayloadOffset(payload_ty, zcu);
4676 if (payload_offset == 0) {
4677 try error_union_ptr_mat.finish(isel);
4678 try payload_ptr_vi.value.defMove(isel, ty_op.operand);
4679 } else {
4680 const payload_ptr_reg = try payload_ptr_vi.value.defRegMod(isel, .integer) orelse break :unused;
4681 try isel.addImm(payload_ptr_reg, error_union_ptr_mat.reg(), payload_offset);
4682 try error_union_ptr_mat.finish(isel);
4683 }
4684 },
4685 }
4686 if (air_tag != .arg) {
4687 var live_reg_it = isel.live_registers.iterator();
4688 while (live_reg_it.next()) |live_reg_entry| switch (live_reg_entry.value.*) {
4689 .allocating => {
4690 tracking_log.err("${t} is still allocated", .{live_reg_entry.key});
4691 isel.dumpValues(.all);
4692 unreachable;
4693 },
4694 _, .free => {},
4695 };
4696 }
4697 if (debug_r21_as_air) {
4698 try isel.moveIntImm(.r21, @backingInt(air.inst_index));
4699 }
4700 }
4701 assert(air.body_index == 0);
4702}
4703
4704/// Generates prologue and epilogue. Returns the length of epilogue.
4705///
4706/// Stack Frame Layout
4707/// +-+-----------------------------------+
4708/// |R| caller frame |
4709/// +-+-----------------------------------+
4710/// |S| incoming stack arguments | +---------------+
4711/// +-+-----------------------------------+ <-| align(16) |
4712/// |L| callee saved FP | | entry/exit SP |
4713/// +-+-----------------------------------+ | FP |
4714/// |L| callee saved GPR area | +---------------+
4715/// +-+-----------------------------------+
4716/// |L| callee saved FPR area | +-----------------+
4717/// +-+-----------------------------------+ <-| FP - saves_size |
4718/// |L| realignment gap | +-----------------+
4719/// +-+-----------------------------------+ <-| align(16) |
4720/// |L| locals | +-----------------+
4721/// +-+-----------------------------------+
4722/// |S| outgoing stack arguments | +----+
4723/// +-+-----------------------------------+ <-| SP |
4724/// +----+
4725/// [S] Size computed by `analyze`, can be used by the body.
4726/// [L] Size computed by `layout`, can be used by the prologue/epilogue.
4727/// [R] Size unknown until runtime, can vary from one call to the next.
4728///
4729/// FP saving/restoring is not yet implemented.
4730pub fn layout(isel: *Select, cc_it: CallAbiIterator, mod: *const Module) !usize {
4731 _ = cc_it;
4732 _ = mod;
4733 const zcu = isel.pt.zcu;
4734 const ip = &zcu.intern_pool;
4735 const nav = ip.getNav(isel.nav_index);
4736 wip_mir_log.debug("{f}<body>:\n", .{nav.fqn.fmt(ip)});
4737
4738 const gpr_size = isel.gprSize();
4739
4740 var saves_buf: [10 + 2 + 8]struct {
4741 register: Register,
4742 needs_restore: bool,
4743 offset: u11,
4744 size: u5,
4745 } = undefined;
4746 var saved_offset: std.EnumArray(Register, u11) = .initUndefined();
4747 const saves, const saves_size = saves: {
4748 var saves_len: usize = 0;
4749 var saves_size: u11 = 0;
4750 var save_reg: Register = undefined;
4751
4752 // callee saved GPR area
4753 save_reg = .r23;
4754 while (true) : (save_reg = @fromBackingInt(@backingInt(save_reg) + 1)) {
4755 if (isel.saved_registers.contains(save_reg)) {
4756 saves_size = std.mem.alignForward(u11, saves_size, gpr_size);
4757 saves_buf[saves_len] = .{
4758 .register = save_reg,
4759 .needs_restore = true,
4760 .offset = saves_size,
4761 .size = gpr_size,
4762 };
4763 saved_offset.set(save_reg, saves_size);
4764 saves_len += 1;
4765 saves_size += gpr_size;
4766 }
4767 if (save_reg == .r31) break;
4768 }
4769 inline for (.{ Register.ra, Register.fp }) |reg| {
4770 if (isel.saved_registers.contains(reg)) {
4771 saves_size = std.mem.alignForward(u11, saves_size, gpr_size);
4772 saves_buf[saves_len] = .{
4773 .register = reg,
4774 .needs_restore = true,
4775 .offset = saves_size,
4776 .size = gpr_size,
4777 };
4778 saved_offset.set(reg, saves_size);
4779 saves_len += 1;
4780 saves_size += gpr_size;
4781 }
4782 }
4783
4784 // callee saved FPR area
4785 save_reg = .f24;
4786 while (true) : (save_reg = @fromBackingInt(@backingInt(save_reg) + 1)) {
4787 if (isel.saved_registers.contains(save_reg)) {
4788 saves_size = std.mem.alignForward(u11, saves_size, 8);
4789 saves_buf[saves_len] = .{
4790 .register = save_reg,
4791 .needs_restore = true,
4792 .offset = saves_size,
4793 .size = 8,
4794 };
4795 saved_offset.set(save_reg, saves_size);
4796 saves_len += 1;
4797 saves_size += 8;
4798 }
4799 if (save_reg == .f31) break;
4800 }
4801 break :saves .{ saves_buf[0..saves_len], std.mem.Alignment.@"16".forward(saves_size) };
4802 };
4803
4804 const stack_frame_size = isel.stack_align.forward(saves_size + isel.stack_size);
4805
4806 // apply layout relocs
4807 for (isel.layout_relocs.items) |label| {
4808 const instruction = isel.instructions.items[label];
4809 const rj: Register = .decode(.int, instruction.DJUk12.rj);
4810 if (isel.saved_registers.contains(rj)) {
4811 const rd: Register = .decode(.int, instruction.DJUk12.rd);
4812 const offset = saved_offset.get(rj);
4813 isel.instructions.items[label] = switch (gpr_size) {
4814 else => unreachable,
4815 4 => .@"ld.w"(rd, .sp, @intCast(stack_frame_size - 8 - offset)),
4816 8 => .@"ld.d"(rd, .sp, @intCast(stack_frame_size - 8 - offset)),
4817 };
4818 }
4819 }
4820
4821 // prologue
4822 {
4823 // move SP
4824 if (stack_frame_size == 0) {} else if (std.math.cast(i12, stack_frame_size)) |stack_size12| {
4825 switch (gpr_size) {
4826 4 => try isel.emit(.@"addi.w"(.sp, .sp, -stack_size12)),
4827 8 => try isel.emit(.@"addi.d"(.sp, .sp, -stack_size12)),
4828 else => unreachable,
4829 }
4830 } else {
4831 switch (gpr_size) {
4832 4 => try isel.emit(.@"sub.w"(.sp, .sp, .t0)),
4833 8 => try isel.emit(.@"sub.d"(.sp, .sp, .t0)),
4834 else => unreachable,
4835 }
4836 try isel.moveIntImm(.t0, @intCast(stack_frame_size));
4837 }
4838
4839 // set FP
4840 if (isel.saved_registers.contains(.fp))
4841 try isel.emit(.ori(.fp, .sp, 0));
4842
4843 // save registers
4844 for (saves) |save| {
4845 switch (save.register.class()) {
4846 .int => switch (gpr_size) {
4847 4 => try isel.emit(.@"st.h"(save.register, .sp, -8 - @as(i12, save.offset))),
4848 8 => try isel.emit(.@"st.d"(save.register, .sp, -8 - @as(i12, save.offset))),
4849 else => unreachable,
4850 },
4851 .fp => try isel.emit(.@"fst.d"(save.register, .sp, -8 - @as(i12, save.offset))),
4852 .fcc => unreachable,
4853 }
4854 }
4855 wip_mir_log.debug("{f}<prologue>:", .{nav.fqn.fmt(ip)});
4856 }
4857
4858 // epilogue
4859 const epilogue = isel.instructions.items.len;
4860 if (isel.returns) {
4861 // return
4862 try isel.emit(.jirl(.zero, .ra, 0));
4863
4864 // restore registers
4865 for (saves) |save| {
4866 if (!save.needs_restore) continue;
4867 switch (save.register.class()) {
4868 .int => switch (gpr_size) {
4869 4 => try isel.emit(.@"ld.h"(save.register, .sp, -8 - @as(i12, save.offset))),
4870 8 => try isel.emit(.@"ld.d"(save.register, .sp, -8 - @as(i12, save.offset))),
4871 else => unreachable,
4872 },
4873 .fp => try isel.emit(.@"fld.d"(save.register, .sp, -8 - @as(i12, save.offset))),
4874 .fcc => unreachable,
4875 }
4876 }
4877
4878 // restore SP
4879 if (stack_frame_size == 0) {} else if (std.math.cast(i12, stack_frame_size)) |stack_size12| {
4880 switch (gpr_size) {
4881 4 => try isel.emit(.@"addi.w"(.sp, .sp, stack_size12)),
4882 8 => try isel.emit(.@"addi.d"(.sp, .sp, stack_size12)),
4883 else => unreachable,
4884 }
4885 } else {
4886 switch (gpr_size) {
4887 4 => try isel.emit(.@"add.w"(.sp, .sp, .t0)),
4888 8 => try isel.emit(.@"add.d"(.sp, .sp, .t0)),
4889 else => unreachable,
4890 }
4891 try isel.moveIntImm(.t0, @intCast(stack_frame_size));
4892 }
4893
4894 wip_mir_log.debug("{f}<epilogue>:\n", .{nav.fqn.fmt(ip)});
4895 }
4896 return epilogue;
4897}
4898
4899fn emit(isel: *Select, instruction: Instruction) !void {
4900 wip_mir_log.debug(" | {f}", .{(Disassemble{}).fmtInstruction(instruction)});
4901 try isel.instructions.append(isel.pt.zcu.gpa, instruction);
4902}
4903
4904pub fn verifyTargetFeatures(isel: *Select) !void {
4905 if (!verify_target_features) return;
4906
4907 for (isel.instructions.items) |inst| {
4908 if (Disassemble.decodeMnemonic(inst)) |decoded_mnemonic| {
4909 switch (decoded_mnemonic) {
4910 inline else => |mnemonic| {
4911 const expected_features = @field(@import("inst_formats.zon").instructions, @tagName(mnemonic)).features;
4912 inline for (@typeInfo(expected_features).@"struct".fields) |expected_feature_field| {
4913 const expected_feature = @tagName(@field(expected_features, expected_feature_field.name));
4914 const std_feature = @field(std.Target.loongarch.Feature, expected_feature);
4915 if (!isel.hasCpuFeature(std_feature)) {
4916 wip_mir_log.err("emitted instruction {t} requires feature {t} which is not available", .{ mnemonic, std_feature });
4917 unreachable;
4918 }
4919 }
4920 },
4921 }
4922 } else {
4923 wip_mir_log.err("invalid instruction was emitted in Select: {x}", .{inst.word});
4924 unreachable;
4925 }
4926 }
4927}
4928
4929fn hasCpuFeature(isel: *Select, feature: std.Target.loongarch.Feature) bool {
4930 return std.Target.loongarch.featureSetHas(isel.target.cpu.features, feature);
4931}
4932
4933fn block(
4934 isel: *Select,
4935 air_inst_index: Air.Inst.Index,
4936 res_ty: ZigType,
4937 air_body: []const Air.Inst.Index,
4938) !void {
4939 if (res_ty.toIntern() != .noreturn_type) {
4940 const snapshot = try isel.takeLocationSnapshot();
4941 tracking_log.debug("block snapshot taken:\n{f}", .{snapshot});
4942 isel.active_blocks.putAssumeCapacityNoClobber(air_inst_index, .{
4943 .snapshot = snapshot,
4944 .target_label = @intCast(isel.instructions.items.len),
4945 });
4946 }
4947 try isel.body(air_body);
4948 if (res_ty.toIntern() != .noreturn_type) {
4949 var block_entry = isel.active_blocks.pop().?;
4950 assert(block_entry.key == air_inst_index);
4951 block_entry.value.deinit(isel);
4952 if (isel.live_values.fetchRemove(air_inst_index)) |result_vi| {
4953 var res_walk = result_vi.value.walk(isel, .{});
4954 while (res_walk.next()) |res_part_vi|
4955 _ = res_part_vi.takeLocationMarkWritten(isel);
4956 result_vi.value.deref(isel);
4957 }
4958 }
4959}
4960
4961fn initValue(isel: *Select, ty: ZigType) error{OutOfMemory}!Value.Index {
4962 const zcu = isel.pt.zcu;
4963 try isel.values.ensureUnusedCapacity(zcu.gpa, 1);
4964 try isel.value_types.ensureUnusedCapacity(zcu.gpa, 1);
4965 return isel.initValueAdvanced(ty.abiAlignment(zcu), 0, ty.abiSize(zcu), ty);
4966}
4967
4968fn initValueAssumeCapacity(isel: *Select, ty: ZigType) Value.Index {
4969 const zcu = isel.pt.zcu;
4970 return isel.initValueAdvanced(ty.abiAlignment(zcu), 0, ty.abiSize(zcu), ty);
4971}
4972
4973fn initValueAdvanced(
4974 isel: *Select,
4975 parent_alignment: InternPool.Alignment,
4976 offset_from_parent: u64,
4977 size: u64,
4978 ty: ?ZigType,
4979) Value.Index {
4980 defer isel.values.addOneAssumeCapacity().* = .{
4981 .refs = 0,
4982 .flags = .{
4983 .alignment = .fromLog2Units(@min(parent_alignment.toLog2Units(), @ctz(offset_from_parent))),
4984 .parent_tag = .none,
4985 // TODO size < 32 when vectors are supported
4986 .location_tag = if (size <= 8)
4987 .small
4988 else if (std.math.cast(u32, size) != null)
4989 .large
4990 else
4991 .extreme,
4992 .parts_len_minus_one = 0,
4993 .splitted = false,
4994 },
4995 .offset_from_parent = offset_from_parent,
4996 .parent_payload = .{ .none = {} },
4997 // TODO ditto
4998 .location_payload = if (size <= 8) .{ .small = .{
4999 .flags = .{
5000 .size = @intCast(size),
5001 .extension = .garbage,
5002 .hint_modifier = .integer,
5003 .hint_register = .zero,
5004 .location_tag = .register,
5005 },
5006 .location_payload = .{ .register = .zero },
5007 } } else if (std.math.cast(u32, size)) |size32| .{ .large = .{
5008 .size = size32,
5009 .stack_slot = .unallocated,
5010 } } else .{ .extreme = .{ .size = size } },
5011 .parts = undefined,
5012 };
5013 defer isel.value_types.appendAssumeCapacity(ty orelse .{ .ip_index = .none });
5014 return @fromBackingInt(@intCast(isel.values.items.len));
5015}
5016
5017const WhichValues = enum { only_referenced, all };
5018pub fn dumpValues(isel: *Select, which: WhichValues) void {
5019 dumpValuesInner(isel, which) catch |err| @panic(@errorName(err));
5020}
5021fn dumpValuesInner(isel: *Select, which: WhichValues) !void {
5022 const zcu = isel.pt.zcu;
5023 const gpa = zcu.gpa;
5024 const ip = &zcu.intern_pool;
5025 const nav = ip.getNav(isel.nav_index);
5026
5027 const locked_stderr = std.debug.lockStderr(&.{});
5028 defer std.debug.unlockStderr();
5029 const stderr = &locked_stderr.file_writer.interface;
5030
5031 var reverse_live_values: std.AutoArrayHashMapUnmanaged(Value.Index, std.ArrayList(Air.Inst.Index)) = .empty;
5032 defer {
5033 for (reverse_live_values.values()) |*list| list.deinit(gpa);
5034 reverse_live_values.deinit(gpa);
5035 }
5036 {
5037 try reverse_live_values.ensureTotalCapacity(gpa, isel.live_values.count());
5038 var live_val_it = isel.live_values.iterator();
5039 while (live_val_it.next()) |live_val_entry| switch (live_val_entry.value_ptr.*) {
5040 _ => {
5041 const gop = reverse_live_values.getOrPutAssumeCapacity(live_val_entry.value_ptr.*);
5042 if (!gop.found_existing) gop.value_ptr.* = .empty;
5043 try gop.value_ptr.append(gpa, live_val_entry.key_ptr.*);
5044 },
5045 .allocating, .free => unreachable,
5046 };
5047 }
5048
5049 var reverse_live_registers: std.AutoHashMapUnmanaged(Value.Index, Register) = .empty;
5050 defer reverse_live_registers.deinit(gpa);
5051 {
5052 try reverse_live_registers.ensureTotalCapacity(gpa, @typeInfo(Register).@"enum".field_names.len);
5053 var live_reg_it = isel.live_registers.iterator();
5054 while (live_reg_it.next()) |live_reg_entry| switch (live_reg_entry.value.*) {
5055 _ => reverse_live_registers.putAssumeCapacityNoClobber(live_reg_entry.value.*, live_reg_entry.key),
5056 .allocating, .free => {},
5057 };
5058 }
5059
5060 var roots: std.AutoArrayHashMapUnmanaged(Value.Index, u32) = .empty;
5061 defer roots.deinit(gpa);
5062 {
5063 try roots.ensureTotalCapacity(gpa, isel.values.items.len);
5064 var vi: Value.Index = @fromBackingInt(@intCast(isel.values.items.len));
5065 iter_values: while (@backingInt(vi) > 0) {
5066 vi = @fromBackingInt(@backingInt(vi) - 1);
5067 if (which == .only_referenced and vi.get(isel).refs == 0) continue;
5068 switch (vi.parent(isel)) {
5069 .none, .constant => {},
5070 .value => continue :iter_values,
5071 .address => |address_vi| roots.putAssumeCapacity(address_vi, 0),
5072 }
5073 roots.putAssumeCapacity(vi, 0);
5074 }
5075 }
5076
5077 try stderr.print("# Begin LA ISelect Value Dump: {f}:\n", .{nav.fqn.fmt(ip)});
5078 while (roots.pop()) |root_entry| {
5079 const vi = root_entry.key;
5080 try stderr.splatByteAll(' ', 2 * (@as(usize, 1) + root_entry.value));
5081 try vi.format(stderr);
5082 {
5083 var first = true;
5084 if (reverse_live_values.get(vi)) |aiis| for (aiis.items) |aii| {
5085 if (aii == Block.main) {
5086 try stderr.print("{s}%main", .{if (first) " <- " else ", "});
5087 } else {
5088 try stderr.print("{s}%{d}", .{ if (first) " <- " else ", ", @backingInt(aii) });
5089 }
5090 first = false;
5091 };
5092 if (reverse_live_registers.get(vi)) |ra| {
5093 try stderr.print("{s}{t}", .{ if (first) " <- " else ", ", ra });
5094 first = false;
5095 }
5096 }
5097 try stderr.writeByte(':');
5098 try isel.printValueInfo(stderr, vi);
5099 try stderr.writeByte('\n');
5100
5101 const value = vi.get(isel);
5102 var part_index = value.flags.parts_len_minus_one;
5103 if (part_index > 0) while (true) : (part_index -= 1) {
5104 try roots.put(
5105 gpa,
5106 @fromBackingInt(@backingInt(value.parts) + part_index),
5107 root_entry.value + 1,
5108 );
5109 if (part_index == 0) break;
5110 };
5111 }
5112 try stderr.print("# End LA ISelect Value Dump: {f}\n", .{nav.fqn.fmt(ip)});
5113}
5114
5115fn printValueAndParts(isel: *Select, writer: *std.Io.Writer, target_vi: Value.Index) !void {
5116 const zcu = isel.pt.zcu;
5117 const gpa = zcu.gpa;
5118
5119 var roots: std.AutoArrayHashMapUnmanaged(Value.Index, u32) = .empty;
5120 defer roots.deinit(gpa);
5121
5122 var root_vi = target_vi;
5123 while (true) switch (root_vi.parent(isel)) {
5124 .none, .constant => break,
5125 .value => |parent_vi| root_vi = parent_vi,
5126 .address => |address_vi| break try roots.put(gpa, address_vi, 0),
5127 };
5128 try roots.put(gpa, root_vi, 0);
5129
5130 while (roots.pop()) |root_entry| {
5131 const vi = root_entry.key;
5132 try writer.splatByteAll(' ', 2 * root_entry.value);
5133 try vi.format(writer);
5134 try writer.writeByte(':');
5135 try isel.printValueInfo(writer, vi);
5136
5137 const value = vi.get(isel);
5138 var part_index = value.flags.parts_len_minus_one;
5139 if (part_index > 0) while (true) : (part_index -= 1) {
5140 try roots.put(
5141 gpa,
5142 @fromBackingInt(@backingInt(value.parts) + part_index),
5143 root_entry.value + 1,
5144 );
5145 if (part_index == 0) break;
5146 };
5147
5148 if (roots.count() != 0)
5149 try writer.writeByte('\n');
5150 }
5151}
5152
5153fn printValueInfo(isel: *Select, writer: *std.Io.Writer, vi: Value.Index) !void {
5154 const zcu = isel.pt.zcu;
5155
5156 const value = vi.get(isel);
5157 switch (value.flags.parent_tag) {
5158 .none => {},
5159 .value => try writer.print(" {f}+0x{x}", .{ value.parent_payload.value, value.offset_from_parent }),
5160 .address => try writer.print(" {f}[0x{x}]", .{ value.parent_payload.address, value.offset_from_parent }),
5161 .constant => try writer.print(" <{f}, {f}>", .{
5162 isel.fmtType(value.parent_payload.constant.typeOf(zcu)),
5163 isel.fmtConstant(value.parent_payload.constant),
5164 }),
5165 }
5166 try writer.print(" align({s})", .{@tagName(value.flags.alignment)});
5167 switch (value.flags.location_tag) {
5168 .small => {
5169 const loc_info = value.location_payload.small;
5170 try writer.print(" {d}B", .{loc_info.flags.size});
5171 if (loc_info.flags.extension != .garbage) try writer.print(" {t}", .{loc_info.flags.extension});
5172
5173 var hints: u8 = 0;
5174 if (loc_info.flags.hint_modifier != .integer) hints += 1;
5175 if (loc_info.flags.hint_register != Register.zero) hints += 1;
5176 if (hints != 0) try writer.writeAll(" hint=");
5177 if (loc_info.flags.hint_modifier != .integer) try writer.print("{t}", .{loc_info.flags.hint_modifier});
5178 if (loc_info.flags.hint_register != Register.zero) try writer.print("{s}${t}", .{ if (hints != 1) "," else "", loc_info.flags.hint_register });
5179
5180 switch (loc_info.flags.location_tag) {
5181 .register => {
5182 if (loc_info.location_payload.register.reg != Register.zero) {
5183 try writer.print(" loc={f}", .{loc_info.location_payload.register});
5184 }
5185 },
5186 .stack_slot => try writer.print(" loc={f}", .{loc_info.location_payload.stack_slot}),
5187 }
5188 },
5189 .large => {
5190 try writer.print(" {d}B large", .{value.location_payload.large.size});
5191 if (value.location_payload.large.stack_slot != Value.Indirect.unallocated)
5192 try writer.print(" loc={f}", .{value.location_payload.large.stack_slot});
5193 },
5194 .extreme => try writer.print(" {d}B extreme", .{value.location_payload.large.size}),
5195 }
5196 if (value.flags.splitted)
5197 try writer.writeAll(" splitted");
5198 if (value.refs != 0)
5199 try writer.print(" refs={d}", .{value.refs});
5200 if (vi.typeOf(isel)) |ty| try writer.print(" {f}", .{isel.fmtType(ty)});
5201}
5202
5203fn fmtValue(isel: *Select, vi: Value.Index) struct {
5204 isel: *Select,
5205 vi: Value.Index,
5206 pub fn format(data: @This(), writer: *std.Io.Writer) std.Io.Writer.Error!void {
5207 data.isel.printValueAndParts(writer, data.vi) catch |err| switch (err) {
5208 error.OutOfMemory => try writer.writeAll("OOM"),
5209 error.WriteFailed => return error.WriteFailed,
5210 };
5211 }
5212} {
5213 return .{ .isel = isel, .vi = vi };
5214}
5215
5216fn fmtLoopLive(isel: *Select, loop_inst: Air.Inst.Index) struct {
5217 isel: *Select,
5218 inst: Air.Inst.Index,
5219 pub fn format(data: @This(), writer: *std.Io.Writer) std.Io.Writer.Error!void {
5220 const loops = data.isel.loops.values();
5221 const loop_index = data.isel.loops.getIndex(data.inst).?;
5222 const live_insts =
5223 data.isel.loop_outer_live.list.items[loops[loop_index].outer_live..loops[loop_index + 1].outer_live];
5224
5225 try writer.print("%{d} <- {{", .{@backingInt(data.inst)});
5226 var first = true;
5227 for (live_insts) |live_inst| {
5228 if (first) first = false else try writer.writeByte(',');
5229 try writer.print(" %{d}", .{@backingInt(live_inst)});
5230 }
5231 if (!first) try writer.writeByte(' ');
5232 try writer.writeByte('}');
5233 }
5234} {
5235 return .{ .isel = isel, .inst = loop_inst };
5236}
5237
5238fn fmtType(isel: *Select, ty: ZigType) ZigType.Formatter {
5239 return ty.fmt(isel.pt);
5240}
5241
5242fn fmtConstant(isel: *Select, constant: Constant) @typeInfo(@TypeOf(Constant.fmtValue)).@"fn".return_type.? {
5243 return constant.fmtValue(isel.pt);
5244}
5245
5246fn fmtRegisterSet(regs: RegisterSet) struct {
5247 regs: RegisterSet,
5248 pub fn format(data: @This(), writer: *std.Io.Writer) std.Io.Writer.Error!void {
5249 var it = data.regs.iterator();
5250 var first = true;
5251 while (it.next()) |reg| {
5252 if (first) first = false else try writer.writeAll(", ");
5253 try writer.print("${t}", .{reg});
5254 }
5255 if (first) try writer.writeAll("(empty)");
5256 }
5257} {
5258 return .{ .regs = regs };
5259}
5260
5261fn use(isel: *Select, air_ref: Air.Inst.Ref) !Value.Index {
5262 const zcu = isel.pt.zcu;
5263 const ip = &zcu.intern_pool;
5264 const vi, const ty = if (air_ref.toIndex()) |air_inst_index| vi_ty: {
5265 const live_gop = try isel.live_values.getOrPut(zcu.gpa, air_inst_index);
5266 if (live_gop.found_existing) return live_gop.value_ptr.*;
5267 const ty = isel.air.typeOf(air_ref, ip);
5268 const vi = try isel.initValue(ty);
5269 tracking_log.debug("{f} <- %{d}", .{ vi, @backingInt(air_inst_index) });
5270 live_gop.value_ptr.* = vi.ref(isel);
5271 break :vi_ty .{ vi, ty };
5272 } else vi_ty: {
5273 const constant: Constant = .fromInterned(air_ref.toInterned().?);
5274 const ty = constant.typeOf(zcu);
5275 const vi = try isel.initValue(ty);
5276 tracking_log.debug("{f} <- <{f}, {f}>", .{
5277 vi,
5278 isel.fmtType(ty),
5279 isel.fmtConstant(constant),
5280 });
5281 vi.setParent(isel, .{ .constant = constant });
5282 break :vi_ty .{ vi, ty };
5283 };
5284 if (ty.isAbiInt(zcu)) {
5285 const int_info = ty.intInfo(zcu);
5286 if (int_info.bits <= 16) vi.setExtension(isel, .fromSignedness(int_info.signedness));
5287 }
5288 return vi;
5289}
5290
5291// TODO: make r22 allocatable
5292fn isRegisterAllocatable(rd: Register) bool {
5293 return switch (rd) {
5294 else => true,
5295 Register.zero, Register.tp, Register.sp, Register.fp, .r21 => false,
5296 };
5297}
5298
5299/// Frees a register by forgetting it.
5300/// Returns true on success, false on failure (i.e. dst_reg is locked/allocated or unallocatable).
5301fn forgetReg(isel: *Select, dst_reg: Register) error{ OutOfMemory, AlreadyReported }!bool {
5302 if (!isRegisterAllocatable(dst_reg)) return false;
5303 const dst_live_vi = isel.live_registers.getPtr(dst_reg);
5304 const dst_vi = switch (dst_live_vi.*) {
5305 _ => |dst_vi| dst_vi,
5306 .allocating => return false,
5307 .free => return true,
5308 };
5309 tracking_log.debug("{f} -> location forgotten", .{dst_vi});
5310 _ = dst_vi.takeLocation(isel);
5311 assert(dst_live_vi.* == .free);
5312 return true;
5313}
5314
5315/// Frees a register by moving it to another place.
5316/// Returns true on success, false on failure (i.e. dst_reg is locked/allocated or unallocatable).
5317fn fillReg(isel: *Select, dst_reg: Register) error{ OutOfMemory, AlreadyReported }!bool {
5318 if (!isRegisterAllocatable(dst_reg)) return false;
5319 const dst_live_vi = isel.live_registers.getPtr(dst_reg);
5320 const dst_vi = switch (dst_live_vi.*) {
5321 _ => |dst_vi| dst_vi,
5322 .allocating => return false,
5323 .free => return true,
5324 };
5325 const src_loc: Value.Location = src: {
5326 if (dst_vi.hintRegister(isel)) |hint_reg| {
5327 dst_live_vi.* = .allocating;
5328 defer dst_live_vi.* = dst_vi;
5329 if (try isel.fillReg(hint_reg)) {
5330 isel.saved_registers.insert(hint_reg);
5331 break :src .{ .register = .{ .mod = dst_vi.hintModifier(isel), .reg = hint_reg } };
5332 }
5333 }
5334 if (dst_vi.isSmall(isel)) {
5335 switch (isel.tryAllocReg(dst_vi.hintModifier(isel).class())) {
5336 .allocated => |reg| {
5337 isel.freeReg(reg);
5338 break :src .{ .register = .{ .mod = dst_vi.hintModifier(isel), .reg = reg } };
5339 },
5340 .fill_candidate, .out_of_registers => {},
5341 }
5342 }
5343 break :src .{ .stack_slot = dst_vi.allocStackSlot(isel) };
5344 };
5345 try dst_vi.moveTo(isel, src_loc);
5346 assert(dst_live_vi.* == .free);
5347 return true;
5348}
5349
5350/// Frees a set of register. If locked is true, these registers are then locked.
5351/// Requires all registers to be unlocked.
5352/// Returns true on success.
5353fn fillRegsBatch(isel: *Select, regs: RegisterSet, locking: bool) error{ OutOfMemory, AlreadyReported }!void {
5354 tracking_log.debug("batch fill: {f}", .{fmtRegisterSet(regs)});
5355 // lock free registers
5356 var regs_it = regs.iterator();
5357 while (regs_it.next()) |reg| {
5358 const live_vi = isel.live_registers.getPtr(reg);
5359 switch (live_vi.*) {
5360 .allocating => unreachable,
5361 .free => live_vi.* = .allocating,
5362 _ => {}, // fill_candidate will be ignored by fillReg so there is no need to protect these values
5363 }
5364 }
5365
5366 // fill registers
5367 regs_it = regs.iterator();
5368 while (regs_it.next()) |reg| {
5369 const live_vi = isel.live_registers.getPtr(reg);
5370 switch (live_vi.*) {
5371 .free => unreachable,
5372 .allocating => {},
5373 _ => {
5374 assert(try isel.fillReg(reg));
5375 live_vi.* = .allocating;
5376 },
5377 }
5378 }
5379
5380 // unlock registers
5381 if (!locking) {
5382 regs_it = regs.iterator();
5383 while (regs_it.next()) |reg| {
5384 const live_vi = isel.live_registers.getPtr(reg);
5385 assert(live_vi.* == .allocating);
5386 live_vi.* = .free;
5387 }
5388 }
5389
5390 return;
5391}
5392
5393/// Frees a register by moving it to stack.
5394/// Returns true on success, false on failure (i.e. dst_reg is locked/allocated or unallocatable).
5395fn fillRegToMemory(isel: *Select, dst_reg: Register) error{ OutOfMemory, AlreadyReported }!bool {
5396 if (!isRegisterAllocatable(dst_reg)) return false;
5397 const dst_live_vi = isel.live_registers.getPtr(dst_reg);
5398 const dst_vi = switch (dst_live_vi.*) {
5399 _ => |dst_vi| dst_vi,
5400 .allocating => return false,
5401 .free => return true,
5402 };
5403 try dst_vi.moveTo(isel, .{ .stack_slot = dst_vi.allocStackSlot(isel) });
5404 assert(dst_live_vi.* == .free);
5405 return true;
5406}
5407
5408const TryAllocRegResult = union(enum) {
5409 allocated: Register,
5410 fill_candidate: Register,
5411 out_of_registers,
5412};
5413
5414fn tryAllocReg(isel: *Select, class: Register.Class) TryAllocRegResult {
5415 return switch (class) {
5416 .int => isel.tryAllocRegInRanges(&.{
5417 .{ .r4, .r11 }, // argument registers
5418 .{ .r12, .r20 }, // temporary registers
5419 .{ .r23, .r31 }, // static registers
5420 .{ .r0, .r31 },
5421 }),
5422 .fp => isel.tryAllocRegInRange(.{ .f0, .f31 }),
5423 .fcc => isel.tryAllocRegInRange(.{ .fcc0, .fcc7 }),
5424 };
5425}
5426
5427fn tryAllocRegInRanges(isel: *Select, comptime ranges: []const struct { Register, Register }) TryAllocRegResult {
5428 inline for (ranges[0 .. ranges.len - 1]) |range| {
5429 switch (isel.tryAllocRegInRange(range)) {
5430 .allocated => |reg| return .{ .allocated = reg },
5431 else => {},
5432 }
5433 }
5434 return isel.tryAllocRegInRange(ranges[ranges.len - 1]);
5435}
5436
5437fn tryAllocRegInRange(isel: *Select, range: struct { Register, Register }) TryAllocRegResult {
5438 var failed_result: TryAllocRegResult = .out_of_registers;
5439 var reg, const last_reg = range;
5440 while (true) : (reg = @fromBackingInt(@backingInt(reg) + 1)) {
5441 if (!isRegisterAllocatable(reg)) continue;
5442 const live_vi = isel.live_registers.getPtr(reg);
5443 switch (live_vi.*) {
5444 _ => switch (failed_result) {
5445 .allocated => unreachable,
5446 .fill_candidate => {},
5447 .out_of_registers => failed_result = .{ .fill_candidate = reg },
5448 },
5449 .allocating => {},
5450 .free => {
5451 live_vi.* = .allocating;
5452 isel.saved_registers.insert(reg);
5453 return .{ .allocated = reg };
5454 },
5455 }
5456 if (reg == last_reg) return failed_result;
5457 }
5458}
5459
5460fn allocReg(isel: *Select, class: Register.Class) !Register {
5461 switch (isel.tryAllocReg(class)) {
5462 .allocated => |reg| return reg,
5463 .fill_candidate => |reg| {
5464 assert(try isel.fillRegToMemory(reg));
5465 const live_vi = isel.live_registers.getPtr(reg);
5466 assert(live_vi.* == .free);
5467 live_vi.* = .allocating;
5468 return reg;
5469 },
5470 .out_of_registers => return isel.fail("ran out of {t} registers", .{class}),
5471 }
5472}
5473
5474fn allocRegForWrite(isel: *Select, class: Register.Class) !Register {
5475 const reg = try isel.allocReg(class);
5476 isel.markRegWritten(reg);
5477 return reg;
5478}
5479
5480fn markRegWritten(isel: *Select, reg: Register) void {
5481 if (isel.active_loops.last()) |loop_index| {
5482 const loop = loop_index.get(isel);
5483 tracking_log.debug("${t} <- written", .{reg});
5484 loop.written_regs.insert(reg);
5485 }
5486}
5487
5488fn markRegsWritten(isel: *Select, regs: RegisterSet) void {
5489 if (isel.active_loops.last()) |loop_index| {
5490 const loop = loop_index.get(isel);
5491 tracking_log.debug("{f} <- written", .{fmtRegisterSet(regs)});
5492 loop.written_regs.setUnion(regs);
5493 }
5494}
5495
5496const RegLock = struct {
5497 reg: Register,
5498 const empty: RegLock = .{ .reg = .zero };
5499 fn unlock(lock: RegLock, isel: *Select) void {
5500 switch (lock.reg) {
5501 else => |reg| isel.freeReg(reg),
5502 Register.zero => {},
5503 }
5504 }
5505};
5506
5507fn lockReg(isel: *Select, reg: Register) RegLock {
5508 assert(reg != Register.zero);
5509 const live_vi = isel.live_registers.getPtr(reg);
5510 assert(live_vi.* == .free);
5511 live_vi.* = .allocating;
5512 return .{ .reg = reg };
5513}
5514
5515fn tryLockReg(isel: *Select, reg: Register) RegLock {
5516 assert(reg != Register.zero);
5517 const live_vi = isel.live_registers.getPtr(reg);
5518 switch (live_vi.*) {
5519 _ => {
5520 isel.dumpValues(.all);
5521 unreachable;
5522 },
5523 .allocating => return .empty,
5524 .free => {
5525 live_vi.* = .allocating;
5526 return .{ .reg = reg };
5527 },
5528 }
5529}
5530
5531fn freeReg(isel: *Select, reg: Register) void {
5532 assert(reg != Register.zero);
5533 const live_vi = isel.live_registers.getPtr(reg);
5534 assert(live_vi.* == .allocating);
5535 live_vi.* = .free;
5536}
5537
5538/// A snapshot of unresolved locations.
5539const LocationSnapshot = struct {
5540 value_locs: std.MultiArrayList(Entry),
5541
5542 const Entry = union(enum(u2)) {
5543 none,
5544 register: Register.Alias,
5545 stack_slot: Value.Indirect,
5546 };
5547
5548 const empty: LocationSnapshot = .{ .value_locs = .empty };
5549
5550 fn deinit(snap: *LocationSnapshot, isel: *Select) void {
5551 const gpa = isel.pt.zcu.gpa;
5552 snap.value_locs.deinit(gpa);
5553 snap.* = undefined;
5554 }
5555
5556 /// Merges the captured locations and current expected locations.
5557 fn merge(snap: *const LocationSnapshot, isel: *Select) !void {
5558 const captured_locs = snap.value_locs.slice();
5559 for (0..snap.value_locs.len) |i| {
5560 const vi: Value.Index = @fromBackingInt(@intCast(i));
5561 const captured_loc: Value.Location = switch (captured_locs.get(i)) {
5562 .none => continue,
5563 .register => |captured_ra| .{ .register = captured_ra },
5564 .stack_slot => |captured_stack| .{ .stack_slot = captured_stack },
5565 };
5566 if (vi.location(isel)) |current_loc| {
5567 if (std.meta.eql(captured_loc, current_loc)) continue;
5568 }
5569 tracking_log.debug("{f} <- {f} (snapshot merge)", .{ vi, captured_loc });
5570
5571 if (captured_loc.asRegister()) |captured_reg| assert(try isel.fillReg(captured_reg));
5572 try vi.moveTo(isel, captured_loc);
5573 }
5574 }
5575
5576 pub fn format(snap: LocationSnapshot, w: *std.Io.Writer) std.Io.Writer.Error!void {
5577 const captured_locs = snap.value_locs.slice();
5578 var first = true;
5579 for (0..snap.value_locs.len) |i| {
5580 const vi: Value.Index = @fromBackingInt(@intCast(i));
5581 const captured_loc = captured_locs.get(i);
5582 if (captured_loc == .none) continue;
5583 if (first) first = false else try w.writeAll("\n");
5584 switch (captured_loc) {
5585 .none => unreachable,
5586 .register => |captured_ra| try w.print(" {f} <- {f}", .{ vi, captured_ra }),
5587 .stack_slot => |captured_stack| try w.print(" {f} <- {f}", .{ vi, captured_stack }),
5588 }
5589 }
5590 if (first) return w.writeAll("(empty)");
5591 }
5592};
5593
5594fn takeLocationSnapshot(isel: *Select) !LocationSnapshot {
5595 const gpa = isel.pt.zcu.gpa;
5596 var snapshot: LocationSnapshot = .empty;
5597 try snapshot.value_locs.resize(gpa, isel.values.items.len);
5598
5599 for (0..isel.values.items.len) |i| {
5600 const vi: Value.Index = @fromBackingInt(@intCast(i));
5601 if (vi.location(isel)) |vi_loc| {
5602 snapshot.value_locs.set(i, switch (vi_loc) {
5603 .register => |vi_ra| if (vi_ra.reg == Register.zero) .none else .{ .register = vi_ra },
5604 .stack_slot => |vi_stack| .{ .stack_slot = vi_stack },
5605 });
5606 } else {
5607 snapshot.value_locs.set(i, .none);
5608 }
5609 }
5610
5611 if (std.debug.runtime_safety) {
5612 var live_vi_it = isel.live_registers.iterator();
5613 while (live_vi_it.next()) |live_vi| {
5614 if (live_vi.value.* == .allocating) {
5615 tracking_log.debug("{t} is still locked when taking snapshot", .{live_vi.key});
5616 unreachable;
5617 }
5618 }
5619 }
5620
5621 return snapshot;
5622}
5623
5624/// Ways to treat bits in destination registers that may not be affected by an operation.
5625const DestProtection = enum {
5626 /// Unrelated bits must be preserved.
5627 preserved,
5628 /// Unrelated bits may be destroyed.
5629 none,
5630 /// Unrelated bits must be filled with 0.
5631 wiped,
5632};
5633
5634fn fillUnusedBits(isel: *Select, rd: Register, rj: Register, dst_mode: Value.Extension, src_mode: Value.Extension, unused_bits: u9) !void {
5635 const gpr_bits = isel.gprBits();
5636 const used_bits = gpr_bits - unused_bits;
5637 wip_mir_log.debug(" | # fillUnusedBits {t}, {t}, {d} bits, {t} -> {t}", .{ rd, rj, used_bits, src_mode, dst_mode });
5638
5639 if (used_bits == gpr_bits or src_mode == dst_mode) {
5640 if (rd != rj) try isel.emit(.ori(rd, rj, 0));
5641 return;
5642 }
5643 switch (dst_mode) {
5644 .garbage => {},
5645 .sign_ext => {
5646 if (used_bits >= gpr_bits) return isel.fail("too many used bits", .{});
5647 switch (used_bits) {
5648 8 => try isel.emit(.@"sext.b"(rd, rj)),
5649 16 => try isel.emit(.@"sext.h"(rd, rj)),
5650 32 => try isel.emit(.@"addi.w"(rd, rj, 0)),
5651 0...7, 9...15, 17...31, 33...63 => {
5652 try isel.emit(.@"srai.d"(rd, rd, @intCast(gpr_bits - used_bits)));
5653 try isel.emit(.@"slli.d"(rd, rj, @intCast(gpr_bits - used_bits)));
5654 },
5655 else => unreachable,
5656 }
5657 },
5658 .zero_ext => {
5659 if (used_bits >= gpr_bits) return isel.fail("too many used bits", .{});
5660 switch (used_bits) {
5661 1...31 => try isel.emit(.@"bstrpick.w"(rd, rj, @intCast(used_bits - 1), 0)),
5662 32...63 => try isel.emit(.@"bstrpick.d"(rd, rj, @intCast(used_bits - 1), 0)),
5663 else => unreachable,
5664 }
5665 },
5666 }
5667}
5668
5669/// Loads from memory [base + offset] to register
5670fn loadReg(
5671 isel: *Select,
5672 dst: Register,
5673 size: u64,
5674 signedness: std.builtin.Signedness,
5675 base: Register,
5676 offset: i65,
5677) !void {
5678 if (dst.class() != .int) return isel.fail("TODO loadReg {t}", .{dst});
5679 switch (size) {
5680 0 => unreachable,
5681 1 => {
5682 if (std.math.cast(i12, offset)) |small_off| return isel.emit(switch (signedness) {
5683 .signed => .@"ld.b"(dst, base, small_off),
5684 .unsigned => .@"ld.bu"(dst, base, small_off),
5685 });
5686 },
5687 2 => {
5688 if (std.math.cast(i12, offset)) |small_off| return isel.emit(switch (signedness) {
5689 .signed => .@"ld.h"(dst, base, small_off),
5690 .unsigned => .@"ld.hu"(dst, base, small_off),
5691 });
5692 },
5693 4 => {
5694 if (std.math.cast(i12, offset)) |small_off| return isel.emit(switch (signedness) {
5695 .signed => .@"ld.w"(dst, base, small_off),
5696 .unsigned => .@"ld.wu"(dst, base, small_off),
5697 });
5698 if (signedness == .signed) if (std.math.cast(i16, offset)) |small_off| {
5699 if ((small_off & 0b11) == 0) {
5700 return isel.emit(.@"ldox4.w"(dst, base, @intCast(@divExact(small_off, 4))));
5701 }
5702 };
5703 },
5704 8 => {
5705 if (std.math.cast(i12, offset)) |small_off| return isel.emit(.@"ld.d"(dst, base, small_off));
5706 if (std.math.cast(i16, offset)) |small_off| {
5707 if ((small_off & 0b11) == 0) {
5708 return isel.emit(.@"ldox4.d"(dst, base, @intCast(@divExact(small_off, 4))));
5709 }
5710 }
5711 },
5712 else => return try isel.failUnimplemented("bad load size: {d}", .{size}),
5713 }
5714
5715 const ptr_reg = try isel.allocRegForWrite(.int);
5716 defer isel.freeReg(ptr_reg);
5717 switch (size) {
5718 1 => try isel.emit(switch (signedness) {
5719 .signed => .@"ldx.b"(dst, base, ptr_reg),
5720 .unsigned => .@"ldx.bu"(dst, base, ptr_reg),
5721 }),
5722 2 => try isel.emit(switch (signedness) {
5723 .signed => .@"ldx.h"(dst, base, ptr_reg),
5724 .unsigned => .@"ldx.hu"(dst, base, ptr_reg),
5725 }),
5726 4 => try isel.emit(switch (signedness) {
5727 .signed => .@"ldx.w"(dst, base, ptr_reg),
5728 .unsigned => .@"ldx.wu"(dst, base, ptr_reg),
5729 }),
5730 8 => try isel.emit(.@"ldx.d"(dst, base, ptr_reg)),
5731 else => {
5732 try isel.loadReg(dst, size, signedness, ptr_reg, 0);
5733 try isel.emit(.@"add.d"(ptr_reg, ptr_reg, base));
5734 },
5735 }
5736 try isel.moveIntImm(ptr_reg, std.math.cast(i64, offset) orelse return isel.fail("unimplemented load with large offset", .{}));
5737}
5738
5739/// Stores a register to memory [base + offset]
5740fn storeReg(
5741 isel: *Select,
5742 src: Register,
5743 size: u64,
5744 base: Register,
5745 offset: i65,
5746) !void {
5747 if (src.class() != .int) return isel.fail("TODO storeReg {t}", .{src});
5748 switch (size) {
5749 0 => unreachable,
5750 1 => {
5751 if (std.math.cast(i12, offset)) |small_off| return isel.emit(.@"st.b"(src, base, small_off));
5752 },
5753 2 => {
5754 if (std.math.cast(i12, offset)) |small_off| return isel.emit(.@"st.h"(src, base, small_off));
5755 },
5756 4 => {
5757 if (std.math.cast(i12, offset)) |small_off| return isel.emit(.@"st.w"(src, base, small_off));
5758 if (std.math.cast(i16, offset)) |small_off| {
5759 if ((small_off & 0b11) == 0) {
5760 return isel.emit(.@"stox4.w"(src, base, @intCast(@divExact(small_off, 4))));
5761 }
5762 }
5763 },
5764 8 => {
5765 if (std.math.cast(i12, offset)) |small_off| return isel.emit(.@"st.d"(src, base, small_off));
5766 if (std.math.cast(i16, offset)) |small_off| {
5767 if ((small_off & 0b11) == 0) {
5768 return isel.emit(.@"stox4.d"(src, base, @intCast(@divExact(small_off, 4))));
5769 }
5770 }
5771 },
5772 else => return try isel.failUnimplemented("bad store size: {d}", .{size}),
5773 }
5774
5775 if (std.math.cast(i64, offset)) |offset64| stx: {
5776 const ptr_reg = try isel.allocRegForWrite(.int);
5777 defer isel.freeReg(ptr_reg);
5778 switch (size) {
5779 1 => try isel.emit(.@"stx.b"(src, base, ptr_reg)),
5780 2 => try isel.emit(.@"stx.h"(src, base, ptr_reg)),
5781 4 => try isel.emit(.@"stx.w"(src, base, ptr_reg)),
5782 8 => try isel.emit(.@"stx.d"(src, base, ptr_reg)),
5783 else => break :stx,
5784 }
5785 try isel.moveIntImm(ptr_reg, offset64);
5786 }
5787
5788 const ptr_reg = try isel.allocRegForWrite(.int);
5789 defer isel.freeReg(ptr_reg);
5790 try isel.storeReg(src, size, ptr_reg, 0);
5791 try isel.emit(if (offset > 0) .@"add.d"(ptr_reg, ptr_reg, base) else .@"sub.d"(ptr_reg, ptr_reg, base));
5792 try isel.moveIntImm(ptr_reg, @intCast(@abs(offset)));
5793}
5794
5795/// Copies a part of a register to another.
5796fn moveReg(
5797 isel: *Select,
5798 dst_ra: Register.Alias,
5799 dst_bit_off: u9,
5800 src_ra: Register.Alias,
5801 src_bit_off: u9,
5802 bit_size: u16,
5803 init_dst_prot: DestProtection,
5804) !void {
5805 if (dst_ra.reg == src_ra.reg and dst_bit_off == src_bit_off) return;
5806 if (bit_size == 0) return;
5807 assert(init_dst_prot != .preserved or (isel.live_registers.get(dst_ra.reg) == .allocating));
5808 assert(isel.live_registers.get(src_ra.reg) == .allocating);
5809
5810 const dst_ra_bit_size = dst_ra.mod.bitSize(isel.target);
5811 const src_ra_bit_size = src_ra.mod.bitSize(isel.target);
5812 const dst_msb_plus_one = dst_bit_off + bit_size;
5813 const src_msb_plus_one = src_bit_off + bit_size;
5814 assert(dst_msb_plus_one <= dst_ra_bit_size and src_msb_plus_one <= src_ra_bit_size);
5815
5816 const dst_prot: DestProtection = switch (init_dst_prot) {
5817 .preserved => if (bit_size == dst_ra_bit_size) .none else .preserved,
5818 else => init_dst_prot,
5819 };
5820
5821 const dst_lock = isel.tryLockReg(dst_ra.reg);
5822 defer dst_lock.unlock(isel);
5823 const src_lock = isel.tryLockReg(src_ra.reg);
5824 defer src_lock.unlock(isel);
5825
5826 switch (dst_ra.mod) {
5827 .integer => switch (src_ra.mod) {
5828 .integer => {
5829 if (dst_bit_off == src_bit_off and dst_prot == .none)
5830 return try isel.emit(.ori(dst_ra.reg, src_ra.reg, 0));
5831 // bstrins
5832 const tmp_reg = tmp_reg: {
5833 if (dst_bit_off == 0 and dst_prot != .none) break :tmp_reg dst_ra.reg;
5834 const tmp_reg = if (src_bit_off == 0) src_ra.reg else try isel.allocRegForWrite(.int);
5835 const dst_msbw = dst_msb_plus_one - 1;
5836 try isel.emit(switch (dst_ra_bit_size) {
5837 32 => .@"bstrins.w"(dst_ra.reg, tmp_reg, @intCast(dst_msbw), @intCast(dst_bit_off)),
5838 64 => .@"bstrins.d"(dst_ra.reg, tmp_reg, @intCast(dst_msbw), @intCast(dst_bit_off)),
5839 else => unreachable,
5840 });
5841 break :tmp_reg tmp_reg;
5842 };
5843 defer if (tmp_reg != dst_ra.reg and tmp_reg != src_ra.reg) isel.freeReg(tmp_reg);
5844 // bstrpick
5845 const src_msbw = src_msb_plus_one - 1;
5846 try isel.emit(switch (dst_ra_bit_size) {
5847 32 => .@"bstrpick.w"(tmp_reg, src_ra.reg, @intCast(src_msbw), @intCast(src_bit_off)),
5848 64 => .@"bstrpick.d"(tmp_reg, src_ra.reg, @intCast(src_msbw), @intCast(src_bit_off)),
5849 else => unreachable,
5850 });
5851 },
5852 else => return isel.fail("unimplemented non-integral moveReg", .{}),
5853 },
5854 else => return isel.fail("unimplemented non-integral moveReg", .{}),
5855 }
5856}
5857
5858/// Moves an immediate to a register.
5859fn moveIntImm(isel: *Select, rd: Register, si64: i64) !void {
5860 wip_mir_log.debug(" | # moveImm {t} <- 0x{x}", .{ rd, si64 });
5861 if (std.math.cast(u12, si64)) |imm12| return isel.emit(.ori(rd, .zero, imm12));
5862
5863 const ori12: u12 = @truncate(@as(u64, @bitCast(si64)));
5864 const lu12i20: i20 = @truncate(si64 >> 12);
5865 const use_lu12iw = lu12i20 != 0;
5866 const lu32i20: i20 = @truncate(si64 >> 32);
5867 const use_lu32id = lu32i20 != hi: {
5868 if (use_lu12iw) break :hi @as(i20, @intCast(@as(i1, @truncate(si64 >> 31))));
5869 break :hi 0;
5870 };
5871 const lu52i12: i12 = @truncate(si64 >> 52);
5872 const use_lu52id = lu52i12 != hi: {
5873 if (use_lu32id) break :hi @as(i12, @intCast(@as(i1, @truncate(si64 >> 51))));
5874 if (use_lu12iw) break :hi @as(i12, @intCast(@as(i1, @truncate(si64 >> 31))));
5875 break :hi 0;
5876 };
5877 const use_ori = (ori12 != 0) or (!use_lu12iw and use_lu32id) or si64 == 0;
5878 const ori_rj = if (use_lu12iw) rd else Register.zero;
5879 const lu52id_rj = if (use_ori or use_lu12iw) rd else Register.zero;
5880
5881 if (use_lu52id) try isel.emit(.@"cu52i.d"(rd, lu52id_rj, lu52i12));
5882 if (use_lu32id) try isel.emit(.@"cu32i.d"(rd, lu32i20));
5883 if (use_ori) try isel.emit(.ori(rd, ori_rj, ori12));
5884 if (use_lu12iw) try isel.emit(.@"lu12i.w"(rd, lu12i20));
5885}
5886
5887fn addImm(isel: *Select, rd: Register, rj: Register, si65: i65) !void {
5888 const gpr_size = isel.gprSize();
5889 if (si65 == 0) {
5890 try isel.emit(.ori(rd, rj, 0));
5891 } else if (std.math.cast(i12, si65)) |si12| {
5892 switch (gpr_size) {
5893 4 => try isel.emit(.@"addi.w"(rd, rj, si12)),
5894 8 => try isel.emit(.@"addi.d"(rd, rj, si12)),
5895 else => unreachable,
5896 }
5897 } else {
5898 if (si65 >= 0) switch (gpr_size) {
5899 4 => try isel.emit(.@"add.w"(rd, rd, rj)),
5900 8 => try isel.emit(.@"add.d"(rd, rd, rj)),
5901 else => unreachable,
5902 } else switch (gpr_size) {
5903 4 => try isel.emit(.@"sub.w"(rd, rd, rj)),
5904 8 => try isel.emit(.@"sub.d"(rd, rd, rj)),
5905 else => unreachable,
5906 }
5907 try isel.moveIntImm(rd, @bitCast(@as(u64, @truncate(@as(u65, @bitCast(si65))))));
5908 }
5909}
5910
5911/// Loads the incoming value of a register.
5912fn ldIncoming(isel: *Select, rd: Register, rj: Register) !void {
5913 wip_mir_log.debug(" | # ldIncoming {t} <- {t}", .{ rd, rj });
5914 try isel.layout_relocs.append(isel.pt.zcu.gpa, @intCast(isel.instructions.items.len));
5915 try isel.emit(.ori(rd, rj, 0));
5916}
5917
5918fn cmp(
5919 isel: *Select,
5920 res_reg: Register,
5921 ty: ZigType,
5922 lhs_vi: Value.Index,
5923 op: std.math.CompareOperator,
5924 rhs_vi: Value.Index,
5925) !void {
5926 wip_mir_log.debug(" | # cmp {f}, {t}, {f}, {t}, {f}", .{ isel.fmtType(ty), res_reg, lhs_vi, op, rhs_vi });
5927 if (!ty.isRuntimeFloat() and !ty.isArrayOrVector(isel.pt.zcu)) {
5928 // integeral comparison
5929 const int_info: std.builtin.Type.Int = if (ty.toIntern() == .bool_type)
5930 .{ .signedness = .unsigned, .bits = 1 }
5931 else if (ty.isAbiInt(isel.pt.zcu))
5932 ty.intInfo(isel.pt.zcu)
5933 else if (ty.isPtrAtRuntime(isel.pt.zcu))
5934 .{ .signedness = .unsigned, .bits = 64 }
5935 else
5936 return isel.fail("bad cmp_{t} {f}", .{ op, isel.fmtType(ty) });
5937
5938 var part_offset = lhs_vi.size(isel);
5939 while (part_offset > 0) {
5940 const part_size = @min(part_offset, isel.gprSize());
5941 part_offset -= part_size;
5942 // TODO optimize constant cmp
5943 // TODO relax LHS and RHS extension mode requirements to != .garbage
5944 const lhs_part_vi = try lhs_vi.partExact(isel, part_offset, part_size);
5945 const lhs_part_mat = try lhs_part_vi.matIntRegZeroExt(isel);
5946 const lhs_part_reg = lhs_part_mat.reg();
5947 const rhs_part_vi = try rhs_vi.partExact(isel, part_offset, part_size);
5948 const rhs_part_mat = try rhs_part_vi.matIntRegZeroExt(isel);
5949 const rhs_part_reg = rhs_part_mat.reg();
5950
5951 const res_part_reg = if (part_offset == 0) res_reg else res_part_reg: {
5952 const res_part_reg = try isel.allocRegForWrite(.int);
5953 try isel.emit(.@"or"(res_reg, res_reg, res_part_reg));
5954 break :res_part_reg res_part_reg;
5955 };
5956 defer if (res_part_reg != res_reg) isel.freeReg(res_part_reg);
5957
5958 switch (op) {
5959 .eq => {
5960 try isel.emit(.sltui(res_part_reg, res_part_reg, 1));
5961 try isel.emit(.xor(res_part_reg, lhs_part_reg, rhs_part_reg));
5962 },
5963 .neq => {
5964 try isel.emit(.sltu(res_part_reg, .zero, res_part_reg));
5965 try isel.emit(.xor(res_part_reg, lhs_part_reg, rhs_part_reg));
5966 },
5967 .lt, .lte, .gt, .gte => {
5968 var rj = lhs_part_reg;
5969 var rk = rhs_part_reg;
5970
5971 switch (op) {
5972 .lte, .gt => std.mem.swap(Register, &rj, &rk),
5973 else => {},
5974 }
5975 switch (op) {
5976 .lte, .gte => try isel.emit(.xori(res_part_reg, res_part_reg, 1)),
5977 else => {},
5978 }
5979
5980 try isel.emit(switch (int_info.signedness) {
5981 .signed => .slt(res_part_reg, rj, rk),
5982 .unsigned => .sltu(res_part_reg, rj, rk),
5983 });
5984 },
5985 }
5986 try rhs_part_mat.finish(isel);
5987 try lhs_part_mat.finish(isel);
5988 }
5989 } else return isel.fail("bad cmp_{t} {f}", .{ op, isel.fmtType(ty) });
5990}
5991
5992const AddOrSubtractOptions = struct {
5993 overflow: Overflow,
5994
5995 const Overflow = union(enum) {
5996 @"unreachable",
5997 panic: Zcu.SimplePanicId,
5998 wrap,
5999 reg: Register,
6000 };
6001};
6002
6003// TODO optimize constant add/sub
6004fn addOrSubtract(
6005 isel: *Select,
6006 ty: ZigType,
6007 res_vi: Value.Index,
6008 op: enum { add, sub },
6009 lhs_vi: Value.Index,
6010 rhs_vi: Value.Index,
6011 opts: AddOrSubtractOptions,
6012) !void {
6013 wip_mir_log.debug(" | # {t} ty = {f}, res = {f}, lhs = {f}, rhs = {f}, overflow = {t}", .{ op, isel.fmtType(ty), res_vi, lhs_vi, rhs_vi, opts.overflow });
6014 // TODO: implement opts.overflow
6015 const zcu = isel.pt.zcu;
6016 assert(ty.isAbiInt(zcu));
6017 const int_info = ty.intInfo(zcu);
6018
6019 if (int_info.bits <= 32) {
6020 try res_vi.reextendToGarbage(isel); // TODO optimize
6021 const res_reg = try res_vi.defRegMod(isel, .integer) orelse return;
6022 const lhs_mat = try lhs_vi.matIntRegZeroExt(isel);
6023 const rhs_mat = try rhs_vi.matIntRegZeroExt(isel);
6024
6025 switch (op) {
6026 .add => try isel.emit(.@"add.w"(res_reg, lhs_mat.reg(), rhs_mat.reg())),
6027 .sub => try isel.emit(.@"sub.w"(res_reg, lhs_mat.reg(), rhs_mat.reg())),
6028 }
6029
6030 try lhs_mat.finish(isel);
6031 try rhs_mat.finish(isel);
6032 } else if (int_info.bits <= 64) {
6033 try res_vi.reextendToGarbage(isel); // TODO optimize
6034 const res_reg = try res_vi.defRegMod(isel, .integer) orelse return;
6035 const lhs_mat = try lhs_vi.matIntRegZeroExt(isel);
6036 const rhs_mat = try rhs_vi.matIntRegZeroExt(isel);
6037
6038 switch (op) {
6039 .add => try isel.emit(.@"add.d"(res_reg, lhs_mat.reg(), rhs_mat.reg())),
6040 .sub => try isel.emit(.@"sub.d"(res_reg, lhs_mat.reg(), rhs_mat.reg())),
6041 }
6042
6043 try lhs_mat.finish(isel);
6044 try rhs_mat.finish(isel);
6045 } else {
6046 if (debug_trap_unimplemented_code) {
6047 isel.wipeLocationDfs(res_vi);
6048 }
6049 return try isel.failUnimplemented("unimplemented {t} {f}", .{ op, isel.fmtType(ty) });
6050 }
6051}
6052
6053/// elem_ptr = base +- elem_size * index
6054/// elem_ptr, base, and index may alias. base_reg must be locked.
6055fn elemPtr(
6056 isel: *Select,
6057 rd: Register,
6058 base_reg: Register,
6059 op: enum { add, sub },
6060 elem_size: u64,
6061 index_vi: Value.Index,
6062) !void {
6063 assert(isel.live_registers.get(base_reg) == .allocating);
6064 wip_mir_log.debug(" | # elemPtr {t} = {t} {s} {f} * {d} (= 0b{b})", .{ rd, base_reg, switch (op) {
6065 .add => "+",
6066 .sub => "-",
6067 }, index_vi, elem_size, elem_size });
6068 switch (@popCount(elem_size)) {
6069 0 => unreachable, // Sema should optimize this
6070 1 => {
6071 const shift = @ctz(elem_size);
6072 if (shift == 0) {
6073 const index_mat = try index_vi.matIntRegZeroExt(isel);
6074 const index_reg = index_mat.reg();
6075 try isel.emit(switch (op) {
6076 .add => switch (isel.gprBits()) {
6077 else => unreachable,
6078 32 => .@"add.w"(rd, base_reg, index_reg),
6079 64 => .@"add.d"(rd, base_reg, index_reg),
6080 },
6081 .sub => switch (isel.gprBits()) {
6082 else => unreachable,
6083 32 => .@"sub.w"(rd, base_reg, index_reg),
6084 64 => .@"sub.d"(rd, base_reg, index_reg),
6085 },
6086 });
6087 try index_mat.finish(isel);
6088 return;
6089 } else if (std.math.cast(u2, shift - 1)) |sa2| {
6090 switch (op) {
6091 .add => {
6092 const index_mat = try index_vi.matIntRegZeroExt(isel);
6093 const index_reg = index_mat.reg();
6094 try isel.emit(switch (isel.gprBits()) {
6095 else => unreachable,
6096 32 => .@"sladd.w"(rd, index_reg, base_reg, sa2),
6097 64 => .@"sladd.d"(rd, index_reg, base_reg, sa2),
6098 });
6099 try index_mat.finish(isel);
6100 return;
6101 },
6102 .sub => {
6103 if (base_reg != rd) {
6104 const index_mat = try index_vi.matIntRegZeroExt(isel);
6105 const index_reg = index_mat.reg();
6106 switch (isel.gprBits()) {
6107 else => unreachable,
6108 32 => {
6109 try isel.emit(.@"sladd.w"(rd, rd, base_reg, sa2));
6110 try isel.emit(.@"sub.w"(rd, .zero, index_reg));
6111 },
6112 64 => {
6113 try isel.emit(.@"sladd.d"(rd, rd, base_reg, sa2));
6114 try isel.emit(.@"sub.d"(rd, .zero, index_reg));
6115 },
6116 }
6117 try index_mat.finish(isel);
6118 return;
6119 }
6120 },
6121 }
6122 }
6123 },
6124 2 => {
6125 const shift1 = @ctz(elem_size);
6126 const mask1 = @as(u64, 1) << @intCast(shift1);
6127 const mask2 = elem_size & ~mask1;
6128
6129 if ((op == .add or base_reg != rd) and mask1 <= 4 and mask2 <= 4) {
6130 try isel.elemPtr(rd, rd, op, mask2, index_vi);
6131 try isel.elemPtr(rd, base_reg, op, mask1, index_vi);
6132 return;
6133 }
6134 },
6135 else => {},
6136 }
6137
6138 const index_mat = try index_vi.matIntRegZeroExt(isel);
6139 const index_reg = index_mat.reg();
6140 const offset_reg = if (base_reg != rd) rd else try isel.allocRegForWrite(.int);
6141 defer if (offset_reg != rd) isel.freeReg(offset_reg);
6142 try isel.emit(switch (op) {
6143 .add => switch (isel.gprBits()) {
6144 else => unreachable,
6145 32 => .@"add.w"(rd, base_reg, offset_reg),
6146 64 => .@"add.d"(rd, base_reg, offset_reg),
6147 },
6148 .sub => switch (isel.gprBits()) {
6149 else => unreachable,
6150 32 => .@"sub.w"(rd, base_reg, offset_reg),
6151 64 => .@"sub.d"(rd, base_reg, offset_reg),
6152 },
6153 });
6154 try isel.emit(switch (isel.gprBits()) {
6155 else => unreachable,
6156 32 => .@"mul.w"(offset_reg, offset_reg, index_reg),
6157 64 => .@"mul.d"(offset_reg, offset_reg, index_reg),
6158 });
6159 try isel.moveIntImm(offset_reg, @bitCast(elem_size));
6160 try index_mat.finish(isel);
6161}
6162
6163fn moveLoc(
6164 isel: *Select,
6165 dst_loc: Value.Location,
6166 dst_off: u64,
6167 src_loc: Value.Location,
6168 src_off: u64,
6169 size: u64,
6170 dst_prot: DestProtection,
6171) !void {
6172 if (dst_loc.isUnallocated()) return;
6173 if (std.meta.eql(dst_loc, src_loc) and dst_off == src_off) return;
6174 if (size == 0) return;
6175 assert(!src_loc.isUnallocated());
6176 wip_mir_log.debug(" | # move {f}[{d}] <- {f}[{d}], {d}B, dst prot={t}", .{
6177 dst_loc,
6178 dst_off,
6179 src_loc,
6180 src_off,
6181 size,
6182 dst_prot,
6183 });
6184
6185 const dst_lock: RegLock = if (dst_prot != .preserved) .empty else dst_loc.tryLock(isel);
6186 defer dst_lock.unlock(isel);
6187 const src_lock = src_loc.tryLock(isel);
6188 defer src_lock.unlock(isel);
6189
6190 switch (dst_loc) {
6191 .register => |dst_ra| switch (src_loc) {
6192 .register => |src_ra| try isel.moveReg(
6193 dst_ra,
6194 @intCast(dst_off * 8),
6195 src_ra,
6196 @intCast(src_off * 8),
6197 @intCast(size * 8),
6198 dst_prot,
6199 ),
6200 .stack_slot => |src_stack| {
6201 const tmp_reg = if (dst_ra.mod == .integer and dst_off == 0)
6202 dst_ra.reg
6203 else
6204 try isel.allocRegForWrite(.int);
6205 defer if (tmp_reg != dst_ra.reg) isel.freeReg(tmp_reg);
6206 try isel.moveReg(
6207 dst_ra,
6208 @intCast(dst_off * 8),
6209 .{ .reg = tmp_reg, .mod = .integer },
6210 0,
6211 @intCast(size * 8),
6212 dst_prot,
6213 );
6214 try isel.loadReg(
6215 tmp_reg,
6216 memOpSizeFitting(size),
6217 .unsigned,
6218 src_stack.base,
6219 src_stack.offset + @as(i65, src_off),
6220 );
6221 },
6222 },
6223 .stack_slot => |dst_stack| {
6224 if (size > isel.gprSize()) {
6225 // large memory copies, src must be stack_slot
6226 const src_stack = src_loc.stack_slot;
6227 // TODO optimize to memmove call
6228
6229 const known_direction, const gen_low_to_high, const gen_high_to_low = move_dir: {
6230 if (dst_stack.base == src_stack.base) {
6231 if (dst_stack.offset == src_stack.offset) return;
6232 break :move_dir if (dst_stack.offset < src_stack.offset)
6233 .{ true, false, true }
6234 else
6235 .{ true, true, false };
6236 }
6237 // cannot determine direction
6238 if (assume_memmove_no_overlap)
6239 break :move_dir .{ true, true, false };
6240 break :move_dir .{ false, true, true };
6241 };
6242 if (!known_direction) {
6243 return isel.failUnimplemented("TODO moveLoc memmove", .{});
6244 }
6245
6246 const gpr_size = isel.gprSize();
6247 const steps = std.math.divCeil(u64, size, gpr_size) catch unreachable;
6248 if (gen_low_to_high) {
6249 var off: u64 = 0;
6250 for (0..@intCast(steps)) |_| {
6251 try isel.moveLoc(dst_loc, dst_off + off, src_loc, src_off + off, gpr_size, dst_prot);
6252 off += gpr_size;
6253 }
6254 }
6255 if (gen_high_to_low) {
6256 var off: u64 = steps * gpr_size;
6257 for (0..@intCast(steps)) |_| {
6258 off -= gpr_size;
6259 try isel.moveLoc(dst_loc, dst_off + off, src_loc, src_off + off, gpr_size, dst_prot);
6260 }
6261 }
6262
6263 return;
6264 }
6265
6266 // Move to a temp reg + store
6267 // If size is not direct mem op size and !kill_dst, old values have to
6268 // be loaded first.
6269 const memop_size = memOpSizeFitting(size);
6270 const need_load = memop_size != size;
6271 const tmp_reg, const tmp_allocated = tmp_reg: {
6272 if (src_off == 0 and !need_load) {
6273 switch (src_loc) {
6274 .register => |src_ra| if (src_ra.mod == .integer)
6275 break :tmp_reg .{ src_ra.reg, false },
6276 else => {},
6277 }
6278 }
6279 break :tmp_reg .{ try isel.allocRegForWrite(.int), true };
6280 };
6281 defer if (tmp_allocated) isel.freeReg(tmp_reg);
6282
6283 const dst_stack_off = dst_stack.offset + @as(i65, dst_off);
6284 try isel.storeReg(tmp_reg, memop_size, dst_stack.base, dst_stack_off);
6285 if (tmp_allocated)
6286 try isel.moveLoc(
6287 .{ .register = .{ .reg = tmp_reg, .mod = .integer } },
6288 0,
6289 src_loc,
6290 src_off,
6291 size,
6292 if (need_load) .preserved else .none,
6293 );
6294 if (need_load)
6295 try isel.loadReg(tmp_reg, memop_size, .unsigned, dst_stack.base, dst_stack_off);
6296 },
6297 }
6298}
6299
6300fn moveUndef(isel: *Select, dst_loc: Value.Location, size: u64) !void {
6301 if (isel.opt_mode == .fast or isel.opt_mode == .small) return;
6302 wip_mir_log.debug(" | # move {f} ({d}B) <- undef", .{ dst_loc, size });
6303 switch (dst_loc) {
6304 .register => |dst_ra| {
6305 assert(dst_ra.mod == .integer); // TODO
6306 try isel.moveIntImm(dst_ra.reg, switch (isel.gprBits()) {
6307 32 => 0xAAAAAAAA,
6308 64 => @bitCast(@as(u64, 0xAAAAAAAAAAAAAAAA)),
6309 else => unreachable,
6310 });
6311 },
6312 .stack_slot => {
6313 // TODO write undef to memory
6314 },
6315 }
6316}
6317
6318fn moveConstant(isel: *Select, dst: Value.Location, init_constant: Constant, init_offset: u64, size: u64) !void {
6319 wip_mir_log.debug(" | # move {f} <- {f} [{d}..{d}]", .{ dst, isel.fmtConstant(init_constant), init_offset, init_offset + size - 1 });
6320 var offset = init_offset;
6321 const zcu = isel.pt.zcu;
6322 const ip = &zcu.intern_pool;
6323 var constant = init_constant.toIntern();
6324 var constant_key = ip.indexToKey(constant);
6325 while (true) {
6326 // Try to coerce the constant value
6327 // also try better codegen
6328 constant_key: switch (constant_key) {
6329 else => {},
6330 .undef => return try isel.moveUndef(dst, size),
6331 .simple_value => |simple_value| switch (simple_value) {
6332 .void => {},
6333 .null, .@"unreachable" => unreachable,
6334 .true => continue :constant_key .{ .int = .{ .ty = .bool_type, .storage = .{ .u64 = 1 } } },
6335 .false => continue :constant_key .{ .int = .{ .ty = .bool_type, .storage = .{ .u64 = 0 } } },
6336 },
6337 .int => |int| if (dst.asRegisterAlias()) |dst_ra| {
6338 if (dst_ra.mod != .integer) break :constant_key;
6339 const dst_reg = dst_ra.reg;
6340 return switch (int.storage) {
6341 .u64 => |imm| try isel.moveIntImm(dst_reg, @bitCast(std.math.shr(u64, imm, 8 * offset))),
6342 .i64 => |imm| switch (size) {
6343 else => unreachable,
6344 1...4 => try isel.moveIntImm(dst_reg, @as(u32, @bitCast(@as(i32, @truncate(std.math.shr(i64, imm, 8 * offset)))))),
6345 5...8 => try isel.moveIntImm(dst_reg, @bitCast(std.math.shr(i64, imm, 8 * offset))),
6346 },
6347 .big_int => |big_int| {
6348 assert(size == isel.gprSize());
6349 var imm: u64 = 0;
6350 const limb_bits = @bitSizeOf(std.math.big.Limb);
6351 const limbs = @divExact(64, limb_bits);
6352 var limb_index: usize = @intCast(@divExact(offset, @divExact(limb_bits, 8)) + limbs);
6353 for (0..limbs) |_| {
6354 limb_index -= 1;
6355 if (limb_index >= big_int.limbs.len) continue;
6356 if (limb_bits < 64) imm <<= limb_bits;
6357 imm |= big_int.limbs[limb_index];
6358 }
6359 if (!big_int.positive) {
6360 limb_index = @min(limb_index, big_int.limbs.len);
6361 imm = while (limb_index > 0) {
6362 limb_index -= 1;
6363 if (big_int.limbs[limb_index] != 0) break ~imm;
6364 } else -%imm;
6365 }
6366 try isel.moveIntImm(dst_reg, @bitCast(imm));
6367 },
6368 };
6369 },
6370 .err => |err| continue :constant_key .{ .int = .{
6371 .ty = err.ty,
6372 .storage = .{ .u64 = ip.getErrorValueIfExists(err.name).? },
6373 } },
6374 .error_union => |error_union| {
6375 const error_union_type = ip.indexToKey(error_union.ty).error_union_type;
6376 const error_set_ty: ZigType = .fromInterned(error_union_type.error_set_type);
6377 const payload_ty: ZigType = .fromInterned(error_union_type.payload_type);
6378 const error_set_offset = codegen.errUnionErrorOffset(payload_ty, zcu);
6379 const error_set_size = error_set_ty.abiSize(zcu);
6380 if (offset >= error_set_offset and offset + size <= error_set_offset + error_set_size) {
6381 offset -= error_set_offset;
6382 continue :constant_key switch (error_union.val) {
6383 .err_name => |err_name| .{ .err = .{
6384 .ty = error_union_type.error_set_type,
6385 .name = err_name,
6386 } },
6387 .payload => .{ .int = .{
6388 .ty = error_union_type.error_set_type,
6389 .storage = .{ .u64 = 0 },
6390 } },
6391 };
6392 }
6393 const payload_offset = codegen.errUnionPayloadOffset(payload_ty, zcu);
6394 const payload_size = payload_ty.abiSize(zcu);
6395 if (offset >= payload_offset and offset + size <= payload_offset + payload_size) {
6396 offset -= payload_offset;
6397 switch (error_union.val) {
6398 .err_name => continue :constant_key .{ .undef = error_union_type.payload_type },
6399 .payload => |payload| {
6400 constant = payload;
6401 constant_key = ip.indexToKey(constant);
6402 continue :constant_key constant_key;
6403 },
6404 }
6405 }
6406 },
6407 .enum_tag => |enum_tag| continue :constant_key .{ .int = ip.indexToKey(enum_tag.int).int },
6408 .float => return isel.fail("float unimplemented", .{}),
6409 .ptr => |ptr| {
6410 assert(offset == 0 and size == isel.gprSize());
6411 const dst_ra: Register.Alias, const use_tmp_reg = select_tmp: {
6412 if (dst.asRegisterAlias()) |dst_ra| {
6413 if (dst_ra.mod == .integer) break :select_tmp .{ dst_ra, false };
6414 }
6415 break :select_tmp .{ .{ .reg = try isel.allocRegForWrite(.int), .mod = .integer }, true };
6416 };
6417 const rd = dst_ra.reg;
6418 defer if (use_tmp_reg) isel.freeReg(rd);
6419
6420 if (use_tmp_reg) try isel.moveLoc(dst, 0, .{ .register = dst_ra }, 0, isel.gprSize(), .none);
6421 return switch (ptr.base_addr) {
6422 .nav => |nav| if (ZigType.fromInterned(ip.getNav(nav).resolved.?.type).isRuntimeFnOrHasRuntimeBits(zcu)) {
6423 // TODO code model
6424 try isel.nav_relocs.append(zcu.gpa, .{
6425 .nav = nav,
6426 .reloc = .{
6427 .label = @intCast(isel.instructions.items.len),
6428 .addend = @intCast(ptr.byte_offset),
6429 },
6430 });
6431 try isel.emit(.@"addi.d"(rd, rd, 0));
6432 try isel.nav_relocs.append(zcu.gpa, .{
6433 .nav = nav,
6434 .reloc = .{
6435 .label = @intCast(isel.instructions.items.len),
6436 .addend = @intCast(ptr.byte_offset),
6437 },
6438 });
6439 try isel.emit(.pcalau12i(rd, 0));
6440 } else continue :constant_key .{ .int = .{
6441 .ty = .usize_type,
6442 .storage = .{ .u64 = isel.pt.zcu.navAlignment(nav).forward(0xaaaaaaaaaaaaaaaa) },
6443 } },
6444 .uav => |uav| if (ZigType.fromInterned(ip.typeOf(uav.val)).isRuntimeFnOrHasRuntimeBits(zcu)) {
6445 // TODO code model
6446 try isel.uav_relocs.append(zcu.gpa, .{
6447 .uav = uav,
6448 .reloc = .{
6449 .label = @intCast(isel.instructions.items.len),
6450 .addend = @intCast(ptr.byte_offset),
6451 },
6452 });
6453 try isel.emit(.@"addi.d"(rd, rd, 0));
6454 try isel.uav_relocs.append(zcu.gpa, .{
6455 .uav = uav,
6456 .reloc = .{
6457 .label = @intCast(isel.instructions.items.len),
6458 .addend = @intCast(ptr.byte_offset),
6459 },
6460 });
6461 try isel.emit(.pcalau12i(rd, 0));
6462 } else continue :constant_key .{ .int = .{
6463 .ty = .usize_type,
6464 .storage = .{ .u64 = ZigType.fromInterned(uav.orig_ty).ptrAlignment(zcu).forward(0xaaaaaaaaaaaaaaaa) },
6465 } },
6466 .int => continue :constant_key .{ .int = .{
6467 .ty = .usize_type,
6468 .storage = .{ .u64 = ptr.byte_offset },
6469 } },
6470 .eu_payload => |base| {
6471 var base_ptr = ip.indexToKey(base).ptr;
6472 const eu_ty = ip.indexToKey(base_ptr.ty).ptr_type.child;
6473 const payload_ty = ip.indexToKey(eu_ty).error_union_type.payload_type;
6474 base_ptr.byte_offset += codegen.errUnionPayloadOffset(.fromInterned(payload_ty), zcu) + ptr.byte_offset;
6475 continue :constant_key .{ .ptr = base_ptr };
6476 },
6477 .opt_payload => |base| {
6478 var base_ptr = ip.indexToKey(base).ptr;
6479 base_ptr.byte_offset += ptr.byte_offset;
6480 continue :constant_key .{ .ptr = base_ptr };
6481 },
6482 .field => |field_idx| {
6483 var base_ptr = ip.indexToKey(field_idx.base).ptr;
6484 const agg_ty: ZigType = .fromInterned(ip.indexToKey(base_ptr.ty).ptr_type.child);
6485 base_ptr.byte_offset += agg_ty.structFieldOffset(@intCast(field_idx.index), zcu) + ptr.byte_offset;
6486 continue :constant_key .{ .ptr = base_ptr };
6487 },
6488 .comptime_alloc, .comptime_field, .arr_elem => unreachable,
6489 };
6490 },
6491 .slice => |slice| {
6492 const ptr_size = isel.gprSize();
6493 if (offset == 0 and size == ptr_size) {
6494 constant = slice.ptr;
6495 continue :constant_key switch (ip.indexToKey(slice.ptr)) {
6496 else => unreachable,
6497 .undef => |undef| .{ .undef = undef },
6498 .ptr => |ptr| .{ .ptr = ptr },
6499 };
6500 } else if (offset == ptr_size) {
6501 offset = 0;
6502 constant = slice.len;
6503 continue :constant_key ip.indexToKey(slice.len);
6504 } else if (offset == 0 and size == (@as(u64, ptr_size) * 2)) {
6505 const dst_stack = dst.asStackSlot().?;
6506 try moveConstant(
6507 isel,
6508 .{ .stack_slot = dst_stack },
6509 .fromInterned(slice.ptr),
6510 0,
6511 ptr_size,
6512 );
6513 try moveConstant(
6514 isel,
6515 .{ .stack_slot = dst_stack.withOffset(ptr_size) },
6516 .fromInterned(slice.len),
6517 0,
6518 ptr_size,
6519 );
6520 return;
6521 }
6522 },
6523 .opt => |opt| {
6524 const child_ty = ip.indexToKey(opt.ty).opt_type;
6525 const child_size = ZigType.fromInterned(child_ty).abiSize(zcu);
6526 if (offset == child_size and size == 1) {
6527 offset = 0;
6528 continue :constant_key .{ .simple_value = switch (opt.val) {
6529 .none => .false,
6530 else => .true,
6531 } };
6532 }
6533 const opt_ty: ZigType = .fromInterned(opt.ty);
6534 if (offset + size <= child_size) continue :constant_key switch (opt.val) {
6535 .none => if (opt_ty.optionalReprIsPayload(zcu)) .{ .int = .{
6536 .ty = opt.ty,
6537 .storage = .{ .u64 = 0 },
6538 } } else .{ .undef = child_ty },
6539 else => |child| {
6540 constant = child;
6541 constant_key = ip.indexToKey(constant);
6542 continue :constant_key constant_key;
6543 },
6544 };
6545 },
6546 .aggregate => |aggregate| switch (ip.indexToKey(aggregate.ty)) {
6547 else => unreachable,
6548 .array_type => |array_type| {
6549 const elem_size = ZigType.fromInterned(array_type.child).abiSize(zcu);
6550 const elem_offset = @mod(offset, elem_size);
6551 if (size <= elem_size - elem_offset) {
6552 defer offset = elem_offset;
6553 continue :constant_key switch (aggregate.storage) {
6554 .bytes => |bytes| .{ .int = .{ .ty = .u8_type, .storage = .{
6555 .u64 = bytes.toSlice(array_type.lenIncludingSentinel(), ip)[@intCast(@divFloor(offset, elem_size))],
6556 } } },
6557 .elems => |elems| {
6558 constant = elems[@intCast(@divFloor(offset, elem_size))];
6559 constant_key = ip.indexToKey(constant);
6560 continue :constant_key constant_key;
6561 },
6562 .repeated_elem => |repeated_elem| {
6563 constant = repeated_elem;
6564 constant_key = ip.indexToKey(constant);
6565 continue :constant_key constant_key;
6566 },
6567 };
6568 }
6569 },
6570 .vector_type => {},
6571 .struct_type => {
6572 const loaded_struct = ip.loadStructType(aggregate.ty);
6573 switch (loaded_struct.layout) {
6574 .auto => {
6575 var field_it = loaded_struct.iterateRuntimeOrder(ip);
6576 while (field_it.next()) |field_index| {
6577 if (loaded_struct.field_is_comptime_bits.get(ip, field_index)) continue;
6578 const field_ty: ZigType = .fromInterned(loaded_struct.field_types.get(ip)[field_index]);
6579 const field_offset = loaded_struct.field_offsets.get(ip)[field_index];
6580 const field_size = field_ty.abiSize(zcu);
6581 if (offset >= field_offset and offset + size <= field_offset + field_size) {
6582 offset -= field_offset;
6583 constant = switch (aggregate.storage) {
6584 .bytes => unreachable,
6585 .elems => |elems| elems[field_index],
6586 .repeated_elem => |repeated_elem| repeated_elem,
6587 };
6588 constant_key = ip.indexToKey(constant);
6589 continue :constant_key constant_key;
6590 }
6591 }
6592 },
6593 .@"extern", .@"packed" => {},
6594 }
6595 },
6596 .tuple_type => |tuple_type| {
6597 var field_offset: u64 = 0;
6598 for (tuple_type.types.get(ip), tuple_type.values.get(ip), 0..) |field_type, field_value, field_index| {
6599 if (field_value != .none) continue;
6600 const field_ty: ZigType = .fromInterned(field_type);
6601 field_offset = field_ty.abiAlignment(zcu).forward(field_offset);
6602 const field_size = field_ty.abiSize(zcu);
6603 if (offset >= field_offset and offset + size <= field_offset + field_size) {
6604 offset -= field_offset;
6605 constant = switch (aggregate.storage) {
6606 .bytes => unreachable,
6607 .elems => |elems| elems[field_index],
6608 .repeated_elem => |repeated_elem| repeated_elem,
6609 };
6610 constant_key = ip.indexToKey(constant);
6611 continue :constant_key constant_key;
6612 }
6613 field_offset += field_size;
6614 }
6615 },
6616 },
6617 .un => |un| {
6618 const loaded_union = ip.loadUnionType(un.ty);
6619 const union_layout = ZigType.getUnionLayout(loaded_union, zcu);
6620 if (loaded_union.has_runtime_tag) {
6621 const tag_offset = union_layout.tagOffset();
6622 if (offset >= tag_offset and offset + size <= tag_offset + union_layout.tag_size) {
6623 offset -= tag_offset;
6624 continue :constant_key switch (ip.indexToKey(un.tag)) {
6625 else => unreachable,
6626 .int => |int| .{ .int = int },
6627 .enum_tag => |enum_tag| .{ .enum_tag = enum_tag },
6628 };
6629 }
6630 }
6631 const payload_offset = union_layout.payloadOffset();
6632 if (offset >= payload_offset and offset + size <= payload_offset + union_layout.payload_size) {
6633 offset -= payload_offset;
6634 constant = un.val;
6635 constant_key = ip.indexToKey(constant);
6636 continue :constant_key constant_key;
6637 }
6638 },
6639 }
6640 const constant_size = ZigType.fromInterned(constant_key.typeOf()).abiSize(zcu);
6641 var buffer: [128]u8 align(8) = @splat(0);
6642 // Large constants should have been coerced to smaller ones, so use a buffer with fixed-size
6643 if (constant_size <= buffer.len and
6644 try isel.writeConstantToMemory(.fromInterned(constant), &buffer))
6645 {
6646 // TODO lower to literals or lazy symbols and memcpy for larger constants
6647 assert(offset + size <= buffer.len);
6648 const part_buffer = buffer[@intCast(offset)..];
6649 const gpr_size = isel.gprSize();
6650
6651 const tmp_reg, const tmp_lock: RegLock = tmp_reg: {
6652 if (dst.asRegisterAlias()) |dst_ra| {
6653 if (dst_ra.mod == .integer) break :tmp_reg .{ dst_ra.reg, isel.tryLockReg(dst_ra.reg) };
6654 }
6655 const tmp_reg = try isel.allocRegForWrite(.int);
6656 break :tmp_reg .{ tmp_reg, .{ .reg = tmp_reg } };
6657 };
6658 defer tmp_lock.unlock(isel);
6659 const tmp_ra: Register.Alias = .{ .mod = .integer, .reg = tmp_reg };
6660
6661 var part_offset = size & (0 -% gpr_size);
6662 while (true) {
6663 const part_size = @min(size - part_offset, gpr_size);
6664 const part_value: u64 = switch (part_size) {
6665 else => unreachable,
6666 0 => {
6667 part_offset -= gpr_size;
6668 continue;
6669 },
6670 inline 1...8 => |ct_size| std.mem.readInt(
6671 @Int(.unsigned, 8 * @as(u16, ct_size)),
6672 part_buffer[0..ct_size],
6673 .little,
6674 ),
6675 };
6676
6677 try isel.moveLoc(dst, part_offset, .{ .register = tmp_ra }, 0, part_size, .preserved);
6678 try isel.moveIntImm(tmp_reg, @bitCast(part_value));
6679
6680 if (part_offset == 0) break else part_offset -= gpr_size;
6681 }
6682
6683 return;
6684 }
6685 if (ZigType.fromInterned(ip.typeOf(constant)).isRuntimeFnOrHasRuntimeBits(zcu)) {
6686 const ptr_ty = try isel.pt.singleConstPtrType(.fromInterned(ip.typeOf(constant)));
6687 const uav: InternPool.Key.Ptr.BaseAddr.Uav = .{
6688 .val = constant,
6689 .orig_ty = ptr_ty.ip_index,
6690 };
6691
6692 // allocate temporary register for pointers
6693 const tmp_reg, const allocated_tmp_reg = tmp_reg: {
6694 if (dst.asRegisterAlias()) |dst_ra| {
6695 if (dst_ra.mod == .integer) break :tmp_reg .{ dst_ra.reg, false };
6696 }
6697 break :tmp_reg .{ try isel.allocRegForWrite(.int), true };
6698 };
6699 defer if (allocated_tmp_reg) isel.freeReg(tmp_reg);
6700
6701 // load from the pointer
6702 try isel.moveLoc(
6703 dst,
6704 0,
6705 .{ .stack_slot = .{ .base = tmp_reg, .offset = 0 } },
6706 offset,
6707 size,
6708 .none,
6709 );
6710
6711 // load constant pointer
6712 try isel.uav_relocs.append(zcu.gpa, .{
6713 .uav = uav,
6714 .reloc = .{ .label = @intCast(isel.instructions.items.len), .addend = 0 },
6715 });
6716 try isel.emit(.@"addi.d"(tmp_reg, tmp_reg, 0));
6717 try isel.uav_relocs.append(zcu.gpa, .{
6718 .uav = uav,
6719 .reloc = .{
6720 .label = @intCast(isel.instructions.items.len),
6721 .addend = 0,
6722 },
6723 });
6724 try isel.emit(.pcalau12i(tmp_reg, 0));
6725
6726 return;
6727 }
6728 return isel.fail("unsupported value <{f}, {f}>[{d}..{d}] (full size={d}), from <{f}, {f}>[{d}..{d}]", .{
6729 isel.fmtType(.fromInterned(constant_key.typeOf())),
6730 isel.fmtConstant(.fromInterned(constant)),
6731 offset,
6732 offset + size - 1,
6733 constant_size,
6734 isel.fmtType(init_constant.typeOf(zcu)),
6735 isel.fmtConstant(init_constant),
6736 init_offset,
6737 init_offset + size - 1,
6738 });
6739 }
6740}
6741
6742/// Returns the minimum legal memory operation size that is equal or greater than the given size.
6743fn memOpSizeFitting(size: u64) u64 {
6744 return switch (size) {
6745 0 => unreachable,
6746 1 => 1,
6747 2 => 2,
6748 3...4 => 4,
6749 5...8 => 8,
6750 9...16 => 16,
6751 17...32 => 32,
6752 else => unreachable,
6753 };
6754}
6755
6756pub const CallAbiIterator = struct {
6757 isel: *Select,
6758 cc: *const std.builtin.CallingConvention,
6759 next_reg: std.EnumArray(RegisterClass, Register) = .init(.{
6760 .gpr = .r4,
6761 .fpr = .f0,
6762 .ret_byref = .r4,
6763 }),
6764 next_stack: usize = 0,
6765 // TODO optimize, use SP to read incoming arguments when possible
6766 stack_pointer: Register = .sp,
6767
6768 const RegisterClass = enum {
6769 gpr,
6770 fpr,
6771 /// Virtual register class, for allocating GPRs for by-reference returning.
6772 ret_byref,
6773 };
6774
6775 fn allocReg(it: *CallAbiIterator, class: RegisterClass) ?Register {
6776 const last_reg: Register = switch (class) {
6777 .gpr, .ret_byref => .r11,
6778 .fpr => .f7,
6779 };
6780 const next = it.next_reg.getPtr(class);
6781 if (@backingInt(last_reg) >= @backingInt(next.*)) {
6782 const allocated = next.*;
6783 next.* = @fromBackingInt(@backingInt(allocated) + 1);
6784 return allocated;
6785 } else return null;
6786 }
6787
6788 /// Trys to allocate some registers, returning amount of allocated registers.
6789 fn allocRegs(it: *CallAbiIterator, class: RegisterClass, result: []Register) usize {
6790 const last_reg: Register = switch (class) {
6791 .gpr, .ret_byref => .r11,
6792 .fpr => .f7,
6793 };
6794 const next = it.next_reg.getPtr(class);
6795 const remaining = @backingInt(last_reg) - @backingInt(next.*) + 1;
6796 if (remaining >= result.len) {
6797 for (result, @backingInt(next.*)..) |*v, reg|
6798 v.* = @fromBackingInt(@intCast(reg));
6799 next.* = @fromBackingInt(@intCast(@backingInt(next.*) + result.len));
6800 return result.len;
6801 } else {
6802 for (@backingInt(next.*)..@backingInt(last_reg) + 1, result[0..remaining]) |reg, *v|
6803 v.* = @fromBackingInt(@intCast(reg));
6804 next.* = @fromBackingInt(@backingInt(last_reg) + 1);
6805 return remaining;
6806 }
6807 }
6808
6809 fn assignStack(it: *CallAbiIterator, wip_vi: Value.Index) void {
6810 const isel = it.isel;
6811 assert(wip_vi.stackSlot(isel) == null);
6812 it.next_stack = @intCast(wip_vi.alignment(isel).forward(it.next_stack));
6813 wip_vi.setStackSlot(isel, .{
6814 .base = it.stack_pointer,
6815 .offset = @intCast(it.next_stack),
6816 });
6817 it.next_stack += @intCast(wip_vi.size(isel));
6818 }
6819
6820 fn assignUsize(it: *CallAbiIterator, isel: *Select, wip_vi: Value.Index) void {
6821 if (it.allocReg(.gpr)) |reg| {
6822 wip_vi.setHintRegister(isel, reg);
6823 } else it.assignStack(wip_vi);
6824 }
6825
6826 fn assignGprPair(it: *CallAbiIterator, isel: *Select, wip_vi: Value.Index, part_sizes: [2]u64, part_bit_size: [2]u9) !void {
6827 const grsize: u8 = isel.gprSize();
6828 var regs: [2]Register = undefined;
6829 const allocated_regs = it.allocRegs(.gpr, &regs);
6830 switch (allocated_regs) {
6831 0 => it.assignStack(wip_vi),
6832 1 => {
6833 wip_vi.setParts(isel, 2);
6834 (try wip_vi.addIntPart(isel, 0, part_sizes[0], part_bit_size[0])).setHintRegister(isel, regs[0]);
6835 it.assignStack(try wip_vi.addIntPart(isel, grsize, part_sizes[1], part_bit_size[1]));
6836 },
6837 2 => {
6838 wip_vi.setParts(isel, 2);
6839 (try wip_vi.addIntPart(isel, 0, part_sizes[0], part_bit_size[0])).setHintRegister(isel, regs[0]);
6840 (try wip_vi.addIntPart(isel, grsize, part_sizes[1], part_bit_size[1])).setHintRegister(isel, regs[1]);
6841 },
6842 else => unreachable,
6843 }
6844 }
6845
6846 fn assignIndirect(it: *CallAbiIterator, isel: *Select, wip_vi: Value.Index, is_return: bool) void {
6847 const wip_address_vi = isel.initValueAssumeCapacity(.usize);
6848 wip_vi.setParent(isel, .{ .address = wip_address_vi });
6849
6850 if (it.allocReg(if (is_return) .ret_byref else .gpr)) |reg| {
6851 wip_address_vi.setHintRegister(isel, reg);
6852 } else it.assignStack(wip_address_vi);
6853 }
6854
6855 pub fn resolve(it: *CallAbiIterator, ty: ZigType, is_return: bool) !?Value.Index {
6856 const isel = it.isel;
6857 const zcu = isel.pt.zcu;
6858 const ip = &zcu.intern_pool;
6859
6860 if (!ty.hasRuntimeBits(zcu)) return null;
6861 try isel.values.ensureUnusedCapacity(zcu.gpa, Value.max_parts);
6862 try isel.value_types.ensureUnusedCapacity(zcu.gpa, Value.max_parts);
6863 const wip_vi = isel.initValueAssumeCapacity(ty);
6864 wip_vi.setExtension(isel, .pcsMode(isel, ty));
6865
6866 const grsize = isel.gprSize();
6867 const grlen: u8 = isel.gprBits();
6868
6869 type_key: switch (ip.indexToKey(ty.toIntern())) {
6870 else => return isel.fail("CallAbiIterator.resolve({f})", .{isel.fmtType(ty)}),
6871 .int_type => |int_ty| {
6872 if (int_ty.bits <= grlen) {
6873 it.assignUsize(isel, wip_vi);
6874 } else if (int_ty.bits <= 2 * grlen) {
6875 try it.assignGprPair(isel, wip_vi, .{ grsize, ty.abiSize(zcu) - grsize }, .{ grlen, @intCast(int_ty.bits - grlen) });
6876 } else it.assignStack(wip_vi);
6877 },
6878 .ptr_type => |ptr_type| switch (ptr_type.flags.size) {
6879 .one, .many, .c => it.assignUsize(isel, wip_vi),
6880 .slice => continue :type_key .{ .int_type = .{
6881 .signedness = .unsigned,
6882 .bits = 2 * grlen,
6883 } },
6884 },
6885 .opt_type => |child_type| if (ty.optionalReprIsPayload(zcu))
6886 continue :type_key ip.indexToKey(child_type)
6887 else switch (ZigType.fromInterned(child_type).abiSize(zcu)) {
6888 0 => continue :type_key .{ .simple_type = .bool },
6889 1...7 => it.assignUsize(isel, wip_vi),
6890 8...15 => |child_size| {
6891 try it.assignGprPair(isel, wip_vi, .{ child_size, 1 }, .{ @intCast(child_size * 8), 1 });
6892 },
6893 else => it.assignIndirect(isel, wip_vi, is_return),
6894 },
6895 .anyframe_type => unreachable,
6896 .error_union_type => switch (wip_vi.size(isel)) {
6897 0 => unreachable,
6898 1...8 => it.assignUsize(isel, wip_vi),
6899 // 9...16 => {}, TODO optimize
6900 else => it.assignIndirect(isel, wip_vi, is_return),
6901 },
6902 .simple_type => |simple_type| switch (simple_type) {
6903 .f80 => continue :type_key .{ .int_type = .{ .signedness = .unsigned, .bits = 80 } },
6904 .usize,
6905 .isize,
6906 .c_char,
6907 .c_short,
6908 .c_ushort,
6909 .c_int,
6910 .c_uint,
6911 .c_long,
6912 .c_ulong,
6913 .c_longlong,
6914 .c_ulonglong,
6915 => continue :type_key .{ .int_type = ty.intInfo(zcu) },
6916 .anyopaque, .bool => it.assignUsize(isel, wip_vi),
6917 .anyerror => continue :type_key .{ .int_type = .{
6918 .signedness = .unsigned,
6919 .bits = zcu.errorSetBits(),
6920 } },
6921 .f16, .f32, .f64, .f128, .c_longdouble => return isel.fail("CallAbiIterator.resolve({t})", .{simple_type}),
6922 else => return isel.fail("CallAbiIterator.resolve({t})", .{simple_type}),
6923 },
6924 .struct_type => {
6925 // TODO: implement floating-point structures rules defined in lapcs
6926 const loaded_struct = ip.loadStructType(ty.toIntern());
6927 switch (loaded_struct.layout) {
6928 .auto, .@"extern" => {},
6929 .@"packed" => continue :type_key ip.indexToKey(loaded_struct.packed_backing_int_type),
6930 }
6931 const size = wip_vi.size(isel);
6932 if (size == 0)
6933 unreachable
6934 else if (size <= grsize)
6935 it.assignUsize(isel, wip_vi)
6936 else if (size <= 2 * @as(u64, grsize))
6937 try it.assignGprPair(isel, wip_vi, .{ grsize, size - grsize }, .{ grlen, @intCast((size * 8) - grlen) })
6938 else
6939 // TODO flatten single-field structs
6940 it.assignIndirect(isel, wip_vi, is_return);
6941 },
6942 .union_type => {
6943 const loaded_union = ip.loadUnionType(ty.toIntern());
6944 switch (loaded_union.layout) {
6945 .auto, .@"extern" => {},
6946 .@"packed" => continue :type_key .{ .int_type = .{
6947 .signedness = .unsigned,
6948 .bits = @intCast(ty.bitSize(zcu)),
6949 } },
6950 }
6951 const size = wip_vi.size(isel);
6952 if (size == 0)
6953 unreachable
6954 else if (size <= grsize)
6955 it.assignUsize(isel, wip_vi)
6956 else if (size <= 2 * @as(u64, grsize)) {
6957 const union_layout = ZigType.getUnionLayout(loaded_union, zcu);
6958 var sizes: [2]u64 = @splat(0);
6959 {
6960 const offset = union_layout.tagOffset();
6961 const end = offset % grsize + union_layout.tag_size;
6962 const part_index: usize = @intCast(offset / grsize);
6963 sizes[part_index] = @max(sizes[part_index], @min(end, grsize));
6964 if (end > grsize) sizes[part_index + 1] = @max(sizes[part_index + 1], end - grsize);
6965 }
6966 {
6967 const offset = union_layout.payloadOffset();
6968 const end = offset % grsize + union_layout.payload_size;
6969 const part_index: usize = @intCast(offset / grsize);
6970 sizes[part_index] = @max(sizes[part_index], @min(end, grsize));
6971 if (end > grsize) sizes[part_index + 1] = @max(sizes[part_index + 1], end - grsize);
6972 }
6973 try it.assignGprPair(isel, wip_vi, sizes, .{ @intCast(sizes[0] * 8), @intCast(sizes[1] * 8) });
6974 } else it.assignIndirect(isel, wip_vi, is_return);
6975 },
6976 .tuple_type => |tuple_ty| {
6977 assert(it.cc.* == .auto);
6978 const size = wip_vi.size(isel);
6979 switch (size) {
6980 0 => unreachable,
6981 1...8 => it.assignUsize(isel, wip_vi),
6982 9...16 => {
6983 var part_offset: u64 = 0;
6984 var part_sizes: [2]u64 = undefined;
6985 var parts_len: Value.PartsLen = 0;
6986 var next_field_end: u64 = 0;
6987 var field_index: usize = 0;
6988 while (part_offset < size) {
6989 const field_end = next_field_end;
6990 const next_field_begin = while (field_index < tuple_ty.types.len) {
6991 defer field_index += 1;
6992 if (tuple_ty.values.get(ip)[field_index] != .none) continue;
6993 const field_ty: ZigType = .fromInterned(tuple_ty.types.get(ip)[field_index]);
6994 const next_field_begin = field_ty.abiAlignment(zcu).forward(field_end);
6995 next_field_end = next_field_begin + field_ty.abiSize(zcu);
6996 break next_field_begin;
6997 } else std.mem.alignForward(u64, size, 8);
6998 while (next_field_begin - part_offset >= 8) {
6999 const part_size = @min(field_end - part_offset, 8);
7000 part_sizes[parts_len] = part_size;
7001 assert(part_offset + part_size <= size);
7002 parts_len += 1;
7003 part_offset += part_size;
7004 if (part_offset >= field_end) part_offset = next_field_begin;
7005 }
7006 }
7007 assert(parts_len == part_sizes.len);
7008 try it.assignGprPair(isel, wip_vi, part_sizes, .{ @intCast(part_sizes[0] * 8), @intCast(part_sizes[1] * 8) });
7009 },
7010 else => it.assignIndirect(isel, wip_vi, is_return),
7011 }
7012 },
7013 // TODO: optimize chance
7014 .array_type => it.assignIndirect(isel, wip_vi, is_return),
7015 .opaque_type, .func_type => continue :type_key .{ .simple_type = .anyopaque },
7016 .enum_type => continue :type_key ip.indexToKey(ip.loadEnumType(ty.toIntern()).int_tag_type),
7017 .error_set_type,
7018 .inferred_error_set_type,
7019 => continue :type_key .{ .simple_type = .anyerror },
7020 }
7021
7022 if (is_return) {
7023 it.next_reg = .init(.{
7024 .gpr = it.next_reg.get(.ret_byref), // skip registers for by-ref returning
7025 .fpr = .f0,
7026 .ret_byref = .zero,
7027 });
7028 it.next_stack = 0;
7029 abi_log.debug("| Return: {f} -> {f}", .{ isel.fmtType(ty), isel.fmtValue(wip_vi) });
7030 } else {
7031 abi_log.debug("| Param: {f} -> {f}", .{ isel.fmtType(ty), isel.fmtValue(wip_vi) });
7032 }
7033
7034 return wip_vi.ref(isel);
7035 }
7036};
7037
7038const call = struct {
7039 const param_reg: Value.Index = @fromBackingInt(@backingInt(Value.Index.allocating) - 2);
7040 const callee_clobbered_reg: Value.Index = @fromBackingInt(@backingInt(Value.Index.allocating) - 1);
7041 const caller_saved_regs: LiveRegisters = .init(.{
7042 .r0 = .free,
7043 .r1 = callee_clobbered_reg,
7044 .r2 = .free,
7045 .r3 = .free,
7046 .r4 = param_reg,
7047 .r5 = param_reg,
7048 .r6 = param_reg,
7049 .r7 = param_reg,
7050 .r8 = param_reg,
7051 .r9 = param_reg,
7052 .r10 = param_reg,
7053 .r11 = param_reg,
7054 .r12 = callee_clobbered_reg,
7055 .r13 = callee_clobbered_reg,
7056 .r14 = callee_clobbered_reg,
7057 .r15 = callee_clobbered_reg,
7058 .r16 = callee_clobbered_reg,
7059 .r17 = callee_clobbered_reg,
7060 .r18 = callee_clobbered_reg,
7061 .r19 = callee_clobbered_reg,
7062 .r20 = callee_clobbered_reg,
7063 .r21 = .free,
7064 .r22 = .free,
7065 .r23 = .free,
7066 .r24 = .free,
7067 .r25 = .free,
7068 .r26 = .free,
7069 .r27 = .free,
7070 .r28 = .free,
7071 .r29 = .free,
7072 .r30 = .free,
7073 .r31 = .free,
7074
7075 .f0 = param_reg,
7076 .f1 = param_reg,
7077 .f2 = param_reg,
7078 .f3 = param_reg,
7079 .f4 = param_reg,
7080 .f5 = param_reg,
7081 .f6 = param_reg,
7082 .f7 = param_reg,
7083 .f8 = callee_clobbered_reg,
7084 .f9 = callee_clobbered_reg,
7085 .f10 = callee_clobbered_reg,
7086 .f11 = callee_clobbered_reg,
7087 .f12 = callee_clobbered_reg,
7088 .f13 = callee_clobbered_reg,
7089 .f14 = callee_clobbered_reg,
7090 .f15 = callee_clobbered_reg,
7091 .f16 = callee_clobbered_reg,
7092 .f17 = callee_clobbered_reg,
7093 .f18 = callee_clobbered_reg,
7094 .f19 = callee_clobbered_reg,
7095 .f20 = callee_clobbered_reg,
7096 .f21 = callee_clobbered_reg,
7097 .f22 = callee_clobbered_reg,
7098 .f23 = callee_clobbered_reg,
7099 .f24 = .free,
7100 .f25 = .free,
7101 .f26 = .free,
7102 .f27 = .free,
7103 .f28 = .free,
7104 .f29 = .free,
7105 .f30 = .free,
7106 .f31 = .free,
7107
7108 .fcc0 = callee_clobbered_reg,
7109 .fcc1 = callee_clobbered_reg,
7110 .fcc2 = callee_clobbered_reg,
7111 .fcc3 = callee_clobbered_reg,
7112 .fcc4 = callee_clobbered_reg,
7113 .fcc5 = callee_clobbered_reg,
7114 .fcc6 = callee_clobbered_reg,
7115 .fcc7 = callee_clobbered_reg,
7116 });
7117
7118 fn prepareReturn(_: *Select) !void {}
7119
7120 fn finishReturn(isel: *Select) !void {
7121 // Lock remaining clobberred registers
7122 const locked_regs = comptime locked_regs: {
7123 var locked_regs: RegisterSet = .empty;
7124 for (std.enums.values(Register)) |reg| switch (caller_saved_regs.get(reg)) {
7125 else => unreachable,
7126 param_reg, callee_clobbered_reg => locked_regs.insert(reg),
7127 .free => {},
7128 };
7129 break :locked_regs locked_regs;
7130 };
7131 try isel.fillRegsBatch(locked_regs, true);
7132 isel.markRegsWritten(locked_regs);
7133 }
7134
7135 fn prepareCallee(isel: *Select) !void {
7136 // Free clobbered registers
7137 var live_reg_it = isel.live_registers.iterator();
7138 while (live_reg_it.next()) |live_reg_entry| switch (caller_saved_regs.get(live_reg_entry.key)) {
7139 else => unreachable,
7140 param_reg => assert(live_reg_entry.value.* == .allocating),
7141 callee_clobbered_reg => isel.freeReg(live_reg_entry.key),
7142 .free => {},
7143 };
7144 }
7145 fn finishCallee(_: *Select) !void {}
7146
7147 fn prepareParams(_: *Select) !void {}
7148 fn paramLiveOut(isel: *Select, vi: Value.Index, layout_vi: Value.Index) !void {
7149 switch (layout_vi.parent(isel)) {
7150 else => return vi.matLiveOut(isel, layout_vi, .{ .mode = .param }),
7151 .address => |addr_vi| return call.paramIndirect(isel, vi, addr_vi),
7152 }
7153 }
7154 fn paramIndirect(isel: *Select, vi: Value.Index, addr_vi: Value.Index) !void {
7155 const val_mat = try vi.mat(isel, .{ .pref = .only_stack });
7156 try paramAddress(
7157 isel,
7158 val_mat.loc().asStackSlot().?,
7159 addr_vi,
7160 );
7161 try val_mat.finish(isel);
7162 }
7163 fn paramAddress(isel: *Select, stack: Value.Indirect, addr_vi: Value.Index) !void {
7164 if (addr_vi.hintRegister(isel)) |addr_reg| {
7165 assert(isel.live_registers.get(addr_reg) == .allocating);
7166 try isel.addImm(addr_reg, stack.base, stack.offset);
7167 } else if (addr_vi.location(isel)) |addr_loc| {
7168 const tmp_reg = try isel.allocRegForWrite(.int);
7169 defer isel.freeReg(tmp_reg);
7170 try isel.addImm(tmp_reg, stack.base, stack.offset);
7171 try isel.moveLoc(
7172 addr_loc,
7173 0,
7174 .{ .register = .{ .mod = .integer, .reg = tmp_reg } },
7175 0,
7176 isel.gprSize(),
7177 .preserved,
7178 );
7179 } else unreachable;
7180 }
7181 fn finishParams(isel: *Select) !void {
7182 // Free parameter registers
7183 var live_reg_it = isel.live_registers.iterator();
7184 while (live_reg_it.next()) |live_reg_entry| switch (caller_saved_regs.get(live_reg_entry.key)) {
7185 else => unreachable,
7186 param_reg => switch (live_reg_entry.value.*) {
7187 _ => {},
7188 .allocating => live_reg_entry.value.* = .free,
7189 .free => unreachable,
7190 },
7191 callee_clobbered_reg, .free => {},
7192 };
7193 }
7194};
7195
7196fn gprSize(isel: *Select) u4 {
7197 return switch (isel.target.cpu.arch) {
7198 .loongarch32 => 4,
7199 .loongarch64 => 8,
7200 else => unreachable,
7201 };
7202}
7203
7204fn gprBits(isel: *Select) u7 {
7205 return switch (isel.target.cpu.arch) {
7206 .loongarch32 => 32,
7207 .loongarch64 => 64,
7208 else => unreachable,
7209 };
7210}
7211
7212fn gprAlignment(isel: *Select) std.mem.Alignment {
7213 return switch (isel.target.cpu.arch) {
7214 .loongarch32 => .@"4",
7215 .loongarch64 => .@"8",
7216 else => unreachable,
7217 };
7218}
7219
7220fn typeOfField(isel: *Select, ty: ZigType, offset: u64) ?ZigType {
7221 const zcu = isel.pt.zcu;
7222 const ip = &zcu.intern_pool;
7223 type_key: switch (ip.indexToKey(ty.toIntern())) {
7224 else => {},
7225 // TODO large int splitting
7226 .int_type => {
7227 if (ty.abiSize(zcu) > isel.gprSize() and offset % isel.gprSize() == 0) return .usize;
7228 },
7229 .ptr_type => |ptr_type| switch (ptr_type.flags.size) {
7230 .one, .many, .c => {},
7231 .slice => if (offset == 0)
7232 return ty.elemPtrType(null, isel.pt) catch unreachable
7233 else if (offset == isel.gprSize())
7234 return .usize,
7235 },
7236 .opt_type => |child_type| if (ty.optionalReprIsPayload(zcu))
7237 continue :type_key ip.indexToKey(child_type)
7238 else {
7239 const child_ty: ZigType = .fromInterned(child_type);
7240 if (offset == 0)
7241 return child_ty
7242 else if (offset == child_ty.abiSize(zcu))
7243 return .usize;
7244 },
7245 .array_type => unreachable, // TODO
7246 .anyframe_type => unreachable,
7247 .error_union_type => |error_union_type| {
7248 const payload_ty: ZigType = .fromInterned(error_union_type.payload_type);
7249 if (offset == codegen.errUnionErrorOffset(payload_ty, zcu))
7250 return .fromInterned(error_union_type.error_set_type)
7251 else if (offset == codegen.errUnionPayloadOffset(payload_ty, zcu))
7252 return payload_ty;
7253 },
7254 .simple_type => |simple_type| switch (simple_type) {
7255 else => {},
7256 .f80 => continue :type_key .{ .int_type = .{ .signedness = .unsigned, .bits = 80 } },
7257 .usize,
7258 .isize,
7259 .c_char,
7260 .c_short,
7261 .c_ushort,
7262 .c_int,
7263 .c_uint,
7264 .c_long,
7265 .c_ulong,
7266 .c_longlong,
7267 .c_ulonglong,
7268 => continue :type_key .{ .int_type = ty.intInfo(zcu) },
7269 .anyerror => continue :type_key .{ .int_type = .{ .signedness = .unsigned, .bits = zcu.errorSetBits() } },
7270 },
7271 .struct_type => {
7272 const loaded_struct = ip.loadStructType(ty.toIntern());
7273 switch (loaded_struct.layout) {
7274 .auto, .@"extern" => {},
7275 .@"packed" => continue :type_key ip.indexToKey(loaded_struct.backingIntTypeUnordered(ip)).int_type,
7276 }
7277 var field_end: u64 = 0;
7278 var field_it = loaded_struct.iterateRuntimeOrder(ip);
7279 while (field_it.next()) |field_index| {
7280 const field_ty: ZigType = .fromInterned(loaded_struct.field_types.get(ip)[field_index]);
7281 const field_begin = switch (loaded_struct.fieldAlign(ip, field_index)) {
7282 .none => field_ty.abiAlignment(zcu),
7283 else => |field_align| field_align,
7284 }.forward(field_end);
7285 const field_size = field_ty.abiSize(zcu);
7286 field_end = field_begin + field_size;
7287 if (field_begin > offset) break;
7288 if (field_begin == offset)
7289 return field_ty
7290 else if (field_end > offset)
7291 return isel.typeOfField(field_ty, offset - field_begin);
7292 }
7293 },
7294 .tuple_type => |tuple_type| {
7295 var field_end: u64 = 0;
7296 for (tuple_type.types.get(ip), tuple_type.values.get(ip)) |field_type, field_value| {
7297 if (field_value != .none) continue;
7298 const field_ty: ZigType = .fromInterned(field_type);
7299 const field_begin = field_ty.abiAlignment(zcu).forward(field_end);
7300 const field_size = field_ty.abiSize(zcu);
7301 if (field_size == 0) continue;
7302 field_end = field_begin + field_size;
7303 if (field_begin > offset) break;
7304 if (field_begin == offset)
7305 return field_ty
7306 else if (field_end > offset)
7307 return isel.typeOfField(field_ty, offset - field_begin);
7308 }
7309 },
7310 .union_type => {
7311 const loaded_union = ip.loadUnionType(ty.toIntern());
7312 switch (loaded_union.flagsUnordered(ip).layout) {
7313 .auto, .@"extern" => {},
7314 .@"packed" => continue :type_key .{ .int_type = .{
7315 .signedness = .unsigned,
7316 .bits = @intCast(ty.bitSize(zcu)),
7317 } },
7318 }
7319 const union_layout = ZigType.getUnionLayout(loaded_union, zcu);
7320 if (offset == union_layout.tagOffset())
7321 return .fromInterned(loaded_union.enum_tag_ty);
7322 },
7323 .opaque_type, .func_type => continue :type_key .{ .simple_type = .anyopaque },
7324 .enum_type => continue :type_key ip.indexToKey(ip.loadEnumType(ty.toIntern()).tag_ty),
7325 .error_set_type,
7326 .inferred_error_set_type,
7327 => continue :type_key .{ .simple_type = .anyerror },
7328 }
7329 tracking_log.debug("cannot split {f} at {d}", .{ isel.fmtType(ty), offset });
7330 return null;
7331}
7332
7333fn hasRepeatedByteRepr(isel: *Select, constant: Constant) error{OutOfMemory}!?u8 {
7334 const zcu = isel.pt.zcu;
7335 const ty = constant.typeOf(zcu);
7336 const abi_size = std.math.cast(usize, ty.abiSize(zcu)) orelse return null;
7337 const byte_buffer = try zcu.gpa.alloc(u8, abi_size);
7338 defer zcu.gpa.free(byte_buffer);
7339 return if (try isel.writeConstantToMemory(constant, byte_buffer) and
7340 std.mem.allEqual(u8, byte_buffer[1..], byte_buffer[0])) byte_buffer[0] else null;
7341}
7342
7343fn writeConstantToMemory(isel: *Select, constant: Constant, buffer: []u8) error{OutOfMemory}!bool {
7344 const zcu = isel.pt.zcu;
7345 const ip = &zcu.intern_pool;
7346 if (try isel.writeConstantKeyToMemory(ip.indexToKey(constant.toIntern()), buffer)) return true;
7347 constant.writeToMemory(isel.pt.zcu, buffer) catch |err| switch (err) {
7348 error.OutOfMemory => return error.OutOfMemory,
7349 error.ReinterpretDeclRef, error.IllDefinedMemoryLayout => return false,
7350 };
7351 return true;
7352}
7353
7354fn writeConstantKeyToMemory(isel: *Select, constant_key: InternPool.Key, buffer: []u8) error{OutOfMemory}!bool {
7355 const zcu = isel.pt.zcu;
7356 const ip = &zcu.intern_pool;
7357 switch (constant_key) {
7358 .int_type,
7359 .ptr_type,
7360 .array_type,
7361 .vector_type,
7362 .opt_type,
7363 .anyframe_type,
7364 .error_union_type,
7365 .simple_type,
7366 .struct_type,
7367 .tuple_type,
7368 .union_type,
7369 .opaque_type,
7370 .enum_type,
7371 .func_type,
7372 .error_set_type,
7373 .inferred_error_set_type,
7374
7375 .enum_literal,
7376 .memoized_call,
7377 => unreachable, // not a runtime value
7378 .err => |err| {
7379 const error_int = ip.getErrorValueIfExists(err.name).?;
7380 switch (buffer.len) {
7381 else => unreachable,
7382 inline 1...4 => |size| std.mem.writeInt(
7383 @Int(.unsigned, 8 * size),
7384 buffer[0..size],
7385 @intCast(error_int),
7386 isel.target.cpu.arch.endian(),
7387 ),
7388 }
7389 },
7390 .error_union => |error_union| {
7391 const error_union_type = ip.indexToKey(error_union.ty).error_union_type;
7392 const error_set_ty: ZigType = .fromInterned(error_union_type.error_set_type);
7393 const payload_ty: ZigType = .fromInterned(error_union_type.payload_type);
7394 const error_set = buffer[@intCast(codegen.errUnionErrorOffset(payload_ty, zcu))..][0..@intCast(error_set_ty.abiSize(zcu))];
7395 switch (error_union.val) {
7396 .err_name => |err_name| if (!try isel.writeConstantKeyToMemory(.{ .err = .{
7397 .ty = error_set_ty.toIntern(),
7398 .name = err_name,
7399 } }, error_set)) return false,
7400 .payload => |payload| {
7401 if (!try isel.writeConstantToMemory(
7402 .fromInterned(payload),
7403 buffer[@intCast(codegen.errUnionPayloadOffset(payload_ty, zcu))..][0..@intCast(payload_ty.abiSize(zcu))],
7404 )) return false;
7405 @memset(error_set, 0);
7406 },
7407 }
7408 },
7409 .opt => |opt| {
7410 const child_size: usize = @intCast(ZigType.fromInterned(ip.indexToKey(opt.ty).opt_type).abiSize(zcu));
7411 switch (opt.val) {
7412 .none => if (!ZigType.fromInterned(opt.ty).optionalReprIsPayload(zcu)) {
7413 buffer[child_size] = @intFromBool(false);
7414 } else @memset(buffer[0..child_size], 0x00),
7415 else => |child_constant| {
7416 if (!try isel.writeConstantToMemory(.fromInterned(child_constant), buffer[0..child_size])) return false;
7417 if (!ZigType.fromInterned(opt.ty).optionalReprIsPayload(zcu)) buffer[child_size] = @intFromBool(true);
7418 },
7419 }
7420 },
7421 .aggregate => |aggregate| switch (ip.indexToKey(aggregate.ty)) {
7422 else => unreachable,
7423 .array_type => |array_type| {
7424 var elem_offset: usize = 0;
7425 const elem_size: usize = @intCast(ZigType.fromInterned(array_type.child).abiSize(zcu));
7426 const len_including_sentinel: usize = @intCast(array_type.lenIncludingSentinel());
7427 switch (aggregate.storage) {
7428 .bytes => |bytes| @memcpy(buffer[0..len_including_sentinel], bytes.toSlice(len_including_sentinel, ip)),
7429 .elems => |elems| for (elems) |elem| {
7430 if (!try isel.writeConstantToMemory(.fromInterned(elem), buffer[elem_offset..][0..elem_size])) return false;
7431 elem_offset += elem_size;
7432 },
7433 .repeated_elem => |repeated_elem| for (0..len_including_sentinel) |_| {
7434 if (!try isel.writeConstantToMemory(.fromInterned(repeated_elem), buffer[elem_offset..][0..elem_size])) return false;
7435 elem_offset += elem_size;
7436 },
7437 }
7438 },
7439 .vector_type => return false,
7440 .struct_type => {
7441 const loaded_struct = ip.loadStructType(aggregate.ty);
7442 switch (loaded_struct.layout) {
7443 .auto => {
7444 var field_it = loaded_struct.iterateRuntimeOrder(ip);
7445 while (field_it.next()) |field_index| {
7446 if (loaded_struct.field_is_comptime_bits.get(ip, field_index)) continue;
7447 const field_ty: ZigType = .fromInterned(loaded_struct.field_types.get(ip)[field_index]);
7448 const field_offset = loaded_struct.field_offsets.get(ip)[field_index];
7449 const field_size = field_ty.abiSize(zcu);
7450 if (!try isel.writeConstantToMemory(.fromInterned(switch (aggregate.storage) {
7451 .bytes => unreachable,
7452 .elems => |elems| elems[field_index],
7453 .repeated_elem => |repeated_elem| repeated_elem,
7454 }), buffer[@intCast(field_offset)..][0..@intCast(field_size)])) return false;
7455 }
7456 },
7457 .@"extern", .@"packed" => return false,
7458 }
7459 },
7460 .tuple_type => |tuple_type| {
7461 var field_offset: u64 = 0;
7462 for (tuple_type.types.get(ip), tuple_type.values.get(ip), 0..) |field_type, field_value, field_index| {
7463 if (field_value != .none) continue;
7464 const field_ty: ZigType = .fromInterned(field_type);
7465 field_offset = field_ty.abiAlignment(zcu).forward(field_offset);
7466 const field_size = field_ty.abiSize(zcu);
7467 if (!try isel.writeConstantToMemory(.fromInterned(switch (aggregate.storage) {
7468 .bytes => unreachable,
7469 .elems => |elems| elems[field_index],
7470 .repeated_elem => |repeated_elem| repeated_elem,
7471 }), buffer[@intCast(field_offset)..][0..@intCast(field_size)])) return false;
7472 field_offset += field_size;
7473 }
7474 },
7475 },
7476 .un => |union_val| {
7477 const loaded_union = ip.loadUnionType(union_val.ty);
7478 switch (loaded_union.layout) {
7479 .auto => {},
7480 .@"extern", .@"packed" => return false,
7481 }
7482 const union_layout = ZigType.getUnionLayout(loaded_union, zcu);
7483 if (loaded_union.has_runtime_tag)
7484 if (!try isel.writeConstantToMemory(
7485 .fromInterned(union_val.tag),
7486 buffer[@intCast(union_layout.tagOffset())..][0..@intCast(union_layout.tag_size)],
7487 )) return false;
7488 if (!try isel.writeConstantToMemory(
7489 .fromInterned(union_val.val),
7490 buffer[@intCast(union_layout.payloadOffset())..][0..@intCast(union_layout.payload_size)],
7491 )) return false;
7492 },
7493 else => return false,
7494 }
7495 return true;
7496}
7497
7498fn wipeLocationDfs(isel: *Select, vi: Value.Index) void {
7499 _ = vi.takeLocationMarkWritten(isel);
7500 var part_it = vi.parts(isel);
7501 while (part_it.next()) |part_vi| {
7502 if (part_vi != vi) isel.wipeLocationDfs(part_vi);
7503 }
7504}
7505
7506const Air = @import("../../Air.zig");
7507const assert = std.debug.assert;
7508const codegen = @import("../../codegen.zig");
7509const Constant = @import("../../Value.zig");
7510const InternPool = @import("../../InternPool.zig");
7511const Module = @import("../../Module.zig");
7512const Select = @This();
7513const std = @import("std");
7514const tracking_log = std.log.scoped(.tracking);
7515const wip_mir_log = std.log.scoped(.@"wip-mir");
7516const abi_log = std.log.scoped(.abi);
7517const Zcu = @import("../../Zcu.zig");
7518const ZigType = @import("../../Type.zig");
src/codegen/loongarch/bits.zig created+243
......@@ -0,0 +1,243 @@
1const std = @import("std");
2const Target = std.Target;
3const assert = std.debug.assert;
4const expectEqual = std.testing.expectEqual;
5const Writer = std.Io.Writer;
6
7/// Register, one per set of aliasing registers
8pub const Register = enum(u7) {
9 // zig fmt: off
10 // integer registers
11 r0, r1, r2, r3, r4, r5, r6, r7,
12 r8, r9, r10, r11, r12, r13, r14, r15,
13 r16, r17, r18, r19, r20, r21, r22, r23,
14 r24, r25, r26, r27, r28, r29, r30, r31,
15
16 // float-point/LSX/LASX registers
17 f0, f1, f2, f3, f4, f5, f6, f7,
18 f8, f9, f10, f11, f12, f13, f14, f15,
19 f16, f17, f18, f19, f20, f21, f22, f23,
20 f24, f25, f26, f27, f28, f29, f30, f31,
21
22 // float-point condition code registers
23 fcc0, fcc1, fcc2, fcc3, fcc4, fcc5, fcc6, fcc7,
24 // zig fmt: on
25
26 pub const zero: Register = .r0;
27 pub const ra: Register = .r1;
28 pub const tp: Register = .r2;
29 pub const sp: Register = .r3;
30 pub const fp: Register = .r22;
31 pub const t0: Register = .r12;
32
33 /// Register banks.
34 pub const Class = enum { int, fp, fcc };
35
36 /// Register accessing modifier.
37 pub const Modifier = enum(u3) {
38 undef,
39 integer,
40 floating32,
41 floating64,
42 lsx,
43 lasx,
44 fcc,
45
46 pub fn class(modifier: Modifier) Class {
47 return switch (modifier) {
48 .undef => unreachable,
49 .integer => .int,
50 .floating32, .floating64, .lsx, .lasx => .fp,
51 .fcc => .fcc,
52 };
53 }
54
55 pub fn bitSize(modifier: Modifier, target: *const Target) u16 {
56 return switch (modifier) {
57 .undef => 0,
58 .integer => switch (target.cpu.arch) {
59 .loongarch32 => 32,
60 .loongarch64 => 64,
61 else => unreachable,
62 },
63 .floating32 => 32,
64 .floating64 => 64,
65 .lsx => 128,
66 .lasx => 256,
67 .fcc => 1,
68 };
69 }
70
71 /// Upper-rounded byte size.
72 pub fn byteSize(modifier: Modifier, target: *const Target) u16 {
73 return switch (modifier) {
74 .undef => 0,
75 .integer => switch (target.cpu.arch) {
76 .loongarch32 => 4,
77 .loongarch64 => 8,
78 else => unreachable,
79 },
80 .floating32 => 4,
81 .floating64 => 8,
82 .lsx => 16,
83 .lasx => 32,
84 .fcc => 1,
85 };
86 }
87
88 pub fn fromFloating(bits: u16) Modifier {
89 return switch (bits) {
90 else => unreachable,
91 32 => .floating32,
92 64 => .floating64,
93 };
94 }
95 };
96
97 pub const Alias = struct {
98 reg: Register,
99 mod: Modifier,
100
101 pub const zero: Alias = .{ .mod = .integer, .reg = .zero };
102
103 pub fn format(self: Alias, w: *std.Io.Writer) std.Io.Writer.Error!void {
104 try w.print("${s}{d}", .{
105 switch (self.mod) {
106 .undef => "?",
107 .integer => "r",
108 .floating32 => "(s)f",
109 .floating64 => "(d)f",
110 .lsx => "v",
111 .lasx => "x",
112 .fcc => "fcc",
113 },
114 self.reg.encode(),
115 });
116 }
117 };
118
119 pub fn class(reg: Register) Class {
120 return switch (@backingInt(reg)) {
121 @backingInt(Register.r0)...@backingInt(Register.r31) => .int,
122 @backingInt(Register.f0)...@backingInt(Register.f31) => .fp,
123 @backingInt(Register.fcc0)...@backingInt(Register.fcc7) => .fcc,
124 else => unreachable,
125 };
126 }
127
128 pub fn encode(reg: Register) u5 {
129 const base: u7 = switch (@backingInt(reg)) {
130 @backingInt(Register.r0)...@backingInt(Register.r31) => @backingInt(Register.r0),
131 @backingInt(Register.f0)...@backingInt(Register.f31) => @backingInt(Register.f0),
132 @backingInt(Register.fcc0)...@backingInt(Register.fcc7) => @backingInt(Register.fcc0),
133 else => unreachable,
134 };
135 return @intCast(@backingInt(reg) - base);
136 }
137
138 pub fn decode(reg_class: Class, reg: u5) Register {
139 const base: u7 = switch (reg_class) {
140 .int => @backingInt(Register.r0),
141 .fp => @backingInt(Register.f0),
142 .fcc => @backingInt(Register.fcc0),
143 };
144 return @fromBackingInt(base + @as(u7, reg));
145 }
146
147 pub fn parse(reg: []const u8) ?Register {
148 if (reg.len == 0) return null;
149 if (reg[0] == '$') return parse(reg[1..]);
150 if (toLowerEqlAssertLower(reg, "zero")) return .zero;
151 if (toLowerEqlAssertLower(reg, "ra")) return .ra;
152 if (toLowerEqlAssertLower(reg, "tp")) return .tp;
153 if (toLowerEqlAssertLower(reg, "sp")) return .sp;
154 if (toLowerEqlAssertLower(reg, "fp")) return .fp;
155 return switch (std.ascii.toLower(reg[0])) {
156 else => null,
157 'r' => reg: {
158 break :reg if (std.fmt.parseInt(u5, reg[1..], 10)) |n| .decode(.int, n) else |_| null;
159 },
160 'f' => reg: {
161 if (reg.len == 4 and toLowerEqlAssertLower(reg[0..3], "fcc"))
162 break :reg if (std.ascii.isDigit(reg[3])) .decode(.fcc, @intCast(reg[3] ^ '0')) else null;
163 if (reg.len > 2 and toLowerEqlAssertLower(reg[0..2], "fa"))
164 break :reg if (std.fmt.parseInt(u5, reg[2..], 10)) |n| .decode(.fp, n) else |_| null;
165 if (reg.len > 2 and toLowerEqlAssertLower(reg[0..2], "ft"))
166 break :reg if (std.fmt.parseInt(u5, reg[2..], 10)) |n| .decode(.fp, 8 + n) else |_| null;
167 if (reg.len > 2 and toLowerEqlAssertLower(reg[0..2], "fs"))
168 break :reg if (std.fmt.parseInt(u5, reg[2..], 10)) |n| .decode(.fp, 24 + n) else |_| null;
169
170 break :reg if (std.fmt.parseInt(u5, reg[1..], 10)) |n| .decode(.fp, n) else |_| null;
171 },
172 'v', 'x' => reg: {
173 if (reg.len < 3 or std.ascii.toLower(reg[1]) != 'r') break :reg null;
174 break :reg if (std.fmt.parseInt(u5, reg[2..], 10)) |n| .decode(.fp, n) else |_| null;
175 },
176 'a' => if (std.fmt.parseInt(u5, reg[1..], 10)) |n| .decode(.int, 4 + n) else |_| null,
177 't' => if (std.fmt.parseInt(u5, reg[1..], 10)) |n| .decode(.int, 12 + n) else |_| null,
178 's' => if (std.fmt.parseInt(u5, reg[1..], 10)) |n| reg: {
179 if (n == 9) break :reg .r22;
180 break :reg .decode(.int, 23 + n);
181 } else |_| null,
182 };
183 }
184
185 fn toLowerEqlAssertLower(lhs: []const u8, rhs: []const u8) bool {
186 if (lhs.len != rhs.len) return false;
187 for (lhs, rhs) |l, r| {
188 assert(!std.ascii.isUpper(r));
189 if (std.ascii.toLower(l) != r) return false;
190 }
191 return true;
192 }
193};
194
195test "register classes" {
196 try expectEqual(.int, Register.r0.class());
197 try expectEqual(.int, Register.r31.class());
198 try expectEqual(.fp, Register.f0.class());
199 try expectEqual(.fp, Register.f31.class());
200 try expectEqual(.fcc, Register.fcc0.class());
201 try expectEqual(.fcc, Register.fcc7.class());
202}
203
204test "register encoding" {
205 try expectEqual(0, Register.r0.encode());
206 try expectEqual(31, Register.r31.encode());
207 try expectEqual(0, Register.f0.encode());
208 try expectEqual(31, Register.f31.encode());
209 try expectEqual(0, Register.fcc0.encode());
210 try expectEqual(7, Register.fcc7.encode());
211}
212
213test "register decoding" {
214 try expectEqual(.r0, Register.decode(.int, 0));
215 try expectEqual(.r31, Register.decode(.int, 31));
216 try expectEqual(.f0, Register.decode(.fp, 0));
217 try expectEqual(.f31, Register.decode(.fp, 31));
218 try expectEqual(.fcc0, Register.decode(.fcc, 0));
219 try expectEqual(.fcc7, Register.decode(.fcc, 7));
220}
221
222test "register parsing" {
223 try expectEqual(.r0, Register.parse("r0").?);
224 try expectEqual(.r0, Register.parse("ZERO").?);
225 try expectEqual(.r0, Register.parse("zero").?);
226 try expectEqual(.r0, Register.parse("$zero").?);
227 try expectEqual(Register.ra, Register.parse("ra").?);
228 try expectEqual(Register.tp, Register.parse("tp").?);
229 try expectEqual(Register.sp, Register.parse("sp").?);
230 try expectEqual(Register.fp, Register.parse("fp").?);
231 try expectEqual(.r7, Register.parse("a3").?);
232 try expectEqual(.r15, Register.parse("t3").?);
233 try expectEqual(.r26, Register.parse("s3").?);
234 try expectEqual(.r22, Register.parse("s9").?);
235 try expectEqual(.fcc0, Register.parse("fcc0").?);
236 try expectEqual(.fcc7, Register.parse("fcc7").?);
237 try expectEqual(.f0, Register.parse("f0").?);
238 try expectEqual(.f3, Register.parse("fa3").?);
239 try expectEqual(.f11, Register.parse("ft3").?);
240 try expectEqual(.f27, Register.parse("fs3").?);
241 try expectEqual(.f0, Register.parse("vr0").?);
242 try expectEqual(.f0, Register.parse("xr0").?);
243}
src/target.zig+3-1
......@@ -853,6 +853,7 @@ pub fn supportsThreads(target: *const std.Target, backend: std.lang.CompilerBack
853853 _ = target;
854854 return switch (backend) {
855855 .stage2_aarch64 => false,
856 .stage2_loongarch => false,
856857 else => true,
857858 };
858859}
......@@ -914,6 +915,7 @@ pub fn zigBackend(target: *const std.Target, use_llvm: bool) std.lang.CompilerBa
914915 return switch (target.cpu.arch) {
915916 .aarch64, .aarch64_be => .stage2_aarch64,
916917 .arm, .armeb, .thumb, .thumbeb => .stage2_arm,
918 .loongarch32, .loongarch64 => .stage2_loongarch,
917919 .powerpc, .powerpcle, .powerpc64, .powerpc64le => .stage2_powerpc,
918920 .riscv64 => .stage2_riscv64,
919921 .sparc64 => .stage2_sparc64,
......@@ -950,7 +952,7 @@ pub inline fn backendSupportsFeature(backend: std.lang.CompilerBackend, comptime
950952 else => false,
951953 },
952954 .field_reordering => switch (backend) {
953 .stage2_aarch64, .stage2_c, .stage2_llvm, .stage2_x86_64, .stage2_wasm => true,
955 .stage2_aarch64, .stage2_c, .stage2_llvm, .stage2_loongarch, .stage2_x86_64, .stage2_wasm => true,
954956 else => false,
955957 },
956958 .separate_thread => switch (backend) {