From e2e69803dc16efe11a6d42c6c49853e16a41fd0c Mon Sep 17 00:00:00 2001 From: joachimschmidt557 Date: Sun, 27 Mar 2022 15:06:37 +0200 Subject: [PATCH 1/6] stage2 ARM: change binOp lowering mechanism to use Mir tags The Air -> Mir correspondence is not 1:1, so this better represents what Mir insruction we actually want to generate. --- src/arch/arm/CodeGen.zig | 140 ++++++++++++++++++--------------------- 1 file changed, 65 insertions(+), 75 deletions(-) diff --git a/src/arch/arm/CodeGen.zig b/src/arch/arm/CodeGen.zig index 85545e33a5..d671fc5004 100644 --- a/src/arch/arm/CodeGen.zig +++ b/src/arch/arm/CodeGen.zig @@ -1196,7 +1196,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) { @@ -2067,7 +2067,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 +2112,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,41 +2130,21 @@ 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, .sub, - .cmp_eq, - .bit_and, - .bool_and, - .bit_or, - .bool_or, - .xor, + .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, @@ -2200,7 +2180,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 +2210,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 +2230,21 @@ 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, .sub, - .cmp_eq, - .bit_and, - .bool_and, - .bit_or, - .bool_or, - .xor, + .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 +2313,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 +2346,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 +2400,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 +2432,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 +2496,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 +2527,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, From 7285f0557cbc26893a7a64f345432ad01981180c Mon Sep 17 00:00:00 2001 From: joachimschmidt557 Date: Sun, 27 Mar 2022 21:30:16 +0200 Subject: [PATCH 2/6] stage2 ARM: implement add/sub_with_overflow for u32/i32 --- src/arch/arm/CodeGen.zig | 228 +++++++++++++++++++++++++++++++++------ src/arch/arm/Emit.zig | 4 + src/arch/arm/Mir.zig | 4 + 3 files changed, 202 insertions(+), 34 deletions(-) diff --git a/src/arch/arm/CodeGen.zig b/src/arch/arm/CodeGen.zig index d671fc5004..3484103ced 100644 --- a/src/arch/arm/CodeGen.zig +++ b/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); -fn airSubWithOverflow(self: *Self, inst: Air.Inst.Index) !void { - _ = inst; - return self.fail("TODO implement airSubWithOverflow for {}", .{self.target.cpu.arch}); + 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, + }; + + 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, diff --git a/src/arch/arm/Emit.zig b/src/arch/arm/Emit.zig index 45bcad485e..dfa0fabb2e 100644 --- a/src/arch/arm/Emit.zig +++ b/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, } } diff --git a/src/arch/arm/Mir.zig b/src/arch/arm/Mir.zig index c6fdfd3051..496042d674 100644 --- a/src/arch/arm/Mir.zig +++ b/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 From 37a8c28802b418718487995a1fa6000b0aab8a84 Mon Sep 17 00:00:00 2001 From: joachimschmidt557 Date: Tue, 29 Mar 2022 20:19:25 +0200 Subject: [PATCH 3/6] stage2 ARM: implement add/sub_with_overflow for ints < 32 bits --- src/arch/arm/CodeGen.zig | 35 ++++++++++++++++++++++++++++++++++- test/behavior/math.zig | 2 -- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/arch/arm/CodeGen.zig b/src/arch/arm/CodeGen.zig index 3484103ced..360b3ad97b 100644 --- a/src/arch/arm/CodeGen.zig +++ b/src/arch/arm/CodeGen.zig @@ -1361,13 +1361,46 @@ fn airOverflow(self: *Self, inst: Air.Inst.Index) !void { 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 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", .{}); + 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 diff --git a/test/behavior/math.zig b/test/behavior/math.zig index 7a388a0983..3ede976ef8 100644 --- a/test/behavior/math.zig +++ b/test/behavior/math.zig @@ -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)); @@ -700,7 +699,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)); From 77e70189f438316a8d4e48b2457be0b5eb5974f3 Mon Sep 17 00:00:00 2001 From: joachimschmidt557 Date: Wed, 30 Mar 2022 11:09:05 +0200 Subject: [PATCH 4/6] stage2 ARM: implement shl_with_overflow for ints <= 32 bits --- src/arch/arm/CodeGen.zig | 50 ++++++++++++++++++++++++++++++++++++++-- test/behavior/math.zig | 1 - 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/src/arch/arm/CodeGen.zig b/src/arch/arm/CodeGen.zig index 360b3ad97b..00d82349eb 100644 --- a/src/arch/arm/CodeGen.zig +++ b/src/arch/arm/CodeGen.zig @@ -1457,8 +1457,54 @@ fn airMulWithOverflow(self: *Self, inst: Air.Inst.Index) !void { } 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 { diff --git a/test/behavior/math.zig b/test/behavior/math.zig index 3ede976ef8..e1886955aa 100644 --- a/test/behavior/math.zig +++ b/test/behavior/math.zig @@ -719,7 +719,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)); From c4778fc0292b7024bf815e20e31029955a7a7241 Mon Sep 17 00:00:00 2001 From: joachimschmidt557 Date: Thu, 31 Mar 2022 18:25:53 +0200 Subject: [PATCH 5/6] stage2 ARM: implement mul_with_overflow for ints <= 16 bits --- src/arch/arm/CodeGen.zig | 63 ++++++++++++++++++++++++++++++++++++++-- src/arch/arm/Emit.zig | 4 ++- src/arch/arm/Mir.zig | 2 ++ src/arch/arm/bits.zig | 55 +++++++++++++++++++++++++++++++++++ test/behavior/math.zig | 1 - 5 files changed, 120 insertions(+), 5 deletions(-) diff --git a/src/arch/arm/CodeGen.zig b/src/arch/arm/CodeGen.zig index 00d82349eb..07403e9f93 100644 --- a/src/arch/arm/CodeGen.zig +++ b/src/arch/arm/CodeGen.zig @@ -1452,8 +1452,63 @@ fn airOverflow(self: *Self, inst: Air.Inst.Index) !void { } 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 { + return self.fail("TODO ARM overflow operations on integers > u16/i16", .{}); + } + }, + else => unreachable, + } + }; + return self.finishAir(inst, result, .{ extra.lhs, extra.rhs, .none }); } fn airShlWithOverflow(self: *Self, inst: Air.Inst.Index) !void { @@ -2382,7 +2437,9 @@ fn binOpRegister( .rm = lhs_reg, .shift_amount = Instruction.ShiftAmount.reg(rhs_reg), } }, - .mul => .{ .rrr = .{ + .mul, + .smulbb, + => .{ .rrr = .{ .rd = dest_reg, .rn = lhs_reg, .rm = rhs_reg, diff --git a/src/arch/arm/Emit.zig b/src/arch/arm/Emit.zig index dfa0fabb2e..10da79e1cb 100644 --- a/src/arch/arm/Emit.zig +++ b/src/arch/arm/Emit.zig @@ -122,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), @@ -130,6 +130,7 @@ pub fn emitMir( .movt => try emit.mirSpecialMove(inst), .mul => try emit.mirMultiply(inst), + .smulbb => try emit.mirMultiply(inst), .nop => try emit.mirNop(), @@ -689,6 +690,7 @@ 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, } } diff --git a/src/arch/arm/Mir.zig b/src/arch/arm/Mir.zig index 496042d674..fe96d4209f 100644 --- a/src/arch/arm/Mir.zig +++ b/src/arch/arm/Mir.zig @@ -102,6 +102,8 @@ pub const Inst = struct { rsb, /// Signed Bit Field Extract sbfx, + /// Signed Multiply (halfwords), bottom half, bottom half + smulbb, /// Store Register str, /// Store Register Byte diff --git a/src/arch/arm/bits.zig b/src/arch/arm/bits.zig index c6dafb6924..af7fb301b9 100644 --- a/src/arch/arm/bits.zig +++ b/src/arch/arm/bits.zig @@ -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| { diff --git a/test/behavior/math.zig b/test/behavior/math.zig index e1886955aa..00728b13a4 100644 --- a/test/behavior/math.zig +++ b/test/behavior/math.zig @@ -678,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)); From 8c12ad98b857cee3f6a8bc557f08b8dfcba2db7e Mon Sep 17 00:00:00 2001 From: joachimschmidt557 Date: Fri, 1 Apr 2022 22:51:18 +0200 Subject: [PATCH 6/6] stage2 ARM: implement mul_with_overflow for ints <= 32 bits --- src/arch/arm/CodeGen.zig | 108 ++++++++++++++++++++++++++++++++++++++- src/arch/arm/Emit.zig | 15 ++++++ src/arch/arm/Mir.zig | 13 +++++ 3 files changed, 135 insertions(+), 1 deletion(-) diff --git a/src/arch/arm/CodeGen.zig b/src/arch/arm/CodeGen.zig index 07403e9f93..9a660ceff6 100644 --- a/src/arch/arm/CodeGen.zig +++ b/src/arch/arm/CodeGen.zig @@ -1500,9 +1500,115 @@ fn airMulWithOverflow(self: *Self, inst: Air.Inst.Index) !void { 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 > u16/i16", .{}); + return self.fail("TODO ARM overflow operations on integers > u32/i32", .{}); } }, else => unreachable, diff --git a/src/arch/arm/Emit.zig b/src/arch/arm/Emit.zig index 10da79e1cb..77fa82d1d2 100644 --- a/src/arch/arm/Emit.zig +++ b/src/arch/arm/Emit.zig @@ -132,6 +132,9 @@ pub fn emitMir( .mul => try emit.mirMultiply(inst), .smulbb => try emit.mirMultiply(inst), + .smull => try emit.mirMultiplyLong(inst), + .umull => try emit.mirMultiplyLong(inst), + .nop => try emit.mirNop(), .pop => try emit.mirBlockDataTransfer(inst), @@ -695,6 +698,18 @@ fn mirMultiply(emit: *Emit, inst: Mir.Inst.Index) !void { } } +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, + } +} + fn mirNop(emit: *Emit) !void { try emit.writeInstruction(Instruction.nop()); } diff --git a/src/arch/arm/Mir.zig b/src/arch/arm/Mir.zig index fe96d4209f..209b3e508e 100644 --- a/src/arch/arm/Mir.zig +++ b/src/arch/arm/Mir.zig @@ -104,6 +104,8 @@ pub const Inst = struct { sbfx, /// Signed Multiply (halfwords), bottom half, bottom half smulbb, + /// Signed Multiply Long + smull, /// Store Register str, /// Store Register Byte @@ -118,6 +120,8 @@ pub const Inst = struct { svc, /// Unsigned Bit Field Extract ubfx, + /// Unsigned Multiply Long + umull, }; /// The position of an MIR instruction within the `Mir` instructions array. @@ -215,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