Commit 7285f0557c

joachimschmidt557 <joachim.schmidt557@outlook.com>
2022-03-27 21:30:16
stage2 ARM: implement add/sub_with_overflow for u32/i32
1 parent e2e6980
Changed files (3)
src/arch/arm/CodeGen.zig
@@ -105,9 +105,12 @@ air_bookkeeping: @TypeOf(air_bookkeeping_init) = air_bookkeeping_init,
 const air_bookkeeping_init = if (std.debug.runtime_safety) @as(usize, 0) else {};
 
 const MCValue = union(enum) {
-    /// No runtime bits. `void` types, empty structs, u0, enums with 1 tag, etc.
-    /// TODO Look into deleting this tag and using `dead` instead, since every use
-    /// of MCValue.none should be instead looking at the type and noticing it is 0 bits.
+    /// No runtime bits. `void` types, empty structs, u0, enums with 1
+    /// tag, etc.
+    ///
+    /// TODO Look into deleting this tag and using `dead` instead,
+    /// since every use of MCValue.none should be instead looking at
+    /// the type and noticing it is 0 bits.
     none,
     /// Control flow will not allow this value to be observed.
     unreach,
@@ -116,20 +119,41 @@ const MCValue = union(enum) {
     /// The value is undefined.
     undef,
     /// A pointer-sized integer that fits in a register.
-    /// If the type is a pointer, this is the pointer address in virtual address space.
+    ///
+    /// If the type is a pointer, this is the pointer address in
+    /// virtual address space.
     immediate: u32,
     /// The value is in a target-specific register.
     register: Register,
+    /// The value is a tuple { wrapped: u32, overflow: u1 } where
+    /// wrapped is stored in the register and the overflow bit is
+    /// stored in the C flag of the CPSR.
+    ///
+    /// This MCValue is only generated by a add_with_overflow or
+    /// sub_with_overflow instruction operating on u32.
+    register_c_flag: Register,
+    /// The value is a tuple { wrapped: i32, overflow: u1 } where
+    /// wrapped is stored in the register and the overflow bit is
+    /// stored in the V flag of the CPSR.
+    ///
+    /// This MCValue is only generated by a add_with_overflow or
+    /// sub_with_overflow instruction operating on i32.
+    register_v_flag: Register,
     /// The value is in memory at a hard-coded address.
-    /// If the type is a pointer, it means the pointer address is at this memory location.
+    ///
+    /// If the type is a pointer, it means the pointer address is at
+    /// this memory location.
     memory: u64,
     /// The value is one of the stack variables.
-    /// If the type is a pointer, it means the pointer address is in the stack at this offset.
+    ///
+    /// If the type is a pointer, it means the pointer address is in
+    /// the stack at this offset.
     stack_offset: u32,
-    /// The value is a pointer to one of the stack variables (payload is stack offset).
+    /// The value is a pointer to one of the stack variables (payload
+    /// is stack offset).
     ptr_stack_offset: u32,
-    /// The value is in the compare flags assuming an unsigned operation,
-    /// with this operator applied on top of it.
+    /// The value is in the compare flags assuming an unsigned
+    /// operation, with this operator applied on top of it.
     compare_flags_unsigned: math.CompareOperator,
     /// The value is in the compare flags assuming a signed operation,
     /// with this operator applied on top of it.
@@ -554,8 +578,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
             .trunc_float,
             => try self.airUnaryMath(inst),
 
-            .add_with_overflow => try self.airAddWithOverflow(inst),
-            .sub_with_overflow => try self.airSubWithOverflow(inst),
+            .add_with_overflow => try self.airOverflow(inst),
+            .sub_with_overflow => try self.airOverflow(inst),
             .mul_with_overflow => try self.airMulWithOverflow(inst),
             .shl_with_overflow => try self.airShlWithOverflow(inst),
 
@@ -726,6 +750,12 @@ fn processDeath(self: *Self, inst: Air.Inst.Index) void {
         .register => |reg| {
             self.register_manager.freeReg(reg);
         },
+        .register_c_flag,
+        .register_v_flag,
+        => |reg| {
+            self.register_manager.freeReg(reg);
+            self.compare_flags_inst = null;
+        },
         .compare_flags_signed, .compare_flags_unsigned => {
             self.compare_flags_inst = null;
         },
@@ -841,8 +871,16 @@ fn allocRegOrMem(self: *Self, inst: Air.Inst.Index, reg_ok: bool) !MCValue {
 pub fn spillInstruction(self: *Self, reg: Register, inst: Air.Inst.Index) !void {
     const stack_mcv = try self.allocRegOrMem(inst, false);
     log.debug("spilling {} (%{d}) to stack mcv {any}", .{ reg, inst, stack_mcv });
+
     const reg_mcv = self.getResolvedInstValue(inst);
-    assert(reg == reg_mcv.register);
+    switch (reg_mcv) {
+        .register,
+        .register_c_flag,
+        .register_v_flag,
+        => |r| assert(r == reg),
+        else => unreachable, // not a register
+    }
+
     const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
     try branch.inst_table.put(self.gpa, inst, stack_mcv);
     try self.genSetStack(self.air.typeOfIndex(inst), stack_mcv.stack_offset, reg_mcv);
@@ -853,7 +891,14 @@ pub fn spillInstruction(self: *Self, reg: Register, inst: Air.Inst.Index) !void
 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);
+        switch (mcv) {
+            .compare_flags_signed,
+            .compare_flags_unsigned,
+            .register_c_flag,
+            .register_v_flag,
+            => {},
+            else => unreachable, // mcv doesn't occupy the compare flags
+        }
 
         const new_mcv = try self.allocRegOrMem(inst_to_save, true);
         try self.setRegOrMem(self.air.typeOfIndex(inst_to_save), new_mcv, mcv);
@@ -1268,7 +1313,7 @@ fn airSlice(self: *Self, inst: Air.Inst.Index) !void {
         const len = try self.resolveInst(bin_op.rhs);
         const len_ty = self.air.typeOf(bin_op.rhs);
 
-        const stack_offset = try self.allocMem(inst, 8, 8);
+        const stack_offset = try self.allocMem(inst, 8, 4);
         try self.genSetStack(ptr_ty, stack_offset, ptr);
         try self.genSetStack(len_ty, stack_offset - 4, len);
         break :result MCValue{ .stack_offset = stack_offset };
@@ -1306,14 +1351,71 @@ fn airMulSat(self: *Self, inst: Air.Inst.Index) !void {
     return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
 }
 
-fn airAddWithOverflow(self: *Self, inst: Air.Inst.Index) !void {
-    _ = inst;
-    return self.fail("TODO implement airAddWithOverflow for {}", .{self.target.cpu.arch});
-}
+fn airOverflow(self: *Self, inst: Air.Inst.Index) !void {
+    const tag = self.air.instructions.items(.tag)[inst];
+    const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
+    const extra = self.air.extraData(Air.Bin, ty_pl.payload).data;
+    const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: {
+        const lhs = try self.resolveInst(extra.lhs);
+        const rhs = try self.resolveInst(extra.rhs);
+        const lhs_ty = self.air.typeOf(extra.lhs);
+        const rhs_ty = self.air.typeOf(extra.rhs);
+
+        switch (lhs_ty.zigTypeTag()) {
+            .Vector => return self.fail("TODO implement add_with_overflow/sub_with_overflow for vectors", .{}),
+            .Int => {
+                assert(lhs_ty.eql(rhs_ty, self.target.*));
+                const int_info = lhs_ty.intInfo(self.target.*);
+                if (int_info.bits < 32) {
+                    return self.fail("TODO ARM overflow operations on integers < u32/i32", .{});
+                } else if (int_info.bits == 32) {
+                    // Only say yes if the operation is
+                    // commutative, i.e. we can swap both of the
+                    // operands
+                    const lhs_immediate_ok = switch (tag) {
+                        .add_with_overflow => lhs == .immediate and Instruction.Operand.fromU32(lhs.immediate) != null,
+                        .sub_with_overflow => false,
+                        else => unreachable,
+                    };
+                    const rhs_immediate_ok = switch (tag) {
+                        .add_with_overflow,
+                        .sub_with_overflow,
+                        => rhs == .immediate and Instruction.Operand.fromU32(rhs.immediate) != null,
+                        else => unreachable,
+                    };
 
-fn airSubWithOverflow(self: *Self, inst: Air.Inst.Index) !void {
-    _ = inst;
-    return self.fail("TODO implement airSubWithOverflow for {}", .{self.target.cpu.arch});
+                    const mir_tag: Mir.Inst.Tag = switch (tag) {
+                        .add_with_overflow => .adds,
+                        .sub_with_overflow => .subs,
+                        else => unreachable,
+                    };
+
+                    try self.spillCompareFlagsIfOccupied();
+                    self.compare_flags_inst = inst;
+
+                    const dest = blk: {
+                        if (rhs_immediate_ok) {
+                            break :blk try self.binOpImmediate(mir_tag, null, lhs, rhs, lhs_ty, false);
+                        } else if (lhs_immediate_ok) {
+                            // swap lhs and rhs
+                            break :blk try self.binOpImmediate(mir_tag, null, rhs, lhs, rhs_ty, true);
+                        } else {
+                            break :blk try self.binOpRegister(mir_tag, null, lhs, rhs, lhs_ty, rhs_ty);
+                        }
+                    };
+
+                    switch (int_info.signedness) {
+                        .unsigned => break :result MCValue{ .register_c_flag = dest.register },
+                        .signed => break :result MCValue{ .register_v_flag = dest.register },
+                    }
+                } else {
+                    return self.fail("TODO ARM overflow operations on integers > u32/i32", .{});
+                }
+            },
+            else => unreachable,
+        }
+    };
+    return self.finishAir(inst, result, .{ extra.lhs, extra.rhs, .none });
 }
 
 fn airMulWithOverflow(self: *Self, inst: Air.Inst.Index) !void {
@@ -1424,7 +1526,6 @@ fn errUnionPayload(self: *Self, error_union_mcv: MCValue, error_union_ty: Type)
     const eu_align = @intCast(u32, error_union_ty.abiAlignment(self.target.*));
     const offset = std.mem.alignForwardGeneric(u32, error_size, eu_align);
 
-    // TODO optimization for small error unions: put into register
     switch (error_union_mcv) {
         .register => return self.fail("TODO errUnionPayload for registers", .{}),
         .stack_argument_offset => |off| {
@@ -1791,8 +1892,11 @@ fn load(self: *Self, dst_mcv: MCValue, ptr: MCValue, ptr_ty: Type) InnerError!vo
         .undef => unreachable,
         .unreach => unreachable,
         .dead => unreachable,
-        .compare_flags_unsigned => unreachable,
-        .compare_flags_signed => unreachable,
+        .compare_flags_unsigned,
+        .compare_flags_signed,
+        .register_c_flag,
+        .register_v_flag,
+        => unreachable, // cannot hold an address
         .immediate => |imm| try self.setRegOrMem(elem_ty, dst_mcv, .{ .memory = imm }),
         .ptr_stack_offset => |off| try self.setRegOrMem(elem_ty, dst_mcv, .{ .stack_offset = off }),
         .register => |reg| {
@@ -1887,8 +1991,11 @@ fn store(self: *Self, ptr: MCValue, value: MCValue, ptr_ty: Type, value_ty: Type
         .undef => unreachable,
         .unreach => unreachable,
         .dead => unreachable,
-        .compare_flags_unsigned => unreachable,
-        .compare_flags_signed => unreachable,
+        .compare_flags_unsigned,
+        .compare_flags_signed,
+        .register_c_flag,
+        .register_v_flag,
+        => unreachable, // cannot hold an address
         .immediate => |imm| {
             try self.setRegOrMem(value_ty, .{ .memory = imm }, value);
         },
@@ -2043,6 +2150,50 @@ fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) !void {
             .memory => |addr| {
                 break :result MCValue{ .memory = addr + struct_field_offset };
             },
+            .register_c_flag,
+            .register_v_flag,
+            => |reg| {
+                switch (index) {
+                    0 => {
+                        // get wrapped value: return register
+                        break :result MCValue{ .register = reg };
+                    },
+                    1 => {
+                        // get overflow bit: set register to C flag
+                        // resp. V flag
+                        const dest_reg = try self.register_manager.allocReg(null);
+
+                        // mov reg, #0
+                        _ = try self.addInst(.{
+                            .tag = .mov,
+                            .data = .{ .rr_op = .{
+                                .rd = dest_reg,
+                                .rn = .r0,
+                                .op = Instruction.Operand.fromU32(0).?,
+                            } },
+                        });
+
+                        // C flag: movcs reg, #1
+                        // V flag: movvs reg, #1
+                        _ = try self.addInst(.{
+                            .tag = .mov,
+                            .cond = switch (mcv) {
+                                .register_c_flag => .cs,
+                                .register_v_flag => .vs,
+                                else => unreachable,
+                            },
+                            .data = .{ .rr_op = .{
+                                .rd = dest_reg,
+                                .rn = .r0,
+                                .op = Instruction.Operand.fromU32(1).?,
+                            } },
+                        });
+
+                        break :result MCValue{ .register = dest_reg };
+                    },
+                    else => unreachable,
+                }
+            },
             else => return self.fail("TODO implement codegen struct_field_val for {}", .{mcv}),
         }
     };
@@ -2132,7 +2283,9 @@ fn binOpRegister(
 
     const mir_data: Mir.Inst.Data = switch (mir_tag) {
         .add,
+        .adds,
         .sub,
+        .subs,
         .cmp,
         .@"and",
         .orr,
@@ -2232,7 +2385,9 @@ fn binOpImmediate(
 
     const mir_data: Mir.Inst.Data = switch (mir_tag) {
         .add,
+        .adds,
         .sub,
+        .subs,
         .cmp,
         .@"and",
         .orr,
@@ -2814,14 +2969,6 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions.
 
         switch (mc_arg) {
             .none => continue,
-            .undef => unreachable,
-            .immediate => unreachable,
-            .unreach => unreachable,
-            .dead => unreachable,
-            .memory => unreachable,
-            .compare_flags_signed => unreachable,
-            .compare_flags_unsigned => unreachable,
-            .ptr_stack_offset => unreachable,
             .register => |reg| {
                 try self.register_manager.getReg(reg, null);
                 try self.genSetReg(arg_ty, reg, arg_mcv);
@@ -2832,6 +2979,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions.
                 info.stack_byte_count - offset,
                 arg_mcv,
             ),
+            else => unreachable,
         }
     }
 
@@ -3774,6 +3922,11 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: u32, mcv: MCValue) InnerErro
                 else => return self.fail("TODO implement storing other types abi_size={}", .{abi_size}),
             }
         },
+        .register_c_flag,
+        .register_v_flag,
+        => {
+            return self.fail("TODO implement genSetStack {}", .{mcv});
+        },
         .memory,
         .stack_argument_offset,
         .stack_offset,
@@ -4015,6 +4168,8 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void
                 } },
             });
         },
+        .register_c_flag => unreachable, // doesn't fit into a register
+        .register_v_flag => unreachable, // doesn't fit into a register
         .memory => |addr| {
             // The value is in memory at a hard-coded address.
             // If the type is a pointer, it means the pointer address is at this memory location.
@@ -4149,6 +4304,11 @@ fn genSetStackArgument(self: *Self, ty: Type, stack_offset: u32, mcv: MCValue) I
                 else => return self.fail("TODO implement storing other types abi_size={}", .{abi_size}),
             }
         },
+        .register_c_flag,
+        .register_v_flag,
+        => {
+            return self.fail("TODO implement genSetStack {}", .{mcv});
+        },
         .stack_offset,
         .memory,
         .stack_argument_offset,
src/arch/arm/Emit.zig
@@ -79,6 +79,7 @@ pub fn emitMir(
         const inst = @intCast(u32, index);
         switch (tag) {
             .add => try emit.mirDataProcessing(inst),
+            .adds => try emit.mirDataProcessing(inst),
             .@"and" => try emit.mirDataProcessing(inst),
             .cmp => try emit.mirDataProcessing(inst),
             .eor => try emit.mirDataProcessing(inst),
@@ -87,6 +88,7 @@ pub fn emitMir(
             .orr => try emit.mirDataProcessing(inst),
             .rsb => try emit.mirDataProcessing(inst),
             .sub => try emit.mirDataProcessing(inst),
+            .subs => try emit.mirDataProcessing(inst),
 
             .asr => try emit.mirShift(inst),
             .lsl => try emit.mirShift(inst),
@@ -474,6 +476,7 @@ fn mirDataProcessing(emit: *Emit, inst: Mir.Inst.Index) !void {
 
     switch (tag) {
         .add => try emit.writeInstruction(Instruction.add(cond, rr_op.rd, rr_op.rn, rr_op.op)),
+        .adds => try emit.writeInstruction(Instruction.adds(cond, rr_op.rd, rr_op.rn, rr_op.op)),
         .@"and" => try emit.writeInstruction(Instruction.@"and"(cond, rr_op.rd, rr_op.rn, rr_op.op)),
         .cmp => try emit.writeInstruction(Instruction.cmp(cond, rr_op.rn, rr_op.op)),
         .eor => try emit.writeInstruction(Instruction.eor(cond, rr_op.rd, rr_op.rn, rr_op.op)),
@@ -482,6 +485,7 @@ fn mirDataProcessing(emit: *Emit, inst: Mir.Inst.Index) !void {
         .orr => try emit.writeInstruction(Instruction.orr(cond, rr_op.rd, rr_op.rn, rr_op.op)),
         .rsb => try emit.writeInstruction(Instruction.rsb(cond, rr_op.rd, rr_op.rn, rr_op.op)),
         .sub => try emit.writeInstruction(Instruction.sub(cond, rr_op.rd, rr_op.rn, rr_op.op)),
+        .subs => try emit.writeInstruction(Instruction.sub(cond, rr_op.rd, rr_op.rn, rr_op.op)),
         else => unreachable,
     }
 }
src/arch/arm/Mir.zig
@@ -28,6 +28,8 @@ pub const Inst = struct {
     pub const Tag = enum(u16) {
         /// Add
         add,
+        /// Add, update condition flags
+        adds,
         /// Bitwise AND
         @"and",
         /// Arithmetic Shift Right
@@ -108,6 +110,8 @@ pub const Inst = struct {
         strh,
         /// Subtract
         sub,
+        /// Subtract, update condition flags
+        subs,
         /// Supervisor Call
         svc,
         /// Unsigned Bit Field Extract