// 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/ // WARNING - THIS LIBRARY IS NOT THREAD SAFE!!! #ifndef _DOLPHIN_ARM_CODEGEN_ #define _DOLPHIN_ARM_CODEGEN_ #include "Common.h" #include "MemoryUtil.h" namespace ArmGen { enum ARMReg { // GPRs R0 = 0, R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, // SPRs // R13 - R15 are SP, LR, and PC. // Almost always referred to by name instead of register number R12 = 12, R13 = 13, R14 = 14, R15 = 15, _IP = 12, _SP = 13, _LR = 14, _PC = 15, // VFP single precision registers S0 = 0, 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, D8, D9, D10, D11, D12, D13, D14, D15, D16, D17, D18, D19, D20, D21, D22, D23, D24, D25, D26, D27, D28, D29, D30, D31, INVALID_REG = 0xFFFFFFFF }; enum CCFlags { CC_EQ = 0, // Equal CC_NEQ, // Not equal CC_CS, // Carry Set CC_CC, // Carry Clear CC_MI, // Minus (Negative) CC_PL, // Plus CC_VS, // Overflow CC_VC, // No Overflow CC_HI, // Unsigned higher CC_LS, // Unsigned lower or same CC_GE, // Signed greater than or equal CC_LT, // Signed less than 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 }; const u32 NO_COND = 0xE0000000; enum ShiftType { LSL = 0, ASL = 0, LSR = 1, ASR = 2, ROR = 3, RRX = 4 }; enum { NUMGPRs = 13, }; class ARMXEmitter; enum OpType { TYPE_IMM = 0, TYPE_REG, TYPE_IMMSREG, TYPE_RSR, TYPE_MEM }; class Operand2 { friend class ARMXEmitter; protected: u32 Value; private: OpType Type; // IMM types u8 Rotation; // Only for u8 values // Register types u8 IndexOrShift; ShiftType Shift; public: OpType GetType() { return Type; } Operand2() {} Operand2(u32 imm, OpType type = TYPE_IMM) { Type = type; Value = imm; Rotation = 0; } Operand2(ARMReg Reg) { Type = TYPE_REG; Value = Reg; Rotation = 0; } Operand2(u8 imm, u8 rotation) { Type = TYPE_IMM; Value = imm; Rotation = rotation; } 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"); IndexOrShift = shift; Shift = type; Value = base; } Operand2(u8 shift, ShiftType type, ARMReg base)// For IMM shifted register { if(shift == 32) shift = 0; switch (type) { case LSL: _assert_msg_(DYNA_REC, shift < 32, "Invalid Operand2: LSL %u", shift); break; case LSR: _assert_msg_(DYNA_REC, shift <= 32, "Invalid Operand2: LSR %u", shift); if (!shift) type = LSL; if (shift == 32) shift = 0; break; case ASR: _assert_msg_(DYNA_REC, shift < 32, "Invalid Operand2: LSR %u", shift); if (!shift) type = LSL; if (shift == 32) shift = 0; break; case ROR: _assert_msg_(DYNA_REC, shift < 32, "Invalid Operand2: ROR %u", shift); if (!shift) type = LSL; break; case RRX: _assert_msg_(DYNA_REC, shift == 0, "Invalid Operand2: RRX does not take an immediate shift amount"); type = ROR; break; } IndexOrShift = shift; Shift = type; Value = base; Type = TYPE_IMMSREG; } const u32 GetData() { switch(Type) { case TYPE_IMM: return Imm12Mod(); // This'll need to be changed later case TYPE_REG: return Rm(); case TYPE_IMMSREG: return IMMSR(); case TYPE_RSR: return RSR(); default: _assert_msg_(DYNA_REC, false, "GetData with Invalid Type"); break; } } const u32 IMMSR() // IMM shifted register { _assert_msg_(DYNA_REC, Type = TYPE_IMMSREG, "IMMSR must be imm shifted register"); return ((IndexOrShift & 0x1f) << 7 | (Shift << 5) | Value); } const u32 RSR() // Register shifted register { _assert_msg_(DYNA_REC, Type == TYPE_RSR, "RSR must be RSR Of Course"); return (IndexOrShift << 8) | (Shift << 5) | 0x10 | Value; } const u32 Rm() { _assert_msg_(DYNA_REC, Type == TYPE_REG, "Rm must be with Reg"); return Value; } const u32 Imm5() { _assert_msg_(DYNA_REC, (Type == TYPE_IMM), "Imm5 not IMM value"); return ((Value & 0x0000001F) << 7); } const u32 Imm8Rot() // IMM8 with Rotation { _assert_msg_(DYNA_REC, (Type == TYPE_IMM), "Imm8Rot not IMM value"); _assert_msg_(DYNA_REC, (Rotation & 0xE1) != 0, "Invalid Operand2: immediate rotation %u", Rotation); return (1 << 25) | (Rotation << 7) | (Value & 0x000000FF); } const u32 Imm12() { _assert_msg_(DYNA_REC, (Type == TYPE_IMM), "Imm12 not IMM"); return (Value & 0x00000FFF); } const u32 Imm12Mod() { // This is a IMM12 with the top four bits being rotation and the // bottom eight being a IMM. This is for instructions that need to // expand a 8bit IMM to a 32bit value and gives you some rotation as // well. // Each rotation rotates to the right by 2 bits _assert_msg_(DYNA_REC, (Type == TYPE_IMM), "Imm12Mod not IMM"); return ((Rotation & 0xF) << 8) | (Value & 0xFF); } const u32 Imm16() { _assert_msg_(DYNA_REC, (Type == TYPE_IMM), "Imm16 not IMM"); return ( (Value & 0xF000) << 4) | (Value & 0x0FFF); } const u32 Imm16Low() { return Imm16(); } const u32 Imm16High() // Returns high 16bits { _assert_msg_(DYNA_REC, (Type == TYPE_IMM), "Imm16 not IMM"); return ( ((Value >> 16) & 0xF000) << 4) | ((Value >> 16) & 0x0FFF); } const u32 Imm24() { _assert_msg_(DYNA_REC, (Type == TYPE_IMM), "Imm16 not IMM"); return (Value & 0x0FFFFFFF); } }; 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 { u8 *ptr; u32 condition; // Remembers our codition at the time int type; //0 = B 1 = BL }; typedef const u8* JumpTarget; class ARMXEmitter { friend struct OpArg; // for Write8 etc private: u8 *code, *startcode; 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); // New Ops void WriteInstruction(u32 op, ARMReg Rd, ARMReg Rn, Operand2 Rm, bool SetFlags = false); 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;} virtual ~ARMXEmitter() {} void SetCodePtr(u8 *ptr); void ReserveCodeSpace(u32 bytes); const u8 *AlignCode16(); const u8 *AlignCodePage(); const u8 *GetCodePtr() const; void Flush(); u8 *GetWritableCodePtr(); void SetCC(CCFlags cond = CC_AL); // Special purpose instructions // Dynamic Endian Switching void SETEND(bool BE); // Debug Breakpoint void BKPT(u16 arg); // Hint instruction void YIELD(); // Do nothing void NOP(int count = 1); //nop padding - TODO: fast nop slides, for amd and intel (check their manuals) #ifdef CALL #undef CALL #endif // Branching FixupBranch B(); FixupBranch B_CC(CCFlags Cond); void B_CC(CCFlags Cond, const void *fnptr); FixupBranch BL(); FixupBranch BL_CC(CCFlags Cond); void SetJumpTarget(FixupBranch const &branch); void B (const void *fnptr); void B (ARMReg src); void BL(const void *fnptr); void BL(ARMReg src); void PUSH(const int num, ...); void POP(const int num, ...); // New Data Ops void AND (ARMReg Rd, ARMReg Rn, Operand2 Rm); void ANDS(ARMReg Rd, ARMReg Rn, Operand2 Rm); void EOR (ARMReg dest, ARMReg src, Operand2 op2); void EORS(ARMReg dest, ARMReg src, Operand2 op2); void SUB (ARMReg dest, ARMReg src, Operand2 op2); void SUBS(ARMReg dest, ARMReg src, Operand2 op2); void RSB (ARMReg dest, ARMReg src, Operand2 op2); void RSBS(ARMReg dest, ARMReg src, Operand2 op2); void ADD (ARMReg dest, ARMReg src, Operand2 op2); void ADDS(ARMReg dest, ARMReg src, Operand2 op2); void ADC (ARMReg dest, ARMReg src, Operand2 op2); void ADCS(ARMReg dest, ARMReg src, Operand2 op2); void LSL (ARMReg dest, ARMReg src, Operand2 op2); void LSL (ARMReg dest, ARMReg src, ARMReg op2); void LSLS(ARMReg dest, ARMReg src, Operand2 op2); 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 RSC (ARMReg dest, ARMReg src, Operand2 op2); void RSCS(ARMReg dest, ARMReg src, Operand2 op2); void TST ( ARMReg src, Operand2 op2); void TEQ ( ARMReg src, Operand2 op2); void CMP ( ARMReg src, Operand2 op2); void CMN ( ARMReg src, Operand2 op2); void ORR (ARMReg dest, ARMReg src, Operand2 op2); 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 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); 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 // Just need to put an underscore here, bit annoying. void _MSR (bool nzcvq, bool g, Operand2 op2); void _MSR (bool nzcvq, bool g, ARMReg src ); void MRS (ARMReg dest); // Memory load/store operations void LDR (ARMReg dest, ARMReg src, Operand2 op2 = 0); // Offset adds to the base register in LDR void LDR (ARMReg dest, ARMReg base, ARMReg offset, bool Index, bool Add); void LDRB(ARMReg dest, ARMReg src, Operand2 op2 = 0); void STR (ARMReg dest, ARMReg src, Operand2 op2 = 0); // Offset adds on to the destination register in STR void STR (ARMReg dest, ARMReg base, ARMReg offset, bool Index, bool Add); void STRB(ARMReg dest, ARMReg src, Operand2 op2 = 0); void STMFD(ARMReg dest, bool WriteBack, const int Regnum, ...); void LDMFD(ARMReg dest, bool WriteBack, const int Regnum, ...); // Exclusive Access operations void LDREX(ARMReg dest, ARMReg base); // dest contains the result if the instruction managed to store the value void STREX(ARMReg dest, ARMReg base, ARMReg op); void DMB (); // 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_ShowConditions(); 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 // Everything that needs to generate X86 code should inherit from this. // You get memory management for free, plus, you can use all the MOV etc functions without // having to prefix them with gen-> or something similar. class ARMXCodeBlock : public ARMXEmitter { protected: u8 *region; size_t region_size; public: ARMXCodeBlock() : region(NULL), region_size(0) {} virtual ~ARMXCodeBlock() { if (region) FreeCodeSpace(); } // Call this before you generate any code. void AllocCodeSpace(int size) { region_size = size; region = (u8*)AllocateExecutableMemory(region_size); SetCodePtr(region); } // Always clear code space with breakpoints, so that if someone accidentally executes // uninitialized, it just breaks into the debugger. void ClearCodeSpace() { // x86/64: 0xCC = breakpoint memset(region, 0xCC, region_size); ResetCodePtr(); } // Call this when shutting down. Don't rely on the destructor, even though it'll do the job. void FreeCodeSpace() { FreeMemoryPages(region, region_size); region = NULL; region_size = 0; } bool IsInCodeSpace(u8 *ptr) { return ptr >= region && ptr < region + region_size; } // Cannot currently be undone. Will write protect the entire code region. // Start over if you need to change the code (call FreeCodeSpace(), AllocCodeSpace()). void WriteProtect() { WriteProtectMemory(region, region_size, true); } void ResetCodePtr() { SetCodePtr(region); } size_t GetSpaceLeft() const { return region_size - (GetCodePtr() - region); } }; } // namespace #endif // _DOLPHIN_INTEL_CODEGEN_