using Ryujinx.Cpu.LightningJit.CodeGen; using Ryujinx.Cpu.LightningJit.CodeGen.Arm64; using System; using System.Diagnostics; namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 { static class InstEmitAlu { private const uint Imm12Limit = 0x1000; public static void AdcI(CodeGenContext context, uint rd, uint rn, uint imm, bool s) { EmitI(context, s ? context.Arm64Assembler.Adcs : context.Arm64Assembler.Adc, rd, rn, imm, s); } public static void AdcR(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint imm5, bool s) { EmitR(context, s ? context.Arm64Assembler.Adcs : context.Arm64Assembler.Adc, rd, rn, rm, sType, imm5, s); } public static void AdcRr(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint rs, bool s) { EmitRr(context, s ? context.Arm64Assembler.Adcs : context.Arm64Assembler.Adc, rd, rn, rm, sType, rs, s); } public static void AddI(CodeGenContext context, uint rd, uint rn, uint imm, bool s) { EmitArithmeticI(context, s ? context.Arm64Assembler.Adds : context.Arm64Assembler.Add, rd, rn, imm, s); } public static void AddR(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint imm5, bool s) { EmitArithmeticR(context, s ? context.Arm64Assembler.Adds : context.Arm64Assembler.Add, rd, rn, rm, sType, imm5, s); } public static void AddRr(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint rs, bool s) { EmitRr(context, s ? context.Arm64Assembler.Adds : context.Arm64Assembler.Add, rd, rn, rm, sType, rs, s); } public static void Adr(CodeGenContext context, uint rd, uint imm, bool add) { Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); uint pc = context.Pc & ~3u; if (add) { pc += imm; } else { pc -= imm; } context.Arm64Assembler.Mov(rdOperand, pc); } public static void AndI(CodeGenContext context, uint rd, uint rn, uint imm, bool immRotated, bool s) { EmitLogicalI(context, s ? context.Arm64Assembler.Ands : context.Arm64Assembler.And, rd, rn, imm, immRotated, s); } public static void AndR(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint imm5, bool s) { EmitLogicalR(context, s ? context.Arm64Assembler.Ands : context.Arm64Assembler.And, rd, rn, rm, sType, imm5, s); } public static void AndRr(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint rs, bool s) { EmitLogicalRr(context, s ? context.Arm64Assembler.Ands : context.Arm64Assembler.And, rd, rn, rm, sType, rs, s); } public static void BicI(CodeGenContext context, uint rd, uint rn, uint imm, bool immRotated, bool s) { if (!s && CodeGenCommon.TryEncodeBitMask(OperandType.I32, ~imm, out _, out _, out _)) { AndI(context, rd, rn, ~imm, immRotated, s); } else { EmitLogicalI(context, s ? context.Arm64Assembler.Bics : context.Arm64Assembler.Bic, rd, rn, imm, immRotated, s, immForm: false); } } public static void BicR(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint imm5, bool s) { EmitLogicalR(context, s ? context.Arm64Assembler.Bics : context.Arm64Assembler.Bic, rd, rn, rm, sType, imm5, s); } public static void BicRr(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint rs, bool s) { EmitLogicalRr(context, s ? context.Arm64Assembler.Bics : context.Arm64Assembler.Bic, rd, rn, rm, sType, rs, s); } public static void CmnI(CodeGenContext context, uint rn, uint imm) { EmitCompareI(context, context.Arm64Assembler.Cmn, rn, imm); } public static void CmnR(CodeGenContext context, uint rn, uint rm, uint sType, uint imm5) { EmitCompareR(context, context.Arm64Assembler.Cmn, rn, rm, sType, imm5); } public static void CmnRr(CodeGenContext context, uint rn, uint rm, uint sType, uint rs) { EmitCompareRr(context, context.Arm64Assembler.Cmn, rn, rm, sType, rs); } public static void CmpI(CodeGenContext context, uint rn, uint imm) { EmitCompareI(context, context.Arm64Assembler.Cmp, rn, imm); } public static void CmpR(CodeGenContext context, uint rn, uint rm, uint sType, uint imm5) { EmitCompareR(context, context.Arm64Assembler.Cmp, rn, rm, sType, imm5); } public static void CmpRr(CodeGenContext context, uint rn, uint rm, uint sType, uint rs) { EmitCompareRr(context, context.Arm64Assembler.Cmp, rn, rm, sType, rs); } public static void EorI(CodeGenContext context, uint rd, uint rn, uint imm, bool immRotated, bool s) { EmitLogicalI(context, s ? context.Arm64Assembler.Eors : context.Arm64Assembler.Eor, rd, rn, imm, immRotated, s); } public static void EorR(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint imm5, bool s) { EmitLogicalR(context, s ? context.Arm64Assembler.Eors : context.Arm64Assembler.Eor, rd, rn, rm, sType, imm5, s); } public static void EorRr(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint rs, bool s) { EmitLogicalRr(context, s ? context.Arm64Assembler.Eors : context.Arm64Assembler.Eor, rd, rn, rm, sType, rs, s); } public static void OrnI(CodeGenContext context, uint rd, uint rn, uint imm, bool immRotated, bool s) { if (!s && CodeGenCommon.TryEncodeBitMask(OperandType.I32, ~imm, out _, out _, out _)) { OrrI(context, rd, rn, ~imm, immRotated, s); } else { EmitLogicalI(context, s ? context.Arm64Assembler.Orns : context.Arm64Assembler.Orn, rd, rn, imm, immRotated, s, immForm: false); } } public static void OrnR(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint imm5, bool s) { EmitLogicalR(context, s ? context.Arm64Assembler.Orns : context.Arm64Assembler.Orn, rd, rn, rm, sType, imm5, s); } public static void OrrI(CodeGenContext context, uint rd, uint rn, uint imm, bool immRotated, bool s) { EmitLogicalI(context, s ? context.Arm64Assembler.Orrs : context.Arm64Assembler.Orr, rd, rn, imm, immRotated, s); } public static void OrrR(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint imm5, bool s) { EmitLogicalR(context, s ? context.Arm64Assembler.Orrs : context.Arm64Assembler.Orr, rd, rn, rm, sType, imm5, s); } public static void OrrRr(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint rs, bool s) { EmitLogicalRr(context, s ? context.Arm64Assembler.Orrs : context.Arm64Assembler.Orr, rd, rn, rm, sType, rs, s); } public static void RsbI(CodeGenContext context, uint rd, uint rn, uint imm, bool s) { if (imm == 0) { Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); if (s) { context.Arm64Assembler.Negs(rdOperand, rnOperand); context.SetNzcvModified(); } else { context.Arm64Assembler.Neg(rdOperand, rnOperand); } } else { EmitI(context, s ? context.Arm64Assembler.Subs : context.Arm64Assembler.Sub, rd, rn, imm, s, reverse: true); } } public static void RsbR(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint imm5, bool s) { EmitR(context, s ? context.Arm64Assembler.Subs : context.Arm64Assembler.Sub, rd, rn, rm, sType, imm5, s, reverse: true); } public static void RsbRr(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint rs, bool s) { EmitRr(context, s ? context.Arm64Assembler.Subs : context.Arm64Assembler.Sub, rd, rn, rm, sType, rs, s, reverse: true); } public static void RscI(CodeGenContext context, uint rd, uint rn, uint imm, bool s) { EmitI(context, s ? context.Arm64Assembler.Sbcs : context.Arm64Assembler.Sbc, rd, rn, imm, s, reverse: true); } public static void RscR(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint imm5, bool s) { EmitR(context, s ? context.Arm64Assembler.Sbcs : context.Arm64Assembler.Sbc, rd, rn, rm, sType, imm5, s, reverse: true); } public static void RscRr(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint rs, bool s) { EmitRr(context, s ? context.Arm64Assembler.Sbcs : context.Arm64Assembler.Sbc, rd, rn, rm, sType, rs, s, reverse: true); } public static void SbcI(CodeGenContext context, uint rd, uint rn, uint imm, bool s) { EmitI(context, s ? context.Arm64Assembler.Sbcs : context.Arm64Assembler.Sbc, rd, rn, imm, s); } public static void SbcR(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint imm5, bool s) { EmitR(context, s ? context.Arm64Assembler.Sbcs : context.Arm64Assembler.Sbc, rd, rn, rm, sType, imm5, s); } public static void SbcRr(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint rs, bool s) { EmitRr(context, s ? context.Arm64Assembler.Sbcs : context.Arm64Assembler.Sbc, rd, rn, rm, sType, rs, s); } public static void SubI(CodeGenContext context, uint rd, uint rn, uint imm, bool s) { EmitArithmeticI(context, s ? context.Arm64Assembler.Subs : context.Arm64Assembler.Sub, rd, rn, imm, s); } public static void SubR(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint imm5, bool s) { EmitArithmeticR(context, s ? context.Arm64Assembler.Subs : context.Arm64Assembler.Sub, rd, rn, rm, sType, imm5, s); } public static void SubRr(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint rs, bool s) { EmitRr(context, s ? context.Arm64Assembler.Subs : context.Arm64Assembler.Sub, rd, rn, rm, sType, rs, s); } public static void TeqI(CodeGenContext context, uint rn, uint imm, bool immRotated) { EmitLogicalI(context, (rnOperand, rmOperand) => EmitTeq(context, rnOperand, rmOperand), rn, imm, immRotated); } public static void TeqR(CodeGenContext context, uint rn, uint rm, uint sType, uint imm5) { EmitLogicalR(context, (rnOperand, rmOperand) => EmitTeq(context, rnOperand, rmOperand), rn, rm, sType, imm5); } public static void TeqRr(CodeGenContext context, uint rn, uint rm, uint sType, uint rs) { EmitLogicalRr(context, (rnOperand, rmOperand) => EmitTeq(context, rnOperand, rmOperand), rn, rm, sType, rs); } public static void TstI(CodeGenContext context, uint rn, uint imm, bool immRotated) { EmitLogicalI(context, context.Arm64Assembler.Tst, rn, imm, immRotated); } public static void TstR(CodeGenContext context, uint rn, uint rm, uint sType, uint imm5) { EmitLogicalR(context, context.Arm64Assembler.Tst, rn, rm, sType, imm5); } public static void TstRr(CodeGenContext context, uint rn, uint rm, uint sType, uint rs) { EmitLogicalRr(context, context.Arm64Assembler.Tst, rn, rm, sType, rs); } private static void EmitArithmeticI(CodeGenContext context, Action action, uint rd, uint rn, uint imm, bool s) { Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); if (imm < Imm12Limit) { Operand rmOperand = new(OperandKind.Constant, OperandType.I32, imm); action(rdOperand, rnOperand, rmOperand); } else { using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); context.Arm64Assembler.Mov(tempRegister.Operand, imm); action(rdOperand, rnOperand, tempRegister.Operand); } if (s) { context.SetNzcvModified(); } } private static void EmitArithmeticR( CodeGenContext context, Action action, uint rd, uint rn, uint rm, uint sType, uint imm5, bool s) { Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); if (CanShiftArithmetic(sType, imm5)) { action(rdOperand, rnOperand, rmOperand, (ArmShiftType)sType, (int)imm5); } else { using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); rmOperand = GetMShiftedByImmediate(context, tempRegister.Operand, rmOperand, imm5, sType); action(rdOperand, rnOperand, rmOperand, ArmShiftType.Lsl, 0); } if (s) { context.SetNzcvModified(); } } private static void EmitCompareI(CodeGenContext context, Action action, uint rn, uint imm) { Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); if (imm < Imm12Limit) { Operand rmOperand = new(OperandKind.Constant, OperandType.I32, imm); action(rnOperand, rmOperand); } else { using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); context.Arm64Assembler.Mov(tempRegister.Operand, imm); action(rnOperand, tempRegister.Operand); } context.SetNzcvModified(); } private static void EmitCompareR( CodeGenContext context, Action action, uint rn, uint rm, uint sType, uint imm5) { Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); if (CanShiftArithmetic(sType, imm5)) { action(rnOperand, rmOperand, (ArmShiftType)sType, (int)imm5); } else { using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); rmOperand = GetMShiftedByImmediate(context, tempRegister.Operand, rmOperand, imm5, sType); action(rnOperand, rmOperand, ArmShiftType.Lsl, 0); } context.SetNzcvModified(); } private static void EmitCompareRr(CodeGenContext context, Action action, uint rn, uint rm, uint sType, uint rs) { Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); Operand rsOperand = InstEmitCommon.GetInputGpr(context, rs); using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); rmOperand = GetMShiftedByReg(context, tempRegister.Operand, rmOperand, rsOperand, sType); action(rnOperand, rmOperand); context.SetNzcvModified(); } private static void EmitI(CodeGenContext context, Action action, uint rd, uint rn, uint imm, bool s, bool reverse = false) { Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); context.Arm64Assembler.Mov(tempRegister.Operand, imm); if (reverse) { action(rdOperand, tempRegister.Operand, rnOperand); } else { action(rdOperand, rnOperand, tempRegister.Operand); } if (s) { context.SetNzcvModified(); } } private static void EmitR(CodeGenContext context, Action action, uint rd, uint rn, uint rm, uint sType, uint imm5, bool s, bool reverse = false) { Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); rmOperand = GetMShiftedByImmediate(context, tempRegister.Operand, rmOperand, imm5, sType); if (reverse) { action(rdOperand, rmOperand, rnOperand); } else { action(rdOperand, rnOperand, rmOperand); } if (s) { context.SetNzcvModified(); } } private static void EmitRr(CodeGenContext context, Action action, uint rd, uint rn, uint rm, uint sType, uint rs, bool s, bool reverse = false) { Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); Operand rsOperand = InstEmitCommon.GetInputGpr(context, rs); using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); rmOperand = GetMShiftedByReg(context, tempRegister.Operand, rmOperand, rsOperand, sType); if (reverse) { action(rdOperand, rmOperand, rnOperand); } else { action(rdOperand, rnOperand, rmOperand); } if (s) { context.SetNzcvModified(); } } private static void EmitLogicalI(CodeGenContext context, Action action, uint rn, uint imm, bool immRotated) { Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); using ScopedRegister flagsRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); InstEmitCommon.GetCurrentFlags(context, flagsRegister.Operand); if (immRotated) { if ((imm & (1u << 31)) != 0) { context.Arm64Assembler.Orr(flagsRegister.Operand, flagsRegister.Operand, InstEmitCommon.Const(2)); } else { context.Arm64Assembler.Bfc(flagsRegister.Operand, 1, 1); } } if (CodeGenCommon.TryEncodeBitMask(OperandType.I32, imm, out _, out _, out _)) { action(rnOperand, InstEmitCommon.Const((int)imm)); } else { using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); context.Arm64Assembler.Mov(tempRegister.Operand, imm); action(rnOperand, tempRegister.Operand); } InstEmitCommon.RestoreCvFlags(context, flagsRegister.Operand); context.SetNzcvModified(); } private static void EmitLogicalI( CodeGenContext context, Action action, uint rd, uint rn, uint imm, bool immRotated, bool s, bool immForm = true) { Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); ScopedRegister flagsRegister = default; if (s) { flagsRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); InstEmitCommon.GetCurrentFlags(context, flagsRegister.Operand); if (immRotated) { if ((imm & (1u << 31)) != 0) { context.Arm64Assembler.Orr(flagsRegister.Operand, flagsRegister.Operand, InstEmitCommon.Const(2)); } else { context.Arm64Assembler.Bfc(flagsRegister.Operand, 1, 1); } } } if (imm == 0 || (immForm && CodeGenCommon.TryEncodeBitMask(OperandType.I32, imm, out _, out _, out _))) { action(rdOperand, rnOperand, InstEmitCommon.Const((int)imm)); } else { using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); context.Arm64Assembler.Mov(tempRegister.Operand, imm); action(rdOperand, rnOperand, tempRegister.Operand); } if (s) { InstEmitCommon.RestoreCvFlags(context, flagsRegister.Operand); flagsRegister.Dispose(); context.SetNzcvModified(); } } private static void EmitLogicalR(CodeGenContext context, Action action, uint rn, uint rm, uint sType, uint imm5) { Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); using ScopedRegister flagsRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); InstEmitCommon.GetCurrentFlags(context, flagsRegister.Operand); rmOperand = GetMShiftedByImmediate(context, tempRegister.Operand, rmOperand, imm5, sType, flagsRegister.Operand); action(rnOperand, rmOperand); InstEmitCommon.RestoreCvFlags(context, flagsRegister.Operand); context.SetNzcvModified(); } private static void EmitLogicalR(CodeGenContext context, Action action, uint rd, uint rn, uint rm, uint sType, uint imm5, bool s) { Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); if (CanShift(sType, imm5) && !s) { action(rdOperand, rnOperand, rmOperand, (ArmShiftType)sType, (int)imm5); } else { using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); ScopedRegister flagsRegister = default; if (s) { flagsRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); InstEmitCommon.GetCurrentFlags(context, flagsRegister.Operand); rmOperand = GetMShiftedByImmediate(context, tempRegister.Operand, rmOperand, imm5, sType, flagsRegister.Operand); } else { rmOperand = GetMShiftedByImmediate(context, tempRegister.Operand, rmOperand, imm5, sType, null); } action(rdOperand, rnOperand, rmOperand, ArmShiftType.Lsl, 0); if (s) { InstEmitCommon.RestoreCvFlags(context, flagsRegister.Operand); flagsRegister.Dispose(); context.SetNzcvModified(); } } } private static void EmitLogicalRr(CodeGenContext context, Action action, uint rn, uint rm, uint sType, uint rs) { Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); Operand rsOperand = InstEmitCommon.GetInputGpr(context, rs); using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); using ScopedRegister flagsRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); InstEmitCommon.GetCurrentFlags(context, flagsRegister.Operand); rmOperand = GetMShiftedByReg(context, tempRegister.Operand, rmOperand, rsOperand, sType, flagsRegister.Operand); action(rnOperand, rmOperand); InstEmitCommon.RestoreCvFlags(context, flagsRegister.Operand); context.SetNzcvModified(); } private static void EmitLogicalRr(CodeGenContext context, Action action, uint rd, uint rn, uint rm, uint sType, uint rs, bool s) { if (s) { Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); Operand rsOperand = InstEmitCommon.GetInputGpr(context, rs); using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); using ScopedRegister flagsRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); InstEmitCommon.GetCurrentFlags(context, flagsRegister.Operand); rmOperand = GetMShiftedByReg(context, tempRegister.Operand, rmOperand, rsOperand, sType, flagsRegister.Operand); action(rdOperand, rnOperand, rmOperand); InstEmitCommon.RestoreCvFlags(context, flagsRegister.Operand); context.SetNzcvModified(); } else { EmitRr(context, action, rd, rn, rm, sType, rs, s); } } public static bool CanShiftArithmetic(uint sType, uint imm5) { // We can't encode ROR or RRX. return sType != 3 && (sType == 0 || imm5 != 0); } public static bool CanShift(uint sType, uint imm5) { // We can encode all shift types directly, except RRX. return imm5 != 0 || sType == 0; } public static Operand GetMShiftedByImmediate(CodeGenContext context, Operand dest, Operand m, uint imm, uint sType, Operand? carryOut = null) { int shift = (int)imm; if (shift == 0) { switch ((ArmShiftType)sType) { case ArmShiftType.Lsr: shift = 32; break; case ArmShiftType.Asr: shift = 32; break; case ArmShiftType.Ror: shift = 1; break; } } if (shift != 0) { switch ((ArmShiftType)sType) { case ArmShiftType.Lsl: m = GetLslC(context, dest, m, carryOut, shift); break; case ArmShiftType.Lsr: m = GetLsrC(context, dest, m, carryOut, shift); break; case ArmShiftType.Asr: m = GetAsrC(context, dest, m, carryOut, shift); break; case ArmShiftType.Ror: if (imm != 0) { m = GetRorC(context, dest, m, carryOut, shift); } else { m = GetRrxC(context, dest, m, carryOut); } break; } } return m; } public static Operand GetMShiftedByReg(CodeGenContext context, Operand dest, Operand m, Operand s, uint sType, Operand? carryOut = null) { Operand shiftResult = m; switch ((ArmShiftType)sType) { case ArmShiftType.Lsl: shiftResult = EmitLslC(context, dest, m, carryOut, s); break; case ArmShiftType.Lsr: shiftResult = EmitLsrC(context, dest, m, carryOut, s); break; case ArmShiftType.Asr: shiftResult = EmitAsrC(context, dest, m, carryOut, s); break; case ArmShiftType.Ror: shiftResult = EmitRorC(context, dest, m, carryOut, s); break; } return shiftResult; } private static void EmitIfHelper(CodeGenContext context, Operand boolValue, Action action, bool expected = true) { Debug.Assert(boolValue.Type == OperandType.I32); int branchInstructionPointer = context.CodeWriter.InstructionPointer; if (expected) { context.Arm64Assembler.Cbnz(boolValue, 0); } else { context.Arm64Assembler.Cbz(boolValue, 0); } action(); int offset = context.CodeWriter.InstructionPointer - branchInstructionPointer; Debug.Assert(offset >= 0); Debug.Assert((offset << 13) >> 13 == offset); uint branchInst = context.CodeWriter.ReadInstructionAt(branchInstructionPointer); context.CodeWriter.WriteInstructionAt(branchInstructionPointer, branchInst | (uint)(offset << 5)); } private static Operand EmitLslC(CodeGenContext context, Operand dest, Operand m, Operand? carryOut, Operand shift) { Debug.Assert(m.Type == OperandType.I32 && shift.Type == OperandType.I32); using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); Operand mask = tempRegister.Operand; context.Arm64Assembler.Uxtb(mask, shift); context.Arm64Assembler.Sub(mask, mask, InstEmitCommon.Const(32)); context.Arm64Assembler.Asr(mask, mask, InstEmitCommon.Const(31)); Operand dest64 = new(OperandKind.Register, OperandType.I64, dest.Value); if (carryOut.HasValue) { context.Arm64Assembler.Lslv(dest64, m, shift); } else { context.Arm64Assembler.Lslv(dest, m, shift); } // If shift >= 32, force the result to 0. context.Arm64Assembler.And(dest, dest, mask); if (carryOut.HasValue) { EmitIfHelper(context, shift, () => { using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); context.Arm64Assembler.Uxtb(mask, shift); context.Arm64Assembler.Sub(mask, mask, InstEmitCommon.Const(33)); context.Arm64Assembler.Lsr(mask, mask, InstEmitCommon.Const(31)); context.Arm64Assembler.Lsr(tempRegister.Operand, dest64, InstEmitCommon.Const(32)); context.Arm64Assembler.And(tempRegister.Operand, tempRegister.Operand, mask); UpdateCarryFlag(context, tempRegister.Operand, carryOut.Value); }, false); } return dest; } private static Operand GetLslC(CodeGenContext context, Operand dest, Operand m, Operand? carryOut, int shift) { Debug.Assert(m.Type == OperandType.I32); if ((uint)shift > 32) { return GetShiftByMoreThan32(context, carryOut); } else if (shift == 32) { if (carryOut.HasValue) { SetCarryMLsb(context, m, carryOut.Value); } return InstEmitCommon.Const(0); } else { if (carryOut.HasValue) { using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); context.Arm64Assembler.Lsr(tempRegister.Operand, m, InstEmitCommon.Const(32 - shift)); UpdateCarryFlag(context, tempRegister.Operand, carryOut.Value); } context.Arm64Assembler.Lsl(dest, m, InstEmitCommon.Const(shift)); return dest; } } private static Operand EmitLsrC(CodeGenContext context, Operand dest, Operand m, Operand? carryOut, Operand shift) { Debug.Assert(m.Type == OperandType.I32 && shift.Type == OperandType.I32); using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); Operand mask = tempRegister.Operand; context.Arm64Assembler.Uxtb(mask, shift); context.Arm64Assembler.Sub(mask, mask, InstEmitCommon.Const(32)); context.Arm64Assembler.Asr(mask, mask, InstEmitCommon.Const(31)); context.Arm64Assembler.Lsrv(dest, m, shift); // If shift >= 32, force the result to 0. context.Arm64Assembler.And(dest, dest, mask); if (carryOut.HasValue) { EmitIfHelper(context, shift, () => { using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); context.Arm64Assembler.Uxtb(mask, shift); context.Arm64Assembler.Sub(mask, mask, InstEmitCommon.Const(33)); context.Arm64Assembler.Lsr(mask, mask, InstEmitCommon.Const(31)); context.Arm64Assembler.Sub(tempRegister.Operand, shift, InstEmitCommon.Const(1)); context.Arm64Assembler.Lsrv(tempRegister.Operand, m, tempRegister.Operand); context.Arm64Assembler.And(tempRegister.Operand, tempRegister.Operand, mask); UpdateCarryFlag(context, tempRegister.Operand, carryOut.Value); }, false); } return dest; } public static Operand GetLsrC(CodeGenContext context, Operand dest, Operand m, Operand? carryOut, int shift) { Debug.Assert(m.Type == OperandType.I32); if ((uint)shift > 32) { return GetShiftByMoreThan32(context, carryOut); } else if (shift == 32) { if (carryOut.HasValue) { SetCarryMMsb(context, m, carryOut.Value); } return InstEmitCommon.Const(0); } else { if (carryOut.HasValue) { SetCarryMShrOut(context, m, shift, carryOut.Value); } context.Arm64Assembler.Lsr(dest, m, InstEmitCommon.Const(shift)); return dest; } } private static Operand GetShiftByMoreThan32(CodeGenContext context, Operand? carryOut) { if (carryOut.HasValue) { // Clear carry flag. context.Arm64Assembler.Bfc(carryOut.Value, 1, 1); } return InstEmitCommon.Const(0); } private static Operand EmitAsrC(CodeGenContext context, Operand dest, Operand m, Operand? carryOut, Operand shift) { Debug.Assert(m.Type == OperandType.I32 && shift.Type == OperandType.I32); using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); Operand mask = tempRegister.Operand; context.Arm64Assembler.Uxtb(mask, shift); context.Arm64Assembler.Sub(mask, mask, InstEmitCommon.Const(31)); context.Arm64Assembler.Orn(mask, shift, mask, ArmShiftType.Asr, 31); context.Arm64Assembler.Asrv(dest, m, mask); if (carryOut.HasValue) { EmitIfHelper(context, shift, () => { // If shift >= 32, carry should be equal to the MSB of Rm. using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); context.Arm64Assembler.Sub(tempRegister.Operand, mask, InstEmitCommon.Const(1)); context.Arm64Assembler.Orr(tempRegister.Operand, tempRegister.Operand, mask, ArmShiftType.Asr, 31); context.Arm64Assembler.Lsrv(tempRegister.Operand, m, tempRegister.Operand); UpdateCarryFlag(context, tempRegister.Operand, carryOut.Value); }, false); } return dest; } private static Operand GetAsrC(CodeGenContext context, Operand dest, Operand m, Operand? carryOut, int shift) { Debug.Assert(m.Type == OperandType.I32); if ((uint)shift >= 32) { context.Arm64Assembler.Asr(dest, m, InstEmitCommon.Const(31)); if (carryOut.HasValue) { SetCarryMLsb(context, dest, carryOut.Value); } return dest; } else { if (carryOut.HasValue) { SetCarryMShrOut(context, m, shift, carryOut.Value); } context.Arm64Assembler.Asr(dest, m, InstEmitCommon.Const(shift)); return dest; } } private static Operand EmitRorC(CodeGenContext context, Operand dest, Operand m, Operand? carryOut, Operand shift) { Debug.Assert(m.Type == OperandType.I32 && shift.Type == OperandType.I32); context.Arm64Assembler.Rorv(dest, m, shift); if (carryOut.HasValue) { EmitIfHelper(context, shift, () => { SetCarryMMsb(context, m, carryOut.Value); }, false); } return dest; } private static Operand GetRorC(CodeGenContext context, Operand dest, Operand m, Operand? carryOut, int shift) { Debug.Assert(m.Type == OperandType.I32); shift &= 0x1f; context.Arm64Assembler.Ror(dest, m, InstEmitCommon.Const(shift)); if (carryOut.HasValue) { SetCarryMMsb(context, dest, carryOut.Value); } return dest; } private static Operand GetRrxC(CodeGenContext context, Operand dest, Operand m, Operand? carryOut) { Debug.Assert(m.Type == OperandType.I32); // Rotate right by 1 with carry. if (carryOut.HasValue) { SetCarryMLsb(context, m, carryOut.Value); } context.Arm64Assembler.Mov(dest, m); using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); context.Arm64Assembler.MrsNzcv(tempRegister.Operand); context.Arm64Assembler.Bfxil(dest, tempRegister.Operand, 29, 1); context.Arm64Assembler.Ror(dest, dest, InstEmitCommon.Const(1)); return dest; } private static void SetCarryMLsb(CodeGenContext context, Operand m, Operand carryOut) { Debug.Assert(m.Type == OperandType.I32); UpdateCarryFlag(context, m, carryOut); } private static void SetCarryMMsb(CodeGenContext context, Operand m, Operand carryOut) { Debug.Assert(m.Type == OperandType.I32); using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); context.Arm64Assembler.Lsr(tempRegister.Operand, m, InstEmitCommon.Const(31)); UpdateCarryFlag(context, tempRegister.Operand, carryOut); } private static void SetCarryMShrOut(CodeGenContext context, Operand m, int shift, Operand carryOut) { Debug.Assert(m.Type == OperandType.I32); using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); context.Arm64Assembler.Lsr(tempRegister.Operand, m, InstEmitCommon.Const(shift - 1)); UpdateCarryFlag(context, tempRegister.Operand, carryOut); } private static void UpdateCarryFlag(CodeGenContext context, Operand value, Operand carryOut) { context.Arm64Assembler.Bfi(carryOut, value, 1, 1); } private static void EmitTeq(CodeGenContext context, Operand rnOperand, Operand rmOperand) { using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); context.Arm64Assembler.Eors(tempRegister.Operand, rnOperand, rmOperand); } } }