using ARMeilleure.Decoders; using ARMeilleure.IntermediateRepresentation; using ARMeilleure.State; using ARMeilleure.Translation; using static ARMeilleure.Instructions.InstEmitAluHelper; using static ARMeilleure.Instructions.InstEmitHelper; using static ARMeilleure.IntermediateRepresentation.Operand.Factory; namespace ARMeilleure.Instructions { static partial class InstEmit32 { public static void Add(ArmEmitterContext context) { IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; Operand n = GetAluN(context); Operand m = GetAluM(context, setCarry: false); Operand res = context.Add(n, m); if (ShouldSetFlags(context)) { EmitNZFlagsCheck(context, res); EmitAddsCCheck(context, n, res); EmitAddsVCheck(context, n, m, res); } EmitAluStore(context, res); } public static void Adc(ArmEmitterContext context) { IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; Operand n = GetAluN(context); Operand m = GetAluM(context, setCarry: false); Operand res = context.Add(n, m); Operand carry = GetFlag(PState.CFlag); res = context.Add(res, carry); if (ShouldSetFlags(context)) { EmitNZFlagsCheck(context, res); EmitAdcsCCheck(context, n, res); EmitAddsVCheck(context, n, m, res); } EmitAluStore(context, res); } public static void And(ArmEmitterContext context) { IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; Operand n = GetAluN(context); Operand m = GetAluM(context); Operand res = context.BitwiseAnd(n, m); if (ShouldSetFlags(context)) { EmitNZFlagsCheck(context, res); } EmitAluStore(context, res); } public static void Bfc(ArmEmitterContext context) { IOpCode32AluBf op = (IOpCode32AluBf)context.CurrOp; Operand d = GetIntA32(context, op.Rd); Operand res = context.BitwiseAnd(d, Const(~op.DestMask)); SetIntA32(context, op.Rd, res); } public static void Bfi(ArmEmitterContext context) { IOpCode32AluBf op = (IOpCode32AluBf)context.CurrOp; Operand n = GetIntA32(context, op.Rn); Operand d = GetIntA32(context, op.Rd); Operand part = context.BitwiseAnd(n, Const(op.SourceMask)); if (op.Lsb != 0) { part = context.ShiftLeft(part, Const(op.Lsb)); } Operand res = context.BitwiseAnd(d, Const(~op.DestMask)); res = context.BitwiseOr(res, context.BitwiseAnd(part, Const(op.DestMask))); SetIntA32(context, op.Rd, res); } public static void Bic(ArmEmitterContext context) { IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; Operand n = GetAluN(context); Operand m = GetAluM(context); Operand res = context.BitwiseAnd(n, context.BitwiseNot(m)); if (ShouldSetFlags(context)) { EmitNZFlagsCheck(context, res); } EmitAluStore(context, res); } public static void Clz(ArmEmitterContext context) { Operand m = GetAluM(context, setCarry: false); Operand res = context.CountLeadingZeros(m); EmitAluStore(context, res); } public static void Cmp(ArmEmitterContext context) { Operand n = GetAluN(context); Operand m = GetAluM(context, setCarry: false); Operand res = context.Subtract(n, m); EmitNZFlagsCheck(context, res); EmitSubsCCheck(context, n, res); EmitSubsVCheck(context, n, m, res); } public static void Cmn(ArmEmitterContext context) { Operand n = GetAluN(context); Operand m = GetAluM(context, setCarry: false); Operand res = context.Add(n, m); EmitNZFlagsCheck(context, res); EmitAddsCCheck(context, n, res); EmitAddsVCheck(context, n, m, res); } public static void Eor(ArmEmitterContext context) { IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; Operand n = GetAluN(context); Operand m = GetAluM(context); Operand res = context.BitwiseExclusiveOr(n, m); if (ShouldSetFlags(context)) { EmitNZFlagsCheck(context, res); } EmitAluStore(context, res); } public static void Mov(ArmEmitterContext context) { IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; Operand m = GetAluM(context); if (ShouldSetFlags(context)) { EmitNZFlagsCheck(context, m); } EmitAluStore(context, m); } public static void Movt(ArmEmitterContext context) { IOpCode32AluImm16 op = (IOpCode32AluImm16)context.CurrOp; Operand d = GetIntA32(context, op.Rd); Operand imm = Const(op.Immediate << 16); // Immeditate value as top halfword. Operand res = context.BitwiseAnd(d, Const(0x0000ffff)); res = context.BitwiseOr(res, imm); EmitAluStore(context, res); } public static void Mul(ArmEmitterContext context) { IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; Operand n = GetAluN(context); Operand m = GetAluM(context); Operand res = context.Multiply(n, m); if (ShouldSetFlags(context)) { EmitNZFlagsCheck(context, res); } EmitAluStore(context, res); } public static void Mvn(ArmEmitterContext context) { IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; Operand m = GetAluM(context); Operand res = context.BitwiseNot(m); if (ShouldSetFlags(context)) { EmitNZFlagsCheck(context, res); } EmitAluStore(context, res); } public static void Orr(ArmEmitterContext context) { IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; Operand n = GetAluN(context); Operand m = GetAluM(context); Operand res = context.BitwiseOr(n, m); if (ShouldSetFlags(context)) { EmitNZFlagsCheck(context, res); } EmitAluStore(context, res); } public static void Orn(ArmEmitterContext context) { IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; Operand n = GetAluN(context); Operand m = GetAluM(context); Operand res = context.BitwiseOr(n, context.BitwiseNot(m)); if (ShouldSetFlags(context)) { EmitNZFlagsCheck(context, res); } EmitAluStore(context, res); } public static void Pkh(ArmEmitterContext context) { OpCode32AluRsImm op = (OpCode32AluRsImm)context.CurrOp; Operand n = GetAluN(context); Operand m = GetAluM(context); Operand res; bool tbform = op.ShiftType == ShiftType.Asr; if (tbform) { res = context.BitwiseOr(context.BitwiseAnd(n, Const(0xFFFF0000)), context.BitwiseAnd(m, Const(0xFFFF))); } else { res = context.BitwiseOr(context.BitwiseAnd(m, Const(0xFFFF0000)), context.BitwiseAnd(n, Const(0xFFFF))); } EmitAluStore(context, res); } public static void Rbit(ArmEmitterContext context) { Operand m = GetAluM(context); Operand res = EmitReverseBits32Op(context, m); EmitAluStore(context, res); } public static void Rev(ArmEmitterContext context) { Operand m = GetAluM(context); Operand res = context.ByteSwap(m); EmitAluStore(context, res); } public static void Rev16(ArmEmitterContext context) { Operand m = GetAluM(context); Operand res = EmitReverseBytes16_32Op(context, m); EmitAluStore(context, res); } public static void Revsh(ArmEmitterContext context) { Operand m = GetAluM(context); Operand res = EmitReverseBytes16_32Op(context, m); EmitAluStore(context, context.SignExtend16(OperandType.I32, res)); } public static void Rsc(ArmEmitterContext context) { IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; Operand n = GetAluN(context); Operand m = GetAluM(context, setCarry: false); Operand res = context.Subtract(m, n); Operand borrow = context.BitwiseExclusiveOr(GetFlag(PState.CFlag), Const(1)); res = context.Subtract(res, borrow); if (ShouldSetFlags(context)) { EmitNZFlagsCheck(context, res); EmitSbcsCCheck(context, m, n); EmitSubsVCheck(context, m, n, res); } EmitAluStore(context, res); } public static void Rsb(ArmEmitterContext context) { IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; Operand n = GetAluN(context); Operand m = GetAluM(context, setCarry: false); Operand res = context.Subtract(m, n); if (ShouldSetFlags(context)) { EmitNZFlagsCheck(context, res); EmitSubsCCheck(context, m, res); EmitSubsVCheck(context, m, n, res); } EmitAluStore(context, res); } public static void Sadd8(ArmEmitterContext context) { EmitAddSub8(context, add: true, unsigned: false); } public static void Sbc(ArmEmitterContext context) { IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; Operand n = GetAluN(context); Operand m = GetAluM(context, setCarry: false); Operand res = context.Subtract(n, m); Operand borrow = context.BitwiseExclusiveOr(GetFlag(PState.CFlag), Const(1)); res = context.Subtract(res, borrow); if (ShouldSetFlags(context)) { EmitNZFlagsCheck(context, res); EmitSbcsCCheck(context, n, m); EmitSubsVCheck(context, n, m, res); } EmitAluStore(context, res); } public static void Sbfx(ArmEmitterContext context) { IOpCode32AluBf op = (IOpCode32AluBf)context.CurrOp; var msb = op.Lsb + op.Msb; // For this instruction, the msb is actually a width. Operand n = GetIntA32(context, op.Rn); Operand res = context.ShiftRightSI(context.ShiftLeft(n, Const(31 - msb)), Const(31 - op.Msb)); SetIntA32(context, op.Rd, res); } public static void Sdiv(ArmEmitterContext context) { EmitDiv(context, unsigned: false); } public static void Sel(ArmEmitterContext context) { IOpCode32AluReg op = (IOpCode32AluReg)context.CurrOp; Operand n = GetIntA32(context, op.Rn); Operand m = GetIntA32(context, op.Rm); Operand ge0 = context.ZeroExtend8(OperandType.I32, context.Negate(GetFlag(PState.GE0Flag))); Operand ge1 = context.ZeroExtend8(OperandType.I32, context.Negate(GetFlag(PState.GE1Flag))); Operand ge2 = context.ZeroExtend8(OperandType.I32, context.Negate(GetFlag(PState.GE2Flag))); Operand ge3 = context.Negate(GetFlag(PState.GE3Flag)); Operand mask = context.BitwiseOr(ge0, context.ShiftLeft(ge1, Const(8))); mask = context.BitwiseOr(mask, context.ShiftLeft(ge2, Const(16))); mask = context.BitwiseOr(mask, context.ShiftLeft(ge3, Const(24))); Operand res = context.BitwiseOr(context.BitwiseAnd(n, mask), context.BitwiseAnd(m, context.BitwiseNot(mask))); SetIntA32(context, op.Rd, res); } public static void Shadd8(ArmEmitterContext context) { EmitHadd8(context, unsigned: false); } public static void Shsub8(ArmEmitterContext context) { EmitHsub8(context, unsigned: false); } public static void Ssat(ArmEmitterContext context) { OpCode32Sat op = (OpCode32Sat)context.CurrOp; EmitSat(context, -(1 << op.SatImm), (1 << op.SatImm) - 1); } public static void Ssat16(ArmEmitterContext context) { OpCode32Sat16 op = (OpCode32Sat16)context.CurrOp; EmitSat16(context, -(1 << op.SatImm), (1 << op.SatImm) - 1); } public static void Ssub8(ArmEmitterContext context) { EmitAddSub8(context, add: false, unsigned: false); } public static void Sub(ArmEmitterContext context) { IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; Operand n = GetAluN(context); Operand m = GetAluM(context, setCarry: false); Operand res = context.Subtract(n, m); if (ShouldSetFlags(context)) { EmitNZFlagsCheck(context, res); EmitSubsCCheck(context, n, res); EmitSubsVCheck(context, n, m, res); } EmitAluStore(context, res); } public static void Sxtb(ArmEmitterContext context) { EmitSignExtend(context, true, 8); } public static void Sxtb16(ArmEmitterContext context) { EmitExtend16(context, true); } public static void Sxth(ArmEmitterContext context) { EmitSignExtend(context, true, 16); } public static void Teq(ArmEmitterContext context) { Operand n = GetAluN(context); Operand m = GetAluM(context); Operand res = context.BitwiseExclusiveOr(n, m); EmitNZFlagsCheck(context, res); } public static void Tst(ArmEmitterContext context) { Operand n = GetAluN(context); Operand m = GetAluM(context); Operand res = context.BitwiseAnd(n, m); EmitNZFlagsCheck(context, res); } public static void Uadd8(ArmEmitterContext context) { EmitAddSub8(context, add: true, unsigned: true); } public static void Ubfx(ArmEmitterContext context) { IOpCode32AluBf op = (IOpCode32AluBf)context.CurrOp; var msb = op.Lsb + op.Msb; // For this instruction, the msb is actually a width. Operand n = GetIntA32(context, op.Rn); Operand res = context.ShiftRightUI(context.ShiftLeft(n, Const(31 - msb)), Const(31 - op.Msb)); SetIntA32(context, op.Rd, res); } public static void Udiv(ArmEmitterContext context) { EmitDiv(context, unsigned: true); } public static void Uhadd8(ArmEmitterContext context) { EmitHadd8(context, unsigned: true); } public static void Uhsub8(ArmEmitterContext context) { EmitHsub8(context, unsigned: true); } public static void Usat(ArmEmitterContext context) { OpCode32Sat op = (OpCode32Sat)context.CurrOp; EmitSat(context, 0, op.SatImm == 32 ? (int)(~0) : (1 << op.SatImm) - 1); } public static void Usat16(ArmEmitterContext context) { OpCode32Sat16 op = (OpCode32Sat16)context.CurrOp; EmitSat16(context, 0, (1 << op.SatImm) - 1); } public static void Usub8(ArmEmitterContext context) { EmitAddSub8(context, add: false, unsigned: true); } public static void Uxtb(ArmEmitterContext context) { EmitSignExtend(context, false, 8); } public static void Uxtb16(ArmEmitterContext context) { EmitExtend16(context, false); } public static void Uxth(ArmEmitterContext context) { EmitSignExtend(context, false, 16); } private static void EmitSignExtend(ArmEmitterContext context, bool signed, int bits) { IOpCode32AluUx op = (IOpCode32AluUx)context.CurrOp; Operand m = GetAluM(context); Operand res; if (op.RotateBits == 0) { res = m; } else { Operand rotate = Const(op.RotateBits); res = context.RotateRight(m, rotate); } switch (bits) { case 8: res = (signed) ? context.SignExtend8(OperandType.I32, res) : context.ZeroExtend8(OperandType.I32, res); break; case 16: res = (signed) ? context.SignExtend16(OperandType.I32, res) : context.ZeroExtend16(OperandType.I32, res); break; } if (op.Add) { res = context.Add(res, GetAluN(context)); } EmitAluStore(context, res); } private static void EmitExtend16(ArmEmitterContext context, bool signed) { IOpCode32AluUx op = (IOpCode32AluUx)context.CurrOp; Operand m = GetAluM(context); Operand res; if (op.RotateBits == 0) { res = m; } else { Operand rotate = Const(op.RotateBits); res = context.RotateRight(m, rotate); } Operand low16, high16; if (signed) { low16 = context.SignExtend8(OperandType.I32, res); high16 = context.SignExtend8(OperandType.I32, context.ShiftRightUI(res, Const(16))); } else { low16 = context.ZeroExtend8(OperandType.I32, res); high16 = context.ZeroExtend8(OperandType.I32, context.ShiftRightUI(res, Const(16))); } if (op.Add) { Operand n = GetAluN(context); Operand lowAdd, highAdd; if (signed) { lowAdd = context.SignExtend16(OperandType.I32, n); highAdd = context.SignExtend16(OperandType.I32, context.ShiftRightUI(n, Const(16))); } else { lowAdd = context.ZeroExtend16(OperandType.I32, n); highAdd = context.ZeroExtend16(OperandType.I32, context.ShiftRightUI(n, Const(16))); } low16 = context.Add(low16, lowAdd); high16 = context.Add(high16, highAdd); } res = context.BitwiseOr( context.ZeroExtend16(OperandType.I32, low16), context.ShiftLeft(context.ZeroExtend16(OperandType.I32, high16), Const(16))); EmitAluStore(context, res); } private static void EmitDiv(ArmEmitterContext context, bool unsigned) { Operand n = GetAluN(context); Operand m = GetAluM(context); Operand zero = Const(m.Type, 0); Operand divisorIsZero = context.ICompareEqual(m, zero); Operand lblBadDiv = Label(); Operand lblEnd = Label(); context.BranchIfTrue(lblBadDiv, divisorIsZero); if (!unsigned) { // ARM64 behaviour: If Rn == INT_MIN && Rm == -1, Rd = INT_MIN (overflow). // TODO: tests to ensure A32 works the same Operand intMin = Const(int.MinValue); Operand minus1 = Const(-1); Operand nIsIntMin = context.ICompareEqual(n, intMin); Operand mIsMinus1 = context.ICompareEqual(m, minus1); Operand lblGoodDiv = Label(); context.BranchIfFalse(lblGoodDiv, context.BitwiseAnd(nIsIntMin, mIsMinus1)); EmitAluStore(context, intMin); context.Branch(lblEnd); context.MarkLabel(lblGoodDiv); } Operand res = unsigned ? context.DivideUI(n, m) : context.Divide(n, m); EmitAluStore(context, res); context.Branch(lblEnd); context.MarkLabel(lblBadDiv); EmitAluStore(context, zero); context.MarkLabel(lblEnd); } private static void EmitAddSub8(ArmEmitterContext context, bool add, bool unsigned) { IOpCode32AluReg op = (IOpCode32AluReg)context.CurrOp; Operand n = GetIntA32(context, op.Rn); Operand m = GetIntA32(context, op.Rm); Operand res = Const(0); for (int byteSel = 0; byteSel < 4; byteSel++) { Operand shift = Const(byteSel * 8); Operand nByte = context.ShiftRightUI(n, shift); Operand mByte = context.ShiftRightUI(m, shift); nByte = unsigned ? context.ZeroExtend8(OperandType.I32, nByte) : context.SignExtend8(OperandType.I32, nByte); mByte = unsigned ? context.ZeroExtend8(OperandType.I32, mByte) : context.SignExtend8(OperandType.I32, mByte); Operand resByte = add ? context.Add(nByte, mByte) : context.Subtract(nByte, mByte); res = context.BitwiseOr(res, context.ShiftLeft(context.ZeroExtend8(OperandType.I32, resByte), shift)); SetFlag(context, PState.GE0Flag + byteSel, unsigned && add ? context.ShiftRightUI(resByte, Const(8)) : context.ShiftRightUI(context.BitwiseNot(resByte), Const(31))); } SetIntA32(context, op.Rd, res); } private static void EmitHadd8(ArmEmitterContext context, bool unsigned) { IOpCode32AluReg op = (IOpCode32AluReg)context.CurrOp; Operand m = GetIntA32(context, op.Rm); Operand n = GetIntA32(context, op.Rn); Operand xor, res, carry; // This relies on the equality x+y == ((x&y) << 1) + (x^y). // Note that x^y always contains the LSB of the result. // Since we want to calculate (x+y)/2, we can instead calculate (x&y) + ((x^y)>>1). // We mask by 0x7F to remove the LSB so that it doesn't leak into the field below. res = context.BitwiseAnd(m, n); carry = context.BitwiseExclusiveOr(m, n); xor = context.ShiftRightUI(carry, Const(1)); xor = context.BitwiseAnd(xor, Const(0x7F7F7F7Fu)); res = context.Add(res, xor); if (!unsigned) { // Propagates the sign bit from (x^y)>>1 upwards by one. carry = context.BitwiseAnd(carry, Const(0x80808080u)); res = context.BitwiseExclusiveOr(res, carry); } SetIntA32(context, op.Rd, res); } private static void EmitHsub8(ArmEmitterContext context, bool unsigned) { IOpCode32AluReg op = (IOpCode32AluReg)context.CurrOp; Operand m = GetIntA32(context, op.Rm); Operand n = GetIntA32(context, op.Rn); Operand left, right, carry, res; // This relies on the equality x-y == (x^y) - (((x^y)&y) << 1). // Note that x^y always contains the LSB of the result. // Since we want to calculate (x+y)/2, we can instead calculate ((x^y)>>1) - ((x^y)&y). carry = context.BitwiseExclusiveOr(m, n); left = context.ShiftRightUI(carry, Const(1)); right = context.BitwiseAnd(carry, m); // We must now perform a partitioned subtraction. // We can do this because minuend contains 7 bit fields. // We use the extra bit in minuend as a bit to borrow from; we set this bit. // We invert this bit at the end as this tells us if that bit was borrowed from. res = context.BitwiseOr(left, Const(0x80808080)); res = context.Subtract(res, right); res = context.BitwiseExclusiveOr(res, Const(0x80808080)); if (!unsigned) { // We then sign extend the result into this bit. carry = context.BitwiseAnd(carry, Const(0x80808080)); res = context.BitwiseExclusiveOr(res, carry); } SetIntA32(context, op.Rd, res); } private static void EmitSat(ArmEmitterContext context, int intMin, int intMax) { OpCode32Sat op = (OpCode32Sat)context.CurrOp; Operand n = GetIntA32(context, op.Rn); int shift = DecodeImmShift(op.ShiftType, op.Imm5); switch (op.ShiftType) { case ShiftType.Lsl: if (shift == 32) { n = Const(0); } else { n = context.ShiftLeft(n, Const(shift)); } break; case ShiftType.Asr: if (shift == 32) { n = context.ShiftRightSI(n, Const(31)); } else { n = context.ShiftRightSI(n, Const(shift)); } break; } Operand lblCheckLtIntMin = Label(); Operand lblNoSat = Label(); Operand lblEnd = Label(); context.BranchIfFalse(lblCheckLtIntMin, context.ICompareGreater(n, Const(intMax))); SetFlag(context, PState.QFlag, Const(1)); SetIntA32(context, op.Rd, Const(intMax)); context.Branch(lblEnd); context.MarkLabel(lblCheckLtIntMin); context.BranchIfFalse(lblNoSat, context.ICompareLess(n, Const(intMin))); SetFlag(context, PState.QFlag, Const(1)); SetIntA32(context, op.Rd, Const(intMin)); context.Branch(lblEnd); context.MarkLabel(lblNoSat); SetIntA32(context, op.Rd, n); context.MarkLabel(lblEnd); } private static void EmitSat16(ArmEmitterContext context, int intMin, int intMax) { OpCode32Sat16 op = (OpCode32Sat16)context.CurrOp; void SetD(int part, Operand value) { if (part == 0) { SetIntA32(context, op.Rd, context.ZeroExtend16(OperandType.I32, value)); } else { SetIntA32(context, op.Rd, context.BitwiseOr(GetIntA32(context, op.Rd), context.ShiftLeft(value, Const(16)))); } } Operand n = GetIntA32(context, op.Rn); Operand nLow = context.SignExtend16(OperandType.I32, n); Operand nHigh = context.ShiftRightSI(n, Const(16)); for (int part = 0; part < 2; part++) { Operand nPart = part == 0 ? nLow : nHigh; Operand lblCheckLtIntMin = Label(); Operand lblNoSat = Label(); Operand lblEnd = Label(); context.BranchIfFalse(lblCheckLtIntMin, context.ICompareGreater(nPart, Const(intMax))); SetFlag(context, PState.QFlag, Const(1)); SetD(part, Const(intMax)); context.Branch(lblEnd); context.MarkLabel(lblCheckLtIntMin); context.BranchIfFalse(lblNoSat, context.ICompareLess(nPart, Const(intMin))); SetFlag(context, PState.QFlag, Const(1)); SetD(part, Const(intMin)); context.Branch(lblEnd); context.MarkLabel(lblNoSat); SetD(part, nPart); context.MarkLabel(lblEnd); } } private static void EmitAluStore(ArmEmitterContext context, Operand value) { IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; EmitGenericAluStoreA32(context, op.Rd, ShouldSetFlags(context), value); } } }