master
1//! SPARCv9 codegen.
2//! This lowers AIR into MIR.
3//! For now this only implements medium/low code model with absolute addressing.
4//! TODO add support for other code models.
5const std = @import("std");
6const assert = std.debug.assert;
7const log = std.log.scoped(.codegen);
8const math = std.math;
9const mem = std.mem;
10const Allocator = mem.Allocator;
11const builtin = @import("builtin");
12const link = @import("../../link.zig");
13const Zcu = @import("../../Zcu.zig");
14const InternPool = @import("../../InternPool.zig");
15const Value = @import("../../Value.zig");
16const ErrorMsg = Zcu.ErrorMsg;
17const codegen = @import("../../codegen.zig");
18const Air = @import("../../Air.zig");
19const Mir = @import("Mir.zig");
20const Emit = @import("Emit.zig");
21const Type = @import("../../Type.zig");
22const CodeGenError = codegen.CodeGenError;
23const Endian = std.builtin.Endian;
24const Alignment = InternPool.Alignment;
25
26const build_options = @import("build_options");
27
28const bits = @import("bits.zig");
29const abi = @import("abi.zig");
30const errUnionPayloadOffset = codegen.errUnionPayloadOffset;
31const errUnionErrorOffset = codegen.errUnionErrorOffset;
32const Instruction = bits.Instruction;
33const ASI = Instruction.ASI;
34const ShiftWidth = Instruction.ShiftWidth;
35const RegisterManager = abi.RegisterManager;
36const RegisterLock = RegisterManager.RegisterLock;
37const Register = bits.Register;
38const gp = abi.RegisterClass.gp;
39
40const Self = @This();
41
42const InnerError = CodeGenError || error{OutOfRegisters};
43
44pub fn legalizeFeatures(_: *const std.Target) ?*const Air.Legalize.Features {
45 return null;
46}
47
48const RegisterView = enum(u1) {
49 caller,
50 callee,
51};
52
53gpa: Allocator,
54pt: Zcu.PerThread,
55air: Air,
56liveness: Air.Liveness,
57bin_file: *link.File,
58target: *const std.Target,
59func_index: InternPool.Index,
60err_msg: ?*ErrorMsg,
61args: []MCValue,
62ret_mcv: MCValue,
63fn_type: Type,
64arg_index: usize,
65src_loc: Zcu.LazySrcLoc,
66stack_align: Alignment,
67
68/// MIR Instructions
69mir_instructions: std.MultiArrayList(Mir.Inst) = .{},
70/// MIR extra data
71mir_extra: std.ArrayList(u32) = .empty,
72
73/// Byte offset within the source file of the ending curly.
74end_di_line: u32,
75end_di_column: u32,
76
77/// The value is an offset into the `Function` `code` from the beginning.
78/// To perform the reloc, write 32-bit signed little-endian integer
79/// which is a relative jump, based on the address following the reloc.
80exitlude_jump_relocs: std.ArrayList(usize) = .empty,
81
82reused_operands: std.StaticBitSet(Air.Liveness.bpi - 1) = undefined,
83
84/// Whenever there is a runtime branch, we push a Branch onto this stack,
85/// and pop it off when the runtime branch joins. This provides an "overlay"
86/// of the table of mappings from instructions to `MCValue` from within the branch.
87/// This way we can modify the `MCValue` for an instruction in different ways
88/// within different branches. Special consideration is needed when a branch
89/// joins with its parent, to make sure all instructions have the same MCValue
90/// across each runtime branch upon joining.
91branch_stack: *std.array_list.Managed(Branch),
92
93// Key is the block instruction
94blocks: std.AutoHashMapUnmanaged(Air.Inst.Index, BlockData) = .empty,
95
96register_manager: RegisterManager = .{},
97
98/// Maps offset to what is stored there.
99stack: std.AutoHashMapUnmanaged(u32, StackAllocation) = .empty,
100
101/// Tracks the current instruction allocated to the condition flags
102condition_flags_inst: ?Air.Inst.Index = null,
103
104/// Tracks the current instruction allocated to the condition register
105condition_register_inst: ?Air.Inst.Index = null,
106
107/// Offset from the stack base, representing the end of the stack frame.
108max_end_stack: u32 = 0,
109/// Represents the current end stack offset. If there is no existing slot
110/// to place a new stack allocation, it goes here, and then bumps `max_end_stack`.
111next_stack_offset: u32 = 0,
112
113/// Debug field, used to find bugs in the compiler.
114air_bookkeeping: @TypeOf(air_bookkeeping_init) = air_bookkeeping_init,
115
116const air_bookkeeping_init = if (std.debug.runtime_safety) @as(usize, 0) else {};
117
118const MCValue = union(enum) {
119 /// No runtime bits. `void` types, empty structs, u0, enums with 1 tag, etc.
120 /// TODO Look into deleting this tag and using `dead` instead, since every use
121 /// of MCValue.none should be instead looking at the type and noticing it is 0 bits.
122 none,
123 /// Control flow will not allow this value to be observed.
124 unreach,
125 /// No more references to this value remain.
126 dead,
127 /// The value is undefined.
128 undef,
129 /// A pointer-sized integer that fits in a register.
130 /// If the type is a pointer, this is the pointer address in virtual address space.
131 immediate: u64,
132 /// The value is in a target-specific register.
133 register: Register,
134 /// The value is a tuple { wrapped, overflow } where
135 /// wrapped is stored in the register and the overflow bit is
136 /// stored in the C (signed) or V (unsigned) flag of the CCR.
137 ///
138 /// This MCValue is only generated by a add_with_overflow or
139 /// sub_with_overflow instruction operating on 32- or 64-bit values.
140 register_with_overflow: struct {
141 reg: Register,
142 flag: struct { cond: Instruction.ICondition, ccr: Instruction.CCR },
143 },
144 /// The value is in memory at a hard-coded address.
145 /// If the type is a pointer, it means the pointer address is at this memory location.
146 memory: u64,
147 /// The value is one of the stack variables.
148 /// If the type is a pointer, it means the pointer address is in the stack at this offset.
149 /// Note that this stores the plain value (i.e without the effects of the stack bias).
150 /// Always convert this value into machine offsets with realStackOffset() before
151 /// lowering into asm!
152 stack_offset: u32,
153 /// The value is a pointer to one of the stack variables (payload is stack offset).
154 ptr_stack_offset: u32,
155 /// The value is in the specified CCR. The value is 1 (if
156 /// the type is u1) or true (if the type in bool) iff the
157 /// specified condition is true.
158 condition_flags: struct {
159 cond: Instruction.Condition,
160 ccr: Instruction.CCR,
161 },
162 /// The value is in the specified Register. The value is 1 (if
163 /// the type is u1) or true (if the type in bool) iff the
164 /// specified condition is true.
165 condition_register: struct {
166 cond: Instruction.RCondition,
167 reg: Register,
168 },
169
170 fn isMemory(mcv: MCValue) bool {
171 return switch (mcv) {
172 .memory, .stack_offset => true,
173 else => false,
174 };
175 }
176
177 fn isImmediate(mcv: MCValue) bool {
178 return switch (mcv) {
179 .immediate => true,
180 else => false,
181 };
182 }
183
184 fn isMutable(mcv: MCValue) bool {
185 return switch (mcv) {
186 .none => unreachable,
187 .unreach => unreachable,
188 .dead => unreachable,
189
190 .immediate,
191 .memory,
192 .condition_flags,
193 .condition_register,
194 .ptr_stack_offset,
195 .undef,
196 => false,
197
198 .register,
199 .stack_offset,
200 => true,
201 };
202 }
203};
204
205const Branch = struct {
206 inst_table: std.AutoArrayHashMapUnmanaged(Air.Inst.Index, MCValue) = .empty,
207
208 fn deinit(self: *Branch, gpa: Allocator) void {
209 self.inst_table.deinit(gpa);
210 self.* = undefined;
211 }
212};
213
214const StackAllocation = struct {
215 inst: Air.Inst.Index,
216 /// TODO do we need size? should be determined by inst.ty.abiSize()
217 size: u32,
218};
219
220const BlockData = struct {
221 relocs: std.ArrayList(Mir.Inst.Index),
222 /// The first break instruction encounters `null` here and chooses a
223 /// machine code value for the block result, populating this field.
224 /// Following break instructions encounter that value and use it for
225 /// the location to store their block results.
226 mcv: MCValue,
227};
228
229const CallMCValues = struct {
230 args: []MCValue,
231 return_value: MCValue,
232 stack_byte_count: u32,
233 stack_align: Alignment,
234
235 fn deinit(self: *CallMCValues, func: *Self) void {
236 func.gpa.free(self.args);
237 self.* = undefined;
238 }
239};
240
241const BigTomb = struct {
242 function: *Self,
243 inst: Air.Inst.Index,
244 lbt: Air.Liveness.BigTomb,
245
246 fn feed(bt: *BigTomb, op_ref: Air.Inst.Ref) void {
247 const dies = bt.lbt.feed();
248 const op_index = op_ref.toIndex() orelse return;
249 if (!dies) return;
250 bt.function.processDeath(op_index);
251 }
252
253 fn finishAir(bt: *BigTomb, result: MCValue) void {
254 const is_used = !bt.function.liveness.isUnused(bt.inst);
255 if (is_used) {
256 log.debug("%{d} => {}", .{ bt.inst, result });
257 const branch = &bt.function.branch_stack.items[bt.function.branch_stack.items.len - 1];
258 branch.inst_table.putAssumeCapacityNoClobber(bt.inst, result);
259 }
260 bt.function.finishAirBookkeeping();
261 }
262};
263
264pub fn generate(
265 lf: *link.File,
266 pt: Zcu.PerThread,
267 src_loc: Zcu.LazySrcLoc,
268 func_index: InternPool.Index,
269 air: *const Air,
270 liveness: *const ?Air.Liveness,
271) CodeGenError!Mir {
272 const zcu = pt.zcu;
273 const gpa = zcu.gpa;
274 const func = zcu.funcInfo(func_index);
275 const func_ty = Type.fromInterned(func.ty);
276 const file_scope = zcu.navFileScope(func.owner_nav);
277 const target = &file_scope.mod.?.resolved_target.result;
278
279 var branch_stack = std.array_list.Managed(Branch).init(gpa);
280 defer {
281 assert(branch_stack.items.len == 1);
282 branch_stack.items[0].deinit(gpa);
283 branch_stack.deinit();
284 }
285 try branch_stack.append(.{});
286
287 var function: Self = .{
288 .gpa = gpa,
289 .pt = pt,
290 .air = air.*,
291 .liveness = liveness.*.?,
292 .target = target,
293 .bin_file = lf,
294 .func_index = func_index,
295 .err_msg = null,
296 .args = undefined, // populated after `resolveCallingConventionValues`
297 .ret_mcv = undefined, // populated after `resolveCallingConventionValues`
298 .fn_type = func_ty,
299 .arg_index = 0,
300 .branch_stack = &branch_stack,
301 .src_loc = src_loc,
302 .stack_align = undefined,
303 .end_di_line = func.rbrace_line,
304 .end_di_column = func.rbrace_column,
305 };
306 defer function.stack.deinit(gpa);
307 defer function.blocks.deinit(gpa);
308 defer function.exitlude_jump_relocs.deinit(gpa);
309
310 var call_info = function.resolveCallingConventionValues(func_ty, .callee) catch |err| switch (err) {
311 error.CodegenFail => return error.CodegenFail,
312 else => |e| return e,
313 };
314 defer call_info.deinit(&function);
315
316 function.args = call_info.args;
317 function.ret_mcv = call_info.return_value;
318 function.stack_align = call_info.stack_align;
319 function.max_end_stack = call_info.stack_byte_count;
320
321 function.gen() catch |err| switch (err) {
322 error.CodegenFail => return error.CodegenFail,
323 error.OutOfRegisters => return function.fail("ran out of registers (Zig compiler bug)", .{}),
324 else => |e| return e,
325 };
326
327 var mir: Mir = .{
328 .instructions = function.mir_instructions.toOwnedSlice(),
329 .extra = &.{}, // fallible, so populated after errdefer
330 };
331 errdefer mir.deinit(gpa);
332 mir.extra = try function.mir_extra.toOwnedSlice(gpa);
333 return mir;
334}
335
336fn gen(self: *Self) !void {
337 const pt = self.pt;
338 const zcu = pt.zcu;
339 const cc = self.fn_type.fnCallingConvention(zcu);
340 if (cc != .naked) {
341 // TODO Finish function prologue and epilogue for sparc64.
342
343 // save %sp, stack_reserved_area, %sp
344 const save_inst = try self.addInst(.{
345 .tag = .save,
346 .data = .{
347 .arithmetic_3op = .{
348 .is_imm = true,
349 .rd = .sp,
350 .rs1 = .sp,
351 .rs2_or_imm = .{ .imm = -abi.stack_reserved_area },
352 },
353 },
354 });
355
356 _ = try self.addInst(.{
357 .tag = .dbg_prologue_end,
358 .data = .{ .nop = {} },
359 });
360
361 try self.genBody(self.air.getMainBody());
362
363 _ = try self.addInst(.{
364 .tag = .dbg_epilogue_begin,
365 .data = .{ .nop = {} },
366 });
367
368 // exitlude jumps
369 if (self.exitlude_jump_relocs.items.len > 0 and
370 self.exitlude_jump_relocs.items[self.exitlude_jump_relocs.items.len - 1] == self.mir_instructions.len - 3)
371 {
372 // If the last Mir instruction (apart from the
373 // dbg_epilogue_begin) is the last exitlude jump
374 // relocation (which would just jump two instructions
375 // further), it can be safely removed
376 const index = self.exitlude_jump_relocs.pop().?;
377
378 // First, remove the delay slot, then remove
379 // the branch instruction itself.
380 self.mir_instructions.orderedRemove(index + 1);
381 self.mir_instructions.orderedRemove(index);
382 }
383
384 for (self.exitlude_jump_relocs.items) |jmp_reloc| {
385 self.mir_instructions.set(jmp_reloc, .{
386 .tag = .bpcc,
387 .data = .{
388 .branch_predict_int = .{
389 .ccr = .xcc,
390 .cond = .al,
391 .inst = @as(u32, @intCast(self.mir_instructions.len)),
392 },
393 },
394 });
395 }
396
397 // Backpatch stack offset
398 const total_stack_size = self.max_end_stack + abi.stack_reserved_area;
399 const stack_size = self.stack_align.forward(total_stack_size);
400 if (math.cast(i13, stack_size)) |size| {
401 self.mir_instructions.set(save_inst, .{
402 .tag = .save,
403 .data = .{
404 .arithmetic_3op = .{
405 .is_imm = true,
406 .rd = .sp,
407 .rs1 = .sp,
408 .rs2_or_imm = .{ .imm = -size },
409 },
410 },
411 });
412 } else {
413 // TODO for large stacks, replace the prologue with:
414 // setx stack_size, %g1
415 // save %sp, %g1, %sp
416 return self.fail("TODO SPARCv9: allow larger stacks", .{});
417 }
418
419 // return %i7 + 8
420 _ = try self.addInst(.{
421 .tag = .@"return",
422 .data = .{
423 .arithmetic_2op = .{
424 .is_imm = true,
425 .rs1 = .i7,
426 .rs2_or_imm = .{ .imm = 8 },
427 },
428 },
429 });
430
431 // Branches in SPARC have a delay slot, that is, the instruction
432 // following it will unconditionally be executed.
433 // See: Section 3.2.3 Control Transfer in SPARCv9 manual.
434 // See also: https://arcb.csc.ncsu.edu/~mueller/codeopt/codeopt00/notes/delaybra.html
435 // TODO Find a way to fill this delay slot
436 // nop
437 _ = try self.addInst(.{
438 .tag = .nop,
439 .data = .{ .nop = {} },
440 });
441 } else {
442 _ = try self.addInst(.{
443 .tag = .dbg_prologue_end,
444 .data = .{ .nop = {} },
445 });
446
447 try self.genBody(self.air.getMainBody());
448
449 _ = try self.addInst(.{
450 .tag = .dbg_epilogue_begin,
451 .data = .{ .nop = {} },
452 });
453 }
454
455 // Drop them off at the rbrace.
456 _ = try self.addInst(.{
457 .tag = .dbg_line,
458 .data = .{ .dbg_line_column = .{
459 .line = self.end_di_line,
460 .column = self.end_di_column,
461 } },
462 });
463}
464
465fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
466 const pt = self.pt;
467 const zcu = pt.zcu;
468 const ip = &zcu.intern_pool;
469 const air_tags = self.air.instructions.items(.tag);
470
471 for (body) |inst| {
472 // TODO: remove now-redundant isUnused calls from AIR handler functions
473 if (self.liveness.isUnused(inst) and !self.air.mustLower(inst, ip))
474 continue;
475
476 const old_air_bookkeeping = self.air_bookkeeping;
477 try self.ensureProcessDeathCapacity(Air.Liveness.bpi);
478
479 self.reused_operands = @TypeOf(self.reused_operands).initEmpty();
480 switch (air_tags[@intFromEnum(inst)]) {
481 // zig fmt: off
482
483 // No "scalarize" legalizations are enabled, so these instructions never appear.
484 .legalize_vec_elem_val => unreachable,
485 .legalize_vec_store_elem => unreachable,
486 // No soft float legalizations are enabled.
487 .legalize_compiler_rt_call => unreachable,
488
489 .ptr_add => try self.airPtrArithmetic(inst, .ptr_add),
490 .ptr_sub => try self.airPtrArithmetic(inst, .ptr_sub),
491
492 .add => try self.airBinOp(inst, .add),
493 .add_wrap => try self.airBinOp(inst, .add_wrap),
494 .sub => try self.airBinOp(inst, .sub),
495 .sub_wrap => try self.airBinOp(inst, .sub_wrap),
496 .mul => try self.airBinOp(inst, .mul),
497 .mul_wrap => try self.airBinOp(inst, .mul_wrap),
498 .shl => try self.airBinOp(inst, .shl),
499 .shl_exact => try self.airBinOp(inst, .shl_exact),
500 .shr => try self.airBinOp(inst, .shr),
501 .shr_exact => try self.airBinOp(inst, .shr_exact),
502 .bool_and => try self.airBinOp(inst, .bool_and),
503 .bool_or => try self.airBinOp(inst, .bool_or),
504 .bit_and => try self.airBinOp(inst, .bit_and),
505 .bit_or => try self.airBinOp(inst, .bit_or),
506 .xor => try self.airBinOp(inst, .xor),
507
508 .add_sat => try self.airAddSat(inst),
509 .sub_sat => try self.airSubSat(inst),
510 .mul_sat => try self.airMulSat(inst),
511 .shl_sat => try self.airShlSat(inst),
512 .min, .max => try self.airMinMax(inst),
513 .rem => try self.airRem(inst),
514 .mod => try self.airMod(inst),
515 .slice => try self.airSlice(inst),
516
517 .sqrt,
518 .sin,
519 .cos,
520 .tan,
521 .exp,
522 .exp2,
523 .log,
524 .log2,
525 .log10,
526 .abs,
527 .floor,
528 .ceil,
529 .round,
530 .trunc_float,
531 .neg,
532 => try self.airUnaryMath(inst),
533
534 .add_with_overflow => try self.airAddSubWithOverflow(inst),
535 .sub_with_overflow => try self.airAddSubWithOverflow(inst),
536 .mul_with_overflow => try self.airMulWithOverflow(inst),
537 .shl_with_overflow => try self.airShlWithOverflow(inst),
538
539 .div_float, .div_trunc, .div_floor, .div_exact => try self.airDiv(inst),
540
541 .cmp_lt => try self.airCmp(inst, .lt),
542 .cmp_lte => try self.airCmp(inst, .lte),
543 .cmp_eq => try self.airCmp(inst, .eq),
544 .cmp_gte => try self.airCmp(inst, .gte),
545 .cmp_gt => try self.airCmp(inst, .gt),
546 .cmp_neq => try self.airCmp(inst, .neq),
547 .cmp_vector => @panic("TODO try self.airCmpVector(inst)"),
548 .cmp_lt_errors_len => try self.airCmpLtErrorsLen(inst),
549
550 .alloc => try self.airAlloc(inst),
551 .ret_ptr => try self.airRetPtr(inst),
552 .arg => try self.airArg(inst),
553 .assembly => try self.airAsm(inst),
554 .bitcast => try self.airBitCast(inst),
555 .block => try self.airBlock(inst),
556 .br => try self.airBr(inst),
557 .repeat => return self.fail("TODO implement `repeat`", .{}),
558 .switch_dispatch => return self.fail("TODO implement `switch_dispatch`", .{}),
559 .trap => try self.airTrap(),
560 .breakpoint => try self.airBreakpoint(),
561 .ret_addr => @panic("TODO try self.airRetAddr(inst)"),
562 .frame_addr => @panic("TODO try self.airFrameAddress(inst)"),
563 .cond_br => try self.airCondBr(inst),
564 .fptrunc => @panic("TODO try self.airFptrunc(inst)"),
565 .fpext => @panic("TODO try self.airFpext(inst)"),
566 .intcast => try self.airIntCast(inst),
567 .trunc => try self.airTrunc(inst),
568 .is_non_null => try self.airIsNonNull(inst),
569 .is_non_null_ptr => @panic("TODO try self.airIsNonNullPtr(inst)"),
570 .is_null => try self.airIsNull(inst),
571 .is_null_ptr => @panic("TODO try self.airIsNullPtr(inst)"),
572 .is_non_err => try self.airIsNonErr(inst),
573 .is_non_err_ptr => @panic("TODO try self.airIsNonErrPtr(inst)"),
574 .is_err => try self.airIsErr(inst),
575 .is_err_ptr => @panic("TODO try self.airIsErrPtr(inst)"),
576 .load => try self.airLoad(inst),
577 .loop => try self.airLoop(inst),
578 .not => try self.airNot(inst),
579 .ret => try self.airRet(inst),
580 .ret_safe => try self.airRet(inst), // TODO
581 .ret_load => try self.airRetLoad(inst),
582 .store => try self.airStore(inst, false),
583 .store_safe => try self.airStore(inst, true),
584 .struct_field_ptr=> try self.airStructFieldPtr(inst),
585 .struct_field_val=> try self.airStructFieldVal(inst),
586 .array_to_slice => try self.airArrayToSlice(inst),
587 .float_from_int => try self.airFloatFromInt(inst),
588 .int_from_float => try self.airIntFromFloat(inst),
589 .cmpxchg_strong,
590 .cmpxchg_weak,
591 => try self.airCmpxchg(inst),
592 .atomic_rmw => try self.airAtomicRmw(inst),
593 .atomic_load => try self.airAtomicLoad(inst),
594 .memcpy => @panic("TODO try self.airMemcpy(inst)"),
595 .memmove => @panic("TODO try self.airMemmove(inst)"),
596 .memset => try self.airMemset(inst, false),
597 .memset_safe => try self.airMemset(inst, true),
598 .set_union_tag => try self.airSetUnionTag(inst),
599 .get_union_tag => try self.airGetUnionTag(inst),
600 .clz => try self.airClz(inst),
601 .ctz => try self.airCtz(inst),
602 .popcount => try self.airPopcount(inst),
603 .byte_swap => try self.airByteSwap(inst),
604 .bit_reverse => try self.airBitReverse(inst),
605 .tag_name => try self.airTagName(inst),
606 .error_name => try self.airErrorName(inst),
607 .splat => try self.airSplat(inst),
608 .select => @panic("TODO try self.airSelect(inst)"),
609 .shuffle_one => @panic("TODO try self.airShuffleOne(inst)"),
610 .shuffle_two => @panic("TODO try self.airShuffleTwo(inst)"),
611 .reduce => @panic("TODO try self.airReduce(inst)"),
612 .aggregate_init => try self.airAggregateInit(inst),
613 .union_init => try self.airUnionInit(inst),
614 .prefetch => try self.airPrefetch(inst),
615 .mul_add => @panic("TODO try self.airMulAdd(inst)"),
616 .addrspace_cast => @panic("TODO try self.airAddrSpaceCast(int)"),
617
618 .@"try" => try self.airTry(inst),
619 .try_cold => try self.airTry(inst),
620 .try_ptr => @panic("TODO try self.airTryPtr(inst)"),
621 .try_ptr_cold => @panic("TODO try self.airTryPtrCold(inst)"),
622
623 .dbg_stmt => try self.airDbgStmt(inst),
624 .dbg_empty_stmt => self.finishAirBookkeeping(),
625 .dbg_inline_block => try self.airDbgInlineBlock(inst),
626 .dbg_var_ptr,
627 .dbg_var_val,
628 .dbg_arg_inline,
629 => try self.airDbgVar(inst),
630
631 .call => try self.airCall(inst, .auto),
632 .call_always_tail => try self.airCall(inst, .always_tail),
633 .call_never_tail => try self.airCall(inst, .never_tail),
634 .call_never_inline => try self.airCall(inst, .never_inline),
635
636 .atomic_store_unordered => @panic("TODO try self.airAtomicStore(inst, .unordered)"),
637 .atomic_store_monotonic => @panic("TODO try self.airAtomicStore(inst, .monotonic)"),
638 .atomic_store_release => @panic("TODO try self.airAtomicStore(inst, .release)"),
639 .atomic_store_seq_cst => @panic("TODO try self.airAtomicStore(inst, .seq_cst)"),
640
641 .struct_field_ptr_index_0 => try self.airStructFieldPtrIndex(inst, 0),
642 .struct_field_ptr_index_1 => try self.airStructFieldPtrIndex(inst, 1),
643 .struct_field_ptr_index_2 => try self.airStructFieldPtrIndex(inst, 2),
644 .struct_field_ptr_index_3 => try self.airStructFieldPtrIndex(inst, 3),
645
646 .field_parent_ptr => @panic("TODO try self.airFieldParentPtr(inst)"),
647
648 .switch_br => try self.airSwitch(inst),
649 .loop_switch_br => return self.fail("TODO implement `loop_switch_br`", .{}),
650 .slice_ptr => try self.airSlicePtr(inst),
651 .slice_len => try self.airSliceLen(inst),
652
653 .ptr_slice_len_ptr => try self.airPtrSliceLenPtr(inst),
654 .ptr_slice_ptr_ptr => try self.airPtrSlicePtrPtr(inst),
655
656 .array_elem_val => try self.airArrayElemVal(inst),
657 .slice_elem_val => try self.airSliceElemVal(inst),
658 .slice_elem_ptr => @panic("TODO try self.airSliceElemPtr(inst)"),
659 .ptr_elem_val => try self.airPtrElemVal(inst),
660 .ptr_elem_ptr => try self.airPtrElemPtr(inst),
661
662 .inferred_alloc, .inferred_alloc_comptime => unreachable,
663 .unreach => self.finishAirBookkeeping(),
664
665 .optional_payload => try self.airOptionalPayload(inst),
666 .optional_payload_ptr => try self.airOptionalPayloadPtr(inst),
667 .optional_payload_ptr_set => try self.airOptionalPayloadPtrSet(inst),
668 .unwrap_errunion_err => try self.airUnwrapErrErr(inst),
669 .unwrap_errunion_payload => try self.airUnwrapErrPayload(inst),
670 .unwrap_errunion_err_ptr => @panic("TODO try self.airUnwrapErrErrPtr(inst)"),
671 .unwrap_errunion_payload_ptr=> @panic("TODO try self.airUnwrapErrPayloadPtr(inst)"),
672 .errunion_payload_ptr_set => try self.airErrUnionPayloadPtrSet(inst),
673 .err_return_trace => @panic("TODO try self.airErrReturnTrace(inst)"),
674 .set_err_return_trace => @panic("TODO try self.airSetErrReturnTrace(inst)"),
675 .save_err_return_trace_index=> @panic("TODO try self.airSaveErrReturnTraceIndex(inst)"),
676
677 .wrap_optional => try self.airWrapOptional(inst),
678 .wrap_errunion_payload => try self.airWrapErrUnionPayload(inst),
679 .wrap_errunion_err => try self.airWrapErrUnionErr(inst),
680
681 .add_optimized,
682 .sub_optimized,
683 .mul_optimized,
684 .div_float_optimized,
685 .div_trunc_optimized,
686 .div_floor_optimized,
687 .div_exact_optimized,
688 .rem_optimized,
689 .mod_optimized,
690 .neg_optimized,
691 .cmp_lt_optimized,
692 .cmp_lte_optimized,
693 .cmp_eq_optimized,
694 .cmp_gte_optimized,
695 .cmp_gt_optimized,
696 .cmp_neq_optimized,
697 .cmp_vector_optimized,
698 .reduce_optimized,
699 .int_from_float_optimized,
700 => @panic("TODO implement optimized float mode"),
701
702 .add_safe,
703 .sub_safe,
704 .mul_safe,
705 .intcast_safe,
706 .int_from_float_safe,
707 .int_from_float_optimized_safe,
708 => @panic("TODO implement safety_checked_instructions"),
709
710 .is_named_enum_value => @panic("TODO implement is_named_enum_value"),
711 .error_set_has_value => @panic("TODO implement error_set_has_value"),
712 .runtime_nav_ptr => @panic("TODO implement runtime_nav_ptr"),
713
714 .c_va_arg => return self.fail("TODO implement c_va_arg", .{}),
715 .c_va_copy => return self.fail("TODO implement c_va_copy", .{}),
716 .c_va_end => return self.fail("TODO implement c_va_end", .{}),
717 .c_va_start => return self.fail("TODO implement c_va_start", .{}),
718
719 .wasm_memory_size => unreachable,
720 .wasm_memory_grow => unreachable,
721
722 .work_item_id => unreachable,
723 .work_group_size => unreachable,
724 .work_group_id => unreachable,
725 // zig fmt: on
726 }
727
728 assert(!self.register_manager.lockedRegsExist());
729
730 if (std.debug.runtime_safety) {
731 if (self.air_bookkeeping < old_air_bookkeeping + 1) {
732 std.debug.panic("in codegen.zig, handling of AIR instruction %{d} ('{t}') did not do proper bookkeeping. Look for a missing call to finishAir.", .{ inst, air_tags[@intFromEnum(inst)] });
733 }
734 }
735 }
736}
737
738fn airAddSat(self: *Self, inst: Air.Inst.Index) !void {
739 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
740 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement add_sat for {}", .{self.target.cpu.arch});
741 return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
742}
743
744fn airAddSubWithOverflow(self: *Self, inst: Air.Inst.Index) !void {
745 const tag = self.air.instructions.items(.tag)[@intFromEnum(inst)];
746 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
747 const extra = self.air.extraData(Air.Bin, ty_pl.payload).data;
748 const pt = self.pt;
749 const zcu = pt.zcu;
750 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
751 const lhs = try self.resolveInst(extra.lhs);
752 const rhs = try self.resolveInst(extra.rhs);
753 const lhs_ty = self.typeOf(extra.lhs);
754 const rhs_ty = self.typeOf(extra.rhs);
755
756 switch (lhs_ty.zigTypeTag(zcu)) {
757 .vector => return self.fail("TODO implement add_with_overflow/sub_with_overflow for vectors", .{}),
758 .int => {
759 assert(lhs_ty.eql(rhs_ty, zcu));
760 const int_info = lhs_ty.intInfo(zcu);
761 switch (int_info.bits) {
762 32, 64 => {
763 // Only say yes if the operation is
764 // commutative, i.e. we can swap both of the
765 // operands
766 const lhs_immediate_ok = switch (tag) {
767 .add_with_overflow => lhs == .immediate and lhs.immediate <= std.math.maxInt(u12),
768 .sub_with_overflow => false,
769 else => unreachable,
770 };
771 const rhs_immediate_ok = switch (tag) {
772 .add_with_overflow,
773 .sub_with_overflow,
774 => rhs == .immediate and rhs.immediate <= std.math.maxInt(u12),
775 else => unreachable,
776 };
777
778 const mir_tag: Mir.Inst.Tag = switch (tag) {
779 .add_with_overflow => .addcc,
780 .sub_with_overflow => .subcc,
781 else => unreachable,
782 };
783
784 try self.spillConditionFlagsIfOccupied();
785
786 const dest = blk: {
787 if (rhs_immediate_ok) {
788 break :blk try self.binOpImmediate(mir_tag, lhs, rhs, lhs_ty, false, null);
789 } else if (lhs_immediate_ok) {
790 // swap lhs and rhs
791 break :blk try self.binOpImmediate(mir_tag, rhs, lhs, rhs_ty, true, null);
792 } else {
793 break :blk try self.binOpRegister(mir_tag, lhs, rhs, lhs_ty, rhs_ty, null);
794 }
795 };
796
797 const cond = switch (int_info.signedness) {
798 .unsigned => switch (tag) {
799 .add_with_overflow => Instruction.ICondition.cs,
800 .sub_with_overflow => Instruction.ICondition.cc,
801 else => unreachable,
802 },
803 .signed => Instruction.ICondition.vs,
804 };
805
806 const ccr = switch (int_info.bits) {
807 32 => Instruction.CCR.icc,
808 64 => Instruction.CCR.xcc,
809 else => unreachable,
810 };
811
812 break :result MCValue{ .register_with_overflow = .{
813 .reg = dest.register,
814 .flag = .{ .cond = cond, .ccr = ccr },
815 } };
816 },
817 else => return self.fail("TODO overflow operations on other integer sizes", .{}),
818 }
819 },
820 else => unreachable,
821 }
822 };
823 return self.finishAir(inst, result, .{ extra.lhs, extra.rhs, .none });
824}
825
826fn airAggregateInit(self: *Self, inst: Air.Inst.Index) !void {
827 const pt = self.pt;
828 const zcu = pt.zcu;
829 const vector_ty = self.typeOfIndex(inst);
830 const len = vector_ty.vectorLen(zcu);
831 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
832 const elements: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[ty_pl.payload..][0..len]);
833 const result: MCValue = res: {
834 if (self.liveness.isUnused(inst)) break :res MCValue.dead;
835 return self.fail("TODO implement airAggregateInit for {}", .{self.target.cpu.arch});
836 };
837
838 if (elements.len <= Air.Liveness.bpi - 1) {
839 var buf = [1]Air.Inst.Ref{.none} ** (Air.Liveness.bpi - 1);
840 @memcpy(buf[0..elements.len], elements);
841 return self.finishAir(inst, result, buf);
842 }
843 var bt = try self.iterateBigTomb(inst, elements.len);
844 for (elements) |elem| {
845 bt.feed(elem);
846 }
847 return bt.finishAir(result);
848}
849
850fn airAlloc(self: *Self, inst: Air.Inst.Index) !void {
851 const stack_offset = try self.allocMemPtr(inst);
852 return self.finishAir(inst, .{ .ptr_stack_offset = stack_offset }, .{ .none, .none, .none });
853}
854
855fn airArrayElemVal(self: *Self, inst: Air.Inst.Index) !void {
856 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
857 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement array_elem_val for {}", .{self.target.cpu.arch});
858 return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
859}
860
861fn airArrayToSlice(self: *Self, inst: Air.Inst.Index) !void {
862 const pt = self.pt;
863 const zcu = pt.zcu;
864 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
865 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
866 const ptr_ty = self.typeOf(ty_op.operand);
867 const ptr = try self.resolveInst(ty_op.operand);
868 const array_ty = ptr_ty.childType(zcu);
869 const array_len: u32 = @intCast(array_ty.arrayLen(zcu));
870 const ptr_bytes = 8;
871 const stack_offset = try self.allocMem(inst, ptr_bytes * 2, .@"8");
872 try self.genSetStack(ptr_ty, stack_offset, ptr);
873 try self.genSetStack(Type.usize, stack_offset - ptr_bytes, .{ .immediate = array_len });
874 break :result MCValue{ .stack_offset = stack_offset };
875 };
876 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
877}
878
879fn airAsm(self: *Self, inst: Air.Inst.Index) !void {
880 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
881 const extra = self.air.extraData(Air.Asm, ty_pl.payload);
882 const is_volatile = extra.data.flags.is_volatile;
883 const outputs_len = extra.data.flags.outputs_len;
884 var extra_i: usize = extra.end;
885 const outputs: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra_i .. extra_i + outputs_len]);
886 extra_i += outputs.len;
887 const inputs: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra_i .. extra_i + extra.data.inputs_len]);
888 extra_i += inputs.len;
889
890 const dead = !is_volatile and self.liveness.isUnused(inst);
891 const result: MCValue = if (dead) .dead else result: {
892 if (outputs.len > 1) {
893 return self.fail("TODO implement codegen for asm with more than 1 output", .{});
894 }
895
896 const output_constraint: ?[]const u8 = for (outputs) |output| {
897 if (output != .none) {
898 return self.fail("TODO implement codegen for non-expr asm", .{});
899 }
900 const extra_bytes = std.mem.sliceAsBytes(self.air.extra.items[extra_i..]);
901 const constraint = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra.items[extra_i..]), 0);
902 const name = std.mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0);
903 // This equation accounts for the fact that even if we have exactly 4 bytes
904 // for the string, we still use the next u32 for the null terminator.
905 extra_i += (constraint.len + name.len + (2 + 3)) / 4;
906
907 break constraint;
908 } else null;
909
910 for (inputs) |input| {
911 const input_bytes = std.mem.sliceAsBytes(self.air.extra.items[extra_i..]);
912 const constraint = std.mem.sliceTo(input_bytes, 0);
913 const name = std.mem.sliceTo(input_bytes[constraint.len + 1 ..], 0);
914 // This equation accounts for the fact that even if we have exactly 4 bytes
915 // for the string, we still use the next u32 for the null terminator.
916 extra_i += (constraint.len + name.len + (2 + 3)) / 4;
917
918 if (constraint.len < 3 or constraint[0] != '{' or constraint[constraint.len - 1] != '}') {
919 return self.fail("unrecognized asm input constraint: '{s}'", .{constraint});
920 }
921 const reg_name = constraint[1 .. constraint.len - 1];
922 const reg = parseRegName(reg_name) orelse
923 return self.fail("unrecognized register: '{s}'", .{reg_name});
924
925 const arg_mcv = try self.resolveInst(input);
926 try self.register_manager.getReg(reg, null);
927 try self.genSetReg(self.typeOf(input), reg, arg_mcv);
928 }
929
930 // TODO honor the clobbers
931 _ = extra.data.clobbers;
932
933 const asm_source = std.mem.sliceAsBytes(self.air.extra.items[extra_i..])[0..extra.data.source_len];
934
935 if (mem.eql(u8, asm_source, "ta 0x6d")) {
936 _ = try self.addInst(.{
937 .tag = .tcc,
938 .data = .{
939 .trap = .{
940 .is_imm = true,
941 .cond = .al,
942 .rs2_or_imm = .{ .imm = 0x6d },
943 },
944 },
945 });
946 } else {
947 return self.fail("TODO implement a full SPARCv9 assembly parsing", .{});
948 }
949
950 if (output_constraint) |output| {
951 if (output.len < 4 or output[0] != '=' or output[1] != '{' or output[output.len - 1] != '}') {
952 return self.fail("unrecognized asm output constraint: '{s}'", .{output});
953 }
954 const reg_name = output[2 .. output.len - 1];
955 const reg = parseRegName(reg_name) orelse
956 return self.fail("unrecognized register: '{s}'", .{reg_name});
957 break :result MCValue{ .register = reg };
958 } else {
959 break :result MCValue{ .none = {} };
960 }
961 };
962
963 simple: {
964 var buf = [1]Air.Inst.Ref{.none} ** (Air.Liveness.bpi - 1);
965 var buf_index: usize = 0;
966 for (outputs) |output| {
967 if (output == .none) continue;
968
969 if (buf_index >= buf.len) break :simple;
970 buf[buf_index] = output;
971 buf_index += 1;
972 }
973 if (buf_index + inputs.len > buf.len) break :simple;
974 @memcpy(buf[buf_index..][0..inputs.len], inputs);
975 return self.finishAir(inst, result, buf);
976 }
977
978 var bt = try self.iterateBigTomb(inst, outputs.len + inputs.len);
979 for (outputs) |output| {
980 if (output == .none) continue;
981
982 bt.feed(output);
983 }
984 for (inputs) |input| {
985 bt.feed(input);
986 }
987 return bt.finishAir(result);
988}
989
990fn airArg(self: *Self, inst: Air.Inst.Index) InnerError!void {
991 const pt = self.pt;
992 const zcu = pt.zcu;
993 const arg_index = self.arg_index;
994 self.arg_index += 1;
995
996 const ty = self.typeOfIndex(inst);
997 const mcv: MCValue = blk: {
998 switch (self.args[arg_index]) {
999 .stack_offset => |off| {
1000 const abi_size = math.cast(u32, ty.abiSize(zcu)) orelse {
1001 return self.fail("type '{f}' too big to fit into stack frame", .{ty.fmt(pt)});
1002 };
1003 const offset = off + abi_size;
1004 break :blk .{ .stack_offset = offset };
1005 },
1006 else => |mcv| break :blk mcv,
1007 }
1008 };
1009
1010 const func_zir = zcu.funcInfo(self.func_index).zir_body_inst.resolveFull(&zcu.intern_pool).?;
1011 const file = zcu.fileByIndex(func_zir.file);
1012 if (!file.mod.?.strip) {
1013 const arg = self.air.instructions.items(.data)[@intFromEnum(inst)].arg;
1014 const zir = &file.zir.?;
1015 const name = zir.nullTerminatedString(zir.getParamName(zir.getParamBody(func_zir.inst)[arg.zir_param_index]).?);
1016
1017 self.genArgDbgInfo(name, ty, mcv) catch |err|
1018 return self.fail("failed to generate debug info for parameter: {s}", .{@errorName(err)});
1019 }
1020
1021 if (self.liveness.isUnused(inst))
1022 return self.finishAirBookkeeping();
1023
1024 switch (mcv) {
1025 .register => |reg| {
1026 self.register_manager.getRegAssumeFree(reg, inst);
1027 },
1028 else => {},
1029 }
1030
1031 return self.finishAir(inst, mcv, .{ .none, .none, .none });
1032}
1033
1034fn airAtomicLoad(self: *Self, inst: Air.Inst.Index) !void {
1035 _ = self.air.instructions.items(.data)[@intFromEnum(inst)].atomic_load;
1036
1037 return self.fail("TODO implement airAtomicLoad for {}", .{
1038 self.target.cpu.arch,
1039 });
1040}
1041
1042fn airAtomicRmw(self: *Self, inst: Air.Inst.Index) !void {
1043 _ = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
1044
1045 return self.fail("TODO implement airAtomicRmw for {}", .{
1046 self.target.cpu.arch,
1047 });
1048}
1049
1050fn airBinOp(self: *Self, inst: Air.Inst.Index, tag: Air.Inst.Tag) !void {
1051 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
1052 const lhs = try self.resolveInst(bin_op.lhs);
1053 const rhs = try self.resolveInst(bin_op.rhs);
1054 const lhs_ty = self.typeOf(bin_op.lhs);
1055 const rhs_ty = self.typeOf(bin_op.rhs);
1056 const result: MCValue = if (self.liveness.isUnused(inst))
1057 .dead
1058 else
1059 try self.binOp(tag, lhs, rhs, lhs_ty, rhs_ty, BinOpMetadata{
1060 .lhs = bin_op.lhs,
1061 .rhs = bin_op.rhs,
1062 .inst = inst,
1063 });
1064 return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
1065}
1066
1067fn airPtrArithmetic(self: *Self, inst: Air.Inst.Index, tag: Air.Inst.Tag) !void {
1068 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
1069 const bin_op = self.air.extraData(Air.Bin, ty_pl.payload).data;
1070 const lhs = try self.resolveInst(bin_op.lhs);
1071 const rhs = try self.resolveInst(bin_op.rhs);
1072 const lhs_ty = self.typeOf(bin_op.lhs);
1073 const rhs_ty = self.typeOf(bin_op.rhs);
1074 const result: MCValue = if (self.liveness.isUnused(inst))
1075 .dead
1076 else
1077 try self.binOp(tag, lhs, rhs, lhs_ty, rhs_ty, BinOpMetadata{
1078 .lhs = bin_op.lhs,
1079 .rhs = bin_op.rhs,
1080 .inst = inst,
1081 });
1082 return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
1083}
1084
1085fn airBitCast(self: *Self, inst: Air.Inst.Index) !void {
1086 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
1087 const result = if (self.liveness.isUnused(inst)) .dead else result: {
1088 const operand = try self.resolveInst(ty_op.operand);
1089 if (self.reuseOperand(inst, ty_op.operand, 0, operand)) break :result operand;
1090
1091 const operand_lock = switch (operand) {
1092 .register => |reg| self.register_manager.lockReg(reg),
1093 .register_with_overflow => |rwo| self.register_manager.lockReg(rwo.reg),
1094 else => null,
1095 };
1096 defer if (operand_lock) |lock| self.register_manager.unlockReg(lock);
1097
1098 const dest = try self.allocRegOrMem(inst, true);
1099 try self.setRegOrMem(self.typeOfIndex(inst), dest, operand);
1100 break :result dest;
1101 };
1102 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
1103}
1104
1105fn airBitReverse(self: *Self, inst: Air.Inst.Index) !void {
1106 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
1107 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airBitReverse for {}", .{self.target.cpu.arch});
1108 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
1109}
1110
1111fn airBlock(self: *Self, inst: Air.Inst.Index) !void {
1112 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
1113 const extra = self.air.extraData(Air.Block, ty_pl.payload);
1114 try self.lowerBlock(inst, @ptrCast(self.air.extra.items[extra.end..][0..extra.data.body_len]));
1115}
1116
1117fn lowerBlock(self: *Self, inst: Air.Inst.Index, body: []const Air.Inst.Index) !void {
1118 try self.blocks.putNoClobber(self.gpa, inst, .{
1119 // A block is a setup to be able to jump to the end.
1120 .relocs = .{},
1121 // It also acts as a receptacle for break operands.
1122 // Here we use `MCValue.none` to represent a null value so that the first
1123 // break instruction will choose a MCValue for the block result and overwrite
1124 // this field. Following break instructions will use that MCValue to put their
1125 // block results.
1126 .mcv = MCValue{ .none = {} },
1127 });
1128 defer self.blocks.getPtr(inst).?.relocs.deinit(self.gpa);
1129
1130 // TODO emit debug info lexical block
1131 try self.genBody(body);
1132
1133 // relocations for `bpcc` instructions
1134 const relocs = &self.blocks.getPtr(inst).?.relocs;
1135 if (relocs.items.len > 0 and relocs.items[relocs.items.len - 1] == self.mir_instructions.len - 1) {
1136 // If the last Mir instruction is the last relocation (which
1137 // would just jump two instruction further), it can be safely
1138 // removed
1139 const index = relocs.pop().?;
1140
1141 // First, remove the delay slot, then remove
1142 // the branch instruction itself.
1143 self.mir_instructions.orderedRemove(index + 1);
1144 self.mir_instructions.orderedRemove(index);
1145 }
1146 for (relocs.items) |reloc| {
1147 try self.performReloc(reloc);
1148 }
1149
1150 const result = self.blocks.getPtr(inst).?.mcv;
1151 return self.finishAir(inst, result, .{ .none, .none, .none });
1152}
1153
1154fn airBr(self: *Self, inst: Air.Inst.Index) !void {
1155 const branch = self.air.instructions.items(.data)[@intFromEnum(inst)].br;
1156 try self.br(branch.block_inst, branch.operand);
1157 return self.finishAir(inst, .dead, .{ branch.operand, .none, .none });
1158}
1159
1160fn airTrap(self: *Self) !void {
1161 // ta 0x05
1162 _ = try self.addInst(.{
1163 .tag = .tcc,
1164 .data = .{
1165 .trap = .{
1166 .is_imm = true,
1167 .cond = .al,
1168 .rs2_or_imm = .{ .imm = 0x05 },
1169 },
1170 },
1171 });
1172 return self.finishAirBookkeeping();
1173}
1174
1175fn airBreakpoint(self: *Self) !void {
1176 // ta 0x01
1177 _ = try self.addInst(.{
1178 .tag = .tcc,
1179 .data = .{
1180 .trap = .{
1181 .is_imm = true,
1182 .cond = .al,
1183 .rs2_or_imm = .{ .imm = 0x01 },
1184 },
1185 },
1186 });
1187 return self.finishAirBookkeeping();
1188}
1189
1190fn airByteSwap(self: *Self, inst: Air.Inst.Index) !void {
1191 const pt = self.pt;
1192 const zcu = pt.zcu;
1193 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
1194
1195 // We have hardware byteswapper in SPARCv9, don't let mainstream compilers mislead you.
1196 // That being said, the strategy to lower this is:
1197 // - If src is an immediate, comptime-swap it.
1198 // - If src is in memory then issue an LD*A with #ASI_P_[oppposite-endian]
1199 // - If src is a register then issue an ST*A with #ASI_P_[oppposite-endian]
1200 // to a stack slot, then follow with a normal load from said stack slot.
1201 // This is because on some implementations, ASI-tagged memory operations are non-piplelinable
1202 // and loads tend to have longer latency than stores, so the sequence will minimize stall.
1203 // The result will always be either another immediate or stored in a register.
1204 // TODO: Fold byteswap+store into a single ST*A and load+byteswap into a single LD*A.
1205 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
1206 const operand = try self.resolveInst(ty_op.operand);
1207 const operand_ty = self.typeOf(ty_op.operand);
1208 switch (operand_ty.zigTypeTag(zcu)) {
1209 .vector => return self.fail("TODO byteswap for vectors", .{}),
1210 .int => {
1211 const int_info = operand_ty.intInfo(zcu);
1212 if (int_info.bits == 8) break :result operand;
1213
1214 const abi_size = int_info.bits >> 3;
1215 const abi_align = operand_ty.abiAlignment(zcu);
1216 const opposite_endian_asi = switch (self.target.cpu.arch.endian()) {
1217 Endian.big => ASI.asi_primary_little,
1218 Endian.little => ASI.asi_primary,
1219 };
1220
1221 switch (operand) {
1222 .immediate => |imm| {
1223 const swapped = switch (int_info.bits) {
1224 16 => @byteSwap(@as(u16, @intCast(imm))),
1225 24 => @byteSwap(@as(u24, @intCast(imm))),
1226 32 => @byteSwap(@as(u32, @intCast(imm))),
1227 40 => @byteSwap(@as(u40, @intCast(imm))),
1228 48 => @byteSwap(@as(u48, @intCast(imm))),
1229 56 => @byteSwap(@as(u56, @intCast(imm))),
1230 64 => @byteSwap(@as(u64, @intCast(imm))),
1231 else => return self.fail("TODO synthesize SPARCv9 byteswap for other integer sizes", .{}),
1232 };
1233 break :result .{ .immediate = swapped };
1234 },
1235 .register => |reg| {
1236 if (int_info.bits > 64 or @popCount(int_info.bits) != 1)
1237 return self.fail("TODO synthesize SPARCv9 byteswap for other integer sizes", .{});
1238
1239 const off = try self.allocMem(inst, abi_size, abi_align);
1240 const off_reg = try self.copyToTmpRegister(operand_ty, .{ .immediate = realStackOffset(off) });
1241
1242 try self.genStoreASI(reg, .sp, off_reg, abi_size, opposite_endian_asi);
1243 try self.genLoad(reg, .sp, Register, off_reg, abi_size);
1244 break :result .{ .register = reg };
1245 },
1246 .memory => {
1247 if (int_info.bits > 64 or @popCount(int_info.bits) != 1)
1248 return self.fail("TODO synthesize SPARCv9 byteswap for other integer sizes", .{});
1249
1250 const addr_reg = try self.copyToTmpRegister(operand_ty, operand);
1251 const dst_reg = try self.register_manager.allocReg(null, gp);
1252
1253 try self.genLoadASI(dst_reg, addr_reg, .g0, abi_size, opposite_endian_asi);
1254 break :result .{ .register = dst_reg };
1255 },
1256 .stack_offset => |off| {
1257 if (int_info.bits > 64 or @popCount(int_info.bits) != 1)
1258 return self.fail("TODO synthesize SPARCv9 byteswap for other integer sizes", .{});
1259
1260 const off_reg = try self.copyToTmpRegister(operand_ty, .{ .immediate = realStackOffset(off) });
1261 const dst_reg = try self.register_manager.allocReg(null, gp);
1262
1263 try self.genLoadASI(dst_reg, .sp, off_reg, abi_size, opposite_endian_asi);
1264 break :result .{ .register = dst_reg };
1265 },
1266 else => unreachable,
1267 }
1268 },
1269 else => unreachable,
1270 }
1271 };
1272
1273 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
1274}
1275
1276fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier) !void {
1277 if (modifier == .always_tail) return self.fail("TODO implement tail calls for {}", .{self.target.cpu.arch});
1278
1279 const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
1280 const callee = pl_op.operand;
1281 const extra = self.air.extraData(Air.Call, pl_op.payload);
1282 const args: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra.end .. extra.end + extra.data.args_len]);
1283 const ty = self.typeOf(callee);
1284 const pt = self.pt;
1285 const zcu = pt.zcu;
1286 const ip = &zcu.intern_pool;
1287 const fn_ty = switch (ty.zigTypeTag(zcu)) {
1288 .@"fn" => ty,
1289 .pointer => ty.childType(zcu),
1290 else => unreachable,
1291 };
1292
1293 var info = try self.resolveCallingConventionValues(fn_ty, .caller);
1294 defer info.deinit(self);
1295
1296 // CCR is volatile across function calls
1297 // (SCD 2.4.1, page 3P-10)
1298 try self.spillConditionFlagsIfOccupied();
1299
1300 // Save caller-saved registers, but crucially *after* we save the
1301 // compare flags as saving compare flags may require a new
1302 // caller-saved register
1303 for (abi.caller_preserved_regs) |reg| {
1304 try self.register_manager.getReg(reg, null);
1305 }
1306
1307 for (info.args, 0..) |mc_arg, arg_i| {
1308 const arg = args[arg_i];
1309 const arg_ty = self.typeOf(arg);
1310 const arg_mcv = try self.resolveInst(arg);
1311
1312 switch (mc_arg) {
1313 .none => continue,
1314 .register => |reg| {
1315 try self.register_manager.getReg(reg, null);
1316 try self.genSetReg(arg_ty, reg, arg_mcv);
1317 },
1318 .stack_offset => {
1319 return self.fail("TODO implement calling with parameters in memory", .{});
1320 },
1321 .ptr_stack_offset => {
1322 return self.fail("TODO implement calling with MCValue.ptr_stack_offset arg", .{});
1323 },
1324 else => unreachable,
1325 }
1326 }
1327
1328 // Due to incremental compilation, how function calls are generated depends
1329 // on linking.
1330 if (try self.air.value(callee, pt)) |func_value| switch (ip.indexToKey(func_value.toIntern())) {
1331 .func => {
1332 return self.fail("TODO implement calling functions", .{});
1333 },
1334 .@"extern" => {
1335 return self.fail("TODO implement calling extern functions", .{});
1336 },
1337 else => {
1338 return self.fail("TODO implement calling bitcasted functions", .{});
1339 },
1340 } else {
1341 assert(ty.zigTypeTag(zcu) == .pointer);
1342 const mcv = try self.resolveInst(callee);
1343 try self.genSetReg(ty, .o7, mcv);
1344
1345 _ = try self.addInst(.{
1346 .tag = .jmpl,
1347 .data = .{
1348 .arithmetic_3op = .{
1349 .is_imm = false,
1350 .rd = .o7,
1351 .rs1 = .o7,
1352 .rs2_or_imm = .{ .rs2 = .g0 },
1353 },
1354 },
1355 });
1356
1357 // TODO Find a way to fill this delay slot
1358 _ = try self.addInst(.{
1359 .tag = .nop,
1360 .data = .{ .nop = {} },
1361 });
1362 }
1363
1364 const result = info.return_value;
1365
1366 if (args.len + 1 <= Air.Liveness.bpi - 1) {
1367 var buf = [1]Air.Inst.Ref{.none} ** (Air.Liveness.bpi - 1);
1368 buf[0] = callee;
1369 @memcpy(buf[1..][0..args.len], args);
1370 return self.finishAir(inst, result, buf);
1371 }
1372
1373 var bt = try self.iterateBigTomb(inst, 1 + args.len);
1374 bt.feed(callee);
1375 for (args) |arg| {
1376 bt.feed(arg);
1377 }
1378 return bt.finishAir(result);
1379}
1380
1381fn airClz(self: *Self, inst: Air.Inst.Index) !void {
1382 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
1383 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airClz for {}", .{self.target.cpu.arch});
1384 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
1385}
1386
1387fn airCmp(self: *Self, inst: Air.Inst.Index, op: math.CompareOperator) !void {
1388 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
1389 const pt = self.pt;
1390 const zcu = pt.zcu;
1391 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
1392 const lhs = try self.resolveInst(bin_op.lhs);
1393 const rhs = try self.resolveInst(bin_op.rhs);
1394 const lhs_ty = self.typeOf(bin_op.lhs);
1395
1396 const int_ty = switch (lhs_ty.zigTypeTag(zcu)) {
1397 .vector => unreachable, // Handled by cmp_vector.
1398 .@"enum" => lhs_ty.intTagType(zcu),
1399 .int => lhs_ty,
1400 .bool => Type.u1,
1401 .pointer => Type.usize,
1402 .error_set => Type.u16,
1403 .optional => blk: {
1404 const payload_ty = lhs_ty.optionalChild(zcu);
1405 if (!payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
1406 break :blk Type.u1;
1407 } else if (lhs_ty.isPtrLikeOptional(zcu)) {
1408 break :blk Type.usize;
1409 } else {
1410 return self.fail("TODO SPARCv9 cmp non-pointer optionals", .{});
1411 }
1412 },
1413 .float => return self.fail("TODO SPARCv9 cmp floats", .{}),
1414 else => unreachable,
1415 };
1416
1417 const int_info = int_ty.intInfo(zcu);
1418 if (int_info.bits <= 64) {
1419 _ = try self.binOp(.cmp_eq, lhs, rhs, int_ty, int_ty, BinOpMetadata{
1420 .lhs = bin_op.lhs,
1421 .rhs = bin_op.rhs,
1422 .inst = inst,
1423 });
1424
1425 try self.spillConditionFlagsIfOccupied();
1426 self.condition_flags_inst = inst;
1427
1428 break :result switch (int_info.signedness) {
1429 .signed => MCValue{ .condition_flags = .{
1430 .cond = .{ .icond = Instruction.ICondition.fromCompareOperatorSigned(op) },
1431 .ccr = .xcc,
1432 } },
1433 .unsigned => MCValue{ .condition_flags = .{
1434 .cond = .{ .icond = Instruction.ICondition.fromCompareOperatorUnsigned(op) },
1435 .ccr = .xcc,
1436 } },
1437 };
1438 } else {
1439 return self.fail("TODO SPARCv9 cmp for ints > 64 bits", .{});
1440 }
1441 };
1442 return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
1443}
1444
1445fn airCmpLtErrorsLen(self: *Self, inst: Air.Inst.Index) !void {
1446 const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
1447 const operand = try self.resolveInst(un_op);
1448 _ = operand;
1449 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airCmpLtErrorsLen for {}", .{self.target.cpu.arch});
1450 return self.finishAir(inst, result, .{ un_op, .none, .none });
1451}
1452
1453fn airCmpxchg(self: *Self, inst: Air.Inst.Index) !void {
1454 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
1455 const extra = self.air.extraData(Air.Block, ty_pl.payload);
1456 _ = extra;
1457
1458 return self.fail("TODO implement airCmpxchg for {}", .{
1459 self.target.cpu.arch,
1460 });
1461}
1462
1463fn airCondBr(self: *Self, inst: Air.Inst.Index) !void {
1464 const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
1465 const condition = try self.resolveInst(pl_op.operand);
1466 const extra = self.air.extraData(Air.CondBr, pl_op.payload);
1467 const then_body: []const Air.Inst.Index = @ptrCast(self.air.extra.items[extra.end..][0..extra.data.then_body_len]);
1468 const else_body: []const Air.Inst.Index = @ptrCast(self.air.extra.items[extra.end + then_body.len ..][0..extra.data.else_body_len]);
1469 const liveness_condbr = self.liveness.getCondBr(inst);
1470
1471 // Here we emit a branch to the false section.
1472 const reloc: Mir.Inst.Index = try self.condBr(condition);
1473
1474 // If the condition dies here in this condbr instruction, process
1475 // that death now instead of later as this has an effect on
1476 // whether it needs to be spilled in the branches
1477 if (self.liveness.operandDies(inst, 0)) {
1478 if (pl_op.operand.toIndex()) |op_index| {
1479 self.processDeath(op_index);
1480 }
1481 }
1482
1483 // Capture the state of register and stack allocation state so that we can revert to it.
1484 const parent_next_stack_offset = self.next_stack_offset;
1485 const parent_free_registers = self.register_manager.free_registers;
1486 var parent_stack = try self.stack.clone(self.gpa);
1487 defer parent_stack.deinit(self.gpa);
1488 const parent_registers = self.register_manager.registers;
1489 const parent_condition_flags_inst = self.condition_flags_inst;
1490
1491 try self.branch_stack.append(.{});
1492 errdefer {
1493 _ = self.branch_stack.pop().?;
1494 }
1495
1496 try self.ensureProcessDeathCapacity(liveness_condbr.then_deaths.len);
1497 for (liveness_condbr.then_deaths) |operand| {
1498 self.processDeath(operand);
1499 }
1500 try self.genBody(then_body);
1501
1502 // Revert to the previous register and stack allocation state.
1503
1504 var saved_then_branch = self.branch_stack.pop().?;
1505 defer saved_then_branch.deinit(self.gpa);
1506
1507 self.register_manager.registers = parent_registers;
1508 self.condition_flags_inst = parent_condition_flags_inst;
1509
1510 self.stack.deinit(self.gpa);
1511 self.stack = parent_stack;
1512 parent_stack = .{};
1513
1514 self.next_stack_offset = parent_next_stack_offset;
1515 self.register_manager.free_registers = parent_free_registers;
1516
1517 try self.performReloc(reloc);
1518 const else_branch = self.branch_stack.addOneAssumeCapacity();
1519 else_branch.* = .{};
1520
1521 try self.ensureProcessDeathCapacity(liveness_condbr.else_deaths.len);
1522 for (liveness_condbr.else_deaths) |operand| {
1523 self.processDeath(operand);
1524 }
1525 try self.genBody(else_body);
1526
1527 // At this point, each branch will possibly have conflicting values for where
1528 // each instruction is stored. They agree, however, on which instructions are alive/dead.
1529 // We use the first ("then") branch as canonical, and here emit
1530 // instructions into the second ("else") branch to make it conform.
1531 // We continue respect the data structure semantic guarantees of the else_branch so
1532 // that we can use all the code emitting abstractions. This is why at the bottom we
1533 // assert that parent_branch.free_registers equals the saved_then_branch.free_registers
1534 // rather than assigning it.
1535 const parent_branch = &self.branch_stack.items[self.branch_stack.items.len - 2];
1536 try parent_branch.inst_table.ensureUnusedCapacity(self.gpa, else_branch.inst_table.count());
1537
1538 const else_slice = else_branch.inst_table.entries.slice();
1539 const else_keys = else_slice.items(.key);
1540 const else_values = else_slice.items(.value);
1541 for (else_keys, 0..) |else_key, else_idx| {
1542 const else_value = else_values[else_idx];
1543 const canon_mcv = if (saved_then_branch.inst_table.fetchSwapRemove(else_key)) |then_entry| blk: {
1544 // The instruction's MCValue is overridden in both branches.
1545 log.debug("condBr put branch table (key = %{d}, value = {})", .{ else_key, then_entry.value });
1546 parent_branch.inst_table.putAssumeCapacity(else_key, then_entry.value);
1547 if (else_value == .dead) {
1548 assert(then_entry.value == .dead);
1549 continue;
1550 }
1551 break :blk then_entry.value;
1552 } else blk: {
1553 if (else_value == .dead)
1554 continue;
1555 // The instruction is only overridden in the else branch.
1556 var i: usize = self.branch_stack.items.len - 2;
1557 while (true) {
1558 i -= 1; // If this overflows, the question is: why wasn't the instruction marked dead?
1559 if (self.branch_stack.items[i].inst_table.get(else_key)) |mcv| {
1560 assert(mcv != .dead);
1561 break :blk mcv;
1562 }
1563 }
1564 };
1565 log.debug("consolidating else_entry {d} {}=>{}", .{ else_key, else_value, canon_mcv });
1566 // TODO make sure the destination stack offset / register does not already have something
1567 // going on there.
1568 try self.setRegOrMem(self.typeOfIndex(else_key), canon_mcv, else_value);
1569 // TODO track the new register / stack allocation
1570 }
1571 try parent_branch.inst_table.ensureUnusedCapacity(self.gpa, saved_then_branch.inst_table.count());
1572 const then_slice = saved_then_branch.inst_table.entries.slice();
1573 const then_keys = then_slice.items(.key);
1574 const then_values = then_slice.items(.value);
1575 for (then_keys, 0..) |then_key, then_idx| {
1576 const then_value = then_values[then_idx];
1577 // We already deleted the items from this table that matched the else_branch.
1578 // So these are all instructions that are only overridden in the then branch.
1579 parent_branch.inst_table.putAssumeCapacity(then_key, then_value);
1580 if (then_value == .dead)
1581 continue;
1582 const parent_mcv = blk: {
1583 var i: usize = self.branch_stack.items.len - 2;
1584 while (true) {
1585 i -= 1;
1586 if (self.branch_stack.items[i].inst_table.get(then_key)) |mcv| {
1587 assert(mcv != .dead);
1588 break :blk mcv;
1589 }
1590 }
1591 };
1592 log.debug("consolidating then_entry {d} {}=>{}", .{ then_key, parent_mcv, then_value });
1593 // TODO make sure the destination stack offset / register does not already have something
1594 // going on there.
1595 try self.setRegOrMem(self.typeOfIndex(then_key), parent_mcv, then_value);
1596 // TODO track the new register / stack allocation
1597 }
1598
1599 {
1600 var item = self.branch_stack.pop().?;
1601 item.deinit(self.gpa);
1602 }
1603
1604 // We already took care of pl_op.operand earlier, so we're going
1605 // to pass .none here
1606 return self.finishAir(inst, .unreach, .{ .none, .none, .none });
1607}
1608
1609fn airCtz(self: *Self, inst: Air.Inst.Index) !void {
1610 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
1611 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airCtz for {}", .{self.target.cpu.arch});
1612 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
1613}
1614
1615fn airDbgInlineBlock(self: *Self, inst: Air.Inst.Index) !void {
1616 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
1617 const extra = self.air.extraData(Air.DbgInlineBlock, ty_pl.payload);
1618 // TODO emit debug info for function change
1619 try self.lowerBlock(inst, @ptrCast(self.air.extra.items[extra.end..][0..extra.data.body_len]));
1620}
1621
1622fn airDbgStmt(self: *Self, inst: Air.Inst.Index) !void {
1623 const dbg_stmt = self.air.instructions.items(.data)[@intFromEnum(inst)].dbg_stmt;
1624
1625 _ = try self.addInst(.{
1626 .tag = .dbg_line,
1627 .data = .{
1628 .dbg_line_column = .{
1629 .line = dbg_stmt.line,
1630 .column = dbg_stmt.column,
1631 },
1632 },
1633 });
1634
1635 return self.finishAirBookkeeping();
1636}
1637
1638fn airDbgVar(self: *Self, inst: Air.Inst.Index) !void {
1639 const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
1640 const name: Air.NullTerminatedString = @enumFromInt(pl_op.payload);
1641 const operand = pl_op.operand;
1642 // TODO emit debug info for this variable
1643 _ = name;
1644 return self.finishAir(inst, .dead, .{ operand, .none, .none });
1645}
1646
1647fn airDiv(self: *Self, inst: Air.Inst.Index) !void {
1648 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
1649 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement div for {}", .{self.target.cpu.arch});
1650 return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
1651}
1652
1653fn airErrorName(self: *Self, inst: Air.Inst.Index) !void {
1654 const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
1655 const operand = try self.resolveInst(un_op);
1656 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else {
1657 _ = operand;
1658 return self.fail("TODO implement airErrorName for {}", .{self.target.cpu.arch});
1659 };
1660 return self.finishAir(inst, result, .{ un_op, .none, .none });
1661}
1662
1663fn airErrUnionPayloadPtrSet(self: *Self, inst: Air.Inst.Index) !void {
1664 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
1665 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement .errunion_payload_ptr_set for {}", .{self.target.cpu.arch});
1666 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
1667}
1668
1669fn airIntFromFloat(self: *Self, inst: Air.Inst.Index) !void {
1670 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
1671 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airIntFromFloat for {}", .{
1672 self.target.cpu.arch,
1673 });
1674 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
1675}
1676
1677fn airGetUnionTag(self: *Self, inst: Air.Inst.Index) !void {
1678 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
1679 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airGetUnionTag for {}", .{self.target.cpu.arch});
1680 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
1681}
1682
1683fn airIntCast(self: *Self, inst: Air.Inst.Index) !void {
1684 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
1685 if (self.liveness.isUnused(inst))
1686 return self.finishAir(inst, .dead, .{ ty_op.operand, .none, .none });
1687
1688 const pt = self.pt;
1689 const zcu = pt.zcu;
1690 const operand_ty = self.typeOf(ty_op.operand);
1691 const operand = try self.resolveInst(ty_op.operand);
1692 const info_a = operand_ty.intInfo(zcu);
1693 const info_b = self.typeOfIndex(inst).intInfo(zcu);
1694 if (info_a.signedness != info_b.signedness)
1695 return self.fail("TODO gen intcast sign safety in semantic analysis", .{});
1696
1697 if (info_a.bits == info_b.bits)
1698 return self.finishAir(inst, operand, .{ ty_op.operand, .none, .none });
1699
1700 return self.fail("TODO implement intCast for {}", .{self.target.cpu.arch});
1701}
1702
1703fn airFloatFromInt(self: *Self, inst: Air.Inst.Index) !void {
1704 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
1705 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airFloatFromInt for {}", .{
1706 self.target.cpu.arch,
1707 });
1708 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
1709}
1710
1711fn airIsErr(self: *Self, inst: Air.Inst.Index) !void {
1712 const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
1713 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
1714 const operand = try self.resolveInst(un_op);
1715 const ty = self.typeOf(un_op);
1716 break :result try self.isErr(ty, operand);
1717 };
1718 return self.finishAir(inst, result, .{ un_op, .none, .none });
1719}
1720
1721fn airIsNonErr(self: *Self, inst: Air.Inst.Index) !void {
1722 const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
1723 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
1724 const operand = try self.resolveInst(un_op);
1725 const ty = self.typeOf(un_op);
1726 break :result try self.isNonErr(ty, operand);
1727 };
1728 return self.finishAir(inst, result, .{ un_op, .none, .none });
1729}
1730
1731fn airIsNull(self: *Self, inst: Air.Inst.Index) !void {
1732 const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
1733 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
1734 const operand = try self.resolveInst(un_op);
1735 break :result try self.isNull(operand);
1736 };
1737 return self.finishAir(inst, result, .{ un_op, .none, .none });
1738}
1739
1740fn airIsNonNull(self: *Self, inst: Air.Inst.Index) !void {
1741 const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
1742 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
1743 const operand = try self.resolveInst(un_op);
1744 break :result try self.isNonNull(operand);
1745 };
1746 return self.finishAir(inst, result, .{ un_op, .none, .none });
1747}
1748
1749fn airLoad(self: *Self, inst: Air.Inst.Index) !void {
1750 const pt = self.pt;
1751 const zcu = pt.zcu;
1752 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
1753 const elem_ty = self.typeOfIndex(inst);
1754 const elem_size = elem_ty.abiSize(zcu);
1755 const result: MCValue = result: {
1756 if (!elem_ty.hasRuntimeBits(zcu))
1757 break :result MCValue.none;
1758
1759 const ptr = try self.resolveInst(ty_op.operand);
1760 const is_volatile = self.typeOf(ty_op.operand).isVolatilePtr(zcu);
1761 if (self.liveness.isUnused(inst) and !is_volatile)
1762 break :result MCValue.dead;
1763
1764 const dst_mcv: MCValue = blk: {
1765 if (elem_size <= 8 and self.reuseOperand(inst, ty_op.operand, 0, ptr)) {
1766 // The MCValue that holds the pointer can be re-used as the value.
1767 break :blk switch (ptr) {
1768 .register => |r| MCValue{ .register = r },
1769 else => ptr,
1770 };
1771 } else {
1772 break :blk try self.allocRegOrMem(inst, true);
1773 }
1774 };
1775 try self.load(dst_mcv, ptr, self.typeOf(ty_op.operand));
1776 break :result dst_mcv;
1777 };
1778 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
1779}
1780
1781fn airLoop(self: *Self, inst: Air.Inst.Index) !void {
1782 // A loop is a setup to be able to jump back to the beginning.
1783 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
1784 const loop = self.air.extraData(Air.Block, ty_pl.payload);
1785 const body: []const Air.Inst.Index = @ptrCast(self.air.extra.items[loop.end .. loop.end + loop.data.body_len]);
1786 const start: u32 = @intCast(self.mir_instructions.len);
1787
1788 try self.genBody(body);
1789 try self.jump(start);
1790
1791 return self.finishAirBookkeeping();
1792}
1793
1794fn airMemset(self: *Self, inst: Air.Inst.Index, safety: bool) !void {
1795 if (safety) {
1796 // TODO if the value is undef, write 0xaa bytes to dest
1797 } else {
1798 // TODO if the value is undef, don't lower this instruction
1799 }
1800 const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
1801 const extra = self.air.extraData(Air.Bin, pl_op.payload);
1802
1803 const operand = pl_op.operand;
1804 const value = extra.data.lhs;
1805 const length = extra.data.rhs;
1806 _ = operand;
1807 _ = value;
1808 _ = length;
1809
1810 return self.fail("TODO implement airMemset for {}", .{self.target.cpu.arch});
1811}
1812
1813fn airMinMax(self: *Self, inst: Air.Inst.Index) !void {
1814 const tag = self.air.instructions.items(.tag)[@intFromEnum(inst)];
1815 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
1816 const lhs = try self.resolveInst(bin_op.lhs);
1817 const rhs = try self.resolveInst(bin_op.rhs);
1818 const lhs_ty = self.typeOf(bin_op.lhs);
1819 const rhs_ty = self.typeOf(bin_op.rhs);
1820
1821 const result: MCValue = if (self.liveness.isUnused(inst))
1822 .dead
1823 else
1824 try self.minMax(tag, lhs, rhs, lhs_ty, rhs_ty);
1825
1826 return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
1827}
1828
1829fn airMod(self: *Self, inst: Air.Inst.Index) !void {
1830 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
1831 const lhs = try self.resolveInst(bin_op.lhs);
1832 const rhs = try self.resolveInst(bin_op.rhs);
1833 const lhs_ty = self.typeOf(bin_op.lhs);
1834 const rhs_ty = self.typeOf(bin_op.rhs);
1835 assert(lhs_ty.eql(rhs_ty, self.pt.zcu));
1836
1837 if (self.liveness.isUnused(inst))
1838 return self.finishAir(inst, .dead, .{ bin_op.lhs, bin_op.rhs, .none });
1839
1840 // TODO add safety check
1841
1842 // We use manual assembly emission to generate faster code
1843 // First, ensure lhs, rhs, rem, and added are in registers
1844
1845 const lhs_is_register = lhs == .register;
1846 const rhs_is_register = rhs == .register;
1847
1848 const lhs_reg = if (lhs_is_register)
1849 lhs.register
1850 else
1851 try self.register_manager.allocReg(null, gp);
1852
1853 const lhs_lock = self.register_manager.lockReg(lhs_reg);
1854 defer if (lhs_lock) |reg| self.register_manager.unlockReg(reg);
1855
1856 const rhs_reg = if (rhs_is_register)
1857 rhs.register
1858 else
1859 try self.register_manager.allocReg(null, gp);
1860 const rhs_lock = self.register_manager.lockReg(rhs_reg);
1861 defer if (rhs_lock) |reg| self.register_manager.unlockReg(reg);
1862
1863 if (!lhs_is_register) try self.genSetReg(lhs_ty, lhs_reg, lhs);
1864 if (!rhs_is_register) try self.genSetReg(rhs_ty, rhs_reg, rhs);
1865
1866 const regs = try self.register_manager.allocRegs(2, .{ null, null }, gp);
1867 const regs_locks = self.register_manager.lockRegsAssumeUnused(2, regs);
1868 defer for (regs_locks) |reg| {
1869 self.register_manager.unlockReg(reg);
1870 };
1871
1872 const add_reg = regs[0];
1873 const mod_reg = regs[1];
1874
1875 // mod_reg = @rem(lhs_reg, rhs_reg)
1876 _ = try self.addInst(.{
1877 .tag = .sdivx,
1878 .data = .{
1879 .arithmetic_3op = .{
1880 .is_imm = false,
1881 .rd = mod_reg,
1882 .rs1 = lhs_reg,
1883 .rs2_or_imm = .{ .rs2 = rhs_reg },
1884 },
1885 },
1886 });
1887
1888 _ = try self.addInst(.{
1889 .tag = .mulx,
1890 .data = .{
1891 .arithmetic_3op = .{
1892 .is_imm = false,
1893 .rd = mod_reg,
1894 .rs1 = mod_reg,
1895 .rs2_or_imm = .{ .rs2 = rhs_reg },
1896 },
1897 },
1898 });
1899
1900 _ = try self.addInst(.{
1901 .tag = .sub,
1902 .data = .{
1903 .arithmetic_3op = .{
1904 .is_imm = false,
1905 .rd = mod_reg,
1906 .rs1 = lhs_reg,
1907 .rs2_or_imm = .{ .rs2 = mod_reg },
1908 },
1909 },
1910 });
1911
1912 // add_reg = mod_reg + rhs_reg
1913 _ = try self.addInst(.{
1914 .tag = .add,
1915 .data = .{
1916 .arithmetic_3op = .{
1917 .is_imm = false,
1918 .rd = add_reg,
1919 .rs1 = mod_reg,
1920 .rs2_or_imm = .{ .rs2 = rhs_reg },
1921 },
1922 },
1923 });
1924
1925 // if (add_reg == rhs_reg) add_reg = 0
1926 _ = try self.addInst(.{
1927 .tag = .cmp,
1928 .data = .{
1929 .arithmetic_2op = .{
1930 .is_imm = false,
1931 .rs1 = add_reg,
1932 .rs2_or_imm = .{ .rs2 = rhs_reg },
1933 },
1934 },
1935 });
1936
1937 _ = try self.addInst(.{
1938 .tag = .movcc,
1939 .data = .{
1940 .conditional_move_int = .{
1941 .is_imm = true,
1942 .ccr = .xcc,
1943 .cond = .{ .icond = .eq },
1944 .rd = add_reg,
1945 .rs2_or_imm = .{ .imm = 0 },
1946 },
1947 },
1948 });
1949
1950 // if (lhs_reg < 0) mod_reg = add_reg
1951 _ = try self.addInst(.{
1952 .tag = .movr,
1953 .data = .{
1954 .conditional_move_reg = .{
1955 .is_imm = false,
1956 .cond = .lt_zero,
1957 .rd = mod_reg,
1958 .rs1 = lhs_reg,
1959 .rs2_or_imm = .{ .rs2 = add_reg },
1960 },
1961 },
1962 });
1963
1964 return self.finishAir(inst, .{ .register = mod_reg }, .{ bin_op.lhs, bin_op.rhs, .none });
1965}
1966
1967fn airMulSat(self: *Self, inst: Air.Inst.Index) !void {
1968 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
1969 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement mul_sat for {}", .{self.target.cpu.arch});
1970 return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
1971}
1972
1973fn airMulWithOverflow(self: *Self, inst: Air.Inst.Index) !void {
1974 //const tag = self.air.instructions.items(.tag)[@intFromEnum(inst)];
1975 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
1976 const extra = self.air.extraData(Air.Bin, ty_pl.payload).data;
1977 const pt = self.pt;
1978 const zcu = pt.zcu;
1979 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
1980 const lhs = try self.resolveInst(extra.lhs);
1981 const rhs = try self.resolveInst(extra.rhs);
1982 const lhs_ty = self.typeOf(extra.lhs);
1983 const rhs_ty = self.typeOf(extra.rhs);
1984
1985 switch (lhs_ty.zigTypeTag(zcu)) {
1986 .vector => return self.fail("TODO implement mul_with_overflow for vectors", .{}),
1987 .int => {
1988 assert(lhs_ty.eql(rhs_ty, zcu));
1989 const int_info = lhs_ty.intInfo(zcu);
1990 switch (int_info.bits) {
1991 1...32 => {
1992 try self.spillConditionFlagsIfOccupied();
1993
1994 const dest = try self.binOp(.mul, lhs, rhs, lhs_ty, rhs_ty, null);
1995
1996 const dest_reg = dest.register;
1997 const dest_reg_lock = self.register_manager.lockRegAssumeUnused(dest_reg);
1998 defer self.register_manager.unlockReg(dest_reg_lock);
1999
2000 const truncated_reg = try self.register_manager.allocReg(null, gp);
2001 const truncated_reg_lock = self.register_manager.lockRegAssumeUnused(truncated_reg);
2002 defer self.register_manager.unlockReg(truncated_reg_lock);
2003
2004 try self.truncRegister(
2005 dest_reg,
2006 truncated_reg,
2007 int_info.signedness,
2008 int_info.bits,
2009 );
2010
2011 _ = try self.addInst(.{
2012 .tag = .cmp,
2013 .data = .{ .arithmetic_2op = .{
2014 .is_imm = false,
2015 .rs1 = dest_reg,
2016 .rs2_or_imm = .{ .rs2 = truncated_reg },
2017 } },
2018 });
2019
2020 const cond = Instruction.ICondition.ne;
2021 const ccr = Instruction.CCR.xcc;
2022
2023 break :result MCValue{ .register_with_overflow = .{
2024 .reg = truncated_reg,
2025 .flag = .{ .cond = cond, .ccr = ccr },
2026 } };
2027 },
2028 // XXX DO NOT call __multi3 directly as it'll result in us doing six multiplications,
2029 // which is far more than strictly necessary
2030 33...64 => return self.fail("TODO copy compiler-rt's mulddi3 for a 64x64->128 multiply", .{}),
2031 else => return self.fail("TODO overflow operations on other integer sizes", .{}),
2032 }
2033 },
2034 else => unreachable,
2035 }
2036 };
2037 return self.finishAir(inst, result, .{ extra.lhs, extra.rhs, .none });
2038}
2039
2040fn airNot(self: *Self, inst: Air.Inst.Index) !void {
2041 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
2042 const pt = self.pt;
2043 const zcu = pt.zcu;
2044 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
2045 const operand = try self.resolveInst(ty_op.operand);
2046 const operand_ty = self.typeOf(ty_op.operand);
2047 switch (operand) {
2048 .dead => unreachable,
2049 .unreach => unreachable,
2050 .condition_flags => |op| {
2051 break :result MCValue{
2052 .condition_flags = .{
2053 .cond = op.cond.negate(),
2054 .ccr = op.ccr,
2055 },
2056 };
2057 },
2058 else => {
2059 switch (operand_ty.zigTypeTag(zcu)) {
2060 .bool => {
2061 const op_reg = switch (operand) {
2062 .register => |r| r,
2063 else => try self.copyToTmpRegister(operand_ty, operand),
2064 };
2065 const reg_lock = self.register_manager.lockRegAssumeUnused(op_reg);
2066 defer self.register_manager.unlockReg(reg_lock);
2067
2068 const dest_reg = blk: {
2069 if (operand == .register and self.reuseOperand(inst, ty_op.operand, 0, operand)) {
2070 break :blk op_reg;
2071 }
2072
2073 const reg = try self.register_manager.allocReg(null, gp);
2074 break :blk reg;
2075 };
2076
2077 _ = try self.addInst(.{
2078 .tag = .xor,
2079 .data = .{
2080 .arithmetic_3op = .{
2081 .is_imm = true,
2082 .rd = dest_reg,
2083 .rs1 = op_reg,
2084 .rs2_or_imm = .{ .imm = 1 },
2085 },
2086 },
2087 });
2088
2089 break :result MCValue{ .register = dest_reg };
2090 },
2091 .vector => return self.fail("TODO bitwise not for vectors", .{}),
2092 .int => {
2093 const int_info = operand_ty.intInfo(zcu);
2094 if (int_info.bits <= 64) {
2095 const op_reg = switch (operand) {
2096 .register => |r| r,
2097 else => try self.copyToTmpRegister(operand_ty, operand),
2098 };
2099 const reg_lock = self.register_manager.lockRegAssumeUnused(op_reg);
2100 defer self.register_manager.unlockReg(reg_lock);
2101
2102 const dest_reg = blk: {
2103 if (operand == .register and self.reuseOperand(inst, ty_op.operand, 0, operand)) {
2104 break :blk op_reg;
2105 }
2106
2107 const reg = try self.register_manager.allocReg(null, gp);
2108 break :blk reg;
2109 };
2110
2111 _ = try self.addInst(.{
2112 .tag = .not,
2113 .data = .{
2114 .arithmetic_2op = .{
2115 .is_imm = false,
2116 .rs1 = dest_reg,
2117 .rs2_or_imm = .{ .rs2 = op_reg },
2118 },
2119 },
2120 });
2121
2122 try self.truncRegister(dest_reg, dest_reg, int_info.signedness, int_info.bits);
2123
2124 break :result MCValue{ .register = dest_reg };
2125 } else {
2126 return self.fail("TODO sparc64 not on integers > u64/i64", .{});
2127 }
2128 },
2129 else => unreachable,
2130 }
2131 },
2132 }
2133 };
2134 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
2135}
2136
2137fn airOptionalPayload(self: *Self, inst: Air.Inst.Index) !void {
2138 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
2139 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement .optional_payload for {}", .{self.target.cpu.arch});
2140 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
2141}
2142
2143fn airOptionalPayloadPtr(self: *Self, inst: Air.Inst.Index) !void {
2144 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
2145 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement .optional_payload_ptr for {}", .{self.target.cpu.arch});
2146 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
2147}
2148
2149fn airOptionalPayloadPtrSet(self: *Self, inst: Air.Inst.Index) !void {
2150 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
2151 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement .optional_payload_ptr_set for {}", .{self.target.cpu.arch});
2152 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
2153}
2154
2155fn airPopcount(self: *Self, inst: Air.Inst.Index) !void {
2156 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
2157 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airPopcount for {}", .{self.target.cpu.arch});
2158 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
2159}
2160
2161fn airPrefetch(self: *Self, inst: Air.Inst.Index) !void {
2162 const prefetch = self.air.instructions.items(.data)[@intFromEnum(inst)].prefetch;
2163 // TODO Emit a PREFETCH/IPREFETCH as necessary, see A.7 and A.42
2164 return self.finishAir(inst, MCValue.dead, .{ prefetch.ptr, .none, .none });
2165}
2166
2167fn airPtrElemVal(self: *Self, inst: Air.Inst.Index) !void {
2168 const is_volatile = false; // TODO
2169 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
2170 const result: MCValue = if (!is_volatile and self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement ptr_elem_val for {}", .{self.target.cpu.arch});
2171 return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
2172}
2173
2174fn airPtrElemPtr(self: *Self, inst: Air.Inst.Index) !void {
2175 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
2176 const extra = self.air.extraData(Air.Bin, ty_pl.payload).data;
2177 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement ptr_elem_ptr for {}", .{self.target.cpu.arch});
2178 return self.finishAir(inst, result, .{ extra.lhs, extra.rhs, .none });
2179}
2180
2181fn airPtrSliceLenPtr(self: *Self, inst: Air.Inst.Index) !void {
2182 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
2183 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
2184 const ptr_bits = self.target.ptrBitWidth();
2185 const ptr_bytes = @divExact(ptr_bits, 8);
2186 const mcv = try self.resolveInst(ty_op.operand);
2187 switch (mcv) {
2188 .dead, .unreach, .none => unreachable,
2189 .ptr_stack_offset => |off| {
2190 break :result MCValue{ .ptr_stack_offset = off - ptr_bytes };
2191 },
2192 else => return self.fail("TODO implement ptr_slice_len_ptr for {}", .{mcv}),
2193 }
2194 };
2195 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
2196}
2197
2198fn airPtrSlicePtrPtr(self: *Self, inst: Air.Inst.Index) !void {
2199 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
2200 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
2201 const mcv = try self.resolveInst(ty_op.operand);
2202 switch (mcv) {
2203 .dead, .unreach, .none => unreachable,
2204 .ptr_stack_offset => |off| {
2205 break :result MCValue{ .ptr_stack_offset = off };
2206 },
2207 else => return self.fail("TODO implement ptr_slice_len_ptr for {}", .{mcv}),
2208 }
2209 };
2210 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
2211}
2212
2213fn airRem(self: *Self, inst: Air.Inst.Index) !void {
2214 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
2215 const lhs = try self.resolveInst(bin_op.lhs);
2216 const rhs = try self.resolveInst(bin_op.rhs);
2217 const lhs_ty = self.typeOf(bin_op.lhs);
2218 const rhs_ty = self.typeOf(bin_op.rhs);
2219
2220 // TODO add safety check
2221
2222 // result = lhs - @divTrunc(lhs, rhs) * rhs
2223 const result: MCValue = if (self.liveness.isUnused(inst)) blk: {
2224 break :blk .dead;
2225 } else blk: {
2226 const tmp0 = try self.binOp(.div_trunc, lhs, rhs, lhs_ty, rhs_ty, null);
2227 const tmp1 = try self.binOp(.mul, tmp0, rhs, lhs_ty, rhs_ty, null);
2228 break :blk try self.binOp(.sub, lhs, tmp1, lhs_ty, rhs_ty, null);
2229 };
2230
2231 return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
2232}
2233
2234fn airRet(self: *Self, inst: Air.Inst.Index) !void {
2235 const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
2236 const operand = try self.resolveInst(un_op);
2237 try self.ret(operand);
2238 return self.finishAir(inst, .dead, .{ un_op, .none, .none });
2239}
2240
2241fn airRetLoad(self: *Self, inst: Air.Inst.Index) !void {
2242 const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
2243 const ptr = try self.resolveInst(un_op);
2244 _ = ptr;
2245 return self.fail("TODO implement airRetLoad for {}", .{self.target.cpu.arch});
2246 //return self.finishAir(inst, .dead, .{ un_op, .none, .none });
2247}
2248
2249fn airRetPtr(self: *Self, inst: Air.Inst.Index) !void {
2250 const stack_offset = try self.allocMemPtr(inst);
2251 return self.finishAir(inst, .{ .ptr_stack_offset = stack_offset }, .{ .none, .none, .none });
2252}
2253
2254fn airSetUnionTag(self: *Self, inst: Air.Inst.Index) !void {
2255 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
2256 _ = bin_op;
2257 return self.fail("TODO implement airSetUnionTag for {}", .{self.target.cpu.arch});
2258}
2259
2260fn airShlSat(self: *Self, inst: Air.Inst.Index) !void {
2261 const zcu = self.pt.zcu;
2262 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
2263 const result: MCValue = if (self.liveness.isUnused(inst))
2264 .dead
2265 else if (self.typeOf(bin_op.lhs).isVector(zcu) and !self.typeOf(bin_op.rhs).isVector(zcu))
2266 return self.fail("TODO implement vector shl_sat with scalar rhs for {}", .{self.target.cpu.arch})
2267 else
2268 return self.fail("TODO implement shl_sat for {}", .{self.target.cpu.arch});
2269 return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
2270}
2271
2272fn airShlWithOverflow(self: *Self, inst: Air.Inst.Index) !void {
2273 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
2274 const extra = self.air.extraData(Air.Bin, ty_pl.payload).data;
2275 const pt = self.pt;
2276 const zcu = pt.zcu;
2277 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
2278 const lhs = try self.resolveInst(extra.lhs);
2279 const rhs = try self.resolveInst(extra.rhs);
2280 const lhs_ty = self.typeOf(extra.lhs);
2281 const rhs_ty = self.typeOf(extra.rhs);
2282
2283 switch (lhs_ty.zigTypeTag(zcu)) {
2284 .vector => if (!rhs_ty.isVector(zcu))
2285 return self.fail("TODO implement vector shl_with_overflow with scalar rhs", .{})
2286 else
2287 return self.fail("TODO implement mul_with_overflow for vectors", .{}),
2288 .int => {
2289 const int_info = lhs_ty.intInfo(zcu);
2290 if (int_info.bits <= 64) {
2291 try self.spillConditionFlagsIfOccupied();
2292
2293 const lhs_lock: ?RegisterLock = if (lhs == .register)
2294 self.register_manager.lockRegAssumeUnused(lhs.register)
2295 else
2296 null;
2297 // TODO this currently crashes stage1
2298 // defer if (lhs_lock) |reg| self.register_manager.unlockReg(reg);
2299
2300 // Increase shift amount (i.e, rhs) by shamt_bits - int_info.bits
2301 // e.g if shifting a i48 then use sr*x (shamt_bits == 64) but increase rhs by 16
2302 // and if shifting a i24 then use sr* (shamt_bits == 32) but increase rhs by 8
2303 const new_rhs = switch (int_info.bits) {
2304 1...31 => if (rhs == .immediate) MCValue{
2305 .immediate = rhs.immediate + 32 - int_info.bits,
2306 } else try self.binOp(.add, rhs, .{ .immediate = 32 - int_info.bits }, rhs_ty, rhs_ty, null),
2307 33...63 => if (rhs == .immediate) MCValue{
2308 .immediate = rhs.immediate + 64 - int_info.bits,
2309 } else try self.binOp(.add, rhs, .{ .immediate = 64 - int_info.bits }, rhs_ty, rhs_ty, null),
2310 32, 64 => rhs,
2311 else => unreachable,
2312 };
2313
2314 const new_rhs_lock: ?RegisterLock = if (new_rhs == .register)
2315 self.register_manager.lockRegAssumeUnused(new_rhs.register)
2316 else
2317 null;
2318 // TODO this currently crashes stage1
2319 // defer if (new_rhs_lock) |reg| self.register_manager.unlockReg(reg);
2320
2321 const dest = try self.binOp(.shl, lhs, new_rhs, lhs_ty, rhs_ty, null);
2322 const dest_reg = dest.register;
2323 const dest_reg_lock = self.register_manager.lockRegAssumeUnused(dest_reg);
2324 defer self.register_manager.unlockReg(dest_reg_lock);
2325
2326 const shr = try self.binOp(.shr, dest, new_rhs, lhs_ty, rhs_ty, null);
2327
2328 _ = try self.addInst(.{
2329 .tag = .cmp,
2330 .data = .{ .arithmetic_2op = .{
2331 .is_imm = false,
2332 .rs1 = dest_reg,
2333 .rs2_or_imm = .{ .rs2 = shr.register },
2334 } },
2335 });
2336
2337 const cond = Instruction.ICondition.ne;
2338 const ccr = switch (int_info.bits) {
2339 1...32 => Instruction.CCR.icc,
2340 33...64 => Instruction.CCR.xcc,
2341 else => unreachable,
2342 };
2343
2344 // TODO Those should really be written as defers, however stage1 currently
2345 // panics when those are turned into defer statements so those are
2346 // written here at the end as ordinary statements.
2347 // Because of that, on failure, the lock on those registers wouldn't be
2348 // released.
2349 if (lhs_lock) |reg| self.register_manager.unlockReg(reg);
2350 if (new_rhs_lock) |reg| self.register_manager.unlockReg(reg);
2351
2352 break :result MCValue{ .register_with_overflow = .{
2353 .reg = dest_reg,
2354 .flag = .{ .cond = cond, .ccr = ccr },
2355 } };
2356 } else {
2357 return self.fail("TODO overflow operations on other integer sizes", .{});
2358 }
2359 },
2360 else => unreachable,
2361 }
2362 };
2363 return self.finishAir(inst, result, .{ extra.lhs, extra.rhs, .none });
2364}
2365
2366fn airSlice(self: *Self, inst: Air.Inst.Index) !void {
2367 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
2368 const bin_op = self.air.extraData(Air.Bin, ty_pl.payload).data;
2369 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
2370 const ptr = try self.resolveInst(bin_op.lhs);
2371 const ptr_ty = self.typeOf(bin_op.lhs);
2372 const len = try self.resolveInst(bin_op.rhs);
2373 const len_ty = self.typeOf(bin_op.rhs);
2374 const ptr_bytes = 8;
2375 const stack_offset = try self.allocMem(inst, ptr_bytes * 2, .@"8");
2376 try self.genSetStack(ptr_ty, stack_offset, ptr);
2377 try self.genSetStack(len_ty, stack_offset - ptr_bytes, len);
2378 break :result MCValue{ .stack_offset = stack_offset };
2379 };
2380 return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
2381}
2382
2383fn airSliceElemVal(self: *Self, inst: Air.Inst.Index) !void {
2384 const pt = self.pt;
2385 const zcu = pt.zcu;
2386 const is_volatile = false; // TODO
2387 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
2388
2389 if (!is_volatile and self.liveness.isUnused(inst)) return self.finishAir(inst, .dead, .{ bin_op.lhs, bin_op.rhs, .none });
2390 const result: MCValue = result: {
2391 const slice_mcv = try self.resolveInst(bin_op.lhs);
2392 const index_mcv = try self.resolveInst(bin_op.rhs);
2393
2394 const slice_ty = self.typeOf(bin_op.lhs);
2395 const elem_ty = slice_ty.childType(zcu);
2396 const elem_size = elem_ty.abiSize(zcu);
2397
2398 const slice_ptr_field_type = slice_ty.slicePtrFieldType(zcu);
2399
2400 const index_lock: ?RegisterLock = if (index_mcv == .register)
2401 self.register_manager.lockRegAssumeUnused(index_mcv.register)
2402 else
2403 null;
2404 defer if (index_lock) |reg| self.register_manager.unlockReg(reg);
2405
2406 const base_mcv: MCValue = switch (slice_mcv) {
2407 .stack_offset => |off| .{ .register = try self.copyToTmpRegister(slice_ptr_field_type, .{ .stack_offset = off }) },
2408 else => return self.fail("TODO slice_elem_val when slice is {}", .{slice_mcv}),
2409 };
2410 const base_lock = self.register_manager.lockRegAssumeUnused(base_mcv.register);
2411 defer self.register_manager.unlockReg(base_lock);
2412
2413 switch (elem_size) {
2414 else => {
2415 // TODO skip the ptr_add emission entirely and use native addressing modes
2416 // i.e sllx/mulx then R+R or scale immediate then R+I
2417 const dest = try self.allocRegOrMem(inst, true);
2418 const addr = try self.binOp(.ptr_add, base_mcv, index_mcv, slice_ptr_field_type, Type.usize, null);
2419 try self.load(dest, addr, slice_ptr_field_type);
2420
2421 break :result dest;
2422 },
2423 }
2424 };
2425 return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
2426}
2427
2428fn airSliceLen(self: *Self, inst: Air.Inst.Index) !void {
2429 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
2430 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
2431 const ptr_bits = self.target.ptrBitWidth();
2432 const ptr_bytes = @divExact(ptr_bits, 8);
2433 const mcv = try self.resolveInst(ty_op.operand);
2434 switch (mcv) {
2435 .dead, .unreach, .none => unreachable,
2436 .register => unreachable, // a slice doesn't fit in one register
2437 .stack_offset => |off| {
2438 break :result MCValue{ .stack_offset = off - ptr_bytes };
2439 },
2440 .memory => |addr| {
2441 break :result MCValue{ .memory = addr + ptr_bytes };
2442 },
2443 else => return self.fail("TODO implement slice_len for {}", .{mcv}),
2444 }
2445 };
2446 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
2447}
2448
2449fn airSlicePtr(self: *Self, inst: Air.Inst.Index) !void {
2450 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
2451 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
2452 const mcv = try self.resolveInst(ty_op.operand);
2453 switch (mcv) {
2454 .dead, .unreach, .none => unreachable,
2455 .register => unreachable, // a slice doesn't fit in one register
2456 .stack_offset => |off| {
2457 break :result MCValue{ .stack_offset = off };
2458 },
2459 .memory => |addr| {
2460 break :result MCValue{ .memory = addr };
2461 },
2462 else => return self.fail("TODO implement slice_len for {}", .{mcv}),
2463 }
2464 };
2465 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
2466}
2467
2468fn airSplat(self: *Self, inst: Air.Inst.Index) !void {
2469 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
2470 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airSplat for {}", .{self.target.cpu.arch});
2471 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
2472}
2473
2474fn airStore(self: *Self, inst: Air.Inst.Index, safety: bool) !void {
2475 if (safety) {
2476 // TODO if the value is undef, write 0xaa bytes to dest
2477 } else {
2478 // TODO if the value is undef, don't lower this instruction
2479 }
2480 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
2481 const ptr = try self.resolveInst(bin_op.lhs);
2482 const value = try self.resolveInst(bin_op.rhs);
2483 const ptr_ty = self.typeOf(bin_op.lhs);
2484 const value_ty = self.typeOf(bin_op.rhs);
2485
2486 try self.store(ptr, value, ptr_ty, value_ty);
2487
2488 return self.finishAir(inst, .dead, .{ bin_op.lhs, bin_op.rhs, .none });
2489}
2490
2491fn airStructFieldPtr(self: *Self, inst: Air.Inst.Index) !void {
2492 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
2493 const extra = self.air.extraData(Air.StructField, ty_pl.payload).data;
2494 const result = try self.structFieldPtr(inst, extra.struct_operand, extra.field_index);
2495 return self.finishAir(inst, result, .{ extra.struct_operand, .none, .none });
2496}
2497
2498fn airStructFieldPtrIndex(self: *Self, inst: Air.Inst.Index, index: u8) !void {
2499 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
2500 const result = try self.structFieldPtr(inst, ty_op.operand, index);
2501 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
2502}
2503
2504fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) !void {
2505 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
2506 const extra = self.air.extraData(Air.StructField, ty_pl.payload).data;
2507 const operand = extra.struct_operand;
2508 const index = extra.field_index;
2509 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
2510 const zcu = self.pt.zcu;
2511 const mcv = try self.resolveInst(operand);
2512 const struct_ty = self.typeOf(operand);
2513 const struct_field_offset: u32 = @intCast(struct_ty.structFieldOffset(index, zcu));
2514
2515 switch (mcv) {
2516 .dead, .unreach => unreachable,
2517 .stack_offset => |off| {
2518 break :result MCValue{ .stack_offset = off - struct_field_offset };
2519 },
2520 .memory => |addr| {
2521 break :result MCValue{ .memory = addr + struct_field_offset };
2522 },
2523 .register_with_overflow => |rwo| {
2524 switch (index) {
2525 0 => {
2526 // get wrapped value: return register
2527 break :result MCValue{ .register = rwo.reg };
2528 },
2529 1 => {
2530 // TODO return special MCValue condition flags
2531 // get overflow bit: set register to C flag
2532 // resp. V flag
2533 const dest_reg = try self.register_manager.allocReg(null, gp);
2534
2535 // TODO handle floating point CCRs
2536 assert(rwo.flag.ccr == .xcc or rwo.flag.ccr == .icc);
2537
2538 _ = try self.addInst(.{
2539 .tag = .mov,
2540 .data = .{
2541 .arithmetic_2op = .{
2542 .is_imm = false,
2543 .rs1 = dest_reg,
2544 .rs2_or_imm = .{ .rs2 = .g0 },
2545 },
2546 },
2547 });
2548
2549 _ = try self.addInst(.{
2550 .tag = .movcc,
2551 .data = .{
2552 .conditional_move_int = .{
2553 .ccr = rwo.flag.ccr,
2554 .cond = .{ .icond = rwo.flag.cond },
2555 .is_imm = true,
2556 .rd = dest_reg,
2557 .rs2_or_imm = .{ .imm = 1 },
2558 },
2559 },
2560 });
2561
2562 break :result MCValue{ .register = dest_reg };
2563 },
2564 else => unreachable,
2565 }
2566 },
2567 else => return self.fail("TODO implement codegen struct_field_val for {}", .{mcv}),
2568 }
2569 };
2570
2571 return self.finishAir(inst, result, .{ extra.struct_operand, .none, .none });
2572}
2573
2574fn airSubSat(self: *Self, inst: Air.Inst.Index) !void {
2575 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
2576 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement sub_sat for {}", .{self.target.cpu.arch});
2577 return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
2578}
2579
2580fn airSwitch(self: *Self, inst: Air.Inst.Index) !void {
2581 _ = inst;
2582 return self.fail("TODO implement switch for {}", .{self.target.cpu.arch});
2583}
2584
2585fn airTagName(self: *Self, inst: Air.Inst.Index) !void {
2586 const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
2587 const operand = try self.resolveInst(un_op);
2588 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else {
2589 _ = operand;
2590 return self.fail("TODO implement airTagName for {}", .{self.target.cpu.arch});
2591 };
2592 return self.finishAir(inst, result, .{ un_op, .none, .none });
2593}
2594
2595fn airTrunc(self: *Self, inst: Air.Inst.Index) !void {
2596 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
2597 const operand = try self.resolveInst(ty_op.operand);
2598 const operand_ty = self.typeOf(ty_op.operand);
2599 const dest_ty = self.typeOfIndex(inst);
2600
2601 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else blk: {
2602 break :blk try self.trunc(inst, operand, operand_ty, dest_ty);
2603 };
2604
2605 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
2606}
2607
2608fn airTry(self: *Self, inst: Air.Inst.Index) !void {
2609 const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
2610 const extra = self.air.extraData(Air.Try, pl_op.payload);
2611 const body: []const Air.Inst.Index = @ptrCast(self.air.extra.items[extra.end..][0..extra.data.body_len]);
2612 const result: MCValue = result: {
2613 const error_union_ty = self.typeOf(pl_op.operand);
2614 const error_union = try self.resolveInst(pl_op.operand);
2615 const is_err_result = try self.isErr(error_union_ty, error_union);
2616 const reloc = try self.condBr(is_err_result);
2617
2618 try self.genBody(body);
2619
2620 try self.performReloc(reloc);
2621 break :result try self.errUnionPayload(error_union, error_union_ty);
2622 };
2623 return self.finishAir(inst, result, .{ pl_op.operand, .none, .none });
2624}
2625
2626fn airUnaryMath(self: *Self, inst: Air.Inst.Index) !void {
2627 const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
2628 const result: MCValue = if (self.liveness.isUnused(inst))
2629 .dead
2630 else
2631 return self.fail("TODO implement airUnaryMath for {}", .{self.target.cpu.arch});
2632 return self.finishAir(inst, result, .{ un_op, .none, .none });
2633}
2634
2635fn airUnionInit(self: *Self, inst: Air.Inst.Index) !void {
2636 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
2637 const extra = self.air.extraData(Air.UnionInit, ty_pl.payload).data;
2638 _ = extra;
2639 return self.fail("TODO implement airUnionInit for {}", .{self.target.cpu.arch});
2640}
2641
2642fn airUnwrapErrErr(self: *Self, inst: Air.Inst.Index) !void {
2643 const pt = self.pt;
2644 const zcu = pt.zcu;
2645 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
2646 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
2647 const error_union_ty = self.typeOf(ty_op.operand);
2648 const payload_ty = error_union_ty.errorUnionPayload(zcu);
2649 const mcv = try self.resolveInst(ty_op.operand);
2650 if (!payload_ty.hasRuntimeBits(zcu)) break :result mcv;
2651
2652 return self.fail("TODO implement unwrap error union error for non-empty payloads", .{});
2653 };
2654 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
2655}
2656
2657fn airUnwrapErrPayload(self: *Self, inst: Air.Inst.Index) !void {
2658 const pt = self.pt;
2659 const zcu = pt.zcu;
2660 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
2661 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
2662 const error_union_ty = self.typeOf(ty_op.operand);
2663 const payload_ty = error_union_ty.errorUnionPayload(zcu);
2664 if (!payload_ty.hasRuntimeBits(zcu)) break :result MCValue.none;
2665
2666 return self.fail("TODO implement unwrap error union payload for non-empty payloads", .{});
2667 };
2668 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
2669}
2670
2671/// E to E!T
2672fn airWrapErrUnionErr(self: *Self, inst: Air.Inst.Index) !void {
2673 const pt = self.pt;
2674 const zcu = pt.zcu;
2675 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
2676 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
2677 const error_union_ty = ty_op.ty.toType();
2678 const payload_ty = error_union_ty.errorUnionPayload(zcu);
2679 const mcv = try self.resolveInst(ty_op.operand);
2680 if (!payload_ty.hasRuntimeBits(zcu)) break :result mcv;
2681
2682 return self.fail("TODO implement wrap errunion error for non-empty payloads", .{});
2683 };
2684 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
2685}
2686
2687/// T to E!T
2688fn airWrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) !void {
2689 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
2690 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement wrap errunion payload for {}", .{self.target.cpu.arch});
2691 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
2692}
2693
2694fn airWrapOptional(self: *Self, inst: Air.Inst.Index) !void {
2695 const pt = self.pt;
2696 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
2697 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
2698 const optional_ty = self.typeOfIndex(inst);
2699
2700 // Optional with a zero-bit payload type is just a boolean true
2701 if (optional_ty.abiSize(pt.zcu) == 1)
2702 break :result MCValue{ .immediate = 1 };
2703
2704 return self.fail("TODO implement wrap optional for {}", .{self.target.cpu.arch});
2705 };
2706 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
2707}
2708
2709// Common helper functions
2710
2711fn addInst(self: *Self, inst: Mir.Inst) error{OutOfMemory}!Mir.Inst.Index {
2712 const gpa = self.gpa;
2713 try self.mir_instructions.ensureUnusedCapacity(gpa, 1);
2714 const result_index: Mir.Inst.Index = @intCast(self.mir_instructions.len);
2715 self.mir_instructions.appendAssumeCapacity(inst);
2716 return result_index;
2717}
2718
2719fn allocMem(self: *Self, inst: Air.Inst.Index, abi_size: u32, abi_align: Alignment) !u32 {
2720 self.stack_align = self.stack_align.max(abi_align);
2721 // TODO find a free slot instead of always appending
2722 const offset: u32 = @intCast(abi_align.forward(self.next_stack_offset) + abi_size);
2723 self.next_stack_offset = offset;
2724 if (self.next_stack_offset > self.max_end_stack)
2725 self.max_end_stack = self.next_stack_offset;
2726 try self.stack.putNoClobber(self.gpa, offset, .{
2727 .inst = inst,
2728 .size = abi_size,
2729 });
2730 return offset;
2731}
2732
2733/// Use a pointer instruction as the basis for allocating stack memory.
2734fn allocMemPtr(self: *Self, inst: Air.Inst.Index) !u32 {
2735 const pt = self.pt;
2736 const zcu = pt.zcu;
2737 const elem_ty = self.typeOfIndex(inst).childType(zcu);
2738
2739 if (!elem_ty.hasRuntimeBits(zcu)) {
2740 // As this stack item will never be dereferenced at runtime,
2741 // return the stack offset 0. Stack offset 0 will be where all
2742 // zero-sized stack allocations live as non-zero-sized
2743 // allocations will always have an offset > 0.
2744 return @as(u32, 0);
2745 }
2746
2747 const abi_size = math.cast(u32, elem_ty.abiSize(zcu)) orelse {
2748 return self.fail("type '{f}' too big to fit into stack frame", .{elem_ty.fmt(pt)});
2749 };
2750 // TODO swap this for inst.ty.ptrAlign
2751 const abi_align = elem_ty.abiAlignment(zcu);
2752 return self.allocMem(inst, abi_size, abi_align);
2753}
2754
2755fn allocRegOrMem(self: *Self, inst: Air.Inst.Index, reg_ok: bool) !MCValue {
2756 const pt = self.pt;
2757 const zcu = pt.zcu;
2758 const elem_ty = self.typeOfIndex(inst);
2759 const abi_size = math.cast(u32, elem_ty.abiSize(zcu)) orelse {
2760 return self.fail("type '{f}' too big to fit into stack frame", .{elem_ty.fmt(pt)});
2761 };
2762 const abi_align = elem_ty.abiAlignment(zcu);
2763 self.stack_align = self.stack_align.max(abi_align);
2764
2765 if (reg_ok) {
2766 // Make sure the type can fit in a register before we try to allocate one.
2767 if (abi_size <= 8) {
2768 if (self.register_manager.tryAllocReg(inst, gp)) |reg| {
2769 return MCValue{ .register = reg };
2770 }
2771 }
2772 }
2773 const stack_offset = try self.allocMem(inst, abi_size, abi_align);
2774 return MCValue{ .stack_offset = stack_offset };
2775}
2776
2777const BinOpMetadata = struct {
2778 inst: Air.Inst.Index,
2779 lhs: Air.Inst.Ref,
2780 rhs: Air.Inst.Ref,
2781};
2782
2783/// For all your binary operation needs, this function will generate
2784/// the corresponding Mir instruction(s). Returns the location of the
2785/// result.
2786///
2787/// If the binary operation itself happens to be an Air instruction,
2788/// pass the corresponding index in the inst parameter. That helps
2789/// this function do stuff like reusing operands.
2790///
2791/// This function does not do any lowering to Mir itself, but instead
2792/// looks at the lhs and rhs and determines which kind of lowering
2793/// would be best suitable and then delegates the lowering to other
2794/// functions.
2795fn binOp(
2796 self: *Self,
2797 tag: Air.Inst.Tag,
2798 lhs: MCValue,
2799 rhs: MCValue,
2800 lhs_ty: Type,
2801 rhs_ty: Type,
2802 metadata: ?BinOpMetadata,
2803) InnerError!MCValue {
2804 const pt = self.pt;
2805 const zcu = pt.zcu;
2806 switch (tag) {
2807 .add,
2808 .sub,
2809 .mul,
2810 .bit_and,
2811 .bit_or,
2812 .xor,
2813 .cmp_eq,
2814 => {
2815 switch (lhs_ty.zigTypeTag(zcu)) {
2816 .float => return self.fail("TODO binary operations on floats", .{}),
2817 .vector => return self.fail("TODO binary operations on vectors", .{}),
2818 .int => {
2819 assert(lhs_ty.eql(rhs_ty, zcu));
2820 const int_info = lhs_ty.intInfo(zcu);
2821 if (int_info.bits <= 64) {
2822 // Only say yes if the operation is
2823 // commutative, i.e. we can swap both of the
2824 // operands
2825 const lhs_immediate_ok = switch (tag) {
2826 .add => lhs == .immediate and lhs.immediate <= std.math.maxInt(u12),
2827 .mul => lhs == .immediate and lhs.immediate <= std.math.maxInt(u12),
2828 .bit_and => lhs == .immediate and lhs.immediate <= std.math.maxInt(u12),
2829 .bit_or => lhs == .immediate and lhs.immediate <= std.math.maxInt(u12),
2830 .xor => lhs == .immediate and lhs.immediate <= std.math.maxInt(u12),
2831 .sub, .cmp_eq => false,
2832 else => unreachable,
2833 };
2834 const rhs_immediate_ok = switch (tag) {
2835 .add,
2836 .sub,
2837 .mul,
2838 .bit_and,
2839 .bit_or,
2840 .xor,
2841 .cmp_eq,
2842 => rhs == .immediate and rhs.immediate <= std.math.maxInt(u12),
2843 else => unreachable,
2844 };
2845
2846 const mir_tag: Mir.Inst.Tag = switch (tag) {
2847 .add => .add,
2848 .sub => .sub,
2849 .mul => .mulx,
2850 .bit_and => .@"and",
2851 .bit_or => .@"or",
2852 .xor => .xor,
2853 .cmp_eq => .cmp,
2854 else => unreachable,
2855 };
2856
2857 if (rhs_immediate_ok) {
2858 return try self.binOpImmediate(mir_tag, lhs, rhs, lhs_ty, false, metadata);
2859 } else if (lhs_immediate_ok) {
2860 // swap lhs and rhs
2861 return try self.binOpImmediate(mir_tag, rhs, lhs, rhs_ty, true, metadata);
2862 } else {
2863 // TODO convert large immediates to register before adding
2864 return try self.binOpRegister(mir_tag, lhs, rhs, lhs_ty, rhs_ty, metadata);
2865 }
2866 } else {
2867 return self.fail("TODO binary operations on int with bits > 64", .{});
2868 }
2869 },
2870 else => unreachable,
2871 }
2872 },
2873
2874 .add_wrap,
2875 .sub_wrap,
2876 .mul_wrap,
2877 => {
2878 const base_tag: Air.Inst.Tag = switch (tag) {
2879 .add_wrap => .add,
2880 .sub_wrap => .sub,
2881 .mul_wrap => .mul,
2882 else => unreachable,
2883 };
2884
2885 // Generate the base operation
2886 const result = try self.binOp(base_tag, lhs, rhs, lhs_ty, rhs_ty, metadata);
2887
2888 // Truncate if necessary
2889 switch (lhs_ty.zigTypeTag(zcu)) {
2890 .vector => return self.fail("TODO binary operations on vectors", .{}),
2891 .int => {
2892 const int_info = lhs_ty.intInfo(zcu);
2893 if (int_info.bits <= 64) {
2894 const result_reg = result.register;
2895 try self.truncRegister(result_reg, result_reg, int_info.signedness, int_info.bits);
2896 return result;
2897 } else {
2898 return self.fail("TODO binary operations on integers > u64/i64", .{});
2899 }
2900 },
2901 else => unreachable,
2902 }
2903 },
2904
2905 .div_trunc => {
2906 switch (lhs_ty.zigTypeTag(zcu)) {
2907 .vector => return self.fail("TODO binary operations on vectors", .{}),
2908 .int => {
2909 assert(lhs_ty.eql(rhs_ty, zcu));
2910 const int_info = lhs_ty.intInfo(zcu);
2911 if (int_info.bits <= 64) {
2912 const rhs_immediate_ok = switch (tag) {
2913 .div_trunc => rhs == .immediate and rhs.immediate <= std.math.maxInt(u12),
2914 else => unreachable,
2915 };
2916
2917 const mir_tag: Mir.Inst.Tag = switch (tag) {
2918 .div_trunc => switch (int_info.signedness) {
2919 .signed => Mir.Inst.Tag.sdivx,
2920 .unsigned => Mir.Inst.Tag.udivx,
2921 },
2922 else => unreachable,
2923 };
2924
2925 if (rhs_immediate_ok) {
2926 return try self.binOpImmediate(mir_tag, lhs, rhs, lhs_ty, true, metadata);
2927 } else {
2928 return try self.binOpRegister(mir_tag, lhs, rhs, lhs_ty, rhs_ty, metadata);
2929 }
2930 } else {
2931 return self.fail("TODO binary operations on int with bits > 64", .{});
2932 }
2933 },
2934 else => unreachable,
2935 }
2936 },
2937
2938 .ptr_add => {
2939 switch (lhs_ty.zigTypeTag(zcu)) {
2940 .pointer => {
2941 const ptr_ty = lhs_ty;
2942 const elem_ty = switch (ptr_ty.ptrSize(zcu)) {
2943 .one => ptr_ty.childType(zcu).childType(zcu), // ptr to array, so get array element type
2944 else => ptr_ty.childType(zcu),
2945 };
2946 const elem_size = elem_ty.abiSize(zcu);
2947
2948 if (elem_size == 1) {
2949 const base_tag: Mir.Inst.Tag = switch (tag) {
2950 .ptr_add => .add,
2951 else => unreachable,
2952 };
2953
2954 return try self.binOpRegister(base_tag, lhs, rhs, lhs_ty, rhs_ty, metadata);
2955 } else {
2956 // convert the offset into a byte offset by
2957 // multiplying it with elem_size
2958
2959 const offset = try self.binOp(.mul, rhs, .{ .immediate = elem_size }, Type.usize, Type.usize, null);
2960 const addr = try self.binOp(tag, lhs, offset, Type.manyptr_u8, Type.usize, null);
2961 return addr;
2962 }
2963 },
2964 else => unreachable,
2965 }
2966 },
2967
2968 .bool_and,
2969 .bool_or,
2970 => {
2971 switch (lhs_ty.zigTypeTag(zcu)) {
2972 .bool => {
2973 assert(lhs != .immediate); // should have been handled by Sema
2974 assert(rhs != .immediate); // should have been handled by Sema
2975
2976 const mir_tag: Mir.Inst.Tag = switch (tag) {
2977 .bool_and => .@"and",
2978 .bool_or => .@"or",
2979 else => unreachable,
2980 };
2981
2982 return try self.binOpRegister(mir_tag, lhs, rhs, lhs_ty, rhs_ty, metadata);
2983 },
2984 else => unreachable,
2985 }
2986 },
2987
2988 .shl,
2989 .shr,
2990 => {
2991 const base_tag: Air.Inst.Tag = switch (tag) {
2992 .shl => .shl_exact,
2993 .shr => .shr_exact,
2994 else => unreachable,
2995 };
2996
2997 // Generate the base operation
2998 const result = try self.binOp(base_tag, lhs, rhs, lhs_ty, rhs_ty, metadata);
2999
3000 // Truncate if necessary
3001 switch (lhs_ty.zigTypeTag(zcu)) {
3002 .vector => if (rhs_ty.isVector(zcu))
3003 return self.fail("TODO vector shift with scalar rhs", .{})
3004 else
3005 return self.fail("TODO binary operations on vectors", .{}),
3006 .int => {
3007 const int_info = lhs_ty.intInfo(zcu);
3008 if (int_info.bits <= 64) {
3009 // 32 and 64 bit operands doesn't need truncating
3010 if (int_info.bits == 32 or int_info.bits == 64) return result;
3011
3012 const result_reg = result.register;
3013 try self.truncRegister(result_reg, result_reg, int_info.signedness, int_info.bits);
3014 return result;
3015 } else {
3016 return self.fail("TODO binary operations on integers > u64/i64", .{});
3017 }
3018 },
3019 else => unreachable,
3020 }
3021 },
3022
3023 .shl_exact,
3024 .shr_exact,
3025 => {
3026 switch (lhs_ty.zigTypeTag(zcu)) {
3027 .vector => if (rhs_ty.isVector(zcu))
3028 return self.fail("TODO vector shift with scalar rhs", .{})
3029 else
3030 return self.fail("TODO binary operations on vectors", .{}),
3031 .int => {
3032 const int_info = lhs_ty.intInfo(zcu);
3033 if (int_info.bits <= 64) {
3034 const rhs_immediate_ok = rhs == .immediate;
3035
3036 const mir_tag: Mir.Inst.Tag = switch (tag) {
3037 .shl_exact => if (int_info.bits <= 32) Mir.Inst.Tag.sll else Mir.Inst.Tag.sllx,
3038 .shr_exact => switch (int_info.signedness) {
3039 .signed => if (int_info.bits <= 32) Mir.Inst.Tag.sra else Mir.Inst.Tag.srax,
3040 .unsigned => if (int_info.bits <= 32) Mir.Inst.Tag.srl else Mir.Inst.Tag.srlx,
3041 },
3042 else => unreachable,
3043 };
3044
3045 if (rhs_immediate_ok) {
3046 return try self.binOpImmediate(mir_tag, lhs, rhs, lhs_ty, false, metadata);
3047 } else {
3048 return try self.binOpRegister(mir_tag, lhs, rhs, lhs_ty, rhs_ty, metadata);
3049 }
3050 } else {
3051 return self.fail("TODO binary operations on int with bits > 64", .{});
3052 }
3053 },
3054 else => unreachable,
3055 }
3056 },
3057
3058 else => return self.fail("TODO implement {} binOp for SPARCv9", .{tag}),
3059 }
3060}
3061
3062/// Don't call this function directly. Use binOp instead.
3063///
3064/// Calling this function signals an intention to generate a Mir
3065/// instruction of the form
3066///
3067/// op dest, lhs, #rhs_imm
3068///
3069/// Set lhs_and_rhs_swapped to true iff inst.bin_op.lhs corresponds to
3070/// rhs and vice versa. This parameter is only used when metadata != null.
3071///
3072/// Asserts that generating an instruction of that form is possible.
3073fn binOpImmediate(
3074 self: *Self,
3075 mir_tag: Mir.Inst.Tag,
3076 lhs: MCValue,
3077 rhs: MCValue,
3078 lhs_ty: Type,
3079 lhs_and_rhs_swapped: bool,
3080 metadata: ?BinOpMetadata,
3081) !MCValue {
3082 const lhs_is_register = lhs == .register;
3083
3084 const lhs_lock: ?RegisterLock = if (lhs_is_register)
3085 self.register_manager.lockReg(lhs.register)
3086 else
3087 null;
3088 defer if (lhs_lock) |reg| self.register_manager.unlockReg(reg);
3089
3090 const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
3091
3092 const lhs_reg = if (lhs_is_register) lhs.register else blk: {
3093 const track_inst: ?Air.Inst.Index = if (metadata) |md| inst: {
3094 break :inst (if (lhs_and_rhs_swapped) md.rhs else md.lhs).toIndex().?;
3095 } else null;
3096
3097 const reg = try self.register_manager.allocReg(track_inst, gp);
3098
3099 if (track_inst) |inst| {
3100 const mcv: MCValue = .{ .register = reg };
3101 log.debug("binOpRegister move lhs %{d} to register: {} -> {}", .{ inst, lhs, mcv });
3102 branch.inst_table.putAssumeCapacity(inst, mcv);
3103
3104 // If we're moving a condition flag MCV to register,
3105 // mark it as free.
3106 if (lhs == .condition_flags) {
3107 assert(self.condition_flags_inst.? == inst);
3108 self.condition_flags_inst = null;
3109 }
3110 }
3111
3112 break :blk reg;
3113 };
3114 const new_lhs_lock = self.register_manager.lockReg(lhs_reg);
3115 defer if (new_lhs_lock) |reg| self.register_manager.unlockReg(reg);
3116
3117 const dest_reg = switch (mir_tag) {
3118 .cmp => undefined, // cmp has no destination register
3119 else => if (metadata) |md| blk: {
3120 if (lhs_is_register and self.reuseOperand(
3121 md.inst,
3122 if (lhs_and_rhs_swapped) md.rhs else md.lhs,
3123 if (lhs_and_rhs_swapped) 1 else 0,
3124 lhs,
3125 )) {
3126 break :blk lhs_reg;
3127 } else {
3128 break :blk try self.register_manager.allocReg(md.inst, gp);
3129 }
3130 } else blk: {
3131 break :blk try self.register_manager.allocReg(null, gp);
3132 },
3133 };
3134
3135 if (!lhs_is_register) try self.genSetReg(lhs_ty, lhs_reg, lhs);
3136
3137 const mir_data: Mir.Inst.Data = switch (mir_tag) {
3138 .add,
3139 .addcc,
3140 .@"and",
3141 .@"or",
3142 .xor,
3143 .xnor,
3144 .mulx,
3145 .sdivx,
3146 .udivx,
3147 .sub,
3148 .subcc,
3149 => .{
3150 .arithmetic_3op = .{
3151 .is_imm = true,
3152 .rd = dest_reg,
3153 .rs1 = lhs_reg,
3154 .rs2_or_imm = .{ .imm = @as(u12, @intCast(rhs.immediate)) },
3155 },
3156 },
3157 .sll,
3158 .srl,
3159 .sra,
3160 => .{
3161 .shift = .{
3162 .is_imm = true,
3163 .rd = dest_reg,
3164 .rs1 = lhs_reg,
3165 .rs2_or_imm = .{ .imm = @as(u5, @intCast(rhs.immediate)) },
3166 },
3167 },
3168 .sllx,
3169 .srlx,
3170 .srax,
3171 => .{
3172 .shift = .{
3173 .is_imm = true,
3174 .rd = dest_reg,
3175 .rs1 = lhs_reg,
3176 .rs2_or_imm = .{ .imm = @as(u6, @intCast(rhs.immediate)) },
3177 },
3178 },
3179 .cmp => .{
3180 .arithmetic_2op = .{
3181 .is_imm = true,
3182 .rs1 = lhs_reg,
3183 .rs2_or_imm = .{ .imm = @as(u12, @intCast(rhs.immediate)) },
3184 },
3185 },
3186 else => unreachable,
3187 };
3188
3189 _ = try self.addInst(.{
3190 .tag = mir_tag,
3191 .data = mir_data,
3192 });
3193
3194 return MCValue{ .register = dest_reg };
3195}
3196
3197/// Don't call this function directly. Use binOp instead.
3198///
3199/// Calling this function signals an intention to generate a Mir
3200/// instruction of the form
3201///
3202/// op dest, lhs, rhs
3203///
3204/// Asserts that generating an instruction of that form is possible.
3205fn binOpRegister(
3206 self: *Self,
3207 mir_tag: Mir.Inst.Tag,
3208 lhs: MCValue,
3209 rhs: MCValue,
3210 lhs_ty: Type,
3211 rhs_ty: Type,
3212 metadata: ?BinOpMetadata,
3213) !MCValue {
3214 const lhs_is_register = lhs == .register;
3215 const rhs_is_register = rhs == .register;
3216
3217 const lhs_lock: ?RegisterLock = if (lhs_is_register)
3218 self.register_manager.lockReg(lhs.register)
3219 else
3220 null;
3221 defer if (lhs_lock) |reg| self.register_manager.unlockReg(reg);
3222
3223 const rhs_lock: ?RegisterLock = if (rhs_is_register)
3224 self.register_manager.lockReg(rhs.register)
3225 else
3226 null;
3227 defer if (rhs_lock) |reg| self.register_manager.unlockReg(reg);
3228
3229 const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
3230
3231 const lhs_reg = if (lhs_is_register) lhs.register else blk: {
3232 const track_inst: ?Air.Inst.Index = if (metadata) |md| inst: {
3233 break :inst md.lhs.toIndex().?;
3234 } else null;
3235
3236 const reg = try self.register_manager.allocReg(track_inst, gp);
3237 if (track_inst) |inst| {
3238 const mcv: MCValue = .{ .register = reg };
3239 log.debug("binOpRegister move lhs %{d} to register: {} -> {}", .{ inst, lhs, mcv });
3240 branch.inst_table.putAssumeCapacity(inst, mcv);
3241
3242 // If we're moving a condition flag MCV to register,
3243 // mark it as free.
3244 if (lhs == .condition_flags) {
3245 assert(self.condition_flags_inst.? == inst);
3246 self.condition_flags_inst = null;
3247 }
3248 }
3249
3250 break :blk reg;
3251 };
3252 const new_lhs_lock = self.register_manager.lockReg(lhs_reg);
3253 defer if (new_lhs_lock) |reg| self.register_manager.unlockReg(reg);
3254
3255 const rhs_reg = if (rhs_is_register) rhs.register else blk: {
3256 const track_inst: ?Air.Inst.Index = if (metadata) |md| inst: {
3257 break :inst md.rhs.toIndex().?;
3258 } else null;
3259
3260 const reg = try self.register_manager.allocReg(track_inst, gp);
3261 if (track_inst) |inst| {
3262 const mcv: MCValue = .{ .register = reg };
3263 log.debug("binOpRegister move rhs %{d} to register: {} -> {}", .{ inst, rhs, mcv });
3264 branch.inst_table.putAssumeCapacity(inst, mcv);
3265
3266 // If we're moving a condition flag MCV to register,
3267 // mark it as free.
3268 if (rhs == .condition_flags) {
3269 assert(self.condition_flags_inst.? == inst);
3270 self.condition_flags_inst = null;
3271 }
3272 }
3273
3274 break :blk reg;
3275 };
3276 const new_rhs_lock = self.register_manager.lockReg(rhs_reg);
3277 defer if (new_rhs_lock) |reg| self.register_manager.unlockReg(reg);
3278
3279 const dest_reg = switch (mir_tag) {
3280 .cmp => undefined, // cmp has no destination register
3281 else => if (metadata) |md| blk: {
3282 if (lhs_is_register and self.reuseOperand(md.inst, md.lhs, 0, lhs)) {
3283 break :blk lhs_reg;
3284 } else if (rhs_is_register and self.reuseOperand(md.inst, md.rhs, 1, rhs)) {
3285 break :blk rhs_reg;
3286 } else {
3287 break :blk try self.register_manager.allocReg(md.inst, gp);
3288 }
3289 } else blk: {
3290 break :blk try self.register_manager.allocReg(null, gp);
3291 },
3292 };
3293
3294 if (!lhs_is_register) try self.genSetReg(lhs_ty, lhs_reg, lhs);
3295 if (!rhs_is_register) try self.genSetReg(rhs_ty, rhs_reg, rhs);
3296
3297 const mir_data: Mir.Inst.Data = switch (mir_tag) {
3298 .add,
3299 .addcc,
3300 .@"and",
3301 .@"or",
3302 .xor,
3303 .xnor,
3304 .mulx,
3305 .sdivx,
3306 .udivx,
3307 .sub,
3308 .subcc,
3309 => .{
3310 .arithmetic_3op = .{
3311 .is_imm = false,
3312 .rd = dest_reg,
3313 .rs1 = lhs_reg,
3314 .rs2_or_imm = .{ .rs2 = rhs_reg },
3315 },
3316 },
3317 .sll,
3318 .srl,
3319 .sra,
3320 .sllx,
3321 .srlx,
3322 .srax,
3323 => .{
3324 .shift = .{
3325 .is_imm = false,
3326 .rd = dest_reg,
3327 .rs1 = lhs_reg,
3328 .rs2_or_imm = .{ .rs2 = rhs_reg },
3329 },
3330 },
3331 .cmp => .{
3332 .arithmetic_2op = .{
3333 .is_imm = false,
3334 .rs1 = lhs_reg,
3335 .rs2_or_imm = .{ .rs2 = rhs_reg },
3336 },
3337 },
3338 else => unreachable,
3339 };
3340
3341 _ = try self.addInst(.{
3342 .tag = mir_tag,
3343 .data = mir_data,
3344 });
3345
3346 return MCValue{ .register = dest_reg };
3347}
3348
3349fn br(self: *Self, block: Air.Inst.Index, operand: Air.Inst.Ref) !void {
3350 const block_data = self.blocks.getPtr(block).?;
3351
3352 const zcu = self.pt.zcu;
3353 if (self.typeOf(operand).hasRuntimeBits(zcu)) {
3354 const operand_mcv = try self.resolveInst(operand);
3355 const block_mcv = block_data.mcv;
3356 if (block_mcv == .none) {
3357 block_data.mcv = switch (operand_mcv) {
3358 .none, .dead, .unreach => unreachable,
3359 .register, .stack_offset, .memory => operand_mcv,
3360 .immediate => blk: {
3361 const new_mcv = try self.allocRegOrMem(block, true);
3362 try self.setRegOrMem(self.typeOfIndex(block), new_mcv, operand_mcv);
3363 break :blk new_mcv;
3364 },
3365 else => return self.fail("TODO implement block_data.mcv = operand_mcv for {}", .{operand_mcv}),
3366 };
3367 } else {
3368 try self.setRegOrMem(self.typeOfIndex(block), block_mcv, operand_mcv);
3369 }
3370 }
3371 return self.brVoid(block);
3372}
3373
3374fn brVoid(self: *Self, block: Air.Inst.Index) !void {
3375 const block_data = self.blocks.getPtr(block).?;
3376
3377 // Emit a jump with a relocation. It will be patched up after the block ends.
3378 try block_data.relocs.ensureUnusedCapacity(self.gpa, 1);
3379
3380 const br_index = try self.addInst(.{
3381 .tag = .bpcc,
3382 .data = .{
3383 .branch_predict_int = .{
3384 .ccr = .xcc,
3385 .cond = .al,
3386 .inst = undefined, // Will be filled by performReloc
3387 },
3388 },
3389 });
3390
3391 // TODO Find a way to fill this delay slot
3392 _ = try self.addInst(.{
3393 .tag = .nop,
3394 .data = .{ .nop = {} },
3395 });
3396
3397 block_data.relocs.appendAssumeCapacity(br_index);
3398}
3399
3400fn condBr(self: *Self, condition: MCValue) !Mir.Inst.Index {
3401 // Here we either emit a BPcc for branching on CCR content,
3402 // or emit a BPr to branch on register content.
3403 const reloc: Mir.Inst.Index = switch (condition) {
3404 .condition_flags => |flags| try self.addInst(.{
3405 .tag = .bpcc,
3406 .data = .{
3407 .branch_predict_int = .{
3408 .ccr = flags.ccr,
3409 // Here we map to the opposite condition because the jump is to the false branch.
3410 .cond = flags.cond.icond.negate(),
3411 .inst = undefined, // Will be filled by performReloc
3412 },
3413 },
3414 }),
3415 .condition_register => |reg| try self.addInst(.{
3416 .tag = .bpr,
3417 .data = .{
3418 .branch_predict_reg = .{
3419 .rs1 = reg.reg,
3420 // Here we map to the opposite condition because the jump is to the false branch.
3421 .cond = reg.cond.negate(),
3422 .inst = undefined, // Will be filled by performReloc
3423 },
3424 },
3425 }),
3426 else => blk: {
3427 const reg = switch (condition) {
3428 .register => |r| r,
3429 else => try self.copyToTmpRegister(Type.bool, condition),
3430 };
3431
3432 break :blk try self.addInst(.{
3433 .tag = .bpr,
3434 .data = .{
3435 .branch_predict_reg = .{
3436 .cond = .eq_zero,
3437 .rs1 = reg,
3438 .inst = undefined, // populated later through performReloc
3439 },
3440 },
3441 });
3442 },
3443 };
3444
3445 // Regardless of the branch type that's emitted, we need to reserve
3446 // a space for the delay slot.
3447 // TODO Find a way to fill this delay slot
3448 _ = try self.addInst(.{
3449 .tag = .nop,
3450 .data = .{ .nop = {} },
3451 });
3452
3453 return reloc;
3454}
3455
3456/// Copies a value to a register without tracking the register. The register is not considered
3457/// allocated. A second call to `copyToTmpRegister` may return the same register.
3458/// This can have a side effect of spilling instructions to the stack to free up a register.
3459fn copyToTmpRegister(self: *Self, ty: Type, mcv: MCValue) !Register {
3460 const reg = try self.register_manager.allocReg(null, gp);
3461 try self.genSetReg(ty, reg, mcv);
3462 return reg;
3463}
3464
3465fn ensureProcessDeathCapacity(self: *Self, additional_count: usize) !void {
3466 const table = &self.branch_stack.items[self.branch_stack.items.len - 1].inst_table;
3467 try table.ensureUnusedCapacity(self.gpa, additional_count);
3468}
3469
3470/// Given an error union, returns the payload
3471fn errUnionPayload(self: *Self, error_union_mcv: MCValue, error_union_ty: Type) !MCValue {
3472 const pt = self.pt;
3473 const zcu = pt.zcu;
3474 const err_ty = error_union_ty.errorUnionSet(zcu);
3475 const payload_ty = error_union_ty.errorUnionPayload(zcu);
3476 if (err_ty.errorSetIsEmpty(zcu)) {
3477 return error_union_mcv;
3478 }
3479 if (!payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
3480 return MCValue.none;
3481 }
3482
3483 const payload_offset: u32 = @intCast(errUnionPayloadOffset(payload_ty, zcu));
3484 switch (error_union_mcv) {
3485 .register => return self.fail("TODO errUnionPayload for registers", .{}),
3486 .stack_offset => |off| {
3487 return MCValue{ .stack_offset = off - payload_offset };
3488 },
3489 .memory => |addr| {
3490 return MCValue{ .memory = addr + payload_offset };
3491 },
3492 else => unreachable, // invalid MCValue for an error union
3493 }
3494}
3495
3496fn fail(self: *Self, comptime format: []const u8, args: anytype) error{ OutOfMemory, CodegenFail } {
3497 @branchHint(.cold);
3498 const zcu = self.pt.zcu;
3499 const func = zcu.funcInfo(self.func_index);
3500 const msg = try ErrorMsg.create(zcu.gpa, self.src_loc, format, args);
3501 return zcu.codegenFailMsg(func.owner_nav, msg);
3502}
3503
3504fn failMsg(self: *Self, msg: *ErrorMsg) error{ OutOfMemory, CodegenFail } {
3505 @branchHint(.cold);
3506 const zcu = self.pt.zcu;
3507 const func = zcu.funcInfo(self.func_index);
3508 return zcu.codegenFailMsg(func.owner_nav, msg);
3509}
3510
3511/// Called when there are no operands, and the instruction is always unreferenced.
3512fn finishAirBookkeeping(self: *Self) void {
3513 if (std.debug.runtime_safety) {
3514 self.air_bookkeeping += 1;
3515 }
3516}
3517
3518fn finishAir(self: *Self, inst: Air.Inst.Index, result: MCValue, operands: [Air.Liveness.bpi - 1]Air.Inst.Ref) void {
3519 const tomb_bits = self.liveness.getTombBits(inst);
3520 for (0.., operands) |op_index, op| {
3521 if (tomb_bits & @as(Air.Liveness.Bpi, 1) << @intCast(op_index) == 0) continue;
3522 if (self.reused_operands.isSet(op_index)) continue;
3523 self.processDeath(op.toIndexAllowNone() orelse continue);
3524 }
3525 if (tomb_bits & 1 << (Air.Liveness.bpi - 1) == 0) {
3526 log.debug("%{d} => {}", .{ inst, result });
3527 const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
3528 branch.inst_table.putAssumeCapacityNoClobber(inst, result);
3529
3530 switch (result) {
3531 .register => |reg| {
3532 // In some cases (such as bitcast), an operand
3533 // may be the same MCValue as the result. If
3534 // that operand died and was a register, it
3535 // was freed by processDeath. We have to
3536 // "re-allocate" the register.
3537 if (self.register_manager.isRegFree(reg)) {
3538 self.register_manager.getRegAssumeFree(reg, inst);
3539 }
3540 },
3541 else => {},
3542 }
3543 }
3544 self.finishAirBookkeeping();
3545}
3546
3547fn genArgDbgInfo(self: Self, name: []const u8, ty: Type, mcv: MCValue) !void {
3548 // TODO: Add a pseudo-instruction or something to defer this work until Emit.
3549 // We aren't allowed to interact with linker state here.
3550 if (true) return;
3551 switch (self.debug_output) {
3552 .dwarf => |dw| switch (mcv) {
3553 .register => |reg| try dw.genLocalDebugInfo(
3554 .local_arg,
3555 name,
3556 ty,
3557 .{ .reg = reg.dwarfNum() },
3558 ),
3559 else => {},
3560 },
3561 else => {},
3562 }
3563}
3564
3565// TODO replace this to call to extern memcpy
3566fn genInlineMemcpy(
3567 self: *Self,
3568 src: Register,
3569 dst: Register,
3570 len: Register,
3571 tmp: Register,
3572) !void {
3573 // Here we assume that len > 0.
3574 // Also we do the copy from end -> start address to save a register.
3575
3576 // sub len, 1, len
3577 _ = try self.addInst(.{
3578 .tag = .sub,
3579 .data = .{ .arithmetic_3op = .{
3580 .is_imm = true,
3581 .rs1 = len,
3582 .rs2_or_imm = .{ .imm = 1 },
3583 .rd = len,
3584 } },
3585 });
3586
3587 // loop:
3588 // ldub [src + len], tmp
3589 _ = try self.addInst(.{
3590 .tag = .ldub,
3591 .data = .{ .arithmetic_3op = .{
3592 .is_imm = false,
3593 .rs1 = src,
3594 .rs2_or_imm = .{ .rs2 = len },
3595 .rd = tmp,
3596 } },
3597 });
3598
3599 // stb tmp, [dst + len]
3600 _ = try self.addInst(.{
3601 .tag = .stb,
3602 .data = .{ .arithmetic_3op = .{
3603 .is_imm = false,
3604 .rs1 = dst,
3605 .rs2_or_imm = .{ .rs2 = len },
3606 .rd = tmp,
3607 } },
3608 });
3609
3610 // brnz len, loop
3611 _ = try self.addInst(.{
3612 .tag = .bpr,
3613 .data = .{ .branch_predict_reg = .{
3614 .cond = .ne_zero,
3615 .rs1 = len,
3616 .inst = @as(u32, @intCast(self.mir_instructions.len - 2)),
3617 } },
3618 });
3619
3620 // Delay slot:
3621 // sub len, 1, len
3622 _ = try self.addInst(.{
3623 .tag = .sub,
3624 .data = .{ .arithmetic_3op = .{
3625 .is_imm = true,
3626 .rs1 = len,
3627 .rs2_or_imm = .{ .imm = 1 },
3628 .rd = len,
3629 } },
3630 });
3631
3632 // end:
3633}
3634
3635fn genLoad(self: *Self, value_reg: Register, addr_reg: Register, comptime off_type: type, off: off_type, abi_size: u64) !void {
3636 assert(off_type == Register or off_type == i13);
3637
3638 const is_imm = (off_type == i13);
3639
3640 switch (abi_size) {
3641 1, 2, 4, 8 => {
3642 const tag: Mir.Inst.Tag = switch (abi_size) {
3643 1 => .ldub,
3644 2 => .lduh,
3645 4 => .lduw,
3646 8 => .ldx,
3647 else => unreachable, // unexpected abi size
3648 };
3649
3650 _ = try self.addInst(.{
3651 .tag = tag,
3652 .data = .{
3653 .arithmetic_3op = .{
3654 .is_imm = is_imm,
3655 .rd = value_reg,
3656 .rs1 = addr_reg,
3657 .rs2_or_imm = if (is_imm) .{ .imm = off } else .{ .rs2 = off },
3658 },
3659 },
3660 });
3661 },
3662 3, 5, 6, 7 => return self.fail("TODO: genLoad for more abi_sizes", .{}),
3663 else => unreachable,
3664 }
3665}
3666
3667fn genLoadASI(self: *Self, value_reg: Register, addr_reg: Register, off_reg: Register, abi_size: u64, asi: ASI) !void {
3668 switch (abi_size) {
3669 1, 2, 4, 8 => {
3670 const tag: Mir.Inst.Tag = switch (abi_size) {
3671 1 => .lduba,
3672 2 => .lduha,
3673 4 => .lduwa,
3674 8 => .ldxa,
3675 else => unreachable, // unexpected abi size
3676 };
3677
3678 _ = try self.addInst(.{
3679 .tag = tag,
3680 .data = .{
3681 .mem_asi = .{
3682 .rd = value_reg,
3683 .rs1 = addr_reg,
3684 .rs2 = off_reg,
3685 .asi = asi,
3686 },
3687 },
3688 });
3689 },
3690 3, 5, 6, 7 => return self.fail("TODO: genLoad for more abi_sizes", .{}),
3691 else => unreachable,
3692 }
3693}
3694
3695fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void {
3696 const pt = self.pt;
3697 const zcu = pt.zcu;
3698 switch (mcv) {
3699 .dead => unreachable,
3700 .unreach, .none => return, // Nothing to do.
3701 .condition_flags => |op| {
3702 const condition = op.cond;
3703 const ccr = op.ccr;
3704
3705 // TODO handle floating point CCRs
3706 assert(ccr == .xcc or ccr == .icc);
3707
3708 _ = try self.addInst(.{
3709 .tag = .mov,
3710 .data = .{
3711 .arithmetic_2op = .{
3712 .is_imm = false,
3713 .rs1 = reg,
3714 .rs2_or_imm = .{ .rs2 = .g0 },
3715 },
3716 },
3717 });
3718
3719 _ = try self.addInst(.{
3720 .tag = .movcc,
3721 .data = .{
3722 .conditional_move_int = .{
3723 .ccr = ccr,
3724 .cond = condition,
3725 .is_imm = true,
3726 .rd = reg,
3727 .rs2_or_imm = .{ .imm = 1 },
3728 },
3729 },
3730 });
3731 },
3732 .condition_register => |op| {
3733 const condition = op.cond;
3734 const register = op.reg;
3735
3736 _ = try self.addInst(.{
3737 .tag = .mov,
3738 .data = .{
3739 .arithmetic_2op = .{
3740 .is_imm = false,
3741 .rs1 = reg,
3742 .rs2_or_imm = .{ .rs2 = .g0 },
3743 },
3744 },
3745 });
3746
3747 _ = try self.addInst(.{
3748 .tag = .movr,
3749 .data = .{
3750 .conditional_move_reg = .{
3751 .cond = condition,
3752 .is_imm = true,
3753 .rd = reg,
3754 .rs1 = register,
3755 .rs2_or_imm = .{ .imm = 1 },
3756 },
3757 },
3758 });
3759 },
3760 .undef => {
3761 if (!self.wantSafety())
3762 return; // The already existing value will do just fine.
3763 // Write the debug undefined value.
3764 return self.genSetReg(ty, reg, .{ .immediate = 0xaaaaaaaaaaaaaaaa });
3765 },
3766 .ptr_stack_offset => |off| {
3767 const real_offset = realStackOffset(off);
3768 const simm13 = math.cast(i13, real_offset) orelse
3769 return self.fail("TODO larger stack offsets: {}", .{real_offset});
3770
3771 _ = try self.addInst(.{
3772 .tag = .add,
3773 .data = .{
3774 .arithmetic_3op = .{
3775 .is_imm = true,
3776 .rd = reg,
3777 .rs1 = .sp,
3778 .rs2_or_imm = .{ .imm = simm13 },
3779 },
3780 },
3781 });
3782 },
3783 .immediate => |x| {
3784 if (x <= math.maxInt(u12)) {
3785 _ = try self.addInst(.{
3786 .tag = .mov,
3787 .data = .{
3788 .arithmetic_2op = .{
3789 .is_imm = true,
3790 .rs1 = reg,
3791 .rs2_or_imm = .{ .imm = @as(u12, @truncate(x)) },
3792 },
3793 },
3794 });
3795 } else if (x <= math.maxInt(u32)) {
3796 _ = try self.addInst(.{
3797 .tag = .sethi,
3798 .data = .{
3799 .sethi = .{
3800 .rd = reg,
3801 .imm = @as(u22, @truncate(x >> 10)),
3802 },
3803 },
3804 });
3805
3806 _ = try self.addInst(.{
3807 .tag = .@"or",
3808 .data = .{
3809 .arithmetic_3op = .{
3810 .is_imm = true,
3811 .rd = reg,
3812 .rs1 = reg,
3813 .rs2_or_imm = .{ .imm = @as(u10, @truncate(x)) },
3814 },
3815 },
3816 });
3817 } else if (x <= math.maxInt(u44)) {
3818 try self.genSetReg(ty, reg, .{ .immediate = @as(u32, @truncate(x >> 12)) });
3819
3820 _ = try self.addInst(.{
3821 .tag = .sllx,
3822 .data = .{
3823 .shift = .{
3824 .is_imm = true,
3825 .rd = reg,
3826 .rs1 = reg,
3827 .rs2_or_imm = .{ .imm = 12 },
3828 },
3829 },
3830 });
3831
3832 _ = try self.addInst(.{
3833 .tag = .@"or",
3834 .data = .{
3835 .arithmetic_3op = .{
3836 .is_imm = true,
3837 .rd = reg,
3838 .rs1 = reg,
3839 .rs2_or_imm = .{ .imm = @as(u12, @truncate(x)) },
3840 },
3841 },
3842 });
3843 } else {
3844 // Need to allocate a temporary register to load 64-bit immediates.
3845 const tmp_reg = try self.register_manager.allocReg(null, gp);
3846
3847 try self.genSetReg(ty, tmp_reg, .{ .immediate = @as(u32, @truncate(x)) });
3848 try self.genSetReg(ty, reg, .{ .immediate = @as(u32, @truncate(x >> 32)) });
3849
3850 _ = try self.addInst(.{
3851 .tag = .sllx,
3852 .data = .{
3853 .shift = .{
3854 .is_imm = true,
3855 .rd = reg,
3856 .rs1 = reg,
3857 .rs2_or_imm = .{ .imm = 32 },
3858 },
3859 },
3860 });
3861
3862 _ = try self.addInst(.{
3863 .tag = .@"or",
3864 .data = .{
3865 .arithmetic_3op = .{
3866 .is_imm = false,
3867 .rd = reg,
3868 .rs1 = reg,
3869 .rs2_or_imm = .{ .rs2 = tmp_reg },
3870 },
3871 },
3872 });
3873 }
3874 },
3875 .register => |src_reg| {
3876 // If the registers are the same, nothing to do.
3877 if (src_reg.id() == reg.id())
3878 return;
3879
3880 _ = try self.addInst(.{
3881 .tag = .mov,
3882 .data = .{
3883 .arithmetic_2op = .{
3884 .is_imm = false,
3885 .rs1 = reg,
3886 .rs2_or_imm = .{ .rs2 = src_reg },
3887 },
3888 },
3889 });
3890 },
3891 .register_with_overflow => unreachable,
3892 .memory => |addr| {
3893 // The value is in memory at a hard-coded address.
3894 // If the type is a pointer, it means the pointer address is at this memory location.
3895 try self.genSetReg(ty, reg, .{ .immediate = addr });
3896 try self.genLoad(reg, reg, i13, 0, ty.abiSize(zcu));
3897 },
3898 .stack_offset => |off| {
3899 const real_offset = realStackOffset(off);
3900 const simm13 = math.cast(i13, real_offset) orelse
3901 return self.fail("TODO larger stack offsets: {}", .{real_offset});
3902 try self.genLoad(reg, .sp, i13, simm13, ty.abiSize(zcu));
3903 },
3904 }
3905}
3906
3907fn genSetStack(self: *Self, ty: Type, stack_offset: u32, mcv: MCValue) InnerError!void {
3908 const pt = self.pt;
3909 const zcu = pt.zcu;
3910 const abi_size = ty.abiSize(zcu);
3911 switch (mcv) {
3912 .dead => unreachable,
3913 .unreach, .none => return, // Nothing to do.
3914 .undef => {
3915 if (!self.wantSafety())
3916 return; // The already existing value will do just fine.
3917 // TODO Upgrade this to a memset call when we have that available.
3918 switch (ty.abiSize(zcu)) {
3919 1 => return self.genSetStack(ty, stack_offset, .{ .immediate = 0xaa }),
3920 2 => return self.genSetStack(ty, stack_offset, .{ .immediate = 0xaaaa }),
3921 4 => return self.genSetStack(ty, stack_offset, .{ .immediate = 0xaaaaaaaa }),
3922 8 => return self.genSetStack(ty, stack_offset, .{ .immediate = 0xaaaaaaaaaaaaaaaa }),
3923 else => return self.fail("TODO implement memset", .{}),
3924 }
3925 },
3926 .condition_flags,
3927 .condition_register,
3928 .immediate,
3929 .ptr_stack_offset,
3930 => {
3931 const reg = try self.copyToTmpRegister(ty, mcv);
3932 return self.genSetStack(ty, stack_offset, MCValue{ .register = reg });
3933 },
3934 .register => |reg| {
3935 const real_offset = realStackOffset(stack_offset);
3936 const simm13 = math.cast(i13, real_offset) orelse
3937 return self.fail("TODO larger stack offsets: {}", .{real_offset});
3938 return self.genStore(reg, .sp, i13, simm13, abi_size);
3939 },
3940 .register_with_overflow => |rwo| {
3941 const reg_lock = self.register_manager.lockReg(rwo.reg);
3942 defer if (reg_lock) |locked_reg| self.register_manager.unlockReg(locked_reg);
3943
3944 const wrapped_ty = ty.fieldType(0, zcu);
3945 try self.genSetStack(wrapped_ty, stack_offset, .{ .register = rwo.reg });
3946
3947 const overflow_bit_ty = ty.fieldType(1, zcu);
3948 const overflow_bit_offset: u32 = @intCast(ty.structFieldOffset(1, zcu));
3949 const cond_reg = try self.register_manager.allocReg(null, gp);
3950
3951 // TODO handle floating point CCRs
3952 assert(rwo.flag.ccr == .xcc or rwo.flag.ccr == .icc);
3953
3954 _ = try self.addInst(.{
3955 .tag = .mov,
3956 .data = .{
3957 .arithmetic_2op = .{
3958 .is_imm = false,
3959 .rs1 = cond_reg,
3960 .rs2_or_imm = .{ .rs2 = .g0 },
3961 },
3962 },
3963 });
3964
3965 _ = try self.addInst(.{
3966 .tag = .movcc,
3967 .data = .{
3968 .conditional_move_int = .{
3969 .ccr = rwo.flag.ccr,
3970 .cond = .{ .icond = rwo.flag.cond },
3971 .is_imm = true,
3972 .rd = cond_reg,
3973 .rs2_or_imm = .{ .imm = 1 },
3974 },
3975 },
3976 });
3977 try self.genSetStack(overflow_bit_ty, stack_offset - overflow_bit_offset, .{
3978 .register = cond_reg,
3979 });
3980 },
3981 .memory, .stack_offset => {
3982 switch (mcv) {
3983 .stack_offset => |off| {
3984 if (stack_offset == off)
3985 return; // Copy stack variable to itself; nothing to do.
3986 },
3987 else => {},
3988 }
3989
3990 if (abi_size <= 8) {
3991 const reg = try self.copyToTmpRegister(ty, mcv);
3992 return self.genSetStack(ty, stack_offset, MCValue{ .register = reg });
3993 } else {
3994 const ptr_ty = try pt.singleMutPtrType(ty);
3995
3996 const regs = try self.register_manager.allocRegs(4, .{ null, null, null, null }, gp);
3997 const regs_locks = self.register_manager.lockRegsAssumeUnused(4, regs);
3998 defer for (regs_locks) |reg| {
3999 self.register_manager.unlockReg(reg);
4000 };
4001
4002 const src_reg = regs[0];
4003 const dst_reg = regs[1];
4004 const len_reg = regs[2];
4005 const tmp_reg = regs[3];
4006
4007 switch (mcv) {
4008 .stack_offset => |off| try self.genSetReg(ptr_ty, src_reg, .{ .ptr_stack_offset = off }),
4009 .memory => |addr| try self.genSetReg(Type.usize, src_reg, .{ .immediate = addr }),
4010 else => unreachable,
4011 }
4012
4013 try self.genSetReg(ptr_ty, dst_reg, .{ .ptr_stack_offset = stack_offset });
4014 try self.genSetReg(Type.usize, len_reg, .{ .immediate = abi_size });
4015 try self.genInlineMemcpy(src_reg, dst_reg, len_reg, tmp_reg);
4016 }
4017 },
4018 }
4019}
4020
4021fn genStore(self: *Self, value_reg: Register, addr_reg: Register, comptime off_type: type, off: off_type, abi_size: u64) !void {
4022 assert(off_type == Register or off_type == i13);
4023
4024 const is_imm = (off_type == i13);
4025
4026 switch (abi_size) {
4027 1, 2, 4, 8 => {
4028 const tag: Mir.Inst.Tag = switch (abi_size) {
4029 1 => .stb,
4030 2 => .sth,
4031 4 => .stw,
4032 8 => .stx,
4033 else => unreachable, // unexpected abi size
4034 };
4035
4036 _ = try self.addInst(.{
4037 .tag = tag,
4038 .data = .{
4039 .arithmetic_3op = .{
4040 .is_imm = is_imm,
4041 .rd = value_reg,
4042 .rs1 = addr_reg,
4043 .rs2_or_imm = if (is_imm) .{ .imm = off } else .{ .rs2 = off },
4044 },
4045 },
4046 });
4047 },
4048 3, 5, 6, 7 => return self.fail("TODO: genLoad for more abi_sizes", .{}),
4049 else => unreachable,
4050 }
4051}
4052
4053fn genStoreASI(self: *Self, value_reg: Register, addr_reg: Register, off_reg: Register, abi_size: u64, asi: ASI) !void {
4054 switch (abi_size) {
4055 1, 2, 4, 8 => {
4056 const tag: Mir.Inst.Tag = switch (abi_size) {
4057 1 => .stba,
4058 2 => .stha,
4059 4 => .stwa,
4060 8 => .stxa,
4061 else => unreachable, // unexpected abi size
4062 };
4063
4064 _ = try self.addInst(.{
4065 .tag = tag,
4066 .data = .{
4067 .mem_asi = .{
4068 .rd = value_reg,
4069 .rs1 = addr_reg,
4070 .rs2 = off_reg,
4071 .asi = asi,
4072 },
4073 },
4074 });
4075 },
4076 3, 5, 6, 7 => return self.fail("TODO: genLoad for more abi_sizes", .{}),
4077 else => unreachable,
4078 }
4079}
4080
4081fn genTypedValue(self: *Self, val: Value) InnerError!MCValue {
4082 const pt = self.pt;
4083 const mcv: MCValue = switch (try codegen.genTypedValue(
4084 self.bin_file,
4085 pt,
4086 self.src_loc,
4087 val,
4088 self.target,
4089 )) {
4090 .mcv => |mcv| switch (mcv) {
4091 .none => .none,
4092 .undef => .undef,
4093 .load_got, .load_symbol, .load_direct, .lea_symbol, .lea_direct => unreachable, // TODO
4094 .immediate => |imm| .{ .immediate = imm },
4095 .memory => |addr| .{ .memory = addr },
4096 },
4097 .fail => |msg| {
4098 self.err_msg = msg;
4099 return error.CodegenFail;
4100 },
4101 };
4102 return mcv;
4103}
4104
4105fn getResolvedInstValue(self: *Self, inst: Air.Inst.Index) MCValue {
4106 // Treat each stack item as a "layer" on top of the previous one.
4107 var i: usize = self.branch_stack.items.len;
4108 while (true) {
4109 i -= 1;
4110 if (self.branch_stack.items[i].inst_table.get(inst)) |mcv| {
4111 log.debug("getResolvedInstValue %{f} => {}", .{ inst, mcv });
4112 assert(mcv != .dead);
4113 return mcv;
4114 }
4115 }
4116}
4117
4118fn isErr(self: *Self, ty: Type, operand: MCValue) !MCValue {
4119 const pt = self.pt;
4120 const zcu = pt.zcu;
4121 const error_type = ty.errorUnionSet(zcu);
4122 const payload_type = ty.errorUnionPayload(zcu);
4123
4124 if (!error_type.hasRuntimeBits(zcu)) {
4125 return MCValue{ .immediate = 0 }; // always false
4126 } else if (!payload_type.hasRuntimeBits(zcu)) {
4127 if (error_type.abiSize(zcu) <= 8) {
4128 const reg_mcv: MCValue = switch (operand) {
4129 .register => operand,
4130 else => .{ .register = try self.copyToTmpRegister(error_type, operand) },
4131 };
4132
4133 _ = try self.addInst(.{
4134 .tag = .cmp,
4135 .data = .{ .arithmetic_2op = .{
4136 .is_imm = true,
4137 .rs1 = reg_mcv.register,
4138 .rs2_or_imm = .{ .imm = 0 },
4139 } },
4140 });
4141
4142 return MCValue{ .condition_flags = .{ .cond = .{ .icond = .gu }, .ccr = .xcc } };
4143 } else {
4144 return self.fail("TODO isErr for errors with size > 8", .{});
4145 }
4146 } else {
4147 return self.fail("TODO isErr for non-empty payloads", .{});
4148 }
4149}
4150
4151fn isNonErr(self: *Self, ty: Type, operand: MCValue) !MCValue {
4152 // Call isErr, then negate the result.
4153 const is_err_result = try self.isErr(ty, operand);
4154 switch (is_err_result) {
4155 .condition_flags => |op| {
4156 return MCValue{ .condition_flags = .{ .cond = op.cond.negate(), .ccr = op.ccr } };
4157 },
4158 .immediate => |imm| {
4159 assert(imm == 0);
4160 return MCValue{ .immediate = 1 };
4161 },
4162 else => unreachable,
4163 }
4164}
4165
4166fn isNull(self: *Self, operand: MCValue) !MCValue {
4167 _ = operand;
4168 // Here you can specialize this instruction if it makes sense to, otherwise the default
4169 // will call isNonNull and invert the result.
4170 return self.fail("TODO call isNonNull and invert the result", .{});
4171}
4172
4173fn isNonNull(self: *Self, operand: MCValue) !MCValue {
4174 // Call isNull, then negate the result.
4175 const is_null_result = try self.isNull(operand);
4176 switch (is_null_result) {
4177 .condition_flags => |op| {
4178 return MCValue{ .condition_flags = .{ .cond = op.cond.negate(), .ccr = op.ccr } };
4179 },
4180 .immediate => |imm| {
4181 assert(imm == 0);
4182 return MCValue{ .immediate = 1 };
4183 },
4184 else => unreachable,
4185 }
4186}
4187
4188fn iterateBigTomb(self: *Self, inst: Air.Inst.Index, operand_count: usize) !BigTomb {
4189 try self.ensureProcessDeathCapacity(operand_count + 1);
4190 return BigTomb{
4191 .function = self,
4192 .inst = inst,
4193 .lbt = self.liveness.iterateBigTomb(inst),
4194 };
4195}
4196
4197/// Send control flow to `inst`.
4198fn jump(self: *Self, inst: Mir.Inst.Index) !void {
4199 _ = try self.addInst(.{
4200 .tag = .bpcc,
4201 .data = .{
4202 .branch_predict_int = .{
4203 .cond = .al,
4204 .ccr = .xcc,
4205 .inst = inst,
4206 },
4207 },
4208 });
4209
4210 // TODO find out a way to fill this delay slot
4211 _ = try self.addInst(.{
4212 .tag = .nop,
4213 .data = .{ .nop = {} },
4214 });
4215}
4216
4217fn load(self: *Self, dst_mcv: MCValue, ptr: MCValue, ptr_ty: Type) InnerError!void {
4218 const pt = self.pt;
4219 const zcu = pt.zcu;
4220 const elem_ty = ptr_ty.childType(zcu);
4221 const elem_size = elem_ty.abiSize(zcu);
4222
4223 switch (ptr) {
4224 .none => unreachable,
4225 .undef => unreachable,
4226 .unreach => unreachable,
4227 .dead => unreachable,
4228 .condition_flags,
4229 .condition_register,
4230 .register_with_overflow,
4231 => unreachable, // cannot hold an address
4232 .immediate => |imm| try self.setRegOrMem(elem_ty, dst_mcv, .{ .memory = imm }),
4233 .ptr_stack_offset => |off| try self.setRegOrMem(elem_ty, dst_mcv, .{ .stack_offset = off }),
4234 .register => |addr_reg| {
4235 const addr_reg_lock = self.register_manager.lockReg(addr_reg);
4236 defer if (addr_reg_lock) |reg| self.register_manager.unlockReg(reg);
4237
4238 switch (dst_mcv) {
4239 .dead => unreachable,
4240 .undef => unreachable,
4241 .condition_flags => unreachable,
4242 .register => |dst_reg| {
4243 try self.genLoad(dst_reg, addr_reg, i13, 0, elem_size);
4244 },
4245 .stack_offset => |off| {
4246 if (elem_size <= 8) {
4247 const tmp_reg = try self.register_manager.allocReg(null, gp);
4248 const tmp_reg_lock = self.register_manager.lockRegAssumeUnused(tmp_reg);
4249 defer self.register_manager.unlockReg(tmp_reg_lock);
4250
4251 try self.load(.{ .register = tmp_reg }, ptr, ptr_ty);
4252 try self.genSetStack(elem_ty, off, MCValue{ .register = tmp_reg });
4253 } else {
4254 const regs = try self.register_manager.allocRegs(3, .{ null, null, null }, gp);
4255 const regs_locks = self.register_manager.lockRegsAssumeUnused(3, regs);
4256 defer for (regs_locks) |reg| {
4257 self.register_manager.unlockReg(reg);
4258 };
4259
4260 const src_reg = addr_reg;
4261 const dst_reg = regs[0];
4262 const len_reg = regs[1];
4263 const tmp_reg = regs[2];
4264
4265 try self.genSetReg(ptr_ty, dst_reg, .{ .ptr_stack_offset = off });
4266 try self.genSetReg(Type.usize, len_reg, .{ .immediate = elem_size });
4267 try self.genInlineMemcpy(src_reg, dst_reg, len_reg, tmp_reg);
4268 }
4269 },
4270 else => return self.fail("TODO load from register into {}", .{dst_mcv}),
4271 }
4272 },
4273 .memory,
4274 .stack_offset,
4275 => {
4276 const addr_reg = try self.copyToTmpRegister(ptr_ty, ptr);
4277 try self.load(dst_mcv, .{ .register = addr_reg }, ptr_ty);
4278 },
4279 }
4280}
4281
4282fn minMax(
4283 self: *Self,
4284 tag: Air.Inst.Tag,
4285 lhs: MCValue,
4286 rhs: MCValue,
4287 lhs_ty: Type,
4288 rhs_ty: Type,
4289) InnerError!MCValue {
4290 const pt = self.pt;
4291 const zcu = pt.zcu;
4292 assert(lhs_ty.eql(rhs_ty, zcu));
4293 switch (lhs_ty.zigTypeTag(zcu)) {
4294 .float => return self.fail("TODO min/max on floats", .{}),
4295 .vector => return self.fail("TODO min/max on vectors", .{}),
4296 .int => {
4297 const int_info = lhs_ty.intInfo(zcu);
4298 if (int_info.bits <= 64) {
4299 // TODO skip register setting when one of the operands
4300 // is a small (fits in i13) immediate.
4301 const rhs_is_register = rhs == .register;
4302 const rhs_reg = if (rhs_is_register)
4303 rhs.register
4304 else
4305 try self.register_manager.allocReg(null, gp);
4306 const rhs_lock = self.register_manager.lockReg(rhs_reg);
4307 defer if (rhs_lock) |reg| self.register_manager.unlockReg(reg);
4308 if (!rhs_is_register) try self.genSetReg(rhs_ty, rhs_reg, rhs);
4309
4310 const result_reg = try self.register_manager.allocReg(null, gp);
4311 const result_lock = self.register_manager.lockReg(result_reg);
4312 defer if (result_lock) |reg| self.register_manager.unlockReg(reg);
4313 try self.genSetReg(lhs_ty, result_reg, lhs);
4314
4315 const cond_choose_rhs: Instruction.ICondition = switch (tag) {
4316 .max => switch (int_info.signedness) {
4317 .signed => Instruction.ICondition.gt,
4318 .unsigned => Instruction.ICondition.gu,
4319 },
4320 .min => switch (int_info.signedness) {
4321 .signed => Instruction.ICondition.lt,
4322 .unsigned => Instruction.ICondition.cs,
4323 },
4324 else => unreachable,
4325 };
4326
4327 _ = try self.addInst(.{
4328 .tag = .cmp,
4329 .data = .{
4330 .arithmetic_2op = .{
4331 .is_imm = false,
4332 .rs1 = result_reg,
4333 .rs2_or_imm = .{ .rs2 = rhs_reg },
4334 },
4335 },
4336 });
4337
4338 _ = try self.addInst(.{
4339 .tag = .movcc,
4340 .data = .{
4341 .conditional_move_int = .{
4342 .is_imm = false,
4343 .ccr = .xcc,
4344 .cond = .{ .icond = cond_choose_rhs },
4345 .rd = result_reg,
4346 .rs2_or_imm = .{ .rs2 = rhs_reg },
4347 },
4348 },
4349 });
4350
4351 return MCValue{ .register = result_reg };
4352 } else {
4353 return self.fail("TODO min/max on integers > u64/i64", .{});
4354 }
4355 },
4356 else => unreachable,
4357 }
4358}
4359
4360fn parseRegName(name: []const u8) ?Register {
4361 if (@hasDecl(Register, "parseRegName")) {
4362 return Register.parseRegName(name);
4363 }
4364 return std.meta.stringToEnum(Register, name);
4365}
4366
4367fn performReloc(self: *Self, inst: Mir.Inst.Index) !void {
4368 const tag = self.mir_instructions.items(.tag)[inst];
4369 switch (tag) {
4370 .bpcc => self.mir_instructions.items(.data)[inst].branch_predict_int.inst = @intCast(self.mir_instructions.len),
4371 .bpr => self.mir_instructions.items(.data)[inst].branch_predict_reg.inst = @intCast(self.mir_instructions.len),
4372 else => unreachable,
4373 }
4374}
4375
4376/// Asserts there is already capacity to insert into top branch inst_table.
4377fn processDeath(self: *Self, inst: Air.Inst.Index) void {
4378 // When editing this function, note that the logic must synchronize with `reuseOperand`.
4379 const prev_value = self.getResolvedInstValue(inst);
4380 const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
4381 branch.inst_table.putAssumeCapacity(inst, .dead);
4382 log.debug("%{f} death: {} -> .dead", .{ inst, prev_value });
4383 switch (prev_value) {
4384 .register => |reg| {
4385 self.register_manager.freeReg(reg);
4386 },
4387 .register_with_overflow => |rwo| {
4388 self.register_manager.freeReg(rwo.reg);
4389 self.condition_flags_inst = null;
4390 },
4391 .condition_flags => {
4392 self.condition_flags_inst = null;
4393 },
4394 else => {}, // TODO process stack allocation death
4395 }
4396}
4397
4398/// Turns stack_offset MCV into a real SPARCv9 stack offset usable for asm.
4399fn realStackOffset(off: u32) u32 {
4400 return off +
4401 // SPARCv9 %sp points away from the stack by some amount.
4402 abi.stack_bias +
4403 // The first couple bytes of each stack frame is reserved
4404 // for ABI and hardware purposes.
4405 abi.stack_reserved_area;
4406 // Only after that we have the usable stack frame portion.
4407}
4408
4409/// Caller must call `CallMCValues.deinit`.
4410fn resolveCallingConventionValues(self: *Self, fn_ty: Type, role: RegisterView) !CallMCValues {
4411 const pt = self.pt;
4412 const zcu = pt.zcu;
4413 const ip = &zcu.intern_pool;
4414 const fn_info = zcu.typeToFunc(fn_ty).?;
4415 const cc = fn_info.cc;
4416 var result: CallMCValues = .{
4417 .args = try self.gpa.alloc(MCValue, fn_info.param_types.len),
4418 // These undefined values must be populated before returning from this function.
4419 .return_value = undefined,
4420 .stack_byte_count = undefined,
4421 .stack_align = undefined,
4422 };
4423 errdefer self.gpa.free(result.args);
4424
4425 const ret_ty = fn_ty.fnReturnType(zcu);
4426
4427 switch (cc) {
4428 .naked => {
4429 assert(result.args.len == 0);
4430 result.return_value = .{ .unreach = {} };
4431 result.stack_byte_count = 0;
4432 result.stack_align = .@"1";
4433 return result;
4434 },
4435 .auto, .sparc64_sysv => {
4436 // SPARC Compliance Definition 2.4.1, Chapter 3
4437 // Low-Level System Information (64-bit psABI) - Function Calling Sequence
4438
4439 var next_register: usize = 0;
4440 var next_stack_offset: u32 = 0;
4441 // TODO: this is never assigned, which is a bug, but I don't know how this code works
4442 // well enough to try and fix it. I *think* `next_register += next_stack_offset` is
4443 // supposed to be `next_stack_offset += param_size` in every case where it appears.
4444 _ = &next_stack_offset;
4445
4446 // The caller puts the argument in %o0-%o5, which becomes %i0-%i5 inside the callee.
4447 const argument_registers = switch (role) {
4448 .caller => abi.c_abi_int_param_regs_caller_view,
4449 .callee => abi.c_abi_int_param_regs_callee_view,
4450 };
4451
4452 for (fn_info.param_types.get(ip), result.args) |ty, *result_arg| {
4453 const param_size: u32 = @intCast(Type.fromInterned(ty).abiSize(zcu));
4454 if (param_size <= 8) {
4455 if (next_register < argument_registers.len) {
4456 result_arg.* = .{ .register = argument_registers[next_register] };
4457 next_register += 1;
4458 } else {
4459 result_arg.* = .{ .stack_offset = next_stack_offset };
4460 next_register += next_stack_offset;
4461 }
4462 } else if (param_size <= 16) {
4463 if (next_register < argument_registers.len - 1) {
4464 return self.fail("TODO MCValues with 2 registers", .{});
4465 } else if (next_register < argument_registers.len) {
4466 return self.fail("TODO MCValues split register + stack", .{});
4467 } else {
4468 result_arg.* = .{ .stack_offset = next_stack_offset };
4469 next_register += next_stack_offset;
4470 }
4471 } else {
4472 result_arg.* = .{ .stack_offset = next_stack_offset };
4473 next_register += next_stack_offset;
4474 }
4475 }
4476
4477 result.stack_byte_count = next_stack_offset;
4478 result.stack_align = .@"16";
4479
4480 if (ret_ty.zigTypeTag(zcu) == .noreturn) {
4481 result.return_value = .{ .unreach = {} };
4482 } else if (!ret_ty.hasRuntimeBits(zcu)) {
4483 result.return_value = .{ .none = {} };
4484 } else {
4485 const ret_ty_size: u32 = @intCast(ret_ty.abiSize(zcu));
4486 // The callee puts the return values in %i0-%i3, which becomes %o0-%o3 inside the caller.
4487 if (ret_ty_size <= 8) {
4488 result.return_value = switch (role) {
4489 .caller => .{ .register = abi.c_abi_int_return_regs_caller_view[0] },
4490 .callee => .{ .register = abi.c_abi_int_return_regs_callee_view[0] },
4491 };
4492 } else {
4493 return self.fail("TODO support more return values for sparc64", .{});
4494 }
4495 }
4496 },
4497 else => return self.fail("TODO implement function parameters for {} on sparc64", .{cc}),
4498 }
4499
4500 return result;
4501}
4502
4503fn resolveInst(self: *Self, ref: Air.Inst.Ref) InnerError!MCValue {
4504 const pt = self.pt;
4505 const ty = self.typeOf(ref);
4506
4507 // If the type has no codegen bits, no need to store it.
4508 if (!ty.hasRuntimeBitsIgnoreComptime(pt.zcu)) return .none;
4509
4510 if (ref.toIndex()) |inst| {
4511 return self.getResolvedInstValue(inst);
4512 }
4513
4514 return self.genTypedValue((try self.air.value(ref, pt)).?);
4515}
4516
4517fn ret(self: *Self, mcv: MCValue) !void {
4518 const pt = self.pt;
4519 const zcu = pt.zcu;
4520 const ret_ty = self.fn_type.fnReturnType(zcu);
4521 try self.setRegOrMem(ret_ty, self.ret_mcv, mcv);
4522
4523 // Just add space for a branch instruction, patch this later
4524 const index = try self.addInst(.{
4525 .tag = .nop,
4526 .data = .{ .nop = {} },
4527 });
4528
4529 // Reserve space for the delay slot too
4530 // TODO find out a way to fill this
4531 _ = try self.addInst(.{
4532 .tag = .nop,
4533 .data = .{ .nop = {} },
4534 });
4535 try self.exitlude_jump_relocs.append(self.gpa, index);
4536}
4537
4538fn reuseOperand(self: *Self, inst: Air.Inst.Index, operand: Air.Inst.Ref, op_index: Air.Liveness.OperandInt, mcv: MCValue) bool {
4539 if (!self.liveness.operandDies(inst, op_index))
4540 return false;
4541
4542 switch (mcv) {
4543 .register => |reg| {
4544 // If it's in the registers table, need to associate the register with the
4545 // new instruction.
4546 if (RegisterManager.indexOfRegIntoTracked(reg)) |index| {
4547 if (!self.register_manager.isRegFree(reg)) {
4548 self.register_manager.registers[index] = inst;
4549 }
4550 }
4551 log.debug("%{d} => {} (reused)", .{ inst, reg });
4552 },
4553 .stack_offset => |off| {
4554 log.debug("%{d} => stack offset {d} (reused)", .{ inst, off });
4555 },
4556 else => return false,
4557 }
4558
4559 // Prevent the operand deaths processing code from deallocating it.
4560 self.reused_operands.set(op_index);
4561
4562 // That makes us responsible for doing the rest of the stuff that processDeath would have done.
4563 const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
4564 branch.inst_table.putAssumeCapacity(operand.toIndex().?, .dead);
4565
4566 return true;
4567}
4568
4569/// Sets the value without any modifications to register allocation metadata or stack allocation metadata.
4570fn setRegOrMem(self: *Self, ty: Type, loc: MCValue, val: MCValue) !void {
4571 switch (loc) {
4572 .none => return,
4573 .register => |reg| return self.genSetReg(ty, reg, val),
4574 .stack_offset => |off| return self.genSetStack(ty, off, val),
4575 .memory => {
4576 return self.fail("TODO implement setRegOrMem for memory", .{});
4577 },
4578 else => unreachable,
4579 }
4580}
4581
4582/// Save the current instruction stored in the condition flags if
4583/// occupied
4584fn spillConditionFlagsIfOccupied(self: *Self) !void {
4585 if (self.condition_flags_inst) |inst_to_save| {
4586 const mcv = self.getResolvedInstValue(inst_to_save);
4587 const new_mcv = switch (mcv) {
4588 .condition_flags => try self.allocRegOrMem(inst_to_save, true),
4589 .register_with_overflow => try self.allocRegOrMem(inst_to_save, false),
4590 else => unreachable, // mcv doesn't occupy the compare flags
4591 };
4592
4593 try self.setRegOrMem(self.typeOfIndex(inst_to_save), new_mcv, mcv);
4594 log.debug("spilling {d} to mcv {any}", .{ inst_to_save, new_mcv });
4595
4596 const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
4597 try branch.inst_table.put(self.gpa, inst_to_save, new_mcv);
4598
4599 self.condition_flags_inst = null;
4600
4601 // TODO consolidate with register manager and spillInstruction
4602 // this call should really belong in the register manager!
4603 switch (mcv) {
4604 .register_with_overflow => |rwo| self.register_manager.freeReg(rwo.reg),
4605 else => {},
4606 }
4607 }
4608}
4609
4610pub fn spillInstruction(self: *Self, reg: Register, inst: Air.Inst.Index) !void {
4611 const stack_mcv = try self.allocRegOrMem(inst, false);
4612 log.debug("spilling {d} to stack mcv {any}", .{ inst, stack_mcv });
4613 const reg_mcv = self.getResolvedInstValue(inst);
4614 assert(reg == reg_mcv.register);
4615 const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
4616 try branch.inst_table.put(self.gpa, inst, stack_mcv);
4617 try self.genSetStack(self.typeOfIndex(inst), stack_mcv.stack_offset, reg_mcv);
4618}
4619
4620fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type) InnerError!void {
4621 const pt = self.pt;
4622 const abi_size = value_ty.abiSize(pt.zcu);
4623
4624 switch (ptr) {
4625 .none => unreachable,
4626 .undef => unreachable,
4627 .unreach => unreachable,
4628 .dead => unreachable,
4629 .condition_flags,
4630 .condition_register,
4631 .register_with_overflow,
4632 => unreachable, // cannot hold an address
4633 .immediate => |imm| {
4634 try self.setRegOrMem(value_ty, .{ .memory = imm }, value);
4635 },
4636 .ptr_stack_offset => |off| {
4637 try self.genSetStack(value_ty, off, value);
4638 },
4639 .register => |addr_reg| {
4640 const addr_reg_lock = self.register_manager.lockReg(addr_reg);
4641 defer if (addr_reg_lock) |reg| self.register_manager.unlockReg(reg);
4642
4643 switch (value) {
4644 .register => |value_reg| {
4645 try self.genStore(value_reg, addr_reg, i13, 0, abi_size);
4646 },
4647 else => {
4648 return self.fail("TODO implement copying of memory", .{});
4649 },
4650 }
4651 },
4652 .memory,
4653 .stack_offset,
4654 => {
4655 const addr_reg = try self.copyToTmpRegister(ptr_ty, ptr);
4656 try self.store(.{ .register = addr_reg }, value, ptr_ty, value_ty);
4657 },
4658 }
4659}
4660
4661fn structFieldPtr(self: *Self, inst: Air.Inst.Index, operand: Air.Inst.Ref, index: u32) !MCValue {
4662 return if (self.liveness.isUnused(inst)) .dead else result: {
4663 const pt = self.pt;
4664 const zcu = pt.zcu;
4665 const mcv = try self.resolveInst(operand);
4666 const ptr_ty = self.typeOf(operand);
4667 const struct_ty = ptr_ty.childType(zcu);
4668 const struct_field_offset: u32 = @intCast(struct_ty.structFieldOffset(index, zcu));
4669 switch (mcv) {
4670 .ptr_stack_offset => |off| {
4671 break :result MCValue{ .ptr_stack_offset = off - struct_field_offset };
4672 },
4673 else => {
4674 const offset_reg = try self.copyToTmpRegister(ptr_ty, .{
4675 .immediate = struct_field_offset,
4676 });
4677 const offset_reg_lock = self.register_manager.lockRegAssumeUnused(offset_reg);
4678 defer self.register_manager.unlockReg(offset_reg_lock);
4679
4680 const addr_reg = try self.copyToTmpRegister(ptr_ty, mcv);
4681 const addr_reg_lock = self.register_manager.lockRegAssumeUnused(addr_reg);
4682 defer self.register_manager.unlockReg(addr_reg_lock);
4683
4684 const dest = try self.binOp(
4685 .add,
4686 .{ .register = addr_reg },
4687 .{ .register = offset_reg },
4688 Type.usize,
4689 Type.usize,
4690 null,
4691 );
4692
4693 break :result dest;
4694 },
4695 }
4696 };
4697}
4698
4699fn trunc(
4700 self: *Self,
4701 maybe_inst: ?Air.Inst.Index,
4702 operand: MCValue,
4703 operand_ty: Type,
4704 dest_ty: Type,
4705) !MCValue {
4706 const pt = self.pt;
4707 const zcu = pt.zcu;
4708 const info_a = operand_ty.intInfo(zcu);
4709 const info_b = dest_ty.intInfo(zcu);
4710
4711 if (info_b.bits <= 64) {
4712 const operand_reg = switch (operand) {
4713 .register => |r| r,
4714 else => operand_reg: {
4715 if (info_a.bits <= 64) {
4716 const reg = try self.copyToTmpRegister(operand_ty, operand);
4717 break :operand_reg reg;
4718 } else {
4719 return self.fail("TODO load least significant word into register", .{});
4720 }
4721 },
4722 };
4723 const lock = self.register_manager.lockReg(operand_reg);
4724 defer if (lock) |reg| self.register_manager.unlockReg(reg);
4725
4726 const dest_reg = if (maybe_inst) |inst| blk: {
4727 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
4728
4729 if (operand == .register and self.reuseOperand(inst, ty_op.operand, 0, operand)) {
4730 break :blk operand_reg;
4731 } else {
4732 const reg = try self.register_manager.allocReg(inst, gp);
4733 break :blk reg;
4734 }
4735 } else blk: {
4736 const reg = try self.register_manager.allocReg(null, gp);
4737 break :blk reg;
4738 };
4739
4740 try self.truncRegister(operand_reg, dest_reg, info_b.signedness, info_b.bits);
4741
4742 return MCValue{ .register = dest_reg };
4743 } else {
4744 return self.fail("TODO: truncate to ints > 64 bits", .{});
4745 }
4746}
4747
4748fn truncRegister(
4749 self: *Self,
4750 operand_reg: Register,
4751 dest_reg: Register,
4752 int_signedness: std.builtin.Signedness,
4753 int_bits: u16,
4754) !void {
4755 switch (int_bits) {
4756 1...31, 33...63 => {
4757 _ = try self.addInst(.{
4758 .tag = .sllx,
4759 .data = .{
4760 .shift = .{
4761 .is_imm = true,
4762 .rd = dest_reg,
4763 .rs1 = operand_reg,
4764 .rs2_or_imm = .{ .imm = @as(u6, @intCast(64 - int_bits)) },
4765 },
4766 },
4767 });
4768 _ = try self.addInst(.{
4769 .tag = switch (int_signedness) {
4770 .signed => .srax,
4771 .unsigned => .srlx,
4772 },
4773 .data = .{
4774 .shift = .{
4775 .is_imm = true,
4776 .rd = dest_reg,
4777 .rs1 = dest_reg,
4778 .rs2_or_imm = .{ .imm = @as(u6, @intCast(int_bits)) },
4779 },
4780 },
4781 });
4782 },
4783 32 => {
4784 _ = try self.addInst(.{
4785 .tag = switch (int_signedness) {
4786 .signed => .sra,
4787 .unsigned => .srl,
4788 },
4789 .data = .{
4790 .shift = .{
4791 .is_imm = true,
4792 .rd = dest_reg,
4793 .rs1 = operand_reg,
4794 .rs2_or_imm = .{ .imm = 0 },
4795 },
4796 },
4797 });
4798 },
4799 64 => {
4800 if (dest_reg == operand_reg)
4801 return; // Copy register to itself; nothing to do.
4802 _ = try self.addInst(.{
4803 .tag = .mov,
4804 .data = .{
4805 .arithmetic_2op = .{
4806 .is_imm = false,
4807 .rs1 = dest_reg,
4808 .rs2_or_imm = .{ .rs2 = operand_reg },
4809 },
4810 },
4811 });
4812 },
4813 else => unreachable,
4814 }
4815}
4816
4817/// TODO support scope overrides. Also note this logic is duplicated with `Zcu.wantSafety`.
4818fn wantSafety(self: *Self) bool {
4819 return switch (self.bin_file.comp.root_mod.optimize_mode) {
4820 .Debug => true,
4821 .ReleaseSafe => true,
4822 .ReleaseFast => false,
4823 .ReleaseSmall => false,
4824 };
4825}
4826
4827fn typeOf(self: *Self, inst: Air.Inst.Ref) Type {
4828 return self.air.typeOf(inst, &self.pt.zcu.intern_pool);
4829}
4830
4831fn typeOfIndex(self: *Self, inst: Air.Inst.Index) Type {
4832 return self.air.typeOfIndex(inst, &self.pt.zcu.intern_pool);
4833}