Commit 7285f0557c
Changed files (3)
src
arch
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