diff --git a/src/arch/arm/CodeGen.zig b/src/arch/arm/CodeGen.zig index 3c70df78f0..ae53d6cad8 100644 --- a/src/arch/arm/CodeGen.zig +++ b/src/arch/arm/CodeGen.zig @@ -76,6 +76,8 @@ blocks: std.AutoHashMapUnmanaged(Air.Inst.Index, BlockData) = .{}, register_manager: RegisterManager(Self, Register, &callee_preserved_regs) = .{}, /// Maps offset to what is stored there. stack: std.AutoHashMapUnmanaged(u32, StackAllocation) = .{}, +/// Tracks the current instruction allocated to the compare flags +compare_flags_inst: ?Air.Inst.Index = null, /// Offset from the stack base, representing the end of the stack frame. max_end_stack: u32 = 0, @@ -647,6 +649,9 @@ fn processDeath(self: *Self, inst: Air.Inst.Index) void { .register => |reg| { self.register_manager.freeReg(reg); }, + .compare_flags_signed, .compare_flags_unsigned => { + self.compare_flags_inst = null; + }, else => {}, // TODO process stack allocation death } } @@ -779,6 +784,28 @@ pub fn spillInstruction(self: *Self, reg: Register, inst: Air.Inst.Index) !void try self.genSetStack(self.air.typeOfIndex(inst), stack_mcv.stack_offset, reg_mcv); } +/// Save the current instruction stored in the compare flags if +/// occupied +fn spillCompareFlagsIfOccupied(self: *Self) !void { + if (self.compare_flags_inst) |inst_to_save| { + const mcv = self.getResolvedInstValue(inst_to_save); + assert(mcv == .compare_flags_signed or mcv == .compare_flags_unsigned); + + const new_mcv = try self.allocRegOrMem(inst_to_save, true); + switch (new_mcv) { + .register => |reg| try self.genSetReg(self.air.typeOfIndex(inst_to_save), reg, mcv), + .stack_offset => |offset| try self.genSetStack(self.air.typeOfIndex(inst_to_save), offset, mcv), + else => unreachable, + } + log.debug("spilling {d} to mcv {any}", .{ inst_to_save, new_mcv }); + + const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; + try branch.inst_table.put(self.gpa, inst_to_save, new_mcv); + + self.compare_flags_inst = null; + } +} + /// Copies a value to a register without tracking the register. The register is not considered /// allocated. A second call to `copyToTmpRegister` may return the same register. /// This can have a side effect of spilling instructions to the stack to free up a register. @@ -890,10 +917,10 @@ fn airNot(self: *Self, inst: Air.Inst.Index) !void { }; break :result r; }, - else => {}, + else => { + break :result try self.genArmBinOp(inst, ty_op.operand, .bool_true, .not); + }, } - - break :result try self.genArmBinOp(inst, ty_op.operand, .bool_true, .not); }; return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } @@ -1831,6 +1858,16 @@ fn airCall(self: *Self, inst: Air.Inst.Index) !void { var info = try self.resolveCallingConventionValues(fn_ty); defer info.deinit(self); + // According to the Procedure Call Standard for the ARM + // Architecture, compare flags are not preserved across + // calls. Therefore, if some value is currently stored there, we + // need to save it. + // + // TODO once caller-saved registers are implemented, save them + // here too, but crucially *after* we save the compare flags as + // saving compare flags may require a new caller-saved register + try self.spillCompareFlagsIfOccupied(); + // Make space for the arguments passed via the stack self.max_end_stack += info.stack_byte_count; @@ -1984,6 +2021,9 @@ fn airCmp(self: *Self, inst: Air.Inst.Index, op: math.CompareOperator) !void { if (ty.zigTypeTag() == .ErrorSet) return self.fail("TODO implement cmp for errors", .{}); + try self.spillCompareFlagsIfOccupied(); + self.compare_flags_inst = inst; + const lhs = try self.resolveInst(bin_op.lhs); const rhs = try self.resolveInst(bin_op.rhs); const result: MCValue = result: { @@ -2094,12 +2134,24 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) !void { }); }; + // If the condition dies here in this condbr instruction, process + // that death now instead of later as this has an effect on + // whether it needs to be spilled in the branches + if (self.liveness.operandDies(inst, 0)) { + const op_int = @enumToInt(pl_op.operand); + if (op_int >= Air.Inst.Ref.typed_value_map.len) { + const op_index = @intCast(Air.Inst.Index, op_int - Air.Inst.Ref.typed_value_map.len); + self.processDeath(op_index); + } + } + // Capture the state of register and stack allocation state so that we can revert to it. const parent_next_stack_offset = self.next_stack_offset; const parent_free_registers = self.register_manager.free_registers; var parent_stack = try self.stack.clone(self.gpa); defer parent_stack.deinit(self.gpa); const parent_registers = self.register_manager.registers; + const parent_compare_flags_inst = self.compare_flags_inst; try self.branch_stack.append(.{}); @@ -2115,6 +2167,7 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) !void { defer saved_then_branch.deinit(self.gpa); self.register_manager.registers = parent_registers; + self.compare_flags_inst = parent_compare_flags_inst; self.stack.deinit(self.gpa); self.stack = parent_stack; @@ -2206,7 +2259,9 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) !void { self.branch_stack.pop().deinit(self.gpa); - return self.finishAir(inst, .unreach, .{ pl_op.operand, .none, .none }); + // We already took care of pl_op.operand earlier, so we're going + // to pass .none here + return self.finishAir(inst, .unreach, .{ .none, .none, .none }); } fn isNull(self: *Self, operand: MCValue) !MCValue { diff --git a/test/stage2/arm.zig b/test/stage2/arm.zig index b9ec95ea03..e3db9473dc 100644 --- a/test/stage2/arm.zig +++ b/test/stage2/arm.zig @@ -547,4 +547,25 @@ pub fn addCases(ctx: *TestContext) !void { , ); } + + { + var case = ctx.exe("save compare flags", linux_arm); + case.addCompareOutput( + \\pub fn main() void { + \\ foo(2, 1); + \\} + \\ + \\fn foo(x: u32, y: u32) void { + \\ const b = x > y; + \\ assert(b); + \\ assert(b); + \\} + \\ + \\pub fn assert(ok: bool) void { + \\ if (!ok) unreachable; // assertion failure + \\} + , + "", + ); + } }