authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-02 14:50:41-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-02 14:50:41-07:00
loga13f0d40eb0109e993d37ef4be9107a57e821bc9
tree1a9b6c0a19c2ed2d52a75040faa0e29ab237a0ce
parent20a543097bb9135137ef8e8eb09e129152220dff

compiler: delete arm backend

this backend was abandoned before it was completed, and it is not worth salvaging.

9 files changed, 168 insertions(+), 9160 deletions(-)

CMakeLists.txt-5
......@@ -549,11 +549,6 @@ set(ZIG_STAGE2_SOURCES
549549 src/Value.zig
550550 src/Zcu.zig
551551 src/Zcu/PerThread.zig
552 src/arch/arm/CodeGen.zig
553 src/arch/arm/Emit.zig
554 src/arch/arm/Mir.zig
555 src/arch/arm/abi.zig
556 src/arch/arm/bits.zig
557552 src/arch/riscv64/abi.zig
558553 src/arch/riscv64/bits.zig
559554 src/arch/riscv64/CodeGen.zig
src/arch/arm/CodeGen.zig deleted-6340
......@@ -1,6340 +0,0 @@
1const std = @import("std");
2const builtin = @import("builtin");
3const mem = std.mem;
4const math = std.math;
5const assert = std.debug.assert;
6const codegen = @import("../../codegen.zig");
7const Air = @import("../../Air.zig");
8const Mir = @import("Mir.zig");
9const Emit = @import("Emit.zig");
10const Type = @import("../../Type.zig");
11const Value = @import("../../Value.zig");
12const link = @import("../../link.zig");
13const Zcu = @import("../../Zcu.zig");
14const InternPool = @import("../../InternPool.zig");
15const Compilation = @import("../../Compilation.zig");
16const ErrorMsg = Zcu.ErrorMsg;
17const Target = std.Target;
18const Allocator = mem.Allocator;
19const trace = @import("../../tracy.zig").trace;
20const leb128 = std.leb;
21const log = std.log.scoped(.codegen);
22const build_options = @import("build_options");
23const Alignment = InternPool.Alignment;
24
25const CodeGenError = codegen.CodeGenError;
26
27const bits = @import("bits.zig");
28const abi = @import("abi.zig");
29const errUnionPayloadOffset = codegen.errUnionPayloadOffset;
30const errUnionErrorOffset = codegen.errUnionErrorOffset;
31const RegisterManager = abi.RegisterManager;
32const RegisterLock = RegisterManager.RegisterLock;
33const Register = bits.Register;
34const Instruction = bits.Instruction;
35const Condition = bits.Condition;
36const callee_preserved_regs = abi.callee_preserved_regs;
37const caller_preserved_regs = abi.caller_preserved_regs;
38const c_abi_int_param_regs = abi.c_abi_int_param_regs;
39const c_abi_int_return_regs = abi.c_abi_int_return_regs;
40const gp = abi.RegisterClass.gp;
41
42const InnerError = CodeGenError || error{OutOfRegisters};
43
44pub fn legalizeFeatures(_: *const std.Target) ?*const Air.Legalize.Features {
45 return null;
46}
47
48gpa: Allocator,
49pt: Zcu.PerThread,
50air: Air,
51liveness: Air.Liveness,
52bin_file: *link.File,
53target: *const std.Target,
54func_index: InternPool.Index,
55err_msg: ?*ErrorMsg,
56args: []MCValue,
57ret_mcv: MCValue,
58fn_type: Type,
59arg_index: u32,
60src_loc: Zcu.LazySrcLoc,
61stack_align: u32,
62
63/// MIR Instructions
64mir_instructions: std.MultiArrayList(Mir.Inst) = .{},
65/// MIR extra data
66mir_extra: std.ArrayListUnmanaged(u32) = .empty,
67
68/// Byte offset within the source file of the ending curly.
69end_di_line: u32,
70end_di_column: u32,
71
72/// The value is an offset into the `Function` `code` from the beginning.
73/// To perform the reloc, write 32-bit signed little-endian integer
74/// which is a relative jump, based on the address following the reloc.
75exitlude_jump_relocs: std.ArrayListUnmanaged(usize) = .empty,
76
77reused_operands: std.StaticBitSet(Air.Liveness.bpi - 1) = undefined,
78
79/// We postpone the creation of debug info for function args and locals
80/// until after all Mir instructions have been generated. Only then we
81/// will know saved_regs_stack_space which is necessary in order to
82/// calculate the right stack offsest with respect to the `.fp` register.
83dbg_info_relocs: std.ArrayListUnmanaged(DbgInfoReloc) = .empty,
84
85/// Whenever there is a runtime branch, we push a Branch onto this stack,
86/// and pop it off when the runtime branch joins. This provides an "overlay"
87/// of the table of mappings from instructions to `MCValue` from within the branch.
88/// This way we can modify the `MCValue` for an instruction in different ways
89/// within different branches. Special consideration is needed when a branch
90/// joins with its parent, to make sure all instructions have the same MCValue
91/// across each runtime branch upon joining.
92branch_stack: *std.ArrayList(Branch),
93
94// Key is the block instruction
95blocks: std.AutoHashMapUnmanaged(Air.Inst.Index, BlockData) = .empty,
96
97register_manager: RegisterManager = .{},
98/// Maps offset to what is stored there.
99stack: std.AutoHashMapUnmanaged(u32, StackAllocation) = .empty,
100/// Tracks the current instruction allocated to the compare flags
101cpsr_flags_inst: ?Air.Inst.Index = null,
102
103/// Offset from the stack base, representing the end of the stack frame.
104max_end_stack: u32 = 0,
105/// Represents the current end stack offset. If there is no existing slot
106/// to place a new stack allocation, it goes here, and then bumps `max_end_stack`.
107next_stack_offset: u32 = 0,
108
109saved_regs_stack_space: u32 = 0,
110
111/// Debug field, used to find bugs in the compiler.
112air_bookkeeping: @TypeOf(air_bookkeeping_init) = air_bookkeeping_init,
113
114const air_bookkeeping_init = if (std.debug.runtime_safety) @as(usize, 0) else {};
115
116const MCValue = union(enum) {
117 /// No runtime bits. `void` types, empty structs, u0, enums with 1
118 /// tag, etc.
119 ///
120 /// TODO Look into deleting this tag and using `dead` instead,
121 /// since every use of MCValue.none should be instead looking at
122 /// the type and noticing it is 0 bits.
123 none,
124 /// Control flow will not allow this value to be observed.
125 unreach,
126 /// No more references to this value remain.
127 dead,
128 /// The value is undefined.
129 undef,
130 /// A pointer-sized integer that fits in a register.
131 ///
132 /// If the type is a pointer, this is the pointer address in
133 /// virtual address space.
134 immediate: u32,
135 /// The value is in a target-specific register.
136 register: Register,
137 /// The value is a tuple { wrapped: u32, overflow: u1 } where
138 /// wrapped is stored in the register and the overflow bit is
139 /// stored in the C flag of the CPSR.
140 ///
141 /// This MCValue is only generated by a add_with_overflow or
142 /// sub_with_overflow instruction operating on u32.
143 register_c_flag: Register,
144 /// The value is a tuple { wrapped: i32, overflow: u1 } where
145 /// wrapped is stored in the register and the overflow bit is
146 /// stored in the V flag of the CPSR.
147 ///
148 /// This MCValue is only generated by a add_with_overflow or
149 /// sub_with_overflow instruction operating on i32.
150 register_v_flag: Register,
151 /// The value is in memory at a hard-coded address.
152 ///
153 /// If the type is a pointer, it means the pointer address is at
154 /// this memory location.
155 memory: u64,
156 /// The value is one of the stack variables.
157 ///
158 /// If the type is a pointer, it means the pointer address is in
159 /// the stack at this offset.
160 stack_offset: u32,
161 /// The value is a pointer to one of the stack variables (payload
162 /// is stack offset).
163 ptr_stack_offset: u32,
164 /// The value resides in the N, Z, C, V flags of the Current
165 /// Program Status Register (CPSR). The value is 1 (if the type is
166 /// u1) or true (if the type in bool) iff the specified condition
167 /// is true.
168 cpsr_flags: Condition,
169 /// The value is a function argument passed via the stack.
170 stack_argument_offset: u32,
171};
172
173const Branch = struct {
174 inst_table: std.AutoArrayHashMapUnmanaged(Air.Inst.Index, MCValue) = .empty,
175
176 fn deinit(self: *Branch, gpa: Allocator) void {
177 self.inst_table.deinit(gpa);
178 self.* = undefined;
179 }
180};
181
182const StackAllocation = struct {
183 inst: Air.Inst.Index,
184 /// TODO do we need size? should be determined by inst.ty.abiSize()
185 size: u32,
186};
187
188const BlockData = struct {
189 relocs: std.ArrayListUnmanaged(Mir.Inst.Index),
190 /// The first break instruction encounters `null` here and chooses a
191 /// machine code value for the block result, populating this field.
192 /// Following break instructions encounter that value and use it for
193 /// the location to store their block results.
194 mcv: MCValue,
195};
196
197const BigTomb = struct {
198 function: *Self,
199 inst: Air.Inst.Index,
200 lbt: Air.Liveness.BigTomb,
201
202 fn feed(bt: *BigTomb, op_ref: Air.Inst.Ref) void {
203 const dies = bt.lbt.feed();
204 const op_index = op_ref.toIndex() orelse return;
205 if (!dies) return;
206 bt.function.processDeath(op_index);
207 }
208
209 fn finishAir(bt: *BigTomb, result: MCValue) void {
210 const is_used = !bt.function.liveness.isUnused(bt.inst);
211 if (is_used) {
212 log.debug("%{d} => {}", .{ bt.inst, result });
213 const branch = &bt.function.branch_stack.items[bt.function.branch_stack.items.len - 1];
214 branch.inst_table.putAssumeCapacityNoClobber(bt.inst, result);
215
216 switch (result) {
217 .register => |reg| {
218 // In some cases (such as bitcast), an operand
219 // may be the same MCValue as the result. If
220 // that operand died and was a register, it
221 // was freed by processDeath. We have to
222 // "re-allocate" the register.
223 if (bt.function.register_manager.isRegFree(reg)) {
224 bt.function.register_manager.getRegAssumeFree(reg, bt.inst);
225 }
226 },
227 .register_c_flag,
228 .register_v_flag,
229 => |reg| {
230 if (bt.function.register_manager.isRegFree(reg)) {
231 bt.function.register_manager.getRegAssumeFree(reg, bt.inst);
232 }
233 bt.function.cpsr_flags_inst = bt.inst;
234 },
235 .cpsr_flags => {
236 bt.function.cpsr_flags_inst = bt.inst;
237 },
238 else => {},
239 }
240 }
241 bt.function.finishAirBookkeeping();
242 }
243};
244
245const DbgInfoReloc = struct {
246 tag: Air.Inst.Tag,
247 ty: Type,
248 name: [:0]const u8,
249 mcv: MCValue,
250
251 fn genDbgInfo(reloc: DbgInfoReloc, function: Self) !void {
252 switch (reloc.tag) {
253 .arg,
254 .dbg_arg_inline,
255 => try reloc.genArgDbgInfo(function),
256
257 .dbg_var_ptr,
258 .dbg_var_val,
259 => try reloc.genVarDbgInfo(function),
260
261 else => unreachable,
262 }
263 }
264
265 fn genArgDbgInfo(reloc: DbgInfoReloc, function: Self) !void {
266 // TODO: Add a pseudo-instruction or something to defer this work until Emit.
267 // We aren't allowed to interact with linker state here.
268 if (true) return;
269 switch (function.debug_output) {
270 .dwarf => |dw| {
271 const loc: link.File.Dwarf.Loc = switch (reloc.mcv) {
272 .register => |reg| .{ .reg = reg.dwarfNum() },
273 .stack_offset,
274 .stack_argument_offset,
275 => blk: {
276 const adjusted_stack_offset = switch (reloc.mcv) {
277 .stack_offset => |offset| -@as(i32, @intCast(offset)),
278 .stack_argument_offset => |offset| @as(i32, @intCast(function.saved_regs_stack_space + offset)),
279 else => unreachable,
280 };
281 break :blk .{ .plus = .{
282 &.{ .reg = 11 },
283 &.{ .consts = adjusted_stack_offset },
284 } };
285 },
286 else => unreachable, // not a possible argument
287 };
288
289 try dw.genLocalDebugInfo(.local_arg, reloc.name, reloc.ty, loc);
290 },
291 .plan9 => {},
292 .none => {},
293 }
294 }
295
296 fn genVarDbgInfo(reloc: DbgInfoReloc, function: Self) !void {
297 // TODO: Add a pseudo-instruction or something to defer this work until Emit.
298 // We aren't allowed to interact with linker state here.
299 if (true) return;
300 switch (function.debug_output) {
301 .dwarf => |dw| {
302 const loc: link.File.Dwarf.Loc = switch (reloc.mcv) {
303 .register => |reg| .{ .reg = reg.dwarfNum() },
304 .ptr_stack_offset,
305 .stack_offset,
306 .stack_argument_offset,
307 => |offset| blk: {
308 const adjusted_offset = switch (reloc.mcv) {
309 .ptr_stack_offset,
310 .stack_offset,
311 => -@as(i32, @intCast(offset)),
312 .stack_argument_offset => @as(i32, @intCast(function.saved_regs_stack_space + offset)),
313 else => unreachable,
314 };
315 break :blk .{ .plus = .{
316 &.{ .reg = 11 },
317 &.{ .consts = adjusted_offset },
318 } };
319 },
320 .memory => |address| .{ .constu = address },
321 .immediate => |x| .{ .constu = x },
322 .none => .empty,
323 else => blk: {
324 log.debug("TODO generate debug info for {}", .{reloc.mcv});
325 break :blk .empty;
326 },
327 };
328 try dw.genLocalDebugInfo(.local_var, reloc.name, reloc.ty, loc);
329 },
330 .plan9 => {},
331 .none => {},
332 }
333 }
334};
335
336const Self = @This();
337
338pub fn generate(
339 lf: *link.File,
340 pt: Zcu.PerThread,
341 src_loc: Zcu.LazySrcLoc,
342 func_index: InternPool.Index,
343 air: *const Air,
344 liveness: *const Air.Liveness,
345) CodeGenError!Mir {
346 const zcu = pt.zcu;
347 const gpa = zcu.gpa;
348 const func = zcu.funcInfo(func_index);
349 const func_ty = Type.fromInterned(func.ty);
350 const file_scope = zcu.navFileScope(func.owner_nav);
351 const target = &file_scope.mod.?.resolved_target.result;
352
353 var branch_stack = std.ArrayList(Branch).init(gpa);
354 defer {
355 assert(branch_stack.items.len == 1);
356 branch_stack.items[0].deinit(gpa);
357 branch_stack.deinit();
358 }
359 try branch_stack.append(.{});
360
361 var function: Self = .{
362 .gpa = gpa,
363 .pt = pt,
364 .air = air.*,
365 .liveness = liveness.*,
366 .target = target,
367 .bin_file = lf,
368 .func_index = func_index,
369 .err_msg = null,
370 .args = undefined, // populated after `resolveCallingConventionValues`
371 .ret_mcv = undefined, // populated after `resolveCallingConventionValues`
372 .fn_type = func_ty,
373 .arg_index = 0,
374 .branch_stack = &branch_stack,
375 .src_loc = src_loc,
376 .stack_align = undefined,
377 .end_di_line = func.rbrace_line,
378 .end_di_column = func.rbrace_column,
379 };
380 defer function.stack.deinit(gpa);
381 defer function.blocks.deinit(gpa);
382 defer function.exitlude_jump_relocs.deinit(gpa);
383 defer function.dbg_info_relocs.deinit(gpa);
384
385 var call_info = function.resolveCallingConventionValues(func_ty) catch |err| switch (err) {
386 error.CodegenFail => return error.CodegenFail,
387 else => |e| return e,
388 };
389 defer call_info.deinit(&function);
390
391 function.args = call_info.args;
392 function.ret_mcv = call_info.return_value;
393 function.stack_align = call_info.stack_align;
394 function.max_end_stack = call_info.stack_byte_count;
395
396 function.gen() catch |err| switch (err) {
397 error.CodegenFail => return error.CodegenFail,
398 error.OutOfRegisters => return function.fail("ran out of registers (Zig compiler bug)", .{}),
399 else => |e| return e,
400 };
401
402 for (function.dbg_info_relocs.items) |reloc| {
403 reloc.genDbgInfo(function) catch |err|
404 return function.fail("failed to generate debug info: {s}", .{@errorName(err)});
405 }
406
407 var mir: Mir = .{
408 .instructions = function.mir_instructions.toOwnedSlice(),
409 .extra = &.{}, // fallible, so assign after errdefer
410 .max_end_stack = function.max_end_stack,
411 .saved_regs_stack_space = function.saved_regs_stack_space,
412 };
413 errdefer mir.deinit(gpa);
414 mir.extra = try function.mir_extra.toOwnedSlice(gpa);
415 return mir;
416}
417
418fn addInst(self: *Self, inst: Mir.Inst) error{OutOfMemory}!Mir.Inst.Index {
419 const gpa = self.gpa;
420
421 try self.mir_instructions.ensureUnusedCapacity(gpa, 1);
422
423 const result_index: Mir.Inst.Index = @intCast(self.mir_instructions.len);
424 self.mir_instructions.appendAssumeCapacity(inst);
425 return result_index;
426}
427
428fn addNop(self: *Self) error{OutOfMemory}!Mir.Inst.Index {
429 return try self.addInst(.{
430 .tag = .nop,
431 .data = .{ .nop = {} },
432 });
433}
434
435pub fn addExtra(self: *Self, extra: anytype) Allocator.Error!u32 {
436 const fields = std.meta.fields(@TypeOf(extra));
437 try self.mir_extra.ensureUnusedCapacity(self.gpa, fields.len);
438 return self.addExtraAssumeCapacity(extra);
439}
440
441pub fn addExtraAssumeCapacity(self: *Self, extra: anytype) u32 {
442 const fields = std.meta.fields(@TypeOf(extra));
443 const result: u32 = @intCast(self.mir_extra.items.len);
444 inline for (fields) |field| {
445 self.mir_extra.appendAssumeCapacity(switch (field.type) {
446 u32 => @field(extra, field.name),
447 i32 => @bitCast(@field(extra, field.name)),
448 else => @compileError("bad field type"),
449 });
450 }
451 return result;
452}
453
454fn gen(self: *Self) !void {
455 const pt = self.pt;
456 const zcu = pt.zcu;
457 const cc = self.fn_type.fnCallingConvention(zcu);
458 if (cc != .naked) {
459 // push {fp, lr}
460 const push_reloc = try self.addNop();
461
462 // mov fp, sp
463 _ = try self.addInst(.{
464 .tag = .mov,
465 .data = .{ .r_op_mov = .{
466 .rd = .fp,
467 .op = Instruction.Operand.reg(.sp, Instruction.Operand.Shift.none),
468 } },
469 });
470
471 // sub sp, sp, #reloc
472 const sub_reloc = try self.addNop();
473
474 // The sub_sp_scratch_r4 instruction may use r4, so we mark r4
475 // as allocated by this function.
476 const index = RegisterManager.indexOfRegIntoTracked(.r4).?;
477 self.register_manager.allocated_registers.set(index);
478
479 if (self.ret_mcv == .stack_offset) {
480 // The address of where to store the return value is in
481 // r0. As this register might get overwritten along the
482 // way, save the address to the stack.
483 const stack_offset = try self.allocMem(4, .@"4", null);
484
485 try self.genSetStack(Type.usize, stack_offset, MCValue{ .register = .r0 });
486 self.ret_mcv = MCValue{ .stack_offset = stack_offset };
487 }
488
489 for (self.args, 0..) |*arg, arg_index| {
490 // Copy register arguments to the stack
491 switch (arg.*) {
492 .register => |reg| {
493 // The first AIR instructions of the main body are guaranteed
494 // to be the functions arguments
495 const inst = self.air.getMainBody()[arg_index];
496 assert(self.air.instructions.items(.tag)[@intFromEnum(inst)] == .arg);
497
498 const ty = self.typeOfIndex(inst);
499
500 const abi_size: u32 = @intCast(ty.abiSize(zcu));
501 const abi_align = ty.abiAlignment(zcu);
502 const stack_offset = try self.allocMem(abi_size, abi_align, inst);
503 try self.genSetStack(ty, stack_offset, MCValue{ .register = reg });
504
505 arg.* = MCValue{ .stack_offset = stack_offset };
506 },
507 else => {},
508 }
509 }
510
511 _ = try self.addInst(.{
512 .tag = .dbg_prologue_end,
513 .cond = undefined,
514 .data = .{ .nop = {} },
515 });
516
517 try self.genBody(self.air.getMainBody());
518
519 // Backpatch push callee saved regs
520 var saved_regs = Instruction.RegisterList{
521 .r11 = true, // fp
522 .r14 = true, // lr
523 };
524 self.saved_regs_stack_space = 8;
525 inline for (callee_preserved_regs) |reg| {
526 if (self.register_manager.isRegAllocated(reg)) {
527 @field(saved_regs, @tagName(reg)) = true;
528 self.saved_regs_stack_space += 4;
529 }
530 }
531 self.mir_instructions.set(push_reloc, .{
532 .tag = .push,
533 .data = .{ .register_list = saved_regs },
534 });
535
536 // Backpatch stack offset
537 const total_stack_size = self.max_end_stack + self.saved_regs_stack_space;
538 const aligned_total_stack_end = mem.alignForward(u32, total_stack_size, self.stack_align);
539 const stack_size = aligned_total_stack_end - self.saved_regs_stack_space;
540 self.max_end_stack = stack_size;
541 self.mir_instructions.set(sub_reloc, .{
542 .tag = .sub_sp_scratch_r4,
543 .data = .{ .imm32 = stack_size },
544 });
545
546 _ = try self.addInst(.{
547 .tag = .dbg_epilogue_begin,
548 .cond = undefined,
549 .data = .{ .nop = {} },
550 });
551
552 // exitlude jumps
553 if (self.exitlude_jump_relocs.items.len > 0 and
554 self.exitlude_jump_relocs.items[self.exitlude_jump_relocs.items.len - 1] == self.mir_instructions.len - 2)
555 {
556 // If the last Mir instruction (apart from the
557 // dbg_epilogue_begin) is the last exitlude jump
558 // relocation (which would just jump one instruction
559 // further), it can be safely removed
560 self.mir_instructions.orderedRemove(self.exitlude_jump_relocs.pop().?);
561 }
562
563 for (self.exitlude_jump_relocs.items) |jmp_reloc| {
564 self.mir_instructions.set(jmp_reloc, .{
565 .tag = .b,
566 .data = .{ .inst = @intCast(self.mir_instructions.len) },
567 });
568 }
569
570 // Epilogue: pop callee saved registers (swap lr with pc in saved_regs)
571 saved_regs.r14 = false; // lr
572 saved_regs.r15 = true; // pc
573
574 // mov sp, fp
575 _ = try self.addInst(.{
576 .tag = .mov,
577 .data = .{ .r_op_mov = .{
578 .rd = .sp,
579 .op = Instruction.Operand.reg(.fp, Instruction.Operand.Shift.none),
580 } },
581 });
582
583 // pop {fp, pc}
584 _ = try self.addInst(.{
585 .tag = .pop,
586 .data = .{ .register_list = saved_regs },
587 });
588 } else {
589 _ = try self.addInst(.{
590 .tag = .dbg_prologue_end,
591 .cond = undefined,
592 .data = .{ .nop = {} },
593 });
594
595 try self.genBody(self.air.getMainBody());
596
597 _ = try self.addInst(.{
598 .tag = .dbg_epilogue_begin,
599 .cond = undefined,
600 .data = .{ .nop = {} },
601 });
602 }
603
604 // Drop them off at the rbrace.
605 _ = try self.addInst(.{
606 .tag = .dbg_line,
607 .cond = undefined,
608 .data = .{ .dbg_line_column = .{
609 .line = self.end_di_line,
610 .column = self.end_di_column,
611 } },
612 });
613}
614
615fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
616 const pt = self.pt;
617 const zcu = pt.zcu;
618 const ip = &zcu.intern_pool;
619 const air_tags = self.air.instructions.items(.tag);
620
621 for (body) |inst| {
622 // TODO: remove now-redundant isUnused calls from AIR handler functions
623 if (self.liveness.isUnused(inst) and !self.air.mustLower(inst, ip))
624 continue;
625
626 const old_air_bookkeeping = self.air_bookkeeping;
627 try self.ensureProcessDeathCapacity(Air.Liveness.bpi);
628
629 self.reused_operands = @TypeOf(self.reused_operands).initEmpty();
630 switch (air_tags[@intFromEnum(inst)]) {
631 // zig fmt: off
632 .add, => try self.airBinOp(inst, .add),
633 .add_wrap => try self.airBinOp(inst, .add_wrap),
634 .sub, => try self.airBinOp(inst, .sub),
635 .sub_wrap => try self.airBinOp(inst, .sub_wrap),
636 .mul => try self.airBinOp(inst, .mul),
637 .mul_wrap => try self.airBinOp(inst, .mul_wrap),
638 .shl => try self.airBinOp(inst, .shl),
639 .shl_exact => try self.airBinOp(inst, .shl_exact),
640 .bool_and => try self.airBinOp(inst, .bool_and),
641 .bool_or => try self.airBinOp(inst, .bool_or),
642 .bit_and => try self.airBinOp(inst, .bit_and),
643 .bit_or => try self.airBinOp(inst, .bit_or),
644 .xor => try self.airBinOp(inst, .xor),
645 .shr => try self.airBinOp(inst, .shr),
646 .shr_exact => try self.airBinOp(inst, .shr_exact),
647 .div_float => try self.airBinOp(inst, .div_float),
648 .div_trunc => try self.airBinOp(inst, .div_trunc),
649 .div_floor => try self.airBinOp(inst, .div_floor),
650 .div_exact => try self.airBinOp(inst, .div_exact),
651 .rem => try self.airBinOp(inst, .rem),
652 .mod => try self.airBinOp(inst, .mod),
653
654 .ptr_add => try self.airPtrArithmetic(inst, .ptr_add),
655 .ptr_sub => try self.airPtrArithmetic(inst, .ptr_sub),
656
657 .min => try self.airMinMax(inst),
658 .max => try self.airMinMax(inst),
659
660 .add_sat => try self.airAddSat(inst),
661 .sub_sat => try self.airSubSat(inst),
662 .mul_sat => try self.airMulSat(inst),
663 .shl_sat => try self.airShlSat(inst),
664 .slice => try self.airSlice(inst),
665
666 .sqrt,
667 .sin,
668 .cos,
669 .tan,
670 .exp,
671 .exp2,
672 .log,
673 .log2,
674 .log10,
675 .floor,
676 .ceil,
677 .round,
678 .trunc_float,
679 .neg,
680 => try self.airUnaryMath(inst),
681
682 .add_with_overflow => try self.airOverflow(inst),
683 .sub_with_overflow => try self.airOverflow(inst),
684 .mul_with_overflow => try self.airMulWithOverflow(inst),
685 .shl_with_overflow => try self.airShlWithOverflow(inst),
686
687 .cmp_lt => try self.airCmp(inst, .lt),
688 .cmp_lte => try self.airCmp(inst, .lte),
689 .cmp_eq => try self.airCmp(inst, .eq),
690 .cmp_gte => try self.airCmp(inst, .gte),
691 .cmp_gt => try self.airCmp(inst, .gt),
692 .cmp_neq => try self.airCmp(inst, .neq),
693
694 .cmp_vector => try self.airCmpVector(inst),
695 .cmp_lt_errors_len => try self.airCmpLtErrorsLen(inst),
696
697 .alloc => try self.airAlloc(inst),
698 .ret_ptr => try self.airRetPtr(inst),
699 .arg => try self.airArg(inst),
700 .assembly => try self.airAsm(inst),
701 .bitcast => try self.airBitCast(inst),
702 .block => try self.airBlock(inst),
703 .br => try self.airBr(inst),
704 .repeat => return self.fail("TODO implement `repeat`", .{}),
705 .switch_dispatch => return self.fail("TODO implement `switch_dispatch`", .{}),
706 .trap => try self.airTrap(),
707 .breakpoint => try self.airBreakpoint(),
708 .ret_addr => try self.airRetAddr(inst),
709 .frame_addr => try self.airFrameAddress(inst),
710 .cond_br => try self.airCondBr(inst),
711 .fptrunc => try self.airFptrunc(inst),
712 .fpext => try self.airFpext(inst),
713 .intcast => try self.airIntCast(inst),
714 .trunc => try self.airTrunc(inst),
715 .is_non_null => try self.airIsNonNull(inst),
716 .is_non_null_ptr => try self.airIsNonNullPtr(inst),
717 .is_null => try self.airIsNull(inst),
718 .is_null_ptr => try self.airIsNullPtr(inst),
719 .is_non_err => try self.airIsNonErr(inst),
720 .is_non_err_ptr => try self.airIsNonErrPtr(inst),
721 .is_err => try self.airIsErr(inst),
722 .is_err_ptr => try self.airIsErrPtr(inst),
723 .load => try self.airLoad(inst),
724 .loop => try self.airLoop(inst),
725 .not => try self.airNot(inst),
726 .ret => try self.airRet(inst),
727 .ret_safe => try self.airRet(inst), // TODO
728 .ret_load => try self.airRetLoad(inst),
729 .store => try self.airStore(inst, false),
730 .store_safe => try self.airStore(inst, true),
731 .struct_field_ptr=> try self.airStructFieldPtr(inst),
732 .struct_field_val=> try self.airStructFieldVal(inst),
733 .array_to_slice => try self.airArrayToSlice(inst),
734 .float_from_int => try self.airFloatFromInt(inst),
735 .int_from_float => try self.airIntFromFloat(inst),
736 .cmpxchg_strong => try self.airCmpxchg(inst),
737 .cmpxchg_weak => try self.airCmpxchg(inst),
738 .atomic_rmw => try self.airAtomicRmw(inst),
739 .atomic_load => try self.airAtomicLoad(inst),
740 .memcpy => try self.airMemcpy(inst),
741 .memmove => try self.airMemmove(inst),
742 .memset => try self.airMemset(inst, false),
743 .memset_safe => try self.airMemset(inst, true),
744 .set_union_tag => try self.airSetUnionTag(inst),
745 .get_union_tag => try self.airGetUnionTag(inst),
746 .clz => try self.airClz(inst),
747 .ctz => try self.airCtz(inst),
748 .popcount => try self.airPopcount(inst),
749 .abs => try self.airAbs(inst),
750 .byte_swap => try self.airByteSwap(inst),
751 .bit_reverse => try self.airBitReverse(inst),
752 .tag_name => try self.airTagName(inst),
753 .error_name => try self.airErrorName(inst),
754 .splat => try self.airSplat(inst),
755 .select => try self.airSelect(inst),
756 .shuffle_one => try self.airShuffleOne(inst),
757 .shuffle_two => try self.airShuffleTwo(inst),
758 .reduce => try self.airReduce(inst),
759 .aggregate_init => try self.airAggregateInit(inst),
760 .union_init => try self.airUnionInit(inst),
761 .prefetch => try self.airPrefetch(inst),
762 .mul_add => try self.airMulAdd(inst),
763 .addrspace_cast => return self.fail("TODO implement addrspace_cast", .{}),
764
765 .@"try" => try self.airTry(inst),
766 .try_cold => try self.airTry(inst),
767 .try_ptr => try self.airTryPtr(inst),
768 .try_ptr_cold => try self.airTryPtr(inst),
769
770 .dbg_stmt => try self.airDbgStmt(inst),
771 .dbg_empty_stmt => self.finishAirBookkeeping(),
772 .dbg_inline_block => try self.airDbgInlineBlock(inst),
773 .dbg_var_ptr,
774 .dbg_var_val,
775 .dbg_arg_inline,
776 => try self.airDbgVar(inst),
777
778 .call => try self.airCall(inst, .auto),
779 .call_always_tail => try self.airCall(inst, .always_tail),
780 .call_never_tail => try self.airCall(inst, .never_tail),
781 .call_never_inline => try self.airCall(inst, .never_inline),
782
783 .atomic_store_unordered => try self.airAtomicStore(inst, .unordered),
784 .atomic_store_monotonic => try self.airAtomicStore(inst, .monotonic),
785 .atomic_store_release => try self.airAtomicStore(inst, .release),
786 .atomic_store_seq_cst => try self.airAtomicStore(inst, .seq_cst),
787
788 .struct_field_ptr_index_0 => try self.airStructFieldPtrIndex(inst, 0),
789 .struct_field_ptr_index_1 => try self.airStructFieldPtrIndex(inst, 1),
790 .struct_field_ptr_index_2 => try self.airStructFieldPtrIndex(inst, 2),
791 .struct_field_ptr_index_3 => try self.airStructFieldPtrIndex(inst, 3),
792
793 .field_parent_ptr => try self.airFieldParentPtr(inst),
794
795 .switch_br => try self.airSwitch(inst),
796 .loop_switch_br => return self.fail("TODO implement `loop_switch_br`", .{}),
797 .slice_ptr => try self.airSlicePtr(inst),
798 .slice_len => try self.airSliceLen(inst),
799
800 .ptr_slice_len_ptr => try self.airPtrSliceLenPtr(inst),
801 .ptr_slice_ptr_ptr => try self.airPtrSlicePtrPtr(inst),
802
803 .array_elem_val => try self.airArrayElemVal(inst),
804 .slice_elem_val => try self.airSliceElemVal(inst),
805 .slice_elem_ptr => try self.airSliceElemPtr(inst),
806 .ptr_elem_val => try self.airPtrElemVal(inst),
807 .ptr_elem_ptr => try self.airPtrElemPtr(inst),
808
809 .inferred_alloc, .inferred_alloc_comptime => unreachable,
810 .unreach => self.finishAirBookkeeping(),
811
812 .optional_payload => try self.airOptionalPayload(inst),
813 .optional_payload_ptr => try self.airOptionalPayloadPtr(inst),
814 .optional_payload_ptr_set => try self.airOptionalPayloadPtrSet(inst),
815 .unwrap_errunion_err => try self.airUnwrapErrErr(inst),
816 .unwrap_errunion_payload => try self.airUnwrapErrPayload(inst),
817 .unwrap_errunion_err_ptr => try self.airUnwrapErrErrPtr(inst),
818 .unwrap_errunion_payload_ptr=> try self.airUnwrapErrPayloadPtr(inst),
819 .errunion_payload_ptr_set => try self.airErrUnionPayloadPtrSet(inst),
820 .err_return_trace => try self.airErrReturnTrace(inst),
821 .set_err_return_trace => try self.airSetErrReturnTrace(inst),
822 .save_err_return_trace_index=> try self.airSaveErrReturnTraceIndex(inst),
823
824 .wrap_optional => try self.airWrapOptional(inst),
825 .wrap_errunion_payload => try self.airWrapErrUnionPayload(inst),
826 .wrap_errunion_err => try self.airWrapErrUnionErr(inst),
827
828 .add_optimized,
829 .sub_optimized,
830 .mul_optimized,
831 .div_float_optimized,
832 .div_trunc_optimized,
833 .div_floor_optimized,
834 .div_exact_optimized,
835 .rem_optimized,
836 .mod_optimized,
837 .neg_optimized,
838 .cmp_lt_optimized,
839 .cmp_lte_optimized,
840 .cmp_eq_optimized,
841 .cmp_gte_optimized,
842 .cmp_gt_optimized,
843 .cmp_neq_optimized,
844 .cmp_vector_optimized,
845 .reduce_optimized,
846 .int_from_float_optimized,
847 => return self.fail("TODO implement optimized float mode", .{}),
848
849 .add_safe,
850 .sub_safe,
851 .mul_safe,
852 .intcast_safe,
853 .int_from_float_safe,
854 .int_from_float_optimized_safe,
855 => return self.fail("TODO implement safety_checked_instructions", .{}),
856
857 .is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}),
858 .error_set_has_value => return self.fail("TODO implement error_set_has_value", .{}),
859 .vector_store_elem => return self.fail("TODO implement vector_store_elem", .{}),
860 .runtime_nav_ptr => return self.fail("TODO implement runtime_nav_ptr", .{}),
861
862 .c_va_arg => return self.fail("TODO implement c_va_arg", .{}),
863 .c_va_copy => return self.fail("TODO implement c_va_copy", .{}),
864 .c_va_end => return self.fail("TODO implement c_va_end", .{}),
865 .c_va_start => return self.fail("TODO implement c_va_start", .{}),
866
867 .wasm_memory_size => unreachable,
868 .wasm_memory_grow => unreachable,
869
870 .work_item_id => unreachable,
871 .work_group_size => unreachable,
872 .work_group_id => unreachable,
873 // zig fmt: on
874 }
875
876 assert(!self.register_manager.lockedRegsExist());
877
878 if (std.debug.runtime_safety) {
879 if (self.air_bookkeeping < old_air_bookkeeping + 1) {
880 std.debug.panic("in codegen.zig, handling of AIR instruction %{d} ('{}') did not do proper bookkeeping. Look for a missing call to finishAir.", .{ inst, air_tags[@intFromEnum(inst)] });
881 }
882 }
883 }
884}
885
886/// Asserts there is already capacity to insert into top branch inst_table.
887fn processDeath(self: *Self, inst: Air.Inst.Index) void {
888 // When editing this function, note that the logic must synchronize with `reuseOperand`.
889 const prev_value = self.getResolvedInstValue(inst);
890 const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
891 branch.inst_table.putAssumeCapacity(inst, .dead);
892 switch (prev_value) {
893 .register => |reg| {
894 self.register_manager.freeReg(reg);
895 },
896 .register_c_flag,
897 .register_v_flag,
898 => |reg| {
899 self.register_manager.freeReg(reg);
900 self.cpsr_flags_inst = null;
901 },
902 .cpsr_flags => {
903 self.cpsr_flags_inst = null;
904 },
905 else => {}, // TODO process stack allocation death
906 }
907}
908
909/// Called when there are no operands, and the instruction is always unreferenced.
910fn finishAirBookkeeping(self: *Self) void {
911 if (std.debug.runtime_safety) {
912 self.air_bookkeeping += 1;
913 }
914}
915
916fn finishAir(self: *Self, inst: Air.Inst.Index, result: MCValue, operands: [Air.Liveness.bpi - 1]Air.Inst.Ref) void {
917 const tomb_bits = self.liveness.getTombBits(inst);
918 for (0.., operands) |op_index, op| {
919 if (tomb_bits & @as(Air.Liveness.Bpi, 1) << @intCast(op_index) == 0) continue;
920 if (self.reused_operands.isSet(op_index)) continue;
921 self.processDeath(op.toIndexAllowNone() orelse continue);
922 }
923 if (tomb_bits & 1 << (Air.Liveness.bpi - 1) == 0) {
924 log.debug("%{d} => {}", .{ inst, result });
925 const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
926 branch.inst_table.putAssumeCapacityNoClobber(inst, result);
927
928 switch (result) {
929 .register => |reg| {
930 // In some cases (such as bitcast), an operand
931 // may be the same MCValue as the result. If
932 // that operand died and was a register, it
933 // was freed by processDeath. We have to
934 // "re-allocate" the register.
935 if (self.register_manager.isRegFree(reg)) {
936 self.register_manager.getRegAssumeFree(reg, inst);
937 }
938 },
939 .register_c_flag,
940 .register_v_flag,
941 => |reg| {
942 if (self.register_manager.isRegFree(reg)) {
943 self.register_manager.getRegAssumeFree(reg, inst);
944 }
945 self.cpsr_flags_inst = inst;
946 },
947 .cpsr_flags => {
948 self.cpsr_flags_inst = inst;
949 },
950 else => {},
951 }
952 }
953 self.finishAirBookkeeping();
954}
955
956fn ensureProcessDeathCapacity(self: *Self, additional_count: usize) !void {
957 const table = &self.branch_stack.items[self.branch_stack.items.len - 1].inst_table;
958 try table.ensureUnusedCapacity(self.gpa, additional_count);
959}
960
961fn allocMem(
962 self: *Self,
963 abi_size: u32,
964 abi_align: Alignment,
965 maybe_inst: ?Air.Inst.Index,
966) !u32 {
967 assert(abi_size > 0);
968 assert(abi_align != .none);
969
970 // TODO find a free slot instead of always appending
971 const offset: u32 = @intCast(abi_align.forward(self.next_stack_offset) + abi_size);
972 self.next_stack_offset = offset;
973 self.max_end_stack = @max(self.max_end_stack, self.next_stack_offset);
974
975 if (maybe_inst) |inst| {
976 try self.stack.putNoClobber(self.gpa, offset, .{
977 .inst = inst,
978 .size = abi_size,
979 });
980 }
981
982 return offset;
983}
984
985/// Use a pointer instruction as the basis for allocating stack memory.
986fn allocMemPtr(self: *Self, inst: Air.Inst.Index) !u32 {
987 const pt = self.pt;
988 const zcu = pt.zcu;
989 const elem_ty = self.typeOfIndex(inst).childType(zcu);
990
991 if (!elem_ty.hasRuntimeBits(zcu)) {
992 // As this stack item will never be dereferenced at runtime,
993 // return the stack offset 0. Stack offset 0 will be where all
994 // zero-sized stack allocations live as non-zero-sized
995 // allocations will always have an offset > 0.
996 return 0;
997 }
998
999 const abi_size = math.cast(u32, elem_ty.abiSize(zcu)) orelse {
1000 return self.fail("type '{}' too big to fit into stack frame", .{elem_ty.fmt(pt)});
1001 };
1002 // TODO swap this for inst.ty.ptrAlign
1003 const abi_align = elem_ty.abiAlignment(zcu);
1004
1005 return self.allocMem(abi_size, abi_align, inst);
1006}
1007
1008fn allocRegOrMem(self: *Self, elem_ty: Type, reg_ok: bool, maybe_inst: ?Air.Inst.Index) !MCValue {
1009 const pt = self.pt;
1010 const abi_size = math.cast(u32, elem_ty.abiSize(pt.zcu)) orelse {
1011 return self.fail("type '{}' too big to fit into stack frame", .{elem_ty.fmt(pt)});
1012 };
1013 const abi_align = elem_ty.abiAlignment(pt.zcu);
1014
1015 if (reg_ok) {
1016 // Make sure the type can fit in a register before we try to allocate one.
1017 const ptr_bits = self.target.ptrBitWidth();
1018 const ptr_bytes: u64 = @divExact(ptr_bits, 8);
1019 if (abi_size <= ptr_bytes) {
1020 if (self.register_manager.tryAllocReg(maybe_inst, gp)) |reg| {
1021 return MCValue{ .register = reg };
1022 }
1023 }
1024 }
1025
1026 const stack_offset = try self.allocMem(abi_size, abi_align, maybe_inst);
1027 return MCValue{ .stack_offset = stack_offset };
1028}
1029
1030pub fn spillInstruction(self: *Self, reg: Register, inst: Air.Inst.Index) !void {
1031 const stack_mcv = try self.allocRegOrMem(self.typeOfIndex(inst), false, inst);
1032 log.debug("spilling {} (%{d}) to stack mcv {any}", .{ reg, inst, stack_mcv });
1033
1034 const reg_mcv = self.getResolvedInstValue(inst);
1035 switch (reg_mcv) {
1036 .register,
1037 .register_c_flag,
1038 .register_v_flag,
1039 => |r| assert(r == reg),
1040 else => unreachable, // not a register
1041 }
1042
1043 const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
1044 try branch.inst_table.put(self.gpa, inst, stack_mcv);
1045 try self.genSetStack(self.typeOfIndex(inst), stack_mcv.stack_offset, reg_mcv);
1046}
1047
1048/// Save the current instruction stored in the compare flags if
1049/// occupied
1050fn spillCompareFlagsIfOccupied(self: *Self) !void {
1051 if (self.cpsr_flags_inst) |inst_to_save| {
1052 const ty = self.typeOfIndex(inst_to_save);
1053 const mcv = self.getResolvedInstValue(inst_to_save);
1054 const new_mcv = switch (mcv) {
1055 .cpsr_flags => try self.allocRegOrMem(ty, true, inst_to_save),
1056 .register_c_flag,
1057 .register_v_flag,
1058 => try self.allocRegOrMem(ty, false, inst_to_save),
1059 else => unreachable, // mcv doesn't occupy the compare flags
1060 };
1061
1062 try self.setRegOrMem(self.typeOfIndex(inst_to_save), new_mcv, mcv);
1063 log.debug("spilling {d} to mcv {any}", .{ inst_to_save, new_mcv });
1064
1065 const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
1066 try branch.inst_table.put(self.gpa, inst_to_save, new_mcv);
1067
1068 self.cpsr_flags_inst = null;
1069
1070 // TODO consolidate with register manager and spillInstruction
1071 // this call should really belong in the register manager!
1072 switch (mcv) {
1073 .register_c_flag,
1074 .register_v_flag,
1075 => |reg| self.register_manager.freeReg(reg),
1076 else => {},
1077 }
1078 }
1079}
1080
1081/// Copies a value to a register without tracking the register. The register is not considered
1082/// allocated. A second call to `copyToTmpRegister` may return the same register.
1083/// This can have a side effect of spilling instructions to the stack to free up a register.
1084fn copyToTmpRegister(self: *Self, ty: Type, mcv: MCValue) !Register {
1085 const reg = try self.register_manager.allocReg(null, gp);
1086 try self.genSetReg(ty, reg, mcv);
1087 return reg;
1088}
1089
1090fn airAlloc(self: *Self, inst: Air.Inst.Index) !void {
1091 const stack_offset = try self.allocMemPtr(inst);
1092 return self.finishAir(inst, .{ .ptr_stack_offset = stack_offset }, .{ .none, .none, .none });
1093}
1094
1095fn airRetPtr(self: *Self, inst: Air.Inst.Index) !void {
1096 const pt = self.pt;
1097 const zcu = pt.zcu;
1098 const result: MCValue = switch (self.ret_mcv) {
1099 .none, .register => .{ .ptr_stack_offset = try self.allocMemPtr(inst) },
1100 .stack_offset => blk: {
1101 // self.ret_mcv is an address to where this function
1102 // should store its result into
1103 const ret_ty = self.fn_type.fnReturnType(zcu);
1104 const ptr_ty = try pt.singleMutPtrType(ret_ty);
1105
1106 // addr_reg will contain the address of where to store the
1107 // result into
1108 const addr_reg = try self.copyToTmpRegister(ptr_ty, self.ret_mcv);
1109 break :blk .{ .register = addr_reg };
1110 },
1111 else => unreachable, // invalid return result
1112 };
1113
1114 return self.finishAir(inst, result, .{ .none, .none, .none });
1115}
1116
1117fn airFptrunc(self: *Self, inst: Air.Inst.Index) !void {
1118 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
1119 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airFptrunc for {}", .{self.target.cpu.arch});
1120 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
1121}
1122
1123fn airFpext(self: *Self, inst: Air.Inst.Index) !void {
1124 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
1125 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airFpext for {}", .{self.target.cpu.arch});
1126 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
1127}
1128
1129fn airIntCast(self: *Self, inst: Air.Inst.Index) !void {
1130 const pt = self.pt;
1131 const zcu = pt.zcu;
1132 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
1133 if (self.liveness.isUnused(inst))
1134 return self.finishAir(inst, .dead, .{ ty_op.operand, .none, .none });
1135
1136 const operand = try self.resolveInst(ty_op.operand);
1137 const operand_ty = self.typeOf(ty_op.operand);
1138 const dest_ty = self.typeOfIndex(inst);
1139
1140 const operand_abi_size = operand_ty.abiSize(zcu);
1141 const dest_abi_size = dest_ty.abiSize(zcu);
1142 const info_a = operand_ty.intInfo(zcu);
1143 const info_b = dest_ty.intInfo(zcu);
1144
1145 const dst_mcv: MCValue = blk: {
1146 if (info_a.bits == info_b.bits) {
1147 break :blk operand;
1148 }
1149 if (operand_abi_size > 4 or dest_abi_size > 4) {
1150 return self.fail("TODO implement intCast for abi sizes larger than 4", .{});
1151 }
1152
1153 const operand_lock: ?RegisterLock = switch (operand) {
1154 .register => |reg| self.register_manager.lockRegAssumeUnused(reg),
1155 else => null,
1156 };
1157 defer if (operand_lock) |lock| self.register_manager.unlockReg(lock);
1158
1159 const reg = try self.register_manager.allocReg(inst, gp);
1160 try self.genSetReg(dest_ty, reg, operand);
1161 break :blk MCValue{ .register = reg };
1162 };
1163
1164 return self.finishAir(inst, dst_mcv, .{ ty_op.operand, .none, .none });
1165}
1166
1167fn truncRegister(
1168 self: *Self,
1169 operand_reg: Register,
1170 dest_reg: Register,
1171 int_signedness: std.builtin.Signedness,
1172 int_bits: u16,
1173) !void {
1174 // TODO check if sxtb/uxtb/sxth/uxth are more efficient
1175 _ = try self.addInst(.{
1176 .tag = switch (int_signedness) {
1177 .signed => .sbfx,
1178 .unsigned => .ubfx,
1179 },
1180 .data = .{ .rr_lsb_width = .{
1181 .rd = dest_reg,
1182 .rn = operand_reg,
1183 .lsb = 0,
1184 .width = @intCast(int_bits),
1185 } },
1186 });
1187}
1188
1189/// Asserts that both operand_ty and dest_ty are integer types
1190fn trunc(
1191 self: *Self,
1192 maybe_inst: ?Air.Inst.Index,
1193 operand_bind: ReadArg.Bind,
1194 operand_ty: Type,
1195 dest_ty: Type,
1196) !MCValue {
1197 const pt = self.pt;
1198 const zcu = pt.zcu;
1199 const info_a = operand_ty.intInfo(zcu);
1200 const info_b = dest_ty.intInfo(zcu);
1201
1202 if (info_b.bits <= 32) {
1203 if (info_a.bits > 32) {
1204 return self.fail("TODO load least significant word into register", .{});
1205 }
1206
1207 var operand_reg: Register = undefined;
1208 var dest_reg: Register = undefined;
1209
1210 const read_args = [_]ReadArg{
1211 .{ .ty = operand_ty, .bind = operand_bind, .class = gp, .reg = &operand_reg },
1212 };
1213 const write_args = [_]WriteArg{
1214 .{ .ty = dest_ty, .bind = .none, .class = gp, .reg = &dest_reg },
1215 };
1216 try self.allocRegs(
1217 &read_args,
1218 &write_args,
1219 if (maybe_inst) |inst| .{
1220 .corresponding_inst = inst,
1221 .operand_mapping = &.{0},
1222 } else null,
1223 );
1224
1225 switch (info_b.bits) {
1226 32 => {
1227 try self.genSetReg(operand_ty, dest_reg, .{ .register = operand_reg });
1228 },
1229 else => {
1230 try self.truncRegister(operand_reg, dest_reg, info_b.signedness, info_b.bits);
1231 },
1232 }
1233
1234 return MCValue{ .register = dest_reg };
1235 } else {
1236 return self.fail("TODO: truncate to ints > 32 bits", .{});
1237 }
1238}
1239
1240fn airTrunc(self: *Self, inst: Air.Inst.Index) !void {
1241 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
1242 const operand_bind: ReadArg.Bind = .{ .inst = ty_op.operand };
1243 const operand_ty = self.typeOf(ty_op.operand);
1244 const dest_ty = self.typeOfIndex(inst);
1245
1246 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else blk: {
1247 break :blk try self.trunc(inst, operand_bind, operand_ty, dest_ty);
1248 };
1249
1250 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
1251}
1252
1253fn airNot(self: *Self, inst: Air.Inst.Index) !void {
1254 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
1255 const pt = self.pt;
1256 const zcu = pt.zcu;
1257 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
1258 const operand_bind: ReadArg.Bind = .{ .inst = ty_op.operand };
1259 const operand_ty = self.typeOf(ty_op.operand);
1260 switch (try operand_bind.resolveToMcv(self)) {
1261 .dead => unreachable,
1262 .unreach => unreachable,
1263 .cpsr_flags => |cond| break :result MCValue{ .cpsr_flags = cond.negate() },
1264 else => {
1265 switch (operand_ty.zigTypeTag(zcu)) {
1266 .bool => {
1267 var op_reg: Register = undefined;
1268 var dest_reg: Register = undefined;
1269
1270 const read_args = [_]ReadArg{
1271 .{ .ty = operand_ty, .bind = operand_bind, .class = gp, .reg = &op_reg },
1272 };
1273 const write_args = [_]WriteArg{
1274 .{ .ty = operand_ty, .bind = .none, .class = gp, .reg = &dest_reg },
1275 };
1276 try self.allocRegs(
1277 &read_args,
1278 &write_args,
1279 ReuseMetadata{
1280 .corresponding_inst = inst,
1281 .operand_mapping = &.{0},
1282 },
1283 );
1284
1285 _ = try self.addInst(.{
1286 .tag = .eor,
1287 .data = .{ .rr_op = .{
1288 .rd = dest_reg,
1289 .rn = op_reg,
1290 .op = Instruction.Operand.fromU32(1).?,
1291 } },
1292 });
1293
1294 break :result MCValue{ .register = dest_reg };
1295 },
1296 .vector => return self.fail("TODO bitwise not for vectors", .{}),
1297 .int => {
1298 const int_info = operand_ty.intInfo(zcu);
1299 if (int_info.bits <= 32) {
1300 var op_reg: Register = undefined;
1301 var dest_reg: Register = undefined;
1302
1303 const read_args = [_]ReadArg{
1304 .{ .ty = operand_ty, .bind = operand_bind, .class = gp, .reg = &op_reg },
1305 };
1306 const write_args = [_]WriteArg{
1307 .{ .ty = operand_ty, .bind = .none, .class = gp, .reg = &dest_reg },
1308 };
1309 try self.allocRegs(
1310 &read_args,
1311 &write_args,
1312 ReuseMetadata{
1313 .corresponding_inst = inst,
1314 .operand_mapping = &.{0},
1315 },
1316 );
1317
1318 _ = try self.addInst(.{
1319 .tag = .mvn,
1320 .data = .{ .r_op_mov = .{
1321 .rd = dest_reg,
1322 .op = Instruction.Operand.reg(op_reg, Instruction.Operand.Shift.none),
1323 } },
1324 });
1325
1326 if (int_info.bits < 32) {
1327 try self.truncRegister(dest_reg, dest_reg, int_info.signedness, int_info.bits);
1328 }
1329
1330 break :result MCValue{ .register = dest_reg };
1331 } else {
1332 return self.fail("TODO ARM not on integers > u32/i32", .{});
1333 }
1334 },
1335 else => unreachable,
1336 }
1337 },
1338 }
1339 };
1340 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
1341}
1342
1343fn minMax(
1344 self: *Self,
1345 tag: Air.Inst.Tag,
1346 lhs_bind: ReadArg.Bind,
1347 rhs_bind: ReadArg.Bind,
1348 lhs_ty: Type,
1349 rhs_ty: Type,
1350 maybe_inst: ?Air.Inst.Index,
1351) !MCValue {
1352 const pt = self.pt;
1353 const zcu = pt.zcu;
1354 switch (lhs_ty.zigTypeTag(zcu)) {
1355 .float => return self.fail("TODO ARM min/max on floats", .{}),
1356 .vector => return self.fail("TODO ARM min/max on vectors", .{}),
1357 .int => {
1358 assert(lhs_ty.eql(rhs_ty, zcu));
1359 const int_info = lhs_ty.intInfo(zcu);
1360 if (int_info.bits <= 32) {
1361 var lhs_reg: Register = undefined;
1362 var rhs_reg: Register = undefined;
1363 var dest_reg: Register = undefined;
1364
1365 const read_args = [_]ReadArg{
1366 .{ .ty = lhs_ty, .bind = lhs_bind, .class = gp, .reg = &lhs_reg },
1367 .{ .ty = rhs_ty, .bind = rhs_bind, .class = gp, .reg = &rhs_reg },
1368 };
1369 const write_args = [_]WriteArg{
1370 .{ .ty = lhs_ty, .bind = .none, .class = gp, .reg = &dest_reg },
1371 };
1372 try self.allocRegs(
1373 &read_args,
1374 &write_args,
1375 if (maybe_inst) |inst| .{
1376 .corresponding_inst = inst,
1377 .operand_mapping = &.{ 0, 1 },
1378 } else null,
1379 );
1380
1381 // lhs == reg should have been checked by airMinMax
1382 //
1383 // By guaranteeing lhs != rhs, we guarantee (dst !=
1384 // lhs) or (dst != rhs), which is a property we use to
1385 // omit generating one instruction when we reuse a
1386 // register.
1387 assert(lhs_reg != rhs_reg); // see note above
1388
1389 _ = try self.addInst(.{
1390 .tag = .cmp,
1391 .data = .{ .r_op_cmp = .{
1392 .rn = lhs_reg,
1393 .op = Instruction.Operand.reg(rhs_reg, Instruction.Operand.Shift.none),
1394 } },
1395 });
1396
1397 const cond_choose_lhs: Condition = switch (tag) {
1398 .max => switch (int_info.signedness) {
1399 .signed => Condition.gt,
1400 .unsigned => Condition.hi,
1401 },
1402 .min => switch (int_info.signedness) {
1403 .signed => Condition.lt,
1404 .unsigned => Condition.cc,
1405 },
1406 else => unreachable,
1407 };
1408 const cond_choose_rhs = cond_choose_lhs.negate();
1409
1410 if (dest_reg != lhs_reg) {
1411 _ = try self.addInst(.{
1412 .tag = .mov,
1413 .cond = cond_choose_lhs,
1414 .data = .{ .r_op_mov = .{
1415 .rd = dest_reg,
1416 .op = Instruction.Operand.reg(lhs_reg, Instruction.Operand.Shift.none),
1417 } },
1418 });
1419 }
1420 if (dest_reg != rhs_reg) {
1421 _ = try self.addInst(.{
1422 .tag = .mov,
1423 .cond = cond_choose_rhs,
1424 .data = .{ .r_op_mov = .{
1425 .rd = dest_reg,
1426 .op = Instruction.Operand.reg(rhs_reg, Instruction.Operand.Shift.none),
1427 } },
1428 });
1429 }
1430
1431 return MCValue{ .register = dest_reg };
1432 } else {
1433 return self.fail("TODO ARM min/max on integers > u32/i32", .{});
1434 }
1435 },
1436 else => unreachable,
1437 }
1438}
1439
1440fn airMinMax(self: *Self, inst: Air.Inst.Index) !void {
1441 const tag = self.air.instructions.items(.tag)[@intFromEnum(inst)];
1442 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
1443 const lhs_ty = self.typeOf(bin_op.lhs);
1444 const rhs_ty = self.typeOf(bin_op.rhs);
1445
1446 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
1447 const lhs_bind: ReadArg.Bind = .{ .inst = bin_op.lhs };
1448 const rhs_bind: ReadArg.Bind = .{ .inst = bin_op.rhs };
1449
1450 const lhs = try self.resolveInst(bin_op.lhs);
1451 if (bin_op.lhs == bin_op.rhs) break :result lhs;
1452
1453 break :result try self.minMax(tag, lhs_bind, rhs_bind, lhs_ty, rhs_ty, inst);
1454 };
1455 return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
1456}
1457
1458fn airSlice(self: *Self, inst: Air.Inst.Index) !void {
1459 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
1460 const bin_op = self.air.extraData(Air.Bin, ty_pl.payload).data;
1461 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
1462 const ptr = try self.resolveInst(bin_op.lhs);
1463 const ptr_ty = self.typeOf(bin_op.lhs);
1464 const len = try self.resolveInst(bin_op.rhs);
1465 const len_ty = self.typeOf(bin_op.rhs);
1466
1467 const stack_offset = try self.allocMem(8, .@"4", inst);
1468 try self.genSetStack(ptr_ty, stack_offset, ptr);
1469 try self.genSetStack(len_ty, stack_offset - 4, len);
1470 break :result MCValue{ .stack_offset = stack_offset };
1471 };
1472 return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
1473}
1474
1475fn airBinOp(self: *Self, inst: Air.Inst.Index, tag: Air.Inst.Tag) !void {
1476 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
1477 const lhs_ty = self.typeOf(bin_op.lhs);
1478 const rhs_ty = self.typeOf(bin_op.rhs);
1479
1480 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
1481 const lhs_bind: ReadArg.Bind = .{ .inst = bin_op.lhs };
1482 const rhs_bind: ReadArg.Bind = .{ .inst = bin_op.rhs };
1483
1484 break :result switch (tag) {
1485 .add => try self.addSub(tag, lhs_bind, rhs_bind, lhs_ty, rhs_ty, inst),
1486 .sub => try self.addSub(tag, lhs_bind, rhs_bind, lhs_ty, rhs_ty, inst),
1487
1488 .mul => try self.mul(lhs_bind, rhs_bind, lhs_ty, rhs_ty, inst),
1489
1490 .div_float => try self.divFloat(lhs_bind, rhs_bind, lhs_ty, rhs_ty, inst),
1491
1492 .div_trunc => try self.divTrunc(lhs_bind, rhs_bind, lhs_ty, rhs_ty, inst),
1493
1494 .div_floor => try self.divFloor(lhs_bind, rhs_bind, lhs_ty, rhs_ty, inst),
1495
1496 .div_exact => try self.divExact(lhs_bind, rhs_bind, lhs_ty, rhs_ty, inst),
1497
1498 .rem => try self.rem(lhs_bind, rhs_bind, lhs_ty, rhs_ty, inst),
1499
1500 .mod => try self.modulo(lhs_bind, rhs_bind, lhs_ty, rhs_ty, inst),
1501
1502 .add_wrap => try self.wrappingArithmetic(tag, lhs_bind, rhs_bind, lhs_ty, rhs_ty, inst),
1503 .sub_wrap => try self.wrappingArithmetic(tag, lhs_bind, rhs_bind, lhs_ty, rhs_ty, inst),
1504 .mul_wrap => try self.wrappingArithmetic(tag, lhs_bind, rhs_bind, lhs_ty, rhs_ty, inst),
1505
1506 .bit_and => try self.bitwise(tag, lhs_bind, rhs_bind, lhs_ty, rhs_ty, inst),
1507 .bit_or => try self.bitwise(tag, lhs_bind, rhs_bind, lhs_ty, rhs_ty, inst),
1508 .xor => try self.bitwise(tag, lhs_bind, rhs_bind, lhs_ty, rhs_ty, inst),
1509
1510 .shl_exact => try self.shiftExact(tag, lhs_bind, rhs_bind, lhs_ty, rhs_ty, inst),
1511 .shr_exact => try self.shiftExact(tag, lhs_bind, rhs_bind, lhs_ty, rhs_ty, inst),
1512
1513 .shl => try self.shiftNormal(tag, lhs_bind, rhs_bind, lhs_ty, rhs_ty, inst),
1514 .shr => try self.shiftNormal(tag, lhs_bind, rhs_bind, lhs_ty, rhs_ty, inst),
1515
1516 .bool_and => try self.booleanOp(tag, lhs_bind, rhs_bind, lhs_ty, rhs_ty, inst),
1517 .bool_or => try self.booleanOp(tag, lhs_bind, rhs_bind, lhs_ty, rhs_ty, inst),
1518
1519 else => unreachable,
1520 };
1521 };
1522 return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
1523}
1524
1525fn airPtrArithmetic(self: *Self, inst: Air.Inst.Index, tag: Air.Inst.Tag) !void {
1526 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
1527 const bin_op = self.air.extraData(Air.Bin, ty_pl.payload).data;
1528 const lhs_ty = self.typeOf(bin_op.lhs);
1529 const rhs_ty = self.typeOf(bin_op.rhs);
1530
1531 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
1532 const lhs_bind: ReadArg.Bind = .{ .inst = bin_op.lhs };
1533 const rhs_bind: ReadArg.Bind = .{ .inst = bin_op.rhs };
1534
1535 break :result try self.ptrArithmetic(tag, lhs_bind, rhs_bind, lhs_ty, rhs_ty, inst);
1536 };
1537 return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
1538}
1539
1540fn airAddSat(self: *Self, inst: Air.Inst.Index) !void {
1541 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
1542 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement add_sat for {}", .{self.target.cpu.arch});
1543 return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
1544}
1545
1546fn airSubSat(self: *Self, inst: Air.Inst.Index) !void {
1547 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
1548 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement sub_sat for {}", .{self.target.cpu.arch});
1549 return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
1550}
1551
1552fn airMulSat(self: *Self, inst: Air.Inst.Index) !void {
1553 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
1554 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement mul_sat for {}", .{self.target.cpu.arch});
1555 return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
1556}
1557
1558fn airOverflow(self: *Self, inst: Air.Inst.Index) !void {
1559 const tag = self.air.instructions.items(.tag)[@intFromEnum(inst)];
1560 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
1561 const extra = self.air.extraData(Air.Bin, ty_pl.payload).data;
1562 const pt = self.pt;
1563 const zcu = pt.zcu;
1564 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
1565 const lhs_bind: ReadArg.Bind = .{ .inst = extra.lhs };
1566 const rhs_bind: ReadArg.Bind = .{ .inst = extra.rhs };
1567 const lhs_ty = self.typeOf(extra.lhs);
1568 const rhs_ty = self.typeOf(extra.rhs);
1569
1570 const tuple_ty = self.typeOfIndex(inst);
1571 const tuple_size: u32 = @intCast(tuple_ty.abiSize(zcu));
1572 const tuple_align = tuple_ty.abiAlignment(zcu);
1573 const overflow_bit_offset: u32 = @intCast(tuple_ty.structFieldOffset(1, zcu));
1574
1575 switch (lhs_ty.zigTypeTag(zcu)) {
1576 .vector => return self.fail("TODO implement add_with_overflow/sub_with_overflow for vectors", .{}),
1577 .int => {
1578 assert(lhs_ty.eql(rhs_ty, zcu));
1579 const int_info = lhs_ty.intInfo(zcu);
1580 if (int_info.bits < 32) {
1581 const stack_offset = try self.allocMem(tuple_size, tuple_align, inst);
1582
1583 try self.spillCompareFlagsIfOccupied();
1584
1585 const base_tag: Air.Inst.Tag = switch (tag) {
1586 .add_with_overflow => .add,
1587 .sub_with_overflow => .sub,
1588 else => unreachable,
1589 };
1590 const dest = try self.addSub(base_tag, lhs_bind, rhs_bind, lhs_ty, rhs_ty, null);
1591 const dest_reg = dest.register;
1592 const dest_reg_lock = self.register_manager.lockRegAssumeUnused(dest_reg);
1593 defer self.register_manager.unlockReg(dest_reg_lock);
1594
1595 const truncated_reg = try self.register_manager.allocReg(null, gp);
1596 const truncated_reg_lock = self.register_manager.lockRegAssumeUnused(truncated_reg);
1597 defer self.register_manager.unlockReg(truncated_reg_lock);
1598
1599 // sbfx/ubfx truncated, dest, #0, #bits
1600 try self.truncRegister(dest_reg, truncated_reg, int_info.signedness, int_info.bits);
1601
1602 // cmp dest, truncated
1603 _ = try self.addInst(.{
1604 .tag = .cmp,
1605 .data = .{ .r_op_cmp = .{
1606 .rn = dest_reg,
1607 .op = Instruction.Operand.reg(truncated_reg, Instruction.Operand.Shift.none),
1608 } },
1609 });
1610
1611 try self.genSetStack(lhs_ty, stack_offset, .{ .register = truncated_reg });
1612 try self.genSetStack(Type.u1, stack_offset - overflow_bit_offset, .{ .cpsr_flags = .ne });
1613
1614 break :result MCValue{ .stack_offset = stack_offset };
1615 } else if (int_info.bits == 32) {
1616 const lhs_immediate = try lhs_bind.resolveToImmediate(self);
1617 const rhs_immediate = try rhs_bind.resolveToImmediate(self);
1618
1619 // Only say yes if the operation is
1620 // commutative, i.e. we can swap both of the
1621 // operands
1622 const lhs_immediate_ok = switch (tag) {
1623 .add_with_overflow => if (lhs_immediate) |imm| Instruction.Operand.fromU32(imm) != null else false,
1624 .sub_with_overflow => false,
1625 else => unreachable,
1626 };
1627 const rhs_immediate_ok = switch (tag) {
1628 .add_with_overflow,
1629 .sub_with_overflow,
1630 => if (rhs_immediate) |imm| Instruction.Operand.fromU32(imm) != null else false,
1631 else => unreachable,
1632 };
1633
1634 const mir_tag: Mir.Inst.Tag = switch (tag) {
1635 .add_with_overflow => .adds,
1636 .sub_with_overflow => .subs,
1637 else => unreachable,
1638 };
1639
1640 try self.spillCompareFlagsIfOccupied();
1641 self.cpsr_flags_inst = inst;
1642
1643 const dest = blk: {
1644 if (rhs_immediate_ok) {
1645 break :blk try self.binOpImmediate(mir_tag, lhs_bind, rhs_immediate.?, lhs_ty, false, null);
1646 } else if (lhs_immediate_ok) {
1647 // swap lhs and rhs
1648 break :blk try self.binOpImmediate(mir_tag, rhs_bind, lhs_immediate.?, rhs_ty, true, null);
1649 } else {
1650 break :blk try self.binOpRegister(mir_tag, lhs_bind, rhs_bind, lhs_ty, rhs_ty, null);
1651 }
1652 };
1653
1654 if (tag == .sub_with_overflow) {
1655 break :result MCValue{ .register_v_flag = dest.register };
1656 }
1657
1658 switch (int_info.signedness) {
1659 .unsigned => break :result MCValue{ .register_c_flag = dest.register },
1660 .signed => break :result MCValue{ .register_v_flag = dest.register },
1661 }
1662 } else {
1663 return self.fail("TODO ARM overflow operations on integers > u32/i32", .{});
1664 }
1665 },
1666 else => unreachable,
1667 }
1668 };
1669 return self.finishAir(inst, result, .{ extra.lhs, extra.rhs, .none });
1670}
1671
1672fn airMulWithOverflow(self: *Self, inst: Air.Inst.Index) !void {
1673 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
1674 const extra = self.air.extraData(Air.Bin, ty_pl.payload).data;
1675 if (self.liveness.isUnused(inst)) return self.finishAir(inst, .dead, .{ extra.lhs, extra.rhs, .none });
1676 const pt = self.pt;
1677 const zcu = pt.zcu;
1678 const result: MCValue = result: {
1679 const lhs_bind: ReadArg.Bind = .{ .inst = extra.lhs };
1680 const rhs_bind: ReadArg.Bind = .{ .inst = extra.rhs };
1681 const lhs_ty = self.typeOf(extra.lhs);
1682 const rhs_ty = self.typeOf(extra.rhs);
1683
1684 const tuple_ty = self.typeOfIndex(inst);
1685 const tuple_size: u32 = @intCast(tuple_ty.abiSize(zcu));
1686 const tuple_align = tuple_ty.abiAlignment(zcu);
1687 const overflow_bit_offset: u32 = @intCast(tuple_ty.structFieldOffset(1, zcu));
1688
1689 switch (lhs_ty.zigTypeTag(zcu)) {
1690 .vector => return self.fail("TODO implement mul_with_overflow for vectors", .{}),
1691 .int => {
1692 assert(lhs_ty.eql(rhs_ty, zcu));
1693 const int_info = lhs_ty.intInfo(zcu);
1694 if (int_info.bits <= 16) {
1695 const stack_offset = try self.allocMem(tuple_size, tuple_align, inst);
1696
1697 try self.spillCompareFlagsIfOccupied();
1698
1699 const base_tag: Mir.Inst.Tag = switch (int_info.signedness) {
1700 .signed => .smulbb,
1701 .unsigned => .mul,
1702 };
1703
1704 const dest = try self.binOpRegister(base_tag, lhs_bind, rhs_bind, lhs_ty, rhs_ty, null);
1705 const dest_reg = dest.register;
1706 const dest_reg_lock = self.register_manager.lockRegAssumeUnused(dest_reg);
1707 defer self.register_manager.unlockReg(dest_reg_lock);
1708
1709 const truncated_reg = try self.register_manager.allocReg(null, gp);
1710 const truncated_reg_lock = self.register_manager.lockRegAssumeUnused(truncated_reg);
1711 defer self.register_manager.unlockReg(truncated_reg_lock);
1712
1713 // sbfx/ubfx truncated, dest, #0, #bits
1714 try self.truncRegister(dest_reg, truncated_reg, int_info.signedness, int_info.bits);
1715
1716 // cmp dest, truncated
1717 _ = try self.addInst(.{
1718 .tag = .cmp,
1719 .data = .{ .r_op_cmp = .{
1720 .rn = dest_reg,
1721 .op = Instruction.Operand.reg(truncated_reg, Instruction.Operand.Shift.none),
1722 } },
1723 });
1724
1725 try self.genSetStack(lhs_ty, stack_offset, .{ .register = truncated_reg });
1726 try self.genSetStack(Type.u1, stack_offset - overflow_bit_offset, .{ .cpsr_flags = .ne });
1727
1728 break :result MCValue{ .stack_offset = stack_offset };
1729 } else if (int_info.bits <= 32) {
1730 const stack_offset = try self.allocMem(tuple_size, tuple_align, inst);
1731
1732 try self.spillCompareFlagsIfOccupied();
1733
1734 const base_tag: Mir.Inst.Tag = switch (int_info.signedness) {
1735 .signed => .smull,
1736 .unsigned => .umull,
1737 };
1738
1739 var lhs_reg: Register = undefined;
1740 var rhs_reg: Register = undefined;
1741 var rdhi: Register = undefined;
1742 var rdlo: Register = undefined;
1743 var truncated_reg: Register = undefined;
1744
1745 const read_args = [_]ReadArg{
1746 .{ .ty = lhs_ty, .bind = lhs_bind, .class = gp, .reg = &lhs_reg },
1747 .{ .ty = rhs_ty, .bind = rhs_bind, .class = gp, .reg = &rhs_reg },
1748 };
1749 const write_args = [_]WriteArg{
1750 .{ .ty = lhs_ty, .bind = .none, .class = gp, .reg = &rdhi },
1751 .{ .ty = lhs_ty, .bind = .none, .class = gp, .reg = &rdlo },
1752 .{ .ty = lhs_ty, .bind = .none, .class = gp, .reg = &truncated_reg },
1753 };
1754 try self.allocRegs(
1755 &read_args,
1756 &write_args,
1757 null,
1758 );
1759
1760 _ = try self.addInst(.{
1761 .tag = base_tag,
1762 .data = .{ .rrrr = .{
1763 .rdlo = rdlo,
1764 .rdhi = rdhi,
1765 .rn = lhs_reg,
1766 .rm = rhs_reg,
1767 } },
1768 });
1769
1770 // sbfx/ubfx truncated, rdlo, #0, #bits
1771 try self.truncRegister(rdlo, truncated_reg, int_info.signedness, int_info.bits);
1772
1773 // str truncated, [...]
1774 try self.genSetStack(lhs_ty, stack_offset, .{ .register = truncated_reg });
1775
1776 // cmp truncated, rdlo
1777 _ = try self.addInst(.{
1778 .tag = .cmp,
1779 .data = .{ .r_op_cmp = .{
1780 .rn = truncated_reg,
1781 .op = Instruction.Operand.reg(rdlo, Instruction.Operand.Shift.none),
1782 } },
1783 });
1784
1785 // mov rdlo, #0
1786 _ = try self.addInst(.{
1787 .tag = .mov,
1788 .data = .{ .r_op_mov = .{
1789 .rd = rdlo,
1790 .op = Instruction.Operand.fromU32(0).?,
1791 } },
1792 });
1793
1794 // movne rdlo, #1
1795 _ = try self.addInst(.{
1796 .tag = .mov,
1797 .cond = .ne,
1798 .data = .{ .r_op_mov = .{
1799 .rd = rdlo,
1800 .op = Instruction.Operand.fromU32(1).?,
1801 } },
1802 });
1803
1804 // cmp rdhi, #0
1805 _ = try self.addInst(.{
1806 .tag = .cmp,
1807 .data = .{ .r_op_cmp = .{
1808 .rn = rdhi,
1809 .op = Instruction.Operand.fromU32(0).?,
1810 } },
1811 });
1812
1813 // movne rdlo, #1
1814 _ = try self.addInst(.{
1815 .tag = .mov,
1816 .cond = .ne,
1817 .data = .{ .r_op_mov = .{
1818 .rd = rdlo,
1819 .op = Instruction.Operand.fromU32(1).?,
1820 } },
1821 });
1822
1823 // strb rdlo, [...]
1824 try self.genSetStack(Type.u1, stack_offset - overflow_bit_offset, .{ .register = rdlo });
1825
1826 break :result MCValue{ .stack_offset = stack_offset };
1827 } else {
1828 return self.fail("TODO ARM overflow operations on integers > u32/i32", .{});
1829 }
1830 },
1831 else => unreachable,
1832 }
1833 };
1834 return self.finishAir(inst, result, .{ extra.lhs, extra.rhs, .none });
1835}
1836
1837fn airShlWithOverflow(self: *Self, inst: Air.Inst.Index) !void {
1838 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
1839 const extra = self.air.extraData(Air.Bin, ty_pl.payload).data;
1840 if (self.liveness.isUnused(inst)) return self.finishAir(inst, .dead, .{ extra.lhs, extra.rhs, .none });
1841 const pt = self.pt;
1842 const zcu = pt.zcu;
1843 const result: MCValue = result: {
1844 const lhs_ty = self.typeOf(extra.lhs);
1845 const rhs_ty = self.typeOf(extra.rhs);
1846
1847 const tuple_ty = self.typeOfIndex(inst);
1848 const tuple_size: u32 = @intCast(tuple_ty.abiSize(zcu));
1849 const tuple_align = tuple_ty.abiAlignment(zcu);
1850 const overflow_bit_offset: u32 = @intCast(tuple_ty.structFieldOffset(1, zcu));
1851
1852 switch (lhs_ty.zigTypeTag(zcu)) {
1853 .vector => if (!rhs_ty.isVector(zcu))
1854 return self.fail("TODO implement vector shl_with_overflow with scalar rhs", .{})
1855 else
1856 return self.fail("TODO implement shl_with_overflow for vectors", .{}),
1857 .int => {
1858 const int_info = lhs_ty.intInfo(zcu);
1859 if (int_info.bits <= 32) {
1860 const stack_offset = try self.allocMem(tuple_size, tuple_align, inst);
1861
1862 try self.spillCompareFlagsIfOccupied();
1863
1864 const shr_mir_tag: Mir.Inst.Tag = switch (int_info.signedness) {
1865 .signed => Mir.Inst.Tag.asr,
1866 .unsigned => Mir.Inst.Tag.lsr,
1867 };
1868
1869 var lhs_reg: Register = undefined;
1870 var rhs_reg: Register = undefined;
1871 var dest_reg: Register = undefined;
1872 var reconstructed_reg: Register = undefined;
1873
1874 const rhs_mcv = try self.resolveInst(extra.rhs);
1875 const rhs_immediate_ok = rhs_mcv == .immediate and Instruction.Operand.fromU32(rhs_mcv.immediate) != null;
1876
1877 const lhs_bind: ReadArg.Bind = .{ .inst = extra.lhs };
1878 const rhs_bind: ReadArg.Bind = .{ .inst = extra.rhs };
1879
1880 if (rhs_immediate_ok) {
1881 const read_args = [_]ReadArg{
1882 .{ .ty = lhs_ty, .bind = lhs_bind, .class = gp, .reg = &lhs_reg },
1883 };
1884 const write_args = [_]WriteArg{
1885 .{ .ty = lhs_ty, .bind = .none, .class = gp, .reg = &dest_reg },
1886 .{ .ty = lhs_ty, .bind = .none, .class = gp, .reg = &reconstructed_reg },
1887 };
1888 try self.allocRegs(
1889 &read_args,
1890 &write_args,
1891 null,
1892 );
1893
1894 // lsl dest, lhs, rhs
1895 _ = try self.addInst(.{
1896 .tag = .lsl,
1897 .data = .{ .rr_shift = .{
1898 .rd = dest_reg,
1899 .rm = lhs_reg,
1900 .shift_amount = Instruction.ShiftAmount.imm(@intCast(rhs_mcv.immediate)),
1901 } },
1902 });
1903
1904 try self.truncRegister(dest_reg, dest_reg, int_info.signedness, int_info.bits);
1905
1906 // asr/lsr reconstructed, dest, rhs
1907 _ = try self.addInst(.{
1908 .tag = shr_mir_tag,
1909 .data = .{ .rr_shift = .{
1910 .rd = reconstructed_reg,
1911 .rm = dest_reg,
1912 .shift_amount = Instruction.ShiftAmount.imm(@intCast(rhs_mcv.immediate)),
1913 } },
1914 });
1915 } else {
1916 const read_args = [_]ReadArg{
1917 .{ .ty = lhs_ty, .bind = lhs_bind, .class = gp, .reg = &lhs_reg },
1918 .{ .ty = rhs_ty, .bind = rhs_bind, .class = gp, .reg = &rhs_reg },
1919 };
1920 const write_args = [_]WriteArg{
1921 .{ .ty = lhs_ty, .bind = .none, .class = gp, .reg = &dest_reg },
1922 .{ .ty = lhs_ty, .bind = .none, .class = gp, .reg = &reconstructed_reg },
1923 };
1924 try self.allocRegs(
1925 &read_args,
1926 &write_args,
1927 null,
1928 );
1929
1930 // lsl dest, lhs, rhs
1931 _ = try self.addInst(.{
1932 .tag = .lsl,
1933 .data = .{ .rr_shift = .{
1934 .rd = dest_reg,
1935 .rm = lhs_reg,
1936 .shift_amount = Instruction.ShiftAmount.reg(rhs_reg),
1937 } },
1938 });
1939
1940 try self.truncRegister(dest_reg, dest_reg, int_info.signedness, int_info.bits);
1941
1942 // asr/lsr reconstructed, dest, rhs
1943 _ = try self.addInst(.{
1944 .tag = shr_mir_tag,
1945 .data = .{ .rr_shift = .{
1946 .rd = reconstructed_reg,
1947 .rm = dest_reg,
1948 .shift_amount = Instruction.ShiftAmount.reg(rhs_reg),
1949 } },
1950 });
1951 }
1952
1953 // cmp lhs, reconstructed
1954 _ = try self.addInst(.{
1955 .tag = .cmp,
1956 .data = .{ .r_op_cmp = .{
1957 .rn = lhs_reg,
1958 .op = Instruction.Operand.reg(reconstructed_reg, Instruction.Operand.Shift.none),
1959 } },
1960 });
1961
1962 try self.genSetStack(lhs_ty, stack_offset, .{ .register = dest_reg });
1963 try self.genSetStack(Type.u1, stack_offset - overflow_bit_offset, .{ .cpsr_flags = .ne });
1964
1965 break :result MCValue{ .stack_offset = stack_offset };
1966 } else {
1967 return self.fail("TODO ARM overflow operations on integers > u32/i32", .{});
1968 }
1969 },
1970 else => unreachable,
1971 }
1972 };
1973 return self.finishAir(inst, result, .{ extra.lhs, extra.rhs, .none });
1974}
1975
1976fn airShlSat(self: *Self, inst: Air.Inst.Index) !void {
1977 const zcu = self.pt.zcu;
1978 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
1979 const result: MCValue = if (self.liveness.isUnused(inst))
1980 .dead
1981 else if (self.typeOf(bin_op.lhs).isVector(zcu) and !self.typeOf(bin_op.rhs).isVector(zcu))
1982 return self.fail("TODO implement vector shl_sat with scalar rhs for {}", .{self.target.cpu.arch})
1983 else
1984 return self.fail("TODO implement shl_sat for {}", .{self.target.cpu.arch});
1985 return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
1986}
1987
1988fn airOptionalPayload(self: *Self, inst: Air.Inst.Index) !void {
1989 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
1990 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement .optional_payload for {}", .{self.target.cpu.arch});
1991 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
1992}
1993
1994fn airOptionalPayloadPtr(self: *Self, inst: Air.Inst.Index) !void {
1995 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
1996 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement .optional_payload_ptr for {}", .{self.target.cpu.arch});
1997 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
1998}
1999
2000fn airOptionalPayloadPtrSet(self: *Self, inst: Air.Inst.Index) !void {
2001 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
2002 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement .optional_payload_ptr_set for {}", .{self.target.cpu.arch});
2003 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
2004}
2005
2006fn airWrapOptional(self: *Self, inst: Air.Inst.Index) !void {
2007 const pt = self.pt;
2008 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
2009 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
2010 const optional_ty = self.typeOfIndex(inst);
2011 const abi_size: u32 = @intCast(optional_ty.abiSize(pt.zcu));
2012
2013 // Optional with a zero-bit payload type is just a boolean true
2014 if (abi_size == 1) {
2015 break :result MCValue{ .immediate = 1 };
2016 } else {
2017 return self.fail("TODO implement wrap optional for {}", .{self.target.cpu.arch});
2018 }
2019 };
2020 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
2021}
2022
2023/// Given an error union, returns the error
2024fn errUnionErr(
2025 self: *Self,
2026 error_union_bind: ReadArg.Bind,
2027 error_union_ty: Type,
2028 maybe_inst: ?Air.Inst.Index,
2029) !MCValue {
2030 const pt = self.pt;
2031 const zcu = pt.zcu;
2032 const err_ty = error_union_ty.errorUnionSet(zcu);
2033 const payload_ty = error_union_ty.errorUnionPayload(zcu);
2034 if (err_ty.errorSetIsEmpty(zcu)) {
2035 return MCValue{ .immediate = 0 };
2036 }
2037 if (!payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
2038 return try error_union_bind.resolveToMcv(self);
2039 }
2040
2041 const err_offset: u32 = @intCast(errUnionErrorOffset(payload_ty, zcu));
2042 switch (try error_union_bind.resolveToMcv(self)) {
2043 .register => {
2044 var operand_reg: Register = undefined;
2045 var dest_reg: Register = undefined;
2046
2047 const read_args = [_]ReadArg{
2048 .{ .ty = error_union_ty, .bind = error_union_bind, .class = gp, .reg = &operand_reg },
2049 };
2050 const write_args = [_]WriteArg{
2051 .{ .ty = err_ty, .bind = .none, .class = gp, .reg = &dest_reg },
2052 };
2053 try self.allocRegs(
2054 &read_args,
2055 &write_args,
2056 if (maybe_inst) |inst| .{
2057 .corresponding_inst = inst,
2058 .operand_mapping = &.{0},
2059 } else null,
2060 );
2061
2062 const err_bit_offset = err_offset * 8;
2063 const err_bit_size: u32 = @intCast(err_ty.abiSize(zcu) * 8);
2064
2065 _ = try self.addInst(.{
2066 .tag = .ubfx, // errors are unsigned integers
2067 .data = .{ .rr_lsb_width = .{
2068 .rd = dest_reg,
2069 .rn = operand_reg,
2070 .lsb = @intCast(err_bit_offset),
2071 .width = @intCast(err_bit_size),
2072 } },
2073 });
2074
2075 return MCValue{ .register = dest_reg };
2076 },
2077 .stack_argument_offset => |off| {
2078 return MCValue{ .stack_argument_offset = off + err_offset };
2079 },
2080 .stack_offset => |off| {
2081 return MCValue{ .stack_offset = off - err_offset };
2082 },
2083 .memory => |addr| {
2084 return MCValue{ .memory = addr + err_offset };
2085 },
2086 else => unreachable, // invalid MCValue for an error union
2087 }
2088}
2089
2090fn airUnwrapErrErr(self: *Self, inst: Air.Inst.Index) !void {
2091 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
2092 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
2093 const error_union_bind: ReadArg.Bind = .{ .inst = ty_op.operand };
2094 const error_union_ty = self.typeOf(ty_op.operand);
2095
2096 break :result try self.errUnionErr(error_union_bind, error_union_ty, inst);
2097 };
2098 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
2099}
2100
2101/// Given an error union, returns the payload
2102fn errUnionPayload(
2103 self: *Self,
2104 error_union_bind: ReadArg.Bind,
2105 error_union_ty: Type,
2106 maybe_inst: ?Air.Inst.Index,
2107) !MCValue {
2108 const pt = self.pt;
2109 const zcu = pt.zcu;
2110 const err_ty = error_union_ty.errorUnionSet(zcu);
2111 const payload_ty = error_union_ty.errorUnionPayload(zcu);
2112 if (err_ty.errorSetIsEmpty(zcu)) {
2113 return try error_union_bind.resolveToMcv(self);
2114 }
2115 if (!payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
2116 return MCValue.none;
2117 }
2118
2119 const payload_offset: u32 = @intCast(errUnionPayloadOffset(payload_ty, zcu));
2120 switch (try error_union_bind.resolveToMcv(self)) {
2121 .register => {
2122 var operand_reg: Register = undefined;
2123 var dest_reg: Register = undefined;
2124
2125 const read_args = [_]ReadArg{
2126 .{ .ty = error_union_ty, .bind = error_union_bind, .class = gp, .reg = &operand_reg },
2127 };
2128 const write_args = [_]WriteArg{
2129 .{ .ty = err_ty, .bind = .none, .class = gp, .reg = &dest_reg },
2130 };
2131 try self.allocRegs(
2132 &read_args,
2133 &write_args,
2134 if (maybe_inst) |inst| .{
2135 .corresponding_inst = inst,
2136 .operand_mapping = &.{0},
2137 } else null,
2138 );
2139
2140 const payload_bit_offset = payload_offset * 8;
2141 const payload_bit_size: u32 = @intCast(payload_ty.abiSize(zcu) * 8);
2142
2143 _ = try self.addInst(.{
2144 .tag = if (payload_ty.isSignedInt(zcu)) Mir.Inst.Tag.sbfx else .ubfx,
2145 .data = .{ .rr_lsb_width = .{
2146 .rd = dest_reg,
2147 .rn = operand_reg,
2148 .lsb = @intCast(payload_bit_offset),
2149 .width = @intCast(payload_bit_size),
2150 } },
2151 });
2152
2153 return MCValue{ .register = dest_reg };
2154 },
2155 .stack_argument_offset => |off| {
2156 return MCValue{ .stack_argument_offset = off + payload_offset };
2157 },
2158 .stack_offset => |off| {
2159 return MCValue{ .stack_offset = off - payload_offset };
2160 },
2161 .memory => |addr| {
2162 return MCValue{ .memory = addr + payload_offset };
2163 },
2164 else => unreachable, // invalid MCValue for an error union
2165 }
2166}
2167
2168fn airUnwrapErrPayload(self: *Self, inst: Air.Inst.Index) !void {
2169 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
2170 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
2171 const error_union_bind: ReadArg.Bind = .{ .inst = ty_op.operand };
2172 const error_union_ty = self.typeOf(ty_op.operand);
2173
2174 break :result try self.errUnionPayload(error_union_bind, error_union_ty, inst);
2175 };
2176 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
2177}
2178
2179// *(E!T) -> E
2180fn airUnwrapErrErrPtr(self: *Self, inst: Air.Inst.Index) !void {
2181 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
2182 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement unwrap error union error ptr for {}", .{self.target.cpu.arch});
2183 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
2184}
2185
2186// *(E!T) -> *T
2187fn airUnwrapErrPayloadPtr(self: *Self, inst: Air.Inst.Index) !void {
2188 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
2189 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement unwrap error union payload ptr for {}", .{self.target.cpu.arch});
2190 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
2191}
2192
2193fn airErrUnionPayloadPtrSet(self: *Self, inst: Air.Inst.Index) !void {
2194 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
2195 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement .errunion_payload_ptr_set for {}", .{self.target.cpu.arch});
2196 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
2197}
2198
2199fn airErrReturnTrace(self: *Self, inst: Air.Inst.Index) !void {
2200 const result: MCValue = if (self.liveness.isUnused(inst))
2201 .dead
2202 else
2203 return self.fail("TODO implement airErrReturnTrace for {}", .{self.target.cpu.arch});
2204 return self.finishAir(inst, result, .{ .none, .none, .none });
2205}
2206
2207fn airSetErrReturnTrace(self: *Self, inst: Air.Inst.Index) !void {
2208 _ = inst;
2209 return self.fail("TODO implement airSetErrReturnTrace for {}", .{self.target.cpu.arch});
2210}
2211
2212fn airSaveErrReturnTraceIndex(self: *Self, inst: Air.Inst.Index) !void {
2213 _ = inst;
2214 return self.fail("TODO implement airSaveErrReturnTraceIndex for {}", .{self.target.cpu.arch});
2215}
2216
2217/// T to E!T
2218fn airWrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) !void {
2219 const pt = self.pt;
2220 const zcu = pt.zcu;
2221 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
2222 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
2223 const error_union_ty = ty_op.ty.toType();
2224 const error_ty = error_union_ty.errorUnionSet(zcu);
2225 const payload_ty = error_union_ty.errorUnionPayload(zcu);
2226 const operand = try self.resolveInst(ty_op.operand);
2227 if (!payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) break :result operand;
2228
2229 const abi_size: u32 = @intCast(error_union_ty.abiSize(zcu));
2230 const abi_align = error_union_ty.abiAlignment(zcu);
2231 const stack_offset: u32 = @intCast(try self.allocMem(abi_size, abi_align, inst));
2232 const payload_off = errUnionPayloadOffset(payload_ty, zcu);
2233 const err_off = errUnionErrorOffset(payload_ty, zcu);
2234 try self.genSetStack(payload_ty, stack_offset - @as(u32, @intCast(payload_off)), operand);
2235 try self.genSetStack(error_ty, stack_offset - @as(u32, @intCast(err_off)), .{ .immediate = 0 });
2236
2237 break :result MCValue{ .stack_offset = stack_offset };
2238 };
2239 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
2240}
2241
2242/// E to E!T
2243fn airWrapErrUnionErr(self: *Self, inst: Air.Inst.Index) !void {
2244 const pt = self.pt;
2245 const zcu = pt.zcu;
2246 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
2247 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
2248 const error_union_ty = ty_op.ty.toType();
2249 const error_ty = error_union_ty.errorUnionSet(zcu);
2250 const payload_ty = error_union_ty.errorUnionPayload(zcu);
2251 const operand = try self.resolveInst(ty_op.operand);
2252 if (!payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) break :result operand;
2253
2254 const abi_size: u32 = @intCast(error_union_ty.abiSize(zcu));
2255 const abi_align = error_union_ty.abiAlignment(zcu);
2256 const stack_offset: u32 = @intCast(try self.allocMem(abi_size, abi_align, inst));
2257 const payload_off = errUnionPayloadOffset(payload_ty, zcu);
2258 const err_off = errUnionErrorOffset(payload_ty, zcu);
2259 try self.genSetStack(error_ty, stack_offset - @as(u32, @intCast(err_off)), operand);
2260 try self.genSetStack(payload_ty, stack_offset - @as(u32, @intCast(payload_off)), .undef);
2261
2262 break :result MCValue{ .stack_offset = stack_offset };
2263 };
2264 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
2265}
2266
2267/// Given a slice, returns the length
2268fn slicePtr(mcv: MCValue) MCValue {
2269 switch (mcv) {
2270 .register => unreachable, // a slice doesn't fit in one register
2271 .stack_argument_offset => |off| {
2272 return MCValue{ .stack_argument_offset = off };
2273 },
2274 .stack_offset => |off| {
2275 return MCValue{ .stack_offset = off };
2276 },
2277 .memory => |addr| {
2278 return MCValue{ .memory = addr };
2279 },
2280 else => unreachable, // invalid MCValue for a slice
2281 }
2282}
2283
2284fn airSlicePtr(self: *Self, inst: Air.Inst.Index) !void {
2285 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
2286 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
2287 const mcv = try self.resolveInst(ty_op.operand);
2288 break :result slicePtr(mcv);
2289 };
2290 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
2291}
2292
2293fn airSliceLen(self: *Self, inst: Air.Inst.Index) !void {
2294 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
2295 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
2296 const mcv = try self.resolveInst(ty_op.operand);
2297 switch (mcv) {
2298 .register => unreachable, // a slice doesn't fit in one register
2299 .stack_argument_offset => |off| {
2300 break :result MCValue{ .stack_argument_offset = off + 4 };
2301 },
2302 .stack_offset => |off| {
2303 break :result MCValue{ .stack_offset = off - 4 };
2304 },
2305 .memory => |addr| {
2306 break :result MCValue{ .memory = addr + 4 };
2307 },
2308 else => unreachable, // invalid MCValue for a slice
2309 }
2310 };
2311 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
2312}
2313
2314fn airPtrSliceLenPtr(self: *Self, inst: Air.Inst.Index) !void {
2315 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
2316 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
2317 const mcv = try self.resolveInst(ty_op.operand);
2318 switch (mcv) {
2319 .dead, .unreach => unreachable,
2320 .ptr_stack_offset => |off| {
2321 break :result MCValue{ .ptr_stack_offset = off - 4 };
2322 },
2323 else => {
2324 const lhs_bind: ReadArg.Bind = .{ .mcv = mcv };
2325 const rhs_bind: ReadArg.Bind = .{ .mcv = .{ .immediate = 4 } };
2326
2327 break :result try self.addSub(.add, lhs_bind, rhs_bind, Type.usize, Type.usize, null);
2328 },
2329 }
2330 };
2331 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
2332}
2333
2334fn airPtrSlicePtrPtr(self: *Self, inst: Air.Inst.Index) !void {
2335 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
2336 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
2337 const mcv = try self.resolveInst(ty_op.operand);
2338 switch (mcv) {
2339 .dead, .unreach => unreachable,
2340 .ptr_stack_offset => |off| {
2341 break :result MCValue{ .ptr_stack_offset = off };
2342 },
2343 else => {
2344 if (self.reuseOperand(inst, ty_op.operand, 0, mcv)) {
2345 break :result mcv;
2346 } else {
2347 break :result MCValue{ .register = try self.copyToTmpRegister(Type.usize, mcv) };
2348 }
2349 },
2350 }
2351 };
2352 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
2353}
2354
2355fn ptrElemVal(
2356 self: *Self,
2357 ptr_bind: ReadArg.Bind,
2358 index_bind: ReadArg.Bind,
2359 ptr_ty: Type,
2360 maybe_inst: ?Air.Inst.Index,
2361) !MCValue {
2362 const pt = self.pt;
2363 const zcu = pt.zcu;
2364 const elem_ty = ptr_ty.childType(zcu);
2365 const elem_size: u32 = @intCast(elem_ty.abiSize(zcu));
2366
2367 switch (elem_size) {
2368 1, 4 => {
2369 var base_reg: Register = undefined;
2370 var index_reg: Register = undefined;
2371 var dest_reg: Register = undefined;
2372
2373 const read_args = [_]ReadArg{
2374 .{ .ty = ptr_ty, .bind = ptr_bind, .class = gp, .reg = &base_reg },
2375 .{ .ty = Type.usize, .bind = index_bind, .class = gp, .reg = &index_reg },
2376 };
2377 const write_args = [_]WriteArg{
2378 .{ .ty = elem_ty, .bind = .none, .class = gp, .reg = &dest_reg },
2379 };
2380 try self.allocRegs(
2381 &read_args,
2382 &write_args,
2383 if (maybe_inst) |inst| .{
2384 .corresponding_inst = inst,
2385 .operand_mapping = &.{ 0, 1 },
2386 } else null,
2387 );
2388
2389 const tag: Mir.Inst.Tag = switch (elem_size) {
2390 1 => .ldrb,
2391 4 => .ldr,
2392 else => unreachable,
2393 };
2394 const shift: u5 = switch (elem_size) {
2395 1 => 0,
2396 4 => 2,
2397 else => unreachable,
2398 };
2399
2400 _ = try self.addInst(.{
2401 .tag = tag,
2402 .data = .{ .rr_offset = .{
2403 .rt = dest_reg,
2404 .rn = base_reg,
2405 .offset = .{ .offset = Instruction.Offset.reg(index_reg, .{ .lsl = shift }) },
2406 } },
2407 });
2408
2409 return MCValue{ .register = dest_reg };
2410 },
2411 else => {
2412 const addr = try self.ptrArithmetic(.ptr_add, ptr_bind, index_bind, ptr_ty, Type.usize, null);
2413
2414 const dest = try self.allocRegOrMem(elem_ty, true, maybe_inst);
2415 try self.load(dest, addr, ptr_ty);
2416 return dest;
2417 },
2418 }
2419}
2420
2421fn airSliceElemVal(self: *Self, inst: Air.Inst.Index) !void {
2422 const pt = self.pt;
2423 const zcu = pt.zcu;
2424 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
2425 const slice_ty = self.typeOf(bin_op.lhs);
2426 const result: MCValue = if (!slice_ty.isVolatilePtr(zcu) and self.liveness.isUnused(inst)) .dead else result: {
2427 const ptr_ty = slice_ty.slicePtrFieldType(zcu);
2428
2429 const slice_mcv = try self.resolveInst(bin_op.lhs);
2430 const base_mcv = slicePtr(slice_mcv);
2431
2432 const base_bind: ReadArg.Bind = .{ .mcv = base_mcv };
2433 const index_bind: ReadArg.Bind = .{ .inst = bin_op.rhs };
2434
2435 break :result try self.ptrElemVal(base_bind, index_bind, ptr_ty, inst);
2436 };
2437 return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
2438}
2439
2440fn airSliceElemPtr(self: *Self, inst: Air.Inst.Index) !void {
2441 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
2442 const extra = self.air.extraData(Air.Bin, ty_pl.payload).data;
2443 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
2444 const slice_mcv = try self.resolveInst(extra.lhs);
2445 const base_mcv = slicePtr(slice_mcv);
2446
2447 const base_bind: ReadArg.Bind = .{ .mcv = base_mcv };
2448 const index_bind: ReadArg.Bind = .{ .inst = extra.rhs };
2449
2450 const slice_ty = self.typeOf(extra.lhs);
2451 const index_ty = self.typeOf(extra.rhs);
2452
2453 const addr = try self.ptrArithmetic(.ptr_add, base_bind, index_bind, slice_ty, index_ty, null);
2454 break :result addr;
2455 };
2456 return self.finishAir(inst, result, .{ extra.lhs, extra.rhs, .none });
2457}
2458
2459fn arrayElemVal(
2460 self: *Self,
2461 array_bind: ReadArg.Bind,
2462 index_bind: ReadArg.Bind,
2463 array_ty: Type,
2464 maybe_inst: ?Air.Inst.Index,
2465) InnerError!MCValue {
2466 const pt = self.pt;
2467 const zcu = pt.zcu;
2468 const elem_ty = array_ty.childType(zcu);
2469
2470 const mcv = try array_bind.resolveToMcv(self);
2471 switch (mcv) {
2472 .stack_offset,
2473 .memory,
2474 .stack_argument_offset,
2475 => {
2476 const ptr_to_mcv = switch (mcv) {
2477 .stack_offset => |off| MCValue{ .ptr_stack_offset = off },
2478 .memory => |addr| MCValue{ .immediate = @intCast(addr) },
2479 .stack_argument_offset => |off| blk: {
2480 const reg = try self.register_manager.allocReg(null, gp);
2481
2482 _ = try self.addInst(.{
2483 .tag = .ldr_ptr_stack_argument,
2484 .data = .{ .r_stack_offset = .{
2485 .rt = reg,
2486 .stack_offset = off,
2487 } },
2488 });
2489
2490 break :blk MCValue{ .register = reg };
2491 },
2492 else => unreachable,
2493 };
2494 const ptr_to_mcv_lock: ?RegisterLock = switch (ptr_to_mcv) {
2495 .register => |reg| self.register_manager.lockRegAssumeUnused(reg),
2496 else => null,
2497 };
2498 defer if (ptr_to_mcv_lock) |lock| self.register_manager.unlockReg(lock);
2499
2500 const base_bind: ReadArg.Bind = .{ .mcv = ptr_to_mcv };
2501
2502 const ptr_ty = try pt.singleMutPtrType(elem_ty);
2503
2504 return try self.ptrElemVal(base_bind, index_bind, ptr_ty, maybe_inst);
2505 },
2506 else => return self.fail("TODO implement array_elem_val for {}", .{mcv}),
2507 }
2508}
2509
2510fn airArrayElemVal(self: *Self, inst: Air.Inst.Index) !void {
2511 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
2512 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
2513 const array_bind: ReadArg.Bind = .{ .inst = bin_op.lhs };
2514 const index_bind: ReadArg.Bind = .{ .inst = bin_op.rhs };
2515 const array_ty = self.typeOf(bin_op.lhs);
2516
2517 break :result try self.arrayElemVal(array_bind, index_bind, array_ty, inst);
2518 };
2519 return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
2520}
2521
2522fn airPtrElemVal(self: *Self, inst: Air.Inst.Index) !void {
2523 const pt = self.pt;
2524 const zcu = pt.zcu;
2525 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
2526 const ptr_ty = self.typeOf(bin_op.lhs);
2527 const result: MCValue = if (!ptr_ty.isVolatilePtr(zcu) and self.liveness.isUnused(inst)) .dead else result: {
2528 const base_bind: ReadArg.Bind = .{ .inst = bin_op.lhs };
2529 const index_bind: ReadArg.Bind = .{ .inst = bin_op.rhs };
2530
2531 break :result try self.ptrElemVal(base_bind, index_bind, ptr_ty, inst);
2532 };
2533 return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
2534}
2535
2536fn airPtrElemPtr(self: *Self, inst: Air.Inst.Index) !void {
2537 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
2538 const extra = self.air.extraData(Air.Bin, ty_pl.payload).data;
2539 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
2540 const ptr_bind: ReadArg.Bind = .{ .inst = extra.lhs };
2541 const index_bind: ReadArg.Bind = .{ .inst = extra.rhs };
2542
2543 const ptr_ty = self.typeOf(extra.lhs);
2544 const index_ty = self.typeOf(extra.rhs);
2545
2546 const addr = try self.ptrArithmetic(.ptr_add, ptr_bind, index_bind, ptr_ty, index_ty, null);
2547 break :result addr;
2548 };
2549 return self.finishAir(inst, result, .{ extra.lhs, extra.rhs, .none });
2550}
2551
2552fn airSetUnionTag(self: *Self, inst: Air.Inst.Index) !void {
2553 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
2554 _ = bin_op;
2555 return self.fail("TODO implement airSetUnionTag for {}", .{self.target.cpu.arch});
2556 // return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
2557}
2558
2559fn airGetUnionTag(self: *Self, inst: Air.Inst.Index) !void {
2560 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
2561 _ = ty_op;
2562 return self.fail("TODO implement airGetUnionTag for {}", .{self.target.cpu.arch});
2563 // return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
2564}
2565
2566fn airClz(self: *Self, inst: Air.Inst.Index) !void {
2567 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
2568 _ = ty_op;
2569 return self.fail("TODO implement airClz for {}", .{self.target.cpu.arch});
2570 // return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
2571}
2572
2573fn airCtz(self: *Self, inst: Air.Inst.Index) !void {
2574 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
2575 _ = ty_op;
2576 return self.fail("TODO implement airCtz for {}", .{self.target.cpu.arch});
2577 // return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
2578}
2579
2580fn airPopcount(self: *Self, inst: Air.Inst.Index) !void {
2581 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
2582 _ = ty_op;
2583 return self.fail("TODO implement airPopcount for {}", .{self.target.cpu.arch});
2584 // return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
2585}
2586
2587fn airAbs(self: *Self, inst: Air.Inst.Index) !void {
2588 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
2589 _ = ty_op;
2590 return self.fail("TODO implement airAbs for {}", .{self.target.cpu.arch});
2591 // return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
2592}
2593
2594fn airByteSwap(self: *Self, inst: Air.Inst.Index) !void {
2595 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
2596 _ = ty_op;
2597 return self.fail("TODO implement airByteSwap for {}", .{self.target.cpu.arch});
2598 // return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
2599}
2600
2601fn airBitReverse(self: *Self, inst: Air.Inst.Index) !void {
2602 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
2603 _ = ty_op;
2604 return self.fail("TODO implement airBitReverse for {}", .{self.target.cpu.arch});
2605 // return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
2606}
2607
2608fn airUnaryMath(self: *Self, inst: Air.Inst.Index) !void {
2609 const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
2610 const result: MCValue = if (self.liveness.isUnused(inst))
2611 .dead
2612 else
2613 return self.fail("TODO implement airUnaryMath for {}", .{self.target.cpu.arch});
2614 return self.finishAir(inst, result, .{ un_op, .none, .none });
2615}
2616
2617fn reuseOperand(
2618 self: *Self,
2619 inst: Air.Inst.Index,
2620 operand: Air.Inst.Ref,
2621 op_index: Air.Liveness.OperandInt,
2622 mcv: MCValue,
2623) bool {
2624 if (!self.liveness.operandDies(inst, op_index))
2625 return false;
2626
2627 switch (mcv) {
2628 .register => |reg| {
2629 // We assert that this register is allocatable by asking
2630 // for its index
2631 const index = RegisterManager.indexOfRegIntoTracked(reg).?; // see note above
2632 if (!self.register_manager.isRegFree(reg)) {
2633 self.register_manager.registers[index] = inst;
2634 }
2635
2636 log.debug("%{d} => {} (reused)", .{ inst, reg });
2637 },
2638 .stack_offset => |off| {
2639 log.debug("%{d} => stack offset {d} (reused)", .{ inst, off });
2640 },
2641 .cpsr_flags => {
2642 log.debug("%{d} => cpsr_flags (reused)", .{inst});
2643 },
2644 else => return false,
2645 }
2646
2647 // Prevent the operand deaths processing code from deallocating it.
2648 self.reused_operands.set(op_index);
2649
2650 // That makes us responsible for doing the rest of the stuff that processDeath would have done.
2651 const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
2652 branch.inst_table.putAssumeCapacity(operand.toIndex().?, .dead);
2653
2654 return true;
2655}
2656
2657fn load(self: *Self, dst_mcv: MCValue, ptr: MCValue, ptr_ty: Type) InnerError!void {
2658 const pt = self.pt;
2659 const zcu = pt.zcu;
2660 const elem_ty = ptr_ty.childType(zcu);
2661 const elem_size: u32 = @intCast(elem_ty.abiSize(zcu));
2662
2663 switch (ptr) {
2664 .none => unreachable,
2665 .undef => unreachable,
2666 .unreach => unreachable,
2667 .dead => unreachable,
2668 .cpsr_flags,
2669 .register_c_flag,
2670 .register_v_flag,
2671 => unreachable, // cannot hold an address
2672 .immediate => |imm| {
2673 try self.setRegOrMem(elem_ty, dst_mcv, .{ .memory = imm });
2674 },
2675 .ptr_stack_offset => |off| {
2676 try self.setRegOrMem(elem_ty, dst_mcv, .{ .stack_offset = off });
2677 },
2678 .register => |reg| {
2679 const reg_lock = self.register_manager.lockReg(reg);
2680 defer if (reg_lock) |reg_locked| self.register_manager.unlockReg(reg_locked);
2681
2682 switch (dst_mcv) {
2683 .register => |dst_reg| {
2684 try self.genLdrRegister(dst_reg, reg, elem_ty);
2685 },
2686 .stack_offset => |off| {
2687 if (elem_size <= 4) {
2688 const tmp_reg = try self.register_manager.allocReg(null, gp);
2689 const tmp_reg_lock = self.register_manager.lockRegAssumeUnused(tmp_reg);
2690 defer self.register_manager.unlockReg(tmp_reg_lock);
2691
2692 try self.load(.{ .register = tmp_reg }, ptr, ptr_ty);
2693 try self.genSetStack(elem_ty, off, MCValue{ .register = tmp_reg });
2694 } else {
2695 // TODO optimize the register allocation
2696 const regs = try self.register_manager.allocRegs(4, .{ null, null, null, null }, gp);
2697 const regs_locks = self.register_manager.lockRegsAssumeUnused(4, regs);
2698 defer for (regs_locks) |reg_locked| {
2699 self.register_manager.unlockReg(reg_locked);
2700 };
2701
2702 const src_reg = reg;
2703 const dst_reg = regs[0];
2704 const len_reg = regs[1];
2705 const count_reg = regs[2];
2706 const tmp_reg = regs[3];
2707
2708 // sub dst_reg, fp, #off
2709 try self.genSetReg(ptr_ty, dst_reg, .{ .ptr_stack_offset = off });
2710
2711 // mov len, #elem_size
2712 try self.genSetReg(Type.usize, len_reg, .{ .immediate = elem_size });
2713
2714 // memcpy(src, dst, len)
2715 try self.genInlineMemcpy(src_reg, dst_reg, len_reg, count_reg, tmp_reg);
2716 }
2717 },
2718 else => unreachable, // attempting to load into non-register or non-stack MCValue
2719 }
2720 },
2721 .memory,
2722 .stack_offset,
2723 .stack_argument_offset,
2724 => {
2725 const reg = try self.register_manager.allocReg(null, gp);
2726 const reg_lock = self.register_manager.lockRegAssumeUnused(reg);
2727 defer self.register_manager.unlockReg(reg_lock);
2728
2729 try self.genSetReg(ptr_ty, reg, ptr);
2730 try self.load(dst_mcv, .{ .register = reg }, ptr_ty);
2731 },
2732 }
2733}
2734
2735fn airLoad(self: *Self, inst: Air.Inst.Index) !void {
2736 const pt = self.pt;
2737 const zcu = pt.zcu;
2738 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
2739 const elem_ty = self.typeOfIndex(inst);
2740 const result: MCValue = result: {
2741 if (!elem_ty.hasRuntimeBits(zcu))
2742 break :result MCValue.none;
2743
2744 const ptr = try self.resolveInst(ty_op.operand);
2745 const is_volatile = self.typeOf(ty_op.operand).isVolatilePtr(zcu);
2746 if (self.liveness.isUnused(inst) and !is_volatile)
2747 break :result MCValue.dead;
2748
2749 const dest_mcv: MCValue = blk: {
2750 const ptr_fits_dest = elem_ty.abiSize(zcu) <= 4;
2751 if (ptr_fits_dest and self.reuseOperand(inst, ty_op.operand, 0, ptr)) {
2752 // The MCValue that holds the pointer can be re-used as the value.
2753 break :blk ptr;
2754 } else {
2755 break :blk try self.allocRegOrMem(elem_ty, true, inst);
2756 }
2757 };
2758 try self.load(dest_mcv, ptr, self.typeOf(ty_op.operand));
2759
2760 break :result dest_mcv;
2761 };
2762 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
2763}
2764
2765fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type) InnerError!void {
2766 const pt = self.pt;
2767 const elem_size: u32 = @intCast(value_ty.abiSize(pt.zcu));
2768
2769 switch (ptr) {
2770 .none => unreachable,
2771 .undef => unreachable,
2772 .unreach => unreachable,
2773 .dead => unreachable,
2774 .cpsr_flags,
2775 .register_c_flag,
2776 .register_v_flag,
2777 => unreachable, // cannot hold an address
2778 .immediate => |imm| {
2779 try self.setRegOrMem(value_ty, .{ .memory = imm }, value);
2780 },
2781 .ptr_stack_offset => |off| {
2782 try self.genSetStack(value_ty, off, value);
2783 },
2784 .register => |addr_reg| {
2785 const addr_reg_lock = self.register_manager.lockReg(addr_reg);
2786 defer if (addr_reg_lock) |reg| self.register_manager.unlockReg(reg);
2787
2788 switch (value) {
2789 .dead => unreachable,
2790 .undef => {
2791 try self.genSetReg(value_ty, addr_reg, value);
2792 },
2793 .register => |value_reg| {
2794 try self.genStrRegister(value_reg, addr_reg, value_ty);
2795 },
2796 else => {
2797 if (elem_size <= 4) {
2798 const tmp_reg = try self.register_manager.allocReg(null, gp);
2799 const tmp_reg_lock = self.register_manager.lockRegAssumeUnused(tmp_reg);
2800 defer self.register_manager.unlockReg(tmp_reg_lock);
2801
2802 try self.genSetReg(value_ty, tmp_reg, value);
2803 try self.store(ptr, .{ .register = tmp_reg }, ptr_ty, value_ty);
2804 } else {
2805 const regs = try self.register_manager.allocRegs(4, .{ null, null, null, null }, gp);
2806 const regs_locks = self.register_manager.lockRegsAssumeUnused(4, regs);
2807 defer for (regs_locks) |reg| {
2808 self.register_manager.unlockReg(reg);
2809 };
2810
2811 const src_reg = regs[0];
2812 const dst_reg = addr_reg;
2813 const len_reg = regs[1];
2814 const count_reg = regs[2];
2815 const tmp_reg = regs[3];
2816
2817 switch (value) {
2818 .stack_offset => |off| {
2819 // sub src_reg, fp, #off
2820 try self.genSetReg(ptr_ty, src_reg, .{ .ptr_stack_offset = off });
2821 },
2822 .memory => |addr| try self.genSetReg(ptr_ty, src_reg, .{ .immediate = @intCast(addr) }),
2823 .stack_argument_offset => |off| {
2824 _ = try self.addInst(.{
2825 .tag = .ldr_ptr_stack_argument,
2826 .data = .{ .r_stack_offset = .{
2827 .rt = src_reg,
2828 .stack_offset = off,
2829 } },
2830 });
2831 },
2832 else => return self.fail("TODO store {} to register", .{value}),
2833 }
2834
2835 // mov len, #elem_size
2836 try self.genSetReg(Type.usize, len_reg, .{ .immediate = elem_size });
2837
2838 // memcpy(src, dst, len)
2839 try self.genInlineMemcpy(src_reg, dst_reg, len_reg, count_reg, tmp_reg);
2840 }
2841 },
2842 }
2843 },
2844 .memory,
2845 .stack_offset,
2846 .stack_argument_offset,
2847 => {
2848 const addr_reg = try self.copyToTmpRegister(ptr_ty, ptr);
2849 try self.store(.{ .register = addr_reg }, value, ptr_ty, value_ty);
2850 },
2851 }
2852}
2853
2854fn airStore(self: *Self, inst: Air.Inst.Index, safety: bool) !void {
2855 if (safety) {
2856 // TODO if the value is undef, write 0xaa bytes to dest
2857 } else {
2858 // TODO if the value is undef, don't lower this instruction
2859 }
2860 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
2861 const ptr = try self.resolveInst(bin_op.lhs);
2862 const value = try self.resolveInst(bin_op.rhs);
2863 const ptr_ty = self.typeOf(bin_op.lhs);
2864 const value_ty = self.typeOf(bin_op.rhs);
2865
2866 try self.store(ptr, value, ptr_ty, value_ty);
2867
2868 return self.finishAir(inst, .dead, .{ bin_op.lhs, bin_op.rhs, .none });
2869}
2870
2871fn airStructFieldPtr(self: *Self, inst: Air.Inst.Index) !void {
2872 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
2873 const extra = self.air.extraData(Air.StructField, ty_pl.payload).data;
2874 const result = try self.structFieldPtr(inst, extra.struct_operand, extra.field_index);
2875 return self.finishAir(inst, result, .{ extra.struct_operand, .none, .none });
2876}
2877
2878fn airStructFieldPtrIndex(self: *Self, inst: Air.Inst.Index, index: u8) !void {
2879 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
2880 const result = try self.structFieldPtr(inst, ty_op.operand, index);
2881 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
2882}
2883
2884fn structFieldPtr(self: *Self, inst: Air.Inst.Index, operand: Air.Inst.Ref, index: u32) !MCValue {
2885 return if (self.liveness.isUnused(inst)) .dead else result: {
2886 const pt = self.pt;
2887 const zcu = pt.zcu;
2888 const mcv = try self.resolveInst(operand);
2889 const ptr_ty = self.typeOf(operand);
2890 const struct_ty = ptr_ty.childType(zcu);
2891 const struct_field_offset: u32 = @intCast(struct_ty.structFieldOffset(index, zcu));
2892 switch (mcv) {
2893 .ptr_stack_offset => |off| {
2894 break :result MCValue{ .ptr_stack_offset = off - struct_field_offset };
2895 },
2896 else => {
2897 const lhs_bind: ReadArg.Bind = .{ .mcv = mcv };
2898 const rhs_bind: ReadArg.Bind = .{ .mcv = .{ .immediate = struct_field_offset } };
2899
2900 break :result try self.addSub(.add, lhs_bind, rhs_bind, Type.usize, Type.usize, null);
2901 },
2902 }
2903 };
2904}
2905
2906fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) !void {
2907 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
2908 const extra = self.air.extraData(Air.StructField, ty_pl.payload).data;
2909 const operand = extra.struct_operand;
2910 const index = extra.field_index;
2911 const pt = self.pt;
2912 const zcu = pt.zcu;
2913 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
2914 const mcv = try self.resolveInst(operand);
2915 const struct_ty = self.typeOf(operand);
2916 const struct_field_offset: u32 = @intCast(struct_ty.structFieldOffset(index, zcu));
2917 const struct_field_ty = struct_ty.fieldType(index, zcu);
2918
2919 switch (mcv) {
2920 .dead, .unreach => unreachable,
2921 .stack_argument_offset => |off| {
2922 break :result MCValue{ .stack_argument_offset = off + struct_field_offset };
2923 },
2924 .stack_offset => |off| {
2925 break :result MCValue{ .stack_offset = off - struct_field_offset };
2926 },
2927 .memory => |addr| {
2928 break :result MCValue{ .memory = addr + struct_field_offset };
2929 },
2930 .register_c_flag,
2931 .register_v_flag,
2932 => |reg| {
2933 const reg_lock = self.register_manager.lockRegAssumeUnused(reg);
2934 defer self.register_manager.unlockReg(reg_lock);
2935
2936 const field: MCValue = switch (index) {
2937 // get wrapped value: return register
2938 0 => MCValue{ .register = reg },
2939
2940 // get overflow bit: return C or V flag
2941 1 => MCValue{ .cpsr_flags = switch (mcv) {
2942 .register_c_flag => .cs,
2943 .register_v_flag => .vs,
2944 else => unreachable,
2945 } },
2946
2947 else => unreachable,
2948 };
2949
2950 if (self.reuseOperand(inst, operand, 0, field)) {
2951 break :result field;
2952 } else {
2953 // Copy to new register
2954 const dest_reg = try self.register_manager.allocReg(null, gp);
2955 try self.genSetReg(struct_field_ty, dest_reg, field);
2956
2957 break :result MCValue{ .register = dest_reg };
2958 }
2959 },
2960 .register => {
2961 var operand_reg: Register = undefined;
2962 var dest_reg: Register = undefined;
2963
2964 const read_args = [_]ReadArg{
2965 .{ .ty = struct_ty, .bind = .{ .mcv = mcv }, .class = gp, .reg = &operand_reg },
2966 };
2967 const write_args = [_]WriteArg{
2968 .{ .ty = struct_field_ty, .bind = .none, .class = gp, .reg = &dest_reg },
2969 };
2970 try self.allocRegs(
2971 &read_args,
2972 &write_args,
2973 ReuseMetadata{
2974 .corresponding_inst = inst,
2975 .operand_mapping = &.{0},
2976 },
2977 );
2978
2979 const field_bit_offset = struct_field_offset * 8;
2980 const field_bit_size: u32 = @intCast(struct_field_ty.abiSize(zcu) * 8);
2981
2982 _ = try self.addInst(.{
2983 .tag = if (struct_field_ty.isSignedInt(zcu)) Mir.Inst.Tag.sbfx else .ubfx,
2984 .data = .{ .rr_lsb_width = .{
2985 .rd = dest_reg,
2986 .rn = operand_reg,
2987 .lsb = @intCast(field_bit_offset),
2988 .width = @intCast(field_bit_size),
2989 } },
2990 });
2991
2992 break :result MCValue{ .register = dest_reg };
2993 },
2994 else => return self.fail("TODO implement codegen struct_field_val for {}", .{mcv}),
2995 }
2996 };
2997
2998 return self.finishAir(inst, result, .{ extra.struct_operand, .none, .none });
2999}
3000
3001fn airFieldParentPtr(self: *Self, inst: Air.Inst.Index) !void {
3002 const pt = self.pt;
3003 const zcu = pt.zcu;
3004 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
3005 const extra = self.air.extraData(Air.FieldParentPtr, ty_pl.payload).data;
3006 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
3007 const field_ptr = try self.resolveInst(extra.field_ptr);
3008 const struct_ty = ty_pl.ty.toType().childType(zcu);
3009
3010 if (struct_ty.zigTypeTag(zcu) == .@"union") {
3011 return self.fail("TODO implement @fieldParentPtr codegen for unions", .{});
3012 }
3013
3014 const struct_field_offset: u32 = @intCast(struct_ty.structFieldOffset(extra.field_index, zcu));
3015 switch (field_ptr) {
3016 .ptr_stack_offset => |off| {
3017 break :result MCValue{ .ptr_stack_offset = off + struct_field_offset };
3018 },
3019 else => {
3020 const lhs_bind: ReadArg.Bind = .{ .mcv = field_ptr };
3021 const rhs_bind: ReadArg.Bind = .{ .mcv = .{ .immediate = struct_field_offset } };
3022
3023 break :result try self.addSub(.sub, lhs_bind, rhs_bind, Type.usize, Type.usize, null);
3024 },
3025 }
3026 };
3027 return self.finishAir(inst, result, .{ extra.field_ptr, .none, .none });
3028}
3029
3030/// An argument to a Mir instruction which is read (and possibly also
3031/// written to) by the respective instruction
3032const ReadArg = struct {
3033 ty: Type,
3034 bind: Bind,
3035 class: RegisterManager.RegisterBitSet,
3036 reg: *Register,
3037
3038 const Bind = union(enum) {
3039 inst: Air.Inst.Ref,
3040 mcv: MCValue,
3041
3042 fn resolveToMcv(bind: Bind, function: *Self) InnerError!MCValue {
3043 return switch (bind) {
3044 .inst => |inst| try function.resolveInst(inst),
3045 .mcv => |mcv| mcv,
3046 };
3047 }
3048
3049 fn resolveToImmediate(bind: Bind, function: *Self) InnerError!?u32 {
3050 switch (bind) {
3051 .inst => |inst| {
3052 // TODO resolve independently of inst_table
3053 const mcv = try function.resolveInst(inst);
3054 switch (mcv) {
3055 .immediate => |imm| return imm,
3056 else => return null,
3057 }
3058 },
3059 .mcv => |mcv| {
3060 switch (mcv) {
3061 .immediate => |imm| return imm,
3062 else => return null,
3063 }
3064 },
3065 }
3066 }
3067 };
3068};
3069
3070/// An argument to a Mir instruction which is written to (but not read
3071/// from) by the respective instruction
3072const WriteArg = struct {
3073 ty: Type,
3074 bind: Bind,
3075 class: RegisterManager.RegisterBitSet,
3076 reg: *Register,
3077
3078 const Bind = union(enum) {
3079 reg: Register,
3080 none: void,
3081 };
3082};
3083
3084/// Holds all data necessary for enabling the potential reuse of
3085/// operand registers as destinations
3086const ReuseMetadata = struct {
3087 corresponding_inst: Air.Inst.Index,
3088
3089 /// Maps every element index of read_args to the corresponding
3090 /// index in the Air instruction
3091 ///
3092 /// When the order of read_args corresponds exactly to the order
3093 /// of the inputs of the Air instruction, this would be e.g.
3094 /// &.{ 0, 1 }. However, when the order is not the same or some
3095 /// inputs to the Air instruction are omitted (e.g. when they can
3096 /// be represented as immediates to the Mir instruction),
3097 /// operand_mapping should reflect that fact.
3098 operand_mapping: []const Air.Liveness.OperandInt,
3099};
3100
3101/// Allocate a set of registers for use as arguments for a Mir
3102/// instruction
3103///
3104/// If the Mir instruction these registers are allocated for
3105/// corresponds exactly to a single Air instruction, populate
3106/// reuse_metadata in order to enable potential reuse of an operand as
3107/// the destination (provided that that operand dies in this
3108/// instruction).
3109///
3110/// Reusing an operand register as destination is the only time two
3111/// arguments may share the same register. In all other cases,
3112/// allocRegs guarantees that a register will never be allocated to
3113/// more than one argument.
3114///
3115/// Furthermore, allocReg guarantees that all arguments which are
3116/// already bound to registers before calling allocRegs will not
3117/// change their register binding. This is done by locking these
3118/// registers.
3119fn allocRegs(
3120 self: *Self,
3121 read_args: []const ReadArg,
3122 write_args: []const WriteArg,
3123 reuse_metadata: ?ReuseMetadata,
3124) InnerError!void {
3125 // Air instructions have exactly one output
3126 assert(!(reuse_metadata != null and write_args.len != 1)); // see note above
3127
3128 // The operand mapping is a 1:1 mapping of read args to their
3129 // corresponding operand index in the Air instruction
3130 assert(!(reuse_metadata != null and reuse_metadata.?.operand_mapping.len != read_args.len)); // see note above
3131
3132 const locks = try self.gpa.alloc(?RegisterLock, read_args.len + write_args.len);
3133 defer self.gpa.free(locks);
3134 const read_locks = locks[0..read_args.len];
3135 const write_locks = locks[read_args.len..];
3136
3137 @memset(locks, null);
3138 defer for (locks) |lock| {
3139 if (lock) |locked_reg| self.register_manager.unlockReg(locked_reg);
3140 };
3141
3142 // When we reuse a read_arg as a destination, the corresponding
3143 // MCValue of the read_arg will be set to .dead. In that case, we
3144 // skip allocating this read_arg.
3145 var reused_read_arg: ?usize = null;
3146
3147 // Lock all args which are already allocated to registers
3148 for (read_args, 0..) |arg, i| {
3149 const mcv = try arg.bind.resolveToMcv(self);
3150 if (mcv == .register) {
3151 read_locks[i] = self.register_manager.lockReg(mcv.register);
3152 }
3153 }
3154
3155 for (write_args, 0..) |arg, i| {
3156 if (arg.bind == .reg) {
3157 write_locks[i] = self.register_manager.lockReg(arg.bind.reg);
3158 }
3159 }
3160
3161 // Allocate registers for all args which aren't allocated to
3162 // registers yet
3163 for (read_args, 0..) |arg, i| {
3164 const mcv = try arg.bind.resolveToMcv(self);
3165 if (mcv == .register) {
3166 arg.reg.* = mcv.register;
3167 } else {
3168 const track_inst: ?Air.Inst.Index = switch (arg.bind) {
3169 .inst => |inst| inst.toIndex().?,
3170 else => null,
3171 };
3172 arg.reg.* = try self.register_manager.allocReg(track_inst, arg.class);
3173 read_locks[i] = self.register_manager.lockReg(arg.reg.*);
3174 }
3175 }
3176
3177 if (reuse_metadata != null) {
3178 const inst = reuse_metadata.?.corresponding_inst;
3179 const operand_mapping = reuse_metadata.?.operand_mapping;
3180 const arg = write_args[0];
3181 if (arg.bind == .reg) {
3182 arg.reg.* = arg.bind.reg;
3183 } else {
3184 reuse_operand: for (read_args, 0..) |read_arg, i| {
3185 if (read_arg.bind == .inst) {
3186 const operand = read_arg.bind.inst;
3187 const mcv = try self.resolveInst(operand);
3188 if (mcv == .register and
3189 std.meta.eql(arg.class, read_arg.class) and
3190 self.reuseOperand(inst, operand, operand_mapping[i], mcv))
3191 {
3192 arg.reg.* = mcv.register;
3193 write_locks[0] = null;
3194 reused_read_arg = i;
3195 break :reuse_operand;
3196 }
3197 }
3198 } else {
3199 arg.reg.* = try self.register_manager.allocReg(inst, arg.class);
3200 write_locks[0] = self.register_manager.lockReg(arg.reg.*);
3201 }
3202 }
3203 } else {
3204 for (write_args, 0..) |arg, i| {
3205 if (arg.bind == .reg) {
3206 arg.reg.* = arg.bind.reg;
3207 } else {
3208 arg.reg.* = try self.register_manager.allocReg(null, arg.class);
3209 write_locks[i] = self.register_manager.lockReg(arg.reg.*);
3210 }
3211 }
3212 }
3213
3214 // For all read_args which need to be moved from non-register to
3215 // register, perform the move
3216 for (read_args, 0..) |arg, i| {
3217 if (reused_read_arg) |j| {
3218 // Check whether this read_arg was reused
3219 if (i == j) continue;
3220 }
3221
3222 const mcv = try arg.bind.resolveToMcv(self);
3223 if (mcv != .register) {
3224 if (arg.bind == .inst) {
3225 const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
3226 const inst = arg.bind.inst.toIndex().?;
3227
3228 // Overwrite the MCValue associated with this inst
3229 branch.inst_table.putAssumeCapacity(inst, .{ .register = arg.reg.* });
3230
3231 // If the previous MCValue occupied some space we track, we
3232 // need to make sure it is marked as free now.
3233 switch (mcv) {
3234 .cpsr_flags => {
3235 assert(self.cpsr_flags_inst.? == inst);
3236 self.cpsr_flags_inst = null;
3237 },
3238 .register => |prev_reg| {
3239 assert(!self.register_manager.isRegFree(prev_reg));
3240 self.register_manager.freeReg(prev_reg);
3241 },
3242 else => {},
3243 }
3244 }
3245
3246 try self.genSetReg(arg.ty, arg.reg.*, mcv);
3247 }
3248 }
3249}
3250
3251/// Wrapper around allocRegs and addInst tailored for specific Mir
3252/// instructions which are binary operations acting on two registers
3253///
3254/// Returns the destination register
3255fn binOpRegister(
3256 self: *Self,
3257 mir_tag: Mir.Inst.Tag,
3258 lhs_bind: ReadArg.Bind,
3259 rhs_bind: ReadArg.Bind,
3260 lhs_ty: Type,
3261 rhs_ty: Type,
3262 maybe_inst: ?Air.Inst.Index,
3263) !MCValue {
3264 var lhs_reg: Register = undefined;
3265 var rhs_reg: Register = undefined;
3266 var dest_reg: Register = undefined;
3267
3268 const read_args = [_]ReadArg{
3269 .{ .ty = lhs_ty, .bind = lhs_bind, .class = gp, .reg = &lhs_reg },
3270 .{ .ty = rhs_ty, .bind = rhs_bind, .class = gp, .reg = &rhs_reg },
3271 };
3272 const write_args = [_]WriteArg{
3273 .{ .ty = lhs_ty, .bind = .none, .class = gp, .reg = &dest_reg },
3274 };
3275 try self.allocRegs(
3276 &read_args,
3277 &write_args,
3278 if (maybe_inst) |inst| .{
3279 .corresponding_inst = inst,
3280 .operand_mapping = &.{ 0, 1 },
3281 } else null,
3282 );
3283
3284 const mir_data: Mir.Inst.Data = switch (mir_tag) {
3285 .add,
3286 .adds,
3287 .sub,
3288 .subs,
3289 .@"and",
3290 .orr,
3291 .eor,
3292 => .{ .rr_op = .{
3293 .rd = dest_reg,
3294 .rn = lhs_reg,
3295 .op = Instruction.Operand.reg(rhs_reg, Instruction.Operand.Shift.none),
3296 } },
3297 .lsl,
3298 .asr,
3299 .lsr,
3300 => .{ .rr_shift = .{
3301 .rd = dest_reg,
3302 .rm = lhs_reg,
3303 .shift_amount = Instruction.ShiftAmount.reg(rhs_reg),
3304 } },
3305 .mul,
3306 .smulbb,
3307 => .{ .rrr = .{
3308 .rd = dest_reg,
3309 .rn = lhs_reg,
3310 .rm = rhs_reg,
3311 } },
3312 else => unreachable,
3313 };
3314
3315 _ = try self.addInst(.{
3316 .tag = mir_tag,
3317 .data = mir_data,
3318 });
3319
3320 return MCValue{ .register = dest_reg };
3321}
3322
3323/// Wrapper around allocRegs and addInst tailored for specific Mir
3324/// instructions which are binary operations acting on a register and
3325/// an immediate
3326///
3327/// Returns the destination register
3328fn binOpImmediate(
3329 self: *Self,
3330 mir_tag: Mir.Inst.Tag,
3331 lhs_bind: ReadArg.Bind,
3332 rhs_immediate: u32,
3333 lhs_ty: Type,
3334 lhs_and_rhs_swapped: bool,
3335 maybe_inst: ?Air.Inst.Index,
3336) !MCValue {
3337 var lhs_reg: Register = undefined;
3338 var dest_reg: Register = undefined;
3339
3340 const read_args = [_]ReadArg{
3341 .{ .ty = lhs_ty, .bind = lhs_bind, .class = gp, .reg = &lhs_reg },
3342 };
3343 const write_args = [_]WriteArg{
3344 .{ .ty = lhs_ty, .bind = .none, .class = gp, .reg = &dest_reg },
3345 };
3346 const operand_mapping: []const Air.Liveness.OperandInt = if (lhs_and_rhs_swapped) &.{1} else &.{0};
3347 try self.allocRegs(
3348 &read_args,
3349 &write_args,
3350 if (maybe_inst) |inst| .{
3351 .corresponding_inst = inst,
3352 .operand_mapping = operand_mapping,
3353 } else null,
3354 );
3355
3356 const mir_data: Mir.Inst.Data = switch (mir_tag) {
3357 .add,
3358 .adds,
3359 .sub,
3360 .subs,
3361 .@"and",
3362 .orr,
3363 .eor,
3364 => .{ .rr_op = .{
3365 .rd = dest_reg,
3366 .rn = lhs_reg,
3367 .op = Instruction.Operand.fromU32(rhs_immediate).?,
3368 } },
3369 .lsl,
3370 .asr,
3371 .lsr,
3372 => .{ .rr_shift = .{
3373 .rd = dest_reg,
3374 .rm = lhs_reg,
3375 .shift_amount = Instruction.ShiftAmount.imm(@intCast(rhs_immediate)),
3376 } },
3377 else => unreachable,
3378 };
3379
3380 _ = try self.addInst(.{
3381 .tag = mir_tag,
3382 .data = mir_data,
3383 });
3384
3385 return MCValue{ .register = dest_reg };
3386}
3387
3388fn addSub(
3389 self: *Self,
3390 tag: Air.Inst.Tag,
3391 lhs_bind: ReadArg.Bind,
3392 rhs_bind: ReadArg.Bind,
3393 lhs_ty: Type,
3394 rhs_ty: Type,
3395 maybe_inst: ?Air.Inst.Index,
3396) InnerError!MCValue {
3397 const pt = self.pt;
3398 const zcu = pt.zcu;
3399 switch (lhs_ty.zigTypeTag(zcu)) {
3400 .float => return self.fail("TODO ARM binary operations on floats", .{}),
3401 .vector => return self.fail("TODO ARM binary operations on vectors", .{}),
3402 .int => {
3403 assert(lhs_ty.eql(rhs_ty, zcu));
3404 const int_info = lhs_ty.intInfo(zcu);
3405 if (int_info.bits <= 32) {
3406 const lhs_immediate = try lhs_bind.resolveToImmediate(self);
3407 const rhs_immediate = try rhs_bind.resolveToImmediate(self);
3408
3409 // Only say yes if the operation is
3410 // commutative, i.e. we can swap both of the
3411 // operands
3412 const lhs_immediate_ok = switch (tag) {
3413 .add => if (lhs_immediate) |imm| Instruction.Operand.fromU32(imm) != null else false,
3414 .sub => false,
3415 else => unreachable,
3416 };
3417 const rhs_immediate_ok = switch (tag) {
3418 .add,
3419 .sub,
3420 => if (rhs_immediate) |imm| Instruction.Operand.fromU32(imm) != null else false,
3421 else => unreachable,
3422 };
3423
3424 const mir_tag: Mir.Inst.Tag = switch (tag) {
3425 .add => .add,
3426 .sub => .sub,
3427 else => unreachable,
3428 };
3429
3430 if (rhs_immediate_ok) {
3431 return try self.binOpImmediate(mir_tag, lhs_bind, rhs_immediate.?, lhs_ty, false, maybe_inst);
3432 } else if (lhs_immediate_ok) {
3433 // swap lhs and rhs
3434 return try self.binOpImmediate(mir_tag, rhs_bind, lhs_immediate.?, rhs_ty, true, maybe_inst);
3435 } else {
3436 return try self.binOpRegister(mir_tag, lhs_bind, rhs_bind, lhs_ty, rhs_ty, maybe_inst);
3437 }
3438 } else {
3439 return self.fail("TODO ARM binary operations on integers > u32/i32", .{});
3440 }
3441 },
3442 else => unreachable,
3443 }
3444}
3445
3446fn mul(
3447 self: *Self,
3448 lhs_bind: ReadArg.Bind,
3449 rhs_bind: ReadArg.Bind,
3450 lhs_ty: Type,
3451 rhs_ty: Type,
3452 maybe_inst: ?Air.Inst.Index,
3453) InnerError!MCValue {
3454 const pt = self.pt;
3455 const zcu = pt.zcu;
3456 switch (lhs_ty.zigTypeTag(zcu)) {
3457 .float => return self.fail("TODO ARM binary operations on floats", .{}),
3458 .vector => return self.fail("TODO ARM binary operations on vectors", .{}),
3459 .int => {
3460 assert(lhs_ty.eql(rhs_ty, zcu));
3461 const int_info = lhs_ty.intInfo(zcu);
3462 if (int_info.bits <= 32) {
3463 // TODO add optimisations for multiplication
3464 // with immediates, for example a * 2 can be
3465 // lowered to a << 1
3466 return try self.binOpRegister(.mul, lhs_bind, rhs_bind, lhs_ty, rhs_ty, maybe_inst);
3467 } else {
3468 return self.fail("TODO ARM binary operations on integers > u32/i32", .{});
3469 }
3470 },
3471 else => unreachable,
3472 }
3473}
3474
3475fn divFloat(
3476 self: *Self,
3477 lhs_bind: ReadArg.Bind,
3478 rhs_bind: ReadArg.Bind,
3479 lhs_ty: Type,
3480 rhs_ty: Type,
3481 maybe_inst: ?Air.Inst.Index,
3482) InnerError!MCValue {
3483 _ = lhs_bind;
3484 _ = rhs_bind;
3485 _ = rhs_ty;
3486 _ = maybe_inst;
3487
3488 const pt = self.pt;
3489 const zcu = pt.zcu;
3490 switch (lhs_ty.zigTypeTag(zcu)) {
3491 .float => return self.fail("TODO ARM binary operations on floats", .{}),
3492 .vector => return self.fail("TODO ARM binary operations on vectors", .{}),
3493 else => unreachable,
3494 }
3495}
3496
3497fn divTrunc(
3498 self: *Self,
3499 lhs_bind: ReadArg.Bind,
3500 rhs_bind: ReadArg.Bind,
3501 lhs_ty: Type,
3502 rhs_ty: Type,
3503 maybe_inst: ?Air.Inst.Index,
3504) InnerError!MCValue {
3505 const pt = self.pt;
3506 const zcu = pt.zcu;
3507 switch (lhs_ty.zigTypeTag(zcu)) {
3508 .float => return self.fail("TODO ARM binary operations on floats", .{}),
3509 .vector => return self.fail("TODO ARM binary operations on vectors", .{}),
3510 .int => {
3511 assert(lhs_ty.eql(rhs_ty, zcu));
3512 const int_info = lhs_ty.intInfo(zcu);
3513 if (int_info.bits <= 32) {
3514 switch (int_info.signedness) {
3515 .signed => {
3516 return self.fail("TODO ARM signed integer division", .{});
3517 },
3518 .unsigned => {
3519 const rhs_immediate = try rhs_bind.resolveToImmediate(self);
3520
3521 if (rhs_immediate) |imm| {
3522 if (std.math.isPowerOfTwo(imm)) {
3523 const shift = std.math.log2_int(u32, imm);
3524 return try self.binOpImmediate(.lsr, lhs_bind, shift, lhs_ty, false, maybe_inst);
3525 } else {
3526 return self.fail("TODO ARM integer division by constants", .{});
3527 }
3528 } else {
3529 return self.fail("TODO ARM integer division", .{});
3530 }
3531 },
3532 }
3533 } else {
3534 return self.fail("TODO ARM integer division for integers > u32/i32", .{});
3535 }
3536 },
3537 else => unreachable,
3538 }
3539}
3540
3541fn divFloor(
3542 self: *Self,
3543 lhs_bind: ReadArg.Bind,
3544 rhs_bind: ReadArg.Bind,
3545 lhs_ty: Type,
3546 rhs_ty: Type,
3547 maybe_inst: ?Air.Inst.Index,
3548) InnerError!MCValue {
3549 const pt = self.pt;
3550 const zcu = pt.zcu;
3551 switch (lhs_ty.zigTypeTag(zcu)) {
3552 .float => return self.fail("TODO ARM binary operations on floats", .{}),
3553 .vector => return self.fail("TODO ARM binary operations on vectors", .{}),
3554 .int => {
3555 assert(lhs_ty.eql(rhs_ty, zcu));
3556 const int_info = lhs_ty.intInfo(zcu);
3557 if (int_info.bits <= 32) {
3558 switch (int_info.signedness) {
3559 .signed => {
3560 return self.fail("TODO ARM signed integer division", .{});
3561 },
3562 .unsigned => {
3563 const rhs_immediate = try rhs_bind.resolveToImmediate(self);
3564
3565 if (rhs_immediate) |imm| {
3566 if (std.math.isPowerOfTwo(imm)) {
3567 const shift = std.math.log2_int(u32, imm);
3568 return try self.binOpImmediate(.lsr, lhs_bind, shift, lhs_ty, false, maybe_inst);
3569 } else {
3570 return self.fail("TODO ARM integer division by constants", .{});
3571 }
3572 } else {
3573 return self.fail("TODO ARM integer division", .{});
3574 }
3575 },
3576 }
3577 } else {
3578 return self.fail("TODO ARM integer division for integers > u32/i32", .{});
3579 }
3580 },
3581 else => unreachable,
3582 }
3583}
3584
3585fn divExact(
3586 self: *Self,
3587 lhs_bind: ReadArg.Bind,
3588 rhs_bind: ReadArg.Bind,
3589 lhs_ty: Type,
3590 rhs_ty: Type,
3591 maybe_inst: ?Air.Inst.Index,
3592) InnerError!MCValue {
3593 _ = lhs_bind;
3594 _ = rhs_bind;
3595 _ = rhs_ty;
3596 _ = maybe_inst;
3597
3598 const pt = self.pt;
3599 const zcu = pt.zcu;
3600 switch (lhs_ty.zigTypeTag(zcu)) {
3601 .float => return self.fail("TODO ARM binary operations on floats", .{}),
3602 .vector => return self.fail("TODO ARM binary operations on vectors", .{}),
3603 .int => return self.fail("TODO ARM div_exact", .{}),
3604 else => unreachable,
3605 }
3606}
3607
3608fn rem(
3609 self: *Self,
3610 lhs_bind: ReadArg.Bind,
3611 rhs_bind: ReadArg.Bind,
3612 lhs_ty: Type,
3613 rhs_ty: Type,
3614 maybe_inst: ?Air.Inst.Index,
3615) InnerError!MCValue {
3616 const pt = self.pt;
3617 const zcu = pt.zcu;
3618 switch (lhs_ty.zigTypeTag(zcu)) {
3619 .float => return self.fail("TODO ARM binary operations on floats", .{}),
3620 .vector => return self.fail("TODO ARM binary operations on vectors", .{}),
3621 .int => {
3622 assert(lhs_ty.eql(rhs_ty, zcu));
3623 const int_info = lhs_ty.intInfo(zcu);
3624 if (int_info.bits <= 32) {
3625 switch (int_info.signedness) {
3626 .signed => {
3627 return self.fail("TODO ARM signed integer zcu", .{});
3628 },
3629 .unsigned => {
3630 const rhs_immediate = try rhs_bind.resolveToImmediate(self);
3631
3632 if (rhs_immediate) |imm| {
3633 if (std.math.isPowerOfTwo(imm)) {
3634 const log2 = std.math.log2_int(u32, imm);
3635
3636 var lhs_reg: Register = undefined;
3637 var dest_reg: Register = undefined;
3638
3639 const read_args = [_]ReadArg{
3640 .{ .ty = lhs_ty, .bind = lhs_bind, .class = gp, .reg = &lhs_reg },
3641 };
3642 const write_args = [_]WriteArg{
3643 .{ .ty = lhs_ty, .bind = .none, .class = gp, .reg = &dest_reg },
3644 };
3645 try self.allocRegs(
3646 &read_args,
3647 &write_args,
3648 if (maybe_inst) |inst| .{
3649 .corresponding_inst = inst,
3650 .operand_mapping = &.{0},
3651 } else null,
3652 );
3653
3654 try self.truncRegister(lhs_reg, dest_reg, int_info.signedness, log2);
3655
3656 return MCValue{ .register = dest_reg };
3657 } else {
3658 return self.fail("TODO ARM integer zcu by constants", .{});
3659 }
3660 } else {
3661 return self.fail("TODO ARM integer zcu", .{});
3662 }
3663 },
3664 }
3665 } else {
3666 return self.fail("TODO ARM integer division for integers > u32/i32", .{});
3667 }
3668 },
3669 else => unreachable,
3670 }
3671}
3672
3673fn modulo(
3674 self: *Self,
3675 lhs_bind: ReadArg.Bind,
3676 rhs_bind: ReadArg.Bind,
3677 lhs_ty: Type,
3678 rhs_ty: Type,
3679 maybe_inst: ?Air.Inst.Index,
3680) InnerError!MCValue {
3681 _ = lhs_bind;
3682 _ = rhs_bind;
3683 _ = rhs_ty;
3684 _ = maybe_inst;
3685
3686 const pt = self.pt;
3687 const zcu = pt.zcu;
3688 switch (lhs_ty.zigTypeTag(zcu)) {
3689 .float => return self.fail("TODO ARM binary operations on floats", .{}),
3690 .vector => return self.fail("TODO ARM binary operations on vectors", .{}),
3691 .int => return self.fail("TODO ARM zcu", .{}),
3692 else => unreachable,
3693 }
3694}
3695
3696fn wrappingArithmetic(
3697 self: *Self,
3698 tag: Air.Inst.Tag,
3699 lhs_bind: ReadArg.Bind,
3700 rhs_bind: ReadArg.Bind,
3701 lhs_ty: Type,
3702 rhs_ty: Type,
3703 maybe_inst: ?Air.Inst.Index,
3704) InnerError!MCValue {
3705 const pt = self.pt;
3706 const zcu = pt.zcu;
3707 switch (lhs_ty.zigTypeTag(zcu)) {
3708 .vector => return self.fail("TODO ARM binary operations on vectors", .{}),
3709 .int => {
3710 const int_info = lhs_ty.intInfo(zcu);
3711 if (int_info.bits <= 32) {
3712 // Generate an add/sub/mul
3713 const result: MCValue = switch (tag) {
3714 .add_wrap => try self.addSub(.add, lhs_bind, rhs_bind, lhs_ty, rhs_ty, maybe_inst),
3715 .sub_wrap => try self.addSub(.sub, lhs_bind, rhs_bind, lhs_ty, rhs_ty, maybe_inst),
3716 .mul_wrap => try self.mul(lhs_bind, rhs_bind, lhs_ty, rhs_ty, maybe_inst),
3717 else => unreachable,
3718 };
3719
3720 // Truncate if necessary
3721 const result_reg = result.register;
3722 if (int_info.bits < 32) {
3723 try self.truncRegister(result_reg, result_reg, int_info.signedness, int_info.bits);
3724 }
3725
3726 return result;
3727 } else {
3728 return self.fail("TODO ARM binary operations on integers > u32/i32", .{});
3729 }
3730 },
3731 else => unreachable,
3732 }
3733}
3734
3735fn bitwise(
3736 self: *Self,
3737 tag: Air.Inst.Tag,
3738 lhs_bind: ReadArg.Bind,
3739 rhs_bind: ReadArg.Bind,
3740 lhs_ty: Type,
3741 rhs_ty: Type,
3742 maybe_inst: ?Air.Inst.Index,
3743) InnerError!MCValue {
3744 const pt = self.pt;
3745 const zcu = pt.zcu;
3746 switch (lhs_ty.zigTypeTag(zcu)) {
3747 .vector => return self.fail("TODO ARM binary operations on vectors", .{}),
3748 .int => {
3749 assert(lhs_ty.eql(rhs_ty, zcu));
3750 const int_info = lhs_ty.intInfo(zcu);
3751 if (int_info.bits <= 32) {
3752 const lhs_immediate = try lhs_bind.resolveToImmediate(self);
3753 const rhs_immediate = try rhs_bind.resolveToImmediate(self);
3754
3755 const lhs_immediate_ok = if (lhs_immediate) |imm| Instruction.Operand.fromU32(imm) != null else false;
3756 const rhs_immediate_ok = if (rhs_immediate) |imm| Instruction.Operand.fromU32(imm) != null else false;
3757
3758 const mir_tag: Mir.Inst.Tag = switch (tag) {
3759 .bit_and => .@"and",
3760 .bit_or => .orr,
3761 .xor => .eor,
3762 else => unreachable,
3763 };
3764
3765 if (rhs_immediate_ok) {
3766 return try self.binOpImmediate(mir_tag, lhs_bind, rhs_immediate.?, lhs_ty, false, maybe_inst);
3767 } else if (lhs_immediate_ok) {
3768 // swap lhs and rhs
3769 return try self.binOpImmediate(mir_tag, rhs_bind, lhs_immediate.?, rhs_ty, true, maybe_inst);
3770 } else {
3771 return try self.binOpRegister(mir_tag, lhs_bind, rhs_bind, lhs_ty, rhs_ty, maybe_inst);
3772 }
3773 } else {
3774 return self.fail("TODO ARM binary operations on integers > u32/i32", .{});
3775 }
3776 },
3777 else => unreachable,
3778 }
3779}
3780
3781fn shiftExact(
3782 self: *Self,
3783 tag: Air.Inst.Tag,
3784 lhs_bind: ReadArg.Bind,
3785 rhs_bind: ReadArg.Bind,
3786 lhs_ty: Type,
3787 rhs_ty: Type,
3788 maybe_inst: ?Air.Inst.Index,
3789) InnerError!MCValue {
3790 const pt = self.pt;
3791 const zcu = pt.zcu;
3792 switch (lhs_ty.zigTypeTag(zcu)) {
3793 .vector => if (!rhs_ty.isVector(zcu))
3794 return self.fail("TODO ARM vector shift with scalar rhs", .{})
3795 else
3796 return self.fail("TODO ARM binary operations on vectors", .{}),
3797 .int => {
3798 const int_info = lhs_ty.intInfo(zcu);
3799 if (int_info.bits <= 32) {
3800 const rhs_immediate = try rhs_bind.resolveToImmediate(self);
3801
3802 const mir_tag: Mir.Inst.Tag = switch (tag) {
3803 .shl_exact => .lsl,
3804 .shr_exact => switch (lhs_ty.intInfo(zcu).signedness) {
3805 .signed => Mir.Inst.Tag.asr,
3806 .unsigned => Mir.Inst.Tag.lsr,
3807 },
3808 else => unreachable,
3809 };
3810
3811 if (rhs_immediate) |imm| {
3812 return try self.binOpImmediate(mir_tag, lhs_bind, imm, lhs_ty, false, maybe_inst);
3813 } else {
3814 return try self.binOpRegister(mir_tag, lhs_bind, rhs_bind, lhs_ty, rhs_ty, maybe_inst);
3815 }
3816 } else {
3817 return self.fail("TODO ARM binary operations on integers > u32/i32", .{});
3818 }
3819 },
3820 else => unreachable,
3821 }
3822}
3823
3824fn shiftNormal(
3825 self: *Self,
3826 tag: Air.Inst.Tag,
3827 lhs_bind: ReadArg.Bind,
3828 rhs_bind: ReadArg.Bind,
3829 lhs_ty: Type,
3830 rhs_ty: Type,
3831 maybe_inst: ?Air.Inst.Index,
3832) InnerError!MCValue {
3833 const pt = self.pt;
3834 const zcu = pt.zcu;
3835 switch (lhs_ty.zigTypeTag(zcu)) {
3836 .vector => if (!rhs_ty.isVector(zcu))
3837 return self.fail("TODO ARM vector shift with scalar rhs", .{})
3838 else
3839 return self.fail("TODO ARM binary operations on vectors", .{}),
3840 .int => {
3841 const int_info = lhs_ty.intInfo(zcu);
3842 if (int_info.bits <= 32) {
3843 // Generate a shl_exact/shr_exact
3844 const result: MCValue = switch (tag) {
3845 .shl => try self.shiftExact(.shl_exact, lhs_bind, rhs_bind, lhs_ty, rhs_ty, maybe_inst),
3846 .shr => try self.shiftExact(.shr_exact, lhs_bind, rhs_bind, lhs_ty, rhs_ty, maybe_inst),
3847 else => unreachable,
3848 };
3849
3850 // Truncate if necessary
3851 switch (tag) {
3852 .shr => return result,
3853 .shl => {
3854 const result_reg = result.register;
3855 if (int_info.bits < 32) {
3856 try self.truncRegister(result_reg, result_reg, int_info.signedness, int_info.bits);
3857 }
3858
3859 return result;
3860 },
3861 else => unreachable,
3862 }
3863 } else {
3864 return self.fail("TODO ARM binary operations on integers > u32/i32", .{});
3865 }
3866 },
3867 else => unreachable,
3868 }
3869}
3870
3871fn booleanOp(
3872 self: *Self,
3873 tag: Air.Inst.Tag,
3874 lhs_bind: ReadArg.Bind,
3875 rhs_bind: ReadArg.Bind,
3876 lhs_ty: Type,
3877 rhs_ty: Type,
3878 maybe_inst: ?Air.Inst.Index,
3879) InnerError!MCValue {
3880 const pt = self.pt;
3881 const zcu = pt.zcu;
3882 switch (lhs_ty.zigTypeTag(zcu)) {
3883 .bool => {
3884 const lhs_immediate = try lhs_bind.resolveToImmediate(self);
3885 const rhs_immediate = try rhs_bind.resolveToImmediate(self);
3886
3887 const mir_tag: Mir.Inst.Tag = switch (tag) {
3888 .bool_and => .@"and",
3889 .bool_or => .orr,
3890 else => unreachable,
3891 };
3892
3893 if (rhs_immediate) |imm| {
3894 return try self.binOpImmediate(mir_tag, lhs_bind, imm, lhs_ty, false, maybe_inst);
3895 } else if (lhs_immediate) |imm| {
3896 // swap lhs and rhs
3897 return try self.binOpImmediate(mir_tag, rhs_bind, imm, rhs_ty, true, maybe_inst);
3898 } else {
3899 return try self.binOpRegister(mir_tag, lhs_bind, rhs_bind, lhs_ty, rhs_ty, maybe_inst);
3900 }
3901 },
3902 else => unreachable,
3903 }
3904}
3905
3906fn ptrArithmetic(
3907 self: *Self,
3908 tag: Air.Inst.Tag,
3909 lhs_bind: ReadArg.Bind,
3910 rhs_bind: ReadArg.Bind,
3911 lhs_ty: Type,
3912 rhs_ty: Type,
3913 maybe_inst: ?Air.Inst.Index,
3914) InnerError!MCValue {
3915 const pt = self.pt;
3916 const zcu = pt.zcu;
3917 switch (lhs_ty.zigTypeTag(zcu)) {
3918 .pointer => {
3919 assert(rhs_ty.eql(Type.usize, zcu));
3920
3921 const ptr_ty = lhs_ty;
3922 const elem_ty = switch (ptr_ty.ptrSize(zcu)) {
3923 .one => ptr_ty.childType(zcu).childType(zcu), // ptr to array, so get array element type
3924 else => ptr_ty.childType(zcu),
3925 };
3926 const elem_size: u32 = @intCast(elem_ty.abiSize(zcu));
3927
3928 const base_tag: Air.Inst.Tag = switch (tag) {
3929 .ptr_add => .add,
3930 .ptr_sub => .sub,
3931 else => unreachable,
3932 };
3933
3934 if (elem_size == 1) {
3935 return try self.addSub(base_tag, lhs_bind, rhs_bind, Type.usize, Type.usize, maybe_inst);
3936 } else {
3937 // convert the offset into a byte offset by
3938 // multiplying it with elem_size
3939 const imm_bind = ReadArg.Bind{ .mcv = .{ .immediate = elem_size } };
3940
3941 const offset = try self.mul(rhs_bind, imm_bind, Type.usize, Type.usize, null);
3942 const offset_bind = ReadArg.Bind{ .mcv = offset };
3943
3944 const addr = try self.addSub(base_tag, lhs_bind, offset_bind, Type.usize, Type.usize, null);
3945 return addr;
3946 }
3947 },
3948 else => unreachable,
3949 }
3950}
3951
3952fn genLdrRegister(self: *Self, dest_reg: Register, addr_reg: Register, ty: Type) !void {
3953 const pt = self.pt;
3954 const zcu = pt.zcu;
3955 const abi_size = ty.abiSize(zcu);
3956
3957 const tag: Mir.Inst.Tag = switch (abi_size) {
3958 1 => if (ty.isSignedInt(zcu)) Mir.Inst.Tag.ldrsb else .ldrb,
3959 2 => if (ty.isSignedInt(zcu)) Mir.Inst.Tag.ldrsh else .ldrh,
3960 3, 4 => .ldr,
3961 else => unreachable,
3962 };
3963
3964 const rr_offset: Mir.Inst.Data = .{ .rr_offset = .{
3965 .rt = dest_reg,
3966 .rn = addr_reg,
3967 .offset = .{ .offset = Instruction.Offset.none },
3968 } };
3969 const rr_extra_offset: Mir.Inst.Data = .{ .rr_extra_offset = .{
3970 .rt = dest_reg,
3971 .rn = addr_reg,
3972 .offset = .{ .offset = Instruction.ExtraLoadStoreOffset.none },
3973 } };
3974
3975 const data: Mir.Inst.Data = switch (abi_size) {
3976 1 => if (ty.isSignedInt(zcu)) rr_extra_offset else rr_offset,
3977 2 => rr_extra_offset,
3978 3, 4 => rr_offset,
3979 else => unreachable,
3980 };
3981
3982 _ = try self.addInst(.{
3983 .tag = tag,
3984 .data = data,
3985 });
3986}
3987
3988fn genStrRegister(self: *Self, source_reg: Register, addr_reg: Register, ty: Type) !void {
3989 const pt = self.pt;
3990 const abi_size = ty.abiSize(pt.zcu);
3991
3992 const tag: Mir.Inst.Tag = switch (abi_size) {
3993 1 => .strb,
3994 2 => .strh,
3995 4 => .str,
3996 3 => return self.fail("TODO: genStrRegister for abi_size={}", .{abi_size}),
3997 else => unreachable,
3998 };
3999
4000 const rr_offset: Mir.Inst.Data = .{ .rr_offset = .{
4001 .rt = source_reg,
4002 .rn = addr_reg,
4003 .offset = .{ .offset = Instruction.Offset.none },
4004 } };
4005 const rr_extra_offset: Mir.Inst.Data = .{ .rr_extra_offset = .{
4006 .rt = source_reg,
4007 .rn = addr_reg,
4008 .offset = .{ .offset = Instruction.ExtraLoadStoreOffset.none },
4009 } };
4010
4011 const data: Mir.Inst.Data = switch (abi_size) {
4012 1, 4 => rr_offset,
4013 2 => rr_extra_offset,
4014 else => unreachable,
4015 };
4016
4017 _ = try self.addInst(.{
4018 .tag = tag,
4019 .data = data,
4020 });
4021}
4022
4023fn genInlineMemcpy(
4024 self: *Self,
4025 src: Register,
4026 dst: Register,
4027 len: Register,
4028 count: Register,
4029 tmp: Register,
4030) !void {
4031 // mov count, #0
4032 _ = try self.addInst(.{
4033 .tag = .mov,
4034 .data = .{ .r_op_mov = .{
4035 .rd = count,
4036 .op = Instruction.Operand.imm(0, 0),
4037 } },
4038 });
4039
4040 // loop:
4041 // cmp count, len
4042 _ = try self.addInst(.{
4043 .tag = .cmp,
4044 .data = .{ .r_op_cmp = .{
4045 .rn = count,
4046 .op = Instruction.Operand.reg(len, Instruction.Operand.Shift.none),
4047 } },
4048 });
4049
4050 // bge end
4051 _ = try self.addInst(.{
4052 .tag = .b,
4053 .cond = .ge,
4054 .data = .{ .inst = @intCast(self.mir_instructions.len + 5) },
4055 });
4056
4057 // ldrb tmp, [src, count]
4058 _ = try self.addInst(.{
4059 .tag = .ldrb,
4060 .data = .{ .rr_offset = .{
4061 .rt = tmp,
4062 .rn = src,
4063 .offset = .{ .offset = Instruction.Offset.reg(count, .none) },
4064 } },
4065 });
4066
4067 // strb tmp, [src, count]
4068 _ = try self.addInst(.{
4069 .tag = .strb,
4070 .data = .{ .rr_offset = .{
4071 .rt = tmp,
4072 .rn = dst,
4073 .offset = .{ .offset = Instruction.Offset.reg(count, .none) },
4074 } },
4075 });
4076
4077 // add count, count, #1
4078 _ = try self.addInst(.{
4079 .tag = .add,
4080 .data = .{ .rr_op = .{
4081 .rd = count,
4082 .rn = count,
4083 .op = Instruction.Operand.imm(1, 0),
4084 } },
4085 });
4086
4087 // b loop
4088 _ = try self.addInst(.{
4089 .tag = .b,
4090 .data = .{ .inst = @intCast(self.mir_instructions.len - 5) },
4091 });
4092
4093 // end:
4094}
4095
4096fn genInlineMemset(
4097 self: *Self,
4098 dst: MCValue,
4099 val: MCValue,
4100 len: MCValue,
4101) !void {
4102 const dst_reg = switch (dst) {
4103 .register => |r| r,
4104 else => try self.copyToTmpRegister(Type.manyptr_u8, dst),
4105 };
4106 const dst_reg_lock = self.register_manager.lockReg(dst_reg);
4107 defer if (dst_reg_lock) |lock| self.register_manager.unlockReg(lock);
4108
4109 const val_reg = switch (val) {
4110 .register => |r| r,
4111 else => try self.copyToTmpRegister(Type.u8, val),
4112 };
4113 const val_reg_lock = self.register_manager.lockReg(val_reg);
4114 defer if (val_reg_lock) |lock| self.register_manager.unlockReg(lock);
4115
4116 const len_reg = switch (len) {
4117 .register => |r| r,
4118 else => try self.copyToTmpRegister(Type.usize, len),
4119 };
4120 const len_reg_lock = self.register_manager.lockReg(len_reg);
4121 defer if (len_reg_lock) |lock| self.register_manager.unlockReg(lock);
4122
4123 const count_reg = try self.register_manager.allocReg(null, gp);
4124
4125 try self.genInlineMemsetCode(dst_reg, val_reg, len_reg, count_reg);
4126}
4127
4128fn genInlineMemsetCode(
4129 self: *Self,
4130 dst: Register,
4131 val: Register,
4132 len: Register,
4133 count: Register,
4134) !void {
4135 // mov count, #0
4136 _ = try self.addInst(.{
4137 .tag = .mov,
4138 .data = .{ .r_op_mov = .{
4139 .rd = count,
4140 .op = Instruction.Operand.imm(0, 0),
4141 } },
4142 });
4143
4144 // loop:
4145 // cmp count, len
4146 _ = try self.addInst(.{
4147 .tag = .cmp,
4148 .data = .{ .r_op_cmp = .{
4149 .rn = count,
4150 .op = Instruction.Operand.reg(len, Instruction.Operand.Shift.none),
4151 } },
4152 });
4153
4154 // bge end
4155 _ = try self.addInst(.{
4156 .tag = .b,
4157 .cond = .ge,
4158 .data = .{ .inst = @intCast(self.mir_instructions.len + 4) },
4159 });
4160
4161 // strb val, [src, count]
4162 _ = try self.addInst(.{
4163 .tag = .strb,
4164 .data = .{ .rr_offset = .{
4165 .rt = val,
4166 .rn = dst,
4167 .offset = .{ .offset = Instruction.Offset.reg(count, .none) },
4168 } },
4169 });
4170
4171 // add count, count, #1
4172 _ = try self.addInst(.{
4173 .tag = .add,
4174 .data = .{ .rr_op = .{
4175 .rd = count,
4176 .rn = count,
4177 .op = Instruction.Operand.imm(1, 0),
4178 } },
4179 });
4180
4181 // b loop
4182 _ = try self.addInst(.{
4183 .tag = .b,
4184 .data = .{ .inst = @intCast(self.mir_instructions.len - 4) },
4185 });
4186
4187 // end:
4188}
4189
4190fn airArg(self: *Self, inst: Air.Inst.Index) !void {
4191 // skip zero-bit arguments as they don't have a corresponding arg instruction
4192 var arg_index = self.arg_index;
4193 while (self.args[arg_index] == .none) arg_index += 1;
4194 self.arg_index = arg_index + 1;
4195
4196 const zcu = self.pt.zcu;
4197 const func_zir = zcu.funcInfo(self.func_index).zir_body_inst.resolveFull(&zcu.intern_pool).?;
4198 const file = zcu.fileByIndex(func_zir.file);
4199 if (!file.mod.?.strip) {
4200 const tag = self.air.instructions.items(.tag)[@intFromEnum(inst)];
4201 const arg = self.air.instructions.items(.data)[@intFromEnum(inst)].arg;
4202 const ty = self.typeOfIndex(inst);
4203 const zir = &file.zir.?;
4204 const name = zir.nullTerminatedString(zir.getParamName(zir.getParamBody(func_zir.inst)[arg.zir_param_index]).?);
4205 try self.dbg_info_relocs.append(self.gpa, .{
4206 .tag = tag,
4207 .ty = ty,
4208 .name = name,
4209 .mcv = self.args[arg_index],
4210 });
4211 }
4212
4213 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else self.args[arg_index];
4214 return self.finishAir(inst, result, .{ .none, .none, .none });
4215}
4216
4217fn airTrap(self: *Self) !void {
4218 _ = try self.addInst(.{
4219 .tag = .undefined_instruction,
4220 .data = .{ .nop = {} },
4221 });
4222 return self.finishAirBookkeeping();
4223}
4224
4225fn airBreakpoint(self: *Self) !void {
4226 _ = try self.addInst(.{
4227 .tag = .bkpt,
4228 .data = .{ .imm16 = 0 },
4229 });
4230 return self.finishAirBookkeeping();
4231}
4232
4233fn airRetAddr(self: *Self, inst: Air.Inst.Index) !void {
4234 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airRetAddr for arm", .{});
4235 return self.finishAir(inst, result, .{ .none, .none, .none });
4236}
4237
4238fn airFrameAddress(self: *Self, inst: Air.Inst.Index) !void {
4239 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airFrameAddress for arm", .{});
4240 return self.finishAir(inst, result, .{ .none, .none, .none });
4241}
4242
4243fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier) !void {
4244 if (modifier == .always_tail) return self.fail("TODO implement tail calls for arm", .{});
4245 const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
4246 const callee = pl_op.operand;
4247 const extra = self.air.extraData(Air.Call, pl_op.payload);
4248 const args: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra.end..][0..extra.data.args_len]);
4249 const ty = self.typeOf(callee);
4250 const pt = self.pt;
4251 const zcu = pt.zcu;
4252 const ip = &zcu.intern_pool;
4253
4254 const fn_ty = switch (ty.zigTypeTag(zcu)) {
4255 .@"fn" => ty,
4256 .pointer => ty.childType(zcu),
4257 else => unreachable,
4258 };
4259
4260 var info = try self.resolveCallingConventionValues(fn_ty);
4261 defer info.deinit(self);
4262
4263 // According to the Procedure Call Standard for the ARM
4264 // Architecture, compare flags are not preserved across
4265 // calls. Therefore, if some value is currently stored there, we
4266 // need to save it.
4267 try self.spillCompareFlagsIfOccupied();
4268
4269 // Save caller-saved registers, but crucially *after* we save the
4270 // compare flags as saving compare flags may require a new
4271 // caller-saved register
4272 for (caller_preserved_regs) |reg| {
4273 try self.register_manager.getReg(reg, null);
4274 }
4275
4276 // If returning by reference, r0 will contain the address of where
4277 // to put the result into. In that case, make sure that r0 remains
4278 // untouched by the parameter passing code
4279 const r0_lock: ?RegisterLock = if (info.return_value == .stack_offset) blk: {
4280 log.debug("airCall: return by reference", .{});
4281 const ret_ty = fn_ty.fnReturnType(zcu);
4282 const ret_abi_size: u32 = @intCast(ret_ty.abiSize(zcu));
4283 const ret_abi_align = ret_ty.abiAlignment(zcu);
4284 const stack_offset = try self.allocMem(ret_abi_size, ret_abi_align, inst);
4285
4286 const ptr_ty = try pt.singleMutPtrType(ret_ty);
4287 try self.register_manager.getReg(.r0, null);
4288 try self.genSetReg(ptr_ty, .r0, .{ .ptr_stack_offset = stack_offset });
4289
4290 info.return_value = .{ .stack_offset = stack_offset };
4291
4292 break :blk self.register_manager.lockRegAssumeUnused(.r0);
4293 } else null;
4294 defer if (r0_lock) |reg| self.register_manager.unlockReg(reg);
4295
4296 // Make space for the arguments passed via the stack
4297 self.max_end_stack += info.stack_byte_count;
4298
4299 for (info.args, 0..) |mc_arg, arg_i| {
4300 const arg = args[arg_i];
4301 const arg_ty = self.typeOf(arg);
4302 const arg_mcv = try self.resolveInst(args[arg_i]);
4303
4304 switch (mc_arg) {
4305 .none => continue,
4306 .register => |reg| {
4307 try self.register_manager.getReg(reg, null);
4308 try self.genSetReg(arg_ty, reg, arg_mcv);
4309 },
4310 .stack_offset => unreachable,
4311 .stack_argument_offset => |offset| try self.genSetStackArgument(
4312 arg_ty,
4313 offset,
4314 arg_mcv,
4315 ),
4316 else => unreachable,
4317 }
4318 }
4319
4320 // Due to incremental compilation, how function calls are generated depends
4321 // on linking.
4322 if (try self.air.value(callee, pt)) |func_value| switch (ip.indexToKey(func_value.toIntern())) {
4323 .func => {
4324 return self.fail("TODO implement calling functions", .{});
4325 },
4326 .@"extern" => {
4327 return self.fail("TODO implement calling extern functions", .{});
4328 },
4329 else => {
4330 return self.fail("TODO implement calling bitcasted functions", .{});
4331 },
4332 } else {
4333 assert(ty.zigTypeTag(zcu) == .pointer);
4334 const mcv = try self.resolveInst(callee);
4335
4336 try self.genSetReg(Type.usize, .lr, mcv);
4337 }
4338
4339 // TODO: add Instruction.supportedOn
4340 // function for ARM
4341 if (self.target.cpu.has(.arm, .has_v5t)) {
4342 _ = try self.addInst(.{
4343 .tag = .blx,
4344 .data = .{ .reg = .lr },
4345 });
4346 } else {
4347 return self.fail("TODO fix blx emulation for ARM <v5", .{});
4348 // _ = try self.addInst(.{
4349 // .tag = .mov,
4350 // .data = .{ .rr_op = .{
4351 // .rd = .lr,
4352 // .rn = .r0,
4353 // .op = Instruction.Operand.reg(.pc, Instruction.Operand.Shift.none),
4354 // } },
4355 // });
4356 // _ = try self.addInst(.{
4357 // .tag = .bx,
4358 // .data = .{ .reg = .lr },
4359 // });
4360 }
4361
4362 const result: MCValue = result: {
4363 switch (info.return_value) {
4364 .register => |reg| {
4365 if (RegisterManager.indexOfRegIntoTracked(reg) == null) {
4366 // Save function return value into a tracked register
4367 log.debug("airCall: copying {} as it is not tracked", .{reg});
4368 const new_reg = try self.copyToTmpRegister(fn_ty.fnReturnType(zcu), info.return_value);
4369 break :result MCValue{ .register = new_reg };
4370 }
4371 },
4372 else => {},
4373 }
4374 break :result info.return_value;
4375 };
4376
4377 if (args.len <= Air.Liveness.bpi - 2) {
4378 var buf = [1]Air.Inst.Ref{.none} ** (Air.Liveness.bpi - 1);
4379 buf[0] = callee;
4380 @memcpy(buf[1..][0..args.len], args);
4381 return self.finishAir(inst, result, buf);
4382 }
4383 var bt = try self.iterateBigTomb(inst, 1 + args.len);
4384 bt.feed(callee);
4385 for (args) |arg| {
4386 bt.feed(arg);
4387 }
4388 return bt.finishAir(result);
4389}
4390
4391fn airRet(self: *Self, inst: Air.Inst.Index) !void {
4392 const pt = self.pt;
4393 const zcu = pt.zcu;
4394 const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
4395 const operand = try self.resolveInst(un_op);
4396 const ret_ty = self.fn_type.fnReturnType(zcu);
4397
4398 switch (self.ret_mcv) {
4399 .none => {},
4400 .immediate => {
4401 assert(ret_ty.isError(zcu));
4402 },
4403 .register => |reg| {
4404 // Return result by value
4405 try self.genSetReg(ret_ty, reg, operand);
4406 },
4407 .stack_offset => {
4408 // Return result by reference
4409 //
4410 // self.ret_mcv is an address to where this function
4411 // should store its result into
4412 const ptr_ty = try pt.singleMutPtrType(ret_ty);
4413 try self.store(self.ret_mcv, operand, ptr_ty, ret_ty);
4414 },
4415 else => unreachable, // invalid return result
4416 }
4417
4418 // Just add space for an instruction, patch this later
4419 try self.exitlude_jump_relocs.append(self.gpa, try self.addNop());
4420
4421 return self.finishAir(inst, .dead, .{ un_op, .none, .none });
4422}
4423
4424fn airRetLoad(self: *Self, inst: Air.Inst.Index) !void {
4425 const pt = self.pt;
4426 const zcu = pt.zcu;
4427 const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
4428 const ptr = try self.resolveInst(un_op);
4429 const ptr_ty = self.typeOf(un_op);
4430 const ret_ty = self.fn_type.fnReturnType(zcu);
4431
4432 switch (self.ret_mcv) {
4433 .none => {},
4434 .register => {
4435 // Return result by value
4436 try self.load(self.ret_mcv, ptr, ptr_ty);
4437 },
4438 .stack_offset => {
4439 // Return result by reference
4440 //
4441 // self.ret_mcv is an address to where this function
4442 // should store its result into
4443 //
4444 // If the operand is a ret_ptr instruction, we are done
4445 // here. Else we need to load the result from the location
4446 // pointed to by the operand and store it to the result
4447 // location.
4448 const op_inst = un_op.toIndex().?;
4449 if (self.air.instructions.items(.tag)[@intFromEnum(op_inst)] != .ret_ptr) {
4450 const abi_size: u32 = @intCast(ret_ty.abiSize(zcu));
4451 const abi_align = ret_ty.abiAlignment(zcu);
4452
4453 const offset = try self.allocMem(abi_size, abi_align, null);
4454
4455 const tmp_mcv = MCValue{ .stack_offset = offset };
4456 try self.load(tmp_mcv, ptr, ptr_ty);
4457 try self.store(self.ret_mcv, tmp_mcv, ptr_ty, ret_ty);
4458 }
4459 },
4460 else => unreachable, // invalid return result
4461 }
4462
4463 // Just add space for an instruction, patch this later
4464 try self.exitlude_jump_relocs.append(self.gpa, try self.addNop());
4465
4466 return self.finishAir(inst, .dead, .{ un_op, .none, .none });
4467}
4468
4469fn airCmp(self: *Self, inst: Air.Inst.Index, op: math.CompareOperator) !void {
4470 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
4471 const lhs_ty = self.typeOf(bin_op.lhs);
4472
4473 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else blk: {
4474 break :blk try self.cmp(.{ .inst = bin_op.lhs }, .{ .inst = bin_op.rhs }, lhs_ty, op);
4475 };
4476
4477 return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
4478}
4479
4480fn cmp(
4481 self: *Self,
4482 lhs: ReadArg.Bind,
4483 rhs: ReadArg.Bind,
4484 lhs_ty: Type,
4485 op: math.CompareOperator,
4486) !MCValue {
4487 const pt = self.pt;
4488 const zcu = pt.zcu;
4489 const int_ty = switch (lhs_ty.zigTypeTag(zcu)) {
4490 .optional => blk: {
4491 const payload_ty = lhs_ty.optionalChild(zcu);
4492 if (!payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
4493 break :blk Type.u1;
4494 } else if (lhs_ty.isPtrLikeOptional(zcu)) {
4495 break :blk Type.usize;
4496 } else {
4497 return self.fail("TODO ARM cmp non-pointer optionals", .{});
4498 }
4499 },
4500 .float => return self.fail("TODO ARM cmp floats", .{}),
4501 .@"enum" => lhs_ty.intTagType(zcu),
4502 .int => lhs_ty,
4503 .bool => Type.u1,
4504 .pointer => Type.usize,
4505 .error_set => Type.u16,
4506 else => unreachable,
4507 };
4508
4509 const int_info = int_ty.intInfo(zcu);
4510 if (int_info.bits <= 32) {
4511 try self.spillCompareFlagsIfOccupied();
4512
4513 var lhs_reg: Register = undefined;
4514 var rhs_reg: Register = undefined;
4515
4516 const rhs_immediate = try rhs.resolveToImmediate(self);
4517 const rhs_immediate_ok = if (rhs_immediate) |imm| Instruction.Operand.fromU32(imm) != null else false;
4518
4519 if (rhs_immediate_ok) {
4520 const read_args = [_]ReadArg{
4521 .{ .ty = int_ty, .bind = lhs, .class = gp, .reg = &lhs_reg },
4522 };
4523 try self.allocRegs(
4524 &read_args,
4525 &.{},
4526 null, // we won't be able to reuse a register as there are no write_regs
4527 );
4528
4529 _ = try self.addInst(.{
4530 .tag = .cmp,
4531 .data = .{ .r_op_cmp = .{
4532 .rn = lhs_reg,
4533 .op = Instruction.Operand.fromU32(rhs_immediate.?).?,
4534 } },
4535 });
4536 } else {
4537 const read_args = [_]ReadArg{
4538 .{ .ty = int_ty, .bind = lhs, .class = gp, .reg = &lhs_reg },
4539 .{ .ty = int_ty, .bind = rhs, .class = gp, .reg = &rhs_reg },
4540 };
4541 try self.allocRegs(
4542 &read_args,
4543 &.{},
4544 null, // we won't be able to reuse a register as there are no write_regs
4545 );
4546
4547 _ = try self.addInst(.{
4548 .tag = .cmp,
4549 .data = .{ .r_op_cmp = .{
4550 .rn = lhs_reg,
4551 .op = Instruction.Operand.reg(rhs_reg, Instruction.Operand.Shift.none),
4552 } },
4553 });
4554 }
4555
4556 return switch (int_info.signedness) {
4557 .signed => MCValue{ .cpsr_flags = Condition.fromCompareOperatorSigned(op) },
4558 .unsigned => MCValue{ .cpsr_flags = Condition.fromCompareOperatorUnsigned(op) },
4559 };
4560 } else {
4561 return self.fail("TODO ARM cmp for ints > 32 bits", .{});
4562 }
4563}
4564
4565fn airCmpVector(self: *Self, inst: Air.Inst.Index) !void {
4566 _ = inst;
4567 return self.fail("TODO implement airCmpVector for {}", .{self.target.cpu.arch});
4568}
4569
4570fn airCmpLtErrorsLen(self: *Self, inst: Air.Inst.Index) !void {
4571 const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
4572 const operand = try self.resolveInst(un_op);
4573 _ = operand;
4574 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airCmpLtErrorsLen for {}", .{self.target.cpu.arch});
4575 return self.finishAir(inst, result, .{ un_op, .none, .none });
4576}
4577
4578fn airDbgStmt(self: *Self, inst: Air.Inst.Index) !void {
4579 const dbg_stmt = self.air.instructions.items(.data)[@intFromEnum(inst)].dbg_stmt;
4580
4581 _ = try self.addInst(.{
4582 .tag = .dbg_line,
4583 .cond = undefined,
4584 .data = .{ .dbg_line_column = .{
4585 .line = dbg_stmt.line,
4586 .column = dbg_stmt.column,
4587 } },
4588 });
4589
4590 return self.finishAirBookkeeping();
4591}
4592
4593fn airDbgInlineBlock(self: *Self, inst: Air.Inst.Index) !void {
4594 const pt = self.pt;
4595 const zcu = pt.zcu;
4596 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
4597 const extra = self.air.extraData(Air.DbgInlineBlock, ty_pl.payload);
4598 const func = zcu.funcInfo(extra.data.func);
4599 // TODO emit debug info for function change
4600 _ = func;
4601 try self.lowerBlock(inst, @ptrCast(self.air.extra.items[extra.end..][0..extra.data.body_len]));
4602}
4603
4604fn airDbgVar(self: *Self, inst: Air.Inst.Index) !void {
4605 const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
4606 const operand = pl_op.operand;
4607 const tag = self.air.instructions.items(.tag)[@intFromEnum(inst)];
4608 const ty = self.typeOf(operand);
4609 const mcv = try self.resolveInst(operand);
4610 const name: Air.NullTerminatedString = @enumFromInt(pl_op.payload);
4611
4612 log.debug("airDbgVar: %{d}: {}, {}", .{ inst, ty.fmtDebug(), mcv });
4613
4614 try self.dbg_info_relocs.append(self.gpa, .{
4615 .tag = tag,
4616 .ty = ty,
4617 .name = name.toSlice(self.air),
4618 .mcv = mcv,
4619 });
4620
4621 return self.finishAir(inst, .dead, .{ operand, .none, .none });
4622}
4623
4624/// Given a boolean condition, emit a jump that is taken when that
4625/// condition is false.
4626fn condBr(self: *Self, condition: MCValue) !Mir.Inst.Index {
4627 const condition_code: Condition = switch (condition) {
4628 .cpsr_flags => |cond| cond.negate(),
4629 else => blk: {
4630 const reg = switch (condition) {
4631 .register => |r| r,
4632 else => try self.copyToTmpRegister(Type.bool, condition),
4633 };
4634
4635 try self.spillCompareFlagsIfOccupied();
4636
4637 // cmp reg, 1
4638 // bne ...
4639 _ = try self.addInst(.{
4640 .tag = .cmp,
4641 .data = .{ .r_op_cmp = .{
4642 .rn = reg,
4643 .op = Instruction.Operand.imm(1, 0),
4644 } },
4645 });
4646
4647 break :blk .ne;
4648 },
4649 };
4650
4651 return try self.addInst(.{
4652 .tag = .b,
4653 .cond = condition_code,
4654 .data = .{ .inst = undefined }, // populated later through performReloc
4655 });
4656}
4657
4658fn airCondBr(self: *Self, inst: Air.Inst.Index) !void {
4659 const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
4660 const cond_inst = try self.resolveInst(pl_op.operand);
4661 const extra = self.air.extraData(Air.CondBr, pl_op.payload);
4662 const then_body: []const Air.Inst.Index = @ptrCast(self.air.extra.items[extra.end..][0..extra.data.then_body_len]);
4663 const else_body: []const Air.Inst.Index = @ptrCast(self.air.extra.items[extra.end + then_body.len ..][0..extra.data.else_body_len]);
4664 const liveness_condbr = self.liveness.getCondBr(inst);
4665
4666 const reloc: Mir.Inst.Index = try self.condBr(cond_inst);
4667
4668 // If the condition dies here in this condbr instruction, process
4669 // that death now instead of later as this has an effect on
4670 // whether it needs to be spilled in the branches
4671 if (self.liveness.operandDies(inst, 0)) {
4672 if (pl_op.operand.toIndex()) |op_index| {
4673 self.processDeath(op_index);
4674 }
4675 }
4676
4677 // Capture the state of register and stack allocation state so that we can revert to it.
4678 const parent_next_stack_offset = self.next_stack_offset;
4679 const parent_free_registers = self.register_manager.free_registers;
4680 var parent_stack = try self.stack.clone(self.gpa);
4681 defer parent_stack.deinit(self.gpa);
4682 const parent_registers = self.register_manager.registers;
4683 const parent_cpsr_flags_inst = self.cpsr_flags_inst;
4684
4685 try self.branch_stack.append(.{});
4686 errdefer {
4687 _ = self.branch_stack.pop().?;
4688 }
4689
4690 try self.ensureProcessDeathCapacity(liveness_condbr.then_deaths.len);
4691 for (liveness_condbr.then_deaths) |operand| {
4692 self.processDeath(operand);
4693 }
4694 try self.genBody(then_body);
4695
4696 // Revert to the previous register and stack allocation state.
4697
4698 var saved_then_branch = self.branch_stack.pop().?;
4699 defer saved_then_branch.deinit(self.gpa);
4700
4701 self.register_manager.registers = parent_registers;
4702 self.cpsr_flags_inst = parent_cpsr_flags_inst;
4703
4704 self.stack.deinit(self.gpa);
4705 self.stack = parent_stack;
4706 parent_stack = .{};
4707
4708 self.next_stack_offset = parent_next_stack_offset;
4709 self.register_manager.free_registers = parent_free_registers;
4710
4711 try self.performReloc(reloc);
4712 const else_branch = self.branch_stack.addOneAssumeCapacity();
4713 else_branch.* = .{};
4714
4715 try self.ensureProcessDeathCapacity(liveness_condbr.else_deaths.len);
4716 for (liveness_condbr.else_deaths) |operand| {
4717 self.processDeath(operand);
4718 }
4719 try self.genBody(else_body);
4720
4721 // At this point, each branch will possibly have conflicting values for where
4722 // each instruction is stored. They agree, however, on which instructions are alive/dead.
4723 // We use the first ("then") branch as canonical, and here emit
4724 // instructions into the second ("else") branch to make it conform.
4725 // We continue respect the data structure semantic guarantees of the else_branch so
4726 // that we can use all the code emitting abstractions. This is why at the bottom we
4727 // assert that parent_branch.free_registers equals the saved_then_branch.free_registers
4728 // rather than assigning it.
4729 const parent_branch = &self.branch_stack.items[self.branch_stack.items.len - 2];
4730 try parent_branch.inst_table.ensureUnusedCapacity(self.gpa, else_branch.inst_table.count());
4731
4732 const else_slice = else_branch.inst_table.entries.slice();
4733 const else_keys = else_slice.items(.key);
4734 const else_values = else_slice.items(.value);
4735 for (else_keys, 0..) |else_key, else_idx| {
4736 const else_value = else_values[else_idx];
4737 const canon_mcv = if (saved_then_branch.inst_table.fetchSwapRemove(else_key)) |then_entry| blk: {
4738 // The instruction's MCValue is overridden in both branches.
4739 parent_branch.inst_table.putAssumeCapacity(else_key, then_entry.value);
4740 if (else_value == .dead) {
4741 assert(then_entry.value == .dead);
4742 continue;
4743 }
4744 break :blk then_entry.value;
4745 } else blk: {
4746 if (else_value == .dead)
4747 continue;
4748 // The instruction is only overridden in the else branch.
4749 var i: usize = self.branch_stack.items.len - 1;
4750 while (true) {
4751 i -= 1; // If this overflows, the question is: why wasn't the instruction marked dead?
4752 if (self.branch_stack.items[i].inst_table.get(else_key)) |mcv| {
4753 assert(mcv != .dead);
4754 break :blk mcv;
4755 }
4756 }
4757 };
4758 log.debug("consolidating else_entry {d} {}=>{}", .{ else_key, else_value, canon_mcv });
4759 // TODO make sure the destination stack offset / register does not already have something
4760 // going on there.
4761 try self.setRegOrMem(self.typeOfIndex(else_key), canon_mcv, else_value);
4762 // TODO track the new register / stack allocation
4763 }
4764 try parent_branch.inst_table.ensureUnusedCapacity(self.gpa, saved_then_branch.inst_table.count());
4765 const then_slice = saved_then_branch.inst_table.entries.slice();
4766 const then_keys = then_slice.items(.key);
4767 const then_values = then_slice.items(.value);
4768 for (then_keys, 0..) |then_key, then_idx| {
4769 const then_value = then_values[then_idx];
4770 // We already deleted the items from this table that matched the else_branch.
4771 // So these are all instructions that are only overridden in the then branch.
4772 parent_branch.inst_table.putAssumeCapacity(then_key, then_value);
4773 if (then_value == .dead)
4774 continue;
4775 const parent_mcv = blk: {
4776 var i: usize = self.branch_stack.items.len - 1;
4777 while (true) {
4778 i -= 1;
4779 if (self.branch_stack.items[i].inst_table.get(then_key)) |mcv| {
4780 assert(mcv != .dead);
4781 break :blk mcv;
4782 }
4783 }
4784 };
4785 log.debug("consolidating then_entry {d} {}=>{}", .{ then_key, parent_mcv, then_value });
4786 // TODO make sure the destination stack offset / register does not already have something
4787 // going on there.
4788 try self.setRegOrMem(self.typeOfIndex(then_key), parent_mcv, then_value);
4789 // TODO track the new register / stack allocation
4790 }
4791
4792 {
4793 var item = self.branch_stack.pop().?;
4794 item.deinit(self.gpa);
4795 }
4796
4797 // We already took care of pl_op.operand earlier, so we're going
4798 // to pass .none here
4799 return self.finishAir(inst, .unreach, .{ .none, .none, .none });
4800}
4801
4802fn isNull(
4803 self: *Self,
4804 operand_bind: ReadArg.Bind,
4805 operand_ty: Type,
4806) !MCValue {
4807 const pt = self.pt;
4808 const zcu = pt.zcu;
4809 if (operand_ty.isPtrLikeOptional(zcu)) {
4810 assert(operand_ty.abiSize(zcu) == 4);
4811
4812 const imm_bind: ReadArg.Bind = .{ .mcv = .{ .immediate = 0 } };
4813 return self.cmp(operand_bind, imm_bind, Type.usize, .eq);
4814 } else {
4815 return self.fail("TODO implement non-pointer optionals", .{});
4816 }
4817}
4818
4819fn isNonNull(
4820 self: *Self,
4821 operand_bind: ReadArg.Bind,
4822 operand_ty: Type,
4823) !MCValue {
4824 const is_null_result = try self.isNull(operand_bind, operand_ty);
4825 assert(is_null_result.cpsr_flags == .eq);
4826
4827 return MCValue{ .cpsr_flags = .ne };
4828}
4829
4830fn airIsNull(self: *Self, inst: Air.Inst.Index) !void {
4831 const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
4832 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
4833 const operand_bind: ReadArg.Bind = .{ .inst = un_op };
4834 const operand_ty = self.typeOf(un_op);
4835
4836 break :result try self.isNull(operand_bind, operand_ty);
4837 };
4838 return self.finishAir(inst, result, .{ un_op, .none, .none });
4839}
4840
4841fn airIsNullPtr(self: *Self, inst: Air.Inst.Index) !void {
4842 const pt = self.pt;
4843 const zcu = pt.zcu;
4844 const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
4845 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
4846 const operand_ptr = try self.resolveInst(un_op);
4847 const ptr_ty = self.typeOf(un_op);
4848 const elem_ty = ptr_ty.childType(zcu);
4849
4850 const operand = try self.allocRegOrMem(elem_ty, true, null);
4851 try self.load(operand, operand_ptr, ptr_ty);
4852
4853 break :result try self.isNull(.{ .mcv = operand }, elem_ty);
4854 };
4855 return self.finishAir(inst, result, .{ un_op, .none, .none });
4856}
4857
4858fn airIsNonNull(self: *Self, inst: Air.Inst.Index) !void {
4859 const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
4860 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
4861 const operand_bind: ReadArg.Bind = .{ .inst = un_op };
4862 const operand_ty = self.typeOf(un_op);
4863
4864 break :result try self.isNonNull(operand_bind, operand_ty);
4865 };
4866 return self.finishAir(inst, result, .{ un_op, .none, .none });
4867}
4868
4869fn airIsNonNullPtr(self: *Self, inst: Air.Inst.Index) !void {
4870 const pt = self.pt;
4871 const zcu = pt.zcu;
4872 const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
4873 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
4874 const operand_ptr = try self.resolveInst(un_op);
4875 const ptr_ty = self.typeOf(un_op);
4876 const elem_ty = ptr_ty.childType(zcu);
4877
4878 const operand = try self.allocRegOrMem(elem_ty, true, null);
4879 try self.load(operand, operand_ptr, ptr_ty);
4880
4881 break :result try self.isNonNull(.{ .mcv = operand }, elem_ty);
4882 };
4883 return self.finishAir(inst, result, .{ un_op, .none, .none });
4884}
4885
4886fn isErr(
4887 self: *Self,
4888 error_union_bind: ReadArg.Bind,
4889 error_union_ty: Type,
4890) !MCValue {
4891 const pt = self.pt;
4892 const zcu = pt.zcu;
4893 const error_type = error_union_ty.errorUnionSet(zcu);
4894
4895 if (error_type.errorSetIsEmpty(zcu)) {
4896 return MCValue{ .immediate = 0 }; // always false
4897 }
4898
4899 const error_mcv = try self.errUnionErr(error_union_bind, error_union_ty, null);
4900 return try self.cmp(.{ .mcv = error_mcv }, .{ .mcv = .{ .immediate = 0 } }, error_type, .gt);
4901}
4902
4903fn isNonErr(
4904 self: *Self,
4905 error_union_bind: ReadArg.Bind,
4906 error_union_ty: Type,
4907) !MCValue {
4908 const is_err_result = try self.isErr(error_union_bind, error_union_ty);
4909 switch (is_err_result) {
4910 .cpsr_flags => |cond| {
4911 assert(cond == .hi);
4912 return MCValue{ .cpsr_flags = cond.negate() };
4913 },
4914 .immediate => |imm| {
4915 assert(imm == 0);
4916 return MCValue{ .immediate = 1 };
4917 },
4918 else => unreachable,
4919 }
4920}
4921
4922fn airIsErr(self: *Self, inst: Air.Inst.Index) !void {
4923 const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
4924 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
4925 const error_union_bind: ReadArg.Bind = .{ .inst = un_op };
4926 const error_union_ty = self.typeOf(un_op);
4927
4928 break :result try self.isErr(error_union_bind, error_union_ty);
4929 };
4930 return self.finishAir(inst, result, .{ un_op, .none, .none });
4931}
4932
4933fn airIsErrPtr(self: *Self, inst: Air.Inst.Index) !void {
4934 const pt = self.pt;
4935 const zcu = pt.zcu;
4936 const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
4937 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
4938 const operand_ptr = try self.resolveInst(un_op);
4939 const ptr_ty = self.typeOf(un_op);
4940 const elem_ty = ptr_ty.childType(zcu);
4941
4942 const operand = try self.allocRegOrMem(elem_ty, true, null);
4943 try self.load(operand, operand_ptr, ptr_ty);
4944
4945 break :result try self.isErr(.{ .mcv = operand }, elem_ty);
4946 };
4947 return self.finishAir(inst, result, .{ un_op, .none, .none });
4948}
4949
4950fn airIsNonErr(self: *Self, inst: Air.Inst.Index) !void {
4951 const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
4952 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
4953 const error_union_bind: ReadArg.Bind = .{ .inst = un_op };
4954 const error_union_ty = self.typeOf(un_op);
4955
4956 break :result try self.isNonErr(error_union_bind, error_union_ty);
4957 };
4958 return self.finishAir(inst, result, .{ un_op, .none, .none });
4959}
4960
4961fn airIsNonErrPtr(self: *Self, inst: Air.Inst.Index) !void {
4962 const pt = self.pt;
4963 const zcu = pt.zcu;
4964 const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
4965 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
4966 const operand_ptr = try self.resolveInst(un_op);
4967 const ptr_ty = self.typeOf(un_op);
4968 const elem_ty = ptr_ty.childType(zcu);
4969
4970 const operand = try self.allocRegOrMem(elem_ty, true, null);
4971 try self.load(operand, operand_ptr, ptr_ty);
4972
4973 break :result try self.isNonErr(.{ .mcv = operand }, elem_ty);
4974 };
4975 return self.finishAir(inst, result, .{ un_op, .none, .none });
4976}
4977
4978fn airLoop(self: *Self, inst: Air.Inst.Index) !void {
4979 // A loop is a setup to be able to jump back to the beginning.
4980 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
4981 const loop = self.air.extraData(Air.Block, ty_pl.payload);
4982 const body: []const Air.Inst.Index = @ptrCast(self.air.extra.items[loop.end..][0..loop.data.body_len]);
4983 const start_index: Mir.Inst.Index = @intCast(self.mir_instructions.len);
4984
4985 try self.genBody(body);
4986 try self.jump(start_index);
4987
4988 return self.finishAirBookkeeping();
4989}
4990
4991/// Send control flow to `inst`.
4992fn jump(self: *Self, inst: Mir.Inst.Index) !void {
4993 _ = try self.addInst(.{
4994 .tag = .b,
4995 .data = .{ .inst = inst },
4996 });
4997}
4998
4999fn airBlock(self: *Self, inst: Air.Inst.Index) !void {
5000 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
5001 const extra = self.air.extraData(Air.Block, ty_pl.payload);
5002 try self.lowerBlock(inst, @ptrCast(self.air.extra.items[extra.end..][0..extra.data.body_len]));
5003}
5004
5005fn lowerBlock(self: *Self, inst: Air.Inst.Index, body: []const Air.Inst.Index) !void {
5006 try self.blocks.putNoClobber(self.gpa, inst, .{
5007 // A block is a setup to be able to jump to the end.
5008 .relocs = .{},
5009 // It also acts as a receptacle for break operands.
5010 // Here we use `MCValue.none` to represent a null value so that the first
5011 // break instruction will choose a MCValue for the block result and overwrite
5012 // this field. Following break instructions will use that MCValue to put their
5013 // block results.
5014 .mcv = MCValue{ .none = {} },
5015 });
5016 defer self.blocks.getPtr(inst).?.relocs.deinit(self.gpa);
5017
5018 // TODO emit debug info lexical block
5019 try self.genBody(body);
5020
5021 // relocations for `br` instructions
5022 const relocs = &self.blocks.getPtr(inst).?.relocs;
5023 if (relocs.items.len > 0 and relocs.items[relocs.items.len - 1] == self.mir_instructions.len - 1) {
5024 // If the last Mir instruction is the last relocation (which
5025 // would just jump one instruction further), it can be safely
5026 // removed
5027 self.mir_instructions.orderedRemove(relocs.pop().?);
5028 }
5029 for (relocs.items) |reloc| {
5030 try self.performReloc(reloc);
5031 }
5032
5033 const result = self.blocks.getPtr(inst).?.mcv;
5034 return self.finishAir(inst, result, .{ .none, .none, .none });
5035}
5036
5037fn airSwitch(self: *Self, inst: Air.Inst.Index) !void {
5038 const switch_br = self.air.unwrapSwitch(inst);
5039 const condition_ty = self.typeOf(switch_br.operand);
5040 const liveness = try self.liveness.getSwitchBr(
5041 self.gpa,
5042 inst,
5043 switch_br.cases_len + 1,
5044 );
5045 defer self.gpa.free(liveness.deaths);
5046
5047 var it = switch_br.iterateCases();
5048 while (it.next()) |case| {
5049 if (case.ranges.len > 0) return self.fail("TODO: switch with ranges", .{});
5050 // For every item, we compare it to condition and branch into
5051 // the prong if they are equal. After we compared to all
5052 // items, we branch into the next prong (or if no other prongs
5053 // exist out of the switch statement).
5054 //
5055 // cmp condition, item1
5056 // beq prong
5057 // cmp condition, item2
5058 // beq prong
5059 // cmp condition, item3
5060 // beq prong
5061 // b out
5062 // prong: ...
5063 // ...
5064 // out: ...
5065 const branch_into_prong_relocs = try self.gpa.alloc(u32, case.items.len);
5066 defer self.gpa.free(branch_into_prong_relocs);
5067
5068 for (case.items, 0..) |item, idx| {
5069 const cmp_result = try self.cmp(.{ .inst = switch_br.operand }, .{ .inst = item }, condition_ty, .neq);
5070 branch_into_prong_relocs[idx] = try self.condBr(cmp_result);
5071 }
5072
5073 const branch_away_from_prong_reloc = try self.addInst(.{
5074 .tag = .b,
5075 .data = .{ .inst = undefined }, // populated later through performReloc
5076 });
5077
5078 for (branch_into_prong_relocs) |reloc| {
5079 try self.performReloc(reloc);
5080 }
5081
5082 // Capture the state of register and stack allocation state so that we can revert to it.
5083 const parent_next_stack_offset = self.next_stack_offset;
5084 const parent_free_registers = self.register_manager.free_registers;
5085 const parent_cpsr_flags_inst = self.cpsr_flags_inst;
5086 var parent_stack = try self.stack.clone(self.gpa);
5087 defer parent_stack.deinit(self.gpa);
5088 const parent_registers = self.register_manager.registers;
5089
5090 try self.branch_stack.append(.{});
5091 errdefer {
5092 _ = self.branch_stack.pop().?;
5093 }
5094
5095 try self.ensureProcessDeathCapacity(liveness.deaths[case.idx].len);
5096 for (liveness.deaths[case.idx]) |operand| {
5097 self.processDeath(operand);
5098 }
5099 try self.genBody(case.body);
5100
5101 // Revert to the previous register and stack allocation state.
5102 var saved_case_branch = self.branch_stack.pop().?;
5103 defer saved_case_branch.deinit(self.gpa);
5104
5105 self.register_manager.registers = parent_registers;
5106 self.cpsr_flags_inst = parent_cpsr_flags_inst;
5107 self.stack.deinit(self.gpa);
5108 self.stack = parent_stack;
5109 parent_stack = .{};
5110
5111 self.next_stack_offset = parent_next_stack_offset;
5112 self.register_manager.free_registers = parent_free_registers;
5113
5114 try self.performReloc(branch_away_from_prong_reloc);
5115 }
5116
5117 if (switch_br.else_body_len > 0) {
5118 const else_body = it.elseBody();
5119
5120 // Capture the state of register and stack allocation state so that we can revert to it.
5121 const parent_next_stack_offset = self.next_stack_offset;
5122 const parent_free_registers = self.register_manager.free_registers;
5123 const parent_cpsr_flags_inst = self.cpsr_flags_inst;
5124 var parent_stack = try self.stack.clone(self.gpa);
5125 defer parent_stack.deinit(self.gpa);
5126 const parent_registers = self.register_manager.registers;
5127
5128 try self.branch_stack.append(.{});
5129 errdefer {
5130 _ = self.branch_stack.pop().?;
5131 }
5132
5133 const else_deaths = liveness.deaths.len - 1;
5134 try self.ensureProcessDeathCapacity(liveness.deaths[else_deaths].len);
5135 for (liveness.deaths[else_deaths]) |operand| {
5136 self.processDeath(operand);
5137 }
5138 try self.genBody(else_body);
5139
5140 // Revert to the previous register and stack allocation state.
5141 var saved_case_branch = self.branch_stack.pop().?;
5142 defer saved_case_branch.deinit(self.gpa);
5143
5144 self.register_manager.registers = parent_registers;
5145 self.cpsr_flags_inst = parent_cpsr_flags_inst;
5146 self.stack.deinit(self.gpa);
5147 self.stack = parent_stack;
5148 parent_stack = .{};
5149
5150 self.next_stack_offset = parent_next_stack_offset;
5151 self.register_manager.free_registers = parent_free_registers;
5152
5153 // TODO consolidate returned MCValues between prongs and else branch like we do
5154 // in airCondBr.
5155 }
5156
5157 return self.finishAir(inst, .unreach, .{ switch_br.operand, .none, .none });
5158}
5159
5160fn performReloc(self: *Self, inst: Mir.Inst.Index) !void {
5161 const tag = self.mir_instructions.items(.tag)[inst];
5162 switch (tag) {
5163 .b => self.mir_instructions.items(.data)[inst].inst = @intCast(self.mir_instructions.len),
5164 else => unreachable,
5165 }
5166}
5167
5168fn airBr(self: *Self, inst: Air.Inst.Index) !void {
5169 const branch = self.air.instructions.items(.data)[@intFromEnum(inst)].br;
5170 try self.br(branch.block_inst, branch.operand);
5171 return self.finishAir(inst, .dead, .{ branch.operand, .none, .none });
5172}
5173
5174fn br(self: *Self, block: Air.Inst.Index, operand: Air.Inst.Ref) !void {
5175 const zcu = self.pt.zcu;
5176 const block_data = self.blocks.getPtr(block).?;
5177
5178 if (self.typeOf(operand).hasRuntimeBits(zcu)) {
5179 const operand_mcv = try self.resolveInst(operand);
5180 const block_mcv = block_data.mcv;
5181 if (block_mcv == .none) {
5182 block_data.mcv = switch (operand_mcv) {
5183 .none, .dead, .unreach => unreachable,
5184 .register, .stack_offset, .memory => operand_mcv,
5185 .immediate, .stack_argument_offset, .cpsr_flags => blk: {
5186 const new_mcv = try self.allocRegOrMem(self.typeOfIndex(block), true, block);
5187 try self.setRegOrMem(self.typeOfIndex(block), new_mcv, operand_mcv);
5188 break :blk new_mcv;
5189 },
5190 else => return self.fail("TODO implement block_data.mcv = operand_mcv for {}", .{operand_mcv}),
5191 };
5192 } else {
5193 try self.setRegOrMem(self.typeOfIndex(block), block_mcv, operand_mcv);
5194 }
5195 }
5196 return self.brVoid(block);
5197}
5198
5199fn brVoid(self: *Self, block: Air.Inst.Index) !void {
5200 const block_data = self.blocks.getPtr(block).?;
5201
5202 // Emit a jump with a relocation. It will be patched up after the block ends.
5203 try block_data.relocs.append(self.gpa, try self.addInst(.{
5204 .tag = .b,
5205 .data = .{ .inst = undefined }, // populated later through performReloc
5206 }));
5207}
5208
5209fn airAsm(self: *Self, inst: Air.Inst.Index) !void {
5210 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
5211 const extra = self.air.extraData(Air.Asm, ty_pl.payload);
5212 const is_volatile = @as(u1, @truncate(extra.data.flags >> 31)) != 0;
5213 const clobbers_len: u31 = @truncate(extra.data.flags);
5214 var extra_i: usize = extra.end;
5215 const outputs: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra_i..][0..extra.data.outputs_len]);
5216 extra_i += outputs.len;
5217 const inputs: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra_i..][0..extra.data.inputs_len]);
5218 extra_i += inputs.len;
5219
5220 const dead = !is_volatile and self.liveness.isUnused(inst);
5221 const result: MCValue = if (dead) .dead else result: {
5222 if (outputs.len > 1) {
5223 return self.fail("TODO implement codegen for asm with more than 1 output", .{});
5224 }
5225
5226 const output_constraint: ?[]const u8 = for (outputs) |output| {
5227 if (output != .none) {
5228 return self.fail("TODO implement codegen for non-expr asm", .{});
5229 }
5230 const extra_bytes = std.mem.sliceAsBytes(self.air.extra.items[extra_i..]);
5231 const constraint = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra.items[extra_i..]), 0);
5232 const name = std.mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0);
5233 // This equation accounts for the fact that even if we have exactly 4 bytes
5234 // for the string, we still use the next u32 for the null terminator.
5235 extra_i += (constraint.len + name.len + (2 + 3)) / 4;
5236
5237 break constraint;
5238 } else null;
5239
5240 for (inputs) |input| {
5241 const input_bytes = std.mem.sliceAsBytes(self.air.extra.items[extra_i..]);
5242 const constraint = std.mem.sliceTo(input_bytes, 0);
5243 const name = std.mem.sliceTo(input_bytes[constraint.len + 1 ..], 0);
5244 // This equation accounts for the fact that even if we have exactly 4 bytes
5245 // for the string, we still use the next u32 for the null terminator.
5246 extra_i += (constraint.len + name.len + (2 + 3)) / 4;
5247
5248 if (constraint.len < 3 or constraint[0] != '{' or constraint[constraint.len - 1] != '}') {
5249 return self.fail("unrecognized asm input constraint: '{s}'", .{constraint});
5250 }
5251 const reg_name = constraint[1 .. constraint.len - 1];
5252 const reg = parseRegName(reg_name) orelse
5253 return self.fail("unrecognized register: '{s}'", .{reg_name});
5254
5255 const arg_mcv = try self.resolveInst(input);
5256 try self.register_manager.getReg(reg, null);
5257 try self.genSetReg(self.typeOf(input), reg, arg_mcv);
5258 }
5259
5260 {
5261 var clobber_i: u32 = 0;
5262 while (clobber_i < clobbers_len) : (clobber_i += 1) {
5263 const clobber = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra.items[extra_i..]), 0);
5264 // This equation accounts for the fact that even if we have exactly 4 bytes
5265 // for the string, we still use the next u32 for the null terminator.
5266 extra_i += clobber.len / 4 + 1;
5267
5268 // TODO honor these
5269 }
5270 }
5271
5272 const asm_source = std.mem.sliceAsBytes(self.air.extra.items[extra_i..])[0..extra.data.source_len];
5273
5274 if (mem.eql(u8, asm_source, "svc #0")) {
5275 _ = try self.addInst(.{
5276 .tag = .svc,
5277 .data = .{ .imm24 = 0 },
5278 });
5279 } else {
5280 return self.fail("TODO implement support for more arm assembly instructions", .{});
5281 }
5282
5283 if (output_constraint) |output| {
5284 if (output.len < 4 or output[0] != '=' or output[1] != '{' or output[output.len - 1] != '}') {
5285 return self.fail("unrecognized asm output constraint: '{s}'", .{output});
5286 }
5287 const reg_name = output[2 .. output.len - 1];
5288 const reg = parseRegName(reg_name) orelse
5289 return self.fail("unrecognized register: '{s}'", .{reg_name});
5290
5291 break :result MCValue{ .register = reg };
5292 } else {
5293 break :result MCValue{ .none = {} };
5294 }
5295 };
5296
5297 simple: {
5298 var buf = [1]Air.Inst.Ref{.none} ** (Air.Liveness.bpi - 1);
5299 var buf_index: usize = 0;
5300 for (outputs) |output| {
5301 if (output == .none) continue;
5302
5303 if (buf_index >= buf.len) break :simple;
5304 buf[buf_index] = output;
5305 buf_index += 1;
5306 }
5307 if (buf_index + inputs.len > buf.len) break :simple;
5308 @memcpy(buf[buf_index..][0..inputs.len], inputs);
5309 return self.finishAir(inst, result, buf);
5310 }
5311 var bt = try self.iterateBigTomb(inst, outputs.len + inputs.len);
5312 for (outputs) |output| {
5313 if (output == .none) continue;
5314
5315 bt.feed(output);
5316 }
5317 for (inputs) |input| {
5318 bt.feed(input);
5319 }
5320 return bt.finishAir(result);
5321}
5322
5323fn iterateBigTomb(self: *Self, inst: Air.Inst.Index, operand_count: usize) !BigTomb {
5324 try self.ensureProcessDeathCapacity(operand_count + 1);
5325 return BigTomb{
5326 .function = self,
5327 .inst = inst,
5328 .lbt = self.liveness.iterateBigTomb(inst),
5329 };
5330}
5331
5332/// Sets the value without any modifications to register allocation metadata or stack allocation metadata.
5333fn setRegOrMem(self: *Self, ty: Type, loc: MCValue, val: MCValue) !void {
5334 switch (loc) {
5335 .none => return,
5336 .register => |reg| return self.genSetReg(ty, reg, val),
5337 .stack_offset => |off| return self.genSetStack(ty, off, val),
5338 .memory => {
5339 return self.fail("TODO implement setRegOrMem for memory", .{});
5340 },
5341 else => unreachable,
5342 }
5343}
5344
5345fn genSetStack(self: *Self, ty: Type, stack_offset: u32, mcv: MCValue) InnerError!void {
5346 const pt = self.pt;
5347 const zcu = pt.zcu;
5348 const abi_size: u32 = @intCast(ty.abiSize(zcu));
5349 switch (mcv) {
5350 .dead => unreachable,
5351 .unreach, .none => return, // Nothing to do.
5352 .undef => {
5353 if (!self.wantSafety())
5354 return; // The already existing value will do just fine.
5355 // TODO Upgrade this to a memset call when we have that available.
5356 switch (abi_size) {
5357 1 => try self.genSetStack(ty, stack_offset, .{ .immediate = 0xaa }),
5358 2 => try self.genSetStack(ty, stack_offset, .{ .immediate = 0xaaaa }),
5359 4 => try self.genSetStack(ty, stack_offset, .{ .immediate = 0xaaaaaaaa }),
5360 else => try self.genInlineMemset(
5361 .{ .ptr_stack_offset = stack_offset },
5362 .{ .immediate = 0xaa },
5363 .{ .immediate = abi_size },
5364 ),
5365 }
5366 },
5367 .cpsr_flags,
5368 .immediate,
5369 .ptr_stack_offset,
5370 => {
5371 const reg = try self.copyToTmpRegister(ty, mcv);
5372 return self.genSetStack(ty, stack_offset, MCValue{ .register = reg });
5373 },
5374 .register => |reg| {
5375 switch (abi_size) {
5376 1, 4 => {
5377 const offset = if (math.cast(u12, stack_offset)) |imm| blk: {
5378 break :blk Instruction.Offset.imm(imm);
5379 } else Instruction.Offset.reg(try self.copyToTmpRegister(Type.u32, MCValue{ .immediate = stack_offset }), .none);
5380
5381 const tag: Mir.Inst.Tag = switch (abi_size) {
5382 1 => .strb,
5383 4 => .str,
5384 else => unreachable,
5385 };
5386
5387 _ = try self.addInst(.{
5388 .tag = tag,
5389 .data = .{ .rr_offset = .{
5390 .rt = reg,
5391 .rn = .fp,
5392 .offset = .{
5393 .offset = offset,
5394 .positive = false,
5395 },
5396 } },
5397 });
5398 },
5399 2 => {
5400 const offset = if (stack_offset <= math.maxInt(u8)) blk: {
5401 break :blk Instruction.ExtraLoadStoreOffset.imm(@intCast(stack_offset));
5402 } else Instruction.ExtraLoadStoreOffset.reg(try self.copyToTmpRegister(Type.u32, MCValue{ .immediate = stack_offset }));
5403
5404 _ = try self.addInst(.{
5405 .tag = .strh,
5406 .data = .{ .rr_extra_offset = .{
5407 .rt = reg,
5408 .rn = .fp,
5409 .offset = .{
5410 .offset = offset,
5411 .positive = false,
5412 },
5413 } },
5414 });
5415 },
5416 else => return self.fail("TODO implement storing other types abi_size={}", .{abi_size}),
5417 }
5418 },
5419 .register_c_flag,
5420 .register_v_flag,
5421 => |reg| {
5422 const reg_lock = self.register_manager.lockReg(reg);
5423 defer if (reg_lock) |locked_reg| self.register_manager.unlockReg(locked_reg);
5424
5425 const wrapped_ty = ty.fieldType(0, zcu);
5426 try self.genSetStack(wrapped_ty, stack_offset, .{ .register = reg });
5427
5428 const overflow_bit_ty = ty.fieldType(1, zcu);
5429 const overflow_bit_offset: u32 = @intCast(ty.structFieldOffset(1, zcu));
5430 const cond_reg = try self.register_manager.allocReg(null, gp);
5431
5432 // C flag: movcs reg, #1
5433 // V flag: movvs reg, #1
5434 _ = try self.addInst(.{
5435 .tag = .mov,
5436 .cond = switch (mcv) {
5437 .register_c_flag => .cs,
5438 .register_v_flag => .vs,
5439 else => unreachable,
5440 },
5441 .data = .{ .r_op_mov = .{
5442 .rd = cond_reg,
5443 .op = Instruction.Operand.fromU32(1).?,
5444 } },
5445 });
5446
5447 try self.genSetStack(overflow_bit_ty, stack_offset - overflow_bit_offset, .{
5448 .register = cond_reg,
5449 });
5450 },
5451 .memory,
5452 .stack_argument_offset,
5453 .stack_offset,
5454 => {
5455 switch (mcv) {
5456 .stack_offset => |off| {
5457 if (stack_offset == off)
5458 return; // Copy stack variable to itself; nothing to do.
5459 },
5460 else => {},
5461 }
5462
5463 if (abi_size <= 4) {
5464 const reg = try self.copyToTmpRegister(ty, mcv);
5465 return self.genSetStack(ty, stack_offset, MCValue{ .register = reg });
5466 } else {
5467 const ptr_ty = try pt.singleMutPtrType(ty);
5468
5469 // TODO call extern memcpy
5470 const regs = try self.register_manager.allocRegs(5, .{ null, null, null, null, null }, gp);
5471 const src_reg = regs[0];
5472 const dst_reg = regs[1];
5473 const len_reg = regs[2];
5474 const count_reg = regs[3];
5475 const tmp_reg = regs[4];
5476
5477 switch (mcv) {
5478 .stack_offset => |off| {
5479 // sub src_reg, fp, #off
5480 try self.genSetReg(ptr_ty, src_reg, .{ .ptr_stack_offset = off });
5481 },
5482 .memory => |addr| try self.genSetReg(ptr_ty, src_reg, .{ .immediate = @intCast(addr) }),
5483 .stack_argument_offset => |off| {
5484 _ = try self.addInst(.{
5485 .tag = .ldr_ptr_stack_argument,
5486 .data = .{ .r_stack_offset = .{
5487 .rt = src_reg,
5488 .stack_offset = off,
5489 } },
5490 });
5491 },
5492 else => unreachable,
5493 }
5494
5495 // sub dst_reg, fp, #stack_offset
5496 try self.genSetReg(ptr_ty, dst_reg, .{ .ptr_stack_offset = stack_offset });
5497
5498 // mov len, #abi_size
5499 try self.genSetReg(Type.usize, len_reg, .{ .immediate = abi_size });
5500
5501 // memcpy(src, dst, len)
5502 try self.genInlineMemcpy(src_reg, dst_reg, len_reg, count_reg, tmp_reg);
5503 }
5504 },
5505 }
5506}
5507
5508fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void {
5509 const pt = self.pt;
5510 const zcu = pt.zcu;
5511 switch (mcv) {
5512 .dead => unreachable,
5513 .unreach, .none => return, // Nothing to do.
5514 .undef => {
5515 if (!self.wantSafety())
5516 return; // The already existing value will do just fine.
5517 // Write the debug undefined value.
5518 return self.genSetReg(ty, reg, .{ .immediate = 0xaaaaaaaa });
5519 },
5520 .ptr_stack_offset => |off| {
5521 // TODO: maybe addressing from sp instead of fp
5522 const op = Instruction.Operand.fromU32(off) orelse
5523 return self.fail("TODO larger stack offsets", .{});
5524
5525 _ = try self.addInst(.{
5526 .tag = .sub,
5527 .data = .{ .rr_op = .{
5528 .rd = reg,
5529 .rn = .fp,
5530 .op = op,
5531 } },
5532 });
5533 },
5534 .cpsr_flags => |condition| {
5535 const zero = Instruction.Operand.imm(0, 0);
5536 const one = Instruction.Operand.imm(1, 0);
5537
5538 // mov reg, 0
5539 _ = try self.addInst(.{
5540 .tag = .mov,
5541 .data = .{ .r_op_mov = .{
5542 .rd = reg,
5543 .op = zero,
5544 } },
5545 });
5546
5547 // moveq reg, 1
5548 _ = try self.addInst(.{
5549 .tag = .mov,
5550 .cond = condition,
5551 .data = .{ .r_op_mov = .{
5552 .rd = reg,
5553 .op = one,
5554 } },
5555 });
5556 },
5557 .immediate => |x| {
5558 if (Instruction.Operand.fromU32(x)) |op| {
5559 _ = try self.addInst(.{
5560 .tag = .mov,
5561 .data = .{ .r_op_mov = .{
5562 .rd = reg,
5563 .op = op,
5564 } },
5565 });
5566 } else if (Instruction.Operand.fromU32(~x)) |op| {
5567 _ = try self.addInst(.{
5568 .tag = .mvn,
5569 .data = .{ .r_op_mov = .{
5570 .rd = reg,
5571 .op = op,
5572 } },
5573 });
5574 } else if (x <= math.maxInt(u16)) {
5575 if (self.target.cpu.has(.arm, .has_v7)) {
5576 _ = try self.addInst(.{
5577 .tag = .movw,
5578 .data = .{ .r_imm16 = .{
5579 .rd = reg,
5580 .imm16 = @intCast(x),
5581 } },
5582 });
5583 } else {
5584 _ = try self.addInst(.{
5585 .tag = .mov,
5586 .data = .{ .r_op_mov = .{
5587 .rd = reg,
5588 .op = Instruction.Operand.imm(@truncate(x), 0),
5589 } },
5590 });
5591 _ = try self.addInst(.{
5592 .tag = .orr,
5593 .data = .{ .rr_op = .{
5594 .rd = reg,
5595 .rn = reg,
5596 .op = Instruction.Operand.imm(@truncate(x >> 8), 12),
5597 } },
5598 });
5599 }
5600 } else {
5601 // TODO write constant to code and load
5602 // relative to pc
5603 if (self.target.cpu.has(.arm, .has_v7)) {
5604 // immediate: 0xaaaabbbb
5605 // movw reg, #0xbbbb
5606 // movt reg, #0xaaaa
5607 _ = try self.addInst(.{
5608 .tag = .movw,
5609 .data = .{ .r_imm16 = .{
5610 .rd = reg,
5611 .imm16 = @truncate(x),
5612 } },
5613 });
5614 _ = try self.addInst(.{
5615 .tag = .movt,
5616 .data = .{ .r_imm16 = .{
5617 .rd = reg,
5618 .imm16 = @truncate(x >> 16),
5619 } },
5620 });
5621 } else {
5622 // immediate: 0xaabbccdd
5623 // mov reg, #0xaa
5624 // orr reg, reg, #0xbb, 24
5625 // orr reg, reg, #0xcc, 16
5626 // orr reg, reg, #0xdd, 8
5627 _ = try self.addInst(.{
5628 .tag = .mov,
5629 .data = .{ .r_op_mov = .{
5630 .rd = reg,
5631 .op = Instruction.Operand.imm(@truncate(x), 0),
5632 } },
5633 });
5634 _ = try self.addInst(.{
5635 .tag = .orr,
5636 .data = .{ .rr_op = .{
5637 .rd = reg,
5638 .rn = reg,
5639 .op = Instruction.Operand.imm(@truncate(x >> 8), 12),
5640 } },
5641 });
5642 _ = try self.addInst(.{
5643 .tag = .orr,
5644 .data = .{ .rr_op = .{
5645 .rd = reg,
5646 .rn = reg,
5647 .op = Instruction.Operand.imm(@truncate(x >> 16), 8),
5648 } },
5649 });
5650 _ = try self.addInst(.{
5651 .tag = .orr,
5652 .data = .{ .rr_op = .{
5653 .rd = reg,
5654 .rn = reg,
5655 .op = Instruction.Operand.imm(@truncate(x >> 24), 4),
5656 } },
5657 });
5658 }
5659 }
5660 },
5661 .register => |src_reg| {
5662 // If the registers are the same, nothing to do.
5663 if (src_reg.id() == reg.id())
5664 return;
5665
5666 // mov reg, src_reg
5667 _ = try self.addInst(.{
5668 .tag = .mov,
5669 .data = .{ .r_op_mov = .{
5670 .rd = reg,
5671 .op = Instruction.Operand.reg(src_reg, Instruction.Operand.Shift.none),
5672 } },
5673 });
5674 },
5675 .register_c_flag => unreachable, // doesn't fit into a register
5676 .register_v_flag => unreachable, // doesn't fit into a register
5677 .memory => |addr| {
5678 // The value is in memory at a hard-coded address.
5679 // If the type is a pointer, it means the pointer address is at this memory location.
5680 try self.genSetReg(ty, reg, .{ .immediate = @intCast(addr) });
5681 try self.genLdrRegister(reg, reg, ty);
5682 },
5683 .stack_offset => |off| {
5684 // TODO: maybe addressing from sp instead of fp
5685 const abi_size: u32 = @intCast(ty.abiSize(zcu));
5686
5687 const tag: Mir.Inst.Tag = switch (abi_size) {
5688 1 => if (ty.isSignedInt(zcu)) Mir.Inst.Tag.ldrsb else .ldrb,
5689 2 => if (ty.isSignedInt(zcu)) Mir.Inst.Tag.ldrsh else .ldrh,
5690 3, 4 => .ldr,
5691 else => unreachable,
5692 };
5693
5694 const extra_offset = switch (abi_size) {
5695 1 => ty.isSignedInt(zcu),
5696 2 => true,
5697 3, 4 => false,
5698 else => unreachable,
5699 };
5700
5701 if (extra_offset) {
5702 const offset = if (off <= math.maxInt(u8)) blk: {
5703 break :blk Instruction.ExtraLoadStoreOffset.imm(@intCast(off));
5704 } else Instruction.ExtraLoadStoreOffset.reg(try self.copyToTmpRegister(Type.usize, MCValue{ .immediate = off }));
5705
5706 _ = try self.addInst(.{
5707 .tag = tag,
5708 .data = .{ .rr_extra_offset = .{
5709 .rt = reg,
5710 .rn = .fp,
5711 .offset = .{
5712 .offset = offset,
5713 .positive = false,
5714 },
5715 } },
5716 });
5717 } else {
5718 const offset = if (off <= math.maxInt(u12)) blk: {
5719 break :blk Instruction.Offset.imm(@intCast(off));
5720 } else Instruction.Offset.reg(try self.copyToTmpRegister(Type.usize, MCValue{ .immediate = off }), .none);
5721
5722 _ = try self.addInst(.{
5723 .tag = tag,
5724 .data = .{ .rr_offset = .{
5725 .rt = reg,
5726 .rn = .fp,
5727 .offset = .{
5728 .offset = offset,
5729 .positive = false,
5730 },
5731 } },
5732 });
5733 }
5734 },
5735 .stack_argument_offset => |off| {
5736 const abi_size = ty.abiSize(zcu);
5737
5738 const tag: Mir.Inst.Tag = switch (abi_size) {
5739 1 => if (ty.isSignedInt(zcu)) Mir.Inst.Tag.ldrsb_stack_argument else .ldrb_stack_argument,
5740 2 => if (ty.isSignedInt(zcu)) Mir.Inst.Tag.ldrsh_stack_argument else .ldrh_stack_argument,
5741 3, 4 => .ldr_stack_argument,
5742 else => unreachable,
5743 };
5744
5745 _ = try self.addInst(.{
5746 .tag = tag,
5747 .data = .{ .r_stack_offset = .{
5748 .rt = reg,
5749 .stack_offset = off,
5750 } },
5751 });
5752 },
5753 }
5754}
5755
5756fn genSetStackArgument(self: *Self, ty: Type, stack_offset: u32, mcv: MCValue) InnerError!void {
5757 const pt = self.pt;
5758 const abi_size: u32 = @intCast(ty.abiSize(pt.zcu));
5759 switch (mcv) {
5760 .dead => unreachable,
5761 .none, .unreach => return,
5762 .undef => {
5763 if (!self.wantSafety())
5764 return; // The already existing value will do just fine.
5765 // TODO Upgrade this to a memset call when we have that available.
5766 switch (abi_size) {
5767 1 => try self.genSetStackArgument(ty, stack_offset, .{ .immediate = 0xaa }),
5768 2 => try self.genSetStackArgument(ty, stack_offset, .{ .immediate = 0xaaaa }),
5769 4 => try self.genSetStackArgument(ty, stack_offset, .{ .immediate = 0xaaaaaaaa }),
5770 else => return self.fail("TODO implement memset", .{}),
5771 }
5772 },
5773 .register => |reg| {
5774 switch (abi_size) {
5775 1, 4 => {
5776 const offset = if (math.cast(u12, stack_offset)) |imm| blk: {
5777 break :blk Instruction.Offset.imm(imm);
5778 } else Instruction.Offset.reg(try self.copyToTmpRegister(Type.u32, MCValue{ .immediate = stack_offset }), .none);
5779
5780 const tag: Mir.Inst.Tag = switch (abi_size) {
5781 1 => .strb,
5782 4 => .str,
5783 else => unreachable,
5784 };
5785
5786 _ = try self.addInst(.{
5787 .tag = tag,
5788 .data = .{ .rr_offset = .{
5789 .rt = reg,
5790 .rn = .sp,
5791 .offset = .{ .offset = offset },
5792 } },
5793 });
5794 },
5795 2 => {
5796 const offset = if (stack_offset <= math.maxInt(u8)) blk: {
5797 break :blk Instruction.ExtraLoadStoreOffset.imm(@intCast(stack_offset));
5798 } else Instruction.ExtraLoadStoreOffset.reg(try self.copyToTmpRegister(Type.u32, MCValue{ .immediate = stack_offset }));
5799
5800 _ = try self.addInst(.{
5801 .tag = .strh,
5802 .data = .{ .rr_extra_offset = .{
5803 .rt = reg,
5804 .rn = .sp,
5805 .offset = .{ .offset = offset },
5806 } },
5807 });
5808 },
5809 else => return self.fail("TODO implement storing other types abi_size={}", .{abi_size}),
5810 }
5811 },
5812 .register_c_flag,
5813 .register_v_flag,
5814 => {
5815 return self.fail("TODO implement genSetStack {}", .{mcv});
5816 },
5817 .stack_offset,
5818 .memory,
5819 .stack_argument_offset,
5820 => {
5821 if (abi_size <= 4) {
5822 const reg = try self.copyToTmpRegister(ty, mcv);
5823 return self.genSetStackArgument(ty, stack_offset, MCValue{ .register = reg });
5824 } else {
5825 const ptr_ty = try pt.singleMutPtrType(ty);
5826
5827 // TODO call extern memcpy
5828 const regs = try self.register_manager.allocRegs(5, .{ null, null, null, null, null }, gp);
5829 const src_reg = regs[0];
5830 const dst_reg = regs[1];
5831 const len_reg = regs[2];
5832 const count_reg = regs[3];
5833 const tmp_reg = regs[4];
5834
5835 switch (mcv) {
5836 .stack_offset => |off| {
5837 // sub src_reg, fp, #off
5838 try self.genSetReg(ptr_ty, src_reg, .{ .ptr_stack_offset = off });
5839 },
5840 .memory => |addr| try self.genSetReg(ptr_ty, src_reg, .{ .immediate = @intCast(addr) }),
5841 .stack_argument_offset => |off| {
5842 _ = try self.addInst(.{
5843 .tag = .ldr_ptr_stack_argument,
5844 .data = .{ .r_stack_offset = .{
5845 .rt = src_reg,
5846 .stack_offset = off,
5847 } },
5848 });
5849 },
5850 else => unreachable,
5851 }
5852
5853 // add dst_reg, sp, #stack_offset
5854 const dst_offset_op: Instruction.Operand = if (Instruction.Operand.fromU32(stack_offset)) |x| x else {
5855 return self.fail("TODO load: set reg to stack offset with all possible offsets", .{});
5856 };
5857 _ = try self.addInst(.{
5858 .tag = .add,
5859 .data = .{ .rr_op = .{
5860 .rd = dst_reg,
5861 .rn = .sp,
5862 .op = dst_offset_op,
5863 } },
5864 });
5865
5866 // mov len, #abi_size
5867 try self.genSetReg(Type.usize, len_reg, .{ .immediate = abi_size });
5868
5869 // memcpy(src, dst, len)
5870 try self.genInlineMemcpy(src_reg, dst_reg, len_reg, count_reg, tmp_reg);
5871 }
5872 },
5873 .cpsr_flags,
5874 .immediate,
5875 .ptr_stack_offset,
5876 => {
5877 const reg = try self.copyToTmpRegister(ty, mcv);
5878 return self.genSetStackArgument(ty, stack_offset, MCValue{ .register = reg });
5879 },
5880 }
5881}
5882
5883fn airBitCast(self: *Self, inst: Air.Inst.Index) !void {
5884 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
5885 const result = if (self.liveness.isUnused(inst)) .dead else result: {
5886 const operand = try self.resolveInst(ty_op.operand);
5887 if (self.reuseOperand(inst, ty_op.operand, 0, operand)) break :result operand;
5888
5889 const operand_lock = switch (operand) {
5890 .register,
5891 .register_c_flag,
5892 .register_v_flag,
5893 => |reg| self.register_manager.lockReg(reg),
5894 else => null,
5895 };
5896 defer if (operand_lock) |lock| self.register_manager.unlockReg(lock);
5897
5898 const dest_ty = self.typeOfIndex(inst);
5899 const dest = try self.allocRegOrMem(dest_ty, true, inst);
5900 try self.setRegOrMem(dest_ty, dest, operand);
5901 break :result dest;
5902 };
5903 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
5904}
5905
5906fn airArrayToSlice(self: *Self, inst: Air.Inst.Index) !void {
5907 const pt = self.pt;
5908 const zcu = pt.zcu;
5909 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
5910 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
5911 const ptr_ty = self.typeOf(ty_op.operand);
5912 const ptr = try self.resolveInst(ty_op.operand);
5913 const array_ty = ptr_ty.childType(zcu);
5914 const array_len: u32 = @intCast(array_ty.arrayLen(zcu));
5915
5916 const stack_offset = try self.allocMem(8, .@"8", inst);
5917 try self.genSetStack(ptr_ty, stack_offset, ptr);
5918 try self.genSetStack(Type.usize, stack_offset - 4, .{ .immediate = array_len });
5919 break :result MCValue{ .stack_offset = stack_offset };
5920 };
5921 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
5922}
5923
5924fn airFloatFromInt(self: *Self, inst: Air.Inst.Index) !void {
5925 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
5926 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airFloatFromInt for {}", .{
5927 self.target.cpu.arch,
5928 });
5929 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
5930}
5931
5932fn airIntFromFloat(self: *Self, inst: Air.Inst.Index) !void {
5933 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
5934 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airIntFromFloat for {}", .{
5935 self.target.cpu.arch,
5936 });
5937 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
5938}
5939
5940fn airCmpxchg(self: *Self, inst: Air.Inst.Index) !void {
5941 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
5942 const extra = self.air.extraData(Air.Block, ty_pl.payload);
5943 _ = extra;
5944
5945 return self.fail("TODO implement airCmpxchg for {}", .{
5946 self.target.cpu.arch,
5947 });
5948}
5949
5950fn airAtomicRmw(self: *Self, inst: Air.Inst.Index) !void {
5951 _ = inst;
5952 return self.fail("TODO implement airCmpxchg for {}", .{self.target.cpu.arch});
5953}
5954
5955fn airAtomicLoad(self: *Self, inst: Air.Inst.Index) !void {
5956 _ = inst;
5957 return self.fail("TODO implement airAtomicLoad for {}", .{self.target.cpu.arch});
5958}
5959
5960fn airAtomicStore(self: *Self, inst: Air.Inst.Index, order: std.builtin.AtomicOrder) !void {
5961 _ = inst;
5962 _ = order;
5963 return self.fail("TODO implement airAtomicStore for {}", .{self.target.cpu.arch});
5964}
5965
5966fn airMemset(self: *Self, inst: Air.Inst.Index, safety: bool) !void {
5967 if (safety) {
5968 // TODO if the value is undef, write 0xaa bytes to dest
5969 } else {
5970 // TODO if the value is undef, don't lower this instruction
5971 }
5972 _ = inst;
5973 return self.fail("TODO implement airMemset for {}", .{self.target.cpu.arch});
5974}
5975
5976fn airMemcpy(self: *Self, inst: Air.Inst.Index) !void {
5977 _ = inst;
5978 return self.fail("TODO implement airMemcpy for {}", .{self.target.cpu.arch});
5979}
5980
5981fn airMemmove(self: *Self, inst: Air.Inst.Index) !void {
5982 _ = inst;
5983 return self.fail("TODO implement airMemmove for {}", .{self.target.cpu.arch});
5984}
5985
5986fn airTagName(self: *Self, inst: Air.Inst.Index) !void {
5987 const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
5988 const operand = try self.resolveInst(un_op);
5989 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else {
5990 _ = operand;
5991 return self.fail("TODO implement airTagName for arm", .{});
5992 };
5993 return self.finishAir(inst, result, .{ un_op, .none, .none });
5994}
5995
5996fn airErrorName(self: *Self, inst: Air.Inst.Index) !void {
5997 const un_op = self.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
5998 const operand = try self.resolveInst(un_op);
5999 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else {
6000 _ = operand;
6001 return self.fail("TODO implement airErrorName for arm", .{});
6002 };
6003 return self.finishAir(inst, result, .{ un_op, .none, .none });
6004}
6005
6006fn airSplat(self: *Self, inst: Air.Inst.Index) !void {
6007 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
6008 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airSplat for arm", .{});
6009 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
6010}
6011
6012fn airSelect(self: *Self, inst: Air.Inst.Index) !void {
6013 const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
6014 const extra = self.air.extraData(Air.Bin, pl_op.payload).data;
6015 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airSelect for arm", .{});
6016 return self.finishAir(inst, result, .{ pl_op.operand, extra.lhs, extra.rhs });
6017}
6018
6019fn airShuffleOne(self: *Self, inst: Air.Inst.Index) !void {
6020 _ = inst;
6021 return self.fail("TODO implement airShuffleOne for arm", .{});
6022}
6023
6024fn airShuffleTwo(self: *Self, inst: Air.Inst.Index) !void {
6025 _ = inst;
6026 return self.fail("TODO implement airShuffleTwo for arm", .{});
6027}
6028
6029fn airReduce(self: *Self, inst: Air.Inst.Index) !void {
6030 const reduce = self.air.instructions.items(.data)[@intFromEnum(inst)].reduce;
6031 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airReduce for arm", .{});
6032 return self.finishAir(inst, result, .{ reduce.operand, .none, .none });
6033}
6034
6035fn airAggregateInit(self: *Self, inst: Air.Inst.Index) !void {
6036 const pt = self.pt;
6037 const zcu = pt.zcu;
6038 const vector_ty = self.typeOfIndex(inst);
6039 const len = vector_ty.vectorLen(zcu);
6040 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
6041 const elements: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[ty_pl.payload..][0..len]);
6042 const result: MCValue = res: {
6043 if (self.liveness.isUnused(inst)) break :res MCValue.dead;
6044 return self.fail("TODO implement airAggregateInit for arm", .{});
6045 };
6046
6047 if (elements.len <= Air.Liveness.bpi - 1) {
6048 var buf = [1]Air.Inst.Ref{.none} ** (Air.Liveness.bpi - 1);
6049 @memcpy(buf[0..elements.len], elements);
6050 return self.finishAir(inst, result, buf);
6051 }
6052 var bt = try self.iterateBigTomb(inst, elements.len);
6053 for (elements) |elem| {
6054 bt.feed(elem);
6055 }
6056 return bt.finishAir(result);
6057}
6058
6059fn airUnionInit(self: *Self, inst: Air.Inst.Index) !void {
6060 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
6061 const extra = self.air.extraData(Air.UnionInit, ty_pl.payload).data;
6062 _ = extra;
6063
6064 return self.fail("TODO implement airUnionInit for arm", .{});
6065}
6066
6067fn airPrefetch(self: *Self, inst: Air.Inst.Index) !void {
6068 const prefetch = self.air.instructions.items(.data)[@intFromEnum(inst)].prefetch;
6069 return self.finishAir(inst, MCValue.dead, .{ prefetch.ptr, .none, .none });
6070}
6071
6072fn airMulAdd(self: *Self, inst: Air.Inst.Index) !void {
6073 const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
6074 const extra = self.air.extraData(Air.Bin, pl_op.payload).data;
6075 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else {
6076 return self.fail("TODO implement airMulAdd for arm", .{});
6077 };
6078 return self.finishAir(inst, result, .{ extra.lhs, extra.rhs, pl_op.operand });
6079}
6080
6081fn airTry(self: *Self, inst: Air.Inst.Index) !void {
6082 const pt = self.pt;
6083 const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
6084 const extra = self.air.extraData(Air.Try, pl_op.payload);
6085 const body: []const Air.Inst.Index = @ptrCast(self.air.extra.items[extra.end..][0..extra.data.body_len]);
6086 const result: MCValue = result: {
6087 const error_union_bind: ReadArg.Bind = .{ .inst = pl_op.operand };
6088 const error_union_ty = self.typeOf(pl_op.operand);
6089 const error_union_size: u32 = @intCast(error_union_ty.abiSize(pt.zcu));
6090 const error_union_align = error_union_ty.abiAlignment(pt.zcu);
6091
6092 // The error union will die in the body. However, we need the
6093 // error union after the body in order to extract the payload
6094 // of the error union, so we create a copy of it
6095 const error_union_copy = try self.allocMem(error_union_size, error_union_align, null);
6096 try self.genSetStack(error_union_ty, error_union_copy, try error_union_bind.resolveToMcv(self));
6097
6098 const is_err_result = try self.isErr(error_union_bind, error_union_ty);
6099 const reloc = try self.condBr(is_err_result);
6100
6101 try self.genBody(body);
6102 try self.performReloc(reloc);
6103
6104 break :result try self.errUnionPayload(.{ .mcv = .{ .stack_offset = error_union_copy } }, error_union_ty, null);
6105 };
6106 return self.finishAir(inst, result, .{ pl_op.operand, .none, .none });
6107}
6108
6109fn airTryPtr(self: *Self, inst: Air.Inst.Index) !void {
6110 const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
6111 const extra = self.air.extraData(Air.TryPtr, ty_pl.payload);
6112 const body = self.air.extra.items[extra.end..][0..extra.data.body_len];
6113 _ = body;
6114 return self.fail("TODO implement airTryPtr for arm", .{});
6115 // return self.finishAir(inst, result, .{ extra.data.ptr, .none, .none });
6116}
6117
6118fn resolveInst(self: *Self, inst: Air.Inst.Ref) InnerError!MCValue {
6119 const pt = self.pt;
6120 const zcu = pt.zcu;
6121
6122 // If the type has no codegen bits, no need to store it.
6123 const inst_ty = self.typeOf(inst);
6124 if (!inst_ty.hasRuntimeBitsIgnoreComptime(zcu) and !inst_ty.isError(zcu))
6125 return MCValue{ .none = {} };
6126
6127 const inst_index = inst.toIndex() orelse return self.genTypedValue((try self.air.value(inst, pt)).?);
6128
6129 return self.getResolvedInstValue(inst_index);
6130}
6131
6132fn getResolvedInstValue(self: *Self, inst: Air.Inst.Index) MCValue {
6133 // Treat each stack item as a "layer" on top of the previous one.
6134 var i: usize = self.branch_stack.items.len;
6135 while (true) {
6136 i -= 1;
6137 if (self.branch_stack.items[i].inst_table.get(inst)) |mcv| {
6138 assert(mcv != .dead);
6139 return mcv;
6140 }
6141 }
6142}
6143
6144fn genTypedValue(self: *Self, val: Value) InnerError!MCValue {
6145 const pt = self.pt;
6146 const mcv: MCValue = switch (try codegen.genTypedValue(
6147 self.bin_file,
6148 pt,
6149 self.src_loc,
6150 val,
6151 self.target,
6152 )) {
6153 .mcv => |mcv| switch (mcv) {
6154 .none => .none,
6155 .undef => .undef,
6156 .load_got, .load_symbol, .load_direct, .lea_symbol, .lea_direct => unreachable, // TODO
6157 .immediate => |imm| .{ .immediate = @truncate(imm) },
6158 .memory => |addr| .{ .memory = addr },
6159 },
6160 .fail => |msg| {
6161 self.err_msg = msg;
6162 return error.CodegenFail;
6163 },
6164 };
6165 return mcv;
6166}
6167
6168const CallMCValues = struct {
6169 args: []MCValue,
6170 return_value: MCValue,
6171 stack_byte_count: u32,
6172 stack_align: u32,
6173
6174 fn deinit(self: *CallMCValues, func: *Self) void {
6175 func.gpa.free(self.args);
6176 self.* = undefined;
6177 }
6178};
6179
6180/// Caller must call `CallMCValues.deinit`.
6181fn resolveCallingConventionValues(self: *Self, fn_ty: Type) !CallMCValues {
6182 const pt = self.pt;
6183 const zcu = pt.zcu;
6184 const ip = &zcu.intern_pool;
6185 const fn_info = zcu.typeToFunc(fn_ty).?;
6186 const cc = fn_info.cc;
6187 var result: CallMCValues = .{
6188 .args = try self.gpa.alloc(MCValue, fn_info.param_types.len),
6189 // These undefined values must be populated before returning from this function.
6190 .return_value = undefined,
6191 .stack_byte_count = undefined,
6192 .stack_align = undefined,
6193 };
6194 errdefer self.gpa.free(result.args);
6195
6196 const ret_ty = fn_ty.fnReturnType(zcu);
6197
6198 switch (cc) {
6199 .naked => {
6200 assert(result.args.len == 0);
6201 result.return_value = .{ .unreach = {} };
6202 result.stack_byte_count = 0;
6203 result.stack_align = 1;
6204 return result;
6205 },
6206 .arm_aapcs => {
6207 // ARM Procedure Call Standard, Chapter 6.5
6208 var ncrn: usize = 0; // Next Core Register Number
6209 var nsaa: u32 = 0; // Next stacked argument address
6210
6211 if (ret_ty.zigTypeTag(zcu) == .noreturn) {
6212 result.return_value = .{ .unreach = {} };
6213 } else if (!ret_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
6214 result.return_value = .{ .none = {} };
6215 } else {
6216 const ret_ty_size: u32 = @intCast(ret_ty.abiSize(zcu));
6217 // TODO handle cases where multiple registers are used
6218 if (ret_ty_size <= 4) {
6219 result.return_value = .{ .register = c_abi_int_return_regs[0] };
6220 } else {
6221 // The result is returned by reference, not by
6222 // value. This means that r0 will contain the
6223 // address of where this function should write the
6224 // result into.
6225 result.return_value = .{ .stack_offset = 0 };
6226 ncrn = 1;
6227 }
6228 }
6229
6230 for (fn_info.param_types.get(ip), result.args) |ty, *result_arg| {
6231 if (Type.fromInterned(ty).abiAlignment(zcu) == .@"8")
6232 ncrn = std.mem.alignForward(usize, ncrn, 2);
6233
6234 const param_size: u32 = @intCast(Type.fromInterned(ty).abiSize(zcu));
6235 if (std.math.divCeil(u32, param_size, 4) catch unreachable <= 4 - ncrn) {
6236 if (param_size <= 4) {
6237 result_arg.* = .{ .register = c_abi_int_param_regs[ncrn] };
6238 ncrn += 1;
6239 } else {
6240 return self.fail("TODO MCValues with multiple registers", .{});
6241 }
6242 } else if (ncrn < 4 and nsaa == 0) {
6243 return self.fail("TODO MCValues split between registers and stack", .{});
6244 } else {
6245 ncrn = 4;
6246 if (Type.fromInterned(ty).abiAlignment(zcu) == .@"8")
6247 nsaa = std.mem.alignForward(u32, nsaa, 8);
6248
6249 result_arg.* = .{ .stack_argument_offset = nsaa };
6250 nsaa += param_size;
6251 }
6252 }
6253
6254 result.stack_byte_count = nsaa;
6255 result.stack_align = 8;
6256 },
6257 .auto => {
6258 if (ret_ty.zigTypeTag(zcu) == .noreturn) {
6259 result.return_value = .{ .unreach = {} };
6260 } else if (!ret_ty.hasRuntimeBitsIgnoreComptime(zcu) and !ret_ty.isError(zcu)) {
6261 result.return_value = .{ .none = {} };
6262 } else {
6263 const ret_ty_size: u32 = @intCast(ret_ty.abiSize(zcu));
6264 if (ret_ty_size == 0) {
6265 assert(ret_ty.isError(zcu));
6266 result.return_value = .{ .immediate = 0 };
6267 } else if (ret_ty_size <= 4) {
6268 result.return_value = .{ .register = .r0 };
6269 } else {
6270 // The result is returned by reference, not by
6271 // value. This means that r0 will contain the
6272 // address of where this function should write the
6273 // result into.
6274 result.return_value = .{ .stack_offset = 0 };
6275 }
6276 }
6277
6278 var stack_offset: u32 = 0;
6279
6280 for (fn_info.param_types.get(ip), result.args) |ty, *result_arg| {
6281 if (Type.fromInterned(ty).abiSize(zcu) > 0) {
6282 const param_size: u32 = @intCast(Type.fromInterned(ty).abiSize(zcu));
6283 const param_alignment = Type.fromInterned(ty).abiAlignment(zcu);
6284
6285 stack_offset = @intCast(param_alignment.forward(stack_offset));
6286 result_arg.* = .{ .stack_argument_offset = stack_offset };
6287 stack_offset += param_size;
6288 } else {
6289 result_arg.* = .{ .none = {} };
6290 }
6291 }
6292
6293 result.stack_byte_count = stack_offset;
6294 result.stack_align = 8;
6295 },
6296 else => return self.fail("TODO implement function parameters for {} on arm", .{cc}),
6297 }
6298
6299 return result;
6300}
6301
6302/// TODO support scope overrides. Also note this logic is duplicated with `Zcu.wantSafety`.
6303fn wantSafety(self: *Self) bool {
6304 return switch (self.bin_file.comp.root_mod.optimize_mode) {
6305 .Debug => true,
6306 .ReleaseSafe => true,
6307 .ReleaseFast => false,
6308 .ReleaseSmall => false,
6309 };
6310}
6311
6312fn fail(self: *Self, comptime format: []const u8, args: anytype) error{ OutOfMemory, CodegenFail } {
6313 @branchHint(.cold);
6314 const zcu = self.pt.zcu;
6315 const func = zcu.funcInfo(self.func_index);
6316 const msg = try ErrorMsg.create(zcu.gpa, self.src_loc, format, args);
6317 return zcu.codegenFailMsg(func.owner_nav, msg);
6318}
6319
6320fn failMsg(self: *Self, msg: *ErrorMsg) error{ OutOfMemory, CodegenFail } {
6321 @branchHint(.cold);
6322 const zcu = self.pt.zcu;
6323 const func = zcu.funcInfo(self.func_index);
6324 return zcu.codegenFailMsg(func.owner_nav, msg);
6325}
6326
6327fn parseRegName(name: []const u8) ?Register {
6328 if (@hasDecl(Register, "parseRegName")) {
6329 return Register.parseRegName(name);
6330 }
6331 return std.meta.stringToEnum(Register, name);
6332}
6333
6334fn typeOf(self: *Self, inst: Air.Inst.Ref) Type {
6335 return self.air.typeOf(inst, &self.pt.zcu.intern_pool);
6336}
6337
6338fn typeOfIndex(self: *Self, inst: Air.Inst.Index) Type {
6339 return self.air.typeOfIndex(inst, &self.pt.zcu.intern_pool);
6340}
src/arch/arm/Emit.zig deleted-712
......@@ -1,712 +0,0 @@
1//! This file contains the functionality for lowering AArch32 MIR into
2//! machine code
3
4const Emit = @This();
5const builtin = @import("builtin");
6const std = @import("std");
7const math = std.math;
8const Mir = @import("Mir.zig");
9const bits = @import("bits.zig");
10const link = @import("../../link.zig");
11const Zcu = @import("../../Zcu.zig");
12const Type = @import("../../Type.zig");
13const ErrorMsg = Zcu.ErrorMsg;
14const Target = std.Target;
15const assert = std.debug.assert;
16const Instruction = bits.Instruction;
17const Register = bits.Register;
18const log = std.log.scoped(.aarch32_emit);
19const CodeGen = @import("CodeGen.zig");
20
21mir: Mir,
22bin_file: *link.File,
23debug_output: link.File.DebugInfoOutput,
24target: *const std.Target,
25err_msg: ?*ErrorMsg = null,
26src_loc: Zcu.LazySrcLoc,
27code: *std.ArrayListUnmanaged(u8),
28
29prev_di_line: u32,
30prev_di_column: u32,
31/// Relative to the beginning of `code`.
32prev_di_pc: usize,
33
34/// The amount of stack space consumed by the saved callee-saved
35/// registers in bytes
36saved_regs_stack_space: u32,
37
38/// The final stack frame size of the function (already aligned to the
39/// respective stack alignment). Does not include prologue stack space.
40stack_size: u32,
41
42/// The branch type of every branch
43branch_types: std.AutoHashMapUnmanaged(Mir.Inst.Index, BranchType) = .empty,
44/// For every forward branch, maps the target instruction to a list of
45/// branches which branch to this target instruction
46branch_forward_origins: std.AutoHashMapUnmanaged(Mir.Inst.Index, std.ArrayListUnmanaged(Mir.Inst.Index)) = .empty,
47/// For backward branches: stores the code offset of the target
48/// instruction
49///
50/// For forward branches: stores the code offset of the branch
51/// instruction
52code_offset_mapping: std.AutoHashMapUnmanaged(Mir.Inst.Index, usize) = .empty,
53
54const InnerError = error{
55 OutOfMemory,
56 EmitFail,
57};
58
59const BranchType = enum {
60 b,
61
62 fn default(tag: Mir.Inst.Tag) BranchType {
63 return switch (tag) {
64 .b => .b,
65 else => unreachable,
66 };
67 }
68};
69
70pub fn emitMir(
71 emit: *Emit,
72) !void {
73 const mir_tags = emit.mir.instructions.items(.tag);
74
75 // Find smallest lowerings for branch instructions
76 try emit.lowerBranches();
77
78 // Emit machine code
79 for (mir_tags, 0..) |tag, index| {
80 const inst = @as(u32, @intCast(index));
81 switch (tag) {
82 .add => try emit.mirDataProcessing(inst),
83 .adds => try emit.mirDataProcessing(inst),
84 .@"and" => try emit.mirDataProcessing(inst),
85 .cmp => try emit.mirDataProcessing(inst),
86 .eor => try emit.mirDataProcessing(inst),
87 .mov => try emit.mirDataProcessing(inst),
88 .mvn => try emit.mirDataProcessing(inst),
89 .orr => try emit.mirDataProcessing(inst),
90 .rsb => try emit.mirDataProcessing(inst),
91 .sub => try emit.mirDataProcessing(inst),
92 .subs => try emit.mirDataProcessing(inst),
93
94 .sub_sp_scratch_r4 => try emit.mirSubStackPointer(inst),
95
96 .asr => try emit.mirShift(inst),
97 .lsl => try emit.mirShift(inst),
98 .lsr => try emit.mirShift(inst),
99
100 .b => try emit.mirBranch(inst),
101
102 .undefined_instruction => try emit.mirUndefinedInstruction(),
103 .bkpt => try emit.mirExceptionGeneration(inst),
104
105 .blx => try emit.mirBranchExchange(inst),
106 .bx => try emit.mirBranchExchange(inst),
107
108 .dbg_line => try emit.mirDbgLine(inst),
109
110 .dbg_prologue_end => try emit.mirDebugPrologueEnd(),
111
112 .dbg_epilogue_begin => try emit.mirDebugEpilogueBegin(),
113
114 .ldr => try emit.mirLoadStore(inst),
115 .ldrb => try emit.mirLoadStore(inst),
116 .str => try emit.mirLoadStore(inst),
117 .strb => try emit.mirLoadStore(inst),
118
119 .ldr_ptr_stack_argument => try emit.mirLoadStackArgument(inst),
120 .ldr_stack_argument => try emit.mirLoadStackArgument(inst),
121 .ldrb_stack_argument => try emit.mirLoadStackArgument(inst),
122 .ldrh_stack_argument => try emit.mirLoadStackArgument(inst),
123 .ldrsb_stack_argument => try emit.mirLoadStackArgument(inst),
124 .ldrsh_stack_argument => try emit.mirLoadStackArgument(inst),
125
126 .ldrh => try emit.mirLoadStoreExtra(inst),
127 .ldrsb => try emit.mirLoadStoreExtra(inst),
128 .ldrsh => try emit.mirLoadStoreExtra(inst),
129 .strh => try emit.mirLoadStoreExtra(inst),
130
131 .movw => try emit.mirSpecialMove(inst),
132 .movt => try emit.mirSpecialMove(inst),
133
134 .mul => try emit.mirMultiply(inst),
135 .smulbb => try emit.mirMultiply(inst),
136
137 .smull => try emit.mirMultiplyLong(inst),
138 .umull => try emit.mirMultiplyLong(inst),
139
140 .nop => try emit.mirNop(),
141
142 .pop => try emit.mirBlockDataTransfer(inst),
143 .push => try emit.mirBlockDataTransfer(inst),
144
145 .svc => try emit.mirSupervisorCall(inst),
146
147 .sbfx => try emit.mirBitFieldExtract(inst),
148 .ubfx => try emit.mirBitFieldExtract(inst),
149 }
150 }
151}
152
153pub fn deinit(emit: *Emit) void {
154 const comp = emit.bin_file.comp;
155 const gpa = comp.gpa;
156
157 var iter = emit.branch_forward_origins.valueIterator();
158 while (iter.next()) |origin_list| {
159 origin_list.deinit(gpa);
160 }
161
162 emit.branch_types.deinit(gpa);
163 emit.branch_forward_origins.deinit(gpa);
164 emit.code_offset_mapping.deinit(gpa);
165 emit.* = undefined;
166}
167
168fn optimalBranchType(emit: *Emit, tag: Mir.Inst.Tag, offset: i64) !BranchType {
169 assert(std.mem.isAlignedGeneric(i64, offset, 4)); // misaligned offset
170
171 switch (tag) {
172 .b => {
173 if (std.math.cast(i24, @divExact(offset, 4))) |_| {
174 return BranchType.b;
175 } else {
176 return emit.fail("TODO support larger branches", .{});
177 }
178 },
179 else => unreachable,
180 }
181}
182
183fn instructionSize(emit: *Emit, inst: Mir.Inst.Index) usize {
184 const tag = emit.mir.instructions.items(.tag)[inst];
185
186 if (isBranch(tag)) {
187 switch (emit.branch_types.get(inst).?) {
188 .b => return 4,
189 }
190 }
191
192 switch (tag) {
193 .dbg_line,
194 .dbg_epilogue_begin,
195 .dbg_prologue_end,
196 => return 0,
197
198 .sub_sp_scratch_r4 => {
199 const imm32 = emit.mir.instructions.items(.data)[inst].imm32;
200
201 if (imm32 == 0) {
202 return 0 * 4;
203 } else if (Instruction.Operand.fromU32(imm32) != null) {
204 // sub
205 return 1 * 4;
206 } else if (emit.target.cpu.has(.arm, .has_v7)) {
207 // movw; movt; sub
208 return 3 * 4;
209 } else {
210 // mov; orr; orr; orr; sub
211 return 5 * 4;
212 }
213 },
214
215 else => return 4,
216 }
217}
218
219fn isBranch(tag: Mir.Inst.Tag) bool {
220 return switch (tag) {
221 .b => true,
222 else => false,
223 };
224}
225
226fn branchTarget(emit: *Emit, inst: Mir.Inst.Index) Mir.Inst.Index {
227 const tag = emit.mir.instructions.items(.tag)[inst];
228
229 switch (tag) {
230 .b => return emit.mir.instructions.items(.data)[inst].inst,
231 else => unreachable,
232 }
233}
234
235fn lowerBranches(emit: *Emit) !void {
236 const comp = emit.bin_file.comp;
237 const gpa = comp.gpa;
238 const mir_tags = emit.mir.instructions.items(.tag);
239
240 // First pass: Note down all branches and their target
241 // instructions, i.e. populate branch_types,
242 // branch_forward_origins, and code_offset_mapping
243 //
244 // TODO optimization opportunity: do this in codegen while
245 // generating MIR
246 for (mir_tags, 0..) |tag, index| {
247 const inst = @as(u32, @intCast(index));
248 if (isBranch(tag)) {
249 const target_inst = emit.branchTarget(inst);
250
251 // Remember this branch instruction
252 try emit.branch_types.put(gpa, inst, BranchType.default(tag));
253
254 // Forward branches require some extra stuff: We only
255 // know their offset once we arrive at the target
256 // instruction. Therefore, we need to be able to
257 // access the branch instruction when we visit the
258 // target instruction in order to manipulate its type
259 // etc.
260 if (target_inst > inst) {
261 // Remember the branch instruction index
262 try emit.code_offset_mapping.put(gpa, inst, 0);
263
264 if (emit.branch_forward_origins.getPtr(target_inst)) |origin_list| {
265 try origin_list.append(gpa, inst);
266 } else {
267 var origin_list: std.ArrayListUnmanaged(Mir.Inst.Index) = .empty;
268 try origin_list.append(gpa, inst);
269 try emit.branch_forward_origins.put(gpa, target_inst, origin_list);
270 }
271 }
272
273 // Remember the target instruction index so that we
274 // update the real code offset in all future passes
275 //
276 // putNoClobber may not be used as the put operation
277 // may clobber the entry when multiple branches branch
278 // to the same target instruction
279 try emit.code_offset_mapping.put(gpa, target_inst, 0);
280 }
281 }
282
283 // Further passes: Until all branches are lowered, interate
284 // through all instructions and calculate new offsets and
285 // potentially new branch types
286 var all_branches_lowered = false;
287 while (!all_branches_lowered) {
288 all_branches_lowered = true;
289 var current_code_offset: usize = 0;
290
291 for (mir_tags, 0..) |tag, index| {
292 const inst = @as(u32, @intCast(index));
293
294 // If this instruction contained in the code offset
295 // mapping (when it is a target of a branch or if it is a
296 // forward branch), update the code offset
297 if (emit.code_offset_mapping.getPtr(inst)) |offset| {
298 offset.* = current_code_offset;
299 }
300
301 // If this instruction is a backward branch, calculate the
302 // offset, which may potentially update the branch type
303 if (isBranch(tag)) {
304 const target_inst = emit.branchTarget(inst);
305 if (target_inst < inst) {
306 const target_offset = emit.code_offset_mapping.get(target_inst).?;
307 const offset = @as(i64, @intCast(target_offset)) - @as(i64, @intCast(current_code_offset + 8));
308 const branch_type = emit.branch_types.getPtr(inst).?;
309 const optimal_branch_type = try emit.optimalBranchType(tag, offset);
310 if (branch_type.* != optimal_branch_type) {
311 branch_type.* = optimal_branch_type;
312 all_branches_lowered = false;
313 }
314
315 log.debug("lowerBranches: branch {} has offset {}", .{ inst, offset });
316 }
317 }
318
319 // If this instruction is the target of one or more
320 // forward branches, calculate the offset, which may
321 // potentially update the branch type
322 if (emit.branch_forward_origins.get(inst)) |origin_list| {
323 for (origin_list.items) |forward_branch_inst| {
324 const branch_tag = emit.mir.instructions.items(.tag)[forward_branch_inst];
325 const forward_branch_inst_offset = emit.code_offset_mapping.get(forward_branch_inst).?;
326 const offset = @as(i64, @intCast(current_code_offset)) - @as(i64, @intCast(forward_branch_inst_offset + 8));
327 const branch_type = emit.branch_types.getPtr(forward_branch_inst).?;
328 const optimal_branch_type = try emit.optimalBranchType(branch_tag, offset);
329 if (branch_type.* != optimal_branch_type) {
330 branch_type.* = optimal_branch_type;
331 all_branches_lowered = false;
332 }
333
334 log.debug("lowerBranches: branch {} has offset {}", .{ forward_branch_inst, offset });
335 }
336 }
337
338 // Increment code offset
339 current_code_offset += emit.instructionSize(inst);
340 }
341 }
342}
343
344fn writeInstruction(emit: *Emit, instruction: Instruction) !void {
345 const comp = emit.bin_file.comp;
346 const gpa = comp.gpa;
347 const endian = emit.target.cpu.arch.endian();
348 std.mem.writeInt(u32, try emit.code.addManyAsArray(gpa, 4), instruction.toU32(), endian);
349}
350
351fn fail(emit: *Emit, comptime format: []const u8, args: anytype) InnerError {
352 @branchHint(.cold);
353 assert(emit.err_msg == null);
354 const comp = emit.bin_file.comp;
355 const gpa = comp.gpa;
356 emit.err_msg = try ErrorMsg.create(gpa, emit.src_loc, format, args);
357 return error.EmitFail;
358}
359
360fn dbgAdvancePCAndLine(self: *Emit, line: u32, column: u32) !void {
361 const delta_line = @as(i32, @intCast(line)) - @as(i32, @intCast(self.prev_di_line));
362 const delta_pc: usize = self.code.items.len - self.prev_di_pc;
363 switch (self.debug_output) {
364 .dwarf => |dw| {
365 try dw.advancePCAndLine(delta_line, delta_pc);
366 self.prev_di_line = line;
367 self.prev_di_column = column;
368 self.prev_di_pc = self.code.items.len;
369 },
370 .plan9 => |dbg_out| {
371 if (delta_pc <= 0) return; // only do this when the pc changes
372
373 // increasing the line number
374 try link.File.Plan9.changeLine(&dbg_out.dbg_line, delta_line);
375 // increasing the pc
376 const d_pc_p9 = @as(i64, @intCast(delta_pc)) - dbg_out.pc_quanta;
377 if (d_pc_p9 > 0) {
378 // minus one because if its the last one, we want to leave space to change the line which is one pc quanta
379 try dbg_out.dbg_line.append(@as(u8, @intCast(@divExact(d_pc_p9, dbg_out.pc_quanta) + 128)) - dbg_out.pc_quanta);
380 if (dbg_out.pcop_change_index) |pci|
381 dbg_out.dbg_line.items[pci] += 1;
382 dbg_out.pcop_change_index = @as(u32, @intCast(dbg_out.dbg_line.items.len - 1));
383 } else if (d_pc_p9 == 0) {
384 // we don't need to do anything, because adding the pc quanta does it for us
385 } else unreachable;
386 if (dbg_out.start_line == null)
387 dbg_out.start_line = self.prev_di_line;
388 dbg_out.end_line = line;
389 // only do this if the pc changed
390 self.prev_di_line = line;
391 self.prev_di_column = column;
392 self.prev_di_pc = self.code.items.len;
393 },
394 .none => {},
395 }
396}
397
398fn mirDataProcessing(emit: *Emit, inst: Mir.Inst.Index) !void {
399 const tag = emit.mir.instructions.items(.tag)[inst];
400 const cond = emit.mir.instructions.items(.cond)[inst];
401
402 switch (tag) {
403 .add,
404 .adds,
405 .@"and",
406 .eor,
407 .orr,
408 .rsb,
409 .sub,
410 .subs,
411 => {
412 const rr_op = emit.mir.instructions.items(.data)[inst].rr_op;
413 switch (tag) {
414 .add => try emit.writeInstruction(Instruction.add(cond, rr_op.rd, rr_op.rn, rr_op.op)),
415 .adds => try emit.writeInstruction(Instruction.adds(cond, rr_op.rd, rr_op.rn, rr_op.op)),
416 .@"and" => try emit.writeInstruction(Instruction.@"and"(cond, rr_op.rd, rr_op.rn, rr_op.op)),
417 .eor => try emit.writeInstruction(Instruction.eor(cond, rr_op.rd, rr_op.rn, rr_op.op)),
418 .orr => try emit.writeInstruction(Instruction.orr(cond, rr_op.rd, rr_op.rn, rr_op.op)),
419 .rsb => try emit.writeInstruction(Instruction.rsb(cond, rr_op.rd, rr_op.rn, rr_op.op)),
420 .sub => try emit.writeInstruction(Instruction.sub(cond, rr_op.rd, rr_op.rn, rr_op.op)),
421 .subs => try emit.writeInstruction(Instruction.subs(cond, rr_op.rd, rr_op.rn, rr_op.op)),
422 else => unreachable,
423 }
424 },
425 .cmp => {
426 const r_op_cmp = emit.mir.instructions.items(.data)[inst].r_op_cmp;
427 try emit.writeInstruction(Instruction.cmp(cond, r_op_cmp.rn, r_op_cmp.op));
428 },
429 .mov,
430 .mvn,
431 => {
432 const r_op_mov = emit.mir.instructions.items(.data)[inst].r_op_mov;
433 switch (tag) {
434 .mov => try emit.writeInstruction(Instruction.mov(cond, r_op_mov.rd, r_op_mov.op)),
435 .mvn => try emit.writeInstruction(Instruction.mvn(cond, r_op_mov.rd, r_op_mov.op)),
436 else => unreachable,
437 }
438 },
439 else => unreachable,
440 }
441}
442
443fn mirSubStackPointer(emit: *Emit, inst: Mir.Inst.Index) !void {
444 const tag = emit.mir.instructions.items(.tag)[inst];
445 const cond = emit.mir.instructions.items(.cond)[inst];
446 const imm32 = emit.mir.instructions.items(.data)[inst].imm32;
447
448 switch (tag) {
449 .sub_sp_scratch_r4 => {
450 if (imm32 == 0) return;
451
452 const operand = Instruction.Operand.fromU32(imm32) orelse blk: {
453 const scratch: Register = .r4;
454
455 if (emit.target.cpu.has(.arm, .has_v7)) {
456 try emit.writeInstruction(Instruction.movw(cond, scratch, @as(u16, @truncate(imm32))));
457 try emit.writeInstruction(Instruction.movt(cond, scratch, @as(u16, @truncate(imm32 >> 16))));
458 } else {
459 try emit.writeInstruction(Instruction.mov(cond, scratch, Instruction.Operand.imm(@as(u8, @truncate(imm32)), 0)));
460 try emit.writeInstruction(Instruction.orr(cond, scratch, scratch, Instruction.Operand.imm(@as(u8, @truncate(imm32 >> 8)), 12)));
461 try emit.writeInstruction(Instruction.orr(cond, scratch, scratch, Instruction.Operand.imm(@as(u8, @truncate(imm32 >> 16)), 8)));
462 try emit.writeInstruction(Instruction.orr(cond, scratch, scratch, Instruction.Operand.imm(@as(u8, @truncate(imm32 >> 24)), 4)));
463 }
464
465 break :blk Instruction.Operand.reg(scratch, Instruction.Operand.Shift.none);
466 };
467
468 try emit.writeInstruction(Instruction.sub(cond, .sp, .sp, operand));
469 },
470 else => unreachable,
471 }
472}
473
474fn mirShift(emit: *Emit, inst: Mir.Inst.Index) !void {
475 const tag = emit.mir.instructions.items(.tag)[inst];
476 const cond = emit.mir.instructions.items(.cond)[inst];
477 const rr_shift = emit.mir.instructions.items(.data)[inst].rr_shift;
478
479 switch (tag) {
480 .asr => try emit.writeInstruction(Instruction.asr(cond, rr_shift.rd, rr_shift.rm, rr_shift.shift_amount)),
481 .lsl => try emit.writeInstruction(Instruction.lsl(cond, rr_shift.rd, rr_shift.rm, rr_shift.shift_amount)),
482 .lsr => try emit.writeInstruction(Instruction.lsr(cond, rr_shift.rd, rr_shift.rm, rr_shift.shift_amount)),
483 else => unreachable,
484 }
485}
486
487fn mirBranch(emit: *Emit, inst: Mir.Inst.Index) !void {
488 const tag = emit.mir.instructions.items(.tag)[inst];
489 const cond = emit.mir.instructions.items(.cond)[inst];
490 const target_inst = emit.mir.instructions.items(.data)[inst].inst;
491
492 const offset = @as(i64, @intCast(emit.code_offset_mapping.get(target_inst).?)) - @as(i64, @intCast(emit.code.items.len + 8));
493 const branch_type = emit.branch_types.get(inst).?;
494
495 switch (branch_type) {
496 .b => switch (tag) {
497 .b => try emit.writeInstruction(Instruction.b(cond, @as(i26, @intCast(offset)))),
498 else => unreachable,
499 },
500 }
501}
502
503fn mirUndefinedInstruction(emit: *Emit) !void {
504 try emit.writeInstruction(Instruction.undefinedInstruction());
505}
506
507fn mirExceptionGeneration(emit: *Emit, inst: Mir.Inst.Index) !void {
508 const tag = emit.mir.instructions.items(.tag)[inst];
509 const imm16 = emit.mir.instructions.items(.data)[inst].imm16;
510
511 switch (tag) {
512 .bkpt => try emit.writeInstruction(Instruction.bkpt(imm16)),
513 else => unreachable,
514 }
515}
516
517fn mirBranchExchange(emit: *Emit, inst: Mir.Inst.Index) !void {
518 const tag = emit.mir.instructions.items(.tag)[inst];
519 const cond = emit.mir.instructions.items(.cond)[inst];
520 const reg = emit.mir.instructions.items(.data)[inst].reg;
521
522 switch (tag) {
523 .blx => try emit.writeInstruction(Instruction.blx(cond, reg)),
524 .bx => try emit.writeInstruction(Instruction.bx(cond, reg)),
525 else => unreachable,
526 }
527}
528
529fn mirDbgLine(emit: *Emit, inst: Mir.Inst.Index) !void {
530 const tag = emit.mir.instructions.items(.tag)[inst];
531 const dbg_line_column = emit.mir.instructions.items(.data)[inst].dbg_line_column;
532
533 switch (tag) {
534 .dbg_line => try emit.dbgAdvancePCAndLine(dbg_line_column.line, dbg_line_column.column),
535 else => unreachable,
536 }
537}
538
539fn mirDebugPrologueEnd(emit: *Emit) !void {
540 switch (emit.debug_output) {
541 .dwarf => |dw| {
542 try dw.setPrologueEnd();
543 try emit.dbgAdvancePCAndLine(emit.prev_di_line, emit.prev_di_column);
544 },
545 .plan9 => {},
546 .none => {},
547 }
548}
549
550fn mirDebugEpilogueBegin(emit: *Emit) !void {
551 switch (emit.debug_output) {
552 .dwarf => |dw| {
553 try dw.setEpilogueBegin();
554 try emit.dbgAdvancePCAndLine(emit.prev_di_line, emit.prev_di_column);
555 },
556 .plan9 => {},
557 .none => {},
558 }
559}
560
561fn mirLoadStore(emit: *Emit, inst: Mir.Inst.Index) !void {
562 const tag = emit.mir.instructions.items(.tag)[inst];
563 const cond = emit.mir.instructions.items(.cond)[inst];
564 const rr_offset = emit.mir.instructions.items(.data)[inst].rr_offset;
565
566 switch (tag) {
567 .ldr => try emit.writeInstruction(Instruction.ldr(cond, rr_offset.rt, rr_offset.rn, rr_offset.offset)),
568 .ldrb => try emit.writeInstruction(Instruction.ldrb(cond, rr_offset.rt, rr_offset.rn, rr_offset.offset)),
569 .str => try emit.writeInstruction(Instruction.str(cond, rr_offset.rt, rr_offset.rn, rr_offset.offset)),
570 .strb => try emit.writeInstruction(Instruction.strb(cond, rr_offset.rt, rr_offset.rn, rr_offset.offset)),
571 else => unreachable,
572 }
573}
574
575fn mirLoadStackArgument(emit: *Emit, inst: Mir.Inst.Index) !void {
576 const tag = emit.mir.instructions.items(.tag)[inst];
577 const cond = emit.mir.instructions.items(.cond)[inst];
578 const r_stack_offset = emit.mir.instructions.items(.data)[inst].r_stack_offset;
579 const rt = r_stack_offset.rt;
580
581 const raw_offset = emit.stack_size + emit.saved_regs_stack_space + r_stack_offset.stack_offset;
582 switch (tag) {
583 .ldr_ptr_stack_argument => {
584 const operand = Instruction.Operand.fromU32(raw_offset) orelse
585 return emit.fail("TODO mirLoadStack larger offsets", .{});
586
587 try emit.writeInstruction(Instruction.add(cond, rt, .sp, operand));
588 },
589 .ldr_stack_argument,
590 .ldrb_stack_argument,
591 => {
592 const offset = if (raw_offset <= math.maxInt(u12)) blk: {
593 break :blk Instruction.Offset.imm(@as(u12, @intCast(raw_offset)));
594 } else return emit.fail("TODO mirLoadStack larger offsets", .{});
595
596 switch (tag) {
597 .ldr_stack_argument => try emit.writeInstruction(Instruction.ldr(cond, rt, .sp, .{ .offset = offset })),
598 .ldrb_stack_argument => try emit.writeInstruction(Instruction.ldrb(cond, rt, .sp, .{ .offset = offset })),
599 else => unreachable,
600 }
601 },
602 .ldrh_stack_argument,
603 .ldrsb_stack_argument,
604 .ldrsh_stack_argument,
605 => {
606 const offset = if (raw_offset <= math.maxInt(u8)) blk: {
607 break :blk Instruction.ExtraLoadStoreOffset.imm(@as(u8, @intCast(raw_offset)));
608 } else return emit.fail("TODO mirLoadStack larger offsets", .{});
609
610 switch (tag) {
611 .ldrh_stack_argument => try emit.writeInstruction(Instruction.ldrh(cond, rt, .sp, .{ .offset = offset })),
612 .ldrsb_stack_argument => try emit.writeInstruction(Instruction.ldrsb(cond, rt, .sp, .{ .offset = offset })),
613 .ldrsh_stack_argument => try emit.writeInstruction(Instruction.ldrsh(cond, rt, .sp, .{ .offset = offset })),
614 else => unreachable,
615 }
616 },
617 else => unreachable,
618 }
619}
620
621fn mirLoadStoreExtra(emit: *Emit, inst: Mir.Inst.Index) !void {
622 const tag = emit.mir.instructions.items(.tag)[inst];
623 const cond = emit.mir.instructions.items(.cond)[inst];
624 const rr_extra_offset = emit.mir.instructions.items(.data)[inst].rr_extra_offset;
625
626 switch (tag) {
627 .ldrh => try emit.writeInstruction(Instruction.ldrh(cond, rr_extra_offset.rt, rr_extra_offset.rn, rr_extra_offset.offset)),
628 .ldrsb => try emit.writeInstruction(Instruction.ldrsb(cond, rr_extra_offset.rt, rr_extra_offset.rn, rr_extra_offset.offset)),
629 .ldrsh => try emit.writeInstruction(Instruction.ldrsh(cond, rr_extra_offset.rt, rr_extra_offset.rn, rr_extra_offset.offset)),
630 .strh => try emit.writeInstruction(Instruction.strh(cond, rr_extra_offset.rt, rr_extra_offset.rn, rr_extra_offset.offset)),
631 else => unreachable,
632 }
633}
634
635fn mirSpecialMove(emit: *Emit, inst: Mir.Inst.Index) !void {
636 const tag = emit.mir.instructions.items(.tag)[inst];
637 const cond = emit.mir.instructions.items(.cond)[inst];
638 const r_imm16 = emit.mir.instructions.items(.data)[inst].r_imm16;
639
640 switch (tag) {
641 .movw => try emit.writeInstruction(Instruction.movw(cond, r_imm16.rd, r_imm16.imm16)),
642 .movt => try emit.writeInstruction(Instruction.movt(cond, r_imm16.rd, r_imm16.imm16)),
643 else => unreachable,
644 }
645}
646
647fn mirMultiply(emit: *Emit, inst: Mir.Inst.Index) !void {
648 const tag = emit.mir.instructions.items(.tag)[inst];
649 const cond = emit.mir.instructions.items(.cond)[inst];
650 const rrr = emit.mir.instructions.items(.data)[inst].rrr;
651
652 switch (tag) {
653 .mul => try emit.writeInstruction(Instruction.mul(cond, rrr.rd, rrr.rn, rrr.rm)),
654 .smulbb => try emit.writeInstruction(Instruction.smulbb(cond, rrr.rd, rrr.rn, rrr.rm)),
655 else => unreachable,
656 }
657}
658
659fn mirMultiplyLong(emit: *Emit, inst: Mir.Inst.Index) !void {
660 const tag = emit.mir.instructions.items(.tag)[inst];
661 const cond = emit.mir.instructions.items(.cond)[inst];
662 const rrrr = emit.mir.instructions.items(.data)[inst].rrrr;
663
664 switch (tag) {
665 .smull => try emit.writeInstruction(Instruction.smull(cond, rrrr.rdlo, rrrr.rdhi, rrrr.rn, rrrr.rm)),
666 .umull => try emit.writeInstruction(Instruction.umull(cond, rrrr.rdlo, rrrr.rdhi, rrrr.rn, rrrr.rm)),
667 else => unreachable,
668 }
669}
670
671fn mirNop(emit: *Emit) !void {
672 try emit.writeInstruction(Instruction.nop());
673}
674
675fn mirBlockDataTransfer(emit: *Emit, inst: Mir.Inst.Index) !void {
676 const tag = emit.mir.instructions.items(.tag)[inst];
677 const cond = emit.mir.instructions.items(.cond)[inst];
678 const register_list = emit.mir.instructions.items(.data)[inst].register_list;
679
680 switch (tag) {
681 .pop => try emit.writeInstruction(Instruction.ldm(cond, .sp, true, register_list)),
682 .push => try emit.writeInstruction(Instruction.stmdb(cond, .sp, true, register_list)),
683 else => unreachable,
684 }
685}
686
687fn mirSupervisorCall(emit: *Emit, inst: Mir.Inst.Index) !void {
688 const tag = emit.mir.instructions.items(.tag)[inst];
689 const cond = emit.mir.instructions.items(.cond)[inst];
690 const imm24 = emit.mir.instructions.items(.data)[inst].imm24;
691
692 switch (tag) {
693 .svc => try emit.writeInstruction(Instruction.svc(cond, imm24)),
694 else => unreachable,
695 }
696}
697
698fn mirBitFieldExtract(emit: *Emit, inst: Mir.Inst.Index) !void {
699 const tag = emit.mir.instructions.items(.tag)[inst];
700 const cond = emit.mir.instructions.items(.cond)[inst];
701 const rr_lsb_width = emit.mir.instructions.items(.data)[inst].rr_lsb_width;
702 const rd = rr_lsb_width.rd;
703 const rn = rr_lsb_width.rn;
704 const lsb = rr_lsb_width.lsb;
705 const width = rr_lsb_width.width;
706
707 switch (tag) {
708 .sbfx => try emit.writeInstruction(Instruction.sbfx(cond, rd, rn, lsb, width)),
709 .ubfx => try emit.writeInstruction(Instruction.ubfx(cond, rd, rn, lsb, width)),
710 else => unreachable,
711 }
712}
src/arch/arm/Mir.zig deleted-340
......@@ -1,340 +0,0 @@
1//! Machine Intermediate Representation.
2//! This data is produced by ARM Codegen or ARM assembly parsing
3//! These instructions have a 1:1 correspondence with machine code instructions
4//! for the target. MIR can be lowered to source-annotated textual assembly code
5//! instructions, or it can be lowered to machine code.
6//! The main purpose of MIR is to postpone the assignment of offsets until Isel,
7//! so that, for example, the smaller encodings of jump instructions can be used.
8
9const Mir = @This();
10const std = @import("std");
11const builtin = @import("builtin");
12const assert = std.debug.assert;
13
14const bits = @import("bits.zig");
15const Register = bits.Register;
16const InternPool = @import("../../InternPool.zig");
17const Emit = @import("Emit.zig");
18const codegen = @import("../../codegen.zig");
19const link = @import("../../link.zig");
20const Zcu = @import("../../Zcu.zig");
21
22max_end_stack: u32,
23saved_regs_stack_space: u32,
24
25instructions: std.MultiArrayList(Inst).Slice,
26/// The meaning of this data is determined by `Inst.Tag` value.
27extra: []const u32,
28
29pub const Inst = struct {
30 tag: Tag,
31 cond: bits.Condition = .al,
32 /// The meaning of this depends on `tag`.
33 data: Data,
34
35 pub const Tag = enum(u16) {
36 /// Add
37 add,
38 /// Add, update condition flags
39 adds,
40 /// Bitwise AND
41 @"and",
42 /// Arithmetic Shift Right
43 asr,
44 /// Branch
45 b,
46 /// Undefined instruction
47 undefined_instruction,
48 /// Breakpoint
49 bkpt,
50 /// Branch with Link and Exchange
51 blx,
52 /// Branch and Exchange
53 bx,
54 /// Compare
55 cmp,
56 /// Pseudo-instruction: End of prologue
57 dbg_prologue_end,
58 /// Pseudo-instruction: Beginning of epilogue
59 dbg_epilogue_begin,
60 /// Pseudo-instruction: Update debug line
61 dbg_line,
62 /// Bitwise Exclusive OR
63 eor,
64 /// Load Register
65 ldr,
66 /// Pseudo-instruction: Load pointer to stack argument offset
67 ldr_ptr_stack_argument,
68 /// Load Register
69 ldr_stack_argument,
70 /// Load Register Byte
71 ldrb,
72 /// Load Register Byte
73 ldrb_stack_argument,
74 /// Load Register Halfword
75 ldrh,
76 /// Load Register Halfword
77 ldrh_stack_argument,
78 /// Load Register Signed Byte
79 ldrsb,
80 /// Load Register Signed Byte
81 ldrsb_stack_argument,
82 /// Load Register Signed Halfword
83 ldrsh,
84 /// Load Register Signed Halfword
85 ldrsh_stack_argument,
86 /// Logical Shift Left
87 lsl,
88 /// Logical Shift Right
89 lsr,
90 /// Move
91 mov,
92 /// Move
93 movw,
94 /// Move Top
95 movt,
96 /// Multiply
97 mul,
98 /// Bitwise NOT
99 mvn,
100 /// No Operation
101 nop,
102 /// Bitwise OR
103 orr,
104 /// Pop multiple registers from Stack
105 pop,
106 /// Push multiple registers to Stack
107 push,
108 /// Reverse Subtract
109 rsb,
110 /// Signed Bit Field Extract
111 sbfx,
112 /// Signed Multiply (halfwords), bottom half, bottom half
113 smulbb,
114 /// Signed Multiply Long
115 smull,
116 /// Store Register
117 str,
118 /// Store Register Byte
119 strb,
120 /// Store Register Halfword
121 strh,
122 /// Subtract
123 sub,
124 /// Pseudo-instruction: Subtract 32-bit immediate from stack
125 ///
126 /// r4 can be used by Emit as a scratch register for loading
127 /// the immediate
128 sub_sp_scratch_r4,
129 /// Subtract, update condition flags
130 subs,
131 /// Supervisor Call
132 svc,
133 /// Unsigned Bit Field Extract
134 ubfx,
135 /// Unsigned Multiply Long
136 umull,
137 };
138
139 /// The position of an MIR instruction within the `Mir` instructions array.
140 pub const Index = u32;
141
142 /// All instructions have a 8-byte payload, which is contained within
143 /// this union. `Tag` determines which union field is active, as well as
144 /// how to interpret the data within.
145 pub const Data = union {
146 /// No additional data
147 ///
148 /// Used by e.g. nop
149 nop: void,
150 /// Another instruction
151 ///
152 /// Used by e.g. b
153 inst: Index,
154 /// A 16-bit immediate value.
155 ///
156 /// Used by e.g. bkpt
157 imm16: u16,
158 /// A 24-bit immediate value.
159 ///
160 /// Used by e.g. svc
161 imm24: u24,
162 /// A 32-bit immediate value.
163 ///
164 /// Used by e.g. sub_sp_scratch_r0
165 imm32: u32,
166 /// Index into `extra`. Meaning of what can be found there is context-dependent.
167 ///
168 /// Used by e.g. load_memory
169 payload: u32,
170 /// A register
171 ///
172 /// Used by e.g. blx
173 reg: Register,
174 /// A register and a stack offset
175 ///
176 /// Used by e.g. ldr_stack_argument
177 r_stack_offset: struct {
178 rt: Register,
179 stack_offset: u32,
180 },
181 /// A register and a 16-bit unsigned immediate
182 ///
183 /// Used by e.g. movw
184 r_imm16: struct {
185 rd: Register,
186 imm16: u16,
187 },
188 /// A register and an operand
189 ///
190 /// Used by mov and mvn
191 r_op_mov: struct {
192 rd: Register,
193 op: bits.Instruction.Operand,
194 },
195 /// A register and an operand
196 ///
197 /// Used by cmp
198 r_op_cmp: struct {
199 rn: Register,
200 op: bits.Instruction.Operand,
201 },
202 /// Two registers and a shift amount
203 ///
204 /// Used by e.g. lsl
205 rr_shift: struct {
206 rd: Register,
207 rm: Register,
208 shift_amount: bits.Instruction.ShiftAmount,
209 },
210 /// Two registers and an operand
211 ///
212 /// Used by e.g. sub
213 rr_op: struct {
214 rd: Register,
215 rn: Register,
216 op: bits.Instruction.Operand,
217 },
218 /// Two registers and an offset
219 ///
220 /// Used by e.g. ldr
221 rr_offset: struct {
222 rt: Register,
223 rn: Register,
224 offset: bits.Instruction.OffsetArgs,
225 },
226 /// Two registers and an extra load/store offset
227 ///
228 /// Used by e.g. ldrh
229 rr_extra_offset: struct {
230 rt: Register,
231 rn: Register,
232 offset: bits.Instruction.ExtraLoadStoreOffsetArgs,
233 },
234 /// Two registers and a lsb (range 0-31) and a width (range
235 /// 1-32)
236 ///
237 /// Used by e.g. sbfx
238 rr_lsb_width: struct {
239 rd: Register,
240 rn: Register,
241 lsb: u5,
242 width: u6,
243 },
244 /// Three registers
245 ///
246 /// Used by e.g. mul
247 rrr: struct {
248 rd: Register,
249 rn: Register,
250 rm: Register,
251 },
252 /// Four registers
253 ///
254 /// Used by e.g. smull
255 rrrr: struct {
256 rdlo: Register,
257 rdhi: Register,
258 rn: Register,
259 rm: Register,
260 },
261 /// An unordered list of registers
262 ///
263 /// Used by e.g. push
264 register_list: bits.Instruction.RegisterList,
265 /// Debug info: line and column
266 ///
267 /// Used by e.g. dbg_line
268 dbg_line_column: struct {
269 line: u32,
270 column: u32,
271 },
272 };
273
274 // Make sure we don't accidentally make instructions bigger than expected.
275 // Note that in safety builds, Zig is allowed to insert a secret field for safety checks.
276 comptime {
277 if (!std.debug.runtime_safety) {
278 assert(@sizeOf(Data) == 8);
279 }
280 }
281};
282
283pub fn deinit(mir: *Mir, gpa: std.mem.Allocator) void {
284 mir.instructions.deinit(gpa);
285 gpa.free(mir.extra);
286 mir.* = undefined;
287}
288
289pub fn emit(
290 mir: Mir,
291 lf: *link.File,
292 pt: Zcu.PerThread,
293 src_loc: Zcu.LazySrcLoc,
294 func_index: InternPool.Index,
295 code: *std.ArrayListUnmanaged(u8),
296 debug_output: link.File.DebugInfoOutput,
297) codegen.CodeGenError!void {
298 const zcu = pt.zcu;
299 const func = zcu.funcInfo(func_index);
300 const nav = func.owner_nav;
301 const mod = zcu.navFileScope(nav).mod.?;
302 var e: Emit = .{
303 .mir = mir,
304 .bin_file = lf,
305 .debug_output = debug_output,
306 .target = &mod.resolved_target.result,
307 .src_loc = src_loc,
308 .code = code,
309 .prev_di_pc = 0,
310 .prev_di_line = func.lbrace_line,
311 .prev_di_column = func.lbrace_column,
312 .stack_size = mir.max_end_stack,
313 .saved_regs_stack_space = mir.saved_regs_stack_space,
314 };
315 defer e.deinit();
316 e.emitMir() catch |err| switch (err) {
317 error.EmitFail => return zcu.codegenFailMsg(nav, e.err_msg.?),
318 else => |e1| return e1,
319 };
320}
321
322/// Returns the requested data, as well as the new index which is at the start of the
323/// trailers for the object.
324pub fn extraData(mir: Mir, comptime T: type, index: usize) struct { data: T, end: usize } {
325 const fields = std.meta.fields(T);
326 var i: usize = index;
327 var result: T = undefined;
328 inline for (fields) |field| {
329 @field(result, field.name) = switch (field.type) {
330 u32 => mir.extra[i],
331 i32 => @as(i32, @bitCast(mir.extra[i])),
332 else => @compileError("bad field type"),
333 };
334 i += 1;
335 }
336 return .{
337 .data = result,
338 .end = i,
339 };
340}
src/arch/arm/abi.zig deleted-187
......@@ -1,187 +0,0 @@
1const std = @import("std");
2const assert = std.debug.assert;
3const bits = @import("bits.zig");
4const Register = bits.Register;
5const RegisterManagerFn = @import("../../register_manager.zig").RegisterManager;
6const Type = @import("../../Type.zig");
7const Zcu = @import("../../Zcu.zig");
8
9pub const Class = union(enum) {
10 memory,
11 byval,
12 i32_array: u8,
13 i64_array: u8,
14
15 fn arrSize(total_size: u64, arr_size: u64) Class {
16 const count = @as(u8, @intCast(std.mem.alignForward(u64, total_size, arr_size) / arr_size));
17 if (arr_size == 32) {
18 return .{ .i32_array = count };
19 } else {
20 return .{ .i64_array = count };
21 }
22 }
23};
24
25pub const Context = enum { ret, arg };
26
27pub fn classifyType(ty: Type, zcu: *Zcu, ctx: Context) Class {
28 assert(ty.hasRuntimeBitsIgnoreComptime(zcu));
29
30 var maybe_float_bits: ?u16 = null;
31 const max_byval_size = 512;
32 const ip = &zcu.intern_pool;
33 switch (ty.zigTypeTag(zcu)) {
34 .@"struct" => {
35 const bit_size = ty.bitSize(zcu);
36 if (ty.containerLayout(zcu) == .@"packed") {
37 if (bit_size > 64) return .memory;
38 return .byval;
39 }
40 if (bit_size > max_byval_size) return .memory;
41 const float_count = countFloats(ty, zcu, &maybe_float_bits);
42 if (float_count <= byval_float_count) return .byval;
43
44 const fields = ty.structFieldCount(zcu);
45 var i: u32 = 0;
46 while (i < fields) : (i += 1) {
47 const field_ty = ty.fieldType(i, zcu);
48 const field_alignment = ty.fieldAlignment(i, zcu);
49 const field_size = field_ty.bitSize(zcu);
50 if (field_size > 32 or field_alignment.compare(.gt, .@"32")) {
51 return Class.arrSize(bit_size, 64);
52 }
53 }
54 return Class.arrSize(bit_size, 32);
55 },
56 .@"union" => {
57 const bit_size = ty.bitSize(zcu);
58 const union_obj = zcu.typeToUnion(ty).?;
59 if (union_obj.flagsUnordered(ip).layout == .@"packed") {
60 if (bit_size > 64) return .memory;
61 return .byval;
62 }
63 if (bit_size > max_byval_size) return .memory;
64 const float_count = countFloats(ty, zcu, &maybe_float_bits);
65 if (float_count <= byval_float_count) return .byval;
66
67 for (union_obj.field_types.get(ip), 0..) |field_ty, field_index| {
68 if (Type.fromInterned(field_ty).bitSize(zcu) > 32 or
69 ty.fieldAlignment(field_index, zcu).compare(.gt, .@"32"))
70 {
71 return Class.arrSize(bit_size, 64);
72 }
73 }
74 return Class.arrSize(bit_size, 32);
75 },
76 .bool, .float => return .byval,
77 .int => {
78 // TODO this is incorrect for _BitInt(128) but implementing
79 // this correctly makes implementing compiler-rt impossible.
80 // const bit_size = ty.bitSize(zcu);
81 // if (bit_size > 64) return .memory;
82 return .byval;
83 },
84 .@"enum", .error_set => {
85 const bit_size = ty.bitSize(zcu);
86 if (bit_size > 64) return .memory;
87 return .byval;
88 },
89 .vector => {
90 const bit_size = ty.bitSize(zcu);
91 // TODO is this controlled by a cpu feature?
92 if (ctx == .ret and bit_size > 128) return .memory;
93 if (bit_size > 512) return .memory;
94 return .byval;
95 },
96 .optional => {
97 assert(ty.isPtrLikeOptional(zcu));
98 return .byval;
99 },
100 .pointer => {
101 assert(!ty.isSlice(zcu));
102 return .byval;
103 },
104 .error_union,
105 .frame,
106 .@"anyframe",
107 .noreturn,
108 .void,
109 .type,
110 .comptime_float,
111 .comptime_int,
112 .undefined,
113 .null,
114 .@"fn",
115 .@"opaque",
116 .enum_literal,
117 .array,
118 => unreachable,
119 }
120}
121
122const byval_float_count = 4;
123fn countFloats(ty: Type, zcu: *Zcu, maybe_float_bits: *?u16) u32 {
124 const ip = &zcu.intern_pool;
125 const target = zcu.getTarget();
126 const invalid = std.math.maxInt(u32);
127 switch (ty.zigTypeTag(zcu)) {
128 .@"union" => {
129 const union_obj = zcu.typeToUnion(ty).?;
130 var max_count: u32 = 0;
131 for (union_obj.field_types.get(ip)) |field_ty| {
132 const field_count = countFloats(Type.fromInterned(field_ty), zcu, maybe_float_bits);
133 if (field_count == invalid) return invalid;
134 if (field_count > max_count) max_count = field_count;
135 if (max_count > byval_float_count) return invalid;
136 }
137 return max_count;
138 },
139 .@"struct" => {
140 const fields_len = ty.structFieldCount(zcu);
141 var count: u32 = 0;
142 var i: u32 = 0;
143 while (i < fields_len) : (i += 1) {
144 const field_ty = ty.fieldType(i, zcu);
145 const field_count = countFloats(field_ty, zcu, maybe_float_bits);
146 if (field_count == invalid) return invalid;
147 count += field_count;
148 if (count > byval_float_count) return invalid;
149 }
150 return count;
151 },
152 .float => {
153 const float_bits = maybe_float_bits.* orelse {
154 const float_bits = ty.floatBits(target);
155 if (float_bits != 32 and float_bits != 64) return invalid;
156 maybe_float_bits.* = float_bits;
157 return 1;
158 };
159 if (ty.floatBits(target) == float_bits) return 1;
160 return invalid;
161 },
162 .void => return 0,
163 else => return invalid,
164 }
165}
166
167pub const callee_preserved_regs = [_]Register{ .r4, .r5, .r6, .r7, .r8, .r10 };
168pub const caller_preserved_regs = [_]Register{ .r0, .r1, .r2, .r3 };
169
170pub const c_abi_int_param_regs = [_]Register{ .r0, .r1, .r2, .r3 };
171pub const c_abi_int_return_regs = [_]Register{ .r0, .r1 };
172
173const allocatable_registers = callee_preserved_regs ++ caller_preserved_regs;
174pub const RegisterManager = RegisterManagerFn(@import("CodeGen.zig"), Register, &allocatable_registers);
175
176// Register classes
177const RegisterBitSet = RegisterManager.RegisterBitSet;
178pub const RegisterClass = struct {
179 pub const gp: RegisterBitSet = blk: {
180 var set = RegisterBitSet.initEmpty();
181 set.setRangeValue(.{
182 .start = 0,
183 .end = caller_preserved_regs.len + callee_preserved_regs.len,
184 }, true);
185 break :blk set;
186 };
187};
src/arch/arm/bits.zig deleted-1566
......@@ -1,1566 +0,0 @@
1const std = @import("std");
2const assert = std.debug.assert;
3const testing = std.testing;
4
5/// The condition field specifies the flags necessary for an
6/// Instruction to be executed
7pub const Condition = enum(u4) {
8 /// equal
9 eq,
10 /// not equal
11 ne,
12 /// unsigned higher or same
13 cs,
14 /// unsigned lower
15 cc,
16 /// negative
17 mi,
18 /// positive or zero
19 pl,
20 /// overflow
21 vs,
22 /// no overflow
23 vc,
24 /// unsigned higer
25 hi,
26 /// unsigned lower or same
27 ls,
28 /// greater or equal
29 ge,
30 /// less than
31 lt,
32 /// greater than
33 gt,
34 /// less than or equal
35 le,
36 /// always
37 al,
38
39 /// Converts a std.math.CompareOperator into a condition flag,
40 /// i.e. returns the condition that is true iff the result of the
41 /// comparison is true. Assumes signed comparison
42 pub fn fromCompareOperatorSigned(op: std.math.CompareOperator) Condition {
43 return switch (op) {
44 .gte => .ge,
45 .gt => .gt,
46 .neq => .ne,
47 .lt => .lt,
48 .lte => .le,
49 .eq => .eq,
50 };
51 }
52
53 /// Converts a std.math.CompareOperator into a condition flag,
54 /// i.e. returns the condition that is true iff the result of the
55 /// comparison is true. Assumes unsigned comparison
56 pub fn fromCompareOperatorUnsigned(op: std.math.CompareOperator) Condition {
57 return switch (op) {
58 .gte => .cs,
59 .gt => .hi,
60 .neq => .ne,
61 .lt => .cc,
62 .lte => .ls,
63 .eq => .eq,
64 };
65 }
66
67 /// Returns the condition which is true iff the given condition is
68 /// false (if such a condition exists)
69 pub fn negate(cond: Condition) Condition {
70 return switch (cond) {
71 .eq => .ne,
72 .ne => .eq,
73 .cs => .cc,
74 .cc => .cs,
75 .mi => .pl,
76 .pl => .mi,
77 .vs => .vc,
78 .vc => .vs,
79 .hi => .ls,
80 .ls => .hi,
81 .ge => .lt,
82 .lt => .ge,
83 .gt => .le,
84 .le => .gt,
85 .al => unreachable,
86 };
87 }
88};
89
90test "condition from CompareOperator" {
91 try testing.expectEqual(@as(Condition, .eq), Condition.fromCompareOperatorSigned(.eq));
92 try testing.expectEqual(@as(Condition, .eq), Condition.fromCompareOperatorUnsigned(.eq));
93
94 try testing.expectEqual(@as(Condition, .gt), Condition.fromCompareOperatorSigned(.gt));
95 try testing.expectEqual(@as(Condition, .hi), Condition.fromCompareOperatorUnsigned(.gt));
96
97 try testing.expectEqual(@as(Condition, .le), Condition.fromCompareOperatorSigned(.lte));
98 try testing.expectEqual(@as(Condition, .ls), Condition.fromCompareOperatorUnsigned(.lte));
99}
100
101test "negate condition" {
102 try testing.expectEqual(@as(Condition, .eq), Condition.ne.negate());
103 try testing.expectEqual(@as(Condition, .ne), Condition.eq.negate());
104}
105
106/// Represents a register in the ARM instruction set architecture
107pub const Register = enum(u5) {
108 r0,
109 r1,
110 r2,
111 r3,
112 r4,
113 r5,
114 r6,
115 r7,
116 r8,
117 r9,
118 r10,
119 r11,
120 r12,
121 r13,
122 r14,
123 r15,
124
125 /// Argument / result / scratch register 1
126 a1,
127 /// Argument / result / scratch register 2
128 a2,
129 /// Argument / scratch register 3
130 a3,
131 /// Argument / scratch register 4
132 a4,
133 /// Variable-register 1
134 v1,
135 /// Variable-register 2
136 v2,
137 /// Variable-register 3
138 v3,
139 /// Variable-register 4
140 v4,
141 /// Variable-register 5
142 v5,
143 /// Platform register
144 v6,
145 /// Variable-register 7
146 v7,
147 /// Frame pointer or Variable-register 8
148 fp,
149 /// Intra-Procedure-call scratch register
150 ip,
151 /// Stack pointer
152 sp,
153 /// Link register
154 lr,
155 /// Program counter
156 pc,
157
158 /// Returns the unique 4-bit ID of this register which is used in
159 /// the machine code
160 pub fn id(reg: Register) u4 {
161 return @truncate(@intFromEnum(reg));
162 }
163
164 pub fn dwarfNum(reg: Register) u4 {
165 return reg.id();
166 }
167};
168
169test "Register.id" {
170 try testing.expectEqual(@as(u4, 15), Register.r15.id());
171 try testing.expectEqual(@as(u4, 15), Register.pc.id());
172}
173
174/// Program status registers containing flags, mode bits and other
175/// vital information
176pub const Psr = enum {
177 cpsr,
178 spsr,
179};
180
181/// Represents an instruction in the ARM instruction set architecture
182pub const Instruction = union(enum) {
183 data_processing: packed struct {
184 // Note to self: The order of the fields top-to-bottom is
185 // right-to-left in the actual 32-bit int representation
186 op2: u12,
187 rd: u4,
188 rn: u4,
189 s: u1,
190 opcode: u4,
191 i: u1,
192 fixed: u2 = 0b00,
193 cond: u4,
194 },
195 multiply: packed struct {
196 rn: u4,
197 fixed_1: u4 = 0b1001,
198 rm: u4,
199 ra: u4,
200 rd: u4,
201 set_cond: u1,
202 accumulate: u1,
203 fixed_2: u6 = 0b000000,
204 cond: u4,
205 },
206 multiply_long: packed struct {
207 rn: u4,
208 fixed_1: u4 = 0b1001,
209 rm: u4,
210 rdlo: u4,
211 rdhi: u4,
212 set_cond: u1,
213 accumulate: u1,
214 unsigned: u1,
215 fixed_2: u5 = 0b00001,
216 cond: u4,
217 },
218 signed_multiply_halfwords: packed struct {
219 rn: u4,
220 fixed_1: u1 = 0b0,
221 n: u1,
222 m: u1,
223 fixed_2: u1 = 0b1,
224 rm: u4,
225 fixed_3: u4 = 0b0000,
226 rd: u4,
227 fixed_4: u8 = 0b00010110,
228 cond: u4,
229 },
230 integer_saturating_arithmetic: packed struct {
231 rm: u4,
232 fixed_1: u8 = 0b0000_0101,
233 rd: u4,
234 rn: u4,
235 fixed_2: u1 = 0b0,
236 opc: u2,
237 fixed_3: u5 = 0b00010,
238 cond: u4,
239 },
240 bit_field_extract: packed struct {
241 rn: u4,
242 fixed_1: u3 = 0b101,
243 lsb: u5,
244 rd: u4,
245 widthm1: u5,
246 fixed_2: u1 = 0b1,
247 unsigned: u1,
248 fixed_3: u5 = 0b01111,
249 cond: u4,
250 },
251 single_data_transfer: packed struct {
252 offset: u12,
253 rd: u4,
254 rn: u4,
255 load_store: u1,
256 write_back: u1,
257 byte_word: u1,
258 up_down: u1,
259 pre_post: u1,
260 imm: u1,
261 fixed: u2 = 0b01,
262 cond: u4,
263 },
264 extra_load_store: packed struct {
265 imm4l: u4,
266 fixed_1: u1 = 0b1,
267 op2: u2,
268 fixed_2: u1 = 0b1,
269 imm4h: u4,
270 rt: u4,
271 rn: u4,
272 o1: u1,
273 write_back: u1,
274 imm: u1,
275 up_down: u1,
276 pre_index: u1,
277 fixed_3: u3 = 0b000,
278 cond: u4,
279 },
280 block_data_transfer: packed struct {
281 register_list: u16,
282 rn: u4,
283 load_store: u1,
284 write_back: u1,
285 psr_or_user: u1,
286 up_down: u1,
287 pre_post: u1,
288 fixed: u3 = 0b100,
289 cond: u4,
290 },
291 branch: packed struct {
292 offset: u24,
293 link: u1,
294 fixed: u3 = 0b101,
295 cond: u4,
296 },
297 branch_exchange: packed struct {
298 rn: u4,
299 fixed_1: u1 = 0b1,
300 link: u1,
301 fixed_2: u22 = 0b0001_0010_1111_1111_1111_00,
302 cond: u4,
303 },
304 supervisor_call: packed struct {
305 comment: u24,
306 fixed: u4 = 0b1111,
307 cond: u4,
308 },
309 undefined_instruction: packed struct {
310 imm32: u32 = 0xe7ffdefe,
311 },
312 breakpoint: packed struct {
313 imm4: u4,
314 fixed_1: u4 = 0b0111,
315 imm12: u12,
316 fixed_2_and_cond: u12 = 0b1110_0001_0010,
317 },
318
319 /// Represents the possible operations which can be performed by a
320 /// Data Processing instruction
321 const Opcode = enum(u4) {
322 // Rd := Op1 AND Op2
323 @"and",
324 // Rd := Op1 EOR Op2
325 eor,
326 // Rd := Op1 - Op2
327 sub,
328 // Rd := Op2 - Op1
329 rsb,
330 // Rd := Op1 + Op2
331 add,
332 // Rd := Op1 + Op2 + C
333 adc,
334 // Rd := Op1 - Op2 + C - 1
335 sbc,
336 // Rd := Op2 - Op1 + C - 1
337 rsc,
338 // set condition codes on Op1 AND Op2
339 tst,
340 // set condition codes on Op1 EOR Op2
341 teq,
342 // set condition codes on Op1 - Op2
343 cmp,
344 // set condition codes on Op1 + Op2
345 cmn,
346 // Rd := Op1 OR Op2
347 orr,
348 // Rd := Op2
349 mov,
350 // Rd := Op1 AND NOT Op2
351 bic,
352 // Rd := NOT Op2
353 mvn,
354 };
355
356 /// Represents the second operand to a data processing instruction
357 /// which can either be content from a register or an immediate
358 /// value
359 pub const Operand = union(enum) {
360 register: packed struct {
361 rm: u4,
362 shift: u8,
363 },
364 immediate: packed struct {
365 imm: u8,
366 rotate: u4,
367 },
368
369 /// Represents multiple ways a register can be shifted. A
370 /// register can be shifted by a specific immediate value or
371 /// by the contents of another register
372 pub const Shift = union(enum) {
373 immediate: packed struct {
374 fixed: u1 = 0b0,
375 typ: u2,
376 amount: u5,
377 },
378 register: packed struct {
379 fixed_1: u1 = 0b1,
380 typ: u2,
381 fixed_2: u1 = 0b0,
382 rs: u4,
383 },
384
385 pub const Type = enum(u2) {
386 logical_left,
387 logical_right,
388 arithmetic_right,
389 rotate_right,
390 };
391
392 pub const none = Shift{
393 .immediate = .{
394 .amount = 0,
395 .typ = 0,
396 },
397 };
398
399 pub fn toU8(self: Shift) u8 {
400 return switch (self) {
401 .register => |v| @as(u8, @bitCast(v)),
402 .immediate => |v| @as(u8, @bitCast(v)),
403 };
404 }
405
406 pub fn reg(rs: Register, typ: Type) Shift {
407 return Shift{
408 .register = .{
409 .rs = rs.id(),
410 .typ = @intFromEnum(typ),
411 },
412 };
413 }
414
415 pub fn imm(amount: u5, typ: Type) Shift {
416 return Shift{
417 .immediate = .{
418 .amount = amount,
419 .typ = @intFromEnum(typ),
420 },
421 };
422 }
423 };
424
425 pub fn toU12(self: Operand) u12 {
426 return switch (self) {
427 .register => |v| @as(u12, @bitCast(v)),
428 .immediate => |v| @as(u12, @bitCast(v)),
429 };
430 }
431
432 pub fn reg(rm: Register, shift: Shift) Operand {
433 return Operand{
434 .register = .{
435 .rm = rm.id(),
436 .shift = shift.toU8(),
437 },
438 };
439 }
440
441 pub fn imm(immediate: u8, rotate: u4) Operand {
442 return Operand{
443 .immediate = .{
444 .imm = immediate,
445 .rotate = rotate,
446 },
447 };
448 }
449
450 /// Tries to convert an unsigned 32 bit integer into an
451 /// immediate operand using rotation. Returns null when there
452 /// is no conversion
453 pub fn fromU32(x: u32) ?Operand {
454 const masks = comptime blk: {
455 const base_mask: u32 = std.math.maxInt(u8);
456 var result = [_]u32{0} ** 16;
457 for (&result, 0..) |*mask, i| mask.* = std.math.rotr(u32, base_mask, 2 * i);
458 break :blk result;
459 };
460
461 return for (masks, 0..) |mask, i| {
462 if (x & mask == x) {
463 break Operand{
464 .immediate = .{
465 .imm = @as(u8, @intCast(std.math.rotl(u32, x, 2 * i))),
466 .rotate = @as(u4, @intCast(i)),
467 },
468 };
469 }
470 } else null;
471 }
472 };
473
474 pub const AddressingMode = enum {
475 /// [<Rn>, <offset>]
476 ///
477 /// Address = Rn + offset
478 offset,
479 /// [<Rn>, <offset>]!
480 ///
481 /// Address = Rn + offset
482 /// Rn = Rn + offset
483 pre_index,
484 /// [<Rn>], <offset>
485 ///
486 /// Address = Rn
487 /// Rn = Rn + offset
488 post_index,
489 };
490
491 /// Represents the offset operand of a load or store
492 /// instruction. Data can be loaded from memory with either an
493 /// immediate offset or an offset that is stored in some register.
494 pub const Offset = union(enum) {
495 immediate: u12,
496 register: packed struct {
497 rm: u4,
498 fixed: u1 = 0b0,
499 stype: u2,
500 imm5: u5,
501 },
502
503 pub const Shift = union(enum) {
504 /// No shift
505 none,
506 /// Logical shift left
507 lsl: u5,
508 /// Logical shift right
509 lsr: u5,
510 /// Arithmetic shift right
511 asr: u5,
512 /// Rotate right
513 ror: u5,
514 /// Rotate right one bit, with extend
515 rrx,
516 };
517
518 pub const none = Offset{
519 .immediate = 0,
520 };
521
522 pub fn toU12(self: Offset) u12 {
523 return switch (self) {
524 .register => |v| @as(u12, @bitCast(v)),
525 .immediate => |v| v,
526 };
527 }
528
529 pub fn reg(rm: Register, shift: Shift) Offset {
530 return Offset{
531 .register = .{
532 .rm = rm.id(),
533 .stype = switch (shift) {
534 .none => 0b00,
535 .lsl => 0b00,
536 .lsr => 0b01,
537 .asr => 0b10,
538 .ror => 0b11,
539 .rrx => 0b11,
540 },
541 .imm5 = switch (shift) {
542 .none => 0,
543 .lsl => |n| n,
544 .lsr => |n| n,
545 .asr => |n| n,
546 .ror => |n| n,
547 .rrx => 0,
548 },
549 },
550 };
551 }
552
553 pub fn imm(immediate: u12) Offset {
554 return Offset{
555 .immediate = immediate,
556 };
557 }
558 };
559
560 /// Represents the offset operand of an extra load or store
561 /// instruction.
562 pub const ExtraLoadStoreOffset = union(enum) {
563 immediate: u8,
564 register: u4,
565
566 pub const none = ExtraLoadStoreOffset{
567 .immediate = 0,
568 };
569
570 pub fn reg(register: Register) ExtraLoadStoreOffset {
571 return ExtraLoadStoreOffset{
572 .register = register.id(),
573 };
574 }
575
576 pub fn imm(immediate: u8) ExtraLoadStoreOffset {
577 return ExtraLoadStoreOffset{
578 .immediate = immediate,
579 };
580 }
581 };
582
583 /// Represents the register list operand to a block data transfer
584 /// instruction
585 pub const RegisterList = packed struct {
586 r0: bool = false,
587 r1: bool = false,
588 r2: bool = false,
589 r3: bool = false,
590 r4: bool = false,
591 r5: bool = false,
592 r6: bool = false,
593 r7: bool = false,
594 r8: bool = false,
595 r9: bool = false,
596 r10: bool = false,
597 r11: bool = false,
598 r12: bool = false,
599 r13: bool = false,
600 r14: bool = false,
601 r15: bool = false,
602 };
603
604 pub fn toU32(self: Instruction) u32 {
605 return switch (self) {
606 .data_processing => |v| @as(u32, @bitCast(v)),
607 .multiply => |v| @as(u32, @bitCast(v)),
608 .multiply_long => |v| @as(u32, @bitCast(v)),
609 .signed_multiply_halfwords => |v| @as(u32, @bitCast(v)),
610 .integer_saturating_arithmetic => |v| @as(u32, @bitCast(v)),
611 .bit_field_extract => |v| @as(u32, @bitCast(v)),
612 .single_data_transfer => |v| @as(u32, @bitCast(v)),
613 .extra_load_store => |v| @as(u32, @bitCast(v)),
614 .block_data_transfer => |v| @as(u32, @bitCast(v)),
615 .branch => |v| @as(u32, @bitCast(v)),
616 .branch_exchange => |v| @as(u32, @bitCast(v)),
617 .supervisor_call => |v| @as(u32, @bitCast(v)),
618 .undefined_instruction => |v| v.imm32,
619 .breakpoint => |v| @as(u32, @intCast(v.imm4)) | (@as(u32, @intCast(v.fixed_1)) << 4) | (@as(u32, @intCast(v.imm12)) << 8) | (@as(u32, @intCast(v.fixed_2_and_cond)) << 20),
620 };
621 }
622
623 // Helper functions for the "real" functions below
624
625 fn dataProcessing(
626 cond: Condition,
627 opcode: Opcode,
628 s: u1,
629 rd: Register,
630 rn: Register,
631 op2: Operand,
632 ) Instruction {
633 return Instruction{
634 .data_processing = .{
635 .cond = @intFromEnum(cond),
636 .i = @intFromBool(op2 == .immediate),
637 .opcode = @intFromEnum(opcode),
638 .s = s,
639 .rn = rn.id(),
640 .rd = rd.id(),
641 .op2 = op2.toU12(),
642 },
643 };
644 }
645
646 fn specialMov(
647 cond: Condition,
648 rd: Register,
649 imm: u16,
650 top: bool,
651 ) Instruction {
652 return Instruction{
653 .data_processing = .{
654 .cond = @intFromEnum(cond),
655 .i = 1,
656 .opcode = if (top) 0b1010 else 0b1000,
657 .s = 0,
658 .rn = @as(u4, @truncate(imm >> 12)),
659 .rd = rd.id(),
660 .op2 = @as(u12, @truncate(imm)),
661 },
662 };
663 }
664
665 fn initMultiply(
666 cond: Condition,
667 set_cond: u1,
668 rd: Register,
669 rn: Register,
670 rm: Register,
671 ra: ?Register,
672 ) Instruction {
673 return Instruction{
674 .multiply = .{
675 .cond = @intFromEnum(cond),
676 .accumulate = @intFromBool(ra != null),
677 .set_cond = set_cond,
678 .rd = rd.id(),
679 .rn = rn.id(),
680 .ra = if (ra) |reg| reg.id() else 0b0000,
681 .rm = rm.id(),
682 },
683 };
684 }
685
686 fn multiplyLong(
687 cond: Condition,
688 signed: u1,
689 accumulate: u1,
690 set_cond: u1,
691 rdhi: Register,
692 rdlo: Register,
693 rm: Register,
694 rn: Register,
695 ) Instruction {
696 return Instruction{
697 .multiply_long = .{
698 .cond = @intFromEnum(cond),
699 .unsigned = signed,
700 .accumulate = accumulate,
701 .set_cond = set_cond,
702 .rdlo = rdlo.id(),
703 .rdhi = rdhi.id(),
704 .rn = rn.id(),
705 .rm = rm.id(),
706 },
707 };
708 }
709
710 fn signedMultiplyHalfwords(
711 n: u1,
712 m: u1,
713 cond: Condition,
714 rd: Register,
715 rn: Register,
716 rm: Register,
717 ) Instruction {
718 return Instruction{
719 .signed_multiply_halfwords = .{
720 .rn = rn.id(),
721 .n = n,
722 .m = m,
723 .rm = rm.id(),
724 .rd = rd.id(),
725 .cond = @intFromEnum(cond),
726 },
727 };
728 }
729
730 fn integerSaturationArithmetic(
731 cond: Condition,
732 rd: Register,
733 rm: Register,
734 rn: Register,
735 opc: u2,
736 ) Instruction {
737 return Instruction{
738 .integer_saturating_arithmetic = .{
739 .rm = rm.id(),
740 .rd = rd.id(),
741 .rn = rn.id(),
742 .opc = opc,
743 .cond = @intFromEnum(cond),
744 },
745 };
746 }
747
748 fn bitFieldExtract(
749 unsigned: u1,
750 cond: Condition,
751 rd: Register,
752 rn: Register,
753 lsb: u5,
754 width: u6,
755 ) Instruction {
756 assert(width > 0 and width <= 32);
757 return Instruction{
758 .bit_field_extract = .{
759 .rn = rn.id(),
760 .lsb = lsb,
761 .rd = rd.id(),
762 .widthm1 = @as(u5, @intCast(width - 1)),
763 .unsigned = unsigned,
764 .cond = @intFromEnum(cond),
765 },
766 };
767 }
768
769 fn singleDataTransfer(
770 cond: Condition,
771 rd: Register,
772 rn: Register,
773 offset: Offset,
774 mode: AddressingMode,
775 positive: bool,
776 byte_word: u1,
777 load_store: u1,
778 ) Instruction {
779 return Instruction{
780 .single_data_transfer = .{
781 .cond = @intFromEnum(cond),
782 .rn = rn.id(),
783 .rd = rd.id(),
784 .offset = offset.toU12(),
785 .load_store = load_store,
786 .write_back = switch (mode) {
787 .offset => 0b0,
788 .pre_index, .post_index => 0b1,
789 },
790 .byte_word = byte_word,
791 .up_down = @intFromBool(positive),
792 .pre_post = switch (mode) {
793 .offset, .pre_index => 0b1,
794 .post_index => 0b0,
795 },
796 .imm = @intFromBool(offset != .immediate),
797 },
798 };
799 }
800
801 fn extraLoadStore(
802 cond: Condition,
803 mode: AddressingMode,
804 positive: bool,
805 o1: u1,
806 op2: u2,
807 rn: Register,
808 rt: Register,
809 offset: ExtraLoadStoreOffset,
810 ) Instruction {
811 const imm4l: u4 = switch (offset) {
812 .immediate => |imm| @as(u4, @truncate(imm)),
813 .register => |reg| reg,
814 };
815 const imm4h: u4 = switch (offset) {
816 .immediate => |imm| @as(u4, @truncate(imm >> 4)),
817 .register => 0b0000,
818 };
819
820 return Instruction{
821 .extra_load_store = .{
822 .imm4l = imm4l,
823 .op2 = op2,
824 .imm4h = imm4h,
825 .rt = rt.id(),
826 .rn = rn.id(),
827 .o1 = o1,
828 .write_back = switch (mode) {
829 .offset => 0b0,
830 .pre_index, .post_index => 0b1,
831 },
832 .imm = @intFromBool(offset == .immediate),
833 .up_down = @intFromBool(positive),
834 .pre_index = switch (mode) {
835 .offset, .pre_index => 0b1,
836 .post_index => 0b0,
837 },
838 .cond = @intFromEnum(cond),
839 },
840 };
841 }
842
843 fn blockDataTransfer(
844 cond: Condition,
845 rn: Register,
846 reg_list: RegisterList,
847 pre_post: u1,
848 up_down: u1,
849 psr_or_user: u1,
850 write_back: bool,
851 load_store: u1,
852 ) Instruction {
853 return Instruction{
854 .block_data_transfer = .{
855 .register_list = @as(u16, @bitCast(reg_list)),
856 .rn = rn.id(),
857 .load_store = load_store,
858 .write_back = @intFromBool(write_back),
859 .psr_or_user = psr_or_user,
860 .up_down = up_down,
861 .pre_post = pre_post,
862 .cond = @intFromEnum(cond),
863 },
864 };
865 }
866
867 fn initBranch(cond: Condition, offset: i26, link: u1) Instruction {
868 return Instruction{
869 .branch = .{
870 .cond = @intFromEnum(cond),
871 .link = link,
872 .offset = @as(u24, @bitCast(@as(i24, @intCast(offset >> 2)))),
873 },
874 };
875 }
876
877 fn branchExchange(cond: Condition, rn: Register, link: u1) Instruction {
878 return Instruction{
879 .branch_exchange = .{
880 .cond = @intFromEnum(cond),
881 .link = link,
882 .rn = rn.id(),
883 },
884 };
885 }
886
887 fn supervisorCall(cond: Condition, comment: u24) Instruction {
888 return Instruction{
889 .supervisor_call = .{
890 .cond = @intFromEnum(cond),
891 .comment = comment,
892 },
893 };
894 }
895
896 // This instruction has no official mnemonic equivalent so it is public as-is.
897 pub fn undefinedInstruction() Instruction {
898 return Instruction{
899 .undefined_instruction = .{},
900 };
901 }
902
903 fn initBreakpoint(imm: u16) Instruction {
904 return Instruction{
905 .breakpoint = .{
906 .imm12 = @as(u12, @truncate(imm >> 4)),
907 .imm4 = @as(u4, @truncate(imm)),
908 },
909 };
910 }
911
912 // Public functions replicating assembler syntax as closely as
913 // possible
914
915 // Data processing
916
917 pub fn @"and"(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction {
918 return dataProcessing(cond, .@"and", 0, rd, rn, op2);
919 }
920
921 pub fn ands(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction {
922 return dataProcessing(cond, .@"and", 1, rd, rn, op2);
923 }
924
925 pub fn eor(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction {
926 return dataProcessing(cond, .eor, 0, rd, rn, op2);
927 }
928
929 pub fn eors(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction {
930 return dataProcessing(cond, .eor, 1, rd, rn, op2);
931 }
932
933 pub fn sub(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction {
934 return dataProcessing(cond, .sub, 0, rd, rn, op2);
935 }
936
937 pub fn subs(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction {
938 return dataProcessing(cond, .sub, 1, rd, rn, op2);
939 }
940
941 pub fn rsb(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction {
942 return dataProcessing(cond, .rsb, 0, rd, rn, op2);
943 }
944
945 pub fn rsbs(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction {
946 return dataProcessing(cond, .rsb, 1, rd, rn, op2);
947 }
948
949 pub fn add(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction {
950 return dataProcessing(cond, .add, 0, rd, rn, op2);
951 }
952
953 pub fn adds(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction {
954 return dataProcessing(cond, .add, 1, rd, rn, op2);
955 }
956
957 pub fn adc(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction {
958 return dataProcessing(cond, .adc, 0, rd, rn, op2);
959 }
960
961 pub fn adcs(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction {
962 return dataProcessing(cond, .adc, 1, rd, rn, op2);
963 }
964
965 pub fn sbc(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction {
966 return dataProcessing(cond, .sbc, 0, rd, rn, op2);
967 }
968
969 pub fn sbcs(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction {
970 return dataProcessing(cond, .sbc, 1, rd, rn, op2);
971 }
972
973 pub fn rsc(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction {
974 return dataProcessing(cond, .rsc, 0, rd, rn, op2);
975 }
976
977 pub fn rscs(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction {
978 return dataProcessing(cond, .rsc, 1, rd, rn, op2);
979 }
980
981 pub fn tst(cond: Condition, rn: Register, op2: Operand) Instruction {
982 return dataProcessing(cond, .tst, 1, .r0, rn, op2);
983 }
984
985 pub fn teq(cond: Condition, rn: Register, op2: Operand) Instruction {
986 return dataProcessing(cond, .teq, 1, .r0, rn, op2);
987 }
988
989 pub fn cmp(cond: Condition, rn: Register, op2: Operand) Instruction {
990 return dataProcessing(cond, .cmp, 1, .r0, rn, op2);
991 }
992
993 pub fn cmn(cond: Condition, rn: Register, op2: Operand) Instruction {
994 return dataProcessing(cond, .cmn, 1, .r0, rn, op2);
995 }
996
997 pub fn orr(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction {
998 return dataProcessing(cond, .orr, 0, rd, rn, op2);
999 }
1000
1001 pub fn orrs(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction {
1002 return dataProcessing(cond, .orr, 1, rd, rn, op2);
1003 }
1004
1005 pub fn mov(cond: Condition, rd: Register, op2: Operand) Instruction {
1006 return dataProcessing(cond, .mov, 0, rd, .r0, op2);
1007 }
1008
1009 pub fn movs(cond: Condition, rd: Register, op2: Operand) Instruction {
1010 return dataProcessing(cond, .mov, 1, rd, .r0, op2);
1011 }
1012
1013 pub fn bic(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction {
1014 return dataProcessing(cond, .bic, 0, rd, rn, op2);
1015 }
1016
1017 pub fn bics(cond: Condition, rd: Register, rn: Register, op2: Operand) Instruction {
1018 return dataProcessing(cond, .bic, 1, rd, rn, op2);
1019 }
1020
1021 pub fn mvn(cond: Condition, rd: Register, op2: Operand) Instruction {
1022 return dataProcessing(cond, .mvn, 0, rd, .r0, op2);
1023 }
1024
1025 pub fn mvns(cond: Condition, rd: Register, op2: Operand) Instruction {
1026 return dataProcessing(cond, .mvn, 1, rd, .r0, op2);
1027 }
1028
1029 // Integer Saturating Arithmetic
1030
1031 pub fn qadd(cond: Condition, rd: Register, rm: Register, rn: Register) Instruction {
1032 return integerSaturationArithmetic(cond, rd, rm, rn, 0b00);
1033 }
1034
1035 pub fn qsub(cond: Condition, rd: Register, rm: Register, rn: Register) Instruction {
1036 return integerSaturationArithmetic(cond, rd, rm, rn, 0b01);
1037 }
1038
1039 pub fn qdadd(cond: Condition, rd: Register, rm: Register, rn: Register) Instruction {
1040 return integerSaturationArithmetic(cond, rd, rm, rn, 0b10);
1041 }
1042
1043 pub fn qdsub(cond: Condition, rd: Register, rm: Register, rn: Register) Instruction {
1044 return integerSaturationArithmetic(cond, rd, rm, rn, 0b11);
1045 }
1046
1047 // movw and movt
1048
1049 pub fn movw(cond: Condition, rd: Register, imm: u16) Instruction {
1050 return specialMov(cond, rd, imm, false);
1051 }
1052
1053 pub fn movt(cond: Condition, rd: Register, imm: u16) Instruction {
1054 return specialMov(cond, rd, imm, true);
1055 }
1056
1057 // PSR transfer
1058
1059 pub fn mrs(cond: Condition, rd: Register, psr: Psr) Instruction {
1060 return Instruction{
1061 .data_processing = .{
1062 .cond = @intFromEnum(cond),
1063 .i = 0,
1064 .opcode = if (psr == .spsr) 0b1010 else 0b1000,
1065 .s = 0,
1066 .rn = 0b1111,
1067 .rd = rd.id(),
1068 .op2 = 0b0000_0000_0000,
1069 },
1070 };
1071 }
1072
1073 pub fn msr(cond: Condition, psr: Psr, op: Operand) Instruction {
1074 return Instruction{
1075 .data_processing = .{
1076 .cond = @intFromEnum(cond),
1077 .i = 0,
1078 .opcode = if (psr == .spsr) 0b1011 else 0b1001,
1079 .s = 0,
1080 .rn = 0b1111,
1081 .rd = 0b1111,
1082 .op2 = op.toU12(),
1083 },
1084 };
1085 }
1086
1087 // Multiply
1088
1089 pub fn mul(cond: Condition, rd: Register, rn: Register, rm: Register) Instruction {
1090 return initMultiply(cond, 0, rd, rn, rm, null);
1091 }
1092
1093 pub fn muls(cond: Condition, rd: Register, rn: Register, rm: Register) Instruction {
1094 return initMultiply(cond, 1, rd, rn, rm, null);
1095 }
1096
1097 pub fn mla(cond: Condition, rd: Register, rn: Register, rm: Register, ra: Register) Instruction {
1098 return initMultiply(cond, 0, rd, rn, rm, ra);
1099 }
1100
1101 pub fn mlas(cond: Condition, rd: Register, rn: Register, rm: Register, ra: Register) Instruction {
1102 return initMultiply(cond, 1, rd, rn, rm, ra);
1103 }
1104
1105 // Multiply long
1106
1107 pub fn umull(cond: Condition, rdlo: Register, rdhi: Register, rn: Register, rm: Register) Instruction {
1108 return multiplyLong(cond, 0, 0, 0, rdhi, rdlo, rm, rn);
1109 }
1110
1111 pub fn umulls(cond: Condition, rdlo: Register, rdhi: Register, rn: Register, rm: Register) Instruction {
1112 return multiplyLong(cond, 0, 0, 1, rdhi, rdlo, rm, rn);
1113 }
1114
1115 pub fn umlal(cond: Condition, rdlo: Register, rdhi: Register, rn: Register, rm: Register) Instruction {
1116 return multiplyLong(cond, 0, 1, 0, rdhi, rdlo, rm, rn);
1117 }
1118
1119 pub fn umlals(cond: Condition, rdlo: Register, rdhi: Register, rn: Register, rm: Register) Instruction {
1120 return multiplyLong(cond, 0, 1, 1, rdhi, rdlo, rm, rn);
1121 }
1122
1123 pub fn smull(cond: Condition, rdlo: Register, rdhi: Register, rn: Register, rm: Register) Instruction {
1124 return multiplyLong(cond, 1, 0, 0, rdhi, rdlo, rm, rn);
1125 }
1126
1127 pub fn smulls(cond: Condition, rdlo: Register, rdhi: Register, rn: Register, rm: Register) Instruction {
1128 return multiplyLong(cond, 1, 0, 1, rdhi, rdlo, rm, rn);
1129 }
1130
1131 pub fn smlal(cond: Condition, rdlo: Register, rdhi: Register, rn: Register, rm: Register) Instruction {
1132 return multiplyLong(cond, 1, 1, 0, rdhi, rdlo, rm, rn);
1133 }
1134
1135 pub fn smlals(cond: Condition, rdlo: Register, rdhi: Register, rn: Register, rm: Register) Instruction {
1136 return multiplyLong(cond, 1, 1, 1, rdhi, rdlo, rm, rn);
1137 }
1138
1139 // Signed Multiply (halfwords)
1140
1141 pub fn smulbb(cond: Condition, rd: Register, rn: Register, rm: Register) Instruction {
1142 return signedMultiplyHalfwords(0, 0, cond, rd, rn, rm);
1143 }
1144
1145 pub fn smulbt(cond: Condition, rd: Register, rn: Register, rm: Register) Instruction {
1146 return signedMultiplyHalfwords(0, 1, cond, rd, rn, rm);
1147 }
1148
1149 pub fn smultb(cond: Condition, rd: Register, rn: Register, rm: Register) Instruction {
1150 return signedMultiplyHalfwords(1, 0, cond, rd, rn, rm);
1151 }
1152
1153 pub fn smultt(cond: Condition, rd: Register, rn: Register, rm: Register) Instruction {
1154 return signedMultiplyHalfwords(1, 1, cond, rd, rn, rm);
1155 }
1156
1157 // Bit field extract
1158
1159 pub fn ubfx(cond: Condition, rd: Register, rn: Register, lsb: u5, width: u6) Instruction {
1160 return bitFieldExtract(0b1, cond, rd, rn, lsb, width);
1161 }
1162
1163 pub fn sbfx(cond: Condition, rd: Register, rn: Register, lsb: u5, width: u6) Instruction {
1164 return bitFieldExtract(0b0, cond, rd, rn, lsb, width);
1165 }
1166
1167 // Single data transfer
1168
1169 pub const OffsetArgs = struct {
1170 mode: AddressingMode = .offset,
1171 positive: bool = true,
1172 offset: Offset,
1173 };
1174
1175 pub fn ldr(cond: Condition, rd: Register, rn: Register, args: OffsetArgs) Instruction {
1176 return singleDataTransfer(cond, rd, rn, args.offset, args.mode, args.positive, 0, 1);
1177 }
1178
1179 pub fn ldrb(cond: Condition, rd: Register, rn: Register, args: OffsetArgs) Instruction {
1180 return singleDataTransfer(cond, rd, rn, args.offset, args.mode, args.positive, 1, 1);
1181 }
1182
1183 pub fn str(cond: Condition, rd: Register, rn: Register, args: OffsetArgs) Instruction {
1184 return singleDataTransfer(cond, rd, rn, args.offset, args.mode, args.positive, 0, 0);
1185 }
1186
1187 pub fn strb(cond: Condition, rd: Register, rn: Register, args: OffsetArgs) Instruction {
1188 return singleDataTransfer(cond, rd, rn, args.offset, args.mode, args.positive, 1, 0);
1189 }
1190
1191 // Extra load/store
1192
1193 pub const ExtraLoadStoreOffsetArgs = struct {
1194 mode: AddressingMode = .offset,
1195 positive: bool = true,
1196 offset: ExtraLoadStoreOffset,
1197 };
1198
1199 pub fn strh(cond: Condition, rt: Register, rn: Register, args: ExtraLoadStoreOffsetArgs) Instruction {
1200 return extraLoadStore(cond, args.mode, args.positive, 0b0, 0b01, rn, rt, args.offset);
1201 }
1202
1203 pub fn ldrh(cond: Condition, rt: Register, rn: Register, args: ExtraLoadStoreOffsetArgs) Instruction {
1204 return extraLoadStore(cond, args.mode, args.positive, 0b1, 0b01, rn, rt, args.offset);
1205 }
1206
1207 pub fn ldrsh(cond: Condition, rt: Register, rn: Register, args: ExtraLoadStoreOffsetArgs) Instruction {
1208 return extraLoadStore(cond, args.mode, args.positive, 0b1, 0b11, rn, rt, args.offset);
1209 }
1210
1211 pub fn ldrsb(cond: Condition, rt: Register, rn: Register, args: ExtraLoadStoreOffsetArgs) Instruction {
1212 return extraLoadStore(cond, args.mode, args.positive, 0b1, 0b10, rn, rt, args.offset);
1213 }
1214
1215 // Block data transfer
1216
1217 pub fn ldmda(cond: Condition, rn: Register, write_back: bool, reg_list: RegisterList) Instruction {
1218 return blockDataTransfer(cond, rn, reg_list, 0, 0, 0, write_back, 1);
1219 }
1220
1221 pub fn ldmdb(cond: Condition, rn: Register, write_back: bool, reg_list: RegisterList) Instruction {
1222 return blockDataTransfer(cond, rn, reg_list, 1, 0, 0, write_back, 1);
1223 }
1224
1225 pub fn ldmib(cond: Condition, rn: Register, write_back: bool, reg_list: RegisterList) Instruction {
1226 return blockDataTransfer(cond, rn, reg_list, 1, 1, 0, write_back, 1);
1227 }
1228
1229 pub fn ldmia(cond: Condition, rn: Register, write_back: bool, reg_list: RegisterList) Instruction {
1230 return blockDataTransfer(cond, rn, reg_list, 0, 1, 0, write_back, 1);
1231 }
1232
1233 pub const ldmfa = ldmda;
1234 pub const ldmea = ldmdb;
1235 pub const ldmed = ldmib;
1236 pub const ldmfd = ldmia;
1237 pub const ldm = ldmia;
1238
1239 pub fn stmda(cond: Condition, rn: Register, write_back: bool, reg_list: RegisterList) Instruction {
1240 return blockDataTransfer(cond, rn, reg_list, 0, 0, 0, write_back, 0);
1241 }
1242
1243 pub fn stmdb(cond: Condition, rn: Register, write_back: bool, reg_list: RegisterList) Instruction {
1244 return blockDataTransfer(cond, rn, reg_list, 1, 0, 0, write_back, 0);
1245 }
1246
1247 pub fn stmib(cond: Condition, rn: Register, write_back: bool, reg_list: RegisterList) Instruction {
1248 return blockDataTransfer(cond, rn, reg_list, 1, 1, 0, write_back, 0);
1249 }
1250
1251 pub fn stmia(cond: Condition, rn: Register, write_back: bool, reg_list: RegisterList) Instruction {
1252 return blockDataTransfer(cond, rn, reg_list, 0, 1, 0, write_back, 0);
1253 }
1254
1255 pub const stmed = stmda;
1256 pub const stmfd = stmdb;
1257 pub const stmfa = stmib;
1258 pub const stmea = stmia;
1259 pub const stm = stmia;
1260
1261 // Branch
1262
1263 pub fn b(cond: Condition, offset: i26) Instruction {
1264 return initBranch(cond, offset, 0);
1265 }
1266
1267 pub fn bl(cond: Condition, offset: i26) Instruction {
1268 return initBranch(cond, offset, 1);
1269 }
1270
1271 // Branch and exchange
1272
1273 pub fn bx(cond: Condition, rn: Register) Instruction {
1274 return branchExchange(cond, rn, 0);
1275 }
1276
1277 pub fn blx(cond: Condition, rn: Register) Instruction {
1278 return branchExchange(cond, rn, 1);
1279 }
1280
1281 // Supervisor Call
1282
1283 pub const swi = svc;
1284
1285 pub fn svc(cond: Condition, comment: u24) Instruction {
1286 return supervisorCall(cond, comment);
1287 }
1288
1289 // Breakpoint
1290
1291 pub fn bkpt(imm: u16) Instruction {
1292 return initBreakpoint(imm);
1293 }
1294
1295 // Aliases
1296
1297 pub fn nop() Instruction {
1298 return mov(.al, .r0, Instruction.Operand.reg(.r0, Instruction.Operand.Shift.none));
1299 }
1300
1301 pub fn pop(cond: Condition, args: anytype) Instruction {
1302 if (@typeInfo(@TypeOf(args)) != .@"struct") {
1303 @compileError("Expected tuple or struct argument, found " ++ @typeName(@TypeOf(args)));
1304 }
1305
1306 if (args.len < 1) {
1307 @compileError("Expected at least one register");
1308 } else if (args.len == 1) {
1309 const reg = args[0];
1310 return ldr(cond, reg, .sp, .{
1311 .mode = .post_index,
1312 .positive = true,
1313 .offset = Offset.imm(4),
1314 });
1315 } else {
1316 var register_list: u16 = 0;
1317 inline for (args) |arg| {
1318 const reg = @as(Register, arg);
1319 register_list |= @as(u16, 1) << reg.id();
1320 }
1321 return ldm(cond, .sp, true, @as(RegisterList, @bitCast(register_list)));
1322 }
1323 }
1324
1325 pub fn push(cond: Condition, args: anytype) Instruction {
1326 if (@typeInfo(@TypeOf(args)) != .@"struct") {
1327 @compileError("Expected tuple or struct argument, found " ++ @typeName(@TypeOf(args)));
1328 }
1329
1330 if (args.len < 1) {
1331 @compileError("Expected at least one register");
1332 } else if (args.len == 1) {
1333 const reg = args[0];
1334 return str(cond, reg, .sp, .{
1335 .mode = .pre_index,
1336 .positive = false,
1337 .offset = Offset.imm(4),
1338 });
1339 } else {
1340 var register_list: u16 = 0;
1341 inline for (args) |arg| {
1342 const reg = @as(Register, arg);
1343 register_list |= @as(u16, 1) << reg.id();
1344 }
1345 return stmdb(cond, .sp, true, @as(RegisterList, @bitCast(register_list)));
1346 }
1347 }
1348
1349 pub const ShiftAmount = union(enum) {
1350 immediate: u5,
1351 register: Register,
1352
1353 pub fn imm(immediate: u5) ShiftAmount {
1354 return .{
1355 .immediate = immediate,
1356 };
1357 }
1358
1359 pub fn reg(register: Register) ShiftAmount {
1360 return .{
1361 .register = register,
1362 };
1363 }
1364 };
1365
1366 pub fn lsl(cond: Condition, rd: Register, rm: Register, shift: ShiftAmount) Instruction {
1367 return switch (shift) {
1368 .immediate => |imm| mov(cond, rd, Operand.reg(rm, Operand.Shift.imm(imm, .logical_left))),
1369 .register => |reg| mov(cond, rd, Operand.reg(rm, Operand.Shift.reg(reg, .logical_left))),
1370 };
1371 }
1372
1373 pub fn lsr(cond: Condition, rd: Register, rm: Register, shift: ShiftAmount) Instruction {
1374 return switch (shift) {
1375 .immediate => |imm| mov(cond, rd, Operand.reg(rm, Operand.Shift.imm(imm, .logical_right))),
1376 .register => |reg| mov(cond, rd, Operand.reg(rm, Operand.Shift.reg(reg, .logical_right))),
1377 };
1378 }
1379
1380 pub fn asr(cond: Condition, rd: Register, rm: Register, shift: ShiftAmount) Instruction {
1381 return switch (shift) {
1382 .immediate => |imm| mov(cond, rd, Operand.reg(rm, Operand.Shift.imm(imm, .arithmetic_right))),
1383 .register => |reg| mov(cond, rd, Operand.reg(rm, Operand.Shift.reg(reg, .arithmetic_right))),
1384 };
1385 }
1386
1387 pub fn ror(cond: Condition, rd: Register, rm: Register, shift: ShiftAmount) Instruction {
1388 return switch (shift) {
1389 .immediate => |imm| mov(cond, rd, Operand.reg(rm, Operand.Shift.imm(imm, .rotate_right))),
1390 .register => |reg| mov(cond, rd, Operand.reg(rm, Operand.Shift.reg(reg, .rotate_right))),
1391 };
1392 }
1393
1394 pub fn lsls(cond: Condition, rd: Register, rm: Register, shift: ShiftAmount) Instruction {
1395 return switch (shift) {
1396 .immediate => |imm| movs(cond, rd, Operand.reg(rm, Operand.Shift.imm(imm, .logical_left))),
1397 .register => |reg| movs(cond, rd, Operand.reg(rm, Operand.Shift.reg(reg, .logical_left))),
1398 };
1399 }
1400
1401 pub fn lsrs(cond: Condition, rd: Register, rm: Register, shift: ShiftAmount) Instruction {
1402 return switch (shift) {
1403 .immediate => |imm| movs(cond, rd, Operand.reg(rm, Operand.Shift.imm(imm, .logical_right))),
1404 .register => |reg| movs(cond, rd, Operand.reg(rm, Operand.Shift.reg(reg, .logical_right))),
1405 };
1406 }
1407
1408 pub fn asrs(cond: Condition, rd: Register, rm: Register, shift: ShiftAmount) Instruction {
1409 return switch (shift) {
1410 .immediate => |imm| movs(cond, rd, Operand.reg(rm, Operand.Shift.imm(imm, .arithmetic_right))),
1411 .register => |reg| movs(cond, rd, Operand.reg(rm, Operand.Shift.reg(reg, .arithmetic_right))),
1412 };
1413 }
1414
1415 pub fn rors(cond: Condition, rd: Register, rm: Register, shift: ShiftAmount) Instruction {
1416 return switch (shift) {
1417 .immediate => |imm| movs(cond, rd, Operand.reg(rm, Operand.Shift.imm(imm, .rotate_right))),
1418 .register => |reg| movs(cond, rd, Operand.reg(rm, Operand.Shift.reg(reg, .rotate_right))),
1419 };
1420 }
1421};
1422
1423test "serialize instructions" {
1424 const Testcase = struct {
1425 inst: Instruction,
1426 expected: u32,
1427 };
1428
1429 const testcases = [_]Testcase{
1430 .{ // add r0, r0, r0
1431 .inst = Instruction.add(.al, .r0, .r0, Instruction.Operand.reg(.r0, Instruction.Operand.Shift.none)),
1432 .expected = 0b1110_00_0_0100_0_0000_0000_00000000_0000,
1433 },
1434 .{ // mov r4, r2
1435 .inst = Instruction.mov(.al, .r4, Instruction.Operand.reg(.r2, Instruction.Operand.Shift.none)),
1436 .expected = 0b1110_00_0_1101_0_0000_0100_00000000_0010,
1437 },
1438 .{ // mov r0, #42
1439 .inst = Instruction.mov(.al, .r0, Instruction.Operand.imm(42, 0)),
1440 .expected = 0b1110_00_1_1101_0_0000_0000_0000_00101010,
1441 },
1442 .{ // mrs r5, cpsr
1443 .inst = Instruction.mrs(.al, .r5, .cpsr),
1444 .expected = 0b1110_00010_0_001111_0101_000000000000,
1445 },
1446 .{ // mul r0, r1, r2
1447 .inst = Instruction.mul(.al, .r0, .r1, .r2),
1448 .expected = 0b1110_000000_0_0_0000_0000_0010_1001_0001,
1449 },
1450 .{ // umlal r0, r1, r5, r6
1451 .inst = Instruction.umlal(.al, .r0, .r1, .r5, .r6),
1452 .expected = 0b1110_00001_0_1_0_0001_0000_0110_1001_0101,
1453 },
1454 .{ // ldr r0, [r2, #42]
1455 .inst = Instruction.ldr(.al, .r0, .r2, .{
1456 .offset = Instruction.Offset.imm(42),
1457 }),
1458 .expected = 0b1110_01_0_1_1_0_0_1_0010_0000_000000101010,
1459 },
1460 .{ // str r0, [r3]
1461 .inst = Instruction.str(.al, .r0, .r3, .{
1462 .offset = Instruction.Offset.none,
1463 }),
1464 .expected = 0b1110_01_0_1_1_0_0_0_0011_0000_000000000000,
1465 },
1466 .{ // strh r1, [r5]
1467 .inst = Instruction.strh(.al, .r1, .r5, .{
1468 .offset = Instruction.ExtraLoadStoreOffset.none,
1469 }),
1470 .expected = 0b1110_000_1_1_1_0_0_0101_0001_0000_1011_0000,
1471 },
1472 .{ // b #12
1473 .inst = Instruction.b(.al, 12),
1474 .expected = 0b1110_101_0_0000_0000_0000_0000_0000_0011,
1475 },
1476 .{ // bl #-4
1477 .inst = Instruction.bl(.al, -4),
1478 .expected = 0b1110_101_1_1111_1111_1111_1111_1111_1111,
1479 },
1480 .{ // bx lr
1481 .inst = Instruction.bx(.al, .lr),
1482 .expected = 0b1110_0001_0010_1111_1111_1111_0001_1110,
1483 },
1484 .{ // svc #0
1485 .inst = Instruction.svc(.al, 0),
1486 .expected = 0b1110_1111_0000_0000_0000_0000_0000_0000,
1487 },
1488 .{ // bkpt #42
1489 .inst = Instruction.bkpt(42),
1490 .expected = 0b1110_0001_0010_000000000010_0111_1010,
1491 },
1492 .{ // stmdb r9, {r0}
1493 .inst = Instruction.stmdb(.al, .r9, false, .{ .r0 = true }),
1494 .expected = 0b1110_100_1_0_0_0_0_1001_0000000000000001,
1495 },
1496 .{ // ldmea r4!, {r2, r5}
1497 .inst = Instruction.ldmea(.al, .r4, true, .{ .r2 = true, .r5 = true }),
1498 .expected = 0b1110_100_1_0_0_1_1_0100_0000000000100100,
1499 },
1500 .{ // qadd r0, r7, r8
1501 .inst = Instruction.qadd(.al, .r0, .r7, .r8),
1502 .expected = 0b1110_00010_00_0_1000_0000_0000_0101_0111,
1503 },
1504 .{ // smulbt r0, r0, r0
1505 .inst = Instruction.smulbt(.al, .r0, .r0, .r0),
1506 .expected = 0b1110_00010110_0000_0000_0000_1_1_0_0_0000,
1507 },
1508 };
1509
1510 for (testcases) |case| {
1511 const actual = case.inst.toU32();
1512 try testing.expectEqual(case.expected, actual);
1513 }
1514}
1515
1516test "aliases" {
1517 const Testcase = struct {
1518 expected: Instruction,
1519 actual: Instruction,
1520 };
1521
1522 const testcases = [_]Testcase{
1523 .{ // pop { r6 }
1524 .actual = Instruction.pop(.al, .{.r6}),
1525 .expected = Instruction.ldr(.al, .r6, .sp, .{
1526 .mode = .post_index,
1527 .positive = true,
1528 .offset = Instruction.Offset.imm(4),
1529 }),
1530 },
1531 .{ // pop { r1, r5 }
1532 .actual = Instruction.pop(.al, .{ .r1, .r5 }),
1533 .expected = Instruction.ldm(.al, .sp, true, .{ .r1 = true, .r5 = true }),
1534 },
1535 .{ // push { r3 }
1536 .actual = Instruction.push(.al, .{.r3}),
1537 .expected = Instruction.str(.al, .r3, .sp, .{
1538 .mode = .pre_index,
1539 .positive = false,
1540 .offset = Instruction.Offset.imm(4),
1541 }),
1542 },
1543 .{ // push { r0, r2 }
1544 .actual = Instruction.push(.al, .{ .r0, .r2 }),
1545 .expected = Instruction.stmdb(.al, .sp, true, .{ .r0 = true, .r2 = true }),
1546 },
1547 .{ // lsl r4, r5, #5
1548 .actual = Instruction.lsl(.al, .r4, .r5, Instruction.ShiftAmount.imm(5)),
1549 .expected = Instruction.mov(.al, .r4, Instruction.Operand.reg(
1550 .r5,
1551 Instruction.Operand.Shift.imm(5, .logical_left),
1552 )),
1553 },
1554 .{ // asrs r1, r1, r3
1555 .actual = Instruction.asrs(.al, .r1, .r1, Instruction.ShiftAmount.reg(.r3)),
1556 .expected = Instruction.movs(.al, .r1, Instruction.Operand.reg(
1557 .r1,
1558 Instruction.Operand.Shift.reg(.r3, .arithmetic_right),
1559 )),
1560 },
1561 };
1562
1563 for (testcases) |case| {
1564 try testing.expectEqual(case.expected.toU32(), case.actual.toU32());
1565 }
1566}
src/codegen.zig+4-9
......@@ -49,7 +49,7 @@ fn importBackend(comptime backend: std.builtin.CompilerBackend) type {
4949 return switch (backend) {
5050 .other, .stage1 => unreachable,
5151 .stage2_aarch64 => unreachable,
52 .stage2_arm => @import("arch/arm/CodeGen.zig"),
52 .stage2_arm => unreachable,
5353 .stage2_c => @import("codegen/c.zig"),
5454 .stage2_llvm => @import("codegen/llvm.zig"),
5555 .stage2_powerpc => unreachable,
......@@ -70,7 +70,6 @@ pub fn legalizeFeatures(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) ?*co
7070 inline .stage2_llvm,
7171 .stage2_c,
7272 .stage2_wasm,
73 .stage2_arm,
7473 .stage2_x86_64,
7574 .stage2_x86,
7675 .stage2_riscv64,
......@@ -87,7 +86,6 @@ pub fn legalizeFeatures(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) ?*co
8786/// MIR from codegen to the linker *regardless* of which backend is in use. So, we use this: a
8887/// union of all MIR types. The active tag is known from the backend in use; see `AnyMir.tag`.
8988pub const AnyMir = union {
90 arm: @import("arch/arm/Mir.zig"),
9189 riscv64: @import("arch/riscv64/Mir.zig"),
9290 sparc64: @import("arch/sparc64/Mir.zig"),
9391 x86_64: @import("arch/x86_64/Mir.zig"),
......@@ -112,8 +110,7 @@ pub const AnyMir = union {
112110 const backend = target_util.zigBackend(&zcu.root_mod.resolved_target.result, zcu.comp.config.use_llvm);
113111 switch (backend) {
114112 else => unreachable,
115 inline .stage2_arm,
116 .stage2_riscv64,
113 inline .stage2_riscv64,
117114 .stage2_sparc64,
118115 .stage2_x86_64,
119116 .stage2_wasm,
......@@ -141,8 +138,7 @@ pub fn generateFunction(
141138 const target = &zcu.navFileScope(func.owner_nav).mod.?.resolved_target.result;
142139 switch (target_util.zigBackend(target, false)) {
143140 else => unreachable,
144 inline .stage2_arm,
145 .stage2_riscv64,
141 inline .stage2_riscv64,
146142 .stage2_sparc64,
147143 .stage2_x86_64,
148144 .stage2_wasm,
......@@ -177,8 +173,7 @@ pub fn emitFunction(
177173 const target = &zcu.navFileScope(func.owner_nav).mod.?.resolved_target.result;
178174 switch (target_util.zigBackend(target, zcu.comp.config.use_llvm)) {
179175 else => unreachable,
180 inline .stage2_arm,
181 .stage2_riscv64,
176 inline .stage2_riscv64,
182177 .stage2_sparc64,
183178 .stage2_x86_64,
184179 => |backend| {
src/codegen/arm/abi.zig created+163
......@@ -0,0 +1,163 @@
1const std = @import("std");
2const assert = std.debug.assert;
3const RegisterManagerFn = @import("../../register_manager.zig").RegisterManager;
4const Type = @import("../../Type.zig");
5const Zcu = @import("../../Zcu.zig");
6
7pub const Class = union(enum) {
8 memory,
9 byval,
10 i32_array: u8,
11 i64_array: u8,
12
13 fn arrSize(total_size: u64, arr_size: u64) Class {
14 const count = @as(u8, @intCast(std.mem.alignForward(u64, total_size, arr_size) / arr_size));
15 if (arr_size == 32) {
16 return .{ .i32_array = count };
17 } else {
18 return .{ .i64_array = count };
19 }
20 }
21};
22
23pub const Context = enum { ret, arg };
24
25pub fn classifyType(ty: Type, zcu: *Zcu, ctx: Context) Class {
26 assert(ty.hasRuntimeBitsIgnoreComptime(zcu));
27
28 var maybe_float_bits: ?u16 = null;
29 const max_byval_size = 512;
30 const ip = &zcu.intern_pool;
31 switch (ty.zigTypeTag(zcu)) {
32 .@"struct" => {
33 const bit_size = ty.bitSize(zcu);
34 if (ty.containerLayout(zcu) == .@"packed") {
35 if (bit_size > 64) return .memory;
36 return .byval;
37 }
38 if (bit_size > max_byval_size) return .memory;
39 const float_count = countFloats(ty, zcu, &maybe_float_bits);
40 if (float_count <= byval_float_count) return .byval;
41
42 const fields = ty.structFieldCount(zcu);
43 var i: u32 = 0;
44 while (i < fields) : (i += 1) {
45 const field_ty = ty.fieldType(i, zcu);
46 const field_alignment = ty.fieldAlignment(i, zcu);
47 const field_size = field_ty.bitSize(zcu);
48 if (field_size > 32 or field_alignment.compare(.gt, .@"32")) {
49 return Class.arrSize(bit_size, 64);
50 }
51 }
52 return Class.arrSize(bit_size, 32);
53 },
54 .@"union" => {
55 const bit_size = ty.bitSize(zcu);
56 const union_obj = zcu.typeToUnion(ty).?;
57 if (union_obj.flagsUnordered(ip).layout == .@"packed") {
58 if (bit_size > 64) return .memory;
59 return .byval;
60 }
61 if (bit_size > max_byval_size) return .memory;
62 const float_count = countFloats(ty, zcu, &maybe_float_bits);
63 if (float_count <= byval_float_count) return .byval;
64
65 for (union_obj.field_types.get(ip), 0..) |field_ty, field_index| {
66 if (Type.fromInterned(field_ty).bitSize(zcu) > 32 or
67 ty.fieldAlignment(field_index, zcu).compare(.gt, .@"32"))
68 {
69 return Class.arrSize(bit_size, 64);
70 }
71 }
72 return Class.arrSize(bit_size, 32);
73 },
74 .bool, .float => return .byval,
75 .int => {
76 // TODO this is incorrect for _BitInt(128) but implementing
77 // this correctly makes implementing compiler-rt impossible.
78 // const bit_size = ty.bitSize(zcu);
79 // if (bit_size > 64) return .memory;
80 return .byval;
81 },
82 .@"enum", .error_set => {
83 const bit_size = ty.bitSize(zcu);
84 if (bit_size > 64) return .memory;
85 return .byval;
86 },
87 .vector => {
88 const bit_size = ty.bitSize(zcu);
89 // TODO is this controlled by a cpu feature?
90 if (ctx == .ret and bit_size > 128) return .memory;
91 if (bit_size > 512) return .memory;
92 return .byval;
93 },
94 .optional => {
95 assert(ty.isPtrLikeOptional(zcu));
96 return .byval;
97 },
98 .pointer => {
99 assert(!ty.isSlice(zcu));
100 return .byval;
101 },
102 .error_union,
103 .frame,
104 .@"anyframe",
105 .noreturn,
106 .void,
107 .type,
108 .comptime_float,
109 .comptime_int,
110 .undefined,
111 .null,
112 .@"fn",
113 .@"opaque",
114 .enum_literal,
115 .array,
116 => unreachable,
117 }
118}
119
120const byval_float_count = 4;
121fn countFloats(ty: Type, zcu: *Zcu, maybe_float_bits: *?u16) u32 {
122 const ip = &zcu.intern_pool;
123 const target = zcu.getTarget();
124 const invalid = std.math.maxInt(u32);
125 switch (ty.zigTypeTag(zcu)) {
126 .@"union" => {
127 const union_obj = zcu.typeToUnion(ty).?;
128 var max_count: u32 = 0;
129 for (union_obj.field_types.get(ip)) |field_ty| {
130 const field_count = countFloats(Type.fromInterned(field_ty), zcu, maybe_float_bits);
131 if (field_count == invalid) return invalid;
132 if (field_count > max_count) max_count = field_count;
133 if (max_count > byval_float_count) return invalid;
134 }
135 return max_count;
136 },
137 .@"struct" => {
138 const fields_len = ty.structFieldCount(zcu);
139 var count: u32 = 0;
140 var i: u32 = 0;
141 while (i < fields_len) : (i += 1) {
142 const field_ty = ty.fieldType(i, zcu);
143 const field_count = countFloats(field_ty, zcu, maybe_float_bits);
144 if (field_count == invalid) return invalid;
145 count += field_count;
146 if (count > byval_float_count) return invalid;
147 }
148 return count;
149 },
150 .float => {
151 const float_bits = maybe_float_bits.* orelse {
152 const float_bits = ty.floatBits(target);
153 if (float_bits != 32 and float_bits != 64) return invalid;
154 maybe_float_bits.* = float_bits;
155 return 1;
156 };
157 if (ty.floatBits(target) == float_bits) return 1;
158 return invalid;
159 },
160 .void => return 0,
161 else => return invalid,
162 }
163}
src/codegen/llvm.zig+1-1
......@@ -23,7 +23,7 @@ const Type = @import("../Type.zig");
2323const x86_64_abi = @import("../arch/x86_64/abi.zig");
2424const wasm_c_abi = @import("../arch/wasm/abi.zig");
2525const aarch64_c_abi = @import("aarch64/abi.zig");
26const arm_c_abi = @import("../arch/arm/abi.zig");
26const arm_c_abi = @import("arm/abi.zig");
2727const riscv_c_abi = @import("../arch/riscv64/abi.zig");
2828const mips_c_abi = @import("../arch/mips/abi.zig");
2929const dev = @import("../dev.zig");