mirror of
https://github.com/ziglang/zig.git
synced 2026-02-15 13:58:27 +00:00
Merge pull request #11373 from joachimschmidt557/stage2-arm
stage2 ARM: implement overflow operations for ints <= 32 bits
This commit is contained in:
commit
3ee44ce949
@ -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);
|
||||
@ -1196,7 +1241,7 @@ fn minMax(
|
||||
// register.
|
||||
assert(lhs_reg != rhs_reg); // see note above
|
||||
|
||||
_ = try self.binOpRegister(.cmp_eq, null, .{ .register = lhs_reg }, .{ .register = rhs_reg }, lhs_ty, rhs_ty);
|
||||
_ = try self.binOpRegister(.cmp, null, .{ .register = lhs_reg }, .{ .register = rhs_reg }, lhs_ty, rhs_ty);
|
||||
|
||||
const cond_choose_lhs: Condition = switch (tag) {
|
||||
.max => switch (int_info.signedness) {
|
||||
@ -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,24 +1351,321 @@ 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);
|
||||
|
||||
fn airSubWithOverflow(self: *Self, inst: Air.Inst.Index) !void {
|
||||
_ = inst;
|
||||
return self.fail("TODO implement airSubWithOverflow for {}", .{self.target.cpu.arch});
|
||||
const tuple_ty = self.air.typeOfIndex(inst);
|
||||
const tuple_size = @intCast(u32, tuple_ty.abiSize(self.target.*));
|
||||
const tuple_align = tuple_ty.abiAlignment(self.target.*);
|
||||
const overflow_bit_offset = @intCast(u32, tuple_ty.structFieldOffset(1, self.target.*));
|
||||
|
||||
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) {
|
||||
const stack_offset = try self.allocMem(inst, tuple_size, tuple_align);
|
||||
|
||||
try self.spillCompareFlagsIfOccupied();
|
||||
self.compare_flags_inst = null;
|
||||
|
||||
const base_tag: Air.Inst.Tag = switch (tag) {
|
||||
.add_with_overflow => .add,
|
||||
.sub_with_overflow => .sub,
|
||||
else => unreachable,
|
||||
};
|
||||
const dest = try self.binOp(base_tag, null, lhs, rhs, lhs_ty, rhs_ty);
|
||||
const dest_reg = dest.register;
|
||||
self.register_manager.freezeRegs(&.{dest_reg});
|
||||
defer self.register_manager.unfreezeRegs(&.{dest_reg});
|
||||
|
||||
const truncated_reg = try self.register_manager.allocReg(null);
|
||||
self.register_manager.freezeRegs(&.{truncated_reg});
|
||||
defer self.register_manager.unfreezeRegs(&.{truncated_reg});
|
||||
|
||||
// sbfx/ubfx truncated, dest, #0, #bits
|
||||
try self.truncRegister(dest_reg, truncated_reg, int_info.signedness, int_info.bits);
|
||||
|
||||
// cmp dest, truncated
|
||||
_ = try self.binOp(.cmp_eq, null, dest, .{ .register = truncated_reg }, Type.usize, Type.usize);
|
||||
|
||||
try self.genSetStack(lhs_ty, stack_offset, .{ .register = truncated_reg });
|
||||
try self.genSetStack(Type.initTag(.u1), stack_offset - overflow_bit_offset, .{ .compare_flags_unsigned = .neq });
|
||||
|
||||
break :result MCValue{ .stack_offset = stack_offset };
|
||||
} 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,
|
||||
};
|
||||
|
||||
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 {
|
||||
_ = inst;
|
||||
return self.fail("TODO implement airMulWithOverflow for {}", .{self.target.cpu.arch});
|
||||
const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
|
||||
const extra = self.air.extraData(Air.Bin, ty_pl.payload).data;
|
||||
if (self.liveness.isUnused(inst)) return self.finishAir(inst, .dead, .{ extra.lhs, extra.rhs, .none });
|
||||
const result: MCValue = 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);
|
||||
|
||||
const tuple_ty = self.air.typeOfIndex(inst);
|
||||
const tuple_size = @intCast(u32, tuple_ty.abiSize(self.target.*));
|
||||
const tuple_align = tuple_ty.abiAlignment(self.target.*);
|
||||
const overflow_bit_offset = @intCast(u32, tuple_ty.structFieldOffset(1, self.target.*));
|
||||
|
||||
switch (lhs_ty.zigTypeTag()) {
|
||||
.Vector => return self.fail("TODO implement mul_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 <= 16) {
|
||||
const stack_offset = try self.allocMem(inst, tuple_size, tuple_align);
|
||||
|
||||
try self.spillCompareFlagsIfOccupied();
|
||||
self.compare_flags_inst = null;
|
||||
|
||||
const base_tag: Mir.Inst.Tag = switch (int_info.signedness) {
|
||||
.signed => .smulbb,
|
||||
.unsigned => .mul,
|
||||
};
|
||||
|
||||
const dest = try self.binOpRegister(base_tag, null, lhs, rhs, lhs_ty, rhs_ty);
|
||||
const dest_reg = dest.register;
|
||||
self.register_manager.freezeRegs(&.{dest_reg});
|
||||
defer self.register_manager.unfreezeRegs(&.{dest_reg});
|
||||
|
||||
const truncated_reg = try self.register_manager.allocReg(null);
|
||||
self.register_manager.freezeRegs(&.{truncated_reg});
|
||||
defer self.register_manager.unfreezeRegs(&.{truncated_reg});
|
||||
|
||||
// sbfx/ubfx truncated, dest, #0, #bits
|
||||
try self.truncRegister(dest_reg, truncated_reg, int_info.signedness, int_info.bits);
|
||||
|
||||
// cmp dest, truncated
|
||||
_ = try self.binOp(.cmp_eq, null, dest, .{ .register = truncated_reg }, Type.usize, Type.usize);
|
||||
|
||||
try self.genSetStack(lhs_ty, stack_offset, .{ .register = truncated_reg });
|
||||
try self.genSetStack(Type.initTag(.u1), stack_offset - overflow_bit_offset, .{ .compare_flags_unsigned = .neq });
|
||||
|
||||
break :result MCValue{ .stack_offset = stack_offset };
|
||||
} else if (int_info.bits <= 32) {
|
||||
const stack_offset = try self.allocMem(inst, tuple_size, tuple_align);
|
||||
|
||||
try self.spillCompareFlagsIfOccupied();
|
||||
self.compare_flags_inst = null;
|
||||
|
||||
const base_tag: Mir.Inst.Tag = switch (int_info.signedness) {
|
||||
.signed => .smull,
|
||||
.unsigned => .umull,
|
||||
};
|
||||
|
||||
// TODO extract umull etc. to binOpTwoRegister
|
||||
// once MCValue.rr is implemented
|
||||
const lhs_is_register = lhs == .register;
|
||||
const rhs_is_register = rhs == .register;
|
||||
|
||||
if (lhs_is_register) self.register_manager.freezeRegs(&.{lhs.register});
|
||||
if (rhs_is_register) self.register_manager.freezeRegs(&.{rhs.register});
|
||||
|
||||
const lhs_reg = if (lhs_is_register) lhs.register else blk: {
|
||||
const reg = try self.register_manager.allocReg(null);
|
||||
self.register_manager.freezeRegs(&.{reg});
|
||||
|
||||
break :blk reg;
|
||||
};
|
||||
defer self.register_manager.unfreezeRegs(&.{lhs_reg});
|
||||
|
||||
const rhs_reg = if (rhs_is_register) rhs.register else blk: {
|
||||
const reg = try self.register_manager.allocReg(null);
|
||||
self.register_manager.freezeRegs(&.{reg});
|
||||
|
||||
break :blk reg;
|
||||
};
|
||||
defer self.register_manager.unfreezeRegs(&.{rhs_reg});
|
||||
|
||||
const dest_regs = try self.register_manager.allocRegs(2, .{ null, null });
|
||||
self.register_manager.freezeRegs(&dest_regs);
|
||||
defer self.register_manager.unfreezeRegs(&dest_regs);
|
||||
const rdlo = dest_regs[0];
|
||||
const rdhi = dest_regs[1];
|
||||
|
||||
if (!lhs_is_register) try self.genSetReg(lhs_ty, lhs_reg, lhs);
|
||||
if (!rhs_is_register) try self.genSetReg(rhs_ty, rhs_reg, rhs);
|
||||
|
||||
const truncated_reg = try self.register_manager.allocReg(null);
|
||||
self.register_manager.freezeRegs(&.{truncated_reg});
|
||||
defer self.register_manager.unfreezeRegs(&.{truncated_reg});
|
||||
|
||||
_ = try self.addInst(.{
|
||||
.tag = base_tag,
|
||||
.data = .{ .rrrr = .{
|
||||
.rdlo = rdlo,
|
||||
.rdhi = rdhi,
|
||||
.rn = lhs_reg,
|
||||
.rm = rhs_reg,
|
||||
} },
|
||||
});
|
||||
|
||||
// sbfx/ubfx truncated, rdlo, #0, #bits
|
||||
try self.truncRegister(rdlo, truncated_reg, int_info.signedness, int_info.bits);
|
||||
|
||||
// str truncated, [...]
|
||||
try self.genSetStack(lhs_ty, stack_offset, .{ .register = truncated_reg });
|
||||
|
||||
// cmp truncated, rdlo
|
||||
_ = try self.binOp(.cmp_eq, null, .{ .register = truncated_reg }, .{ .register = rdlo }, Type.usize, Type.usize);
|
||||
|
||||
// mov rdlo, #0
|
||||
_ = try self.addInst(.{
|
||||
.tag = .mov,
|
||||
.data = .{ .rr_op = .{
|
||||
.rd = rdlo,
|
||||
.rn = .r0,
|
||||
.op = Instruction.Operand.fromU32(0).?,
|
||||
} },
|
||||
});
|
||||
|
||||
// movne rdlo, #1
|
||||
_ = try self.addInst(.{
|
||||
.tag = .mov,
|
||||
.cond = .ne,
|
||||
.data = .{ .rr_op = .{
|
||||
.rd = rdlo,
|
||||
.rn = .r0,
|
||||
.op = Instruction.Operand.fromU32(1).?,
|
||||
} },
|
||||
});
|
||||
|
||||
// cmp rdhi, #0
|
||||
_ = try self.binOp(.cmp_eq, null, .{ .register = rdhi }, .{ .immediate = 0 }, Type.usize, Type.usize);
|
||||
|
||||
// movne rdlo, #1
|
||||
_ = try self.addInst(.{
|
||||
.tag = .mov,
|
||||
.cond = .ne,
|
||||
.data = .{ .rr_op = .{
|
||||
.rd = rdlo,
|
||||
.rn = .r0,
|
||||
.op = Instruction.Operand.fromU32(1).?,
|
||||
} },
|
||||
});
|
||||
|
||||
// strb rdlo, [...]
|
||||
try self.genSetStack(Type.initTag(.u1), stack_offset - overflow_bit_offset, .{ .register = rdlo });
|
||||
|
||||
break :result MCValue{ .stack_offset = stack_offset };
|
||||
} 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 airShlWithOverflow(self: *Self, inst: Air.Inst.Index) !void {
|
||||
_ = inst;
|
||||
return self.fail("TODO implement airShlWithOverflow for {}", .{self.target.cpu.arch});
|
||||
const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
|
||||
const extra = self.air.extraData(Air.Bin, ty_pl.payload).data;
|
||||
if (self.liveness.isUnused(inst)) return self.finishAir(inst, .dead, .{ extra.lhs, extra.rhs, .none });
|
||||
const result: MCValue = 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);
|
||||
|
||||
const tuple_ty = self.air.typeOfIndex(inst);
|
||||
const tuple_size = @intCast(u32, tuple_ty.abiSize(self.target.*));
|
||||
const tuple_align = tuple_ty.abiAlignment(self.target.*);
|
||||
const overflow_bit_offset = @intCast(u32, tuple_ty.structFieldOffset(1, self.target.*));
|
||||
|
||||
switch (lhs_ty.zigTypeTag()) {
|
||||
.Vector => return self.fail("TODO implement shl_with_overflow for vectors", .{}),
|
||||
.Int => {
|
||||
const int_info = lhs_ty.intInfo(self.target.*);
|
||||
if (int_info.bits <= 32) {
|
||||
const stack_offset = try self.allocMem(inst, tuple_size, tuple_align);
|
||||
|
||||
if (lhs == .register) self.register_manager.freezeRegs(&.{lhs.register});
|
||||
defer if (lhs == .register) self.register_manager.unfreezeRegs(&.{lhs.register});
|
||||
|
||||
try self.spillCompareFlagsIfOccupied();
|
||||
self.compare_flags_inst = null;
|
||||
|
||||
// lsl dest, lhs, rhs
|
||||
const dest = try self.binOp(.shl, null, lhs, rhs, lhs_ty, rhs_ty);
|
||||
|
||||
// asr/lsr reconstructed, dest, rhs
|
||||
const reconstructed = try self.binOp(.shr, null, dest, rhs, lhs_ty, rhs_ty);
|
||||
|
||||
// cmp lhs, reconstructed
|
||||
_ = try self.binOp(.cmp_eq, null, lhs, reconstructed, lhs_ty, lhs_ty);
|
||||
|
||||
try self.genSetStack(lhs_ty, stack_offset, dest);
|
||||
try self.genSetStack(Type.initTag(.u1), stack_offset - overflow_bit_offset, .{ .compare_flags_unsigned = .neq });
|
||||
|
||||
break :result MCValue{ .stack_offset = stack_offset };
|
||||
} 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 airDiv(self: *Self, inst: Air.Inst.Index) !void {
|
||||
@ -1424,7 +1766,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 +2132,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 +2231,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 +2390,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}),
|
||||
}
|
||||
};
|
||||
@ -2067,7 +2458,7 @@ fn airFieldParentPtr(self: *Self, inst: Air.Inst.Index) !void {
|
||||
/// Asserts that generating an instruction of that form is possible.
|
||||
fn binOpRegister(
|
||||
self: *Self,
|
||||
tag: Air.Inst.Tag,
|
||||
mir_tag: Mir.Inst.Tag,
|
||||
maybe_inst: ?Air.Inst.Index,
|
||||
lhs: MCValue,
|
||||
rhs: MCValue,
|
||||
@ -2112,8 +2503,8 @@ fn binOpRegister(
|
||||
};
|
||||
defer self.register_manager.unfreezeRegs(&.{rhs_reg});
|
||||
|
||||
const dest_reg = switch (tag) {
|
||||
.cmp_eq => .r0, // cmp has no destination regardless
|
||||
const dest_reg = switch (mir_tag) {
|
||||
.cmp => .r0, // cmp has no destination regardless
|
||||
else => if (maybe_inst) |inst| blk: {
|
||||
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
||||
|
||||
@ -2130,47 +2521,31 @@ fn binOpRegister(
|
||||
if (!lhs_is_register) try self.genSetReg(lhs_ty, lhs_reg, lhs);
|
||||
if (!rhs_is_register) try self.genSetReg(rhs_ty, rhs_reg, rhs);
|
||||
|
||||
const mir_tag: Mir.Inst.Tag = switch (tag) {
|
||||
.add => .add,
|
||||
.sub => .sub,
|
||||
.cmp_eq => .cmp,
|
||||
.mul => .mul,
|
||||
.bit_and,
|
||||
.bool_and,
|
||||
=> .@"and",
|
||||
.bit_or,
|
||||
.bool_or,
|
||||
=> .orr,
|
||||
.shl_exact => .lsl,
|
||||
.shr_exact => switch (lhs_ty.intInfo(self.target.*).signedness) {
|
||||
.signed => Mir.Inst.Tag.asr,
|
||||
.unsigned => Mir.Inst.Tag.lsr,
|
||||
},
|
||||
.xor => .eor,
|
||||
else => unreachable,
|
||||
};
|
||||
const mir_data: Mir.Inst.Data = switch (tag) {
|
||||
const mir_data: Mir.Inst.Data = switch (mir_tag) {
|
||||
.add,
|
||||
.adds,
|
||||
.sub,
|
||||
.cmp_eq,
|
||||
.bit_and,
|
||||
.bool_and,
|
||||
.bit_or,
|
||||
.bool_or,
|
||||
.xor,
|
||||
.subs,
|
||||
.cmp,
|
||||
.@"and",
|
||||
.orr,
|
||||
.eor,
|
||||
=> .{ .rr_op = .{
|
||||
.rd = dest_reg,
|
||||
.rn = lhs_reg,
|
||||
.op = Instruction.Operand.reg(rhs_reg, Instruction.Operand.Shift.none),
|
||||
} },
|
||||
.shl_exact,
|
||||
.shr_exact,
|
||||
.lsl,
|
||||
.asr,
|
||||
.lsr,
|
||||
=> .{ .rr_shift = .{
|
||||
.rd = dest_reg,
|
||||
.rm = lhs_reg,
|
||||
.shift_amount = Instruction.ShiftAmount.reg(rhs_reg),
|
||||
} },
|
||||
.mul => .{ .rrr = .{
|
||||
.mul,
|
||||
.smulbb,
|
||||
=> .{ .rrr = .{
|
||||
.rd = dest_reg,
|
||||
.rn = lhs_reg,
|
||||
.rm = rhs_reg,
|
||||
@ -2200,7 +2575,7 @@ fn binOpRegister(
|
||||
/// Asserts that generating an instruction of that form is possible.
|
||||
fn binOpImmediate(
|
||||
self: *Self,
|
||||
tag: Air.Inst.Tag,
|
||||
mir_tag: Mir.Inst.Tag,
|
||||
maybe_inst: ?Air.Inst.Index,
|
||||
lhs: MCValue,
|
||||
rhs: MCValue,
|
||||
@ -2230,8 +2605,8 @@ fn binOpImmediate(
|
||||
};
|
||||
defer self.register_manager.unfreezeRegs(&.{lhs_reg});
|
||||
|
||||
const dest_reg = switch (tag) {
|
||||
.cmp_eq => .r0, // cmp has no destination reg
|
||||
const dest_reg = switch (mir_tag) {
|
||||
.cmp => .r0, // cmp has no destination reg
|
||||
else => if (maybe_inst) |inst| blk: {
|
||||
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
||||
|
||||
@ -2250,40 +2625,23 @@ fn binOpImmediate(
|
||||
|
||||
if (!lhs_is_register) try self.genSetReg(lhs_ty, lhs_reg, lhs);
|
||||
|
||||
const mir_tag: Mir.Inst.Tag = switch (tag) {
|
||||
.add => .add,
|
||||
.sub => .sub,
|
||||
.cmp_eq => .cmp,
|
||||
.bit_and,
|
||||
.bool_and,
|
||||
=> .@"and",
|
||||
.bit_or,
|
||||
.bool_or,
|
||||
=> .orr,
|
||||
.shl_exact => .lsl,
|
||||
.shr_exact => switch (lhs_ty.intInfo(self.target.*).signedness) {
|
||||
.signed => Mir.Inst.Tag.asr,
|
||||
.unsigned => Mir.Inst.Tag.lsr,
|
||||
},
|
||||
.xor => .eor,
|
||||
else => unreachable,
|
||||
};
|
||||
const mir_data: Mir.Inst.Data = switch (tag) {
|
||||
const mir_data: Mir.Inst.Data = switch (mir_tag) {
|
||||
.add,
|
||||
.adds,
|
||||
.sub,
|
||||
.cmp_eq,
|
||||
.bit_and,
|
||||
.bool_and,
|
||||
.bit_or,
|
||||
.bool_or,
|
||||
.xor,
|
||||
.subs,
|
||||
.cmp,
|
||||
.@"and",
|
||||
.orr,
|
||||
.eor,
|
||||
=> .{ .rr_op = .{
|
||||
.rd = dest_reg,
|
||||
.rn = lhs_reg,
|
||||
.op = Instruction.Operand.fromU32(rhs.immediate).?,
|
||||
} },
|
||||
.shl_exact,
|
||||
.shr_exact,
|
||||
.lsl,
|
||||
.asr,
|
||||
.lsr,
|
||||
=> .{ .rr_shift = .{
|
||||
.rd = dest_reg,
|
||||
.rm = lhs_reg,
|
||||
@ -2352,13 +2710,20 @@ fn binOp(
|
||||
else => unreachable,
|
||||
};
|
||||
|
||||
const mir_tag: Mir.Inst.Tag = switch (tag) {
|
||||
.add => .add,
|
||||
.sub => .sub,
|
||||
.cmp_eq => .cmp,
|
||||
else => unreachable,
|
||||
};
|
||||
|
||||
if (rhs_immediate_ok) {
|
||||
return try self.binOpImmediate(tag, maybe_inst, lhs, rhs, lhs_ty, false);
|
||||
return try self.binOpImmediate(mir_tag, maybe_inst, lhs, rhs, lhs_ty, false);
|
||||
} else if (lhs_immediate_ok) {
|
||||
// swap lhs and rhs
|
||||
return try self.binOpImmediate(tag, maybe_inst, rhs, lhs, rhs_ty, true);
|
||||
return try self.binOpImmediate(mir_tag, maybe_inst, rhs, lhs, rhs_ty, true);
|
||||
} else {
|
||||
return try self.binOpRegister(tag, maybe_inst, lhs, rhs, lhs_ty, rhs_ty);
|
||||
return try self.binOpRegister(mir_tag, maybe_inst, lhs, rhs, lhs_ty, rhs_ty);
|
||||
}
|
||||
} else {
|
||||
return self.fail("TODO ARM binary operations on integers > u32/i32", .{});
|
||||
@ -2378,7 +2743,7 @@ fn binOp(
|
||||
// TODO add optimisations for multiplication
|
||||
// with immediates, for example a * 2 can be
|
||||
// lowered to a << 1
|
||||
return try self.binOpRegister(tag, maybe_inst, lhs, rhs, lhs_ty, rhs_ty);
|
||||
return try self.binOpRegister(.mul, maybe_inst, lhs, rhs, lhs_ty, rhs_ty);
|
||||
} else {
|
||||
return self.fail("TODO ARM binary operations on integers > u32/i32", .{});
|
||||
}
|
||||
@ -2432,13 +2797,20 @@ fn binOp(
|
||||
const lhs_immediate_ok = lhs == .immediate and Instruction.Operand.fromU32(lhs.immediate) != null;
|
||||
const rhs_immediate_ok = rhs == .immediate and Instruction.Operand.fromU32(rhs.immediate) != null;
|
||||
|
||||
const mir_tag: Mir.Inst.Tag = switch (tag) {
|
||||
.bit_and => .@"and",
|
||||
.bit_or => .orr,
|
||||
.xor => .eor,
|
||||
else => unreachable,
|
||||
};
|
||||
|
||||
if (rhs_immediate_ok) {
|
||||
return try self.binOpImmediate(tag, maybe_inst, lhs, rhs, lhs_ty, false);
|
||||
return try self.binOpImmediate(mir_tag, maybe_inst, lhs, rhs, lhs_ty, false);
|
||||
} else if (lhs_immediate_ok) {
|
||||
// swap lhs and rhs
|
||||
return try self.binOpImmediate(tag, maybe_inst, rhs, lhs, rhs_ty, true);
|
||||
return try self.binOpImmediate(mir_tag, maybe_inst, rhs, lhs, rhs_ty, true);
|
||||
} else {
|
||||
return try self.binOpRegister(tag, maybe_inst, lhs, rhs, lhs_ty, rhs_ty);
|
||||
return try self.binOpRegister(mir_tag, maybe_inst, lhs, rhs, lhs_ty, rhs_ty);
|
||||
}
|
||||
} else {
|
||||
return self.fail("TODO ARM binary operations on integers > u32/i32", .{});
|
||||
@ -2457,10 +2829,19 @@ fn binOp(
|
||||
if (int_info.bits <= 32) {
|
||||
const rhs_immediate_ok = rhs == .immediate;
|
||||
|
||||
const mir_tag: Mir.Inst.Tag = switch (tag) {
|
||||
.shl_exact => .lsl,
|
||||
.shr_exact => switch (lhs_ty.intInfo(self.target.*).signedness) {
|
||||
.signed => Mir.Inst.Tag.asr,
|
||||
.unsigned => Mir.Inst.Tag.lsr,
|
||||
},
|
||||
else => unreachable,
|
||||
};
|
||||
|
||||
if (rhs_immediate_ok) {
|
||||
return try self.binOpImmediate(tag, maybe_inst, lhs, rhs, lhs_ty, false);
|
||||
return try self.binOpImmediate(mir_tag, maybe_inst, lhs, rhs, lhs_ty, false);
|
||||
} else {
|
||||
return try self.binOpRegister(tag, maybe_inst, lhs, rhs, lhs_ty, rhs_ty);
|
||||
return try self.binOpRegister(mir_tag, maybe_inst, lhs, rhs, lhs_ty, rhs_ty);
|
||||
}
|
||||
} else {
|
||||
return self.fail("TODO ARM binary operations on integers > u32/i32", .{});
|
||||
@ -2512,13 +2893,19 @@ fn binOp(
|
||||
const lhs_immediate_ok = lhs == .immediate;
|
||||
const rhs_immediate_ok = rhs == .immediate;
|
||||
|
||||
const mir_tag: Mir.Inst.Tag = switch (tag) {
|
||||
.bool_and => .@"and",
|
||||
.bool_or => .orr,
|
||||
else => unreachable,
|
||||
};
|
||||
|
||||
if (rhs_immediate_ok) {
|
||||
return try self.binOpImmediate(tag, maybe_inst, lhs, rhs, lhs_ty, false);
|
||||
return try self.binOpImmediate(mir_tag, maybe_inst, lhs, rhs, lhs_ty, false);
|
||||
} else if (lhs_immediate_ok) {
|
||||
// swap lhs and rhs
|
||||
return try self.binOpImmediate(tag, maybe_inst, rhs, lhs, rhs_ty, true);
|
||||
return try self.binOpImmediate(mir_tag, maybe_inst, rhs, lhs, rhs_ty, true);
|
||||
} else {
|
||||
return try self.binOpRegister(tag, maybe_inst, lhs, rhs, lhs_ty, rhs_ty);
|
||||
return try self.binOpRegister(mir_tag, maybe_inst, lhs, rhs, lhs_ty, rhs_ty);
|
||||
}
|
||||
},
|
||||
else => unreachable,
|
||||
@ -2537,7 +2924,7 @@ fn binOp(
|
||||
const elem_size = @intCast(u32, elem_ty.abiSize(self.target.*));
|
||||
|
||||
if (elem_size == 1) {
|
||||
const base_tag: Air.Inst.Tag = switch (tag) {
|
||||
const base_tag: Mir.Inst.Tag = switch (tag) {
|
||||
.ptr_add => .add,
|
||||
.ptr_sub => .sub,
|
||||
else => unreachable,
|
||||
@ -2824,14 +3211,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);
|
||||
@ -2842,6 +3221,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions.
|
||||
info.stack_byte_count - offset,
|
||||
arg_mcv,
|
||||
),
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
@ -3784,6 +4164,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,
|
||||
@ -4025,6 +4410,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.
|
||||
@ -4159,6 +4546,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,
|
||||
|
||||
@ -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),
|
||||
@ -120,7 +122,7 @@ pub fn emitMir(
|
||||
.ldrsh_stack_argument => try emit.mirLoadStackArgument(inst),
|
||||
|
||||
.ldrh => try emit.mirLoadStoreExtra(inst),
|
||||
.ldrsb => try emit.mirLoadStore(inst),
|
||||
.ldrsb => try emit.mirLoadStoreExtra(inst),
|
||||
.ldrsh => try emit.mirLoadStoreExtra(inst),
|
||||
.strh => try emit.mirLoadStoreExtra(inst),
|
||||
|
||||
@ -128,6 +130,10 @@ pub fn emitMir(
|
||||
.movt => try emit.mirSpecialMove(inst),
|
||||
|
||||
.mul => try emit.mirMultiply(inst),
|
||||
.smulbb => try emit.mirMultiply(inst),
|
||||
|
||||
.smull => try emit.mirMultiplyLong(inst),
|
||||
.umull => try emit.mirMultiplyLong(inst),
|
||||
|
||||
.nop => try emit.mirNop(),
|
||||
|
||||
@ -474,6 +480,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 +489,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,
|
||||
}
|
||||
}
|
||||
@ -685,6 +693,19 @@ fn mirMultiply(emit: *Emit, inst: Mir.Inst.Index) !void {
|
||||
|
||||
switch (tag) {
|
||||
.mul => try emit.writeInstruction(Instruction.mul(cond, rrr.rd, rrr.rn, rrr.rm)),
|
||||
.smulbb => try emit.writeInstruction(Instruction.smulbb(cond, rrr.rd, rrr.rn, rrr.rm)),
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
fn mirMultiplyLong(emit: *Emit, inst: Mir.Inst.Index) !void {
|
||||
const tag = emit.mir.instructions.items(.tag)[inst];
|
||||
const cond = emit.mir.instructions.items(.cond)[inst];
|
||||
const rrrr = emit.mir.instructions.items(.data)[inst].rrrr;
|
||||
|
||||
switch (tag) {
|
||||
.smull => try emit.writeInstruction(Instruction.smull(cond, rrrr.rdlo, rrrr.rdhi, rrrr.rn, rrrr.rm)),
|
||||
.umull => try emit.writeInstruction(Instruction.umull(cond, rrrr.rdlo, rrrr.rdhi, rrrr.rn, rrrr.rm)),
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
@ -100,6 +102,10 @@ pub const Inst = struct {
|
||||
rsb,
|
||||
/// Signed Bit Field Extract
|
||||
sbfx,
|
||||
/// Signed Multiply (halfwords), bottom half, bottom half
|
||||
smulbb,
|
||||
/// Signed Multiply Long
|
||||
smull,
|
||||
/// Store Register
|
||||
str,
|
||||
/// Store Register Byte
|
||||
@ -108,10 +114,14 @@ pub const Inst = struct {
|
||||
strh,
|
||||
/// Subtract
|
||||
sub,
|
||||
/// Subtract, update condition flags
|
||||
subs,
|
||||
/// Supervisor Call
|
||||
svc,
|
||||
/// Unsigned Bit Field Extract
|
||||
ubfx,
|
||||
/// Unsigned Multiply Long
|
||||
umull,
|
||||
};
|
||||
|
||||
/// The position of an MIR instruction within the `Mir` instructions array.
|
||||
@ -209,6 +219,15 @@ pub const Inst = struct {
|
||||
rn: Register,
|
||||
rm: Register,
|
||||
},
|
||||
/// Four registers
|
||||
///
|
||||
/// Used by e.g. smull
|
||||
rrrr: struct {
|
||||
rdlo: Register,
|
||||
rdhi: Register,
|
||||
rn: Register,
|
||||
rm: Register,
|
||||
},
|
||||
/// An unordered list of registers
|
||||
///
|
||||
/// Used by e.g. push
|
||||
|
||||
@ -216,6 +216,18 @@ pub const Instruction = union(enum) {
|
||||
fixed_2: u5 = 0b00001,
|
||||
cond: u4,
|
||||
},
|
||||
signed_multiply_halfwords: packed struct {
|
||||
rn: u4,
|
||||
fixed_1: u1 = 0b0,
|
||||
n: u1,
|
||||
m: u1,
|
||||
fixed_2: u1 = 0b1,
|
||||
rm: u4,
|
||||
fixed_3: u4 = 0b0000,
|
||||
rd: u4,
|
||||
fixed_4: u8 = 0b00010110,
|
||||
cond: u4,
|
||||
},
|
||||
integer_saturating_arithmetic: packed struct {
|
||||
rm: u4,
|
||||
fixed_1: u8 = 0b0000_0101,
|
||||
@ -592,6 +604,7 @@ pub const Instruction = union(enum) {
|
||||
.data_processing => |v| @bitCast(u32, v),
|
||||
.multiply => |v| @bitCast(u32, v),
|
||||
.multiply_long => |v| @bitCast(u32, v),
|
||||
.signed_multiply_halfwords => |v| @bitCast(u32, v),
|
||||
.integer_saturating_arithmetic => |v| @bitCast(u32, v),
|
||||
.bit_field_extract => |v| @bitCast(u32, v),
|
||||
.single_data_transfer => |v| @bitCast(u32, v),
|
||||
@ -691,6 +704,26 @@ pub const Instruction = union(enum) {
|
||||
};
|
||||
}
|
||||
|
||||
fn signedMultiplyHalfwords(
|
||||
n: u1,
|
||||
m: u1,
|
||||
cond: Condition,
|
||||
rd: Register,
|
||||
rn: Register,
|
||||
rm: Register,
|
||||
) Instruction {
|
||||
return Instruction{
|
||||
.signed_multiply_halfwords = .{
|
||||
.rn = rn.id(),
|
||||
.n = n,
|
||||
.m = m,
|
||||
.rm = rm.id(),
|
||||
.rd = rd.id(),
|
||||
.cond = @enumToInt(cond),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn integerSaturationArithmetic(
|
||||
cond: Condition,
|
||||
rd: Register,
|
||||
@ -1093,6 +1126,24 @@ pub const Instruction = union(enum) {
|
||||
return multiplyLong(cond, 1, 1, 1, rdhi, rdlo, rm, rn);
|
||||
}
|
||||
|
||||
// Signed Multiply (halfwords)
|
||||
|
||||
pub fn smulbb(cond: Condition, rd: Register, rn: Register, rm: Register) Instruction {
|
||||
return signedMultiplyHalfwords(0, 0, cond, rd, rn, rm);
|
||||
}
|
||||
|
||||
pub fn smulbt(cond: Condition, rd: Register, rn: Register, rm: Register) Instruction {
|
||||
return signedMultiplyHalfwords(0, 1, cond, rd, rn, rm);
|
||||
}
|
||||
|
||||
pub fn smultb(cond: Condition, rd: Register, rn: Register, rm: Register) Instruction {
|
||||
return signedMultiplyHalfwords(1, 0, cond, rd, rn, rm);
|
||||
}
|
||||
|
||||
pub fn smultt(cond: Condition, rd: Register, rn: Register, rm: Register) Instruction {
|
||||
return signedMultiplyHalfwords(1, 1, cond, rd, rn, rm);
|
||||
}
|
||||
|
||||
// Bit field extract
|
||||
|
||||
pub fn ubfx(cond: Condition, rd: Register, rn: Register, lsb: u5, width: u6) Instruction {
|
||||
@ -1440,6 +1491,10 @@ test "serialize instructions" {
|
||||
.inst = Instruction.qadd(.al, .r0, .r7, .r8),
|
||||
.expected = 0b1110_00010_00_0_1000_0000_0000_0101_0111,
|
||||
},
|
||||
.{ // smulbt r0, r0, r0
|
||||
.inst = Instruction.smulbt(.al, .r0, .r0, .r0),
|
||||
.expected = 0b1110_00010110_0000_0000_0000_1_1_0_0_0000,
|
||||
},
|
||||
};
|
||||
|
||||
for (testcases) |case| {
|
||||
|
||||
@ -635,7 +635,6 @@ test "128-bit multiplication" {
|
||||
test "@addWithOverflow" {
|
||||
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
|
||||
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
|
||||
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
|
||||
|
||||
var result: u8 = undefined;
|
||||
try expect(@addWithOverflow(u8, 250, 100, &result));
|
||||
@ -679,7 +678,6 @@ test "small int addition" {
|
||||
test "@mulWithOverflow" {
|
||||
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
|
||||
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
|
||||
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
|
||||
|
||||
var result: u8 = undefined;
|
||||
try expect(@mulWithOverflow(u8, 86, 3, &result));
|
||||
@ -700,7 +698,6 @@ test "@mulWithOverflow" {
|
||||
test "@subWithOverflow" {
|
||||
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
|
||||
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
|
||||
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
|
||||
|
||||
var result: u8 = undefined;
|
||||
try expect(@subWithOverflow(u8, 1, 2, &result));
|
||||
@ -721,7 +718,6 @@ test "@shlWithOverflow" {
|
||||
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
|
||||
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
|
||||
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
|
||||
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
|
||||
|
||||
var result: u16 = undefined;
|
||||
try expect(@shlWithOverflow(u16, 0b0010111111111111, 3, &result));
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user