using Translator.Core.Disassembly; using Translator.Core.Ir; using Translator.Core.Lifting; using Xunit; namespace Translator.Tests; public class PairedSingleLifterTests { private static PpcRegisterOperand Fpr(int index) => new($"f{index}", index); private static PpcRegisterOperand Gpr(int index) => new($"r{index}", index); [Fact] public void LiftsPsSum0UsingPpcHelperOperandOrder() { var instruction = PpcInstruction.Synthetic( 0x80000000, 0, "ps_sum0", new PpcOperand[] { Fpr(1), Fpr(2), Fpr(3), Fpr(4) }); var ir = Assert.Single(new PpcLifter().Lift(new[] { instruction })).Ir; var call = Assert.IsType(Assert.Single(ir)); Assert.Equal("f1", call.Destination); Assert.Equal("PPC_PsSum0", call.Target); Assert.Equal(new[] { "f2", "f4", "f3" }, call.Arguments.Select(a => a.RegisterName)); } [Fact] public void LiftsPsSelWithControlInSecondArgument() { var instruction = PpcInstruction.Synthetic( 0x80000000, 0, "ps_sel", new PpcOperand[] { Fpr(1), Fpr(2), Fpr(3), Fpr(4) }); var ir = Assert.Single(new PpcLifter().Lift(new[] { instruction })).Ir; var call = Assert.IsType(Assert.Single(ir)); Assert.Equal("PPC_PsSel", call.Target); Assert.Equal(new[] { "f3", "f2", "f4" }, call.Arguments.Select(a => a.RegisterName)); } [Fact] public void LiftsRawOpcodeAliasForPsMul() { var instruction = PpcInstruction.Synthetic( 0x80000000, 0x106100B2u, "opc_4_50", System.Array.Empty()); var ir = Assert.Single(new PpcLifter().Lift(new[] { instruction })).Ir; var call = Assert.IsType(Assert.Single(ir)); Assert.Equal("PPC_PsMul", call.Target); Assert.Equal("f3", call.Destination); Assert.Equal(new[] { "f1", "f2" }, call.Arguments.Select(a => a.RegisterName)); } [Fact] public void LiftsPsNegFromExplicitMnemonic() { var instruction = PpcInstruction.Synthetic( 0x80000000, 0, "ps_neg", new PpcOperand[] { Fpr(4), Fpr(7) }); var ir = Assert.Single(new PpcLifter().Lift(new[] { instruction })).Ir; var call = Assert.IsType(Assert.Single(ir)); Assert.Equal("f4", call.Destination); Assert.Equal("PPC_PsNeg", call.Target); Assert.Equal("f7", Assert.Single(call.Arguments).RegisterName); } [Fact] public void LiftsPsqLoadWithAbsoluteBaseZeroAddress() { var instruction = PpcInstruction.Synthetic( 0x80000000, 0, "psq_l", new PpcOperand[] { Fpr(2), new PpcDisplacementOperand(0x24, "r0", 0), new PpcImmediateOperand(1), new PpcImmediateOperand(3) }); var ir = Assert.Single(new PpcLifter().Lift(new[] { instruction })).Ir; Assert.Equal(3, ir.Count); Assert.IsType(ir[0]); var assign = Assert.IsType(ir[1]); Assert.Equal("f2_psq_ea", assign.Destination); Assert.Equal(0x24, assign.Value.Constant); var call = Assert.IsType(ir[2]); Assert.Equal("PPC_PsqL", call.Target); Assert.Equal("f2", call.Destination); Assert.Equal(new long?[] { null, 1, 3 }, call.Arguments.Select(a => a.Constant).ToArray()); } [Fact] public void LiftsPsqStoreUpdateFormAndWritesBackBase() { var instruction = PpcInstruction.Synthetic( 0x80000000, 0, "psq_stu", new PpcOperand[] { Fpr(2), new PpcDisplacementOperand(0x10, "r4", 4), new PpcImmediateOperand(0), new PpcImmediateOperand(6) }); var ir = Assert.Single(new PpcLifter().Lift(new[] { instruction })).Ir; Assert.Equal(4, ir.Count); Assert.IsType(ir[0]); var addr = Assert.IsType(ir[1]); Assert.Equal("r4_psq_ea", addr.Destination); var store = Assert.IsType(ir[2]); Assert.Equal("PPC_PsqSt", store.Target); Assert.Equal("r4_psq_ea", store.Arguments[0].RegisterName); Assert.Equal("f2", store.Arguments[1].RegisterName); var writeback = Assert.IsType(ir[3]); Assert.Equal("r4", writeback.Destination); Assert.Equal("r4_psq_ea", writeback.Value.RegisterName); } [Fact] public void RejectsInvalidPsqUpdateWithR0Base() { var instruction = PpcInstruction.Synthetic( 0x80000000, 0, "psq_lu", new PpcOperand[] { Fpr(1), new PpcDisplacementOperand(4, "r0", 0), new PpcImmediateOperand(0), new PpcImmediateOperand(0) }); Assert.Throws(() => new PpcLifter().Lift(new[] { instruction })); } [Fact] public void LiftsPsCompareUsingCrFieldImmediate() { var instruction = PpcInstruction.Synthetic( 0x80000000, 0, "ps_cmpu1", new PpcOperand[] { new PpcConditionRegisterOperand("cr7", 28), Fpr(2), Fpr(3) }); var ir = Assert.Single(new PpcLifter().Lift(new[] { instruction })).Ir; var call = Assert.IsType(Assert.Single(ir)); Assert.Equal("PPC_PsCmpu1", call.Target); Assert.Equal(7, call.Arguments[0].Constant); Assert.Equal("f2", call.Arguments[1].RegisterName); Assert.Equal("f3", call.Arguments[2].RegisterName); } [Fact] public void LiftsPsSelAliasAsPsNegWhenRawXformMatches() { var raw = (4u << 21) | (7u << 11) | (40u << 1); var instruction = PpcInstruction.Synthetic( 0x80000000, raw, "ps_sel", new PpcOperand[] { Fpr(4), Fpr(1), Fpr(2), Fpr(3) }); var ir = Assert.Single(new PpcLifter().Lift(new[] { instruction })).Ir; var call = Assert.IsType(Assert.Single(ir)); Assert.Equal("PPC_PsNeg", call.Target); Assert.Equal("f4", call.Destination); Assert.Equal("f7", Assert.Single(call.Arguments).RegisterName); } [Fact] public void LiftsRawOpcodeVariantsForMaddsAndIndexedLoadStore() { var maddsRaw = (3u << 21) | (1u << 16) | (2u << 11) | (4u << 6); var madds = PpcInstruction.Synthetic(0x80000000, maddsRaw, "opc_4_28", System.Array.Empty()); var maddsIr = Assert.Single(new PpcLifter().Lift(new[] { madds })).Ir; var maddsCall = Assert.IsType(Assert.Single(maddsIr)); Assert.Equal("PPC_PsMadds0", maddsCall.Target); Assert.Equal("f3", maddsCall.Destination); Assert.Equal(new[] { "f1", "f4", "f2" }, maddsCall.Arguments.Select(a => a.RegisterName)); var loadRaw = (3u << 21) | (4u << 16) | (5u << 11) | (1u << 10) | (6u << 7); var load = PpcInstruction.Synthetic(0x80000004, loadRaw, "opc_4_76", System.Array.Empty()); var loadIr = Assert.Single(new PpcLifter().Lift(new[] { load })).Ir; Assert.Equal(3, loadIr.Count); var loadCall = Assert.IsType(loadIr[1]); Assert.Equal("PPC_PsqL", loadCall.Target); Assert.Equal("f3", loadCall.Destination); Assert.Equal(1, loadCall.Arguments[1].Constant); Assert.Equal(6, loadCall.Arguments[2].Constant); var loadWriteback = Assert.IsType(loadIr[2]); Assert.Equal("r4", loadWriteback.Destination); var storeRaw = (2u << 21) | (4u << 16) | (5u << 11) | (1u << 10) | (3u << 7); var store = PpcInstruction.Synthetic(0x80000008, storeRaw, "opc_4_78", System.Array.Empty()); var storeIr = Assert.Single(new PpcLifter().Lift(new[] { store })).Ir; Assert.Equal(3, storeIr.Count); var storeCall = Assert.IsType(storeIr[1]); Assert.Equal("PPC_PsqSt", storeCall.Target); Assert.Equal("f2", storeCall.Arguments[1].RegisterName); Assert.Equal(1, storeCall.Arguments[2].Constant); Assert.Equal(3, storeCall.Arguments[3].Constant); var storeWriteback = Assert.IsType(storeIr[2]); Assert.Equal("r4", storeWriteback.Destination); } [Fact] public void LiftsMergeAndScalarEstimateHelpers() { var merge = PpcInstruction.Synthetic( 0x80000000, 0, "ps_merge10", new PpcOperand[] { Fpr(1), Fpr(2), Fpr(3) }); var mergeIr = Assert.Single(new PpcLifter().Lift(new[] { merge })).Ir; var mergeCall = Assert.IsType(Assert.Single(mergeIr)); Assert.Equal("PPC_PsMerge10", mergeCall.Target); var res = PpcInstruction.Synthetic( 0x80000004, 0, "ps_rsqrte", new PpcOperand[] { Fpr(4), Fpr(5) }); var resIr = Assert.Single(new PpcLifter().Lift(new[] { res })).Ir; var resCall = Assert.IsType(Assert.Single(resIr)); Assert.Equal("PPC_PsRsqrte", resCall.Target); Assert.Equal("f4", resCall.Destination); var nabs = PpcInstruction.Synthetic( 0x80000008, 0, "ps_nabs", new PpcOperand[] { Fpr(6), Fpr(7) }); var nabsIr = Assert.Single(new PpcLifter().Lift(new[] { nabs })).Ir; var nabsCall = Assert.IsType(Assert.Single(nabsIr)); Assert.Equal("PPC_PsNabs", nabsCall.Target); Assert.Equal("f6", nabsCall.Destination); } [Fact] public void KeepsPairedMoveDistinctFromScalarFmr() { var move = PpcInstruction.Synthetic( 0x80000000, 0, "ps_mr", new PpcOperand[] { Fpr(2), Fpr(7) }); var moveIr = Assert.Single(new PpcLifter().Lift(new[] { move })).Ir; var moveCall = Assert.IsType(Assert.Single(moveIr)); Assert.Equal("PPC_PsMr", moveCall.Target); Assert.Equal("f2", moveCall.Destination); Assert.Equal("f7", Assert.Single(moveCall.Arguments).RegisterName); } [Fact] public void LiftsDotVariantsForPairedSingleArithmetic() { var div = PpcInstruction.Synthetic( 0x7FFFFFFC, 0, "ps_div.", new PpcOperand[] { Fpr(1), Fpr(2), Fpr(3) }); var divIr = Assert.Single(new PpcLifter().Lift(new[] { div })).Ir; var divCall = Assert.IsType(Assert.Single(divIr)); Assert.Equal("PPC_PsDiv", divCall.Target); Assert.Equal(new[] { "f2", "f3" }, divCall.Arguments.Select(a => a.RegisterName)); var madds0 = PpcInstruction.Synthetic( 0x80000000, 0, "ps_madds0.", new PpcOperand[] { Fpr(1), Fpr(2), Fpr(3), Fpr(4) }); var madds0Ir = Assert.Single(new PpcLifter().Lift(new[] { madds0 })).Ir; Assert.Equal("PPC_PsMadds0", Assert.IsType(Assert.Single(madds0Ir)).Target); var madds1 = PpcInstruction.Synthetic( 0x80000004, 0, "ps_madds1.", new PpcOperand[] { Fpr(1), Fpr(2), Fpr(3), Fpr(4) }); var madds1Ir = Assert.Single(new PpcLifter().Lift(new[] { madds1 })).Ir; var madds1Call = Assert.IsType(Assert.Single(madds1Ir)); Assert.Equal("PPC_PsMadds1", madds1Call.Target); Assert.Equal(new[] { "f2", "f3", "f4" }, madds1Call.Arguments.Select(a => a.RegisterName)); var sum0 = PpcInstruction.Synthetic( 0x80000008, 0, "ps_sum0.", new PpcOperand[] { Fpr(5), Fpr(6), Fpr(7), Fpr(8) }); var sum0Ir = Assert.Single(new PpcLifter().Lift(new[] { sum0 })).Ir; var sum0Call = Assert.IsType(Assert.Single(sum0Ir)); Assert.Equal("PPC_PsSum0", sum0Call.Target); Assert.Equal(new[] { "f6", "f8", "f7" }, sum0Call.Arguments.Select(a => a.RegisterName)); var sum1 = PpcInstruction.Synthetic( 0x8000000C, 0, "ps_sum1.", new PpcOperand[] { Fpr(5), Fpr(6), Fpr(7), Fpr(8) }); var sum1Ir = Assert.Single(new PpcLifter().Lift(new[] { sum1 })).Ir; var sum1Call = Assert.IsType(Assert.Single(sum1Ir)); Assert.Equal("PPC_PsSum1", sum1Call.Target); Assert.Equal(new[] { "f6", "f8", "f7" }, sum1Call.Arguments.Select(a => a.RegisterName)); var muls0 = PpcInstruction.Synthetic( 0x80000008, 0, "ps_muls0.", new PpcOperand[] { Fpr(9), Fpr(10), Fpr(11) }); var muls0Ir = Assert.Single(new PpcLifter().Lift(new[] { muls0 })).Ir; Assert.Equal("PPC_PsMuls0", Assert.IsType(Assert.Single(muls0Ir)).Target); var muls1 = PpcInstruction.Synthetic( 0x8000000C, 0, "ps_muls1.", new PpcOperand[] { Fpr(12), Fpr(13), Fpr(14) }); var muls1Ir = Assert.Single(new PpcLifter().Lift(new[] { muls1 })).Ir; Assert.Equal("PPC_PsMuls1", Assert.IsType(Assert.Single(muls1Ir)).Target); } [Fact] public void LiftsExplicitUnaryMoveAndCompareVariants() { var res = PpcInstruction.Synthetic( 0x80000000, 0, "ps_res", new PpcOperand[] { Fpr(1), Fpr(2) }); var resIr = Assert.Single(new PpcLifter().Lift(new[] { res })).Ir; Assert.Equal("PPC_PsRes", Assert.IsType(Assert.Single(resIr)).Target); var abs = PpcInstruction.Synthetic( 0x80000004, 0, "ps_abs.", new PpcOperand[] { Fpr(3), Fpr(4) }); var absIr = Assert.Single(new PpcLifter().Lift(new[] { abs })).Ir; Assert.Equal("PPC_PsAbs", Assert.IsType(Assert.Single(absIr)).Target); var mr = PpcInstruction.Synthetic( 0x80000008, 0, "ps_mr", new PpcOperand[] { Fpr(5), Fpr(6) }); var mrIr = Assert.Single(new PpcLifter().Lift(new[] { mr })).Ir; var mrCall = Assert.IsType(Assert.Single(mrIr)); Assert.Equal("PPC_PsMr", mrCall.Target); Assert.Equal("f5", mrCall.Destination); Assert.Equal("f6", Assert.Single(mrCall.Arguments).RegisterName); var mrDot = PpcInstruction.Synthetic( 0x8000000A, 0, "ps_mr.", new PpcOperand[] { Fpr(5), Fpr(6) }); Assert.Equal( "PPC_PsMr", Assert.IsType(Assert.Single(Assert.Single(new PpcLifter().Lift(new[] { mrDot })).Ir)).Target); var cmpo0 = PpcInstruction.Synthetic( 0x8000000C, 0, "ps_cmpo0.", new PpcOperand[] { new PpcConditionRegisterOperand("cr5", 20), Fpr(7), Fpr(8) }); var cmpo0Ir = Assert.Single(new PpcLifter().Lift(new[] { cmpo0 })).Ir; var cmpo0Call = Assert.IsType(Assert.Single(cmpo0Ir)); Assert.Equal("PPC_PsCmpo0", cmpo0Call.Target); Assert.Equal(5, cmpo0Call.Arguments[0].Constant); var cmpu0 = PpcInstruction.Synthetic( 0x80000010, 0, "ps_cmpu0", new PpcOperand[] { new PpcConditionRegisterOperand("cr3", 12), Fpr(9), Fpr(10) }); var cmpu0Ir = Assert.Single(new PpcLifter().Lift(new[] { cmpu0 })).Ir; var cmpu0Call = Assert.IsType(Assert.Single(cmpu0Ir)); Assert.Equal("PPC_PsCmpu0", cmpu0Call.Target); Assert.Equal(3, cmpu0Call.Arguments[0].Constant); var cmpo1 = PpcInstruction.Synthetic( 0x80000014, 0, "ps_cmpo1", new PpcOperand[] { new PpcConditionRegisterOperand("cr2", 8), Fpr(11), Fpr(12) }); var cmpo1Ir = Assert.Single(new PpcLifter().Lift(new[] { cmpo1 })).Ir; var cmpo1Call = Assert.IsType(Assert.Single(cmpo1Ir)); Assert.Equal("PPC_PsCmpo1", cmpo1Call.Target); Assert.Equal(2, cmpo1Call.Arguments[0].Constant); var cmpu1 = PpcInstruction.Synthetic( 0x80000018, 0, "ps_cmpu1.", new PpcOperand[] { new PpcConditionRegisterOperand("cr1", 4), Fpr(13), Fpr(14) }); var cmpu1Ir = Assert.Single(new PpcLifter().Lift(new[] { cmpu1 })).Ir; var cmpu1Call = Assert.IsType(Assert.Single(cmpu1Ir)); Assert.Equal("PPC_PsCmpu1", cmpu1Call.Target); Assert.Equal(1, cmpu1Call.Arguments[0].Constant); } [Fact] public void LiftsRawOpc4AliasesForCompareMoveAndIndexedMemory() { var compare = PpcInstruction.Synthetic( 0x80000000, (1u << 16) | (2u << 11), "opc_4_0", System.Array.Empty()); var compareIr = Assert.Single(new PpcLifter().Lift(new[] { compare })).Ir; var compareSetCr = Assert.IsType(Assert.Single(compareIr)); Assert.Equal(0, compareSetCr.FieldIndex); Assert.Equal("f1", compareSetCr.Left.RegisterName); Assert.Equal("f2", compareSetCr.Right.RegisterName); var move = PpcInstruction.Synthetic( 0x80000004, (4u << 21) | (7u << 11), "opc_4_4", System.Array.Empty()); var moveIr = Assert.Single(new PpcLifter().Lift(new[] { move })).Ir; var moveCall = Assert.IsType(Assert.Single(moveIr)); Assert.Equal("PPC_PsMr", moveCall.Target); Assert.Equal("f4", moveCall.Destination); Assert.Equal("f7", Assert.Single(moveCall.Arguments).RegisterName); var neg = PpcInstruction.Synthetic( 0x80000008, (5u << 21) | (8u << 11), "opc_4_5", System.Array.Empty()); var negIr = Assert.Single(new PpcLifter().Lift(new[] { neg })).Ir; var negCall = Assert.IsType(Assert.Single(negIr)); Assert.Equal("PPC_PsNabs", negCall.Target); Assert.Equal("f5", negCall.Destination); Assert.Equal("f8", Assert.Single(negCall.Arguments).RegisterName); var load = PpcInstruction.Synthetic( 0x8000000C, (3u << 21) | (4u << 16) | (5u << 11) | (1u << 10) | (6u << 7), "opc_4_12", System.Array.Empty()); var loadIr = Assert.Single(new PpcLifter().Lift(new[] { load })).Ir; Assert.Equal(2, loadIr.Count); var loadCall = Assert.IsType(loadIr[1]); Assert.Equal("PPC_PsqL", loadCall.Target); Assert.Equal("f3", loadCall.Destination); Assert.Equal(1, loadCall.Arguments[1].Constant); Assert.Equal(6, loadCall.Arguments[2].Constant); var store = PpcInstruction.Synthetic( 0x80000010, (2u << 21) | (4u << 16) | (5u << 11) | (1u << 10) | (3u << 7), "opc_4_14", System.Array.Empty()); var storeIr = Assert.Single(new PpcLifter().Lift(new[] { store })).Ir; Assert.Equal(2, storeIr.Count); var storeCall = Assert.IsType(storeIr[1]); Assert.Equal("PPC_PsqSt", storeCall.Target); Assert.Equal("f2", storeCall.Arguments[1].RegisterName); Assert.Equal(1, storeCall.Arguments[2].Constant); Assert.Equal(3, storeCall.Arguments[3].Constant); } [Fact] public void RejectsMalformedAndUnknownRawOpc4Variants() { var malformed = PpcInstruction.Synthetic( 0x80000000, 0, "opc_4_bad", System.Array.Empty()); Assert.Throws(() => new PpcLifter().Lift(new[] { malformed })); var unknown = PpcInstruction.Synthetic( 0x80000004, 0, "opc_4_999", System.Array.Empty()); Assert.Throws(() => new PpcLifter().Lift(new[] { unknown })); } [Theory] [InlineData("opc_4_20", "call", "PPC_PsSum0")] [InlineData("opc_4_22", "call", "PPC_PsSum1")] [InlineData("opc_4_24", "call", "PPC_PsMuls0")] [InlineData("opc_4_26", "call", "PPC_PsMuls1")] [InlineData("opc_4_30", "call", "PPC_PsMadds1")] [InlineData("opc_4_36", "call", "PPC_PsDiv")] [InlineData("opc_4_40", "call", "PPC_PsSub")] [InlineData("opc_4_42", "call", "PPC_PsAdd")] [InlineData("opc_4_44", "call", "PPC_PsAbs")] [InlineData("opc_4_46", "call", "PPC_PsSel")] [InlineData("opc_4_48", "call", "PPC_PsRes")] [InlineData("opc_4_52", "call", "PPC_PsRsqrte")] [InlineData("opc_4_56", "call", "PPC_PsMsub")] [InlineData("opc_4_58", "call", "PPC_PsMadd")] [InlineData("opc_4_60", "call", "PPC_PsNmsub")] [InlineData("opc_4_62", "call", "PPC_PsNmadd")] public void LiftsAdditionalRawOpc4ArithmeticVariants(string mnemonic, string kind, string opOrTarget) { var raw = (3u << 21) | (1u << 16) | (2u << 11) | (4u << 6); var instruction = PpcInstruction.Synthetic(0x80000000, raw, mnemonic, System.Array.Empty()); var ir = Assert.Single(new PpcLifter().Lift(new[] { instruction })).Ir; if (kind == "call") { var call = Assert.IsType(Assert.Single(ir)); Assert.Equal(opOrTarget, call.Target); Assert.Equal("f3", call.Destination); } else { var binary = Assert.IsType(Assert.Single(ir)); Assert.Equal(opOrTarget, binary.Op); Assert.Equal("f3", binary.Destination); } } }