diff options
Diffstat (limited to 'ARMeilleure/Instructions')
| -rw-r--r-- | ARMeilleure/Instructions/DelegateTypes.cs | 2 | ||||
| -rw-r--r-- | ARMeilleure/Instructions/InstEmitAluHelper.cs | 22 | ||||
| -rw-r--r-- | ARMeilleure/Instructions/InstEmitException.cs | 5 | ||||
| -rw-r--r-- | ARMeilleure/Instructions/InstEmitException32.cs | 3 | ||||
| -rw-r--r-- | ARMeilleure/Instructions/InstEmitFlow.cs | 18 | ||||
| -rw-r--r-- | ARMeilleure/Instructions/InstEmitFlow32.cs | 14 | ||||
| -rw-r--r-- | ARMeilleure/Instructions/InstEmitFlowHelper.cs | 200 | ||||
| -rw-r--r-- | ARMeilleure/Instructions/InstEmitHelper.cs | 32 | ||||
| -rw-r--r-- | ARMeilleure/Instructions/InstEmitMemoryHelper.cs | 2 | ||||
| -rw-r--r-- | ARMeilleure/Instructions/NativeInterface.cs | 43 |
10 files changed, 265 insertions, 76 deletions
diff --git a/ARMeilleure/Instructions/DelegateTypes.cs b/ARMeilleure/Instructions/DelegateTypes.cs index b65149cb..41614f88 100644 --- a/ARMeilleure/Instructions/DelegateTypes.cs +++ b/ARMeilleure/Instructions/DelegateTypes.cs @@ -3,6 +3,8 @@ using System; namespace ARMeilleure.Instructions { + delegate bool _Bool(); + delegate double _F64_F64(double a1); delegate double _F64_F64_Bool(double a1, bool a2); delegate double _F64_F64_F64(double a1, double a2); diff --git a/ARMeilleure/Instructions/InstEmitAluHelper.cs b/ARMeilleure/Instructions/InstEmitAluHelper.cs index 916a1da5..12fa1bf1 100644 --- a/ARMeilleure/Instructions/InstEmitAluHelper.cs +++ b/ARMeilleure/Instructions/InstEmitAluHelper.cs @@ -116,12 +116,14 @@ namespace ARMeilleure.Instructions { Debug.Assert(value.Type == OperandType.I32); - context.StoreToContext(); - if (IsThumb(context.CurrOp)) { - // Make this count as a call, the translator will ignore the low bit for the address. - context.Return(context.ZeroExtend32(OperandType.I64, context.BitwiseOr(value, Const(1)))); + context.StoreToContext(); + bool isReturn = IsA32Return(context); + + Operand addr = context.BitwiseOr(value, Const(1)); + + InstEmitFlowHelper.EmitVirtualJump(context, addr, isReturn); } else { @@ -138,18 +140,8 @@ namespace ARMeilleure.Instructions if (setFlags) { // TODO: Load SPSR etc. - Operand isThumb = GetFlag(PState.TFlag); - - Operand lblThumb = Label(); - - context.BranchIfTrue(lblThumb, isThumb); - - // Make this count as a call, the translator will ignore the low bit for the address. - context.Return(context.ZeroExtend32(OperandType.I64, context.BitwiseOr(context.BitwiseAnd(value, Const(~3)), Const(1)))); - - context.MarkLabel(lblThumb); - context.Return(context.ZeroExtend32(OperandType.I64, context.BitwiseOr(value, Const(1)))); + EmitBxWritePc(context, value); } else { diff --git a/ARMeilleure/Instructions/InstEmitException.cs b/ARMeilleure/Instructions/InstEmitException.cs index 6f7b6fd5..f0bde242 100644 --- a/ARMeilleure/Instructions/InstEmitException.cs +++ b/ARMeilleure/Instructions/InstEmitException.cs @@ -2,6 +2,7 @@ using ARMeilleure.Decoders; using ARMeilleure.Translation; using System; +using static ARMeilleure.Instructions.InstEmitFlowHelper; using static ARMeilleure.IntermediateRepresentation.OperandHelper; namespace ARMeilleure.Instructions @@ -30,7 +31,7 @@ namespace ARMeilleure.Instructions if (context.CurrBlock.Next == null) { - context.Return(Const(op.Address + 4)); + EmitTailContinue(context, Const(op.Address + 4)); } } @@ -48,7 +49,7 @@ namespace ARMeilleure.Instructions if (context.CurrBlock.Next == null) { - context.Return(Const(op.Address + 4)); + EmitTailContinue(context, Const(op.Address + 4)); } } } diff --git a/ARMeilleure/Instructions/InstEmitException32.cs b/ARMeilleure/Instructions/InstEmitException32.cs index a73f0dec..8ffad1d1 100644 --- a/ARMeilleure/Instructions/InstEmitException32.cs +++ b/ARMeilleure/Instructions/InstEmitException32.cs @@ -1,6 +1,7 @@ using ARMeilleure.Decoders; using ARMeilleure.Translation; +using static ARMeilleure.Instructions.InstEmitFlowHelper; using static ARMeilleure.IntermediateRepresentation.OperandHelper; namespace ARMeilleure.Instructions @@ -29,7 +30,7 @@ namespace ARMeilleure.Instructions if (context.CurrBlock.Next == null) { - context.Return(Const(op.Address + 4)); + EmitTailContinue(context, Const(op.Address + 4)); } } } diff --git a/ARMeilleure/Instructions/InstEmitFlow.cs b/ARMeilleure/Instructions/InstEmitFlow.cs index 93d36e1b..bac9ec58 100644 --- a/ARMeilleure/Instructions/InstEmitFlow.cs +++ b/ARMeilleure/Instructions/InstEmitFlow.cs @@ -21,7 +21,7 @@ namespace ARMeilleure.Instructions } else { - context.Return(Const(op.Immediate)); + EmitTailContinue(context, Const(op.Immediate), context.CurrBlock.TailCall); } } @@ -56,7 +56,7 @@ namespace ARMeilleure.Instructions { OpCodeBReg op = (OpCodeBReg)context.CurrOp; - EmitVirtualJump(context, GetIntOrZR(context, op.Rn)); + EmitVirtualJump(context, GetIntOrZR(context, op.Rn), op.Rn == RegisterAlias.Lr); } public static void Cbnz(ArmEmitterContext context) => EmitCb(context, onNotZero: true); @@ -71,7 +71,7 @@ namespace ARMeilleure.Instructions public static void Ret(ArmEmitterContext context) { - context.Return(context.BitwiseOr(GetIntOrZR(context, RegisterAlias.Lr), Const(CallFlag))); + context.Return(GetIntOrZR(context, RegisterAlias.Lr)); } public static void Tbnz(ArmEmitterContext context) => EmitTb(context, onNotZero: true); @@ -96,7 +96,7 @@ namespace ARMeilleure.Instructions if (context.CurrBlock.Next == null) { - context.Return(Const(op.Address + 4)); + EmitTailContinue(context, Const(op.Address + 4)); } } else @@ -105,11 +105,11 @@ namespace ARMeilleure.Instructions EmitCondBranch(context, lblTaken, cond); - context.Return(Const(op.Address + 4)); + EmitTailContinue(context, Const(op.Address + 4)); context.MarkLabel(lblTaken); - context.Return(Const(op.Immediate)); + EmitTailContinue(context, Const(op.Immediate)); } } @@ -132,7 +132,7 @@ namespace ARMeilleure.Instructions if (context.CurrBlock.Next == null) { - context.Return(Const(op.Address + 4)); + EmitTailContinue(context, Const(op.Address + 4)); } } else @@ -148,11 +148,11 @@ namespace ARMeilleure.Instructions context.BranchIfFalse(lblTaken, value); } - context.Return(Const(op.Address + 4)); + EmitTailContinue(context, Const(op.Address + 4)); context.MarkLabel(lblTaken); - context.Return(Const(op.Immediate)); + EmitTailContinue(context, Const(op.Immediate)); } } } diff --git a/ARMeilleure/Instructions/InstEmitFlow32.cs b/ARMeilleure/Instructions/InstEmitFlow32.cs index cbb9ad5b..47233eb9 100644 --- a/ARMeilleure/Instructions/InstEmitFlow32.cs +++ b/ARMeilleure/Instructions/InstEmitFlow32.cs @@ -21,8 +21,7 @@ namespace ARMeilleure.Instructions } else { - context.StoreToContext(); - context.Return(Const(op.Immediate)); + EmitTailContinue(context, Const(op.Immediate)); } } @@ -57,7 +56,7 @@ namespace ARMeilleure.Instructions SetFlag(context, PState.TFlag, Const(isThumb ? 0 : 1)); } - InstEmitFlowHelper.EmitCall(context, (ulong)op.Immediate); + EmitCall(context, (ulong)op.Immediate); } public static void Blxr(ArmEmitterContext context) @@ -66,9 +65,8 @@ namespace ARMeilleure.Instructions uint pc = op.GetPc(); - Operand addr = GetIntA32(context, op.Rm); + Operand addr = context.Copy(GetIntA32(context, op.Rm)); Operand bitOne = context.BitwiseAnd(addr, Const(1)); - addr = context.BitwiseOr(addr, Const((int)CallFlag)); // Set call flag. bool isThumb = IsThumb(context.CurrOp); @@ -80,16 +78,14 @@ namespace ARMeilleure.Instructions SetFlag(context, PState.TFlag, bitOne); - context.Return(addr); // Call. + EmitVirtualCall(context, addr); } public static void Bx(ArmEmitterContext context) { IOpCode32BReg op = (IOpCode32BReg)context.CurrOp; - context.StoreToContext(); - - EmitBxWritePc(context, GetIntA32(context, op.Rm)); + EmitBxWritePc(context, GetIntA32(context, op.Rm), op.Rm); } } }
\ No newline at end of file diff --git a/ARMeilleure/Instructions/InstEmitFlowHelper.cs b/ARMeilleure/Instructions/InstEmitFlowHelper.cs index a8eb21d3..f0a81e85 100644 --- a/ARMeilleure/Instructions/InstEmitFlowHelper.cs +++ b/ARMeilleure/Instructions/InstEmitFlowHelper.cs @@ -2,6 +2,7 @@ using ARMeilleure.Decoders; using ARMeilleure.IntermediateRepresentation; using ARMeilleure.State; using ARMeilleure.Translation; +using System; using static ARMeilleure.Instructions.InstEmitHelper; using static ARMeilleure.IntermediateRepresentation.OperandHelper; @@ -142,7 +143,29 @@ namespace ARMeilleure.Instructions public static void EmitCall(ArmEmitterContext context, ulong immediate) { - context.Return(Const(immediate | CallFlag)); + EmitJumpTableBranch(context, Const(immediate)); + } + + private static void EmitNativeCall(ArmEmitterContext context, Operand nativeContextPtr, Operand funcAddr, bool isJump = false) + { + context.StoreToContext(); + Operand returnAddress; + if (isJump) + { + context.Tailcall(funcAddr, nativeContextPtr); + } + else + { + returnAddress = context.Call(funcAddr, OperandType.I64, nativeContextPtr); + context.LoadFromContext(); + + EmitContinueOrReturnCheck(context, returnAddress); + } + } + + private static void EmitNativeCall(ArmEmitterContext context, Operand funcAddr, bool isJump = false) + { + EmitNativeCall(context, context.LoadArgument(OperandType.I64, 0), funcAddr, isJump); } public static void EmitVirtualCall(ArmEmitterContext context, Operand target) @@ -150,37 +173,45 @@ namespace ARMeilleure.Instructions EmitVirtualCallOrJump(context, target, isJump: false); } - public static void EmitVirtualJump(ArmEmitterContext context, Operand target) + public static void EmitVirtualJump(ArmEmitterContext context, Operand target, bool isReturn) { - EmitVirtualCallOrJump(context, target, isJump: true); + EmitVirtualCallOrJump(context, target, isJump: true, isReturn: isReturn); } - private static void EmitVirtualCallOrJump(ArmEmitterContext context, Operand target, bool isJump) + private static void EmitVirtualCallOrJump(ArmEmitterContext context, Operand target, bool isJump, bool isReturn = false) { - context.Return(context.BitwiseOr(target, Const(target.Type, (long)CallFlag))); + if (isReturn) + { + context.Return(target); + } + else + { + EmitJumpTableBranch(context, target, isJump); + } } - private static void EmitContinueOrReturnCheck(ArmEmitterContext context, Operand retVal) + private static void EmitContinueOrReturnCheck(ArmEmitterContext context, Operand returnAddress) { - // Note: The return value of the called method will be placed - // at the Stack, the return value is always a Int64 with the - // return address of the function. We check if the address is - // correct, if it isn't we keep returning until we reach the dispatcher. - ulong nextAddr = GetNextOpAddress(context.CurrOp); + // Note: The return value of a translated function is always an Int64 with the + // address execution has returned to. We expect this address to be immediately after the + // current instruction, if it isn't we keep returning until we reach the dispatcher. + Operand nextAddr = Const(GetNextOpAddress(context.CurrOp)); - if (context.CurrBlock.Next != null) - { - Operand lblContinue = Label(); + // Try to continue within this block. + // If the return address isn't to our next instruction, we need to return so the JIT can figure out what to do. + Operand lblContinue = Label(); - context.BranchIfTrue(lblContinue, context.ICompareEqual(retVal, Const(nextAddr))); + // We need to clear out the call flag for the return address before comparing it. + context.BranchIfTrue(lblContinue, context.ICompareEqual(context.BitwiseAnd(returnAddress, Const(~CallFlag)), nextAddr)); - context.Return(Const(nextAddr)); + context.Return(returnAddress); - context.MarkLabel(lblContinue); - } - else + context.MarkLabel(lblContinue); + + if (context.CurrBlock.Next == null) { - context.Return(Const(nextAddr)); + // No code following this instruction, try and find the next block and jump to it. + EmitTailContinue(context, nextAddr); } } @@ -188,5 +219,134 @@ namespace ARMeilleure.Instructions { return op.Address + (ulong)op.OpCodeSizeInBytes; } + + public static void EmitTailContinue(ArmEmitterContext context, Operand address, bool allowRejit = false) + { + bool useTailContinue = true; // Left option here as it may be useful if we need to return to managed rather than tail call in future. (eg. for debug) + if (useTailContinue) + { + if (allowRejit) + { + address = context.BitwiseOr(address, Const(1L)); + } + + Operand fallbackAddr = context.Call(new _U64_U64(NativeInterface.GetFunctionAddress), address); + + EmitNativeCall(context, fallbackAddr, true); + } + else + { + context.Return(address); + } + } + + private static void EmitNativeCallWithGuestAddress(ArmEmitterContext context, Operand funcAddr, Operand guestAddress, bool isJump) + { + Operand nativeContextPtr = context.LoadArgument(OperandType.I64, 0); + context.Store(context.Add(nativeContextPtr, Const(NativeContext.GetCallAddressOffset())), guestAddress); + + EmitNativeCall(context, nativeContextPtr, funcAddr, isJump); + } + + private static void EmitBranchFallback(ArmEmitterContext context, Operand address, bool isJump) + { + address = context.BitwiseOr(address, Const(address.Type, (long)CallFlag)); // Set call flag. + Operand fallbackAddr = context.Call(new _U64_U64(NativeInterface.GetFunctionAddress), address); + EmitNativeCall(context, fallbackAddr, isJump); + } + + public static void EmitDynamicTableCall(ArmEmitterContext context, Operand tableAddress, Operand address, bool isJump) + { + // Loop over elements of the dynamic table. Unrolled loop. + + Operand endLabel = Label(); + Operand fallbackLabel = Label(); + + Action<Operand> emitTableEntry = (Operand entrySkipLabel) => + { + // Try to take this entry in the table if its guest address equals 0. + Operand gotResult = context.CompareAndSwap(tableAddress, Const(0L), address); + + // Is the address ours? (either taken via CompareAndSwap (0), or what was already here) + context.BranchIfFalse(entrySkipLabel, context.BitwiseOr(context.ICompareEqual(gotResult, address), context.ICompareEqual(gotResult, Const(0L)))); + + // It's ours, so what function is it pointing to? + Operand targetFunctionPtr = context.Add(tableAddress, Const(8L)); + Operand targetFunction = context.Load(OperandType.I64, targetFunctionPtr); + + // Call the function. + // We pass in the entry address as the guest address, as the entry may need to be updated by the indirect call stub. + EmitNativeCallWithGuestAddress(context, targetFunction, tableAddress, isJump); + context.Branch(endLabel); + }; + + // Currently this uses a size of 1, as higher values inflate code size for no real benefit. + for (int i = 0; i < JumpTable.DynamicTableElems; i++) + { + if (i == JumpTable.DynamicTableElems - 1) + { + emitTableEntry(fallbackLabel); // If this is the last entry, avoid emitting the additional label and add. + } + else + { + Operand nextLabel = Label(); + + emitTableEntry(nextLabel); + + context.MarkLabel(nextLabel); + tableAddress = context.Add(tableAddress, Const((long)JumpTable.JumpTableStride)); // Move to the next table entry. + } + } + + context.MarkLabel(fallbackLabel); + + EmitBranchFallback(context, address, isJump); + + context.MarkLabel(endLabel); + } + + public static void EmitJumpTableBranch(ArmEmitterContext context, Operand address, bool isJump = false) + { + if (address.Type == OperandType.I32) + { + address = context.ZeroExtend32(OperandType.I64, address); + } + + // TODO: Constant folding. Indirect calls are slower in the best case and emit more code so we want to avoid them when possible. + bool isConst = address.Kind == OperandKind.Constant; + long constAddr = (long)address.Value; + + if (!context.HighCq) + { + // Don't emit indirect calls or jumps if we're compiling in lowCq mode. + // This avoids wasting space on the jump and indirect tables. + // Just ask the translator for the function address. + + EmitBranchFallback(context, address, isJump); + } + else if (!isConst) + { + // Virtual branch/call - store first used addresses on a small table for fast lookup. + int entry = context.JumpTable.ReserveDynamicEntry(isJump); + + int jumpOffset = entry * JumpTable.JumpTableStride * JumpTable.DynamicTableElems; + Operand dynTablePtr = Const(context.JumpTable.DynamicPointer.ToInt64() + jumpOffset); + + EmitDynamicTableCall(context, dynTablePtr, address, isJump); + } + else + { + int entry = context.JumpTable.ReserveTableEntry(context.BaseAddress & (~3L), constAddr, isJump); + + int jumpOffset = entry * JumpTable.JumpTableStride + 8; // Offset directly to the host address. + + // TODO: Relocatable jump table ptr for AOT. Would prefer a solution to patch this constant into functions as they are loaded rather than calculate at runtime. + Operand tableEntryPtr = Const(context.JumpTable.JumpPointer.ToInt64() + jumpOffset); + + Operand funcAddr = context.Load(OperandType.I64, tableEntryPtr); + + EmitNativeCallWithGuestAddress(context, funcAddr, address, isJump); // Call the function directly. If it's not present yet, this will call the direct call stub. + } + } } } diff --git a/ARMeilleure/Instructions/InstEmitHelper.cs b/ARMeilleure/Instructions/InstEmitHelper.cs index f5495c66..a4227543 100644 --- a/ARMeilleure/Instructions/InstEmitHelper.cs +++ b/ARMeilleure/Instructions/InstEmitHelper.cs @@ -144,22 +144,34 @@ namespace ARMeilleure.Instructions } } - public static void EmitBxWritePc(ArmEmitterContext context, Operand pc) + public static bool IsA32Return(ArmEmitterContext context) { + switch (context.CurrOp) + { + case IOpCode32MemMult op: + return true; // Setting PC using LDM is nearly always a return. + case OpCode32AluRsImm op: + return op.Rm == RegisterAlias.Aarch32Lr; + case OpCode32AluRsReg op: + return op.Rm == RegisterAlias.Aarch32Lr; + case OpCode32AluReg op: + return op.Rm == RegisterAlias.Aarch32Lr; + case OpCode32Mem op: + return op.Rn == RegisterAlias.Aarch32Sp && op.WBack && !op.Index; // Setting PC to an address stored on the stack is nearly always a return. + } + return false; + } + + public static void EmitBxWritePc(ArmEmitterContext context, Operand pc, int sourceRegister = 0) + { + bool isReturn = sourceRegister == RegisterAlias.Aarch32Lr || IsA32Return(context); Operand mode = context.BitwiseAnd(pc, Const(1)); SetFlag(context, PState.TFlag, mode); - Operand lblArmMode = Label(); - - context.BranchIfTrue(lblArmMode, mode); - - // Make this count as a call, the translator will ignore the low bit for the address. - context.Return(context.ZeroExtend32(OperandType.I64, context.BitwiseOr(pc, Const((int)InstEmitFlowHelper.CallFlag)))); - - context.MarkLabel(lblArmMode); + Operand addr = context.ConditionalSelect(mode, context.BitwiseOr(pc, Const((int)InstEmitFlowHelper.CallFlag)), context.BitwiseAnd(pc, Const(~3))); - context.Return(context.ZeroExtend32(OperandType.I64, context.BitwiseOr(context.BitwiseAnd(pc, Const(~3)), Const((int)InstEmitFlowHelper.CallFlag)))); + InstEmitFlowHelper.EmitVirtualJump(context, addr, isReturn); } public static Operand GetIntOrZR(ArmEmitterContext context, int regIndex) diff --git a/ARMeilleure/Instructions/InstEmitMemoryHelper.cs b/ARMeilleure/Instructions/InstEmitMemoryHelper.cs index 70861d16..e1dec331 100644 --- a/ARMeilleure/Instructions/InstEmitMemoryHelper.cs +++ b/ARMeilleure/Instructions/InstEmitMemoryHelper.cs @@ -51,7 +51,7 @@ namespace ARMeilleure.Instructions EmitReadInt(context, address, rt, size); } - if (!isSimd) + if (!isSimd && !(context.CurrOp is OpCode32 && rt == State.RegisterAlias.Aarch32Pc)) { Operand value = GetInt(context, rt); diff --git a/ARMeilleure/Instructions/NativeInterface.cs b/ARMeilleure/Instructions/NativeInterface.cs index 988e86bd..4514c0da 100644 --- a/ARMeilleure/Instructions/NativeInterface.cs +++ b/ARMeilleure/Instructions/NativeInterface.cs @@ -1,6 +1,8 @@ using ARMeilleure.Memory; using ARMeilleure.State; +using ARMeilleure.Translation; using System; +using System.Runtime.InteropServices; namespace ARMeilleure.Instructions { @@ -10,17 +12,19 @@ namespace ARMeilleure.Instructions private class ThreadContext { - public ExecutionContext Context { get; } - public MemoryManager Memory { get; } + public ExecutionContext Context { get; } + public MemoryManager Memory { get; } + public Translator Translator { get; } public ulong ExclusiveAddress { get; set; } public ulong ExclusiveValueLow { get; set; } public ulong ExclusiveValueHigh { get; set; } - public ThreadContext(ExecutionContext context, MemoryManager memory) + public ThreadContext(ExecutionContext context, MemoryManager memory, Translator translator) { - Context = context; - Memory = memory; + Context = context; + Memory = memory; + Translator = translator; ExclusiveAddress = ulong.MaxValue; } @@ -29,9 +33,9 @@ namespace ARMeilleure.Instructions [ThreadStatic] private static ThreadContext _context; - public static void RegisterThread(ExecutionContext context, MemoryManager memory) + public static void RegisterThread(ExecutionContext context, MemoryManager memory, Translator translator) { - _context = new ThreadContext(context, memory); + _context = new ThreadContext(context, memory, translator); } public static void UnregisterThread() @@ -381,18 +385,39 @@ namespace ARMeilleure.Instructions return address & ~((4UL << ErgSizeLog2) - 1); } + public static ulong GetFunctionAddress(ulong address) + { + TranslatedFunction function = _context.Translator.GetOrTranslate(address, GetContext().ExecutionMode); + return (ulong)function.GetPointer().ToInt64(); + } + + public static ulong GetIndirectFunctionAddress(ulong address, ulong entryAddress) + { + TranslatedFunction function = _context.Translator.GetOrTranslate(address, GetContext().ExecutionMode); + ulong ptr = (ulong)function.GetPointer().ToInt64(); + if (function.HighCq) + { + // Rewrite the host function address in the table to point to the highCq function. + Marshal.WriteInt64((IntPtr)entryAddress, 8, (long)ptr); + } + return ptr; + } + public static void ClearExclusive() { _context.ExclusiveAddress = ulong.MaxValue; } - public static void CheckSynchronization() + public static bool CheckSynchronization() { Statistics.PauseTimer(); - GetContext().CheckInterrupt(); + ExecutionContext context = GetContext(); + context.CheckInterrupt(); Statistics.ResumeTimer(); + + return context.Running; } public static ExecutionContext GetContext() |
