diff --git a/CMakeLists.txt b/CMakeLists.txt index 232be84cad..bd0e5eabdf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -169,11 +169,10 @@ endmacro() set(CommonExtra) if(ARM) set(CommonExtra ${CommonExtra} - Common/ArmABI.h - Common/ArmABI.cpp + Common/ArmCPUDetect.cpp Common/ArmEmitter.h Common/ArmEmitter.cpp - Common/ThunkARM.cpp) + Common/ArmThunk.cpp) elseif(X86) set(CommonExtra ${CommonExtra} Common/ABI.cpp @@ -584,19 +583,20 @@ include_directories(ext/libkirk) set(CoreExtra) if(ARM) set(CoreExtra ${CoreExtra} - Core/MIPS/ARM/Asm.cpp - Core/MIPS/ARM/Asm.h - Core/MIPS/ARM/CompALU.cpp - Core/MIPS/ARM/CompBranch.cpp - Core/MIPS/ARM/CompFPU.cpp - Core/MIPS/ARM/CompLoadStore.cpp - Core/MIPS/ARM/CompVFPU.cpp - Core/MIPS/ARM/Jit.cpp - Core/MIPS/ARM/Jit.h - Core/MIPS/ARM/JitCache.cpp - Core/MIPS/ARM/JitCache.h - Core/MIPS/ARM/RegCache.cpp - Core/MIPS/ARM/RegCache.h) + Core/MIPS/ARM/ArmAsm.cpp + Core/MIPS/ARM/ArmAsm.h + Core/MIPS/ARM/ArmCompALU.cpp + Core/MIPS/ARM/ArmCompBranch.cpp + Core/MIPS/ARM/ArmCompFPU.cpp + Core/MIPS/ARM/ArmCompLoadStore.cpp + Core/MIPS/ARM/ArmCompVFPU.cpp + Core/MIPS/ARM/ArmJit.cpp + Core/MIPS/ARM/ArmJit.h + Core/MIPS/ARM/ArmJitCache.cpp + Core/MIPS/ARM/ArmJitCache.h + Core/MIPS/ARM/ArmRegCache.cpp + Core/MIPS/ARM/ArmRegCache.h + ext/disarm.cpp) elseif(X86) set(CoreExtra ${CoreExtra} Core/MIPS/x86/Asm.cpp @@ -916,6 +916,9 @@ set(NativeAppSource android/jni/GamepadEmu.cpp android/jni/UIShader.cpp android/jni/ui_atlas.cpp) +if(ANDROID) + set(NativeAppSource ${NativeAppSource} android/jni/ArmEmitterTest.cpp) +endif() set(NativeAssets android/assets/ui_atlas.zim assets/ppge_atlas.zim) diff --git a/Common/ArmABI.cpp b/Common/ArmABI.cpp index 63824d808f..688f54d381 100644 --- a/Common/ArmABI.cpp +++ b/Common/ArmABI.cpp @@ -2,7 +2,7 @@ // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 2.0 or later versions. +// the Free Software Foundation, version 2.0. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -23,30 +23,50 @@ using namespace ArmGen; // If passing arguments, don't use this. void ARMXEmitter::ARMABI_CallFunction(void *func) { - ARMABI_MOVI2R(R14, Mem(func)); PUSH(5, R0, R1, R2, R3, _LR); + ARMABI_MOVI2R(R14, (u32)func); BL(R14); POP(5, R0, R1, R2, R3, _LR); } void ARMXEmitter::ARMABI_CallFunctionC(void *func, u32 Arg) { - ARMABI_MOVI2R(R14, Mem(func)); PUSH(5, R0, R1, R2, R3, _LR); + ARMABI_MOVI2R(R14, (u32)func); ARMABI_MOVI2R(R0, Arg); BL(R14); POP(5, R0, R1, R2, R3, _LR); - } + +void ARMXEmitter::ARMABI_CallFunctionCNoSave(void *func, u32 Arg) +{ + PUSH(1, _LR); + ARMABI_MOVI2R(R14, (u32)func); + ARMABI_MOVI2R(R0, Arg); + BL(R14); + POP(1, _LR); +} + void ARMXEmitter::ARMABI_CallFunctionCC(void *func, u32 Arg1, u32 Arg2) { - ARMABI_MOVI2R(R14, Mem(func)); PUSH(5, R0, R1, R2, R3, _LR); + ARMABI_MOVI2R(R14, (u32)func); ARMABI_MOVI2R(R0, Arg1); ARMABI_MOVI2R(R1, Arg2); BL(R14); POP(5, R0, R1, R2, R3, _LR); } +void ARMXEmitter::ARMABI_CallFunctionCCC(void *func, u32 Arg1, u32 Arg2, u32 Arg3) +{ + PUSH(5, R0, R1, R2, R3, _LR); + ARMABI_MOVI2R(R14, (u32)func); + ARMABI_MOVI2R(R0, Arg1); + ARMABI_MOVI2R(R1, Arg2); + ARMABI_MOVI2R(R2, Arg3); + BL(R14); + POP(5, R0, R1, R2, R3, _LR); +} + void ARMXEmitter::ARMABI_PushAllCalleeSavedRegsAndAdjustStack() { // Note: 4 * 4 = 16 bytes, so alignment is preserved. PUSH(4, R0, R1, R2, R3); @@ -55,23 +75,7 @@ void ARMXEmitter::ARMABI_PushAllCalleeSavedRegsAndAdjustStack() { void ARMXEmitter::ARMABI_PopAllCalleeSavedRegsAndAdjustStack() { POP(4, R0, R1, R2, R3); } -void ARMXEmitter::ARMABI_MOVI2R(ARMReg reg, Operand2 val) -{ - // TODO: There are more fancy ways to save calls if we check if - // The imm can be rotated or shifted a certain way. - // Masks tend to be able to be moved in to a reg with one call - MOVW(reg, val); - if(val.Value & 0xFFFF0000) - MOVT(reg, val, true); -} -// Moves IMM to memory location -void ARMXEmitter::ARMABI_MOVI2M(Operand2 op, Operand2 val) -{ - // This moves imm to a memory location - MOVW(R14, val); MOVT(R14, val, true); - MOVW(R12, op); MOVT(R12, op, true); - STR(R12, R14); // R10 is what we want to store -} + const char *conditions[] = {"EQ", "NEQ", "CS", "CC", "MI", "PL", "VS", "VC", "HI", "LS", "GE", "LT", "GT", "LE", "AL" }; static void ShowCondition(u32 cond) { @@ -102,7 +106,7 @@ void ARMXEmitter::UpdateAPSR(bool NZCVQ, u8 Flags, bool GE, u8 GEval) // Can't update GE with the other ones with a immediate // Got to use a scratch register u32 Imm = (Flags << 27) | ((GEval & 0xF) << 16); - ARMABI_MOVI2R(R14, IMM(Imm)); + ARMABI_MOVI2R(R14, Imm); _MSR(true, true, R14); } else diff --git a/Common/ArmABI.h b/Common/ArmABI.h index 1646d10682..5f27879700 100644 --- a/Common/ArmABI.h +++ b/Common/ArmABI.h @@ -2,7 +2,7 @@ // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 2.0 or later versions. +// the Free Software Foundation, version 2.0. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of diff --git a/Common/ArmCPUDetect.cpp b/Common/ArmCPUDetect.cpp new file mode 100644 index 0000000000..36c5e27411 --- /dev/null +++ b/Common/ArmCPUDetect.cpp @@ -0,0 +1,197 @@ +// Copyright (C) 2003 Dolphin Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official SVN repository and contact information can be found at +// http://code.google.com/p/dolphin-emu/ + +#include "Common.h" +#include "CPUDetect.h" +#include "StringUtil.h" + +// Only Linux platforms have /proc/cpuinfo +#if !defined(BLACKBERRY) && !defined(IOS) && !defined(__SYMBIAN32__) +const char procfile[] = "/proc/cpuinfo"; + +char *GetCPUString() +{ + const char marker[] = "Hardware\t: "; + char *cpu_string = 0; + // Count the number of processor lines in /proc/cpuinfo + char buf[1024]; + FILE *fp; + + fp = fopen(procfile, "r"); + if (!fp) + return 0; + + while (fgets(buf, sizeof(buf), fp)) + { + if (strncmp(buf, marker, sizeof(marker) - 1)) + continue; + cpu_string = buf + sizeof(marker) - 1; + cpu_string = strndup(cpu_string, strlen(cpu_string) - 1); // Strip the newline + // INFO_LOG(BOOT, "CPU: %s", cpu_string); + break; + } + return cpu_string; +} +bool CheckCPUFeature(const char *feature) +{ + const char marker[] = "Features\t: "; + char buf[1024]; + FILE *fp; + + fp = fopen(procfile, "r"); + if (!fp) + return 0; + + while (fgets(buf, sizeof(buf), fp)) + { + if (strncmp(buf, marker, sizeof(marker) - 1)) + continue; + char *featurestring = buf + sizeof(marker) - 1; + char *token = strtok(featurestring, " "); + while (token != NULL) + { + if (strstr(token, feature)) + return true; + token = strtok(NULL, " "); + } + } + return false; +} +#endif +int GetCoreCount() +{ +#ifdef __SYMBIAN32__ + return 1; +#elif defined(BLACKBERRY) || defined(IOS) + return 2; +#else + const char marker[] = "processor\t: "; + int cores = 0; + char buf[1024]; + FILE *fp; + + fp = fopen(procfile, "r"); + if (!fp) + return 0; + + while (fgets(buf, sizeof(buf), fp)) + { + if (strncmp(buf, marker, sizeof(marker) - 1)) + continue; + ++cores; + } + return cores; +#endif +} + +CPUInfo cpu_info; + +CPUInfo::CPUInfo() { + Detect(); +} + +// Detects the various cpu features +void CPUInfo::Detect() +{ + // Set some defaults here + // When ARMv8 cpus come out, these need to be updated. + HTT = false; + OS64bit = false; + CPU64bit = false; + Mode64bit = false; + vendor = VENDOR_ARM; + + // Get the information about the CPU + num_cores = GetCoreCount(); +#if !defined(BLACKBERRY) && !defined(IOS) && !defined(__SYMBIAN32__) + strncpy(cpu_string, GetCPUString(), sizeof(cpu_string)); + bSwp = CheckCPUFeature("swp"); + bHalf = CheckCPUFeature("half"); + bThumb = CheckCPUFeature("thumb"); + bFastMult = CheckCPUFeature("fastmult"); + bVFP = CheckCPUFeature("vfp"); + bEDSP = CheckCPUFeature("edsp"); + bThumbEE = CheckCPUFeature("thumbee"); + bNEON = CheckCPUFeature("neon"); + bVFPv3 = CheckCPUFeature("vfpv3"); + bTLS = CheckCPUFeature("tls"); + bVFPv4 = CheckCPUFeature("vfpv4"); + bIDIVa = CheckCPUFeature("idiva"); + bIDIVt = CheckCPUFeature("idivt"); + // These two require ARMv8 or higher + bFP = CheckCPUFeature("fp"); + bASIMD = CheckCPUFeature("asimd"); +#else + // Hardcode this for now + bSwp = true; + bHalf = true; + bThumb = false; + bFastMult = true; + bVFP = true; + bEDSP = true; +#ifdef __SYMBIAN32__ + bThumbEE = false; + bNEON = false; +#else + bThumbEE = true; + bNEON = true; +#endif + bVFPv3 = true; + bTLS = true; + bVFPv4 = false; + bIDIVa = false; + bIDIVt = false; + bFP = false; + bASIMD = false; +#endif +// On android, we build a separate library for ARMv7 so this is fine. +// TODO: Check for ARMv7 on other platforms. +#if defined(__ARM_ARCH_7A__) + bArmV7 = true; +#else + bArmV7 = false; +#endif +} + +// Turn the cpu info into a string we can show +std::string CPUInfo::Summarize() +{ + std::string sum; +#if defined(BLACKBERRY) || defined(IOS) || defined(__SYMBIAN32__) + sum = StringFromFormat("%i cores", num_cores); +#else + if (num_cores == 1) + sum = StringFromFormat("%s, %i core", cpu_string, num_cores); + else + sum = StringFromFormat("%s, %i cores", cpu_string, num_cores); +#endif + if (bSwp) sum += ", SWP"; + if (bHalf) sum += ", Half"; + if (bThumb) sum += ", Thumb"; + if (bFastMult) sum += ", FastMult"; + if (bVFP) sum += ", VFP"; + if (bEDSP) sum += ", EDSP"; + if (bThumbEE) sum += ", ThumbEE"; + if (bNEON) sum += ", NEON"; + if (bVFPv3) sum += ", VFPv3"; + if (bTLS) sum += ", TLS"; + if (bVFPv4) sum += ", VFPv4"; + if (bIDIVa) sum += ", IDIVa"; + if (bIDIVt) sum += ", IDIVt"; + + return sum; +} diff --git a/Common/ArmEmitter.cpp b/Common/ArmEmitter.cpp index 67f9c2a900..a27d6e82ad 100644 --- a/Common/ArmEmitter.cpp +++ b/Common/ArmEmitter.cpp @@ -2,7 +2,7 @@ // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 2.0 or later versions. +// the Free Software Foundation, version 2.0. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -34,6 +34,93 @@ namespace ArmGen { +inline u32 RotR(u32 a, int amount) { + if (!amount) return a; + return (a >> amount) | (a << (32 - amount)); +} + +inline u32 RotL(u32 a, int amount) { + if (!amount) return a; + return (a << amount) | (a >> (32 - amount)); +} + +bool TryMakeOperand2(u32 imm, Operand2 &op2) { + // Just brute force it. + for (int i = 0; i < 16; i++) { + int mask = RotR(0xFF, i * 2); + if ((imm & mask) == imm) { + op2 = Operand2((u8)(RotL(imm, i * 2)), (u8)i); + return true; + } + } + return false; +} + +bool TryMakeOperand2_AllowInverse(u32 imm, Operand2 &op2, bool *inverse) +{ + if (!TryMakeOperand2(imm, op2)) { + *inverse = true; + return TryMakeOperand2(~imm, op2); + } else { + *inverse = false; + return true; + } +} + +bool TryMakeOperand2_AllowNegation(s32 imm, Operand2 &op2, bool *negated) +{ + if (!TryMakeOperand2(imm, op2)) { + *negated = true; + return TryMakeOperand2(-imm, op2); + } else { + *negated = false; + return true; + } +} + +void ARMXEmitter::ARMABI_MOVI2R(ARMReg reg, u32 val) +{ + Operand2 op2; + bool inverse; + if (TryMakeOperand2_AllowInverse(val, op2, &inverse)) { + if (!inverse) + MOV(reg, op2); + else + MVN(reg, op2); + } else { + if (cpu_info.bArmV7) { + // ARMv7 - can use MOVT/MOVW, best choice + MOVW(reg, val & 0xFFFF); + if(val & 0xFFFF0000) + MOVT(reg, val, true); + } else { + // ARMv6 - fallback sequence. + // TODO: Optimize further. Can for example choose negation etc. + // Literal pools is another way to do this but much more complicated + // so I can't really be bothered for an outdated CPU architecture like ARMv6. + bool first = true; + int shift = 16; + for (int i = 0; i < 4; i++) { + if (val & 0xFF) { + if (first) { + MOV(reg, Operand2((u8)val, (u8)(shift & 0xF))); + first = false; + } else { + ORR(reg, reg, Operand2((u8)val, (u8)(shift & 0xF))); + } + } + shift -= 4; + val >>= 8; + } + } + } +} + +void ARMXEmitter::QuickCallFunction(ARMReg reg, void *func) { + ARMABI_MOVI2R(reg, (u32)(func)); + BL(reg); +} + void ARMXEmitter::SetCodePtr(u8 *ptr) { code = ptr; @@ -68,17 +155,23 @@ const u8 *ARMXEmitter::AlignCodePage() return code; } -void ARMXEmitter::Flush() +void ARMXEmitter::FlushIcache() +{ + FlushIcacheSection(lastCacheFlushEnd, code); + lastCacheFlushEnd = code; +} + +void ARMXEmitter::FlushIcacheSection(u8 *start, u8 *end) { #ifdef __SYMBIAN32__ - User::IMB_Range( startcode, code ); + User::IMB_Range( start, end); #elif defined(BLACKBERRY) - msync(startcode, code-startcode, MS_SYNC | MS_INVALIDATE_ICACHE); + msync(start, end - start, MS_SYNC | MS_INVALIDATE_ICACHE); #else - __builtin___clear_cache (startcode, code); + __builtin___clear_cache (start, end); #endif - SLEEP(0); } + void ARMXEmitter::SetCC(CCFlags cond) { condition = cond << 28; @@ -181,7 +274,7 @@ void ARMXEmitter::B (const void *fnptr) void ARMXEmitter::B(ARMReg src) { - Write32(condition | (18 << 20) | (0xFFF << 8) | (1 << 4) | src); + Write32(condition | 0x12FFF10 | src); } void ARMXEmitter::BL(const void *fnptr) @@ -324,12 +417,28 @@ void ARMXEmitter::WriteInstruction (u32 Op, ARMReg Rd, ARMReg Rn, Operand2 Rm, b break; } } - if (op == -1) + if (op == (u32)-1) _assert_msg_(DYNA_REC, false, "%s not yet support %d", InstNames[Op], Rm.GetType()); Write32(condition | (op << 21) | (SetFlags ? (1 << 20) : 0) | Rn << 16 | Rd << 12 | Data); } // Data Operations +void ARMXEmitter::WriteSignedMultiply(u32 Op, u32 Op2, u32 Op3, ARMReg dest, ARMReg r1, ARMReg r2) +{ + Write32(condition | (0x7 << 24) | (Op << 20) | (dest << 16) | (Op2 << 12) | (r1 << 8) | (Op3 << 5) | (1 << 4) | r2); +} +void ARMXEmitter::UDIV(ARMReg dest, ARMReg dividend, ARMReg divisor) +{ + if (!cpu_info.bIDIVa) + PanicAlert("Trying to use integer divide on hardware that doesn't support it. Bad programmer."); + WriteSignedMultiply(3, 0xF, 0, dest, divisor, dividend); +} +void ARMXEmitter::SDIV(ARMReg dest, ARMReg dividend, ARMReg divisor) +{ + if (!cpu_info.bIDIVa) + PanicAlert("Trying to use integer divide on hardware that doesn't support it. Bad programmer."); + WriteSignedMultiply(1, 0xF, 0, dest, divisor, dividend); +} void ARMXEmitter::LSL (ARMReg dest, ARMReg src, Operand2 op2) { WriteShiftedDataOp(0, false, dest, src, op2);} void ARMXEmitter::LSLS(ARMReg dest, ARMReg src, Operand2 op2) { WriteShiftedDataOp(0, true, dest, src, op2);} void ARMXEmitter::LSL (ARMReg dest, ARMReg src, ARMReg op2) { WriteShiftedDataOp(1, false, dest, src, op2);} @@ -361,13 +470,13 @@ void ARMXEmitter::REV (ARMReg dest, ARMReg src ) Write32(condition | (107 << 20) | (15 << 16) | (dest << 12) | (243 << 4) | src); } -void ARMXEmitter::_MSR (bool nzcvq, bool g, Operand2 op2) +void ARMXEmitter::_MSR (bool write_nzcvq, bool write_g, Operand2 op2) { - Write32(condition | (0x320F << 12) | (nzcvq << 19) | (g << 18) | op2.Imm12Mod()); + Write32(condition | (0x320F << 12) | (write_nzcvq << 19) | (write_g << 18) | op2.Imm12Mod()); } -void ARMXEmitter::_MSR (bool nzcvq, bool g, ARMReg src) +void ARMXEmitter::_MSR (bool write_nzcvq, bool write_g, ARMReg src) { - Write32(condition | (0x120F << 12) | (nzcvq << 19) | (g << 18) | src); + Write32(condition | (0x120F << 12) | (write_nzcvq << 19) | (write_g << 18) | src); } void ARMXEmitter::MRS (ARMReg dest) { @@ -404,8 +513,7 @@ void ARMXEmitter::DMB () void ARMXEmitter::LDR (ARMReg dest, ARMReg src, Operand2 op) { WriteStoreOp(0x41, src, dest, op);} void ARMXEmitter::LDRB(ARMReg dest, ARMReg src, Operand2 op) { WriteStoreOp(0x45, src, dest, op);} -void ARMXEmitter::LDR (ARMReg dest, ARMReg base, ARMReg offset, bool Index, -bool Add) +void ARMXEmitter::LDR (ARMReg dest, ARMReg base, ARMReg offset, bool Index, bool Add) { Write32(condition | (0x61 << 20) | (Index << 24) | (Add << 23) | (base << 16) | (dest << 12) | offset); } @@ -443,20 +551,97 @@ void ARMXEmitter::LDMFD(ARMReg dest, bool WriteBack, const int Regnum, ...) va_end(vl); WriteRegStoreOp(0x89, dest, WriteBack, RegList); } -// helper routines for setting pointers -void ARMXEmitter::CallCdeclFunction3(void* fnptr, u32 arg0, u32 arg1, u32 arg2) + +// NEON and ASIMD +void ARMXEmitter::VLDR(ARMReg Dest, ARMReg Base, Operand2 op) { + _assert_msg_(DYNA_REC, Dest >= S0 && Dest <= D31, "Passed Invalid dest register to VLDR"); + _assert_msg_(DYNA_REC, Base <= R15, "Passed invalid Base register to VLDR"); + bool single_reg = Dest < D0; + if (single_reg) + Dest = (ARMReg)(Dest - S0); + else + Dest = (ARMReg)(Dest - D0); + Write32(NO_COND | (13 << 24) | ((Dest & 0x10) << 18) | (1 << 20) | (Base << 16) \ + | (5 << 9) | (!single_reg << 8) | op.Imm8()); +} +void ARMXEmitter::VMOV(ARMReg Dest, ARMReg Src) +{ + if (Dest > R15) + { + if (Src < S0) + { + if (Dest < D0) + { + // Moving to a Neon register FROM ARM Reg + Dest = (ARMReg)(Dest - S0); + Write32(NO_COND | (0xE0 << 20) | ((Dest & 0x1E) << 15) | (Src << 12) \ + | (0xA << 8) | ((Dest & 0x1) << 7) | (1 << 4)); + return; + } + else + { + // Move 64bit from Arm reg + _assert_msg_(DYNA_REC, false, "This VMOV doesn't support moving 64bit ARM to NEON"); + } + } + } + else + { + if (Src > R15) + { + if (Src < D0) + { + // Moving to ARM Reg from Neon Register + Src = (ARMReg)(Src - S0); + Write32(NO_COND | (0xE1 << 20) | ((Src & 0x1E) << 15) | (Dest << 12) \ + | (0xA << 8) | ((Src & 0x1) << 7) | (1 << 4)); + + return; + } + else + { + // Move 64bit To Arm reg + _assert_msg_(DYNA_REC, false, "This VMOV doesn't support moving 64bit ARM From NEON"); + } + } + else + { + // Move Arm reg to Arm reg + _assert_msg_(DYNA_REC, false, "VMOV doesn't support moving ARM registers"); + } + } + // Moving NEON registers + int SrcSize = Src < D0 ? 1 : Src < Q0 ? 2 : 4; + int DestSize = Dest < D0 ? 1 : Dest < Q0 ? 2 : 4; + bool Single = DestSize == 1; + _assert_msg_(DYNA_REC, SrcSize == DestSize, "VMOV doesn't support moving different register sizes"); + if (Single) + { + Dest = (ARMReg)(Dest - S0); + Src = (ARMReg)(Src - S0); + Write32(NO_COND | (0x1D << 23) | ((Dest & 0x1) << 22) | (0x3 << 20) | ((Dest & 0x1E) << 11) \ + | (0x5 << 9) | (1 << 6) | ((Src & 0x1) << 5) | ((Src & 0x1E) >> 1)); + } + else + { + // Double and quad + bool Quad = DestSize == 4; + if (Quad) + { + // Gets encoded as a Double register + Dest = (ARMReg)((Dest - Q0) * 2); + Src = (ARMReg)((Src - Q0) * 2); + } + else + { + Dest = (ARMReg)(Dest - D0); + Src = (ARMReg)(Src - D0); + } + Write32((0xF2 << 24) | ((Dest & 0x10) << 18) | (1 << 21) | ((Src & 0xF) << 16) \ + | ((Dest & 0xF) << 12) | (1 << 8) | ((Src & 0x10) << 3) | (Quad << 6) \ + | ((Src & 0x10) << 1) | (1 << 4) | (Src & 0xF)); + } } -void ARMXEmitter::CallCdeclFunction4(void* fnptr, u32 arg0, u32 arg1, u32 arg2, u32 arg3) -{ -} - -void ARMXEmitter::CallCdeclFunction5(void* fnptr, u32 arg0, u32 arg1, u32 arg2, u32 arg3, u32 arg4) -{ -} - -void ARMXEmitter::CallCdeclFunction6(void* fnptr, u32 arg0, u32 arg1, u32 arg2, u32 arg3, u32 arg4, u32 arg5) -{ -} } diff --git a/Common/ArmEmitter.h b/Common/ArmEmitter.h index aa1aef6faa..201b3b5c57 100644 --- a/Common/ArmEmitter.h +++ b/Common/ArmEmitter.h @@ -2,7 +2,7 @@ // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 2.0 or later versions. +// the Free Software Foundation, version 2.0. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -26,12 +26,14 @@ #include #endif +#undef _IP #undef R0 #undef _SP +#undef _LR +#undef _PC namespace ArmGen { - enum ARMReg { // GPRs @@ -46,17 +48,21 @@ enum ARMReg // VFP single precision registers - S0 = 0, S1, S2, S3, S4, S5, S6, + S0, S1, S2, S3, S4, S5, S6, S7, S8, S9, S10, S11, S12, S13, S14, S15, S16, S17, S18, S19, S20, S21, S22, S23, S24, S25, S26, S27, S28, S29, S30, S31, // VFP Double Precision registers - D0 = 0, D1, D2, D3, D4, D5, D6, D7, + D0, D1, D2, D3, D4, D5, D6, D7, D8, D9, D10, D11, D12, D13, D14, D15, D16, D17, D18, D19, D20, D21, D22, D23, D24, D25, D26, D27, D28, D29, D30, D31, + + // ASIMD Quad-Word registers + Q0, Q1, Q2, Q3, Q4, Q5, Q6, Q7, + Q8, Q9, Q10, Q11, Q12, Q13, Q14, Q15, INVALID_REG = 0xFFFFFFFF }; @@ -77,19 +83,19 @@ enum CCFlags CC_GT, // Signed greater than CC_LE, // Signed less than or equal CC_AL, // Always (unconditional) 14 - CC_HS = 2, // Alias of CC_CS - CC_LO = 3, // Alias of CC_CC + CC_HS = CC_CS, // Alias of CC_CS Unsigned higher or same + CC_LO = CC_CC, // Alias of CC_CC Unsigned lower }; const u32 NO_COND = 0xE0000000; enum ShiftType { - LSL = 0, - ASL = 0, - LSR = 1, - ASR = 2, - ROR = 3, - RRX = 4 + ST_LSL = 0, + ST_ASL = 0, + ST_LSR = 1, + ST_ASR = 2, + ST_ROR = 3, + ST_RRX = 4 }; enum @@ -108,6 +114,7 @@ enum OpType TYPE_MEM }; +// This is no longer a proper operand2 class. Need to split up. class Operand2 { friend class ARMXEmitter; @@ -151,7 +158,7 @@ public: Operand2(ARMReg base, ShiftType type, ARMReg shift) // RSR { Type = TYPE_RSR; - _assert_msg_(DYNA_REC, type != RRX, "Invalid Operand2: RRX does not take a register shift amount"); + _assert_msg_(DYNA_REC, type != ST_RRX, "Invalid Operand2: RRX does not take a register shift amount"); IndexOrShift = shift; Shift = type; Value = base; @@ -162,31 +169,31 @@ public: if(shift == 32) shift = 0; switch (type) { - case LSL: + case ST_LSL: _assert_msg_(DYNA_REC, shift < 32, "Invalid Operand2: LSL %u", shift); break; - case LSR: + case ST_LSR: _assert_msg_(DYNA_REC, shift <= 32, "Invalid Operand2: LSR %u", shift); if (!shift) - type = LSL; + type = ST_LSL; if (shift == 32) shift = 0; break; - case ASR: + case ST_ASR: _assert_msg_(DYNA_REC, shift < 32, "Invalid Operand2: LSR %u", shift); if (!shift) - type = LSL; + type = ST_LSL; if (shift == 32) shift = 0; break; - case ROR: + case ST_ROR: _assert_msg_(DYNA_REC, shift < 32, "Invalid Operand2: ROR %u", shift); if (!shift) - type = LSL; + type = ST_LSL; break; - case RRX: + case ST_RRX: _assert_msg_(DYNA_REC, shift == 0, "Invalid Operand2: RRX does not take an immediate shift amount"); - type = ROR; + type = ST_ROR; break; } IndexOrShift = shift; @@ -198,17 +205,17 @@ public: { switch(Type) { - case TYPE_IMM: + case TYPE_IMM: return Imm12Mod(); // This'll need to be changed later - case TYPE_REG: + case TYPE_REG: return Rm(); - case TYPE_IMMSREG: + case TYPE_IMMSREG: return IMMSR(); - case TYPE_RSR: + case TYPE_RSR: return RSR(); - default: - _assert_msg_(DYNA_REC, false, "GetData with Invalid Type"); - break; + default: + _assert_msg_(DYNA_REC, false, "GetData with Invalid Type"); + return 0; } } const u32 IMMSR() // IMM shifted register @@ -232,6 +239,11 @@ public: _assert_msg_(DYNA_REC, (Type == TYPE_IMM), "Imm5 not IMM value"); return ((Value & 0x0000001F) << 7); } + const u32 Imm8() + { + _assert_msg_(DYNA_REC, (Type == TYPE_IMM), "Imm8Rot not IMM value"); + return Value & 0xFF; + } const u32 Imm8Rot() // IMM8 with Rotation { _assert_msg_(DYNA_REC, (Type == TYPE_IMM), "Imm8Rot not IMM value"); @@ -273,16 +285,30 @@ public: _assert_msg_(DYNA_REC, (Type == TYPE_IMM), "Imm16 not IMM"); return (Value & 0x0FFFFFFF); } - - + // NEON and ASIMD specific + const u32 Imm8ASIMD() + { + _assert_msg_(DYNA_REC, (Type == TYPE_IMM), "Imm8ASIMD not IMM"); + return ((Value & 0x80) << 17) | ((Value & 0x70) << 12) | (Value & 0xF); + } + const u32 Imm8VFP() + { + _assert_msg_(DYNA_REC, (Type == TYPE_IMM), "Imm8VFP not IMM"); + return ((Value & 0xF0) << 12) | (Value & 0xF); + } }; + +// Use these when you don't know if an imm can be represented as an operand2. +// This lets you generate both an optimal and a fallback solution by checking +// the return value, which will be false if these fail to find a Operand2 that +// represents your 32-bit imm value. +bool TryMakeOperand2(u32 imm, Operand2 &op2); +bool TryMakeOperand2_AllowInverse(u32 imm, Operand2 &op2, bool *inverse); +bool TryMakeOperand2_AllowNegation(s32 imm, Operand2 &op2, bool *negated); + inline Operand2 R(ARMReg Reg) { return Operand2(Reg, TYPE_REG); } inline Operand2 IMM(u32 Imm) { return Operand2(Imm, TYPE_IMM); } inline Operand2 Mem(void *ptr) { return Operand2((u32)ptr, TYPE_IMM); } -//usage: int a[]; ARRAY_OFFSET(a,10) -#define ARRAY_OFFSET(array,index) ((u32)((u64)&(array)[index]-(u64)&(array)[0])) -//usage: struct {int e;} s; STRUCT_OFFSET(s,e) -#define STRUCT_OFFSET(str,elem) ((u32)((u64)&(str).elem-(u64)&(str))) struct FixupBranch { @@ -298,12 +324,14 @@ class ARMXEmitter friend struct OpArg; // for Write8 etc private: u8 *code, *startcode; + u8 *lastCacheFlushEnd; u32 condition; void WriteStoreOp(u32 op, ARMReg dest, ARMReg src, Operand2 op2); void WriteRegStoreOp(u32 op, ARMReg dest, bool WriteBack, u16 RegList); void WriteShiftedDataOp(u32 op, bool SetFlags, ARMReg dest, ARMReg src, ARMReg op2); void WriteShiftedDataOp(u32 op, bool SetFlags, ARMReg dest, ARMReg src, Operand2 op2); + void WriteSignedMultiply(u32 Op, u32 Op2, u32 Op3, ARMReg dest, ARMReg r1, ARMReg r2); // New Ops void WriteInstruction(u32 op, ARMReg Rd, ARMReg Rn, Operand2 Rm, bool SetFlags = false); @@ -312,8 +340,15 @@ protected: inline void Write32(u32 value) {*(u32*)code = value; code+=4;} public: - ARMXEmitter() { code = NULL; startcode = NULL; condition = CC_AL << 28;} - ARMXEmitter(u8 *code_ptr) { code = code_ptr; startcode = code_ptr; condition = CC_AL << 28;} + ARMXEmitter() : code(0), startcode(0), lastCacheFlushEnd(0) { + condition = CC_AL << 28; + } + ARMXEmitter(u8 *code_ptr) { + code = code_ptr; + lastCacheFlushEnd = code_ptr; + startcode = code_ptr; + condition = CC_AL << 28; + } virtual ~ARMXEmitter() {} void SetCodePtr(u8 *ptr); @@ -321,7 +356,8 @@ public: const u8 *AlignCode16(); const u8 *AlignCodePage(); const u8 *GetCodePtr() const; - void Flush(); + void FlushIcache(); + void FlushIcacheSection(u8 *start, u8 *end); u8 *GetWritableCodePtr(); void SetCC(CCFlags cond = CC_AL); @@ -378,7 +414,7 @@ public: void LSLS(ARMReg dest, ARMReg src, ARMReg op2); void SBC (ARMReg dest, ARMReg src, Operand2 op2); void SBCS(ARMReg dest, ARMReg src, Operand2 op2); - void REV (ARMReg dest, ARMReg src ); + void REV (ARMReg dest, ARMReg src); void RSC (ARMReg dest, ARMReg src, Operand2 op2); void RSCS(ARMReg dest, ARMReg src, Operand2 op2); void TST ( ARMReg src, Operand2 op2); @@ -389,22 +425,26 @@ public: void ORRS(ARMReg dest, ARMReg src, Operand2 op2); void MOV (ARMReg dest, Operand2 op2); void MOVS(ARMReg dest, Operand2 op2); - void BIC (ARMReg dest, ARMReg src, Operand2 op2); + void BIC (ARMReg dest, ARMReg src, Operand2 op2); // BIC = ANDN void BICS(ARMReg dest, ARMReg src, Operand2 op2); void MVN (ARMReg dest, Operand2 op2); void MVNS(ARMReg dest, Operand2 op2); void MOVW(ARMReg dest, Operand2 op2); void MOVT(ARMReg dest, Operand2 op2, bool TopBits = false); + // UDIV and SDIV are only available on CPUs that have + // the idiva hardare capacity + void UDIV(ARMReg dest, ARMReg dividend, ARMReg divisor); + void SDIV(ARMReg dest, ARMReg dividend, ARMReg divisor); void MUL (ARMReg dest, ARMReg src, ARMReg op2); void MULS(ARMReg dest, ARMReg src, ARMReg op2); void SXTB(ARMReg dest, ARMReg op2); void SXTH(ARMReg dest, ARMReg op2, u8 rotation = 0); void SXTAH(ARMReg dest, ARMReg src, ARMReg op2, u8 rotation = 0); - // Using just MSR here messes with our defines on the PPC side of stuff + // Using just MSR here messes with our defines on the PPC side of stuff (when this code was in dolphin...) // Just need to put an underscore here, bit annoying. - void _MSR (bool nzcvq, bool g, Operand2 op2); + void _MSR (bool nzcvq, bool g, Operand2 op2); void _MSR (bool nzcvq, bool g, ARMReg src ); void MRS (ARMReg dest); @@ -426,34 +466,23 @@ public: // dest contains the result if the instruction managed to store the value void STREX(ARMReg dest, ARMReg base, ARMReg op); void DMB (); - + + // NEON and ASIMD instructions + // None of these will be created with conditional since ARM + // is deprecating conditional execution of ASIMD instructions. + // Some ASIMD instructions don't even have a conditional encoding. + + void VLDR(ARMReg dest, ARMReg Base, Operand2 op); + void VMOV(ARMReg Dest, ARMReg Src); + + void QuickCallFunction(ARMReg scratchreg, void *func); // Utility functions - // The difference between this and CALL is that this aligns the stack - // where appropriate. - void ARMABI_CallFunction(void *func); - void ARMABI_CallFunctionC(void *func, u32 Arg0); - void ARMABI_CallFunctionCC(void *func, u32 Arg1, u32 Arg2); - void ARMABI_PushAllCalleeSavedRegsAndAdjustStack(); - void ARMABI_PopAllCalleeSavedRegsAndAdjustStack(); - void ARMABI_MOVI2R(ARMReg reg, Operand2 val); - void ARMABI_MOVI2M(Operand2 op, Operand2 val); + void ARMABI_MOVI2R(ARMReg reg, u32 val); void ARMABI_ShowConditions(); + void ARMABI_Return(); void UpdateAPSR(bool NZCVQ, u8 Flags, bool GE, u8 GEval); - - // Strange call wrappers. - void CallCdeclFunction3(void* fnptr, u32 arg0, u32 arg1, u32 arg2); - void CallCdeclFunction4(void* fnptr, u32 arg0, u32 arg1, u32 arg2, u32 arg3); - void CallCdeclFunction5(void* fnptr, u32 arg0, u32 arg1, u32 arg2, u32 arg3, u32 arg4); - void CallCdeclFunction6(void* fnptr, u32 arg0, u32 arg1, u32 arg2, u32 arg3, u32 arg4, u32 arg5); - #define CallCdeclFunction3_I(a,b,c,d) CallCdeclFunction3((void *)(a), (b), (c), (d)) - #define CallCdeclFunction4_I(a,b,c,d,e) CallCdeclFunction4((void *)(a), (b), (c), (d), (e)) - #define CallCdeclFunction5_I(a,b,c,d,e,f) CallCdeclFunction5((void *)(a), (b), (c), (d), (e), (f)) - #define CallCdeclFunction6_I(a,b,c,d,e,f,g) CallCdeclFunction6((void *)(a), (b), (c), (d), (e), (f), (g)) - - #define DECLARE_IMPORT(x) - }; // class ARMXEmitter diff --git a/Common/ThunkARM.cpp b/Common/ArmThunk.cpp similarity index 94% rename from Common/ThunkARM.cpp rename to Common/ArmThunk.cpp index b470f868e8..98cee1683d 100644 --- a/Common/ThunkARM.cpp +++ b/Common/ArmThunk.cpp @@ -2,7 +2,7 @@ // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 2.0 or later versions. +// the Free Software Foundation, version 2.0. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of diff --git a/Common/CPUDetect.cpp b/Common/CPUDetect.cpp index 8c964c4f6b..c43c7fc5d7 100644 --- a/Common/CPUDetect.cpp +++ b/Common/CPUDetect.cpp @@ -2,7 +2,7 @@ // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 2.0 or later versions. +// the Free Software Foundation, version 2.0. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -29,7 +29,9 @@ #undef _interlockedbittestandreset64 #else +#ifndef _M_GENERIC #include +#endif #if defined __FreeBSD__ #include @@ -38,7 +40,9 @@ static inline void do_cpuid(unsigned int *eax, unsigned int *ebx, unsigned int *ecx, unsigned int *edx) { -#ifdef _LP64 +#if defined _M_GENERIC + (*eax) = (*ebx) = (*ecx) = (*edx) = 0; +#elif defined _LP64 // Note: EBX is reserved on Mac OS X and in PIC on Linux, so it has to // restored at the end of the asm block. __asm__ ( @@ -92,10 +96,6 @@ CPUInfo::CPUInfo() { Detect(); } -#ifdef _WIN32 -#include -#endif - // Detects the various cpu features void CPUInfo::Detect() { diff --git a/Common/CPUDetect.h b/Common/CPUDetect.h index 5b98ab4805..4ca4712bb6 100644 --- a/Common/CPUDetect.h +++ b/Common/CPUDetect.h @@ -2,7 +2,7 @@ // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 2.0 or later versions. +// the Free Software Foundation, version 2.0. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -25,7 +25,8 @@ enum CPUVendor { VENDOR_INTEL = 0, VENDOR_AMD = 1, - VENDOR_OTHER = 2, + VENDOR_ARM = 2, + VENDOR_OTHER = 3, }; struct CPUInfo @@ -55,6 +56,26 @@ struct CPUInfo bool bAES; bool bLAHFSAHF64; bool bLongMode; + + // ARM specific CPUInfo + bool bSwp; + bool bHalf; + bool bThumb; + bool bFastMult; + bool bVFP; + bool bEDSP; + bool bThumbEE; + bool bNEON; + bool bVFPv3; + bool bTLS; + bool bVFPv4; + bool bIDIVa; + bool bIDIVt; + bool bArmV7; // enable MOVT, MOVW etc + + // ARMv8 specific + bool bFP; + bool bASIMD; // Call Detect() explicit CPUInfo(); diff --git a/Common/Common.vcxproj b/Common/Common.vcxproj index 8bc466d54b..4c8a256ba4 100644 --- a/Common/Common.vcxproj +++ b/Common/Common.vcxproj @@ -140,12 +140,6 @@ - - true - true - true - true - true true @@ -195,7 +189,7 @@ - + true true true diff --git a/Common/Common.vcxproj.filters b/Common/Common.vcxproj.filters index a9b4e859bc..3e2c3b50bc 100644 --- a/Common/Common.vcxproj.filters +++ b/Common/Common.vcxproj.filters @@ -42,7 +42,6 @@ - @@ -70,8 +69,8 @@ - + diff --git a/Common/LogManager.cpp b/Common/LogManager.cpp index 34523616de..eae0a62e26 100644 --- a/Common/LogManager.cpp +++ b/Common/LogManager.cpp @@ -152,6 +152,8 @@ void LogManager::LoadConfig(IniFile::Section *section) void LogManager::Log(LogTypes::LOG_LEVELS level, LogTypes::LOG_TYPE type, const char *file, int line, const char *format, va_list args) { + std::lock_guard lk(m_log_lock); + char temp[MAX_MSGLEN]; char msg[MAX_MSGLEN * 2]; LogContainer *log = m_Log[type]; diff --git a/Common/LogManager.h b/Common/LogManager.h index 376fc8a6b7..8e5e37d17b 100644 --- a/Common/LogManager.h +++ b/Common/LogManager.h @@ -106,6 +106,7 @@ private: ConsoleListener *m_consoleLog; DebuggerLogListener *m_debuggerLog; static LogManager *m_logManager; // Singleton. Ugh. + std::mutex m_log_lock; LogManager(); ~LogManager(); diff --git a/Common/MemArena.cpp b/Common/MemArena.cpp index f7ad48565c..7487a6a53a 100644 --- a/Common/MemArena.cpp +++ b/Common/MemArena.cpp @@ -243,13 +243,13 @@ u8* MemArena::Find4GBBase() // We are unable to use relative addresses due to lack of mmap() return NULL; #else - void* base = mmap(0, 0x5000000, PROT_READ | PROT_WRITE, + void* base = mmap(0, 0x10000000, PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED, -1, 0); if (base == MAP_FAILED) { - PanicAlert("Failed to map 100 MB of memory space: %s", strerror(errno)); + PanicAlert("Failed to map 256 MB of memory space: %s", strerror(errno)); return 0; } - munmap(base, 0x5000000); + munmap(base, 0x10000000); return static_cast(base); #endif #endif diff --git a/Common/x64Emitter.cpp b/Common/x64Emitter.cpp index 7c0d88320c..a423f03762 100644 --- a/Common/x64Emitter.cpp +++ b/Common/x64Emitter.cpp @@ -1465,184 +1465,6 @@ void XEmitter::FWAIT() } void XEmitter::RTDSC() { Write8(0x0F); Write8(0x31); } - -// helper routines for setting pointers -void XEmitter::CallCdeclFunction3(void* fnptr, u32 arg0, u32 arg1, u32 arg2) -{ - using namespace Gen; -#ifdef _M_X64 -#ifdef _MSC_VER - MOV(32, R(RCX), Imm32(arg0)); - MOV(32, R(RDX), Imm32(arg1)); - MOV(32, R(R8), Imm32(arg2)); - CALL(fnptr); -#else - MOV(32, R(RDI), Imm32(arg0)); - MOV(32, R(RSI), Imm32(arg1)); - MOV(32, R(RDX), Imm32(arg2)); - CALL(fnptr); -#endif +} // Gen -#else - ABI_AlignStack(3 * 4); - PUSH(32, Imm32(arg2)); - PUSH(32, Imm32(arg1)); - PUSH(32, Imm32(arg0)); - CALL(fnptr); -#ifdef _WIN32 - // don't inc stack -#else - ABI_RestoreStack(3 * 4); -#endif -#endif -} - -void XEmitter::CallCdeclFunction4(void* fnptr, u32 arg0, u32 arg1, u32 arg2, u32 arg3) -{ - using namespace Gen; -#ifdef _M_X64 - -#ifdef _MSC_VER - MOV(32, R(RCX), Imm32(arg0)); - MOV(32, R(RDX), Imm32(arg1)); - MOV(32, R(R8), Imm32(arg2)); - MOV(32, R(R9), Imm32(arg3)); - CALL(fnptr); -#else - MOV(32, R(RDI), Imm32(arg0)); - MOV(32, R(RSI), Imm32(arg1)); - MOV(32, R(RDX), Imm32(arg2)); - MOV(32, R(RCX), Imm32(arg3)); - CALL(fnptr); -#endif - -#else - ABI_AlignStack(4 * 4); - PUSH(32, Imm32(arg3)); - PUSH(32, Imm32(arg2)); - PUSH(32, Imm32(arg1)); - PUSH(32, Imm32(arg0)); - CALL(fnptr); -#ifdef _WIN32 - // don't inc stack -#else - ABI_RestoreStack(4 * 4); -#endif -#endif -} - -void XEmitter::CallCdeclFunction5(void* fnptr, u32 arg0, u32 arg1, u32 arg2, u32 arg3, u32 arg4) -{ - using namespace Gen; -#ifdef _M_X64 - -#ifdef _MSC_VER - MOV(32, R(RCX), Imm32(arg0)); - MOV(32, R(RDX), Imm32(arg1)); - MOV(32, R(R8), Imm32(arg2)); - MOV(32, R(R9), Imm32(arg3)); - MOV(32, MDisp(RSP, 0x20), Imm32(arg4)); - CALL(fnptr); -#else - MOV(32, R(RDI), Imm32(arg0)); - MOV(32, R(RSI), Imm32(arg1)); - MOV(32, R(RDX), Imm32(arg2)); - MOV(32, R(RCX), Imm32(arg3)); - MOV(32, R(R8), Imm32(arg4)); - CALL(fnptr); -#endif - -#else - ABI_AlignStack(5 * 4); - PUSH(32, Imm32(arg4)); - PUSH(32, Imm32(arg3)); - PUSH(32, Imm32(arg2)); - PUSH(32, Imm32(arg1)); - PUSH(32, Imm32(arg0)); - CALL(fnptr); -#ifdef _WIN32 - // don't inc stack -#else - ABI_RestoreStack(5 * 4); -#endif -#endif -} - -void XEmitter::CallCdeclFunction6(void* fnptr, u32 arg0, u32 arg1, u32 arg2, u32 arg3, u32 arg4, u32 arg5) -{ - using namespace Gen; -#ifdef _M_X64 - -#ifdef _MSC_VER - MOV(32, R(RCX), Imm32(arg0)); - MOV(32, R(RDX), Imm32(arg1)); - MOV(32, R(R8), Imm32(arg2)); - MOV(32, R(R9), Imm32(arg3)); - MOV(32, MDisp(RSP, 0x20), Imm32(arg4)); - MOV(32, MDisp(RSP, 0x28), Imm32(arg5)); - CALL(fnptr); -#else - MOV(32, R(RDI), Imm32(arg0)); - MOV(32, R(RSI), Imm32(arg1)); - MOV(32, R(RDX), Imm32(arg2)); - MOV(32, R(RCX), Imm32(arg3)); - MOV(32, R(R8), Imm32(arg4)); - MOV(32, R(R9), Imm32(arg5)); - CALL(fnptr); -#endif - -#else - ABI_AlignStack(6 * 4); - PUSH(32, Imm32(arg5)); - PUSH(32, Imm32(arg4)); - PUSH(32, Imm32(arg3)); - PUSH(32, Imm32(arg2)); - PUSH(32, Imm32(arg1)); - PUSH(32, Imm32(arg0)); - CALL(fnptr); -#ifdef _WIN32 - // don't inc stack -#else - ABI_RestoreStack(6 * 4); -#endif -#endif -} - -#ifdef _M_X64 - -// See header -void XEmitter::___CallCdeclImport3(void* impptr, u32 arg0, u32 arg1, u32 arg2) { - MOV(32, R(RCX), Imm32(arg0)); - MOV(32, R(RDX), Imm32(arg1)); - MOV(32, R(R8), Imm32(arg2)); - CALLptr(M(impptr)); -} -void XEmitter::___CallCdeclImport4(void* impptr, u32 arg0, u32 arg1, u32 arg2, u32 arg3) { - MOV(32, R(RCX), Imm32(arg0)); - MOV(32, R(RDX), Imm32(arg1)); - MOV(32, R(R8), Imm32(arg2)); - MOV(32, R(R9), Imm32(arg3)); - CALLptr(M(impptr)); -} -void XEmitter::___CallCdeclImport5(void* impptr, u32 arg0, u32 arg1, u32 arg2, u32 arg3, u32 arg4) { - MOV(32, R(RCX), Imm32(arg0)); - MOV(32, R(RDX), Imm32(arg1)); - MOV(32, R(R8), Imm32(arg2)); - MOV(32, R(R9), Imm32(arg3)); - MOV(32, MDisp(RSP, 0x20), Imm32(arg4)); - CALLptr(M(impptr)); -} -void XEmitter::___CallCdeclImport6(void* impptr, u32 arg0, u32 arg1, u32 arg2, u32 arg3, u32 arg4, u32 arg5) { - MOV(32, R(RCX), Imm32(arg0)); - MOV(32, R(RDX), Imm32(arg1)); - MOV(32, R(R8), Imm32(arg2)); - MOV(32, R(R9), Imm32(arg3)); - MOV(32, MDisp(RSP, 0x20), Imm32(arg4)); - MOV(32, MDisp(RSP, 0x28), Imm32(arg5)); - CALLptr(M(impptr)); -} - -#endif - -} diff --git a/Common/x64Emitter.h b/Common/x64Emitter.h index 816a82e90a..21a1deaeae 100644 --- a/Common/x64Emitter.h +++ b/Common/x64Emitter.h @@ -687,43 +687,6 @@ public: #else inline int ABI_GetNumXMMRegs() { return 16; } #endif - - // Strange call wrappers. - void CallCdeclFunction3(void* fnptr, u32 arg0, u32 arg1, u32 arg2); - void CallCdeclFunction4(void* fnptr, u32 arg0, u32 arg1, u32 arg2, u32 arg3); - void CallCdeclFunction5(void* fnptr, u32 arg0, u32 arg1, u32 arg2, u32 arg3, u32 arg4); - void CallCdeclFunction6(void* fnptr, u32 arg0, u32 arg1, u32 arg2, u32 arg3, u32 arg4, u32 arg5); - -#if defined(_M_IX86) - - #define CallCdeclFunction3_I(a,b,c,d) CallCdeclFunction3((void *)(a), (b), (c), (d)) - #define CallCdeclFunction4_I(a,b,c,d,e) CallCdeclFunction4((void *)(a), (b), (c), (d), (e)) - #define CallCdeclFunction5_I(a,b,c,d,e,f) CallCdeclFunction5((void *)(a), (b), (c), (d), (e), (f)) - #define CallCdeclFunction6_I(a,b,c,d,e,f,g) CallCdeclFunction6((void *)(a), (b), (c), (d), (e), (f), (g)) - - #define DECLARE_IMPORT(x) - -#else - - // Comments from VertexLoader.cpp about these horrors: - - // This is a horrible hack that is necessary in 64-bit mode because Opengl32.dll is based way, way above the 32-bit - // address space that is within reach of a CALL, and just doing &fn gives us these high uncallable addresses. So we - // want to grab the function pointers from the import table instead. - - void ___CallCdeclImport3(void* impptr, u32 arg0, u32 arg1, u32 arg2); - void ___CallCdeclImport4(void* impptr, u32 arg0, u32 arg1, u32 arg2, u32 arg3); - void ___CallCdeclImport5(void* impptr, u32 arg0, u32 arg1, u32 arg2, u32 arg3, u32 arg4); - void ___CallCdeclImport6(void* impptr, u32 arg0, u32 arg1, u32 arg2, u32 arg3, u32 arg4, u32 arg5); - - #define CallCdeclFunction3_I(a,b,c,d) ___CallCdeclImport3(&__imp_##a,b,c,d) - #define CallCdeclFunction4_I(a,b,c,d,e) ___CallCdeclImport4(&__imp_##a,b,c,d,e) - #define CallCdeclFunction5_I(a,b,c,d,e,f) ___CallCdeclImport5(&__imp_##a,b,c,d,e,f) - #define CallCdeclFunction6_I(a,b,c,d,e,f,g) ___CallCdeclImport6(&__imp_##a,b,c,d,e,f,g) - - #define DECLARE_IMPORT(x) extern "C" void *__imp_##x - -#endif }; // class XEmitter diff --git a/Core/CPU.cpp b/Core/CPU.cpp index a0abe65ff6..923971085e 100644 --- a/Core/CPU.cpp +++ b/Core/CPU.cpp @@ -20,6 +20,6 @@ // See the MIPS folder, the real stuff is there. -CPU *currentCPU = 0; +MIPSState *currentCPU = 0; int numCPUs; diff --git a/Core/CPU.h b/Core/CPU.h index 06ce01a3e4..ebc28caffb 100644 --- a/Core/CPU.h +++ b/Core/CPU.h @@ -28,24 +28,9 @@ enum GPR_SIZE_64=1 }; -enum CPUType -{ - CPUTYPE_INVALID=0, - CPUTYPE_ALLEGREX=1, - CPUTYPE_ME=2, -}; - -class CPU -{ -private: - u32 id; -public: - virtual void SingleStep() = 0; - virtual int RunLoopUntil(u64 globalTicks) = 0; -}; - #define MAX_NUM_CPU 2 +class MIPSState; -extern CPU *currentCPU; +extern MIPSState *currentCPU; extern int numCPUs; diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 8e3574417c..142f3b401a 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -116,6 +116,7 @@ + @@ -185,49 +186,55 @@ - + true true true true - + true true true true - + true true true true - + true true true true - + true true true true - + true true true true - + true true true true - + + true + true + true + true + + true true true @@ -262,6 +269,7 @@ + @@ -338,25 +346,25 @@ - + true true true true - + true true true true - + true true true true - + true true true @@ -409,4 +417,4 @@ - + \ No newline at end of file diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index c0c03cd556..c7baaebccd 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -111,30 +111,6 @@ MIPS\x86 - - MIPS\ARM - - - MIPS\ARM - - - MIPS\ARM - - - MIPS\ARM - - - MIPS\ARM - - - MIPS\ARM - - - MIPS\ARM - - - MIPS\ARM - MIPS\JitCommon @@ -363,6 +339,36 @@ Ext\Snappy + + MIPS\ARM + + + MIPS\ARM + + + MIPS\ARM + + + MIPS\ARM + + + MIPS\ARM + + + MIPS\ARM + + + MIPS\ARM + + + MIPS\ARM + + + MIPS\ARM + + + Ext + @@ -413,18 +419,6 @@ MIPS\x86 - - MIPS\ARM - - - MIPS\ARM - - - MIPS\ARM - - - MIPS\ARM - MIPS\JitCommon @@ -674,6 +668,21 @@ Ext\Snappy + + MIPS\ARM + + + MIPS\ARM + + + MIPS\ARM + + + MIPS\ARM + + + Ext + @@ -681,4 +690,4 @@ - + \ No newline at end of file diff --git a/Core/CoreTiming.cpp b/Core/CoreTiming.cpp index 70a9431f5c..d3ec3c92f3 100644 --- a/Core/CoreTiming.cpp +++ b/Core/CoreTiming.cpp @@ -67,7 +67,9 @@ Event *eventPool = 0; Event *eventTsPool = 0; int allocatedTsEvents = 0; -int downcount, slicelength; +// Downcount has been moved to currentMIPS, to save a couple of clocks in every ARM JIT block +// as we can already reach that structure through a register. +int slicelength; s64 globalTimer; s64 idledCycles; @@ -153,7 +155,7 @@ void UnregisterAllEvents() void Init() { - downcount = INITIAL_SLICE_LENGTH; + currentMIPS->downcount = INITIAL_SLICE_LENGTH; slicelength = INITIAL_SLICE_LENGTH; globalTimer = 0; idledCycles = 0; @@ -183,7 +185,7 @@ void Shutdown() u64 GetTicks() { - return (u64)globalTimer + slicelength - downcount; + return (u64)globalTimer + slicelength - currentMIPS->downcount; } u64 GetIdleTicks() @@ -462,23 +464,23 @@ void MoveEvents() void Advance() { - int cyclesExecuted = slicelength - downcount; + int cyclesExecuted = slicelength - currentMIPS->downcount; globalTimer += cyclesExecuted; - downcount = slicelength; + currentMIPS->downcount = slicelength; ProcessFifoWaitEvents(); if (!first) { - // WARN_LOG(CPU, "WARNING - no events in queue. Setting downcount to 10000"); - downcount += 10000; + // WARN_LOG(CPU, "WARNING - no events in queue. Setting currentMIPS->downcount to 10000"); + currentMIPS->downcount += 10000; } else { slicelength = (int)(first->time - globalTimer); if (slicelength > MAX_SLICE_LENGTH) slicelength = MAX_SLICE_LENGTH; - downcount = slicelength; + currentMIPS->downcount = slicelength; } if (advanceCallback) advanceCallback(cyclesExecuted); @@ -496,13 +498,13 @@ void LogPendingEvents() void Idle(int maxIdle) { - int cyclesDown = downcount; + int cyclesDown = currentMIPS->downcount; if (maxIdle != 0 && cyclesDown > maxIdle) cyclesDown = maxIdle; if (first && cyclesDown > 0) { - int cyclesExecuted = slicelength - downcount; + int cyclesExecuted = slicelength - currentMIPS->downcount; int cyclesNextEvent = (int) (first->time - globalTimer); if (cyclesNextEvent < cyclesExecuted + cyclesDown) @@ -517,9 +519,9 @@ void Idle(int maxIdle) DEBUG_LOG(CPU, "Idle for %i cycles! (%f ms)", cyclesDown, cyclesDown / (float)(CPU_HZ * 0.001f)); idledCycles += cyclesDown; - downcount -= cyclesDown; - if (downcount == 0) - downcount = -1; + currentMIPS->downcount -= cyclesDown; + if (currentMIPS->downcount == 0) + currentMIPS->downcount = -1; } std::string GetScheduledEventsSummary() @@ -561,7 +563,6 @@ void DoState(PointerWrap &p) p.DoLinkedList(tsFirst, &tsLast); p.Do(CPU_HZ); - p.Do(downcount); p.Do(slicelength); p.Do(globalTimer); p.Do(idledCycles); diff --git a/Core/CoreTiming.h b/Core/CoreTiming.h index bed8aa7a46..0858982683 100644 --- a/Core/CoreTiming.h +++ b/Core/CoreTiming.h @@ -119,7 +119,6 @@ namespace CoreTiming void SetClockFrequencyMHz(int cpuMhz); int GetClockFrequencyMHz(); - extern int downcount; extern int slicelength; }; // end of namespace diff --git a/Core/HLE/sceKernelThread.cpp b/Core/HLE/sceKernelThread.cpp index 62d64a9534..2f6788b274 100644 --- a/Core/HLE/sceKernelThread.cpp +++ b/Core/HLE/sceKernelThread.cpp @@ -2084,7 +2084,7 @@ ActionAfterMipsCall *Thread::getRunningCallbackAction() if (this->GetUID() == currentThread && g_inCbCount > 0) { MipsCall *call = mipsCalls.get(this->currentCallbackId); - ActionAfterMipsCall *action; + ActionAfterMipsCall *action = 0; if (call) action = dynamic_cast(call->doAfter); diff --git a/Core/HLE/sceMpeg.cpp b/Core/HLE/sceMpeg.cpp index 978a228c24..0519e3dc24 100644 --- a/Core/HLE/sceMpeg.cpp +++ b/Core/HLE/sceMpeg.cpp @@ -782,7 +782,7 @@ u32 sceMpegUnRegistStream(u32 mpeg, int streamUid) return -1; } - StreamInfo info; + StreamInfo info = {0}; switch (info.type) { case MPEG_AVC_STREAM: @@ -1119,7 +1119,8 @@ u32 sceMpegChangeGetAuMode(u32 mpeg, int streamUid, int mode) return -1; } - StreamInfo info; + // NOTE: Where is the info supposed to come from? + StreamInfo info = {0}; info.sid = streamUid; if (info.sid) { switch (info.type) { diff --git a/Core/MIPS/ARM/ArmAsm.cpp b/Core/MIPS/ARM/ArmAsm.cpp new file mode 100644 index 0000000000..c8cfcaca64 --- /dev/null +++ b/Core/MIPS/ARM/ArmAsm.cpp @@ -0,0 +1,184 @@ +// Copyright (c) 2012- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include "ArmEmitter.h" + +#include "../../MemMap.h" + +#include "../MIPS.h" +#include "../../CoreTiming.h" +#include "MemoryUtil.h" + +#include "ArmJit.h" +#include "../JitCommon/JitCommon.h" +#include "../../Core.h" +#include "ArmAsm.h" + +using namespace ArmGen; + +//static int temp32; // unused? + +//TODO - make an option +//#if _DEBUG +static bool enableDebug = false; +//#else +// bool enableDebug = false; +//#endif + +//static bool enableStatistics = false; //unused? + +//The standard ARM calling convention allocates the 16 ARM registers as: + +// r15 is the program counter. +// r14 is the link register. (The BL instruction, used in a subroutine call, stores the return address in this register). +// r13 is the stack pointer. (The Push/Pop instructions in "Thumb" operating mode use this register only). +// r12 is the Intra-Procedure-call scratch register. +// r4 to r11: used to hold local variables. +// r0 to r3: used to hold argument values passed to a subroutine, and also hold results returned from a subroutine. + +// R2, R3, R4, R5, R6, R7, R8, R10, R11 // omitting R9? + + +// STATIC ALLOCATION ARM: +// R11 : Memory base pointer. +// R10 : MIPS state +// R9 : +extern volatile CoreState coreState; + +void JitAt() +{ + MIPSComp::jit->Compile(currentMIPS->pc); +} + +/* +double testD; + +u32 DoubleTest(u32 sp) { + volatile double local = 1.0; + testD += local; + return (u32)(&local); +} + +void ShowPC(u32 sp) { + if (currentMIPS) { + ERROR_LOG(HLE, "ShowPC : %08x ArmSP : %08x", currentMIPS->pc, sp); + } else { + ERROR_LOG(HLE, "Universe corrupt?"); + } +}*/ + +void DisassembleArm(const u8 *data, int size); + +// PLAN: no more block numbers - crazy opcodes just contain offset within +// dynarec buffer +// At this offset - 4, there is an int specifying the block number. + +namespace MIPSComp { + +void Jit::GenerateFixedCode() +{ + enterCode = AlignCode16(); + + INFO_LOG(HLE, "Base: %08x", (u32)Memory::base); + + SetCC(CC_AL); + + PUSH(9, R4, R5, R6, R7, R8, R9, R10, R11, _LR); + // Take care to 8-byte align stack for function calls. + // This actually misaligns the stack within the JIT itself but that doesn't really matter + // as the JIT does not use the stack at all. + SUB(_SP, _SP, 4); + + // Fixed registers, these are always kept when in Jit context. + // R13 cannot be used as it's the stack pointer. + ARMABI_MOVI2R(R11, (u32)Memory::base); + ARMABI_MOVI2R(R10, (u32)mips_); + ARMABI_MOVI2R(R9, (u32)GetBlockCache()->GetCodePointers()); + + outerLoop = GetCodePtr(); + QuickCallFunction(R0, (void *)&CoreTiming::Advance); + FixupBranch skipToRealDispatch = B(); //skip the sync and compare first time + + dispatcherCheckCoreState = GetCodePtr(); + + ARMABI_MOVI2R(R0, (u32)&coreState); + LDR(R0, R0); + CMP(R0, 0); + FixupBranch badCoreState = B_CC(CC_NEQ); + FixupBranch skipToRealDispatch2 = B(); //skip the sync and compare first time + + // At this point : flags = EQ. Fine for the next check, no need to jump over it. + dispatcher = GetCodePtr(); + + // The result of slice decrementation should be in flags if somebody jumped here + // IMPORTANT - We jump on negative, not carry!!! + FixupBranch bail = B_CC(CC_MI); + + SetJumpTarget(skipToRealDispatch); + SetJumpTarget(skipToRealDispatch2); + + dispatcherNoCheck = GetCodePtr(); + + // Debug + // MOV(R0, R13); + // QuickCallFunction(R1, (void *)&ShowPC); + + LDR(R0, R10, offsetof(MIPSState, pc)); + BIC(R0, R0, Operand2(0xC0, 4)); // &= 0x3FFFFFFF + LDR(R0, R11, R0, true, true); + AND(R1, R0, Operand2(0xFC, 4)); // rotation is to the right, in 2-bit increments. + BIC(R0, R0, Operand2(0xFC, 4)); + CMP(R1, Operand2(MIPS_EMUHACK_OPCODE >> 24, 4)); + SetCC(CC_EQ); + // IDEA - we have 26 bits, why not just use offsets from base of code? + // Another idea: Shift the bloc number left by two in the op, this would let us do + // LDR(R0, R9, R0, true, true); here, replacing the two next instructions. + ADD(R0, R9, Operand2(2, ST_LSL, R0)); + LDR(R0, R0); + B(R0); + SetCC(CC_AL); + + //Ok, no block, let's jit + QuickCallFunction(R2, (void *)&JitAt); + + B(dispatcherNoCheck); // no point in special casing this + + SetJumpTarget(bail); + + ARMABI_MOVI2R(R0, (u32)&coreState); + LDR(R0, R0); + CMP(R0, 0); + B_CC(CC_EQ, outerLoop); + + SetJumpTarget(badCoreState); + + breakpointBailout = GetCodePtr(); + + ADD(_SP, _SP, 4); + + POP(9, R4, R5, R6, R7, R8, R9, R10, R11, _PC); // Returns + + // Uncomment if you want to see the output... + // INFO_LOG(HLE, "THE DISASM ========================"); + // DisassembleArm(enterCode, GetCodePtr() - enterCode); + // INFO_LOG(HLE, "END OF THE DISASM ========================"); + + // Don't forget to zap the instruction cache! + FlushIcache(); +} + +} // namespace \ No newline at end of file diff --git a/Core/MIPS/ARM/ArmAsm.h b/Core/MIPS/ARM/ArmAsm.h new file mode 100644 index 0000000000..37f4acba04 --- /dev/null +++ b/Core/MIPS/ARM/ArmAsm.h @@ -0,0 +1,27 @@ +// Copyright (c) 2012- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#ifndef _JIT64ASM_H +#define _JIT64ASM_H + +#include +#include "../MIPS.h" + +// Runtime generated assembly routines, like the Dispatcher. + + +#endif // _JIT64ASM_H diff --git a/Core/MIPS/ARM/CompALU.cpp b/Core/MIPS/ARM/ArmCompALU.cpp similarity index 51% rename from Core/MIPS/ARM/CompALU.cpp rename to Core/MIPS/ARM/ArmCompALU.cpp index da608b6655..a9b01bddf9 100644 --- a/Core/MIPS/ARM/CompALU.cpp +++ b/Core/MIPS/ARM/ArmCompALU.cpp @@ -15,9 +15,9 @@ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. -#include "Jit.h" -#include "RegCache.h" -#include +#include "ArmJit.h" +#include "ArmRegCache.h" +#include "ArmEmitter.h" using namespace MIPSAnalyst; #define _RS ((op>>21) & 0x1F) @@ -34,28 +34,31 @@ using namespace MIPSAnalyst; namespace MIPSComp { - /* - void Jit::CompImmLogic(u32 op, void (XEmitter::*arith)(int, const OpArg &, const OpArg &)) - { - u32 uimm = (u16)(op & 0xFFFF); - int rt = _RT; - int rs = _RS; - gpr.Lock(rt, rs); - gpr.BindToRegister(rt, rt == rs, true); - if (rt != rs) - MOV(32, gpr.R(rt), gpr.R(rs)); - (this->*arith)(32, gpr.R(rt), Imm32(uimm)); - gpr.UnlockAll(); + static u32 EvalOr(u32 a, u32 b) { return a | b; } + static u32 EvalXor(u32 a, u32 b) { return a ^ b; } + static u32 EvalAnd(u32 a, u32 b) { return a & b; } + void Jit::CompImmLogic(int rs, int rt, u32 uimm, void (ARMXEmitter::*arith)(ARMReg dst, ARMReg src, Operand2 op2), u32 (*eval)(u32 a, u32 b)) + { + if (gpr.IsImm(rs)) { + gpr.SetImm(rt, (*eval)(gpr.GetImm(rs), uimm)); + } else { + gpr.MapDirtyIn(rt, rs); + // TODO: Special case when uimm can be represented as an Operand2 + Operand2 op2; + if (TryMakeOperand2(uimm, op2)) { + (this->*arith)(gpr.R(rt), gpr.R(rs), op2); + } else { + ARMABI_MOVI2R(R0, (u32)uimm); + (this->*arith)(gpr.R(rt), gpr.R(rs), R0); + } + } } - */ void Jit::Comp_IType(u32 op) { - OLDD - /* - s32 simm = (s16)(op & 0xFFFF); - u32 uimm = (u16)(op & 0xFFFF); + s32 simm = (s32)(s16)(op & 0xFFFF); // sign extension + u32 uimm = op & 0xFFFF; int rt = _RT; int rs = _RS; @@ -63,99 +66,85 @@ namespace MIPSComp switch (op >> 26) { case 8: // same as addiu? - case 9: //R(rt) = R(rs) + simm; break; //addiu + case 9: // R(rt) = R(rs) + simm; break; //addiu { - if (gpr.R(rs).IsImm()) - { - gpr.SetImmediate32(rt, gpr.R(rs).GetImmValue() + simm); - break; + if (gpr.IsImm(rs)) { + gpr.SetImm(rt, gpr.GetImm(rs) + simm); + } else if (rs == 0) { // add to zero register = immediate + gpr.SetImm(rt, (u32)simm); + } else { + gpr.MapDirtyIn(rt, rs); + Operand2 op2; + bool negated; + if (TryMakeOperand2_AllowNegation(simm, op2, &negated)) { + if (!negated) + ADD(gpr.R(rt), gpr.R(rs), op2); + else + SUB(gpr.R(rt), gpr.R(rs), op2); + } else { + ARMABI_MOVI2R(R0, (u32)simm); + ADD(gpr.R(rt), gpr.R(rs), R0); + } } - - gpr.Lock(rt, rs); - if (rs != 0) - { - gpr.BindToRegister(rt, rt == rs, true); - if (rt != rs) - MOV(32, gpr.R(rt), gpr.R(rs)); - if (simm != 0) - ADD(32, gpr.R(rt), Imm32((u32)(s32)simm)); - // TODO: Can also do LEA if both operands happen to be in registers. - } - else - { - gpr.SetImmediate32(rt, simm); - } - gpr.UnlockAll(); + break; } - break; + + case 12: CompImmLogic(rs, rt, uimm, &ARMXEmitter::AND, &EvalAnd); break; + case 13: CompImmLogic(rs, rt, uimm, &ARMXEmitter::ORR, &EvalOr); break; + case 14: CompImmLogic(rs, rt, uimm, &ARMXEmitter::EOR, &EvalXor); break; case 10: // R(rt) = (s32)R(rs) < simm; break; //slti - gpr.Lock(rt, rs); - gpr.BindToRegister(rt, rt == rs, true); - XOR(32, R(EAX), R(EAX)); - CMP(32, gpr.R(rs), Imm32(simm)); - SETcc(CC_L, R(EAX)); - MOV(32, gpr.R(rt), R(EAX)); - gpr.UnlockAll(); + { + gpr.MapDirtyIn(rt, rs); + Operand2 op2; + if (TryMakeOperand2(simm, op2)) { + CMP(gpr.R(rs), op2); + } else { + ARMABI_MOVI2R(R0, simm); + CMP(gpr.R(rs), R0); + } + SetCC(CC_LT); + ARMABI_MOVI2R(gpr.R(rt), 1); + SetCC(CC_GE); + ARMABI_MOVI2R(gpr.R(rt), 0); + SetCC(CC_AL); + } break; - + /* case 11: // R(rt) = R(rs) < uimm; break; //sltiu - gpr.Lock(rt, rs); - gpr.BindToRegister(rt, rt == rs, true); - XOR(32, R(EAX), R(EAX)); - CMP(32, gpr.R(rs), Imm32((u32)simm)); - SETcc(CC_B, R(EAX)); - MOV(32, gpr.R(rt), R(EAX)); - gpr.UnlockAll(); + { + gpr.MapDirtyIn(rt, rs); + Operand2 op2; + if (TryMakeOperand2(uimm, op2)) { + CMP(gpr.R(rs), op2); + } else { + ARMABI_MOVI2R(R0, uimm); + CMP(gpr.R(rs), R0); + } + SetCC(CC_LO); + ARMABI_MOVI2R(gpr.R(rt), 1); + SetCC(CC_HS); + ARMABI_MOVI2R(gpr.R(rt), 0); + SetCC(CC_AL); + } break; - - case 12: CompImmLogic(op, &XEmitter::AND); break; - case 13: CompImmLogic(op, &XEmitter::OR); break; - case 14: CompImmLogic(op, &XEmitter::XOR); break; - - case 15: //R(rt) = uimm << 16; break; //lui - gpr.SetImmediate32(rt, uimm << 16); + */ + case 15: // R(rt) = uimm << 16; //lui + gpr.SetImm(rt, uimm << 16); break; default: Comp_Generic(op); break; - }*/ - + } } - - //rd = rs X rt - /* - void Jit::CompTriArith(u32 op, void (XEmitter::*arith)(int, const OpArg &, const OpArg &)) - { - int rt = _RT; - int rs = _RS; - int rd = _RD; - - gpr.Lock(rt, rs, rd); - MOV(32, R(EAX), gpr.R(rs)); - MOV(32, R(EBX), gpr.R(rt)); - gpr.BindToRegister(rd, true, true); - (this->*arith)(32, R(EAX), R(EBX)); - MOV(32, gpr.R(rd), R(EAX)); - gpr.UnlockAll(); - } - */ - + void Jit::Comp_RType3(u32 op) { - OLDD - - int rt = _RT; int rs = _RS; int rd = _RD; - - gpr.Lock(rd, rs, rt); - gpr.BindToRegister(rs, true, false); - gpr.BindToRegister(rt, true, false); - gpr.BindToRegister(rd, true, true); - + switch (op & 63) { //case 10: if (!R(rt)) R(rd) = R(rs); break; //movz @@ -163,42 +152,59 @@ namespace MIPSComp // case 32: //R(rd) = R(rs) + R(rt); break; //add case 33: //R(rd) = R(rs) + R(rt); break; //addu - ADD(gpr.RX(rd), gpr.RX(rs), gpr.RX(rt)); + // Some optimized special cases + if (rs == 0) { + gpr.MapDirtyIn(rd, rt); + MOV(gpr.R(rd), gpr.R(rt)); + } else if (rt == 0) { + gpr.MapDirtyIn(rd, rs); + MOV(gpr.R(rd), gpr.R(rs)); + } else { + gpr.MapDirtyInIn(rd, rs, rt); + ADD(gpr.R(rd), gpr.R(rs), gpr.R(rt)); + } break; - case 134: //R(rd) = R(rs) - R(rt); break; //sub - case 135: - SUB(gpr.RX(rd), gpr.RX(rs), gpr.RX(rt)); + case 34: //R(rd) = R(rs) - R(rt); break; //sub + case 35: + gpr.MapDirtyInIn(rd, rs, rt); + SUB(gpr.R(rd), gpr.R(rs), gpr.R(rt)); break; - case 136: //R(rd) = R(rs) & R(rt); break; //and - AND(gpr.RX(rd), gpr.RX(rs), gpr.RX(rt)); + case 36: //R(rd) = R(rs) & R(rt); break; //and + gpr.MapDirtyInIn(rd, rs, rt); + AND(gpr.R(rd), gpr.R(rs), gpr.R(rt)); break; - case 137: //R(rd) = R(rs) | R(rt); break; //or - ORR(gpr.RX(rd), gpr.RX(rs), gpr.RX(rt)); + case 37: //R(rd) = R(rs) | R(rt); break; //or + gpr.MapDirtyInIn(rd, rs, rt); + ORR(gpr.R(rd), gpr.R(rs), gpr.R(rt)); break; - case 138: //R(rd) = R(rs) ^ R(rt); break; //xor/eor - EOR(gpr.RX(rd), gpr.RX(rs), gpr.RX(rt)); + case 38: //R(rd) = R(rs) ^ R(rt); break; //xor/eor + gpr.MapDirtyInIn(rd, rs, rt); + EOR(gpr.R(rd), gpr.R(rs), gpr.R(rt)); break; case 39: // R(rd) = ~(R(rs) | R(rt)); //nor - ORR(gpr.RX(rd), gpr.RX(rs), gpr.RX(rt)); - MVN(gpr.RX(rd), gpr.RX(rd)); + gpr.MapDirtyInIn(rd, rs, rt); + ORR(gpr.R(rd), gpr.R(rs), gpr.R(rt)); + MVN(gpr.R(rd), gpr.R(rd)); break; case 42: //R(rd) = (int)R(rs) < (int)R(rt); break; //slt - CMP(gpr.RX(rs), gpr.RX(rt)); + gpr.MapDirtyInIn(rd, rs, rt); + CMP(gpr.R(rs), gpr.R(rt)); SetCC(CC_LT); - ARMABI_MOVI2R(gpr.RX(rd), 1); + ARMABI_MOVI2R(gpr.R(rd), 1); SetCC(CC_GE); - ARMABI_MOVI2R(gpr.RX(rd), 0); + ARMABI_MOVI2R(gpr.R(rd), 0); SetCC(CC_AL); break; case 43: //R(rd) = R(rs) < R(rt); break; //sltu - CMP(gpr.RX(rs), gpr.RX(rt)); + gpr.MapDirtyInIn(rd, rs, rt); + CMP(gpr.R(rs), gpr.R(rt)); SetCC(CC_LO); - ARMABI_MOVI2R(gpr.RX(rd), 1); + ARMABI_MOVI2R(gpr.R(rd), 1); SetCC(CC_HS); - ARMABI_MOVI2R(gpr.RX(rd), 0); + ARMABI_MOVI2R(gpr.R(rd), 0); SetCC(CC_AL); break; @@ -209,29 +215,22 @@ namespace MIPSComp // CMP(a,b); CMOVGT(a,b) default: - gpr.UnlockAll(); + // gpr.UnlockAll(); Comp_Generic(op); break; } - gpr.UnlockAll(); - } - /* - - void Jit::CompShiftImm(u32 op, void (XEmitter::*shift)(int, OpArg, OpArg)) + void Jit::CompShiftImm(u32 op, ArmGen::ShiftType shiftType) { int rd = _RD; int rt = _RT; - gpr.Lock(rd, rt); int sa = _SA; - gpr.BindToRegister(rd, rd == rt, true); - if (rd != rt) - MOV(32, gpr.R(rd), gpr.R(rt)); - (this->*shift)(32, gpr.R(rd), Imm8(sa)); - gpr.UnlockAll(); + + gpr.MapDirtyIn(rd, rt); + MOV(gpr.R(rd), Operand2(sa, shiftType, gpr.R(rt))); } - */ + // "over-shifts" work the same as on x86 - only bottom 5 bits are used to get the shift value /* void Jit::CompShiftVar(u32 op, void (XEmitter::*shift)(int, OpArg, OpArg)) @@ -254,12 +253,11 @@ namespace MIPSComp void Jit::Comp_ShiftType(u32 op) { // WARNIGN : ROTR - OLDD switch (op & 0x3f) { - //case 0: CompShiftImm(op, &ARMXEmitter::SHL); break; - //case 2: CompShiftImm(op, &XEmitter::SHR); break; // srl - //case 3: CompShiftImm(op, &XEmitter::SAR); break; // sra + case 0: CompShiftImm(op, ST_LSL); break; + case 2: CompShiftImm(op, ST_LSR); break; // srl + case 3: CompShiftImm(op, ST_ASR); break; // sra // case 4: CompShiftVar(op, &XEmitter::SHL); break; // R(rd) = R(rt) << R(rs); break; //sllv // case 6: CompShiftVar(op, &XEmitter::SHR); break; // R(rd) = R(rt) >> R(rs); break; //srlv diff --git a/Core/MIPS/ARM/ArmCompBranch.cpp b/Core/MIPS/ARM/ArmCompBranch.cpp new file mode 100644 index 0000000000..32d75392b6 --- /dev/null +++ b/Core/MIPS/ARM/ArmCompBranch.cpp @@ -0,0 +1,435 @@ +// Copyright (c) 2012- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. +#include "../../HLE/HLE.h" + +#include "../MIPS.h" +#include "../MIPSCodeUtils.h" +#include "../MIPSAnalyst.h" +#include "../MIPSTables.h" + +#include "ArmJit.h" +#include "ArmRegCache.h" +#include "ArmJitCache.h" +#include + +#define _RS ((op>>21) & 0x1F) +#define _RT ((op>>16) & 0x1F) +#define _RD ((op>>11) & 0x1F) +#define _FS ((op>>11) & 0x1F) +#define _FT ((op>>16) & 0x1F) +#define _FD ((op>>6 ) & 0x1F) +#define _POS ((op>>6 ) & 0x1F) +#define _SIZE ((op>>11 ) & 0x1F) + +#define LOOPOPTIMIZATION 0 + +using namespace MIPSAnalyst; + +namespace MIPSComp +{ + +void Jit::BranchRSRTComp(u32 op, ArmGen::CCFlags cc, bool likely) +{ + if (js.inDelaySlot) { + ERROR_LOG(JIT, "Branch in delay slot at %08x", js.compilerPC); + return; + } + int offset = (signed short)(op&0xFFFF)<<2; + int rt = _RT; + int rs = _RS; + u32 targetAddr = js.compilerPC + offset + 4; + + u32 delaySlotOp = Memory::ReadUnchecked_U32(js.compilerPC+4); + + //Compile the delay slot + bool delaySlotIsNice = GetOutReg(delaySlotOp) != rt && GetOutReg(delaySlotOp) != rs; + if (!delaySlotIsNice) + { + //ERROR_LOG(CPU, "Not nice delay slot in BranchRSRTComp :( %08x", js.compilerPC); + } + // The delay slot being nice doesn't really matter though... + + if (rt == 0) + { + gpr.MapReg(rs); + CMP(gpr.R(rs), Operand2(0, TYPE_IMM)); + } + else if (rs == 0 && (cc == CC_EQ || cc == CC_NEQ)) // only these are easily 'flippable' + { + gpr.MapReg(rt); + CMP(gpr.R(rt), Operand2(0, TYPE_IMM)); + } + else + { + gpr.SpillLock(rs, rt); + gpr.MapReg(rs); + gpr.MapReg(rt); + gpr.ReleaseSpillLocks(); + CMP(gpr.R(rs), gpr.R(rt)); + } + FlushAll(); + + js.inDelaySlot = true; + ArmGen::FixupBranch ptr; + if (!likely) + { + // preserve flag around the delay slot! Maybe this is not always necessary on ARM where + // we can (mostly) control whether we set the flag or not. Of course, if someone puts an slt in to the + // delay slot, we're screwed. + MRS(R8); // Save flags register. R8 is preserved through function calls and is not allocated. + CompileAt(js.compilerPC + 4); + FlushAll(); + _MSR(true, false, R8); // Restore flags register + + ptr = B_CC(cc); + } + else + { + ptr = B_CC(cc); + CompileAt(js.compilerPC + 4); + FlushAll(); + } + js.inDelaySlot = false; + + // Take the branch + WriteExit(targetAddr, 0); + + SetJumpTarget(ptr); + // Not taken + WriteExit(js.compilerPC+8, 1); + + js.compiling = false; +} + + +void Jit::BranchRSZeroComp(u32 op, ArmGen::CCFlags cc, bool likely) +{ + if (js.inDelaySlot) { + ERROR_LOG(JIT, "Branch in delay slot at %08x", js.compilerPC); + return; + } + int offset = (signed short)(op&0xFFFF)<<2; + int rs = _RS; + u32 targetAddr = js.compilerPC + offset + 4; + + u32 delaySlotOp = Memory::ReadUnchecked_U32(js.compilerPC + 4); + + bool delaySlotIsNice = GetOutReg(delaySlotOp) != rs; + if (!delaySlotIsNice) + { + // ERROR_LOG(CPU, "Not nice delay slot in BranchRSZeroComp :( %08x", js.compilerPC); + } + gpr.MapReg(rs); + CMP(gpr.R(rs), Operand2(0, TYPE_IMM)); + FlushAll(); + + ArmGen::FixupBranch ptr; + js.inDelaySlot = true; + if (!likely) + { + // preserve flag around the delay slot! Maybe this is not always necessary on ARM where + // we can (mostly) control whether we set the flag or not. Of course, if someone puts an slt in to the + // delay slot, we're screwed. + MRS(R8); // Save flags register + CompileAt(js.compilerPC + 4); + FlushAll(); + _MSR(true, false, R8); // Restore flags register + ptr = B_CC(cc); + } + else + { + ptr = B_CC(cc); + CompileAt(js.compilerPC + 4); + FlushAll(); + } + js.inDelaySlot = false; + + // Take the branch + WriteExit(targetAddr, 0); + + SetJumpTarget(ptr); + // Not taken + WriteExit(js.compilerPC + 8, 1); + js.compiling = false; +} + + +void Jit::Comp_RelBranch(u32 op) +{ + // The CC flags here should be opposite of the actual branch becuase they skip the branching action. + switch (op>>26) + { + case 4: BranchRSRTComp(op, CC_NEQ, false); break;//beq + case 5: BranchRSRTComp(op, CC_EQ, false); break;//bne + + case 6: BranchRSZeroComp(op, CC_GT, false); break;//blez + case 7: BranchRSZeroComp(op, CC_LE, false); break;//bgtz + + case 20: BranchRSRTComp(op, CC_NEQ, true); break;//beql + case 21: BranchRSRTComp(op, CC_EQ, true); break;//bnel + + case 22: BranchRSZeroComp(op, CC_GT, true); break;//blezl + case 23: BranchRSZeroComp(op, CC_LE, true); break;//bgtzl + + default: + _dbg_assert_msg_(CPU,0,"Trying to compile instruction that can't be compiled"); + break; + } + js.compiling = false; +} + +void Jit::Comp_RelBranchRI(u32 op) +{ + switch ((op >> 16) & 0x1F) + { + case 0: BranchRSZeroComp(op, CC_GE, false); break; //if ((s32)R(rs) < 0) DelayBranchTo(addr); else PC += 4; break;//bltz + case 1: BranchRSZeroComp(op, CC_LT, false); break; //if ((s32)R(rs) >= 0) DelayBranchTo(addr); else PC += 4; break;//bgez + case 2: BranchRSZeroComp(op, CC_GE, true); break; //if ((s32)R(rs) < 0) DelayBranchTo(addr); else PC += 8; break;//bltzl + case 3: BranchRSZeroComp(op, CC_LT, true); break; //if ((s32)R(rs) >= 0) DelayBranchTo(addr); else PC += 8; break;//bgezl + default: + _dbg_assert_msg_(CPU,0,"Trying to compile instruction that can't be compiled"); + break; + } + js.compiling = false; +} + +// If likely is set, discard the branch slot if NOT taken. +void Jit::BranchFPFlag(u32 op, ArmGen::CCFlags cc, bool likely) +{ + if (js.inDelaySlot) { + ERROR_LOG(JIT, "Branch in delay slot at %08x", js.compilerPC); + return; + } + int offset = (signed short)(op & 0xFFFF) << 2; + u32 targetAddr = js.compilerPC + offset + 4; + + u32 delaySlotOp = Memory::ReadUnchecked_U32(js.compilerPC + 4); + + bool delaySlotIsNice = IsDelaySlotNice(op, delaySlotOp); + if (!delaySlotIsNice) + { + //ERROR_LOG(CPU, "Not nice delay slot in BranchFPFlag :("); + } + FlushAll(); + + LDR(R0, R10, offsetof(MIPSState, fpcond)); + TST(R0, Operand2(1, TYPE_IMM)); + ArmGen::FixupBranch ptr; + js.inDelaySlot = true; + if (!likely) + { + MRS(R8); // Save flags register + + CompileAt(js.compilerPC + 4); + FlushAll(); + + _MSR(true, false, R8); // Restore flags register + ptr = B_CC(cc); + } + else + { + ptr = B_CC(cc); + CompileAt(js.compilerPC + 4); + FlushAll(); + } + js.inDelaySlot = false; + + // Take the branch + WriteExit(targetAddr, 0); + + SetJumpTarget(ptr); + // Not taken + WriteExit(js.compilerPC + 8, 1); + js.compiling = false; +} + +void Jit::Comp_FPUBranch(u32 op) +{ + switch((op >> 16) & 0x1f) + { + case 0: BranchFPFlag(op, CC_NEQ, false); break; // bc1f + case 1: BranchFPFlag(op, CC_EQ, false); break; // bc1t + case 2: BranchFPFlag(op, CC_NEQ, true); break; // bc1fl + case 3: BranchFPFlag(op, CC_EQ, true); break; // bc1tl + default: + _dbg_assert_msg_(CPU,0,"Trying to interpret instruction that can't be interpreted"); + break; + } + js.compiling = false; +} + +// If likely is set, discard the branch slot if NOT taken. +void Jit::BranchVFPUFlag(u32 op, ArmGen::CCFlags cc, bool likely) +{ + int offset = (signed short)(op & 0xFFFF) << 2; + u32 targetAddr = js.compilerPC + offset + 4; + + u32 delaySlotOp = Memory::ReadUnchecked_U32(js.compilerPC + 4); + + bool delaySlotIsNice = IsDelaySlotNice(op, delaySlotOp); + if (!delaySlotIsNice) + { + //ERROR_LOG(CPU, "Not nice delay slot in BranchFPFlag :("); + } + FlushAll(); + + ARMABI_MOVI2R(R0, (u32)&(mips_->vfpuCtrl[VFPU_CTRL_CC])); + LDR(R0, R0, Operand2(0, TYPE_IMM)); + + int imm3 = (op >> 18) & 7; + TST(R0, Operand2(1 << imm3, TYPE_IMM)); + + ArmGen::FixupBranch ptr; + js.inDelaySlot = true; + if (!likely) + { + MRS(R8); // Save flags register + + CompileAt(js.compilerPC + 4); + FlushAll(); + + _MSR(true, false, R8); // Restore flags register + ptr = B_CC(cc); + } + else + { + ptr = B_CC(cc); + CompileAt(js.compilerPC + 4); + FlushAll(); + } + js.inDelaySlot = false; + + // Take the branch + WriteExit(targetAddr, 0); + + SetJumpTarget(ptr); + // Not taken + WriteExit(js.compilerPC + 8, 1); + js.compiling = false; +} + +void Jit::Comp_VBranch(u32 op) +{ + switch ((op >> 16) & 3) + { + case 0: BranchVFPUFlag(op, CC_NEQ, false); break; // bvf + case 1: BranchVFPUFlag(op, CC_EQ, false); break; // bvt + case 2: BranchVFPUFlag(op, CC_NEQ, true); break; // bvfl + case 3: BranchVFPUFlag(op, CC_EQ, true); break; // bvtl + } + js.compiling = false; +} + +void PrintAtExit() { + INFO_LOG(HLE, "at jump"); +} + +void Jit::Comp_Jump(u32 op) +{ + if (js.inDelaySlot) { + ERROR_LOG(JIT, "Branch in delay slot at %08x", js.compilerPC); + return; + } + u32 off = ((op & 0x03FFFFFF) << 2); + u32 targetAddr = (js.compilerPC & 0xF0000000) | off; + // Delay slot + CompileAt(js.compilerPC + 4); + FlushAll(); + + switch (op >> 26) + { + case 2: //j + WriteExit(targetAddr, 0); + break; + + case 3: //jal + ADD(R1, R10, MIPS_REG_RA * 4); // compute address of RA in ram + ARMABI_MOVI2R(R0, js.compilerPC + 8); + STR(R1, R0); + WriteExit(targetAddr, 0); + break; + + default: + _dbg_assert_msg_(CPU,0,"Trying to compile instruction that can't be compiled"); + break; + } + js.compiling = false; +} + +void Jit::Comp_JumpReg(u32 op) +{ + if (js.inDelaySlot) { + ERROR_LOG(JIT, "Branch in delay slot at %08x", js.compilerPC); + return; + } + int rs = _RS; + + u32 delaySlotOp = Memory::ReadUnchecked_U32(js.compilerPC + 4); + bool delaySlotIsNice = GetOutReg(delaySlotOp) != rs; + // Do what with that information? + delaySlotIsNice = false; + + if (delaySlotIsNice) { + CompileAt(js.compilerPC + 4); + gpr.MapReg(rs); + MOV(R8, gpr.R(rs)); // Save the destination address through the delay slot. Could use isNice to avoid when the jit is fully implemented + FlushAll(); + MovToPC(R8); // For syscall to be able to return. Could be avoided with some checking. + } else { + // Delay slot + gpr.MapReg(rs); + MOV(R8, gpr.R(rs)); // Save the destination address through the delay slot. Could use isNice to avoid when the jit is fully implemented + MovToPC(R8); // For syscall to be able to return. Could be avoided with some checking. + CompileAt(js.compilerPC + 4); + FlushAll(); + if (!js.compiling) { + // INFO_LOG(HLE, "Syscall in delay slot!"); + return; + } + } + + switch (op & 0x3f) + { + case 8: //jr + break; + case 9: //jalr + ADD(R1, R10, MIPS_REG_RA * 4); // compute address of RA in ram + ARMABI_MOVI2R(R0, js.compilerPC + 8); + STR(R1, R0); + break; + default: + _dbg_assert_msg_(CPU,0,"Trying to compile instruction that can't be compiled"); + break; + } + + WriteExitDestInR(R8); + js.compiling = false; +} + + +void Jit::Comp_Syscall(u32 op) +{ + FlushAll(); + + ARMABI_MOVI2R(R0, op); + QuickCallFunction(R1, (void *)&CallSyscall); + + WriteSyscallExit(); + js.compiling = false; +} + +} // namespace Mipscomp diff --git a/Core/MIPS/ARM/CompFPU.cpp b/Core/MIPS/ARM/ArmCompFPU.cpp similarity index 98% rename from Core/MIPS/ARM/CompFPU.cpp rename to Core/MIPS/ARM/ArmCompFPU.cpp index 0ed608eac2..9b3dee1bc4 100644 --- a/Core/MIPS/ARM/CompFPU.cpp +++ b/Core/MIPS/ARM/ArmCompFPU.cpp @@ -16,8 +16,8 @@ // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. #include "../MIPS.h" -#include "Jit.h" -#include "RegCache.h" +#include "ArmJit.h" +#include "ArmRegCache.h" #define _RS ((op>>21) & 0x1F) #define _RT ((op>>16) & 0x1F) @@ -115,6 +115,7 @@ void Jit::Comp_FPULS(u32 op) void Jit::Comp_FPU2op(u32 op) { + OLDD int fs = _FS; int fd = _FD; @@ -173,6 +174,7 @@ void Jit::Comp_FPU2op(u32 op) void Jit::Comp_mxc1(u32 op) { + OLDD int fs = _FS; int rt = _RT; diff --git a/Core/MIPS/ARM/ArmCompLoadStore.cpp b/Core/MIPS/ARM/ArmCompLoadStore.cpp new file mode 100644 index 0000000000..b310b89363 --- /dev/null +++ b/Core/MIPS/ARM/ArmCompLoadStore.cpp @@ -0,0 +1,214 @@ +// Copyright (c) 2012- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + + +// Optimization ideas: +// +// It's common to see sequences of stores writing or reading to a contiguous set of +// addresses in function prologues/epilogues: +// sw s5, 104(sp) +// sw s4, 100(sp) +// sw s3, 96(sp) +// sw s2, 92(sp) +// sw s1, 88(sp) +// sw s0, 84(sp) +// sw ra, 108(sp) +// mov s4, a0 +// mov s3, a1 +// ... +// Such sequences could easily be detected and turned into nice contiguous +// sequences of ARM stores instead of the current 3 instructions per sw/lw. +// +// Also, if we kept track of the likely register content of a cached register, +// (pointer or data), we could avoid many BIC instructions. + + +#include "../../MemMap.h" +#include "../MIPSAnalyst.h" +#include "../../Config.h" +#include "ArmJit.h" +#include "ArmRegCache.h" + +#define _RS ((op>>21) & 0x1F) +#define _RT ((op>>16) & 0x1F) +#define _RD ((op>>11) & 0x1F) +#define _FS ((op>>11) & 0x1F) +#define _FT ((op>>16) & 0x1F) +#define _FD ((op>>6 ) & 0x1F) +#define _POS ((op>>6 ) & 0x1F) +#define _SIZE ((op>>11 ) & 0x1F) + +#define OLDD Comp_Generic(op); return; + +namespace MIPSComp +{ + void Jit::Comp_ITypeMem(u32 op) + { + int offset = (signed short)(op&0xFFFF); + int rt = _RT; + int rs = _RS; + int o = op>>26; + if (((op >> 29) & 1) == 0 && rt == 0) { + // Don't load anything into $zr + return; + } + switch (o) + { + case 37: //R(rt) = ReadMem16(addr); break; //lhu + Comp_Generic(op); + return; + + case 35: //R(rt) = ReadMem32(addr); //lw + case 36: //R(rt) = ReadMem8 (addr); break; //lbu + if (g_Config.bFastMemory) { + if (gpr.IsImm(rs)) { + // We can compute the full address at compile time. Kickass. + u32 addr = (offset + gpr.GetImm(rs)) & 0x3FFFFFFF; + gpr.MapReg(rt, MAP_NOINIT | MAP_DIRTY); // must be OK even if rs == rt since we have the value from imm already. + ARMABI_MOVI2R(R0, addr); + } else { + gpr.MapDirtyIn(rt, rs); + Operand2 op2; + if (offset) { + bool negated; + if (TryMakeOperand2_AllowNegation(offset, op2, &negated)) { + if (!negated) + ADD(R0, gpr.R(rs), op2); + else + SUB(R0, gpr.R(rs), op2); + } else { + // Try to avoid using MOVT + if (offset < 0) { + ARMABI_MOVI2R(R0, (u32)(-offset)); + SUB(R0, gpr.R(rs), R0); + } else { + ARMABI_MOVI2R(R0, (u32)offset); + ADD(R0, gpr.R(rs), R0); + } + } + BIC(R0, R0, Operand2(0xC0, 4)); // &= 0x3FFFFFFF + } else { + BIC(R0, gpr.R(rs), Operand2(0xC0, 4)); // &= 0x3FFFFFFF + } + } + if (o == 35) { + LDR(gpr.R(rt), R11, R0, true, true); + } else if (o == 36) { + ADD(R0, R0, R11); // TODO: Merge with next instruction + LDRB(gpr.R(rt), R0); + } + } else { + Comp_Generic(op); + return; + } + break; + + case 41: //WriteMem16(addr, R(rt)); break; //sh + Comp_Generic(op); + return; + + case 40: //sb + case 43: //WriteMem32(addr, R(rt)); break; //sw + if (g_Config.bFastMemory) { + if (gpr.IsImm(rs)) { + // We can compute the full address at compile time. Kickass. + u32 addr = (offset + gpr.GetImm(rs)) & 0x3FFFFFFF; + gpr.MapReg(rt); + ARMABI_MOVI2R(R0, addr); + } else { + gpr.MapInIn(rt, rs); + Operand2 op2; + if (offset) { + bool negated; + if (TryMakeOperand2_AllowNegation(offset, op2, &negated)) { + if (!negated) + ADD(R0, gpr.R(rs), op2); + else + SUB(R0, gpr.R(rs), op2); + } else { + // Try to avoid using MOVT + if (offset < 0) { + ARMABI_MOVI2R(R0, (u32)(-offset)); + SUB(R0, gpr.R(rs), R0); + } else { + ARMABI_MOVI2R(R0, (u32)offset); + ADD(R0, gpr.R(rs), R0); + } + } + BIC(R0, R0, Operand2(0xC0, 4)); // &= 0x3FFFFFFF + } else { + BIC(R0, gpr.R(rs), Operand2(0xC0, 4)); // &= 0x3FFFFFFF + } + } + if (o == 43) { + STR(R0, gpr.R(rt), R11, true, true); + } else if (o == 40) { + ADD(R0, R0, R11); + STRB(R0, gpr.R(rt)); + } + } else { + Comp_Generic(op); + return; + } + break; + // break; + /* + case 34: //lwl + { + Crash(); + //u32 shift = (addr & 3) << 3; + //u32 mem = ReadMem32(addr & 0xfffffffc); + //R(rt) = ( u32(R(rt)) & (0x00ffffff >> shift) ) | ( mem << (24 - shift) ); + } + break; + + case 38: //lwr + { + Crash(); + //u32 shift = (addr & 3) << 3; + //u32 mem = ReadMem32(addr & 0xfffffffc); + + //R(rt) = ( u32(rt) & (0xffffff00 << (24 - shift)) ) | ( mem >> shift ); + } + break; + + case 42: //swl + { + Crash(); + //u32 shift = (addr & 3) << 3; + //u32 mem = ReadMem32(addr & 0xfffffffc); + //WriteMem32((addr & 0xfffffffc), ( ( u32(R(rt)) >> (24 - shift) ) ) | + // ( mem & (0xffffff00 << shift) )); + } + break; + case 46: //swr + { + Crash(); + // u32 shift = (addr & 3) << 3; + // u32 mem = ReadMem32(addr & 0xfffffffc); +// +// WriteMem32((addr & 0xfffffffc), ( ( u32(R(rt)) << shift ) | +// (mem & (0x00ffffff >> (24 - shift)) ) ) ); + } + break;*/ + default: + Comp_Generic(op); + return ; + } + + } +} diff --git a/Core/MIPS/ARM/CompVFPU.cpp b/Core/MIPS/ARM/ArmCompVFPU.cpp similarity index 87% rename from Core/MIPS/ARM/CompVFPU.cpp rename to Core/MIPS/ARM/ArmCompVFPU.cpp index 7abf7a19af..b434446b68 100644 --- a/Core/MIPS/ARM/CompVFPU.cpp +++ b/Core/MIPS/ARM/ArmCompVFPU.cpp @@ -1,8 +1,8 @@ #include "../../MemMap.h" #include "../MIPSAnalyst.h" -#include "Jit.h" -#include "RegCache.h" +#include "ArmJit.h" +#include "ArmRegCache.h" #define _RS ((op>>21) & 0x1F) diff --git a/Core/MIPS/ARM/ArmJit.cpp b/Core/MIPS/ARM/ArmJit.cpp new file mode 100644 index 0000000000..cb0373ba43 --- /dev/null +++ b/Core/MIPS/ARM/ArmJit.cpp @@ -0,0 +1,283 @@ +// Copyright (c) 2012- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include "../../Core.h" +#include "../../CoreTiming.h" +#include "../MIPS.h" +#include "../MIPSCodeUtils.h" +#include "../MIPSInt.h" +#include "../MIPSTables.h" + +#include "ArmRegCache.h" +#include "ArmJit.h" + +#include "../../ext/disarm.h" + +void DisassembleArm(const u8 *data, int size) { + char temp[256]; + for (int i = 0; i < size; i += 4) { + const u32 *codePtr = (const u32 *)(data + i); + u32 inst = codePtr[0]; + u32 next = (i < size - 4) ? codePtr[1] : 0; + // MAGIC SPECIAL CASE for MOVW/MOVT readability! + if ((inst & 0x0FF00000) == 0x03000000 && (next & 0x0FF00000) == 0x03400000) { + u32 low = ((inst & 0x000F0000) >> 4) | (inst & 0x0FFF); + u32 hi = ((next & 0x000F0000) >> 4) | (next & 0x0FFF); + int reg0 = (inst & 0x0000F000) >> 12; + int reg1 = (next & 0x0000F000) >> 12; + if (reg0 == reg1) { + sprintf(temp, "%08x MOV32? %s, %04x%04x", (u32)inst, ArmRegName(reg0), hi, low); + INFO_LOG(DYNA_REC, "A: %s", temp); + i += 4; + continue; + } + } + ArmDis((u32)codePtr, inst, temp); + INFO_LOG(DYNA_REC, "A: %s", temp); + } +} + +namespace MIPSComp +{ + +Jit::Jit(MIPSState *mips) : blocks(mips), gpr(mips), mips_(mips) +{ + blocks.Init(); + gpr.SetEmitter(this); + //fpr.SetEmitter(this); + AllocCodeSpace(1024 * 1024 * 16); // 32MB is the absolute max because that's what an ARM branch instruction can reach, backwards and forwards. + GenerateFixedCode(); +} + +void Jit::FlushAll() +{ + gpr.FlushAll(); + //fpr.Flush(FLUSH_ALL); +} + +void Jit::ClearCache() +{ + blocks.Clear(); + ClearCodeSpace(); + GenerateFixedCode(); +} + +void Jit::CompileAt(u32 addr) +{ + u32 op = Memory::Read_Instruction(addr); + MIPSCompileOp(op); +} + +void Jit::Compile(u32 em_address) +{ + if (GetSpaceLeft() < 0x10000 || blocks.IsFull()) + { + ClearCache(); + } + + int block_num = blocks.AllocateBlock(em_address); + ArmJitBlock *b = blocks.GetBlock(block_num); + blocks.FinalizeBlock(block_num, jo.enableBlocklink, DoJit(em_address, b)); +} + +void Jit::RunLoopUntil(u64 globalticks) +{ + // TODO: copy globalticks somewhere + ((void (*)())enterCode)(); +} + +const u8 *Jit::DoJit(u32 em_address, ArmJitBlock *b) +{ + js.cancel = false; + js.blockStart = js.compilerPC = mips_->pc; + js.downcountAmount = 0; + js.curBlock = b; + js.compiling = true; + js.inDelaySlot = false; + + // We add a check before the block, used when entering from a linked block. + b->checkedEntry = GetCodePtr(); + // Downcount flag check. The last block decremented downcounter, and the flag should still be available. + SetCC(CC_LT); + ARMABI_MOVI2R(R0, js.blockStart); + MovToPC(R0); + B((const void *)outerLoop); + SetCC(CC_AL); + + b->normalEntry = GetCodePtr(); + // TODO: this needs work + MIPSAnalyst::AnalysisResults analysis; // = MIPSAnalyst::Analyze(em_address); + + gpr.Start(analysis); + + int numInstructions = 0; + int cycles = 0; + static int dontLogBlocks = 20; + static int logBlocks = 40; + if (logBlocks > 0) logBlocks--; + if (dontLogBlocks > 0) dontLogBlocks--; + +#define LOGASM +#ifdef LOGASM + char temp[256]; +#endif + while (js.compiling) + { + gpr.SetCompilerPC(js.compilerPC); // Let it know for log messages + u32 inst = Memory::Read_Instruction(js.compilerPC); +#ifdef LOGASM + if (logBlocks > 0 && dontLogBlocks == 0) { + MIPSDisAsm(inst, js.compilerPC, temp, true); + INFO_LOG(DYNA_REC, "M: %08x %s", js.compilerPC, temp); + } +#endif + js.downcountAmount += MIPSGetInstructionCycleEstimate(inst); + + MIPSCompileOp(inst); + // FlushAll(); ///HACKK + js.compilerPC += 4; + numInstructions++; + } +#ifdef LOGASM + if (logBlocks > 0 && dontLogBlocks == 0) { + MIPSDisAsm(Memory::Read_Instruction(js.compilerPC), js.compilerPC, temp, true); + INFO_LOG(DYNA_REC, "M: %08x %s", js.compilerPC, temp); + } +#endif + + b->codeSize = GetCodePtr() - b->normalEntry; + +#ifdef LOGASM + if (logBlocks > 0 && dontLogBlocks == 0) { + INFO_LOG(DYNA_REC, "=============== ARM ==============="); + DisassembleArm(b->normalEntry, GetCodePtr() - b->normalEntry); + } +#endif + AlignCode16(); + + // Don't forget to zap the instruction cache! + FlushIcache(); + + b->originalSize = numInstructions; + return b->normalEntry; +} + +void Jit::Comp_RunBlock(u32 op) +{ + // This shouldn't be necessary, the dispatcher should catch us before we get here. + ERROR_LOG(DYNA_REC, "Comp_RunBlock should never be reached!"); +} + +void Jit::Comp_Generic(u32 op) +{ + FlushAll(); + MIPSInterpretFunc func = MIPSGetInterpretFunc(op); + if (func) + { + ARMABI_MOVI2R(R0, js.compilerPC); + MovToPC(R0); + ARMABI_MOVI2R(R0, op); + QuickCallFunction(R1, (void *)func); + } +} + +void Jit::MovFromPC(ARMReg r) { + LDR(r, R10, offsetof(MIPSState, pc)); +} + +void Jit::MovToPC(ARMReg r) { + STR(R10, r, offsetof(MIPSState, pc)); +} + +void Jit::DoDownCount() +{ + LDR(R1, R10, offsetof(MIPSState, downcount)); + Operand2 op2; + if (TryMakeOperand2(js.downcountAmount, op2)) // We can enlarge this if we used rotations + { + SUBS(R1, R1, op2); + STR(R10, R1, offsetof(MIPSState, downcount)); + } else { + // Should be fine to use R2 here, flushed the regcache anyway. + // If js.downcountAmount can be expressed as an Imm8, we don't need this anyway. + ARMABI_MOVI2R(R2, js.downcountAmount); + SUBS(R1, R1, R2); + STR(R10, R1, offsetof(MIPSState, downcount)); + } +} + +// IDEA - could have a WriteDualExit that takes two destinations and two condition flags, +// and just have conditional that set PC "twice". This only works when we fall back to dispatcher +// though, as we need to have the SUBS flag set in the end. So with block linking in the mix, +// I don't think this gives us that much benefit. +void Jit::WriteExit(u32 destination, int exit_num) +{ + DoDownCount(); + //If nobody has taken care of this yet (this can be removed when all branches are done) + ArmJitBlock *b = js.curBlock; + b->exitAddress[exit_num] = destination; + b->exitPtrs[exit_num] = GetWritableCodePtr(); + + // Link opportunity! + int block = blocks.GetBlockNumberFromStartAddress(destination); + if (block >= 0 && jo.enableBlocklink) { + // It exists! Joy of joy! + B(blocks.GetBlock(block)->checkedEntry); + b->linkStatus[exit_num] = true; + } else { + ARMABI_MOVI2R(R0, destination); + MovToPC(R0); + B((const void *)dispatcher); + } +} + +void Jit::WriteExitDestInR(ARMReg Reg) +{ + MovToPC(Reg); + DoDownCount(); + // TODO: shouldn't need an indirect branch here... + B((const void *)dispatcher); +} + +void Jit::WriteSyscallExit() +{ + DoDownCount(); + B((const void *)dispatcherCheckCoreState); +} + + +#define _RS ((op>>21) & 0x1F) +#define _RT ((op>>16) & 0x1F) +#define _RD ((op>>11) & 0x1F) +#define _FS ((op>>11) & 0x1F) +#define _FT ((op>>16) & 0x1F) +#define _FD ((op>>6) & 0x1F) +#define _POS ((op>>6) & 0x1F) +#define _SIZE ((op>>11) & 0x1F) + +//memory regions: +// +// 08-0A +// 48-4A +// 04-05 +// 44-45 +// mov eax, addrreg + // shr eax, 28 +// mov eax, [table+eax] +// mov dreg, [eax+offreg] + +} diff --git a/Core/MIPS/ARM/Jit.h b/Core/MIPS/ARM/ArmJit.h similarity index 68% rename from Core/MIPS/ARM/Jit.h rename to Core/MIPS/ARM/ArmJit.h index 567d77d7c9..036d71dc0b 100644 --- a/Core/MIPS/ARM/Jit.h +++ b/Core/MIPS/ARM/ArmJit.h @@ -18,31 +18,26 @@ #pragma once #include "../../../Globals.h" -#include "Asm.h" - -#if !defined(ARM) -#error DO NOT BUILD ARM JIT ON NON-ARM -#endif - +#include "ArmAsm.h" #include -#include "JitCache.h" -#include "RegCache.h" +#include "ArmJitCache.h" +#include "ArmRegCache.h" namespace MIPSComp { -struct JitOptions +struct ArmJitOptions { - JitOptions() + ArmJitOptions() { - enableBlocklink = false; + enableBlocklink = true; } bool enableBlocklink; }; -struct JitState +struct ArmJitState { u32 compilerPC; u32 blockStart; @@ -50,7 +45,7 @@ struct JitState bool inDelaySlot; int downcountAmount; bool compiling; // TODO: get rid of this in favor of using analysis results to determine end of block - JitBlock *curBlock; + ArmJitBlock *curBlock; }; class Jit : public ArmGen::ARMXCodeBlock @@ -66,7 +61,7 @@ public: void RunLoopUntil(u64 globalticks); void Compile(u32 em_address); // Compiles a block at current MIPS PC - const u8 *DoJit(u32 em_address, JitBlock *b); + const u8 *DoJit(u32 em_address, ArmJitBlock *b); void CompileAt(u32 addr); void Comp_RunBlock(u32 op); @@ -92,26 +87,33 @@ public: void Comp_FPU2op(u32 op); void Comp_mxc1(u32 op); - JitBlockCache *GetBlockCache() { return &blocks; } - AsmRoutineManager &Asm() { return asm_; } + ArmJitBlockCache *GetBlockCache() { return &blocks; } void ClearCache(); private: + void GenerateFixedCode(); void FlushAll(); + // TODO: Split into two parts, the first part can be shared in branches. + void DoDownCount(); + void MovFromPC(ARMReg r); + void MovToPC(ARMReg r); + void WriteExit(u32 destination, int exit_num); - void WriteExitDestInEAX(); -// void WriteRfiExitDestInEAX(); + void WriteExitDestInR(ARMReg Reg); void WriteSyscallExit(); // Utility compilation functions - //void BranchFPFlag(u32 op, ArmGen::CCFlags cc, bool likely); - //void BranchRSZeroComp(u32 op, ArmGen::CCFlags cc, bool likely); - //void BranchRSRTComp(u32 op, ArmGen::CCFlags cc, bool likely); + void BranchFPFlag(u32 op, ArmGen::CCFlags cc, bool likely); + void BranchVFPUFlag(u32 op, ArmGen::CCFlags cc, bool likely); + void BranchRSZeroComp(u32 op, ArmGen::CCFlags cc, bool likely); + void BranchRSRTComp(u32 op, ArmGen::CCFlags cc, bool likely); // Utilities to reduce duplicated code - /* + void CompImmLogic(int rs, int rt, u32 uimm, void (ARMXEmitter::*arith)(ARMReg dst, ARMReg src, Operand2 op2), u32 (*eval)(u32 a, u32 b)); + void CompShiftImm(u32 op, ArmGen::ShiftType shiftType); + /* void CompImmLogic(u32 op, void (ARMXEmitter::*arith)(int, const OpArg &, const OpArg &)); void CompTriArith(u32 op, void (ARMXEmitter::*arith)(int, const OpArg &, const OpArg &)); void CompShiftImm(u32 op, void (ARMXEmitter::*shift)(int, OpArg, OpArg)); @@ -120,16 +122,25 @@ private: void CompFPTriArith(u32 op, void (XEmitter::*arith)(X64Reg reg, OpArg), bool orderMatters); */ - JitBlockCache blocks; - JitOptions jo; - JitState js; + ArmJitBlockCache blocks; + ArmJitOptions jo; + ArmJitState js; - GPRRegCache gpr; - FPURegCache fpr; - - AsmRoutineManager asm_; + ArmRegCache gpr; + // FPURegCache fpr; MIPSState *mips_; + +public: + // Code pointers + const u8 *enterCode; + + const u8 *outerLoop; + const u8 *dispatcherCheckCoreState; + const u8 *dispatcher; + const u8 *dispatcherNoCheck; + + const u8 *breakpointBailout; }; typedef void (Jit::*MIPSCompileFunc)(u32 opcode); diff --git a/Core/MIPS/ARM/JitCache.cpp b/Core/MIPS/ARM/ArmJitCache.cpp similarity index 74% rename from Core/MIPS/ARM/JitCache.cpp rename to Core/MIPS/ARM/ArmJitCache.cpp index 68a5aa5d3c..c457e0c9c3 100644 --- a/Core/MIPS/ARM/JitCache.cpp +++ b/Core/MIPS/ARM/ArmJitCache.cpp @@ -29,12 +29,11 @@ #include "../MIPSTables.h" #include "../MIPSAnalyst.h" -#include "JitCache.h" -#include "../JitCommon/JitCommon.h" -#include "Asm.h" -// #include "JitBase.h" +#include "ArmEmitter.h" -#include +#include "ArmJitCache.h" +#include "../JitCommon/JitCommon.h" +#include "ArmAsm.h" #if defined USE_OPROFILE && USE_OPROFILE #include @@ -52,30 +51,30 @@ using namespace ArmGen; #define INVALID_EXIT 0xFFFFFFFF -bool JitBlock::ContainsAddress(u32 em_address) +bool ArmJitBlock::ContainsAddress(u32 em_address) { // WARNING - THIS DOES NOT WORK WITH INLINING ENABLED. return (em_address >= originalAddress && em_address < originalAddress + 4 * originalSize); } -bool JitBlockCache::IsFull() const +bool ArmJitBlockCache::IsFull() const { return GetNumBlocks() >= MAX_NUM_BLOCKS - 1; } -void JitBlockCache::Init() +void ArmJitBlockCache::Init() { MAX_NUM_BLOCKS = 65536*2; #if defined USE_OPROFILE && USE_OPROFILE agent = op_open_agent(); #endif - blocks = new JitBlock[MAX_NUM_BLOCKS]; + blocks = new ArmJitBlock[MAX_NUM_BLOCKS]; blockCodePointers = new const u8*[MAX_NUM_BLOCKS]; Clear(); } -void JitBlockCache::Shutdown() +void ArmJitBlockCache::Shutdown() { delete[] blocks; delete[] blockCodePointers; @@ -91,15 +90,14 @@ void JitBlockCache::Shutdown() #endif } -JitBlockCache::~JitBlockCache() +ArmJitBlockCache::~ArmJitBlockCache() { Shutdown(); } - -// This clears the JIT cache. It's called from JitCache.cpp when the JIT cache +// This clears the JIT block cache. It's called from JitCache.cpp when the JIT cache // is full and when saving and loading states. -void JitBlockCache::Clear() +void ArmJitBlockCache::Clear() { for (int i = 0; i < num_blocks; i++) { @@ -108,44 +106,33 @@ void JitBlockCache::Clear() links_to.clear(); block_map.clear(); num_blocks = 0; - memset(blockCodePointers, 0, sizeof(u8*)*MAX_NUM_BLOCKS); + memset(blockCodePointers, 0xCC, sizeof(u8*)*MAX_NUM_BLOCKS); } -void JitBlockCache::ClearSafe() +void ArmJitBlockCache::ClearSafe() { #ifdef JIT_UNLIMITED_ICACHE memset(iCache, JIT_ICACHE_INVALID_BYTE, JIT_ICACHE_SIZE); #endif } -/*void JitBlockCache::DestroyBlocksWithFlag(BlockFlag death_flag) -{ - for (int i = 0; i < num_blocks; i++) - { - if (blocks[i].flags & death_flag) - { - DestroyBlock(i, false); - } - } -}*/ - -void JitBlockCache::Reset() +void ArmJitBlockCache::Reset() { Shutdown(); Init(); } -JitBlock *JitBlockCache::GetBlock(int no) +ArmJitBlock *ArmJitBlockCache::GetBlock(int no) { return &blocks[no]; } -int JitBlockCache::GetNumBlocks() const +int ArmJitBlockCache::GetNumBlocks() const { return num_blocks; } -bool JitBlockCache::RangeIntersect(int s1, int e1, int s2, int e2) const +bool ArmJitBlockCache::RangeIntersect(int s1, int e1, int s2, int e2) const { // check if any endpoint is inside the other range if ((s1 >= s2 && s1 <= e2) || @@ -157,9 +144,9 @@ bool JitBlockCache::RangeIntersect(int s1, int e1, int s2, int e2) const return false; } -int JitBlockCache::AllocateBlock(u32 em_address) +int ArmJitBlockCache::AllocateBlock(u32 em_address) { - JitBlock &b = blocks[num_blocks]; + ArmJitBlock &b = blocks[num_blocks]; b.invalid = false; b.originalAddress = em_address; b.exitAddress[0] = INVALID_EXIT; @@ -173,10 +160,10 @@ int JitBlockCache::AllocateBlock(u32 em_address) return num_blocks - 1; } -void JitBlockCache::FinalizeBlock(int block_num, bool block_link, const u8 *code_ptr) +void ArmJitBlockCache::FinalizeBlock(int block_num, bool block_link, const u8 *code_ptr) { blockCodePointers[block_num] = code_ptr; - JitBlock &b = blocks[block_num]; + ArmJitBlock &b = blocks[block_num]; b.originalFirstOpcode = Memory::Read_Opcode_JIT(b.originalAddress); u32 opcode = MIPS_MAKE_EMUHACK(0, block_num); @@ -222,12 +209,12 @@ void JitBlockCache::FinalizeBlock(int block_num, bool block_link, const u8 *code #endif } -const u8 **JitBlockCache::GetCodePointers() +const u8 **ArmJitBlockCache::GetCodePointers() { return blockCodePointers; } -int JitBlockCache::GetBlockNumberFromStartAddress(u32 addr) +int ArmJitBlockCache::GetBlockNumberFromStartAddress(u32 addr) { if (!blocks) return -1; @@ -242,14 +229,14 @@ int JitBlockCache::GetBlockNumberFromStartAddress(u32 addr) return bl; } -void JitBlockCache::GetBlockNumbersFromAddress(u32 em_address, std::vector *block_numbers) +void ArmJitBlockCache::GetBlockNumbersFromAddress(u32 em_address, std::vector *block_numbers) { for (int i = 0; i < num_blocks; i++) if (blocks[i].ContainsAddress(em_address)) block_numbers->push_back(i); } -u32 JitBlockCache::GetOriginalFirstOp(int block_num) +u32 ArmJitBlockCache::GetOriginalFirstOp(int block_num) { if (block_num >= num_blocks) { @@ -259,12 +246,12 @@ u32 JitBlockCache::GetOriginalFirstOp(int block_num) return blocks[block_num].originalFirstOpcode; } -CompiledCode JitBlockCache::GetCompiledCodeFromBlock(int block_num) +CompiledCode ArmJitBlockCache::GetCompiledCodeFromBlock(int block_num) { return (CompiledCode)blockCodePointers[block_num]; } -std::string JitBlockCache::GetCompiledDisassembly(int block_num) +std::string ArmJitBlockCache::GetCompiledDisassembly(int block_num) { /* std::string buf; @@ -280,16 +267,11 @@ std::string JitBlockCache::GetCompiledDisassembly(int block_num) } - -//Block linker //Make sure to have as many blocks as possible compiled before calling this -//It's O(N), so it's fast :) -//Can be faster by doing a queue for blocks to link up, and only process those -//Should probably be done - -void JitBlockCache::LinkBlockExits(int i) +//It's O(1), so it's fast :) +void ArmJitBlockCache::LinkBlockExits(int i) { - JitBlock &b = blocks[i]; + ArmJitBlock &b = blocks[i]; if (b.invalid) { // This block is dead. Don't relink it. @@ -303,7 +285,8 @@ void JitBlockCache::LinkBlockExits(int i) if (destinationBlock != -1) { ARMXEmitter emit(b.exitPtrs[e]); - // emit.JMP(blocks[destinationBlock].checkedEntry, true); + emit.B(blocks[destinationBlock].checkedEntry); + emit.FlushIcache(); b.linkStatus[e] = true; } } @@ -312,10 +295,10 @@ void JitBlockCache::LinkBlockExits(int i) using namespace std; -void JitBlockCache::LinkBlock(int i) +void ArmJitBlockCache::LinkBlock(int i) { LinkBlockExits(i); - JitBlock &b = blocks[i]; + ArmJitBlock &b = blocks[i]; std::map::iterator iter; pair::iterator, multimap::iterator> ppp; // equal_range(b) returns pair representing the range @@ -329,16 +312,16 @@ void JitBlockCache::LinkBlock(int i) } } -void JitBlockCache::UnlinkBlock(int i) +void ArmJitBlockCache::UnlinkBlock(int i) { - JitBlock &b = blocks[i]; + ArmJitBlock &b = blocks[i]; std::map::iterator iter; pair::iterator, multimap::iterator> ppp; ppp = links_to.equal_range(b.originalAddress); if (ppp.first == ppp.second) return; for (multimap::iterator iter2 = ppp.first; iter2 != ppp.second; ++iter2) { - JitBlock &sourceBlock = blocks[iter2->second]; + ArmJitBlock &sourceBlock = blocks[iter2->second]; for (int e = 0; e < 2; e++) { if (sourceBlock.exitAddress[e] == b.originalAddress) @@ -347,14 +330,14 @@ void JitBlockCache::UnlinkBlock(int i) } } -void JitBlockCache::DestroyBlock(int block_num, bool invalidate) +void ArmJitBlockCache::DestroyBlock(int block_num, bool invalidate) { if (block_num < 0 || block_num >= num_blocks) { ERROR_LOG(JIT, "DestroyBlock: Invalid block number %d", block_num); return; } - JitBlock &b = blocks[block_num]; + ArmJitBlock &b = blocks[block_num]; if (b.invalid) { if (invalidate) @@ -367,25 +350,21 @@ void JitBlockCache::DestroyBlock(int block_num, bool invalidate) UnlinkBlock(block_num); + blockCodePointers[block_num] = 0; // Send anyone who tries to run this block back to the dispatcher. // Not entirely ideal, but .. pretty good. - // Spurious entrances from previously linked blocks can only come through checkedEntry + // I hope there's enough space... + // checkedEntry is the only "linked" entrance so it's enough to overwrite that. ARMXEmitter emit((u8 *)b.checkedEntry); - //emit.MOV(32, M(&mips->pc), Imm32(b.originalAddress)); - //emit.JMP(MIPSComp::jit->Asm().dispatcher, true); - - // this is not needed really - /* - emit.SetCodePtr((u8 *)blockCodePointers[blocknum]); - emit.MOV(32, M(&PC), Imm32(b.originalAddress)); - emit.JMP(asm_routines.dispatcher, true); - */ + emit.ARMABI_MOVI2R(R0, b.originalAddress); + emit.STR(R10, R0, offsetof(MIPSState, pc)); + emit.B(MIPSComp::jit->dispatcher); + emit.FlushIcache(); } -void JitBlockCache::InvalidateICache(u32 address, const u32 length) +void ArmJitBlockCache::InvalidateICache(u32 address, const u32 length) { - // Convert the logical address to a physical address for the block map - u32 pAddr = address & 0x1FFFFFFF; + u32 pAddr = address & 0x3FFFFFFF; // destroy JIT blocks // !! this works correctly under assumption that any two overlapping blocks end at the same address diff --git a/Core/MIPS/ARM/JitCache.h b/Core/MIPS/ARM/ArmJitCache.h similarity index 95% rename from Core/MIPS/ARM/JitCache.h rename to Core/MIPS/ARM/ArmJitCache.h index ae38f06e6a..d464417115 100644 --- a/Core/MIPS/ARM/JitCache.h +++ b/Core/MIPS/ARM/ArmJitCache.h @@ -22,7 +22,7 @@ #include #include "../MIPSAnalyst.h" - +#include "../MIPS.h" // Define this in order to get VTune profile support for the Jit generated code. // Add the VTune include/lib directories to the project directories to get this to build. // #define USE_VTUNE @@ -42,7 +42,7 @@ #define JIT_OPCODE 0xFFCCCCCC // yeah this ain't gonna work -struct JitBlock +struct ArmJitBlock { const u8 *checkedEntry; const u8 *normalEntry; @@ -77,11 +77,11 @@ struct JitBlock typedef void (*CompiledCode)(); -class JitBlockCache +class ArmJitBlockCache { MIPSState *mips; const u8 **blockCodePointers; - JitBlock *blocks; + ArmJitBlock *blocks; int num_blocks; std::multimap links_to; std::map, u32> block_map; // (end_addr, start_addr) -> number @@ -94,10 +94,10 @@ class JitBlockCache void UnlinkBlock(int i); public: - JitBlockCache(MIPSState *mips_) : + ArmJitBlockCache(MIPSState *mips_) : mips(mips_), blockCodePointers(0), blocks(0), num_blocks(0), MAX_NUM_BLOCKS(0) { } - ~JitBlockCache(); + ~ArmJitBlockCache(); int AllocateBlock(u32 em_address); void FinalizeBlock(int block_num, bool block_link, const u8 *code_ptr); @@ -110,7 +110,7 @@ public: bool IsFull() const; // Code Cache - JitBlock *GetBlock(int block_num); + ArmJitBlock *GetBlock(int block_num); int GetNumBlocks() const; const u8 **GetCodePointers(); diff --git a/Core/MIPS/ARM/ArmRegCache.cpp b/Core/MIPS/ARM/ArmRegCache.cpp new file mode 100644 index 0000000000..3b9122bb18 --- /dev/null +++ b/Core/MIPS/ARM/ArmRegCache.cpp @@ -0,0 +1,268 @@ +// Copyright (C) 2003 Dolphin Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official SVN repository and contact information can be found at +// http://code.google.com/p/dolphin-emu/ + +#include "ArmRegCache.h" +#include "ArmEmitter.h" + +using namespace ArmGen; + +#define CTXREG (R10) + +ArmRegCache::ArmRegCache(MIPSState *mips) : mips_(mips) { +} + +void ArmRegCache::Init(ARMXEmitter *emitter) { + emit = emitter; +} + +void ArmRegCache::Start(MIPSAnalyst::AnalysisResults &stats) { + for (int i = 0; i < 16; i++) { + ar[i].mipsReg = -1; + ar[i].isDirty = false; + } + for (int i = 0; i < NUM_MIPSREG; i++) { + mr[i].loc = ML_MEM; + mr[i].reg = INVALID_REG; + mr[i].imm = -1; + mr[i].spillLock = false; + } +} + +static const ARMReg *GetMIPSAllocationOrder(int &count) { + // Note that R0 and R1 are reserved as scratch for now. We can probably free up R1 eventually. + // R8 is used to preserve flags in nasty branches. + // R9 and upwards are reserved for jit basics. + // Six allocated registers should be enough... + static const ARMReg allocationOrder[] = { + R2, R3, R4, R5, R6, R7 + }; + count = sizeof(allocationOrder) / sizeof(const int); + return allocationOrder; +} + +ARMReg ArmRegCache::MapReg(MIPSReg mipsReg, int mapFlags) { + // Let's see if it's already mapped. If so we just need to update the dirty flag. + // We don't need to check for ML_NOINIT because we assume that anyone who maps + // with that flag immediately writes a "known" value to the register. + if (mr[mipsReg].loc == ML_ARMREG) { + if (ar[mr[mipsReg].reg].mipsReg != mipsReg) { + ERROR_LOG(HLE, "Register mapping out of sync! %i", mipsReg); + } + if (mapFlags & MAP_DIRTY) { + ar[mr[mipsReg].reg].isDirty = true; + } + return mr[mipsReg].reg; + } + + // Okay, not mapped, so we need to allocate an ARM register. + + int allocCount; + const ARMReg *allocOrder = GetMIPSAllocationOrder(allocCount); + +allocate: + for (int i = 0; i < allocCount; i++) { + int reg = allocOrder[i]; + + if (ar[reg].mipsReg == -1) { + // That means it's free. Grab it, and load the value into it (if requested). + ar[reg].isDirty = (mapFlags & MAP_DIRTY) ? true : false; + if (!(mapFlags & MAP_NOINIT)) { + if (mr[mipsReg].loc == ML_MEM) { + if (mipsReg != 0) { + emit->LDR((ARMReg)reg, CTXREG, 4 * mipsReg); + } else { + // If we get a request to load the zero register, at least we won't spend + // time on a memory access... + emit->MOV((ARMReg)reg, 0); + } + } else if (mr[mipsReg].loc == ML_IMM) { + emit->ARMABI_MOVI2R((ARMReg)reg, mr[mipsReg].imm); + ar[reg].isDirty = true; // IMM is always dirty. + } + } + ar[reg].mipsReg = mipsReg; + mr[mipsReg].loc = ML_ARMREG; + mr[mipsReg].reg = (ARMReg)reg; + return (ARMReg)reg; + } + } + + // Still nothing. Let's spill a reg and goto 10. + // TODO: Use age or something to choose which register to spill? + // TODO: Spill dirty regs first? or opposite? + int bestToSpill = -1; + for (int i = 0; i < allocCount; i++) { + int reg = allocOrder[i]; + if (ar[reg].mipsReg != -1 && mr[ar[reg].mipsReg].spillLock) + continue; + bestToSpill = reg; + break; + } + + if (bestToSpill != -1) { + // ERROR_LOG(JIT, "Out of registers at PC %08x - spills register %i.", mips_->pc, bestToSpill); + FlushArmReg((ARMReg)bestToSpill); + goto allocate; + } + + // Uh oh, we have all them spilllocked.... + ERROR_LOG(JIT, "Out of spillable registers at PC %08x!!!", mips_->pc); + return INVALID_REG; +} + +void ArmRegCache::MapInIn(MIPSReg rd, MIPSReg rs) { + SpillLock(rd, rs); + MapReg(rd); + MapReg(rs); + ReleaseSpillLocks(); +} + +void ArmRegCache::MapDirtyIn(MIPSReg rd, MIPSReg rs, bool avoidLoad) { + SpillLock(rd, rs); + bool overlap = avoidLoad && rd == rs; + MapReg(rd, MAP_DIRTY | (overlap ? 0 : MAP_NOINIT)); + MapReg(rs); + ReleaseSpillLocks(); +} + +void ArmRegCache::MapDirtyInIn(MIPSReg rd, MIPSReg rs, MIPSReg rt, bool avoidLoad) { + SpillLock(rd, rs, rt); + bool overlap = avoidLoad && (rd == rs || rd == rt); + MapReg(rd, MAP_DIRTY | (overlap ? 0 : MAP_NOINIT)); + MapReg(rt); + MapReg(rs); + ReleaseSpillLocks(); +} + +void ArmRegCache::FlushArmReg(ARMReg r) { + if (ar[r].mipsReg == -1) { + // Nothing to do, reg not mapped. + return; + } + if (ar[r].mipsReg != -1) { + if (ar[r].isDirty && mr[ar[r].mipsReg].loc == ML_ARMREG) + emit->STR(CTXREG, r, 4 * ar[r].mipsReg); + // IMMs won't be in an ARM reg. + mr[ar[r].mipsReg].loc = ML_MEM; + mr[ar[r].mipsReg].reg = INVALID_REG; + mr[ar[r].mipsReg].imm = 0; + } else { + ERROR_LOG(HLE, "Dirty but no mipsreg?"); + } + ar[r].isDirty = false; + ar[r].mipsReg = -1; +} + +void ArmRegCache::FlushMipsReg(MIPSReg r) { + switch (mr[r].loc) { + case ML_IMM: + // IMM is always "dirty". + emit->ARMABI_MOVI2R(R0, mr[r].imm); + emit->STR(CTXREG, R0, GetMipsRegOffset(r)); + break; + + case ML_ARMREG: + if (mr[r].reg == INVALID_REG) { + ERROR_LOG(HLE, "FlushMipsReg: MipsReg had bad ArmReg"); + } + if (ar[mr[r].reg].isDirty) { + emit->STR(CTXREG, mr[r].reg, GetMipsRegOffset(r)); + ar[mr[r].reg].isDirty = false; + } + ar[mr[r].reg].mipsReg = -1; + break; + + case ML_MEM: + // Already there, nothing to do. + break; + + default: + //BAD + break; + } + mr[r].loc = ML_MEM; + mr[r].reg = INVALID_REG; + mr[r].imm = 0; +} + +void ArmRegCache::FlushAll() { + for (int i = 0; i < NUM_MIPSREG; i++) { + FlushMipsReg(i); + } + // Sanity check + for (int i = 0; i < NUM_ARMREG; i++) { + if (ar[i].mipsReg != -1) { + ERROR_LOG(JIT, "Flush fail: ar[%i].mipsReg=%i", i, ar[i].mipsReg); + } + } +} + +void ArmRegCache::SetImm(MIPSReg r, u32 immVal) { + // Zap existing value if cached in a reg + if (mr[r].loc == ML_ARMREG) { + ar[mr[r].reg].mipsReg = -1; + ar[mr[r].reg].isDirty = false; + } + mr[r].loc = ML_IMM; + mr[r].imm = immVal; + mr[r].reg = INVALID_REG; +} + +bool ArmRegCache::IsImm(MIPSReg r) const { + return mr[r].loc == ML_IMM; +} + +u32 ArmRegCache::GetImm(MIPSReg r) const { + if (mr[r].loc != ML_IMM) { + ERROR_LOG(JIT, "Trying to get imm from non-imm register %i", r); + } + return mr[r].imm; +} + +int ArmRegCache::GetMipsRegOffset(MIPSReg r) { + if (r < 32) + return r * 4; + switch (r) { + case MIPSREG_HI: + return offsetof(MIPSState, hi); + case MIPSREG_LO: + return offsetof(MIPSState, lo); + } + ERROR_LOG(JIT, "bad mips register %i", r); + return 0; // or what? +} + +void ArmRegCache::SpillLock(MIPSReg r1, MIPSReg r2, MIPSReg r3) { + mr[r1].spillLock = true; + if (r2 != -1) mr[r2].spillLock = true; + if (r3 != -1) mr[r3].spillLock = true; +} + +void ArmRegCache::ReleaseSpillLocks() { + for (int i = 0; i < NUM_MIPSREG; i++) { + mr[i].spillLock = false; + } +} + +ARMReg ArmRegCache::R(int mipsReg) { + if (mr[mipsReg].loc == ML_ARMREG) { + return mr[mipsReg].reg; + } else { + ERROR_LOG(JIT, "Reg %i not in arm reg. compilerPC = %08x", mipsReg, compilerPC_); + return INVALID_REG; // BAAAD + } +} diff --git a/Core/MIPS/ARM/ArmRegCache.h b/Core/MIPS/ARM/ArmRegCache.h new file mode 100644 index 0000000000..e6adada891 --- /dev/null +++ b/Core/MIPS/ARM/ArmRegCache.h @@ -0,0 +1,115 @@ +// Copyright (C) 2003 Dolphin Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official SVN repository and contact information can be found at +// http://code.google.com/p/dolphin-emu/ + +#pragma once + +#include "ArmEmitter.h" +#include "../MIPS.h" +#include "../MIPSAnalyst.h" + +using namespace ArmGen; + +// R2 to R8: mapped MIPS regs +// R9 = code pointers +// R10 = MIPS context +// R11 = base pointer + +// Special MIPS registers: +enum { + MIPSREG_HI = 32, + MIPSREG_LO = 33, + TOTAL_MAPPABLE_MIPSREGS = 34, +}; + +typedef int MIPSReg; + +struct RegARM { + int mipsReg; // if -1, no mipsreg attached. + bool isDirty; // Should the register be written back? +}; + +enum RegMIPSLoc { + ML_IMM, + ML_ARMREG, + ML_MEM, +}; + +struct RegMIPS { + // Where is this MIPS register? + RegMIPSLoc loc; + // Data (only one of these is used, depending on loc. Could make a union). + u32 imm; + ARMReg reg; + bool spillLock; // if true, this register cannot be spilled. + // If loc == ML_MEM, it's back in its location in the CPU context struct. +}; + +// Initing is the default so the flag is reversed. +enum { + MAP_DIRTY = 1, + MAP_NOINIT = 2, +}; + +class ArmRegCache +{ +public: + ArmRegCache(MIPSState *mips); + ~ArmRegCache() {} + + void Init(ARMXEmitter *emitter); + void Start(MIPSAnalyst::AnalysisResults &stats); + + // Protect the arm register containing a MIPS register from spilling, to ensure that + // it's being kept allocated. + void SpillLock(MIPSReg reg, MIPSReg reg2 = -1, MIPSReg reg3 = -1); + void ReleaseSpillLocks(); + + void SetImm(MIPSReg reg, u32 immVal); + bool IsImm(MIPSReg reg) const; + u32 GetImm(MIPSReg reg) const; + + // Returns an ARM register containing the requested MIPS register. + ARMReg MapReg(MIPSReg reg, int mapFlags = 0); + void MapInIn(MIPSReg rd, MIPSReg rs); + void MapDirtyIn(MIPSReg rd, MIPSReg rs, bool avoidLoad = true); + void MapDirtyInIn(MIPSReg rd, MIPSReg rs, MIPSReg rt, bool avoidLoad = true); + void FlushArmReg(ARMReg r); + void FlushMipsReg(MIPSReg r); + + void FlushAll(); + + ARMReg R(int preg); // Returns a cached register + + void SetEmitter(ARMXEmitter *emitter) { emit = emitter; } + + // For better log output only. + void SetCompilerPC(u32 compilerPC) { compilerPC_ = compilerPC; } + +private: + int GetMipsRegOffset(MIPSReg r); + MIPSState *mips_; + ARMXEmitter *emit; + u32 compilerPC_; + + enum { + NUM_ARMREG = 16, + NUM_MIPSREG = TOTAL_MAPPABLE_MIPSREGS, + }; + + RegARM ar[NUM_ARMREG]; + RegMIPS mr[NUM_MIPSREG]; +}; diff --git a/Core/MIPS/ARM/Asm.cpp b/Core/MIPS/ARM/Asm.cpp deleted file mode 100644 index da68215c7b..0000000000 --- a/Core/MIPS/ARM/Asm.cpp +++ /dev/null @@ -1,195 +0,0 @@ -// Copyright (c) 2012- PPSSPP Project. - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 2.0 or later versions. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License 2.0 for more details. - -// A copy of the GPL 2.0 should have been included with the program. -// If not, see http://www.gnu.org/licenses/ - -// Official git repository and contact information can be found at -// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. - -#include "ABI.h" -#include - -#include "../../MemMap.h" - -#include "../MIPS.h" -#include "../../CoreTiming.h" -#include "MemoryUtil.h" - -#include "ABI.h" -#include "Jit.h" -#include "../JitCommon/JitCommon.h" -#include "../../Core.h" -#include "Asm.h" - -using namespace ArmGen; - -//static int temp32; // unused? - -//TODO - make an option -//#if _DEBUG -static bool enableDebug = false; -//#else -// bool enableDebug = false; -//#endif - -//static bool enableStatistics = false; //unused? - -//The standard ARM calling convention allocates the 16 ARM registers as: - -// r15 is the program counter. -// r14 is the link register. (The BL instruction, used in a subroutine call, stores the return address in this register). -// r13 is the stack pointer. (The Push/Pop instructions in "Thumb" operating mode use this register only). -// r12 is the Intra-Procedure-call scratch register. -// r4 to r11: used to hold local variables. -// r0 to r3: used to hold argument values passed to a subroutine, and also hold results returned from a subroutine. - -// R2, R3, R4, R5, R6, R7, R8, R10, R11 // omitting R9? - - -// STATIC ALLOCATION ARM: -// R11 : Memory base pointer. - -extern volatile CoreState coreState; - -void Jit() -{ - MIPSComp::jit->Compile(currentMIPS->pc); -} - -// PLAN: no more block numbers - crazy opcodes just contain offset within -// dynarec buffer -// At this offset - 4, there is an int specifying the block number. - -void AsmRoutineManager::Generate(MIPSState *mips, MIPSComp::Jit *jit) -{ - enterCode = AlignCode16(); - //ARMABI_PushAllCalleeSavedRegsAndAdjustStack(); -/* -#ifndef _M_IX86 - // Two statically allocated registers. - MOV(64, R(RBX), Imm64((u64)Memory::base)); - MOV(64, R(R15), Imm64((u64)jit->GetBlockCache()->GetCodePointers())); //It's below 2GB so 32 bits are good enough -#endif - */ - - SetCC(CC_AL); - //ARMABI_MOVIMM32(R11, (u32)Memory::base); - //ARMABI_MOVIMM32(R10, (u32)jit->GetBlockCache()->GetCodePointers()); - - outerLoop = GetCodePtr(); - //ARMABI_CallFunction(reinterpret_cast(&CoreTiming::Advance)); - FixupBranch skipToRealDispatch = B(); //skip the sync and compare first time - - dispatcher = GetCodePtr(); - // The result of slice decrementation should be in flags if somebody jumped here - // IMPORTANT - We jump on negative, not carry!!! - SetCC(CC_LT); - FixupBranch bail = B(); - - SetCC(CC_AL); - SetJumpTarget(skipToRealDispatch); - //INT3(); - - dispatcherNoCheck = GetCodePtr(); - // LDR(R0, R11, R0, M(&mips->pc)); - dispatcherPcInR0 = GetCodePtr(); - - _assert_msg_(CPU, Memory::base != 0, "Memory base bogus"); - //ARMABI_MOVIMM32(R1, Memory::MEMVIEW32_MASK + 1); - AND(R0, R0, R1); - LDR(R0, R11, R0); - MOV(R1, R0); - //AND(R1, R1, Imm32(MIPS_EMUHACK_MASK)); - //AND(R0, R0, Imm32(MIPS_EMUHACK_VALUE_MASK)); - //CMP(R0, Imm32(MIPS_EMUHACK_OPCODE)); - SetCC(CC_NEQ); - FixupBranch notfound = B(); - SetCC(CC_AL); - // IDEA - we have 24 bits, why not just use offsets from base of code? - if (enableDebug) - { - // ADD(32, M(&mips->debugCount), Imm8(1)); - } - //grab from list and jump to it - // ADD(R0, R10, LSL(R0, 2)); - B(R0); - SetJumpTarget(notfound); - - //BL(&Jit); - - //B(dispatcherNoCheck); // no point in special casing this - - SetJumpTarget(bail); - doTiming = GetCodePtr(); - - testExternalExceptions = GetCodePtr(); - //TEST(32, M((void *)&mips->exceptions), Imm32(MIPSState::BREAKING_EXCEPTIONS)); - //FixupBranch noExtException = J_CC(CC_Z); - //MOV(32, R(EAX), M(&mips->pc)); - // MOV(32, M(&NPC), R(EAX)); - // ABI_CallFunction(reinterpret_cast(&MIPSState::CheckExternalExceptions)); - //SetJumpTarget(noExtException); - - //CMP(M((void*)&coreState), Imm8(0)); - SetCC(CC_EQ); - //B(outerLoop); - SetCC(CC_AL); - - //Landing pad for drec space - //ARMABI_PopAllCalleeSavedRegsAndAdjustStack(); - B(_LR); - - breakpointBailout = GetCodePtr(); - - //Landing pad for drec space - //ARMABI_PopAllCalleeSavedRegsAndAdjustStack(); - //RET(); - - GenerateCommon(); -} - -void AsmRoutineManager::GenerateCommon() -{ - /* - fifoDirectWrite8 = AlignCode4(); - GenFifoWrite(8); - fifoDirectWrite16 = AlignCode4(); - GenFifoWrite(16); - fifoDirectWrite32 = AlignCode4(); - GenFifoWrite(32); - fifoDirectWriteFloat = AlignCode4(); - GenFifoFloatWrite(); - fifoDirectWriteXmm64 = AlignCode4(); - GenFifoXmm64Write(); - - GenQuantizedLoads(); - GenQuantizedStores(); - GenQuantizedSingleStores(); - */ - - //CMPSD(R(XMM0), M(&zero), - // TODO - - // Fast write routines - special case the most common hardware write - // TODO: use this. - // Even in x86, the param values will be in the right registers. - /* - const u8 *fastMemWrite8 = AlignCode16(); - CMP(32, R(ABI_PARAM2), Imm32(0xCC008000)); - FixupBranch skip_fast_write = J_CC(CC_NE, false); - MOV(32, EAX, M(&m_gatherPipeCount)); - MOV(8, MDisp(EAX, (u32)&m_gatherPipe), ABI_PARAM1); - ADD(32, 1, M(&m_gatherPipeCount)); - RET(); - SetJumpTarget(skip_fast_write); - CALL((void *)&Memory::Write_U8);*/ -} diff --git a/Core/MIPS/ARM/Asm.h b/Core/MIPS/ARM/Asm.h deleted file mode 100644 index 4897d885f4..0000000000 --- a/Core/MIPS/ARM/Asm.h +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright (c) 2012- PPSSPP Project. - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 2.0 or later versions. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License 2.0 for more details. - -// A copy of the GPL 2.0 should have been included with the program. -// If not, see http://www.gnu.org/licenses/ - -// Official git repository and contact information can be found at -// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. - -#ifndef _JIT64ASM_H -#define _JIT64ASM_H - -#include -#include "../MIPS.h" - -// In PPSSPP, we don't use inline assembly. Instead, we generate all machine-near -// code at runtime. In the case of fixed code like this, after writing it, we write -// protect the memory, essentially making it work just like precompiled code. - -// There are some advantages to this approach: -// 1) No need to setup an external assembler in the build. -// 2) Cross platform, as long as it's x86/x64. -// 3) Can optimize code at runtime for the specific CPU model. -// There aren't really any disadvantages other than having to maintain code emitters, -// which we have to do anyway :) -// -// To add a new asm routine, just add another const here, and add the code to Generate. -// Also, possibly increase the size of the code buffer. - -namespace MIPSComp -{ - class Jit; -} - -class AsmRoutineManager : public ArmGen::ARMXCodeBlock -{ -private: - void Generate(MIPSState *mips, MIPSComp::Jit *jit); - void GenerateCommon(); - -public: - AsmRoutineManager() - { - } - - void Init(MIPSState *mips, MIPSComp::Jit *jit) - { - AllocCodeSpace(8192); - Generate(mips, jit); - WriteProtect(); - } - ~AsmRoutineManager() - { - FreeCodeSpace(); - } - - const u8 *enterCode; - - const u8 *outerLoop; - const u8 *dispatcher; - const u8 *dispatcherNoCheck; - const u8 *dispatcherPcInR0; - - const u8 *fpException; - const u8 *testExceptions; - const u8 *testExternalExceptions; - const u8 *dispatchPcInEAX; - const u8 *doTiming; - - const u8 *breakpointBailout; -}; - -#endif // _JIT64ASM_H diff --git a/Core/MIPS/ARM/CompBranch.cpp b/Core/MIPS/ARM/CompBranch.cpp deleted file mode 100644 index e9112d5fcb..0000000000 --- a/Core/MIPS/ARM/CompBranch.cpp +++ /dev/null @@ -1,356 +0,0 @@ -// Copyright (c) 2012- PPSSPP Project. - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 2.0 or later versions. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License 2.0 for more details. - -// A copy of the GPL 2.0 should have been included with the program. -// If not, see http://www.gnu.org/licenses/ - -// Official git repository and contact information can be found at -// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. -#include "../../HLE/HLE.h" - -#include "../MIPS.h" -#include "../MIPSCodeUtils.h" -#include "../MIPSAnalyst.h" -#include "../MIPSTables.h" - -#include "Jit.h" -#include "RegCache.h" -#include "JitCache.h" -#include - -#define _RS ((op>>21) & 0x1F) -#define _RT ((op>>16) & 0x1F) -#define _RD ((op>>11) & 0x1F) -#define _FS ((op>>11) & 0x1F) -#define _FT ((op>>16) & 0x1F) -#define _FD ((op>>6 ) & 0x1F) -#define _POS ((op>>6 ) & 0x1F) -#define _SIZE ((op>>11 ) & 0x1F) - -#define LOOPOPTIMIZATION 0 - -using namespace MIPSAnalyst; - -namespace MIPSComp -{ - /* -void Jit::BranchRSRTComp(u32 op, Gen::CCFlags cc, bool likely) -{ - int offset = (signed short)(op&0xFFFF)<<2; - int rt = _RT; - int rs = _RS; - u32 targetAddr = js.compilerPC + offset + 4; - - u32 delaySlotOp = Memory::ReadUnchecked_U32(js.compilerPC+4); - - //Compile the delay slot - bool delaySlotIsNice = GetOutReg(delaySlotOp) != rt && GetOutReg(delaySlotOp) != rs;// IsDelaySlotNice(op, delaySlotOp); - if (!delaySlotIsNice) - { - ERROR_LOG(CPU, "Not nice delay slot in BranchRSRTComp :( %08x", js.compilerPC); - } - // The delay slot being nice doesn't really matter though... - - if (rs == 0) - { - CMP(32, gpr.R(rt), Imm32(0)); - } - else - { - gpr.BindToRegister(rs, true, false); - CMP(32, gpr.R(rs), rt == 0 ? Imm32(0) : gpr.R(rt)); - } - FlushAll(); - - js.inDelaySlot = true; - Gen::FixupBranch ptr; - if (!likely) - { - PUSHF(); // preserve flag around the delay slot! - CompileAt(js.compilerPC + 4); - FlushAll(); - POPF(); // restore flag! - ptr = J_CC(cc, true); - } - else - { - ptr = J_CC(cc, true); - CompileAt(js.compilerPC + 4); - FlushAll(); - } - js.inDelaySlot = false; - - // Take the branch - WriteExit(targetAddr, 0); - - SetJumpTarget(ptr); - // Not taken - WriteExit(js.compilerPC+8, 1); - - js.compiling = false; -} -*/ - /* -void Jit::BranchRSZeroComp(u32 op, Gen::CCFlags cc, bool likely) -{ - int offset = (signed short)(op&0xFFFF)<<2; - int rs = _RS; - u32 targetAddr = js.compilerPC + offset + 4; - - u32 delaySlotOp = Memory::ReadUnchecked_U32(js.compilerPC + 4); - - bool delaySlotIsNice = GetOutReg(delaySlotOp) != rs; - if (!delaySlotIsNice) - { - ERROR_LOG(CPU, "Not nice delay slot in BranchRSZeroComp :( %08x", js.compilerPC); - } - - gpr.BindToRegister(rs, true, false); - CMP(32, gpr.R(rs), Imm32(0)); - FlushAll(); - - Gen::FixupBranch ptr; - js.inDelaySlot = true; - if (!likely) - { - PUSHF(); // preserve flag around the delay slot! Better hope the delay slot instruction doesn't need to fall back to interpreter... - CompileAt(js.compilerPC + 4); - FlushAll(); - POPF(); // restore flag! - ptr = J_CC(cc, true); - } - else - { - ptr = J_CC(cc, true); - CompileAt(js.compilerPC + 4); - FlushAll(); - } - js.inDelaySlot = false; - - // Take the branch - WriteExit(targetAddr, 0); - - SetJumpTarget(ptr); - // Not taken - WriteExit(js.compilerPC + 8, 1); - js.compiling = false; -} -*/ - -void Jit::Comp_RelBranch(u32 op) -{ - /* - switch (op>>26) - { - case 4: BranchRSRTComp(op, CC_NZ, false); break;//beq - case 5: BranchRSRTComp(op, CC_Z, false); break;//bne - - case 6: BranchRSZeroComp(op, CC_G, false); break;//blez - case 7: BranchRSZeroComp(op, CC_LE, false); break;//bgtz - - case 20: BranchRSRTComp(op, CC_NZ, true); break;//beql - case 21: BranchRSRTComp(op, CC_Z, true); break;//bnel - - case 22: BranchRSZeroComp(op, CC_G, true); break;//blezl - case 23: BranchRSZeroComp(op, CC_LE, true); break;//bgtzl - - default: - _dbg_assert_msg_(CPU,0,"Trying to compile instruction that can't be compiled"); - break; - } - */ - js.compiling = false; -} - -void Jit::Comp_RelBranchRI(u32 op) -{ - /* - switch ((op >> 16) & 0x1F) - { - case 0: BranchRSZeroComp(op, CC_GE, false); break; //if ((s32)R(rs) < 0) DelayBranchTo(addr); else PC += 4; break;//bltz - case 1: BranchRSZeroComp(op, CC_L, false); break; //if ((s32)R(rs) >= 0) DelayBranchTo(addr); else PC += 4; break;//bgez - case 2: BranchRSZeroComp(op, CC_GE, true); break; //if ((s32)R(rs) < 0) DelayBranchTo(addr); else PC += 8; break;//bltzl - case 3: BranchRSZeroComp(op, CC_L, true); break; //if ((s32)R(rs) >= 0) DelayBranchTo(addr); else PC += 8; break;//bgezl - default: - _dbg_assert_msg_(CPU,0,"Trying to compile instruction that can't be compiled"); - break; - } - */ - js.compiling = false; -} - - -// If likely is set, discard the branch slot if NOT taken. -/* -void Jit::BranchFPFlag(u32 op, Gen::CCFlags cc, bool likely) -{ - int offset = (signed short)(op & 0xFFFF) << 2; - u32 targetAddr = js.compilerPC + offset + 4; - - u32 delaySlotOp = Memory::ReadUnchecked_U32(js.compilerPC + 4); - - bool delaySlotIsNice = IsDelaySlotNice(op, delaySlotOp); - if (!delaySlotIsNice) - { - ERROR_LOG(CPU, "Not nice delay slot in BranchFPFlag :("); - } - FlushAll(); - - TEST(32, M((void *)&(mips_->fpcond)), Imm32(1)); - Gen::FixupBranch ptr; - js.inDelaySlot = true; - if (!likely) - { - PUSHF(); // preserve flag around the delay slot! - CompileAt(js.compilerPC + 4); - FlushAll(); - POPF(); // restore flag! - ptr = J_CC(cc, true); - } - else - { - ptr = J_CC(cc, true); - CompileAt(js.compilerPC + 4); - FlushAll(); - } - js.inDelaySlot = false; - - // Take the branch - WriteExit(targetAddr, 0); - - SetJumpTarget(ptr); - // Not taken - WriteExit(js.compilerPC + 8, 1); - - js.compiling = false; -} -*/ - - -void Jit::Comp_FPUBranch(u32 op) -{ - /* - switch((op >> 16) & 0x1f) - { - case 0: BranchFPFlag(op, CC_NZ, false); break; // bc1f - case 1: BranchFPFlag(op, CC_Z, false); break; // bc1t - case 2: BranchFPFlag(op, CC_NZ, true); break; // bc1fl - case 3: BranchFPFlag(op, CC_Z, true); break; // bc1tl - default: - _dbg_assert_msg_(CPU,0,"Trying to interpret instruction that can't be interpreted"); - break; - } - */ - js.compiling = false; -} - -void Jit::Comp_VBranch(u32 op) -{ - /* - Comp_Generic(op + 4); - Comp_Generic(op); - js.compiling = false; - return; - - int imm = (signed short)(op&0xFFFF)<<2; - u32 targetAddr = js.compilerPC + imm + 4; - - int imm3 = (op >> 18) & 7; - int val = (mips_->vfpuCtrl[VFPU_CTRL_CC] >> imm3) & 1; - - switch ((op >> 16) & 3) - { - //case 0: if (!val) DelayBranchTo(addr); else PC += 4; break; //bvf - //case 1: if ( val) DelayBranchTo(addr); else PC += 4; break; //bvt - //case 2: if (!val) DelayBranchTo(addr); else PC += 8; break; //bvfl - //case 3: if ( val) DelayBranchTo(addr); else PC += 8; break; //bvtl - //TODO - } - js.compiling = false; - */ -} - -void Jit::Comp_Jump(u32 op) -{ - /* - u32 off = ((op & 0x3FFFFFF) << 2); - u32 targetAddr = (js.compilerPC & 0xF0000000) | off; - //Delay slot - CompileAt(js.compilerPC + 4); - FlushAll(); - - switch (op >> 26) - { - case 2: //j - WriteExit(targetAddr, 0); - break; - - case 3: //jal - MOV(32, M(&mips_->r[MIPS_REG_RA]), Imm32(js.compilerPC + 8)); // Save return address - WriteExit(targetAddr, 0); - break; - - default: - _dbg_assert_msg_(CPU,0,"Trying to compile instruction that can't be compiled"); - break; - } - js.compiling = false; - */ -} - -void Jit::Comp_JumpReg(u32 op) -{ - /* - int rs = _RS; - - u32 delaySlotOp = Memory::ReadUnchecked_U32(js.compilerPC + 4); - bool delaySlotIsNice = GetOutReg(delaySlotOp) != rs; - // Do what with that information? - - gpr.BindToRegister(rs, true, false); - PUSH(32, gpr.R(rs)); - CompileAt(js.compilerPC + 4); - FlushAll(); - POP(32, R(EAX)); - - switch (op & 0x3f) - { - case 8: //jr - break; - case 9: //jalr - MOV(32, M(&mips_->r[MIPS_REG_RA]), Imm32(js.compilerPC + 8)); - break; - default: - _dbg_assert_msg_(CPU,0,"Trying to compile instruction that can't be compiled"); - break; - } - - WriteExitDestInEAX(); - js.compiling = false; - */ -} - - -void Jit::Comp_Syscall(u32 op) -{ - /* - FlushAll(); - - MOV(32, R(EAX), M(&mips_->r[MIPS_REG_RA])); - MOV(32, M(&mips_->pc), R(EAX)); - - ABI_CallFunctionC(&CallSyscall, op); - - WriteSyscallExit();*/ - -} - -} // namespace Mipscomp diff --git a/Core/MIPS/ARM/CompLoadStore.cpp b/Core/MIPS/ARM/CompLoadStore.cpp deleted file mode 100644 index e8d8430447..0000000000 --- a/Core/MIPS/ARM/CompLoadStore.cpp +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright (c) 2012- PPSSPP Project. - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 2.0 or later versions. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License 2.0 for more details. - -// A copy of the GPL 2.0 should have been included with the program. -// If not, see http://www.gnu.org/licenses/ - -// Official git repository and contact information can be found at -// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. -#include "../../MemMap.h" -#include "../MIPSAnalyst.h" - -#include "Jit.h" -#include "RegCache.h" - - -#define _RS ((op>>21) & 0x1F) -#define _RT ((op>>16) & 0x1F) -#define _RD ((op>>11) & 0x1F) -#define _FS ((op>>11) & 0x1F) -#define _FT ((op>>16) & 0x1F) -#define _FD ((op>>6 ) & 0x1F) -#define _POS ((op>>6 ) & 0x1F) -#define _SIZE ((op>>11 ) & 0x1F) - -#define OLDD Comp_Generic(op); return; - -namespace MIPSComp -{ - void Jit::Comp_ITypeMem(u32 op) - { - OLDD - - int offset = (signed short)(op&0xFFFF); - int rt = _RT; - int rs = _RS; - int o = op>>26; - if (((op >> 29) & 1) == 0 && rt == 0) { - // Don't load anything into $zr - return; - } - switch (o) - { - case 37: //R(rt) = ReadMem16(addr); break; //lhu - case 36: //R(rt) = ReadMem8 (addr); break; //lbu - Comp_Generic(op); - return; - - case 35: //R(rt) = ReadMem32(addr); break; //lw - /* - gpr.Lock(rt, rs); - gpr.BindToRegister(rt, rt == rs, true); - MOV(32, R(EAX), gpr.R(rs)); - AND(32, R(EAX), Imm32(Memory::MEMVIEW32_MASK)); - MOV(32, gpr.R(rt), MDisp(EAX, (u32)Memory::base + offset)); - gpr.UnlockAll();*/ - break; - - case 132: //R(rt) = (u32)(s32)(s8) ReadMem8 (addr); break; //lb - case 133: //R(rt) = (u32)(s32)(s16)ReadMem16(addr); break; //lh - case 136: //R(rt) = ReadMem8 (addr); break; //lbu - case 140: //WriteMem8 (addr, R(rt)); break; //sb - - case 40: - case 41: //WriteMem16(addr, R(rt)); break; //sh - Comp_Generic(op); - return; - - case 43: //WriteMem32(addr, R(rt)); break; //sw - /* - { - gpr.Lock(rt, rs); - gpr.BindToRegister(rt, true, false); - MOV(32, R(EAX), gpr.R(rs)); - AND(32, R(EAX), Imm32(Memory::MEMVIEW32_MASK)); - MOV(32, MDisp(EAX, (u32)Memory::base + offset), gpr.R(rt)); - gpr.UnlockAll(); - }*/ - - break; - - case 134: //lwl - { - Crash(); - //u32 shift = (addr & 3) << 3; - //u32 mem = ReadMem32(addr & 0xfffffffc); - //R(rt) = ( u32(R(rt)) & (0x00ffffff >> shift) ) | ( mem << (24 - shift) ); - } - break; - - case 138: //lwr - { - Crash(); - //u32 shift = (addr & 3) << 3; - //u32 mem = ReadMem32(addr & 0xfffffffc); - - //R(rt) = ( u32(rt) & (0xffffff00 << (24 - shift)) ) | ( mem >> shift ); - } - break; - - case 142: //swl - { - Crash(); - //u32 shift = (addr & 3) << 3; - //u32 mem = ReadMem32(addr & 0xfffffffc); - //WriteMem32((addr & 0xfffffffc), ( ( u32(R(rt)) >> (24 - shift) ) ) | - // ( mem & (0xffffff00 << shift) )); - } - break; - case 146: //swr - { - Crash(); - // u32 shift = (addr & 3) << 3; - // u32 mem = ReadMem32(addr & 0xfffffffc); -// -// WriteMem32((addr & 0xfffffffc), ( ( u32(R(rt)) << shift ) | -// (mem & (0x00ffffff >> (24 - shift)) ) ) ); - } - break; - default: - Comp_Generic(op); - return ; - } - - } -} diff --git a/Core/MIPS/ARM/Jit.cpp b/Core/MIPS/ARM/Jit.cpp deleted file mode 100644 index c1e55cada4..0000000000 --- a/Core/MIPS/ARM/Jit.cpp +++ /dev/null @@ -1,236 +0,0 @@ -// Copyright (c) 2012- PPSSPP Project. - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 2.0 or later versions. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License 2.0 for more details. - -// A copy of the GPL 2.0 should have been included with the program. -// If not, see http://www.gnu.org/licenses/ - -// Official git repository and contact information can be found at -// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. - -#include "../../Core.h" -#include "../../CoreTiming.h" -#include "../MIPS.h" -#include "../MIPSCodeUtils.h" -#include "../MIPSInt.h" -#include "../MIPSTables.h" - -#include "RegCache.h" -#include "Jit.h" - - -extern u32 *pspmainram; - -namespace MIPSComp -{ -/* -void Comp_MemRead32KnownRAM(int srcreg, int offset, int reg) -{ - MOV_MemoryToReg(1, reg, ModRM_disp32_EAX+srcreg, (u32)pspmainram - 0x08000000 + offset); -} -void Comp_MemWrite32KnownRAM(int srcreg, int offset, int reg) -{ - MOV_RegToMemory(1, reg, ModRM_disp32_EAX+srcreg, (u32)pspmainram - 0x08000000 + offset); -} - -#define RM(reg) (&(currentMIPS->r[reg])) -struct RegInfo -{ - bool cached; - int x86reg; -}; - -RegInfo regs[32]; - -void MovToReg(int reg, u32 value) -{ - if (regs[reg].cached) - { - ::MOV_ImmToReg(1,regs[reg].x86reg,value,0); - } - else - { - ::MOV_ImmToMemory(1,ModRM_disp32,(u32)(RM(reg)),value); - } -} -*/ - -Jit::Jit(MIPSState *mips) : blocks(mips), mips_(mips) -{ - blocks.Init(); - asm_.Init(mips, this); - gpr.SetEmitter(this); - fpr.SetEmitter(this); - AllocCodeSpace(1024 * 1024 * 16); -} - - -void Jit::FlushAll() -{ - gpr.Flush(FLUSH_ALL); - fpr.Flush(FLUSH_ALL); -} - -void Jit::ClearCache() -{ - blocks.Clear(); - ClearCodeSpace(); -} - -u8 *codeCache; -#define CACHESIZE 16384*1024 -void Jit::CompileAt(u32 addr) -{ - u32 op = Memory::Read_Instruction(addr); - MIPSCompileOp(op); -} - -void Jit::Compile(u32 em_address) -{ - if (GetSpaceLeft() < 0x10000 || blocks.IsFull()) - { - ClearCache(); - } - - int block_num = blocks.AllocateBlock(em_address); - JitBlock *b = blocks.GetBlock(block_num); - blocks.FinalizeBlock(block_num, jo.enableBlocklink, DoJit(em_address, b)); -} - -void Jit::RunLoopUntil(u64 globalticks) -{ - // TODO: copy globalticks somewhere - ((void (*)())asm_.enterCode)(); -} - -const u8 *Jit::DoJit(u32 em_address, JitBlock *b) -{ - js.cancel = false; - js.blockStart = js.compilerPC = mips_->pc; - js.downcountAmount = 0; - js.curBlock = b; - js.compiling = true; - js.inDelaySlot = false; - - b->normalEntry = GetCodePtr(); - - // TODO: this needs work - MIPSAnalyst::AnalysisResults analysis; // = MIPSAnalyst::Analyze(em_address); - - gpr.Start(mips_, analysis); - fpr.Start(mips_, analysis); - - int numInstructions = 0; - int cycles = 0; - while (js.compiling) - { - u32 inst = Memory::Read_Instruction(js.compilerPC); - js.downcountAmount += MIPSGetInstructionCycleEstimate(inst); - - MIPSCompileOp(inst); - - js.compilerPC += 4; - numInstructions++; - } - - b->codeSize = GetCodePtr() - b->normalEntry; - NOP(); - AlignCode16(); - b->originalSize = numInstructions; - return b->normalEntry; -} - -void Jit::Comp_RunBlock(u32 op) -{ - // This shouldn't be necessary, the dispatcher should catch us before we get here. - ERROR_LOG(DYNA_REC, "Comp_RunBlock"); -} - -void Jit::Comp_Generic(u32 op) -{ - FlushAll(); - MIPSInterpretFunc func = MIPSGetInterpretFunc(op); - if (func) - { - //MOV(32, M(&mips_->pc), Imm32(js.compilerPC)); - //ABI_CallFunctionC(func, op); - } -} - -void Jit::WriteExit(u32 destination, int exit_num) -{ - //SUB(32, M(&CoreTiming::downcount), js.downcountAmount > 127 ? Imm32(js.downcountAmount) : Imm8(js.downcountAmount)); - - //If nobody has taken care of this yet (this can be removed when all branches are done) - JitBlock *b = js.curBlock; - b->exitAddress[exit_num] = destination; - b->exitPtrs[exit_num] = GetWritableCodePtr(); - - // Link opportunity! - int block = blocks.GetBlockNumberFromStartAddress(destination); - if (block >= 0 && jo.enableBlocklink) - { - // It exists! Joy of joy! - //JMP(blocks.GetBlock(block)->checkedEntry, true); - b->linkStatus[exit_num] = true; - } - else - { - //MOV(32, M(&mips_->pc), Imm32(destination)); - //JMP(asm_.dispatcher, true); - } -} - -void Jit::WriteExitDestInEAX() -{ - /* - MOV(32, M(&mips_->pc), R(EAX)); - SUB(32, M(&CoreTiming::downcount), js.downcountAmount > 127 ? Imm32(js.downcountAmount) : Imm8(js.downcountAmount)); - JMP(asm_.dispatcher, true); - */ -} -/* -void Jit::WriteRfiExitDestInEAX() -{ - MOV(32, M(&mips_->pc), R(EAX)); - SUB(32, M(&CoreTiming::downcount), js.downcountAmount > 127 ? Imm32(js.downcountAmount) : Imm8(js.downcountAmount)); - JMP(asm_routines.testExceptions, true); -}*/ - -void Jit::WriteSyscallExit() -{ - - /* - SUB(32, M(&CoreTiming::downcount), js.downcountAmount > 127 ? Imm32(js.downcountAmount) : Imm8(js.downcountAmount)); - JMP(asm_.dispatcher, true); - */ -} - -#define _RS ((op>>21) & 0x1F) -#define _RT ((op>>16) & 0x1F) -#define _RD ((op>>11) & 0x1F) -#define _FS ((op>>11) & 0x1F) -#define _FT ((op>>16) & 0x1F) -#define _FD ((op>>6) & 0x1F) -#define _POS ((op>>6) & 0x1F) -#define _SIZE ((op>>11) & 0x1F) - -//memory regions: -// -// 08-0A -// 48-4A -// 04-05 -// 44-45 -// mov eax, addrreg - // shr eax, 28 -// mov eax, [table+eax] -// mov dreg, [eax+offreg] - -} diff --git a/Core/MIPS/ARM/RegCache.cpp b/Core/MIPS/ARM/RegCache.cpp deleted file mode 100644 index 30f8f4dbe3..0000000000 --- a/Core/MIPS/ARM/RegCache.cpp +++ /dev/null @@ -1,409 +0,0 @@ -// Copyright (C) 2003 Dolphin Project. - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 2.0 or later versions. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License 2.0 for more details. - -// A copy of the GPL 2.0 should have been included with the program. -// If not, see http://www.gnu.org/licenses/ - -// Official SVN repository and contact information can be found at -// http://code.google.com/p/dolphin-emu/ - -#include "../MIPS.h" -#include "../MIPSTables.h" -#include "../MIPSAnalyst.h" -#include "Jit.h" -#include "Asm.h" -#include "RegCache.h" -#include "CommonFuncs.h" - -using namespace ArmGen; - -RegCache::RegCache() : emit(0) { - memset(locks, 0, sizeof(locks)); - memset(xlocks, 0, sizeof(xlocks)); - memset(saved_locks, 0, sizeof(saved_locks)); - memset(saved_xlocks, 0, sizeof(saved_xlocks)); - memset(regs, 0, sizeof(regs)); - memset(xregs, 0, sizeof(xregs)); - memset(saved_regs, 0, sizeof(saved_regs)); - memset(saved_xregs, 0, sizeof(saved_xregs)); -} - -static const int allocationOrder[] = -{ - R2, R3, R4, R5, R6, R7, R8, R10, R11 // omitting R9? -}; - -void RegCache::Start(MIPSState *mips, MIPSAnalyst::AnalysisResults &stats) -{ - for (int i = 0; i < NUMARMREGS; i++) - { - xregs[i].free = true; - xregs[i].dirty = false; - xlocks[i] = false; - } - for (int i = 0; i < 32; i++) - { - regs[i].location = GetDefaultLocation(i); - regs[i].away = false; - } - - // todo: sort to find the most popular regs - /* - int maxPreload = 2; - for (int i = 0; i < 32; i++) - { - if (stats.numReads[i] > 2 || stats.numWrites[i] >= 2) - { - LoadToX64(i, true, false); //stats.firstRead[i] <= stats.firstWrite[i], false); - maxPreload--; - if (!maxPreload) - break; - } - }*/ - //Find top regs - preload them (load bursts ain't bad) - //But only preload IF written OR reads >= 3 -} - -// these are MIPS reg indices -void RegCache::Lock(int p1, int p2, int p3, int p4) -{ - locks[p1] = true; - if (p2 != 0xFF) locks[p2] = true; - if (p3 != 0xFF) locks[p3] = true; - if (p4 != 0xFF) locks[p4] = true; -} - -// these are x64 reg indices -void RegCache::LockX(int x1, int x2, int x3, int x4) -{ - if (xlocks[x1]) { - PanicAlert("RegCache: x %i already locked!", x1); - } - xlocks[x1] = true; - if (x2 != 0xFF) xlocks[x2] = true; - if (x3 != 0xFF) xlocks[x3] = true; - if (x4 != 0xFF) xlocks[x4] = true; -} - -bool RegCache::IsFreeX(int xreg) const -{ - return xregs[xreg].free && !xlocks[xreg]; -} - -void RegCache::UnlockAll() -{ - for (int i = 0; i < 32; i++) - locks[i] = false; -} - -void RegCache::UnlockAllX() -{ - for (int i = 0; i < NUMARMREGS; i++) - xlocks[i] = false; -} - -ARMReg RegCache::GetFreeXReg() -{ - int aCount; - const int *aOrder = GetAllocationOrder(aCount); - for (int i = 0; i < aCount; i++) - { - ARMReg xr = (ARMReg)aOrder[i]; - if (!xlocks[xr] && xregs[xr].free) - { - return (ARMReg)xr; - } - } - //Okay, not found :( Force grab one - - //TODO - add a pass to grab xregs whose ppcreg is not used in the next 3 instructions - for (int i = 0; i < aCount; i++) - { - ARMReg xr = (ARMReg)aOrder[i]; - if (xlocks[xr]) - continue; - int preg = xregs[xr].ppcReg; - if (!locks[preg]) - { - StoreFromRegister(preg); - return xr; - } - } - //Still no dice? Die! - _assert_msg_(DYNA_REC, 0, "Regcache ran out of regs"); - return (ARMReg) -1; -} - -void RegCache::SaveState() -{ - memcpy(saved_locks, locks, sizeof(locks)); - memcpy(saved_xlocks, xlocks, sizeof(xlocks)); - memcpy(saved_regs, regs, sizeof(regs)); - memcpy(saved_xregs, xregs, sizeof(xregs)); -} - -void RegCache::LoadState() -{ - memcpy(xlocks, saved_xlocks, sizeof(xlocks)); - memcpy(locks, saved_locks, sizeof(locks)); - memcpy(regs, saved_regs, sizeof(regs)); - memcpy(xregs, saved_xregs, sizeof(xregs)); -} - -void RegCache::FlushR(ARMReg reg) -{ - if (reg >= NUMARMREGS) - PanicAlert("Flushing non existent reg"); - if (!xregs[reg].free) - { - StoreFromRegister(xregs[reg].ppcReg); - } -} - -int RegCache::SanityCheck() const -{ - for (int i = 0; i < 32; i++) { - if (regs[i].away) { - if (regs[i].location.IsSimpleReg()) { - ARMReg simple = regs[i].location.GetSimpleReg(); - if (xlocks[simple]) - return 1; - if (xregs[simple].ppcReg != i) - return 2; - } - else if (regs[i].location.IsImm()) - return 3; - } - } - return 0; -} - -void RegCache::DiscardRegContentsIfCached(int preg) -{ - if (regs[preg].away && regs[preg].location.IsSimpleReg()) - { - ARMReg xr = regs[preg].location.GetSimpleReg(); - xregs[xr].free = true; - xregs[xr].dirty = false; - xregs[xr].ppcReg = -1; - regs[preg].away = false; - regs[preg].location = GetDefaultLocation(preg); - } -} - - -void GPRRegCache::SetImmediate32(int preg, u32 immValue) -{ - //if (regs[preg].away == true && regs[preg].location.IsImm() && regs[preg].location.offset == immValue) - // return; - DiscardRegContentsIfCached(preg); - regs[preg].away = true; - regs[preg].location.SetImm32(immValue); -} - -void GPRRegCache::Start(MIPSState *mips, MIPSAnalyst::AnalysisResults &stats) -{ - RegCache::Start(mips, stats); -} - -void FPURegCache::Start(MIPSState *mips, MIPSAnalyst::AnalysisResults &stats) -{ - RegCache::Start(mips, stats); -} - -const int *GPRRegCache::GetAllocationOrder(int &count) -{ - count = sizeof(allocationOrder) / sizeof(const int); - return allocationOrder; -} - -const int *FPURegCache::GetAllocationOrder(int &count) -{ - static const int allocationOrder[] = - { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 - }; - count = sizeof(allocationOrder) / sizeof(int); - return allocationOrder; -} - -Location GPRRegCache::GetDefaultLocation(int reg) const -{ - Location loc; - loc.SetM(&mips->r[reg]); - return loc; -} - -Location FPURegCache::GetDefaultLocation(int reg) const -{ - Location loc; - loc.SetM(&mips->f[reg]); - return loc; -} - -void RegCache::KillImmediate(int preg, bool doLoad, bool makeDirty) -{ - if (regs[preg].away) - { - if (regs[preg].location.IsImm()) - BindToRegister(preg, doLoad, makeDirty); - else if (regs[preg].location.IsSimpleReg()) - xregs[RX(preg)].dirty |= makeDirty; - } -} - -void GPRRegCache::BindToRegister(int i, bool doLoad, bool makeDirty) -{ - if (!regs[i].away && regs[i].location.IsImm()) - PanicAlert("Bad immediate"); - - if (!regs[i].away || (regs[i].away && regs[i].location.IsImm())) - { - ARMReg xr = GetFreeXReg(); - if (xregs[xr].dirty) PanicAlert("Xreg already dirty"); - if (xlocks[xr]) PanicAlert("GetFreeXReg returned locked register"); - xregs[xr].free = false; - xregs[xr].ppcReg = i; - xregs[xr].dirty = makeDirty || regs[i].location.IsImm(); - Location newloc; - newloc.SetReg(xr); - - //if (doLoad) - // emit->MOV(32, newloc, regs[i].location); - for (int j = 0; j < 32; j++) - { - if (i != j && regs[j].location.IsSimpleReg() && regs[j].location.GetSimpleReg() == xr) - { - PanicAlert("bad"); - } - } - regs[i].away = true; - regs[i].location = newloc; - } - else - { - // reg location must be simplereg; memory locations - // and immediates are taken care of above. - xregs[RX(i)].dirty |= makeDirty; - } - if (xlocks[RX(i)]) { - PanicAlert("Seriously WTF, this reg should have been flushed"); - } -} - -void GPRRegCache::StoreFromRegister(int i) -{ - if (regs[i].away) - { - bool doStore; - if (regs[i].location.IsSimpleReg()) - { - ARMReg xr = RX(i); - xregs[xr].free = true; - xregs[xr].ppcReg = -1; - doStore = xregs[xr].dirty; - xregs[xr].dirty = false; - } - else - { - //must be immediate - do nothing - doStore = true; - } - Location newLoc = GetDefaultLocation(i); - if (doStore) - ; - // emit->MOV(32, newLoc, regs[i].location); - regs[i].location = newLoc; - regs[i].away = false; - } -} - -void FPURegCache::BindToRegister(int i, bool doLoad, bool makeDirty) -{ - _assert_msg_(DYNA_REC, !regs[i].location.IsImm(), "WTF - load - imm"); - if (!regs[i].away) - { - // Reg is at home in the memory register file. Let's pull it out. - ARMReg xr = GetFreeXReg(); - _assert_msg_(DYNA_REC, xr < NUMARMREGS, "WTF - load - invalid reg"); - xregs[xr].ppcReg = i; - xregs[xr].free = false; - xregs[xr].dirty = makeDirty; - Location newloc; - newloc.SetReg(xr); - if (doLoad) - { - //if (!regs[i].location.IsImm() && (regs[i].location.offset & 0xF)) - //{ - // PanicAlert("WARNING - misaligned fp register location %i", i); - //} - //emit->MOVAPD(xr, regs[i].location); - } - regs[i].location = newloc; - regs[i].away = true; - } else { - // There are no immediates in the FPR reg file, so we already had this in a register. Make dirty as necessary. - xregs[RX(i)].dirty |= makeDirty; - } -} - -void FPURegCache::StoreFromRegister(int i) -{ - _assert_msg_(DYNA_REC, !regs[i].location.IsImm(), "WTF - store - imm"); - if (regs[i].away) - { - ARMReg xr = regs[i].location.GetSimpleReg(); - _assert_msg_(DYNA_REC, xr < NUMARMREGS, "WTF - store - invalid reg"); - xregs[xr].free = true; - xregs[xr].dirty = false; - xregs[xr].ppcReg = -1; - Location newLoc = GetDefaultLocation(i); - // emit->MOVAPD(newLoc, xr); - regs[i].location = newLoc; - regs[i].away = false; - } - else - { - // _assert_msg_(DYNA_REC,0,"already stored"); - } -} - -void RegCache::Flush(FlushMode mode) -{ - for (int i = 0; i < NUMARMREGS; i++) { - if (xlocks[i]) - PanicAlert("Someone forgot to unlock X64 reg %i.", i); - } - for (int i = 0; i < 32; i++) - { - if (locks[i]) - { - PanicAlert("Somebody forgot to unlock PPC reg %i.", i); - } - if (regs[i].away) - { - if (regs[i].location.IsSimpleReg()) - { - ARMReg xr = RX(i); - StoreFromRegister(i); - xregs[xr].dirty = false; - } - else if (regs[i].location.IsImm()) - { - StoreFromRegister(i); - } - else - { - _assert_msg_(DYNA_REC,0,"Jit64 - Flush unhandled case, reg %i PC: %08x", i, mips->pc); - } - } - } -} diff --git a/Core/MIPS/ARM/RegCache.h b/Core/MIPS/ARM/RegCache.h deleted file mode 100644 index afab156b52..0000000000 --- a/Core/MIPS/ARM/RegCache.h +++ /dev/null @@ -1,170 +0,0 @@ -// Copyright (C) 2003 Dolphin Project. - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 2.0 or later versions. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License 2.0 for more details. - -// A copy of the GPL 2.0 should have been included with the program. -// If not, see http://www.gnu.org/licenses/ - -// Official SVN repository and contact information can be found at -// http://code.google.com/p/dolphin-emu/ - -#pragma once - -#include "../MIPSAnalyst.h" -#include - -using namespace ArmGen; -enum FlushMode -{ - FLUSH_ALL -}; - -enum GrabMode -{ - M_READ = 1, - M_WRITE = 2, - M_READWRITE = 3, -}; - -enum Loc { - LOC_IMM, - LOC_REG, - LOC_MEM -}; - -struct Location -{ - Loc loc; - bool IsSimpleReg() const {return loc == LOC_REG;} - ARMReg GetSimpleReg() const {return reg;} - bool IsImm() const { return loc == LOC_IMM; } - void SetImm32(u32 i) {loc = LOC_IMM; imm = i;} - void SetM(void *p) {loc = LOC_MEM; ptr = (u32 *)p;} - void SetReg(ARMReg r) {loc = LOC_REG; reg = r;} - - union { - u32 *ptr; - ARMReg reg; - u32 imm; - }; -}; - -struct MIPSCachedReg -{ - Location location; - bool away; // value not in source register -}; - -struct ARMCachedReg -{ - int ppcReg; - bool dirty; - bool free; -}; - -typedef int XReg; -typedef int PReg; - -#define NUMARMREGS 15 - -class RegCache -{ -private: - bool locks[32]; - bool saved_locks[32]; - bool saved_xlocks[NUMARMREGS]; - -protected: - bool xlocks[NUMARMREGS]; - MIPSCachedReg regs[32]; - ARMCachedReg xregs[NUMARMREGS]; - - MIPSCachedReg saved_regs[32]; - ARMCachedReg saved_xregs[NUMARMREGS]; - - virtual const int *GetAllocationOrder(int &count) = 0; - - ARMXEmitter *emit; - -public: - MIPSState *mips; - RegCache(); - - virtual ~RegCache() {} - virtual void Start(MIPSState *mips, MIPSAnalyst::AnalysisResults &stats) = 0; - - void DiscardRegContentsIfCached(int preg); - void SetEmitter(ARMXEmitter *emitter) {emit = emitter;} - - void FlushR(ARMReg reg); - void FlushR(ARMReg reg, ARMReg reg2) {FlushR(reg); FlushR(reg2);} - void FlushLockX(ARMReg reg) { - FlushR(reg); - LockX(reg); - } - void FlushLockX(ARMReg reg1, ARMReg reg2) { - FlushR(reg1); FlushR(reg2); - LockX(reg1); LockX(reg2); - } - virtual void Flush(FlushMode mode); - // virtual void Flush(PPCAnalyst::CodeOp *op) {Flush(FLUSH_ALL);} - int SanityCheck() const; - void KillImmediate(int preg, bool doLoad, bool makeDirty); - - //TODO - instead of doload, use "read", "write" - //read only will not set dirty flag - virtual void BindToRegister(int preg, bool doLoad = true, bool makeDirty = true) = 0; - virtual void StoreFromRegister(int preg) = 0; - - const Location &R(int preg) const {return regs[preg].location;} - ARMReg RX(int preg) const - { - if (regs[preg].away && regs[preg].location.IsSimpleReg()) - return regs[preg].location.GetSimpleReg(); - PanicAlert("Not so simple - %i", preg); - return (ARMReg)-1; - } - virtual Location GetDefaultLocation(int reg) const = 0; - - // Register locking. A locked registers will not be spilled when trying to find a new free register. - void Lock(int p1, int p2=0xff, int p3=0xff, int p4=0xff); - void LockX(int x1, int x2=0xff, int x3=0xff, int x4=0xff); - void UnlockAll(); - void UnlockAllX(); - - bool IsFreeX(int xreg) const; - - ARMReg GetFreeXReg(); - - void SaveState(); - void LoadState(); -}; - -class GPRRegCache : public RegCache -{ -public: - void Start(MIPSState *mips, MIPSAnalyst::AnalysisResults &stats); - void BindToRegister(int preg, bool doLoad = true, bool makeDirty = true); - void StoreFromRegister(int preg); - Location GetDefaultLocation(int reg) const; - const int *GetAllocationOrder(int &count); - void SetImmediate32(int preg, u32 immValue); -}; - - -class FPURegCache : public RegCache -{ -public: - void Start(MIPSState *mips, MIPSAnalyst::AnalysisResults &stats); - void BindToRegister(int preg, bool doLoad = true, bool makeDirty = true); - void StoreFromRegister(int preg); - const int *GetAllocationOrder(int &count); - Location GetDefaultLocation(int reg) const; -}; diff --git a/Core/MIPS/JitCommon/JitCommon.h b/Core/MIPS/JitCommon/JitCommon.h index a7b4673484..a8630e6fbe 100644 --- a/Core/MIPS/JitCommon/JitCommon.h +++ b/Core/MIPS/JitCommon/JitCommon.h @@ -18,7 +18,7 @@ #pragma once #if defined(ARM) -#include "../ARM/Jit.h" +#include "../ARM/ArmJit.h" #else #include "../x86/Jit.h" #endif diff --git a/Core/MIPS/MIPS.cpp b/Core/MIPS/MIPS.cpp index d784419212..b7c3a39323 100644 --- a/Core/MIPS/MIPS.cpp +++ b/Core/MIPS/MIPS.cpp @@ -24,8 +24,8 @@ #include "../HLE/sceDisplay.h" #if defined(ARM) -#include "ARM/JitCache.h" -#include "ARM/Jit.h" +#include "ARM/ArmJitCache.h" +#include "ARM/ArmJit.h" #else #include "x86/JitCache.h" #include "x86/Jit.h" @@ -40,7 +40,6 @@ MIPSDebugInterface *currentDebugMIPS = &debugr4k; MIPSState::MIPSState() { - cpuType = CPUTYPE_ALLEGREX; MIPSComp::jit = 0; } @@ -57,7 +56,7 @@ void MIPSState::Reset() delete MIPSComp::jit; MIPSComp::jit = 0; } - + if (PSP_CoreParameter().cpuCore == CPU_JIT) MIPSComp::jit = new MIPSComp::Jit(this); @@ -90,11 +89,11 @@ void MIPSState::Reset() fcr0 = 0; fcr31 = 0; debugCount = 0; - exceptions = 0; currentMIPS = this; inDelaySlot = false; llBit = 0; nextPC = 0; + downcount = 0; // Initialize the VFPU random number generator with .. something? rng.Init(0x1337); } @@ -112,6 +111,7 @@ void MIPSState::DoState(PointerWrap &p) p.DoArray(vfpuWriteMask, sizeof(vfpuWriteMask) / sizeof(vfpuWriteMask[0])); p.Do(pc); p.Do(nextPC); + p.Do(downcount); p.Do(hi); p.Do(lo); p.Do(fpcond); @@ -120,8 +120,6 @@ void MIPSState::DoState(PointerWrap &p) rng.DoState(p); p.Do(inDelaySlot); p.Do(llBit); - p.Do(cpuType); - p.Do(exceptions); p.Do(debugCount); p.DoMarker("MIPSState"); } @@ -135,7 +133,7 @@ void MIPSState::SetWriteMask(const bool wm[4]) void MIPSState::SingleStep() { int cycles = MIPS_SingleStep(); - CoreTiming::downcount -= cycles; + currentMIPS->downcount -= cycles; CoreTiming::Advance(); } diff --git a/Core/MIPS/MIPS.h b/Core/MIPS/MIPS.h index 1e8bd29a6f..0bf9c6c529 100644 --- a/Core/MIPS/MIPS.h +++ b/Core/MIPS/MIPS.h @@ -104,7 +104,7 @@ private: u32 m_z; }; -class MIPSState : public CPU +class MIPSState { public: MIPSState(); @@ -113,6 +113,7 @@ public: void Reset(); void DoState(PointerWrap &p); + // MUST start with r! u32 r[32]; float f[32]; float v[128]; @@ -121,24 +122,20 @@ public: u32 pc; u32 nextPC; + u32 downcount; // This really doesn't belong here, it belongs in CoreTiming. But you gotta do what you gotta do, this needs to be reachable in the ARM JIT. + u32 hi; u32 lo; - u32 fpcond; //separate for speed - TODO: not worth it - u32 fcr0; u32 fcr31; //fpu control register - - GMRng rng; // VFPU hardware random number generator. Probably not the right type. + u32 fpcond; // cache the cond flag of fcr31 (& 1 << 23) bool inDelaySlot; int llBit; // ll/sc - CPUType cpuType; - // TODO: How do we handle exceptions? - u32 exceptions; - enum { BREAKING_EXCEPTIONS = 1 }; + GMRng rng; // VFPU hardware random number generator. Probably not the right type. // Debug stuff u32 debugCount; // can be used to count basic blocks before crashes, etc. diff --git a/Core/MIPS/MIPSDebugInterface.cpp b/Core/MIPS/MIPSDebugInterface.cpp index 6c4d7d3d07..c925aaff51 100644 --- a/Core/MIPS/MIPSDebugInterface.cpp +++ b/Core/MIPS/MIPSDebugInterface.cpp @@ -28,7 +28,7 @@ const char *MIPSDebugInterface::disasm(unsigned int address, unsigned int align) { - CPU *x = currentCPU; + MIPSState *x = currentCPU; currentCPU = cpu; static char mojs[256]; diff --git a/Core/MIPS/MIPSInt.cpp b/Core/MIPS/MIPSInt.cpp index c3bcfe4ab9..13d0d90d5d 100644 --- a/Core/MIPS/MIPSInt.cpp +++ b/Core/MIPS/MIPSInt.cpp @@ -272,6 +272,7 @@ namespace MIPSInt // There's one of these in Star Soldier at 0881808c, which seems benign - it should probably be ignored. if (op == 0x03e00008) return; + ERROR_LOG(HLE, "Jump in delay slot :("); _dbg_assert_msg_(CPU,0,"Jump in delay slot :("); } diff --git a/Core/MIPS/MIPSTables.cpp b/Core/MIPS/MIPSTables.cpp index b16044e73f..f30413c8a3 100644 --- a/Core/MIPS/MIPSTables.cpp +++ b/Core/MIPS/MIPSTables.cpp @@ -885,7 +885,7 @@ void MIPSCompileOp(u32 op) } -void MIPSDisAsm(u32 op, u32 pc, char *out) +void MIPSDisAsm(u32 op, u32 pc, char *out, bool tabsToSpaces) { if (op == 0) { @@ -896,10 +896,16 @@ void MIPSDisAsm(u32 op, u32 pc, char *out) { disPC = pc; const MIPSInstruction *instr = MIPSGetInstruction(op); - if (instr && instr->disasm) + if (instr && instr->disasm) { instr->disasm(op, out); - else - { + if (tabsToSpaces) { + while (*out) { + if (*out == '\t') + *out = ' '; + out++; + } + } + } else { strcpy(out, "no instruction :("); //__asm int 3 MIPSGetInstruction(op); @@ -942,7 +948,7 @@ int MIPSInterpret_RunUntil(u64 globalTicks) while (coreState == CORE_RUNNING) { // NEVER stop in a delay slot! - while (CoreTiming::downcount >= 0 && coreState == CORE_RUNNING) + while (curMips->downcount >= 0 && coreState == CORE_RUNNING) { // int cycles = 0; { @@ -984,7 +990,7 @@ int MIPSInterpret_RunUntil(u64 globalTicks) curMips->pc = curMips->nextPC; curMips->inDelaySlot = false; } - CoreTiming::downcount -= 1; + curMips->downcount -= 1; goto again; } } @@ -1019,7 +1025,7 @@ int MIPSInterpret_RunFastUntil(u64 globalTicks) MIPSState *curMips = currentMIPS; while (coreState == CORE_RUNNING) { - while (CoreTiming::downcount >= 0 && coreState == CORE_RUNNING) // TODO: Try to get rid of the latter check + while (curMips->downcount >= 0 && coreState == CORE_RUNNING) // TODO: Try to get rid of the latter check { again: bool wasInDelaySlot = curMips->inDelaySlot; @@ -1116,7 +1122,7 @@ int MIPSInterpret_RunFastUntil(u64 globalTicks) curMips->pc = curMips->nextPC; curMips->inDelaySlot = false; } - CoreTiming::downcount -= 1; + curMips->downcount -= 1; goto again; } } diff --git a/Core/MIPS/MIPSTables.h b/Core/MIPS/MIPSTables.h index 7f71735236..753e7dedc3 100644 --- a/Core/MIPS/MIPSTables.h +++ b/Core/MIPS/MIPSTables.h @@ -53,7 +53,7 @@ typedef void (CDECL *MIPSInterpretFunc)(u32 opcode); void MIPSCompileOp(u32 op); -void MIPSDisAsm(u32 op, u32 pc, char *out); +void MIPSDisAsm(u32 op, u32 pc, char *out, bool tabsToSpaces = false); u32 MIPSGetInfo(u32 op); void MIPSInterpret(u32 op); //only for those rare ones int MIPSInterpret_RunFastUntil(u64 globalTicks); diff --git a/Core/MIPS/x86/Asm.cpp b/Core/MIPS/x86/Asm.cpp index 08cfa7d0c0..36816f24b4 100644 --- a/Core/MIPS/x86/Asm.cpp +++ b/Core/MIPS/x86/Asm.cpp @@ -83,19 +83,18 @@ void AsmRoutineManager::Generate(MIPSState *mips, MIPSComp::Jit *jit) CMP(32, M((void*)&coreState), Imm32(0)); FixupBranch badCoreState = J_CC(CC_NZ, true); + FixupBranch skipToRealDispatch2 = J(); //skip the sync and compare first time dispatcher = GetCodePtr(); // The result of slice decrementation should be in flags if somebody jumped here // IMPORTANT - We jump on negative, not carry!!! - FixupBranch bail = J_CC(CC_BE, true); + FixupBranch bail = J_CC(CC_S, true); SetJumpTarget(skipToRealDispatch); + SetJumpTarget(skipToRealDispatch2); dispatcherNoCheck = GetCodePtr(); - // Debug - // CALL(&ImHere); - MOV(32, R(EAX), M(&mips->pc)); #ifdef _M_IX86 AND(32, R(EAX), Imm32(Memory::MEMVIEW32_MASK)); @@ -141,51 +140,10 @@ void AsmRoutineManager::Generate(MIPSState *mips, MIPSComp::Jit *jit) J_CC(CC_Z, outerLoop, true); SetJumpTarget(badCoreState); - //Landing pad for drec space ABI_PopAllCalleeSavedRegsAndAdjustStack(); RET(); breakpointBailout = GetCodePtr(); - //Landing pad for drec space ABI_PopAllCalleeSavedRegsAndAdjustStack(); RET(); - - GenerateCommon(); -} - -void AsmRoutineManager::GenerateCommon() -{ - /* - fifoDirectWrite8 = AlignCode4(); - GenFifoWrite(8); - fifoDirectWrite16 = AlignCode4(); - GenFifoWrite(16); - fifoDirectWrite32 = AlignCode4(); - GenFifoWrite(32); - fifoDirectWriteFloat = AlignCode4(); - GenFifoFloatWrite(); - fifoDirectWriteXmm64 = AlignCode4(); - GenFifoXmm64Write(); - - GenQuantizedLoads(); - GenQuantizedStores(); - GenQuantizedSingleStores(); - */ - - //CMPSD(R(XMM0), M(&zero), - // TODO - - // Fast write routines - special case the most common hardware write - // TODO: use this. - // Even in x86, the param values will be in the right registers. - /* - const u8 *fastMemWrite8 = AlignCode16(); - CMP(32, R(ABI_PARAM2), Imm32(0xCC008000)); - FixupBranch skip_fast_write = J_CC(CC_NE, false); - MOV(32, EAX, M(&m_gatherPipeCount)); - MOV(8, MDisp(EAX, (u32)&m_gatherPipe), ABI_PARAM1); - ADD(32, 1, M(&m_gatherPipeCount)); - RET(); - SetJumpTarget(skip_fast_write); - CALL((void *)&Memory::Write_U8);*/ -} +} \ No newline at end of file diff --git a/Core/MIPS/x86/Asm.h b/Core/MIPS/x86/Asm.h index 226332c676..b20811a7fe 100644 --- a/Core/MIPS/x86/Asm.h +++ b/Core/MIPS/x86/Asm.h @@ -20,19 +20,8 @@ #include "x64Emitter.h" #include "../MIPS.h" -// In Dolphin, we don't use inline assembly. Instead, we generate all machine-near -// code at runtime. In the case of fixed code like this, after writing it, we write -// protect the memory, essentially making it work just like precompiled code. -// There are some advantages to this approach: -// 1) No need to setup an external assembler in the build. -// 2) Cross platform, as long as it's x86/x64. -// 3) Can optimize code at runtime for the specific CPU model. -// There aren't really any disadvantages other than having to maintain a x86 emitter, -// which we have to do anyway :) -// -// To add a new asm routine, just add another const here, and add the code to Generate. -// Also, possibly increase the size of the code buffer. +// Runtime generated assembly routines, like the Dispatcher. namespace MIPSComp { @@ -49,6 +38,10 @@ public: AsmRoutineManager() { } + ~AsmRoutineManager() + { + FreeCodeSpace(); + } void Init(MIPSState *mips, MIPSComp::Jit *jit) { @@ -56,10 +49,6 @@ public: Generate(mips, jit); WriteProtect(); } - ~AsmRoutineManager() - { - FreeCodeSpace(); - } const u8 *enterCode; @@ -68,8 +57,6 @@ public: const u8 *dispatcherCheckCoreState; const u8 *dispatcherNoCheck; - const u8 *fpException; - const u8 *breakpointBailout; }; diff --git a/Core/MIPS/x86/CompBranch.cpp b/Core/MIPS/x86/CompBranch.cpp index a3cd04f889..f9e95e8451 100644 --- a/Core/MIPS/x86/CompBranch.cpp +++ b/Core/MIPS/x86/CompBranch.cpp @@ -80,9 +80,10 @@ void Jit::BranchRSRTComp(u32 op, Gen::CCFlags cc, bool likely) } delaySlotIsNice = false; // Until we have time to fully fix this - if (rs == 0) + if (rt == 0) { - CMP(32, gpr.R(rt), Imm32(0)); + gpr.KillImmediate(rs, true, true); + CMP(32, gpr.R(rs), Imm32(0)); } else { @@ -342,11 +343,6 @@ void Jit::BranchVFPUFlag(u32 op, Gen::CCFlags cc, bool likely) void Jit::Comp_VBranch(u32 op) { - // _dbg_assert_msg_(CPU,0,"comp_vbranch not supported"); - - //int imm = (signed short)(op&0xFFFF)<<2; - //u32 targetAddr = js.compilerPC + imm + 4; - switch ((op >> 16) & 3) { case 0: BranchVFPUFlag(op, CC_NZ, false); break; //bvf @@ -368,7 +364,6 @@ void Jit::Comp_Jump(u32 op) } u32 off = ((op & 0x3FFFFFF) << 2); u32 targetAddr = (js.compilerPC & 0xF0000000) | off; - //Delay slot CompileAt(js.compilerPC + 4); FlushAll(); @@ -447,10 +442,9 @@ void Jit::Comp_JumpReg(u32 op) void Jit::Comp_Syscall(u32 op) { - // This will most often be called from Comp_JumpReg (jr ra) so we take over the exit sequence... - FlushAll(); - ABI_CallFunctionC((void *)(&CallSyscall), op); + + ABI_CallFunctionC((void *)&CallSyscall, op); WriteSyscallExit(); js.compiling = false; diff --git a/Core/MIPS/x86/Jit.cpp b/Core/MIPS/x86/Jit.cpp index 58b04562fc..f5149f1264 100644 --- a/Core/MIPS/x86/Jit.cpp +++ b/Core/MIPS/x86/Jit.cpp @@ -25,9 +25,6 @@ #include "RegCache.h" #include "Jit.h" - -extern u32 *pspmainram; - namespace MIPSComp { @@ -52,8 +49,6 @@ void Jit::ClearCache() ClearCodeSpace(); } -u8 *codeCache; -#define CACHESIZE 16384*1024 void Jit::CompileAt(u32 addr) { u32 op = Memory::Read_Instruction(addr); @@ -142,7 +137,7 @@ void Jit::Comp_Generic(u32 op) void Jit::WriteExit(u32 destination, int exit_num) { - SUB(32, M(&CoreTiming::downcount), js.downcountAmount > 127 ? Imm32(js.downcountAmount) : Imm8(js.downcountAmount)); + SUB(32, M(¤tMIPS->downcount), js.downcountAmount > 127 ? Imm32(js.downcountAmount) : Imm8(js.downcountAmount)); //If nobody has taken care of this yet (this can be removed when all branches are done) JitBlock *b = js.curBlock; @@ -151,32 +146,28 @@ void Jit::WriteExit(u32 destination, int exit_num) // Link opportunity! int block = blocks.GetBlockNumberFromStartAddress(destination); - if (jo.enableBlocklink) - { - if (block >= 0 && jo.enableBlocklink) - { - // It exists! Joy of joy! - JMP(blocks.GetBlock(block)->checkedEntry, true); - b->linkStatus[exit_num] = true; - return; - } + if (block >= 0 && jo.enableBlocklink) { + // It exists! Joy of joy! + JMP(blocks.GetBlock(block)->checkedEntry, true); + b->linkStatus[exit_num] = true; + } else { + // No blocklinking. + MOV(32, M(&mips_->pc), Imm32(destination)); + JMP(asm_.dispatcher, true); } - // No blocklinking. - MOV(32, M(&mips_->pc), Imm32(destination)); - JMP(asm_.dispatcher, true); } void Jit::WriteExitDestInEAX() { // TODO: Some wasted potential, dispatcher will alwa MOV(32, M(&mips_->pc), R(EAX)); - SUB(32, M(&CoreTiming::downcount), js.downcountAmount > 127 ? Imm32(js.downcountAmount) : Imm8(js.downcountAmount)); + SUB(32, M(¤tMIPS->downcount), js.downcountAmount > 127 ? Imm32(js.downcountAmount) : Imm8(js.downcountAmount)); JMP(asm_.dispatcher, true); } void Jit::WriteSyscallExit() { - SUB(32, M(&CoreTiming::downcount), js.downcountAmount > 127 ? Imm32(js.downcountAmount) : Imm8(js.downcountAmount)); + SUB(32, M(¤tMIPS->downcount), js.downcountAmount > 127 ? Imm32(js.downcountAmount) : Imm8(js.downcountAmount)); JMP(asm_.dispatcherCheckCoreState, true); } diff --git a/Core/MIPS/x86/Jit.h b/Core/MIPS/x86/Jit.h index cd5a511efc..9f3281830f 100644 --- a/Core/MIPS/x86/Jit.h +++ b/Core/MIPS/x86/Jit.h @@ -35,7 +35,7 @@ struct JitOptions { JitOptions() { - enableBlocklink = false; + enableBlocklink = true; } bool enableBlocklink; diff --git a/Core/MemMapFunctions.cpp b/Core/MemMapFunctions.cpp index a4e55a2425..1f9b17bef7 100644 --- a/Core/MemMapFunctions.cpp +++ b/Core/MemMapFunctions.cpp @@ -55,7 +55,7 @@ u8 *GetPointer(const u32 address) } else { - ERROR_LOG(MEMMAP, "Unknown GetPointer %08x PC %08x LR %08x", address, currentMIPS->pc, currentMIPS->r[MIPS_REG_RA]); + ERROR_LOG(MEMMAP, "Unknown GetPointer %08x PC %08x LR %08x", address, currentMIPS->pc, currentMIPS->r[MIPS_REG_RA]); return 0; } } @@ -83,7 +83,7 @@ inline void ReadFromHardware(T &var, const u32 address) } else { - WARN_LOG(MEMMAP, "ReadFromHardware: Invalid address %08x PC %08x LR %08x", address, currentMIPS->pc, currentMIPS->r[MIPS_REG_RA]); + WARN_LOG(MEMMAP, "ReadFromHardware: Invalid address %08x PC %08x LR %08x", address, currentMIPS->pc, currentMIPS->r[MIPS_REG_RA]); if (!g_Config.bIgnoreBadMemAccess) { Core_EnableStepping(true); host->SetDebugMode(true); diff --git a/Qt/Core.pro b/Qt/Core.pro index f82865ed40..4310b33c87 100755 --- a/Qt/Core.pro +++ b/Qt/Core.pro @@ -9,20 +9,21 @@ include(Settings.pri) INCLUDEPATH += ../native ../Common ../ arm { - SOURCES += ../Core/MIPS/ARM/Asm.cpp \ #CoreARM - ../Core/MIPS/ARM/CompALU.cpp \ - ../Core/MIPS/ARM/CompBranch.cpp \ - ../Core/MIPS/ARM/CompFPU.cpp \ - ../Core/MIPS/ARM/CompLoadStore.cpp \ - ../Core/MIPS/ARM/CompVFPU.cpp \ - ../Core/MIPS/ARM/Jit.cpp \ - ../Core/MIPS/ARM/JitCache.cpp \ - ../Core/MIPS/ARM/RegCache.cpp + SOURCES += ../Core/MIPS/ARM/ArmAsm.cpp \ #CoreARM + ../Core/MIPS/ARM/ArmCompALU.cpp \ + ../Core/MIPS/ARM/ArmCompBranch.cpp \ + ../Core/MIPS/ARM/ArmCompFPU.cpp \ + ../Core/MIPS/ARM/ArmCompLoadStore.cpp \ + ../Core/MIPS/ARM/ArmCompVFPU.cpp \ + ../Core/MIPS/ARM/ArmJit.cpp \ + ../Core/MIPS/ARM/ArmJitCache.cpp \ + ../Core/MIPS/ARM/ArmRegCache.cpp \ + ../ext/disarm.cpp - HEADERS += ../Core/MIPS/ARM/Asm.h \ - ../Core/MIPS/ARM/Jit.h \ - ../Core/MIPS/ARM/JitCache.h \ - ../Core/MIPS/ARM/RegCache.h + HEADERS += ../Core/MIPS/ARM/ArmAsm.h \ + ../Core/MIPS/ARM/ArmJit.h \ + ../Core/MIPS/ARM/ArmJitCache.h \ + ../Core/MIPS/ARM/ArmRegCache.h } x86 { SOURCES += ../Core/MIPS/x86/Asm.cpp \ diff --git a/Windows/PPSSPP.vcxproj b/Windows/PPSSPP.vcxproj index bd233cf1af..019fde5bcc 100644 --- a/Windows/PPSSPP.vcxproj +++ b/Windows/PPSSPP.vcxproj @@ -219,6 +219,12 @@ + + true + true + true + true + true true @@ -297,6 +303,7 @@ + @@ -329,6 +336,7 @@ + diff --git a/Windows/PPSSPP.vcxproj.filters b/Windows/PPSSPP.vcxproj.filters index c76c21f6ca..d2538d562b 100644 --- a/Windows/PPSSPP.vcxproj.filters +++ b/Windows/PPSSPP.vcxproj.filters @@ -104,6 +104,9 @@ + + Android + @@ -182,6 +185,9 @@ Windows\Input + + Android + @@ -204,6 +210,9 @@ Android + + Android + diff --git a/Windows/main.cpp b/Windows/main.cpp index 8bc5bb5260..c875f0c7b2 100644 --- a/Windows/main.cpp +++ b/Windows/main.cpp @@ -23,6 +23,7 @@ #include "../Core/Config.h" #include "../Core/SaveState.h" #include "EmuThread.h" +#include "ext/disarm.h" #include "LogManager.h" #include "ConsoleListener.h" diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 5265b4bb7b..3e3356e0d8 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -25,7 +25,7 @@ NATIVE := ../../native SRC := ../.. LOCAL_CFLAGS := -DUSE_PROFILER -DARM -DGL_GLEXT_PROTOTYPES -DUSING_GLES2 -O2 -fsigned-char -Wall -Wno-multichar -Wno-psabi -Wno-unused-variable -fno-strict-aliasing -ffast-math -LOCAL_CPPFLAGS := -std=gnu++0x +LOCAL_CXXFLAGS := -std=gnu++0x LOCAL_C_INCLUDES := \ $(LOCAL_PATH)/../../Common \ $(LOCAL_PATH)/../.. \ @@ -46,8 +46,10 @@ LOCAL_SRC_FILES := \ MenuScreens.cpp \ UIShader.cpp \ GamepadEmu.cpp \ + ArmEmitterTest.cpp \ ui_atlas.cpp \ $(SRC)/native/android/app-android.cpp \ + $(SRC)/ext/disarm.cpp \ $(SRC)/ext/libkirk/AES.c \ $(SRC)/ext/libkirk/SHA1.c \ $(SRC)/ext/libkirk/bn.c \ @@ -55,8 +57,9 @@ LOCAL_SRC_FILES := \ $(SRC)/ext/libkirk/kirk_engine.c \ $(SRC)/ext/snappy/snappy-c.cpp \ $(SRC)/ext/snappy/snappy.cpp \ - $(SRC)/Common/ArmABI.cpp \ $(SRC)/Common/ArmEmitter.cpp \ + $(SRC)/Common/ArmCPUDetect.cpp \ + $(SRC)/Common/ArmThunk.cpp \ $(SRC)/Common/LogManager.cpp \ $(SRC)/Common/MemArena.cpp \ $(SRC)/Common/MemoryUtil.cpp \ @@ -66,7 +69,6 @@ LOCAL_SRC_FILES := \ $(SRC)/Common/StringUtil.cpp \ $(SRC)/Common/Thread.cpp \ $(SRC)/Common/Timer.cpp \ - $(SRC)/Common/ThunkARM.cpp \ $(SRC)/Common/Misc.cpp \ $(SRC)/Common/MathUtil.cpp \ $(SRC)/GPU/Math3D.cpp \ @@ -166,14 +168,15 @@ LOCAL_SRC_FILES := \ $(SRC)/Core/MIPS/MIPSCodeUtils.cpp \ $(SRC)/Core/MIPS/MIPSDebugInterface.cpp \ $(SRC)/Core/MIPS/JitCommon/JitCommon.cpp \ - $(SRC)/Core/MIPS/ARM/JitCache.cpp \ - $(SRC)/Core/MIPS/ARM/CompALU.cpp \ - $(SRC)/Core/MIPS/ARM/CompBranch.cpp \ - $(SRC)/Core/MIPS/ARM/CompFPU.cpp \ - $(SRC)/Core/MIPS/ARM/Asm.cpp \ - $(SRC)/Core/MIPS/ARM/Jit.cpp \ - $(SRC)/Core/MIPS/ARM/CompLoadStore.cpp \ - $(SRC)/Core/MIPS/ARM/RegCache.cpp \ + $(SRC)/Core/MIPS/ARM/ArmJitCache.cpp \ + $(SRC)/Core/MIPS/ARM/ArmCompALU.cpp \ + $(SRC)/Core/MIPS/ARM/ArmCompBranch.cpp \ + $(SRC)/Core/MIPS/ARM/ArmCompFPU.cpp \ + $(SRC)/Core/MIPS/ARM/ArmCompLoadStore.cpp \ + $(SRC)/Core/MIPS/ARM/ArmCompVFPU.cpp \ + $(SRC)/Core/MIPS/ARM/ArmAsm.cpp \ + $(SRC)/Core/MIPS/ARM/ArmJit.cpp \ + $(SRC)/Core/MIPS/ARM/ArmRegCache.cpp \ $(SRC)/Core/Util/BlockAllocator.cpp \ $(SRC)/Core/Util/ppge_atlas.cpp \ $(SRC)/Core/Util/PPGeDraw.cpp diff --git a/android/jni/Application.mk b/android/jni/Application.mk index dd83bdcd41..d18d3a46ff 100644 --- a/android/jni/Application.mk +++ b/android/jni/Application.mk @@ -1,2 +1,4 @@ APP_STL := stlport_static -APP_ABI := armeabi armeabi-v7a +APP_ABI := armeabi-v7a armeabi + +#APP_ABI := armeabi-v7a diff --git a/android/jni/ArmEmitterTest.cpp b/android/jni/ArmEmitterTest.cpp new file mode 100644 index 0000000000..a035bcba7b --- /dev/null +++ b/android/jni/ArmEmitterTest.cpp @@ -0,0 +1,80 @@ +#include "base/logging.h" +#include "ArmEmitterTest.h" + +#include "Common/ArmEmitter.h" + +static bool functionWasCalled; + +using namespace ArmGen; + +class TestCode : public ArmGen::ARMXCodeBlock { +public: + TestCode(); + void Generate(); + const u8 *testCodePtr; + const u8 *testCodePtr2; +}; + +TestCode::TestCode() +{ + AllocCodeSpace(0x10000); +} + + +u32 TestLeaf(u32 a, u32 b, u32 c) +{ + ILOG("TestLeaf: %08x %08x %08x\n", a, b, c); + return 0xFF; +} + +void TestLeaf2(u32 a) +{ + ILOG("TestLeaf2 %08x\n"); +} + + +void TestCode::Generate() +{ + testCodePtr = this->GetCodePtr(); + // Sonic1 commented that R11 is the frame pointer in debug mode, whatever "debug mode" means. + PUSH(2, R11, _LR); + ARMABI_MOVI2R(R0, 0x13371338); + AND(R1, R0, Operand2(0xFC, 4)); + BIC(R0, R0, Operand2(0xFC, 4)); + CMP(R1, Operand2(0x10, 4)); + SetCC(CC_EQ); + MOV(R2, Operand2(0x99, 0)); + SetCC(CC_NEQ); + MOV(R2, Operand2(0xFF, 0)); + SetCC(); + QuickCallFunction(R3, (void*)&TestLeaf); + + //ARMABI_CallFunctionCCC((void*)&TestLeaf, 0x1, 0x100, 0x1337); + //ARMABI_CallFunctionCCC((void*)&TestLeaf, 0x2, 0x100, 0x31337); + //ARMABI_CallFunctionCCC((void*)&TestLeaf, 0x3, 0x100, 0x1337); + POP(2, R11, _PC); // Yup, this is how you return. + + testCodePtr2 = this->GetCodePtr(); + PUSH(2, R11, _LR); + QuickCallFunction(R3, (void*)&TestLeaf2); + POP(2, R11, _PC); +} + + +void CallPtr(const void *ptr) +{ + ((void(*)())ptr)(); +} + + + +void ArmEmitterTest() +{ + ILOG("Running ARM emitter test!"); + TestCode gen; + gen.ReserveCodeSpace(0x4000); + gen.Generate(); + + CallPtr(gen.testCodePtr); + ILOG("ARM emitter test 1 passed!"); +} diff --git a/android/jni/ArmEmitterTest.h b/android/jni/ArmEmitterTest.h new file mode 100644 index 0000000000..4d2100ce61 --- /dev/null +++ b/android/jni/ArmEmitterTest.h @@ -0,0 +1,5 @@ +#pragma once + +// Just a test of the ARM emitter, playing around with running some code without having the whole emu around. + +void ArmEmitterTest(); \ No newline at end of file diff --git a/android/jni/EmuScreen.cpp b/android/jni/EmuScreen.cpp index 0d50fda4c6..875dff43ed 100644 --- a/android/jni/EmuScreen.cpp +++ b/android/jni/EmuScreen.cpp @@ -48,10 +48,6 @@ EmuScreen::EmuScreen(const std::string &filename) : invalid_(true) CoreParameter coreParam; coreParam.cpuCore = (CPUCore)g_Config.iCpuCore; -#if defined(ARM) - if (coreParam.cpuCore == CPU_JIT) - coreParam.cpuCore = CPU_FASTINTERPRETER; -#endif coreParam.gpuCore = GPU_GLES; coreParam.enableSound = g_Config.bEnableSound; coreParam.fileToStart = fileToStart; diff --git a/android/jni/MenuScreens.cpp b/android/jni/MenuScreens.cpp index 765e765f25..08756fa1f8 100644 --- a/android/jni/MenuScreens.cpp +++ b/android/jni/MenuScreens.cpp @@ -305,16 +305,18 @@ void SettingsScreen::render() { g_Config.iWindowZoom = doubleRes ? 2 : 1; } UICheckBox(GEN_ID, x, y += stride, "Hardware Transform", ALIGN_TOPLEFT, &g_Config.bHardwareTransform); - UICheckBox(GEN_ID, x, y += stride, "Use VBO for drawing", ALIGN_TOPLEFT, &g_Config.bUseVBO); + UICheckBox(GEN_ID, x, y += stride, "Draw using VBO", ALIGN_TOPLEFT, &g_Config.bUseVBO); - bool useFastInt = g_Config.iCpuCore == CPU_FASTINTERPRETER; - UICheckBox(GEN_ID, x, y += stride, "Slightly faster interpreter (may crash)", ALIGN_TOPLEFT, &useFastInt); + bool useJit = g_Config.iCpuCore == CPU_JIT; + UICheckBox(GEN_ID, x, y += stride, "JIT (Dynarec)", ALIGN_TOPLEFT, &useJit); + if (g_Config.iCpuCore == CPU_JIT) + UICheckBox(GEN_ID, x + 350, y, "Fastmem (unstable)", ALIGN_TOPLEFT, &g_Config.bFastMemory); + g_Config.iCpuCore = useJit ? CPU_JIT : CPU_INTERPRETER; // ui_draw2d.DrawText(UBUNTU48, "much faster JIT coming later", x, y+=50, 0xcFFFFFFF, ALIGN_LEFT); UICheckBox(GEN_ID, x, y += stride, "On-screen Touch Controls", ALIGN_TOPLEFT, &g_Config.bShowTouchControls); if (g_Config.bShowTouchControls) UICheckBox(GEN_ID, x + 50, y += stride, "Show Analog Stick", ALIGN_TOPLEFT, &g_Config.bShowAnalogStick); - g_Config.iCpuCore = useFastInt ? CPU_FASTINTERPRETER : CPU_INTERPRETER; - // UICheckBox(GEN_ID, x, y += 50, "Draw raw framebuffer (for some homebrew)", ALIGN_TOPLEFT, &g_Config.bDisplayFramebuffer); + // UICheckBox(GEN_ID, x, y += stride, "Draw raw framebuffer (for some homebrew)", ALIGN_TOPLEFT, &g_Config.bDisplayFramebuffer); if (UIButton(GEN_ID, Pos(dp_xres - 10, dp_yres-10), LARGE_BUTTON_WIDTH, "Back", ALIGN_RIGHT | ALIGN_BOTTOM)) { screenManager()->switchScreen(new MenuScreen()); diff --git a/android/jni/NativeApp.cpp b/android/jni/NativeApp.cpp index ac5924bf4d..a5fbc3a1a4 100644 --- a/android/jni/NativeApp.cpp +++ b/android/jni/NativeApp.cpp @@ -50,6 +50,8 @@ #include "MenuScreens.h" #include "UIShader.h" +#include "ArmEmitterTest.h" + Texture *uiTexture; ScreenManager *screenManager; @@ -152,6 +154,8 @@ void NativeGetAppInfo(std::string *app_dir_name, std::string *app_nice_name, boo *app_nice_name = "PPSSPP"; *app_dir_name = "ppsspp"; *landscape = true; + + // ArmEmitterTest(); } void NativeInit(int argc, const char *argv[], const char *savegame_directory, const char *external_directory, const char *installID) @@ -190,6 +194,7 @@ void NativeInit(int argc, const char *argv[], const char *savegame_directory, co switch (argv[i][1]) { case 'd': // Enable debug logging + // Note that you must also change the max log level in Log.h. logLevel = LogTypes::LDEBUG; break; case 'g': @@ -293,6 +298,9 @@ void NativeInitGraphics() theme.checkOn = I_CHECKEDBOX; theme.checkOff = I_SQUARE; + ui_draw2d.Init(); + ui_draw2d_front.Init(); + UIInit(&ui_atlas, theme); uiTexture = new Texture(); @@ -372,6 +380,9 @@ void NativeShutdownGraphics() delete screenManager; screenManager = 0; + ui_draw2d.Shutdown(); + ui_draw2d_front.Shutdown(); + UIShader_Shutdown(); gl_lost_manager_shutdown(); diff --git a/android/src/org/ppsspp/ppsspp/PpssppActivity.java b/android/src/org/ppsspp/ppsspp/PpssppActivity.java index bd7cd15d12..c752713aaa 100644 --- a/android/src/org/ppsspp/ppsspp/PpssppActivity.java +++ b/android/src/org/ppsspp/ppsspp/PpssppActivity.java @@ -15,4 +15,4 @@ public class PpssppActivity extends NativeActivity { { return false; } -} \ No newline at end of file +} \ No newline at end of file diff --git a/ext/disarm.cpp b/ext/disarm.cpp new file mode 100644 index 0000000000..f2f7ffc193 --- /dev/null +++ b/ext/disarm.cpp @@ -0,0 +1,965 @@ +/* disarm -- a simple disassembler for ARM instructions + * (c) 2000 Gareth McCaughan + * + * This file may be distributed and used freely provided: + * 1. You do not distribute any version that lacks this + * copyright notice (exactly as it appears here, extending + * from the start to the end of the C-language comment + * containing these words)); and, + * 2. If you distribute any modified version, its source + * contains a clear description of the ways in which + * it differs from the original version, and a clear + * indication that the changes are not mine. + * There is no restriction on your permission to use and + * distribute object code or executable code derived from + * this. + * + * The original version of this file (or perhaps a later + * version by the original author) may or may not be + * available at http://web.ukonline.co.uk/g.mccaughan/g/software.html . + * + * Share and enjoy! -- g + */ + +/* (*This* comment is NOT part of the notice mentioned in the + * distribution conditions above.) + * + * The bulk of this code was ripped brutally from the middle + * of a much more interesting piece of software whose purpose + * is to disassemble object files in the format known as AOF; + * it's quite clever at spotting blocks of non-code embedded + * in code, identifying labels, and so on. + * + * This program, on the other hand, is very much simpler. + * It simply disassembles one instruction at a time. Some + * traces of the original purpose can be seen here and there. + * You might want to make this do a two-phase disassembly, + * adding labels etc the second time around. I've made this + * work by loading the whole file into memory first, partly + * because that makes a two-pass approach easier. + * + * One word of warning: I believe that the syntax this program + * uses for the MSR instruction is now obsolete. + * + * Usage: + * disarm + * will disassemble every word in . + * + * should be something understood by strtol. + * So you can get hex (which is probably what you want) + * by prefixing "0x". + * + * The -r option will byte-reverse each word before it's + * disassembled. + * + * The code is rather unmaintainable. I'm sorry. + * + * Changes since original release: + * ????-??-?? v0.00 Initial release. + * 2007-09-02 v0.11 Change %X to %lX in a format string. + * (Thanks to Vincent Zweije for reporting this.) + */ + +#include +#include +#include + +typedef unsigned int word; +typedef unsigned int address; +typedef unsigned int addrdiff; +#define W(x) ((word*)(x)) + +#define declstruct(name) typedef struct name s##name, * p##name +#define defstruct(name) struct name +#define defequiv(new,old) typedef struct old s##new, * p##new + +declstruct(DisOptions); +declstruct(Instruction); + +typedef enum { + target_None, /* instruction doesn't refer to an address */ + target_Data, /* instruction refers to address of data */ + target_FloatS, /* instruction refers to address of single-float */ + target_FloatD, /* instruction refers to address of double-float */ + target_FloatE, /* blah blah extended-float */ + target_FloatP, /* blah blah packed decimal float */ + target_Code, /* instruction refers to address of code */ + target_Unknown /* instruction refers to address of *something* */ +} eTargetType; + +defstruct(Instruction) { + char text[128]; /* the disassembled instruction */ + int undefined; /* non-0 iff it's an undefined instr */ + int badbits; /* non-0 iff something reserved has the wrong value */ + int oddbits; /* non-0 iff something unspecified isn't 0 */ + int is_SWI; /* non-0 iff it's a SWI */ + word swinum; /* only set for SWIs */ + address target; /* address instr refers to */ + eTargetType target_type; /* and what we expect to be there */ + int offset; /* offset from register in LDR or STR or similar */ + char * addrstart; /* start of address part of instruction, or 0 */ +}; + +#define disopt_SWInames 1 /* use names, not &nnnn */ +#define disopt_CommaSpace 2 /* put spaces after commas */ +#define disopt_FIXS 4 /* bogus FIX syntax for ObjAsm */ +#define disopt_ReverseBytes 8 /* byte-reverse words first */ + +defstruct(DisOptions) { + word flags; + const char * * regnames; /* pointer to 16 |char *|s: register names */ +}; + +static pInstruction instr_disassemble(word, address, pDisOptions); + +#define INSTR_grok_v4 + +/* Preprocessor defs you can give to affect this stuff: + * INSTR_grok_v4 understand ARMv4 instructions (halfword & sign-ext LDR/STR) + * INSTR_new_msr be prepared to produce new MSR syntax if asked + * The first of these is supported; the second isn't. + */ + +/* Some important single-bit fields. */ + +#define Sbit (1<<20) /* set condition codes (data processing) */ +#define Lbit (1<<20) /* load, not store (data transfer) */ +#define Wbit (1<<21) /* writeback (data transfer) */ +#define Bbit (1<<22) /* single byte (data transfer, SWP) */ +#define Ubit (1<<23) /* up, not down (data transfer) */ +#define Pbit (1<<24) /* pre-, not post-, indexed (data transfer) */ +#define Ibit (1<<25) /* non-immediate (data transfer) */ + /* immediate (data processing) */ +#define SPSRbit (1<<22) /* SPSR, not CPSR (MRS, MSR) */ + +/* Some important 4-bit fields. */ + +#define RD(x) ((x)<<12) /* destination register */ +#define RN(x) ((x)<<16) /* operand/base register */ +#define CP(x) ((x)<<8) /* coprocessor number */ +#define RDbits RD(15) +#define RNbits RN(15) +#define CPbits CP(15) +#define RD_is(x) ((instr&RDbits)==RD(x)) +#define RN_is(x) ((instr&RNbits)==RN(x)) +#define CP_is(x) ((instr&CPbits)==CP(x)) + +/* A slightly efficient way of telling whether two bits are the same + * or not. It's assumed that a>(b-a)))&(1<=0; i-=4) *op++ = "0123456789ABCDEF"[(w>>i)&15]; + return op; +} + +/* op = reg(op,'x',n) === op += sprintf(op,"x%lu",n&15). + */ +static char * reg(char * op, char c, word n) { + *op++=c; + n&=15; + if (n>=10) { *op++='1'; n+='0'-10; } else n+='0'; + *op++=(char)n; + return op; +} + +/* op = num(op,n) appends n in decimal or &n in hex + * depending on whether n<100. It's assumed that n>=0. + */ +static char * num(char * op, word w) { + if (w>=100) { + int i; + word t; + *op++='&'; + for (i=28; (t=(w>>i)&15)==0; i-=4) ; + for (; i>=0; i-=4) *op++ = "0123456789ABCDEF"[(w>>i)&15]; + } + else { + /* divide by 10. You can prove this works by exhaustive search. :-) */ + word t = w-(w>>2); t=(t+(t>>4)) >> 3; + { word u = w-10*t; + if (u==10) { u=0; ++t; } + if (t) *op++=(char)(t+'0'); + *op++=(char)(u+'0'); + } + } + return op; +} + +/* instr_disassemble + * Disassemble a single instruction. + * + * args: instr a single ARM instruction + * addr the address it's presumed to have come from + * opts cosmetic preferences for our output + * + * reqs: opts must be filled in right. In particular, it must contain + * a list of register names. + * + * return: a pointer to a structure containing the disassembled instruction + * and some other information about it. + * + * This is basically a replacement for the SWI Debugger_Disassemble, + * but it has the following advantages: + * + * + it's 3-4 times as fast + * + it's better at identifying undefined instructions, + * and instructions not invariant under { disassemble; ObjAsm; } + * + it provides some other useful information as well + * + its output syntax is the same as ObjAsm's input syntax + * (where possible) + * + it doesn't disassemble FIX incorrectly unless you ask it to + * + it's more configurable in some respects + * + * It also has the following disadvantages: + * + * - it increases the size of ObjDism + * - it doesn't provide so many `helpful' usage comments etc + * - it's less configurable in some respects + * - it doesn't (yet) know about ARMv4 instructions + * + * This function proceeds in two phases. The first is very simple: + * it works out what sort of instruction it's looking at and sets up + * three strings: + * - |mnemonic| (the basic mnemonic: LDR or whatever) + * - |flagchars| (things to go after the cond code: B or whatever) + * - |format| (a string describing how to display the instruction) + * The second phase consists of interpreting |format|, character by + * character. Some characters (e.g., letters) just mean `append this + * character to the output string'; some mean more complicated things + * like `append the name of the register whose number is in bits 12..15' + * or, worse, `append a description of the field'. + * + * I'm afraid the magic characters in |format| are rather arbitrary. + * One criterion in choosing them was that they should form a contiguous + * subrange of the character set! Sorry. + * + * Things I still want to do: + * + * - more configurability? + * - make it much faster, if possible + * - make it much smaller, if possible + * + * Format characters: + * + * \01..\05 copro register number from nybble (\001 == nybble 0, sorry) + * $ SWI number + * % register set for LDM/STM (takes note of bit 22 for ^) + * & address for B/BL + * ' ! if bit 21 set, else nothing (mnemonic: half a !) + * ( #regs for SFM (bits 22,15 = fpn, assumed already tweaked) + * ) copro opcode in bits 20..23 (for CDP) + * * op2 (takes note of bottom 12 bits, and bit 25) + * + FP register or immediate value: bits 0..3 + * , comma or comma-space + * - copro extra info in bits 5..7 preceded by , omitted if 0 + * . address in ADR instruction + * / address for LDR/STR (takes note of bit 23 & reg in bits 16..19) + * 0..4 register number from nybble + * 5..9 FP register number from nybble + * : copro opcode in bits 21..23 (for MRC/MCR) + * ; copro number in bits 8..11 + * + * ADDED BY HRYDGARD: + * ^ 16-bit immediate + * + * NB that / takes note of bit 22, too, and does its own ! when + * appropriate. + * + * On typical instructions this seems to take about 100us on my ARM6; + * that's about 3000 cycles, which seems grossly excessive. I'm not + * sure where all those cycles are being spent. Perhaps it's possible + * to make it much, much faster. Most of this time is spent on phase 2. + */ + +extern pInstruction +instr_disassemble(word instr, address addr, pDisOptions opts) { + static char flagchars[4]; + static sInstruction result; + const char * mnemonic = 0; + char * flagp = flagchars; + const char * format = 0; + word fpn; + eTargetType poss_tt = target_None; +#ifdef INSTR_grok_v4 + int is_v4 = 0; +#endif + + /* PHASE 0. Set up default values for |result|. */ + + if (opts->flags & disopt_ReverseBytes) { + instr = ((instr & 0xFF00FF00) >> 8) | ((instr & 0x00FF00FF) << 8); + instr = (instr >> 16) | (instr << 16); + } + + fpn = ((instr>>15)&1) + ((instr>>21)&2); + + result.undefined = + result.badbits = + result.oddbits = + result.is_SWI = 0; + result.target_type = target_None; + result.offset = 0x80000000; + result.addrstart = 0; + + /* PHASE 1. Decode and classify instruction. */ + + switch ((instr>>24)&15) { + case 0: + /* multiply or data processing, or LDRH etc */ + if ((instr&(15<<4))!=(9<<4)) goto lMaybeLDRHetc; + /* multiply */ + if (instr&(1<<23)) { + /* int multiply */ + mnemonic = "UMULL\0UMLAL\0SMULL\0SMLAL" + 6*((instr>>21)&3); + format = "3,4,0,2"; + } + else { + if (instr&(1<<22)) goto lUndefined; /* "class C" */ + /* short multiply */ + if (instr&(1<<21)) { + mnemonic = "MLA"; + format = "4,0,2,3"; + } + else { + mnemonic = "MUL"; + format = "4,0,2"; + } + } + if (instr&Sbit) *flagp++='S'; + break; + case 1: + if ((instr & 0x0FFFFFF0) == ((18 << 20) | (0xFFF << 8) | (1 << 4))) { + mnemonic = "B"; + format = "0"; + break; + } else if ((instr & 0x0FFFFFF0) == 0x12FFF30) { + mnemonic = "BL"; + format = "0"; + break; + } + case 3: + /* SWP or MRS/MSR or data processing */ + // hrydgard addition: MOVW/MOVT + if ((instr & 0x0FF00000) == 0x03000000) { + mnemonic = "MOVW"; + format = "3,^"; + break; + } + else if ((instr & 0x0FF00000) == 0x03400000) { + mnemonic = "MOVT"; + format = "3,^"; + break; + } + else if ((instr&0x02B00FF0)==0x00000090) { + /* SWP */ + mnemonic = "SWP"; + format = "3,0,[4]"; + if (instr&Bbit) *flagp++='B'; + break; + } + else if ((instr&0x02BF0FFF)==0x000F0000) { + /* MRS */ + mnemonic = "MRS"; + format = (instr&SPSRbit) ? "3,SPSR" : "3,CPSR"; + break; + } + else if ((instr&0x02BFFFF0)==0x0029F000) { + /* MSR psr,Rs */ + mnemonic = "MSR"; + format = (instr&SPSRbit) ? "SPSR,0" : "CPSR,0"; + break; + } + else if ((instr&0x00BFF000)==0x0028F000) { + /* MSR {C,S}PSR_flag,op2 */ + mnemonic = "MSR"; + format = (instr&SPSRbit) ? "SPSR_flg,*" : "CPSR_flg,*"; + if (!(instr&Ibit) && (instr&(15<<4))) +#ifdef INSTR_grok_v4 + goto lMaybeLDRHetc; +#else + goto lUndefined; /* shifted reg in MSR illegal */ +#endif + break; + } + /* fall through here */ +lMaybeLDRHetc: +#ifdef INSTR_grok_v4 + if ((instr&(14<<24))==0 + && ((instr&(9<<4))==(9<<4))) { + /* Might well be LDRH or similar. */ + if ((instr&(Wbit+Pbit))==Wbit) goto lUndefined; /* "class E", case 1 */ + if ((instr&(Lbit+(1<<6)))==(1<<6)) goto lUndefined; /* STRSH etc */ + mnemonic = "STR\0LDR" + ((instr&Lbit) >> 18); + if (instr&(1<<6)) *flagp++='S'; + *flagp++ = (instr&(1<<5)) ? 'B' : 'H'; + format = "3,/"; + /* aargh: */ + if (!(instr&(1<<22))) instr |= Ibit; + is_v4=1; + break; + } +#endif + case 2: + /* data processing */ + { word op21 = instr&(15<<21); + if ((op21==(2<<21) || (op21==(4<<21))) /* ADD or SUB */ + && ((instr&(RNbits+Ibit+Sbit))==RN(15)+Ibit) /* imm, no S */ + /*&& ((instr&(30<<7))==0 || (instr&3))*/) { /* normal rot */ + /* ADD ...,pc,#... or SUB ...,pc,#...: turn into ADR */ + mnemonic = "ADR"; + format = "3,."; + if ((instr&(30<<7))!=0 && !(instr&3)) result.oddbits=1; + break; + } + mnemonic = "AND\0EOR\0SUB\0RSB\0ADD\0ADC\0SBC\0RSC\0" + "TST\0TEQ\0CMP\0CMN\0ORR\0MOV\0BIC\0MVN" /* \0 */ + + (op21 >> 19); + /* Rd needed for all but TST,TEQ,CMP,CMN (8..11) */ + /* Rn needed for all but MOV,MVN (13,15) */ + if (op21 < ( 8<<21)) format = "3,4,*"; + else if (op21 < (12<<21)) { + format = "4,*"; + if (instr&RDbits) { + if ((instr&Sbit) && RD_is(15)) + *flagp++='P'; + else result.oddbits=1; + } + if (!(instr&Sbit)) goto lUndefined; /* CMP etc, no S bit */ + } + else if (op21 & (1<<21)) { + format = "3,*"; + if (instr&RNbits) result.oddbits=1; + } + else format = "3,4,*"; + if (instr&Sbit && (op21<(8<<21) || op21>=(12<<21))) *flagp++='S'; + } + break; + case 4: + case 5: + case 6: + case 7: + /* undefined or STR/LDR */ + if ((instr&Ibit) && (instr&(1<<4))) goto lUndefined; /* "class A" */ + mnemonic = "STR\0LDR" + ((instr&Lbit) >> 18); + format = "3,/"; + if (instr&Bbit) *flagp++='B'; + if ((instr&(Wbit+Pbit))==Wbit) *flagp++='T'; + poss_tt = target_Data; + break; + case 8: + case 9: + /* STM/LDM */ + mnemonic = "STM\0LDM" + ((instr&Lbit) >> 18); + if (RN_is(13)) { + /* r13, so treat as stack */ + word x = (instr&(3<<23)) >> 22; + if (instr&Lbit) x^=6; + { const char * foo = "EDEAFDFA"+x; + *flagp++ = *foo++; + *flagp++ = *foo; + } + } + else { + /* not r13, so don't treat as stack */ + *flagp++ = (instr&Ubit) ? 'I' : 'D'; + *flagp++ = (instr&Pbit) ? 'B' : 'A'; + } + format = "4',%"; + break; + case 10: + case 11: + /* B or BL */ + mnemonic = "B\0BL"+((instr&(1<<24))>>23); + format = "&"; + break; + case 12: + case 13: + /* STC or LDC */ + if (CP_is(1)) { + /* copro 1: FPU. This is STF or LDF. */ + mnemonic = "STF\0LDF" + ((instr&Lbit) >> 18); + format = "8,/"; + *flagp++ = "SDEP"[fpn]; + poss_tt = (eTargetType)(target_FloatS+fpn); + } + else if (CP_is(2)) { + /* copro 2: this is LFM or SFM. */ + mnemonic = "SFM\0LFM" + ((instr&Lbit) >> 18); + if (!fpn) fpn=4; + if (RN_is(13) && BitsDiffer(23,24)) { + if ((instr&255)!=fpn) goto lNonStackLFM; + /* r13 and U!=P, so treat as stack */ + if (BitsDiffer(20,24)) { + /* L != P, so FD */ + *flagp++ = 'F'; *flagp++ = 'D'; + } + else { + /* L == P, so EA */ + *flagp++ = 'E'; *flagp++ = 'A'; + } + format = "8,(,[4]'"; + } + else { +lNonStackLFM: + /* not r13 or U=P or wrong offset, so don't treat as stack */ + format = "8,(,/"; + poss_tt = target_FloatE; + } + } + else { + /* some other copro number: STC or LDC. */ + mnemonic = "STC\0LDC" + ((instr&Lbit) >> 18); + format = ";,\004,/"; + if (instr&(1<<22)) *flagp++ = 'L'; + poss_tt = target_Unknown; + } + break; + case 14: + /* CDP or MRC/MCR */ + if (instr&(1<<4)) { + /* MRC/MCR. */ + if (CP_is(1)) { + /* copro 1: FPU. */ + if ((instr&Lbit) && RD_is(15)) { + /* MCR in FPU with Rd=r15: comparison (ugh) */ + if (!(instr&(1<<23))) goto lUndefined; /* unused operation */ + mnemonic = "CMF\0\0CNF\0\0CMFE\0CNFE" + (5*(instr&(3<<21)) >> 21); + format = "9,+"; + if (instr&((1<<19)+(7<<5))) + result.badbits=1; /* size,rmode reseved */ + } + else { + /* normal FPU MCR/MRC */ + word op20 = instr&(15<<20); + if (op20>=6<<20) goto lUndefined; + mnemonic = "FLT\0FIX\0WFS\0RFS\0WFC\0RFC" + (op20>>18); + if (op20==0) { + /* FLT instruction */ + format = "9,3"; + { char c = "SDE*"[((instr>>7)&1) + ((instr>>18)&2)]; + if (c=='*') goto lUndefined; else *flagp++=c; + } + if (instr&15) result.oddbits=1; /* Fm and const flag unused */ + } + else { + /* not FLT instruction */ + if (instr&((1<<7)+(1<<19))) + result.badbits=1; /* size bits reserved */ + if (op20==1<<20) { + /* FIX instruction */ + format = "3,+"; + if (opts->flags&disopt_FIXS) + *flagp++ = "SDEP"[((instr>>7)&1) + ((instr>>18)&2)]; + *flagp++ = "\0PMZ"[(instr&(3<<5))>>5]; + if (instr&(7<<15)) result.oddbits=1; /* Fn unused */ + if (instr&(1<<3)) result.badbits=1; /* no immediate consts */ + } + else { + /* neither FLT nor FIX */ + format = "3"; + if (instr&(3<<5)) result.badbits=1; /* rmode reserved */ + if (instr&(15+(7<<15))) result.oddbits=1;/* iFm, Fn unused */ + } + } + } + } + else { + /* some other copro number. Not FPU. */ + /* NB that ObjAsm documentation gets MCR and MRC the wrong way round! + */ + mnemonic = "MCR\0MRC"; + mnemonic += (instr&Lbit) >> 18; + format = ";,:,3,\005,\001-"; + } + } + else { + /* CDP. */ + if (CP_is(1)) { + /* copro 1: FPU. */ + mnemonic = /* dyadics: */ + "ADF\0MUF\0SUF\0RSF\0" + "DVF\0RDF\0POW\0RPW\0" + "RMF\0FML\0FDV\0FRD\0" + "POL\0***\0***\0***\0" + /* monadics: */ + "MVF\0MNF\0ABS\0RND\0" + "SQT\0LOG\0LGN\0EXP\0" + "SIN\0COS\0TAN\0ASN\0" + "ACS\0ATN\0URD\0NRM\0" + + ((instr&(15<<20)) >> 18) /* opcode -> bits 5432 */ + + ((instr&(1<<15)) >> 9); /* monadicP -> bit 6 */ + format = (instr&(1<<15)) ? "8,+" : "8,9,+"; + *flagp++ = "SDE*"[((instr>>7)&1) + ((instr>>18)&2)]; + *flagp++ = "\0PMZ"[(instr&(3<<5))>>5]; + /* NB that foregoing relies on this being the last flag! */ + if (*mnemonic=='*' || *flagchars=='*') goto lUndefined; + } + else { + /* some other copro number. Not FPU. */ + mnemonic = "CDP"; + format = ";,),\004,\005,\001-"; + } + } + break; + case 15: + /* SWI */ + mnemonic = "SWI"; + format = "$"; + break; +/* Nasty hack: this is code that won't be reached in the normal + * course of events, and after the last case of the switch is a + * convenient place for it. + */ +lUndefined: + strcpy(result.text, "Undefined instruction"); + result.undefined = 1; + return &result; + } + *flagp=0; + + /* PHASE 2. Produce string. */ + + { char * op = result.text; + + /* 2a. Mnemonic. */ + + op = append(op,mnemonic); + + /* 2b. Condition code. */ + + { word cond = instr>>28; + if (cond!=14) { + const char * ip = "EQNECSCCMIPLVSVCHILSGELTGTLEALNV"+2*cond; + *op++ = *ip++; + *op++ = *ip; + } + } + + /* 2c. Flags. */ + + { const char * ip = flagchars; + while (*ip) *op++ = *ip++; + } + + /* 2d. A tab character. */ + + *op++ = '\t'; + + /* 2e. Other stuff, determined by format string. */ + + { const char * ip = format; + char c; + + const char * * regnames = opts->regnames; + word oflags = opts->flags; + + while ((c=*ip++) != 0) { + switch(c) { + case '^': // hrydgard addition + { + unsigned short imm16 = ((instr & 0x000F0000) >> 4) | (instr & 0x0FFF); + op += sprintf(op, "%04x", imm16); + } + break; + case '$': + result.is_SWI = 1; + result.swinum = instr&0x00FFFFFF; + result.addrstart = op; + op += sprintf(op, "&%X", result.swinum); + break; + case '%': + *op++='{'; + { word w = instr&0xFFFF; + int i=0; + while (w) { + int j; + while (!(w&(1ul<1) ? '-' : ','; + op = append(op, regnames[j]); + } + i=j; w=(w>>(j+1))<<(j+1); + if (w) *op++=','; + } + } + *op++='}'; + if (instr&(1<<22)) *op++='^'; + break; + case '&': + { address target = (addr+8 + ((((int)instr)<<8)>>6)) & 0x03FFFFFC; + result.addrstart = op; + op = hex8(op, target); + result.target_type = target_Code; + result.target = target; + } + break; + case '\'': +lPling: + if (instr&Wbit) *op++='!'; + break; + case '(': + *op++ = (char)('0'+fpn); + break; + case ')': + { word w = (instr>>20)&15; + if (w>=10) { *op++='1'; *op++=(char)('0'-10+w); } + else *op++=(char)(w+'0'); + } + break; + case '*': + case '.': + if (instr&Ibit) { + /* immediate constant */ + word imm8 = (instr&255); + word rot = (instr>>7)&30; + if (rot && !(imm8&3) && c=='*') { + /* Funny immediate const. Guaranteed not '.', btw */ + *op++='#'; *op++='&'; + *op++="0123456789ABCDEF"[imm8>>4]; + *op++="0123456789ABCDEF"[imm8&15]; + *op++=','; + op = num(op, rot); + } + else { + imm8 = (imm8>>rot) | (imm8<<(32-rot)); + if (c=='*') { + *op++='#'; + if (imm8>256 && ((imm8&(imm8-1))==0)) { + /* only one bit set, and that later than bit 8. + * Represent as 1<<... . + */ + op = append(op,"1<<"); + { int n=0; + while (!(imm8&15)) { n+=4; imm8=imm8>>4; } + /* Now imm8 is 1, 2, 4 or 8. */ + n += (0x30002010 >> 4*(imm8-1))&15; + op = num(op, n); + } + } + else { + if (((int)imm8)<0 && ((int)imm8)>-100) { + *op++='-'; imm8=-imm8; + } + op = num(op, imm8); + } + } + else { + address a = addr+8; + if (instr&(1<<22)) a-=imm8; else a+=imm8; + result.addrstart=op; + op = hex8(op, a); + result.target=a; result.target_type=target_Unknown; + } + } + } + else { + /* rotated register */ + const char * rot = "LSL\0LSR\0ASR\0ROR" + ((instr&(3<<5)) >> 3); + op = append(op, regnames[instr&15]); + if (instr&(1<<4)) { + /* register rotation */ + if (instr&(1<<7)) goto lUndefined; + *op++=','; if (oflags&disopt_CommaSpace) *op++=' '; + op = append(op,rot); *op++=' '; + op = append(op,regnames[(instr&(15<<8))>>8]); + } + else { + /* constant rotation */ + word n = instr&(31<<7); + if (!n) { + if (!(instr&(3<<5))) break; + else if ((instr&(3<<5))==(3<<5)) { + op = append(op, ",RRX"); + break; + } + else n=32<<7; + } + *op++ = ','; if (oflags&disopt_CommaSpace) *op++=' '; + op = num(append(append(op,rot)," #"),n>>7); + } + } + break; + case '+': + if (instr&(1<<3)) { + word w = instr&7; + *op++='#'; + if (w<6) *op++=(char)('0'+w); + else op = append(op, w==6 ? "0.5" : "10"); + } + else { + *op++='f'; + *op++=(char)('0'+(instr&7)); + } + break; + case ',': + *op++=','; + if (oflags&disopt_CommaSpace) *op++=' '; + break; + case '-': + { word w = instr&(7<<5); + if (w) { + *op++=','; + if (oflags&disopt_CommaSpace) *op++=' '; + *op++ = (char)('0'+(w>>5)); + } + } + break; + case '/': + result.addrstart = op; + *op++='['; + op = append(op, regnames[(instr&RNbits)>>16]); + if (!(instr&Pbit)) *op++=']'; + *op++=','; if (oflags&disopt_CommaSpace) *op++=' '; + /* For following, NB that bit 25 is always 0 for LDC, SFM etc */ + if (instr&Ibit) { + /* shifted offset */ + if (!(instr&Ubit)) *op++='-'; + /* We're going to transfer to '*', basically. The stupid + * thing is that the meaning of bit 25 is reversed there; + * I don't know why the designers of the ARM did that. + */ + instr ^= Ibit; + if (instr&(1<<4)) { +#ifdef INSTR_grok_v4 + if (is_v4 && !(instr&(15<<8))) { + ip = (instr&Pbit) ? "0]" : "0"; + break; + } +#else + goto lUndefined; /* LSL r3 forbidden */ +#endif + } + /* Need a ] iff it was pre-indexed; and an optional ! iff + * it's pre-indexed *or* a copro instruction, + * except that FPU operations don't need the !. Bletch. + */ + if (instr&Pbit) ip="*]'"; + else if (instr&(1<<27)) { + if (CP_is(1) || CP_is(2)) { + if (!(instr&Wbit)) goto lUndefined; + ip="*"; + } + else ip="*'"; + } + else ip="*"; + } + else { + /* immediate offset */ + word offset; + if (instr&(1<<27)) { + /* LDF or LFM or similar */ + offset = (instr&255)<<2; + } +#ifdef INSTR_grok_v4 + else if (is_v4) offset = (instr&15) + ((instr&(15<<8))>>4); +#endif + else { + /* LDR or STR */ + offset = instr&0xFFF; + } + *op++='#'; + if (!(instr&Ubit)) { + if (offset) *op++='-'; + else result.oddbits=1; + result.offset = -offset; + } + else result.offset = offset; + op = num(op, offset); + if (RN_is(15) && (instr&Pbit)) { + /* Immediate, pre-indexed and PC-relative. Set target. */ + result.target_type = poss_tt; + result.target = (instr&Ubit) ? addr+8 + offset + : addr+8 - offset; + if (!(instr&Wbit)) { + /* no writeback, either. Use friendly form. */ + op = hex8(result.addrstart, result.target); + break; + } + } + if (instr&Pbit) { *op++=']'; goto lPling; } + else if (instr&(1<<27)) { + if (CP_is(1) || CP_is(2)) { + if (!(instr&Wbit)) goto lUndefined; + } + else goto lPling; + } + } + break; + case '0': case '1': case '2': case '3': case '4': + op = append(op, regnames[(instr>>(4*(c-'0')))&15]); + break; + case '5': case '6': case '7': case '8': case '9': + *op++='f'; + *op++=(char)('0' + ((instr>>(4*(c-'5')))&7)); + break; + case ':': + *op++ = (char)('0' + ((instr>>21)&7)); + break; + case ';': + op = reg(op, 'p', instr>>8); + break; + default: + if (c<=5) + op = reg(op, 'c', instr >> (4*(c-1))); + else *op++ = c; + } + } + *op=0; + } + } + + /* DONE! */ + + return &result; +} + +static const char * reg_names[16] = { + "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", + "r8", "r9", "r10", "r11", "ip", "sp", "lr", "pc" +}; + +static sDisOptions options = { + disopt_CommaSpace, + reg_names +}; + +const char *ArmRegName(int r) { + return reg_names[r]; +} +void ArmDis(unsigned int addr, unsigned int w, char *output) { + pInstruction instr = instr_disassemble(w, addr, &options); + sprintf(output, "%08x\t%s", w, instr->text); + if (instr->undefined || instr->badbits || instr->oddbits) { + if (instr->undefined) sprintf(output, " [undefined instr %08x]", w); + if (instr->badbits) sprintf(output, " [illegal bits %08x]", w); + + // HUH? LDR and STR gets this a lot + // strcat(output, " ? (extra bits)"); + // if (instr->oddbits) sprintf(output, " [unexpected bits %08x]", w); + } + // zap tabs + while (*output) { + if (*output == '\t') + *output = ' '; + output++; + } +} \ No newline at end of file diff --git a/ext/disarm.h b/ext/disarm.h new file mode 100644 index 0000000000..5032880a18 --- /dev/null +++ b/ext/disarm.h @@ -0,0 +1,34 @@ +// Copyright (c) 2012- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#pragma once + + +// This stuff only disassembles very old ARM but should be sufficient +// for the basics except for MOVW/MOVT. + + + +// Disarm itself has the license you can see in the cpp file. +// I'm not entirely sure it's 100% gpl compatible but it's nearly +// public domain so meh. + +// The only changes I've done is C++ compat and replaced the main +// program with this function. + +const char *ArmRegName(int r); +void ArmDis(unsigned int addr, unsigned int w, char *output);