From 8a7d99cdeae2355511d4eb43aefb76d0d886bcf8 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Fri, 26 Apr 2019 01:55:12 -0300 Subject: [PATCH] Refactoring and optimization on CPU translation (#661) * Refactoring and optimization on CPU translation * Remove now unused property * Rename ilBlock -> block (local) * Change equality comparison on RegisterMask for consistency Co-Authored-By: gdkchan * Add back the aggressive inlining attribute to the Synchronize method * Implement IEquatable on the Register struct * Fix identation --- ChocolArm64/Decoders/Block.cs | 72 ++- ChocolArm64/Decoders/Condition.cs | 10 + ChocolArm64/Decoders/Decoder.cs | 216 +++++---- ChocolArm64/Instructions/InstEmit32Helper.cs | 29 +- ChocolArm64/Instructions/InstEmitAlu.cs | 1 + ChocolArm64/Instructions/InstEmitAlu32.cs | 3 +- ChocolArm64/Instructions/InstEmitCcmp.cs | 1 + ChocolArm64/Instructions/InstEmitCsel.cs | 1 + ChocolArm64/Instructions/InstEmitException.cs | 9 +- ChocolArm64/Instructions/InstEmitFlow.cs | 17 +- ChocolArm64/Instructions/InstEmitFlow32.cs | 4 +- .../Instructions/InstEmitFlowHelper.cs | 11 +- ChocolArm64/Instructions/InstEmitMemory32.cs | 1 + ChocolArm64/Instructions/InstEmitMemoryEx.cs | 1 + .../Instructions/InstEmitMemoryHelper.cs | 1 + .../Instructions/InstEmitSimdArithmetic.cs | 1 + ChocolArm64/Instructions/InstEmitSimdCmp.cs | 1 + ChocolArm64/Instructions/InstEmitSimdMove.cs | 1 + .../IntermediateRepresentation/BasicBlock.cs | 122 +++++ .../IntermediateRepresentation/ILLabel.cs | 4 + .../IntermediateRepresentation/Operation.cs | 112 +++++ .../OperationType.cs | 18 + .../RegisterMask.cs | 56 +++ ChocolArm64/State/CpuThreadState.cs | 20 +- ChocolArm64/State/Register.cs | 11 +- ChocolArm64/State/RegisterConsts.cs | 8 + ChocolArm64/Translation/IILEmit.cs | 7 - ChocolArm64/Translation/ILBarrier.cs | 7 - ChocolArm64/Translation/ILBlock.cs | 74 --- ChocolArm64/Translation/ILEmitterCtx.cs | 424 +++++++----------- ChocolArm64/Translation/ILGeneratorEx.cs | 8 - ChocolArm64/Translation/ILLabel.cs | 28 -- ChocolArm64/Translation/ILMethodBuilder.cs | 123 ----- ChocolArm64/Translation/ILOpCode.cs | 19 - ChocolArm64/Translation/ILOpCodeBranch.cs | 21 - ChocolArm64/Translation/ILOpCodeCall.cs | 23 - ChocolArm64/Translation/ILOpCodeConst.cs | 67 --- ChocolArm64/Translation/ILOpCodeLoad.cs | 46 -- ChocolArm64/Translation/ILOpCodeLoadField.cs | 20 - ChocolArm64/Translation/ILOpCodeLoadState.cs | 51 --- ChocolArm64/Translation/ILOpCodeLog.cs | 17 - ChocolArm64/Translation/ILOpCodeStore.cs | 46 -- ChocolArm64/Translation/ILOpCodeStoreState.cs | 60 --- ChocolArm64/Translation/RegisterUsage.cs | 335 +++++--------- ChocolArm64/Translation/TranslatedSub.cs | 29 +- .../Translation/TranslatedSubBuilder.cs | 274 +++++++++++ ChocolArm64/Translation/Translator.cs | 117 ++++- ChocolArm64/Translation/VarType.cs | 10 - 48 files changed, 1257 insertions(+), 1280 deletions(-) create mode 100644 ChocolArm64/IntermediateRepresentation/BasicBlock.cs create mode 100644 ChocolArm64/IntermediateRepresentation/ILLabel.cs create mode 100644 ChocolArm64/IntermediateRepresentation/Operation.cs create mode 100644 ChocolArm64/IntermediateRepresentation/OperationType.cs create mode 100644 ChocolArm64/IntermediateRepresentation/RegisterMask.cs create mode 100644 ChocolArm64/State/RegisterConsts.cs delete mode 100644 ChocolArm64/Translation/IILEmit.cs delete mode 100644 ChocolArm64/Translation/ILBarrier.cs delete mode 100644 ChocolArm64/Translation/ILBlock.cs delete mode 100644 ChocolArm64/Translation/ILLabel.cs delete mode 100644 ChocolArm64/Translation/ILMethodBuilder.cs delete mode 100644 ChocolArm64/Translation/ILOpCode.cs delete mode 100644 ChocolArm64/Translation/ILOpCodeBranch.cs delete mode 100644 ChocolArm64/Translation/ILOpCodeCall.cs delete mode 100644 ChocolArm64/Translation/ILOpCodeConst.cs delete mode 100644 ChocolArm64/Translation/ILOpCodeLoad.cs delete mode 100644 ChocolArm64/Translation/ILOpCodeLoadField.cs delete mode 100644 ChocolArm64/Translation/ILOpCodeLoadState.cs delete mode 100644 ChocolArm64/Translation/ILOpCodeLog.cs delete mode 100644 ChocolArm64/Translation/ILOpCodeStore.cs delete mode 100644 ChocolArm64/Translation/ILOpCodeStoreState.cs create mode 100644 ChocolArm64/Translation/TranslatedSubBuilder.cs delete mode 100644 ChocolArm64/Translation/VarType.cs diff --git a/ChocolArm64/Decoders/Block.cs b/ChocolArm64/Decoders/Block.cs index c89ea7c6..fc87fd18 100644 --- a/ChocolArm64/Decoders/Block.cs +++ b/ChocolArm64/Decoders/Block.cs @@ -1,11 +1,12 @@ +using System; using System.Collections.Generic; namespace ChocolArm64.Decoders { class Block { - public long Position { get; set; } - public long EndPosition { get; set; } + public ulong Address { get; set; } + public ulong EndAddress { get; set; } public Block Next { get; set; } public Block Branch { get; set; } @@ -17,9 +18,72 @@ namespace ChocolArm64.Decoders OpCodes = new List(); } - public Block(long position) : this() + public Block(ulong address) : this() { - Position = position; + Address = address; + } + + public void Split(Block rightBlock) + { + int splitIndex = BinarySearch(OpCodes, rightBlock.Address); + + if ((ulong)OpCodes[splitIndex].Position < rightBlock.Address) + { + splitIndex++; + } + + int splitCount = OpCodes.Count - splitIndex; + + if (splitCount <= 0) + { + throw new ArgumentException("Can't split at right block address."); + } + + rightBlock.EndAddress = EndAddress; + + rightBlock.Next = Next; + rightBlock.Branch = Branch; + + rightBlock.OpCodes.AddRange(OpCodes.GetRange(splitIndex, splitCount)); + + EndAddress = rightBlock.Address; + + Next = rightBlock; + Branch = null; + + OpCodes.RemoveRange(splitIndex, splitCount); + } + + private static int BinarySearch(List opCodes, ulong address) + { + int left = 0; + int middle = 0; + int right = opCodes.Count - 1; + + while (left <= right) + { + int size = right - left; + + middle = left + (size >> 1); + + OpCode64 opCode = opCodes[middle]; + + if (address == (ulong)opCode.Position) + { + break; + } + + if (address < (ulong)opCode.Position) + { + right = middle - 1; + } + else + { + left = middle + 1; + } + } + + return middle; } public OpCode64 GetLastOp() diff --git a/ChocolArm64/Decoders/Condition.cs b/ChocolArm64/Decoders/Condition.cs index d1aa5772..3f341a98 100644 --- a/ChocolArm64/Decoders/Condition.cs +++ b/ChocolArm64/Decoders/Condition.cs @@ -19,4 +19,14 @@ namespace ChocolArm64.Decoders Al = 14, Nv = 15 } + + static class ConditionExtensions + { + public static Condition Invert(this Condition cond) + { + //Bit 0 of all conditions is basically a negation bit, so + //inverting this bit has the effect of inverting the condition. + return (Condition)((int)cond ^ 1); + } + } } \ No newline at end of file diff --git a/ChocolArm64/Decoders/Decoder.cs b/ChocolArm64/Decoders/Decoder.cs index 6b5d79f0..6a95bc28 100644 --- a/ChocolArm64/Decoders/Decoder.cs +++ b/ChocolArm64/Decoders/Decoder.cs @@ -19,11 +19,11 @@ namespace ChocolArm64.Decoders _opActivators = new ConcurrentDictionary(); } - public static Block DecodeBasicBlock(MemoryManager memory, long start, ExecutionMode mode) + public static Block[] DecodeBasicBlock(MemoryManager memory, ulong address, ExecutionMode mode) { - Block block = new Block(start); + Block block = new Block(address); - FillBlock(memory, mode, block); + FillBlock(memory, mode, block, ulong.MaxValue); OpCode64 lastOp = block.GetLastOp(); @@ -35,140 +35,186 @@ namespace ChocolArm64.Decoders //(which indicates that the block is a loop that jumps back to the start), and the //other possible case is a jump somewhere on the middle of the block, which is //also a loop, but in this case we need to split the block in half. - if (op.Imm == start) + if ((ulong)op.Imm == address) { block.Branch = block; } - else if ((ulong)op.Imm > (ulong)start && - (ulong)op.Imm < (ulong)block.EndPosition) + else if ((ulong)op.Imm > address && + (ulong)op.Imm < block.EndAddress) { - Block botBlock = new Block(op.Imm); + Block rightBlock = new Block((ulong)op.Imm); - int botBlockIndex = 0; + block.Split(rightBlock); - long currPosition = start; - - while ((ulong)currPosition < (ulong)op.Imm) - { - currPosition += block.OpCodes[botBlockIndex++].OpCodeSizeInBytes; - } - - botBlock.OpCodes.AddRange(block.OpCodes); - - botBlock.OpCodes.RemoveRange(0, botBlockIndex); - - block.OpCodes.RemoveRange(botBlockIndex, block.OpCodes.Count - botBlockIndex); - - botBlock.EndPosition = block.EndPosition; - - block.EndPosition = op.Imm; - - botBlock.Branch = botBlock; - block.Next = botBlock; + return new Block[] { block, rightBlock }; } } - return block; + return new Block[] { block }; } - public static Block DecodeSubroutine(MemoryManager memory, long start, ExecutionMode mode) + public static Block[] DecodeSubroutine(MemoryManager memory, ulong address, ExecutionMode mode) { - Dictionary visited = new Dictionary(); - Dictionary visitedEnd = new Dictionary(); + List blocks = new List(); - Queue blocks = new Queue(); + Queue workQueue = new Queue(); - Block Enqueue(long position) + Dictionary visited = new Dictionary(); + + Block GetBlock(ulong blkAddress) { - if (!visited.TryGetValue(position, out Block output)) + if (!visited.TryGetValue(blkAddress, out Block block)) { - output = new Block(position); + block = new Block(blkAddress); - blocks.Enqueue(output); + workQueue.Enqueue(block); - visited.Add(position, output); + visited.Add(blkAddress, block); } - return output; + return block; } - Block entry = Enqueue(start); + GetBlock(address); - while (blocks.Count > 0) + while (workQueue.TryDequeue(out Block currBlock)) { - Block current = blocks.Dequeue(); - - FillBlock(memory, mode, current); - - //Set child blocks. "Branch" is the block the branch instruction - //points to (when taken), "Next" is the block at the next address, - //executed when the branch is not taken. For Unconditional Branches - //(except BL/BLR that are sub calls) or end of executable, Next is null. - if (current.OpCodes.Count > 0) + //Check if the current block is inside another block. + if (BinarySearch(blocks, currBlock.Address, out int nBlkIndex)) { - OpCode64 lastOp = current.GetLastOp(); + Block nBlock = blocks[nBlkIndex]; + + if (nBlock.Address == currBlock.Address) + { + throw new InvalidOperationException("Found duplicate block address on the list."); + } + + nBlock.Split(currBlock); + + blocks.Insert(nBlkIndex + 1, currBlock); + + continue; + } + + //If we have a block after the current one, set the limit address. + ulong limitAddress = ulong.MaxValue; + + if (nBlkIndex != blocks.Count) + { + Block nBlock = blocks[nBlkIndex]; + + int nextIndex = nBlkIndex + 1; + + if (nBlock.Address < currBlock.Address && nextIndex < blocks.Count) + { + limitAddress = blocks[nextIndex].Address; + } + else if (nBlock.Address > currBlock.Address) + { + limitAddress = blocks[nBlkIndex].Address; + } + } + + FillBlock(memory, mode, currBlock, limitAddress); + + if (currBlock.OpCodes.Count != 0) + { + //Set child blocks. "Branch" is the block the branch instruction + //points to (when taken), "Next" is the block at the next address, + //executed when the branch is not taken. For Unconditional Branches + //(except BL/BLR that are sub calls) or end of executable, Next is null. + OpCode64 lastOp = currBlock.GetLastOp(); bool isCall = IsCall(lastOp); if (lastOp is IOpCodeBImm op && !isCall) { - current.Branch = Enqueue(op.Imm); + currBlock.Branch = GetBlock((ulong)op.Imm); } if (!IsUnconditionalBranch(lastOp) || isCall) { - current.Next = Enqueue(current.EndPosition); + currBlock.Next = GetBlock(currBlock.EndAddress); } } - //If we have on the graph two blocks with the same end position, - //then we need to split the bigger block and have two small blocks, - //the end position of the bigger "Current" block should then be == to - //the position of the "Smaller" block. - while (visitedEnd.TryGetValue(current.EndPosition, out Block smaller)) + //Insert the new block on the list (sorted by address). + if (blocks.Count != 0) { - if (current.Position > smaller.Position) - { - Block temp = smaller; + Block nBlock = blocks[nBlkIndex]; - smaller = current; - current = temp; - } - - current.EndPosition = smaller.Position; - current.Next = smaller; - current.Branch = null; - - current.OpCodes.RemoveRange( - current.OpCodes.Count - smaller.OpCodes.Count, - smaller.OpCodes.Count); - - visitedEnd[smaller.EndPosition] = smaller; + blocks.Insert(nBlkIndex + (nBlock.Address < currBlock.Address ? 1 : 0), currBlock); + } + else + { + blocks.Add(currBlock); } - - visitedEnd.Add(current.EndPosition, current); } - return entry; + return blocks.ToArray(); } - private static void FillBlock(MemoryManager memory, ExecutionMode mode, Block block) + private static bool BinarySearch(List blocks, ulong address, out int index) { - long position = block.Position; + index = 0; + + int left = 0; + int right = blocks.Count - 1; + + while (left <= right) + { + int size = right - left; + + int middle = left + (size >> 1); + + Block block = blocks[middle]; + + index = middle; + + if (address >= block.Address && address < block.EndAddress) + { + return true; + } + + if (address < block.Address) + { + right = middle - 1; + } + else + { + left = middle + 1; + } + } + + return false; + } + + private static void FillBlock( + MemoryManager memory, + ExecutionMode mode, + Block block, + ulong limitAddress) + { + ulong address = block.Address; OpCode64 opCode; do { - opCode = DecodeOpCode(memory, position, mode); + if (address >= limitAddress) + { + break; + } + + opCode = DecodeOpCode(memory, address, mode); block.OpCodes.Add(opCode); - position += opCode.OpCodeSizeInBytes; + address += (ulong)opCode.OpCodeSizeInBytes; } while (!(IsBranch(opCode) || IsException(opCode))); - block.EndPosition = position; + block.EndAddress = address; } private static bool IsBranch(OpCode64 opCode) @@ -269,9 +315,9 @@ namespace ChocolArm64.Decoders opCode.Emitter == InstEmit.Und; } - public static OpCode64 DecodeOpCode(MemoryManager memory, long position, ExecutionMode mode) + public static OpCode64 DecodeOpCode(MemoryManager memory, ulong address, ExecutionMode mode) { - int opCode = memory.ReadInt32(position); + int opCode = memory.ReadInt32((long)address); Inst inst; @@ -291,11 +337,11 @@ namespace ChocolArm64.Decoders } } - OpCode64 decodedOpCode = new OpCode64(Inst.Undefined, position, opCode); + OpCode64 decodedOpCode = new OpCode64(Inst.Undefined, (long)address, opCode); if (inst.Type != null) { - decodedOpCode = MakeOpCode(inst.Type, inst, position, opCode); + decodedOpCode = MakeOpCode(inst.Type, inst, (long)address, opCode); } return decodedOpCode; diff --git a/ChocolArm64/Instructions/InstEmit32Helper.cs b/ChocolArm64/Instructions/InstEmit32Helper.cs index 792e96f5..49377981 100644 --- a/ChocolArm64/Instructions/InstEmit32Helper.cs +++ b/ChocolArm64/Instructions/InstEmit32Helper.cs @@ -1,4 +1,5 @@ using ChocolArm64.Decoders; +using ChocolArm64.IntermediateRepresentation; using ChocolArm64.State; using ChocolArm64.Translation; using System; @@ -31,7 +32,7 @@ namespace ChocolArm64.Instructions { if (register == RegisterAlias.Aarch32Pc) { - context.EmitStoreState(); + context.EmitStoreContext(); EmitBxWritePc(context); } @@ -112,13 +113,13 @@ namespace ChocolArm64.Instructions switch (mode) { case Aarch32Mode.User: - case Aarch32Mode.System: return RegisterAlias.SpUsr; - case Aarch32Mode.Fiq: return RegisterAlias.SpFiq; - case Aarch32Mode.Irq: return RegisterAlias.SpIrq; - case Aarch32Mode.Supervisor: return RegisterAlias.SpSvc; - case Aarch32Mode.Abort: return RegisterAlias.SpAbt; - case Aarch32Mode.Hypervisor: return RegisterAlias.SpHyp; - case Aarch32Mode.Undefined: return RegisterAlias.SpUnd; + case Aarch32Mode.System: return RegisterAlias.SpUsr; + case Aarch32Mode.Fiq: return RegisterAlias.SpFiq; + case Aarch32Mode.Irq: return RegisterAlias.SpIrq; + case Aarch32Mode.Supervisor: return RegisterAlias.SpSvc; + case Aarch32Mode.Abort: return RegisterAlias.SpAbt; + case Aarch32Mode.Hypervisor: return RegisterAlias.SpHyp; + case Aarch32Mode.Undefined: return RegisterAlias.SpUnd; default: throw new ArgumentException(nameof(mode)); } @@ -128,12 +129,12 @@ namespace ChocolArm64.Instructions { case Aarch32Mode.User: case Aarch32Mode.Hypervisor: - case Aarch32Mode.System: return RegisterAlias.LrUsr; - case Aarch32Mode.Fiq: return RegisterAlias.LrFiq; - case Aarch32Mode.Irq: return RegisterAlias.LrIrq; - case Aarch32Mode.Supervisor: return RegisterAlias.LrSvc; - case Aarch32Mode.Abort: return RegisterAlias.LrAbt; - case Aarch32Mode.Undefined: return RegisterAlias.LrUnd; + case Aarch32Mode.System: return RegisterAlias.LrUsr; + case Aarch32Mode.Fiq: return RegisterAlias.LrFiq; + case Aarch32Mode.Irq: return RegisterAlias.LrIrq; + case Aarch32Mode.Supervisor: return RegisterAlias.LrSvc; + case Aarch32Mode.Abort: return RegisterAlias.LrAbt; + case Aarch32Mode.Undefined: return RegisterAlias.LrUnd; default: throw new ArgumentException(nameof(mode)); } diff --git a/ChocolArm64/Instructions/InstEmitAlu.cs b/ChocolArm64/Instructions/InstEmitAlu.cs index bd49124e..36ce8c7f 100644 --- a/ChocolArm64/Instructions/InstEmitAlu.cs +++ b/ChocolArm64/Instructions/InstEmitAlu.cs @@ -1,4 +1,5 @@ using ChocolArm64.Decoders; +using ChocolArm64.IntermediateRepresentation; using ChocolArm64.State; using ChocolArm64.Translation; using System; diff --git a/ChocolArm64/Instructions/InstEmitAlu32.cs b/ChocolArm64/Instructions/InstEmitAlu32.cs index 539e7c43..0b4bac2f 100644 --- a/ChocolArm64/Instructions/InstEmitAlu32.cs +++ b/ChocolArm64/Instructions/InstEmitAlu32.cs @@ -1,4 +1,5 @@ using ChocolArm64.Decoders; +using ChocolArm64.IntermediateRepresentation; using ChocolArm64.State; using ChocolArm64.Translation; using System.Reflection.Emit; @@ -122,7 +123,7 @@ namespace ChocolArm64.Instructions private static void EmitAluWritePc(ILEmitterCtx context) { - context.EmitStoreState(); + context.EmitStoreContext(); if (IsThumb(context.CurrOp)) { diff --git a/ChocolArm64/Instructions/InstEmitCcmp.cs b/ChocolArm64/Instructions/InstEmitCcmp.cs index 714ae06a..e21dc696 100644 --- a/ChocolArm64/Instructions/InstEmitCcmp.cs +++ b/ChocolArm64/Instructions/InstEmitCcmp.cs @@ -1,4 +1,5 @@ using ChocolArm64.Decoders; +using ChocolArm64.IntermediateRepresentation; using ChocolArm64.State; using ChocolArm64.Translation; using System; diff --git a/ChocolArm64/Instructions/InstEmitCsel.cs b/ChocolArm64/Instructions/InstEmitCsel.cs index 19b073ce..7008a8c7 100644 --- a/ChocolArm64/Instructions/InstEmitCsel.cs +++ b/ChocolArm64/Instructions/InstEmitCsel.cs @@ -1,4 +1,5 @@ using ChocolArm64.Decoders; +using ChocolArm64.IntermediateRepresentation; using ChocolArm64.Translation; using System.Reflection.Emit; diff --git a/ChocolArm64/Instructions/InstEmitException.cs b/ChocolArm64/Instructions/InstEmitException.cs index 9444397a..7922c62b 100644 --- a/ChocolArm64/Instructions/InstEmitException.cs +++ b/ChocolArm64/Instructions/InstEmitException.cs @@ -1,4 +1,5 @@ using ChocolArm64.Decoders; +using ChocolArm64.IntermediateRepresentation; using ChocolArm64.State; using ChocolArm64.Translation; using System.Reflection.Emit; @@ -21,7 +22,7 @@ namespace ChocolArm64.Instructions { OpCodeException64 op = (OpCodeException64)context.CurrOp; - context.EmitStoreState(); + context.EmitStoreContext(); context.EmitLdarg(TranslatedSub.StateArgIdx); @@ -48,7 +49,7 @@ namespace ChocolArm64.Instructions if (context.CurrBlock.Next != null) { - context.EmitLoadState(); + context.EmitLoadContext(); } else { @@ -62,7 +63,7 @@ namespace ChocolArm64.Instructions { OpCode64 op = context.CurrOp; - context.EmitStoreState(); + context.EmitStoreContext(); context.EmitLdarg(TranslatedSub.StateArgIdx); @@ -73,7 +74,7 @@ namespace ChocolArm64.Instructions if (context.CurrBlock.Next != null) { - context.EmitLoadState(); + context.EmitLoadContext(); } else { diff --git a/ChocolArm64/Instructions/InstEmitFlow.cs b/ChocolArm64/Instructions/InstEmitFlow.cs index 5eae89cc..6355b8b4 100644 --- a/ChocolArm64/Instructions/InstEmitFlow.cs +++ b/ChocolArm64/Instructions/InstEmitFlow.cs @@ -1,4 +1,5 @@ using ChocolArm64.Decoders; +using ChocolArm64.IntermediateRepresentation; using ChocolArm64.State; using ChocolArm64.Translation; using System.Reflection.Emit; @@ -19,7 +20,7 @@ namespace ChocolArm64.Instructions } else { - context.EmitStoreState(); + context.EmitStoreContext(); context.EmitLdc_I8(op.Imm); context.Emit(OpCodes.Ret); @@ -50,7 +51,7 @@ namespace ChocolArm64.Instructions context.EmitLdintzr(op.Rn); context.EmitLdc_I(op.Position + 4); context.EmitStint(RegisterAlias.Lr); - context.EmitStoreState(); + context.EmitStoreContext(); EmitVirtualCall(context); } @@ -61,7 +62,7 @@ namespace ChocolArm64.Instructions context.HasIndirectJump = true; - context.EmitStoreState(); + context.EmitStoreContext(); context.EmitLdintzr(op.Rn); EmitVirtualJump(context); @@ -82,7 +83,7 @@ namespace ChocolArm64.Instructions public static void Ret(ILEmitterCtx context) { - context.EmitStoreState(); + context.EmitStoreContext(); context.EmitLdint(RegisterAlias.Lr); context.Emit(OpCodes.Ret); @@ -115,7 +116,7 @@ namespace ChocolArm64.Instructions if (context.CurrBlock.Next == null) { - context.EmitStoreState(); + context.EmitStoreContext(); context.EmitLdc_I8(op.Position + 4); context.Emit(OpCodes.Ret); @@ -123,7 +124,7 @@ namespace ChocolArm64.Instructions } else { - context.EmitStoreState(); + context.EmitStoreContext(); ILLabel lblTaken = new ILLabel(); @@ -151,7 +152,7 @@ namespace ChocolArm64.Instructions if (context.CurrBlock.Next == null) { - context.EmitStoreState(); + context.EmitStoreContext(); context.EmitLdc_I8(op.Position + 4); context.Emit(OpCodes.Ret); @@ -159,7 +160,7 @@ namespace ChocolArm64.Instructions } else { - context.EmitStoreState(); + context.EmitStoreContext(); ILLabel lblTaken = new ILLabel(); diff --git a/ChocolArm64/Instructions/InstEmitFlow32.cs b/ChocolArm64/Instructions/InstEmitFlow32.cs index dea490c7..b0b9754f 100644 --- a/ChocolArm64/Instructions/InstEmitFlow32.cs +++ b/ChocolArm64/Instructions/InstEmitFlow32.cs @@ -19,7 +19,7 @@ namespace ChocolArm64.Instructions } else { - context.EmitStoreState(); + context.EmitStoreContext(); context.EmitLdc_I8(op.Imm); context.Emit(OpCodes.Ret); @@ -40,7 +40,7 @@ namespace ChocolArm64.Instructions { IOpCode32BReg op = (IOpCode32BReg)context.CurrOp; - context.EmitStoreState(); + context.EmitStoreContext(); EmitLoadFromRegister(context, op.Rm); diff --git a/ChocolArm64/Instructions/InstEmitFlowHelper.cs b/ChocolArm64/Instructions/InstEmitFlowHelper.cs index a6091a57..e7a6bf38 100644 --- a/ChocolArm64/Instructions/InstEmitFlowHelper.cs +++ b/ChocolArm64/Instructions/InstEmitFlowHelper.cs @@ -1,3 +1,4 @@ +using ChocolArm64.IntermediateRepresentation; using ChocolArm64.State; using ChocolArm64.Translation; using System.Reflection; @@ -11,7 +12,7 @@ namespace ChocolArm64.Instructions { if (context.Tier == TranslationTier.Tier0) { - context.EmitStoreState(); + context.EmitStoreContext(); context.TranslateAhead(imm); @@ -26,13 +27,13 @@ namespace ChocolArm64.Instructions { context.HasSlowCall = true; - context.EmitStoreState(); + context.EmitStoreContext(); context.TranslateAhead(imm); context.EmitLdarg(TranslatedSub.StateArgIdx); - context.EmitFieldLoad(typeof(CpuThreadState).GetField(nameof(CpuThreadState.CurrentTranslator), + context.EmitLdfld(typeof(CpuThreadState).GetField(nameof(CpuThreadState.CurrentTranslator), BindingFlags.Instance | BindingFlags.NonPublic)); @@ -72,7 +73,7 @@ namespace ChocolArm64.Instructions context.EmitSttmp(); context.EmitLdarg(TranslatedSub.StateArgIdx); - context.EmitFieldLoad(typeof(CpuThreadState).GetField(nameof(CpuThreadState.CurrentTranslator), + context.EmitLdfld(typeof(CpuThreadState).GetField(nameof(CpuThreadState.CurrentTranslator), BindingFlags.Instance | BindingFlags.NonPublic)); @@ -132,7 +133,7 @@ namespace ChocolArm64.Instructions context.Emit(OpCodes.Pop); - context.EmitLoadState(); + context.EmitLoadContext(); } else { diff --git a/ChocolArm64/Instructions/InstEmitMemory32.cs b/ChocolArm64/Instructions/InstEmitMemory32.cs index 1e1419e6..647d5755 100644 --- a/ChocolArm64/Instructions/InstEmitMemory32.cs +++ b/ChocolArm64/Instructions/InstEmitMemory32.cs @@ -1,4 +1,5 @@ using ChocolArm64.Decoders; +using ChocolArm64.IntermediateRepresentation; using ChocolArm64.State; using ChocolArm64.Translation; using System; diff --git a/ChocolArm64/Instructions/InstEmitMemoryEx.cs b/ChocolArm64/Instructions/InstEmitMemoryEx.cs index 920c695f..329fba7e 100644 --- a/ChocolArm64/Instructions/InstEmitMemoryEx.cs +++ b/ChocolArm64/Instructions/InstEmitMemoryEx.cs @@ -1,4 +1,5 @@ using ChocolArm64.Decoders; +using ChocolArm64.IntermediateRepresentation; using ChocolArm64.Memory; using ChocolArm64.State; using ChocolArm64.Translation; diff --git a/ChocolArm64/Instructions/InstEmitMemoryHelper.cs b/ChocolArm64/Instructions/InstEmitMemoryHelper.cs index c225cdd8..4dc40b1a 100644 --- a/ChocolArm64/Instructions/InstEmitMemoryHelper.cs +++ b/ChocolArm64/Instructions/InstEmitMemoryHelper.cs @@ -1,4 +1,5 @@ using ChocolArm64.Decoders; +using ChocolArm64.IntermediateRepresentation; using ChocolArm64.Memory; using ChocolArm64.State; using ChocolArm64.Translation; diff --git a/ChocolArm64/Instructions/InstEmitSimdArithmetic.cs b/ChocolArm64/Instructions/InstEmitSimdArithmetic.cs index 357d88b5..0e610bbb 100644 --- a/ChocolArm64/Instructions/InstEmitSimdArithmetic.cs +++ b/ChocolArm64/Instructions/InstEmitSimdArithmetic.cs @@ -2,6 +2,7 @@ // https://www.agner.org/optimize/#vectorclass @ vectori128.h using ChocolArm64.Decoders; +using ChocolArm64.IntermediateRepresentation; using ChocolArm64.State; using ChocolArm64.Translation; using System; diff --git a/ChocolArm64/Instructions/InstEmitSimdCmp.cs b/ChocolArm64/Instructions/InstEmitSimdCmp.cs index d54edb7e..b2925006 100644 --- a/ChocolArm64/Instructions/InstEmitSimdCmp.cs +++ b/ChocolArm64/Instructions/InstEmitSimdCmp.cs @@ -1,4 +1,5 @@ using ChocolArm64.Decoders; +using ChocolArm64.IntermediateRepresentation; using ChocolArm64.State; using ChocolArm64.Translation; using System; diff --git a/ChocolArm64/Instructions/InstEmitSimdMove.cs b/ChocolArm64/Instructions/InstEmitSimdMove.cs index 131ddec6..841dcfe7 100644 --- a/ChocolArm64/Instructions/InstEmitSimdMove.cs +++ b/ChocolArm64/Instructions/InstEmitSimdMove.cs @@ -1,4 +1,5 @@ using ChocolArm64.Decoders; +using ChocolArm64.IntermediateRepresentation; using ChocolArm64.State; using ChocolArm64.Translation; using System; diff --git a/ChocolArm64/IntermediateRepresentation/BasicBlock.cs b/ChocolArm64/IntermediateRepresentation/BasicBlock.cs new file mode 100644 index 00000000..ce39fddb --- /dev/null +++ b/ChocolArm64/IntermediateRepresentation/BasicBlock.cs @@ -0,0 +1,122 @@ +using ChocolArm64.State; +using System; +using System.Collections.Generic; + +using static ChocolArm64.State.RegisterConsts; + +namespace ChocolArm64.IntermediateRepresentation +{ + class BasicBlock + { + public int Index { get; set; } + + public RegisterMask RegInputs { get; private set; } + public RegisterMask RegOutputs { get; private set; } + + public bool HasStateLoad { get; private set; } + + private List _operations; + + public int Count => _operations.Count; + + private BasicBlock _next; + private BasicBlock _branch; + + public BasicBlock Next + { + get => _next; + set => _next = AddSuccessor(_next, value); + } + + public BasicBlock Branch + { + get => _branch; + set => _branch = AddSuccessor(_branch, value); + } + + public List Predecessors { get; } + + public BasicBlock(int index = 0) + { + Index = index; + + _operations = new List(); + + Predecessors = new List(); + } + + private BasicBlock AddSuccessor(BasicBlock oldBlock, BasicBlock newBlock) + { + oldBlock?.Predecessors.Remove(this); + newBlock?.Predecessors.Add(this); + + return newBlock; + } + + public void Add(Operation operation) + { + if (operation.Type == OperationType.LoadLocal || + operation.Type == OperationType.StoreLocal) + { + int index = operation.GetArg(0); + + if (IsRegIndex(index)) + { + long intMask = 0; + long vecMask = 0; + + switch (operation.GetArg(1)) + { + case RegisterType.Flag: intMask = (1L << RegsCount) << index; break; + case RegisterType.Int: intMask = 1L << index; break; + case RegisterType.Vector: vecMask = 1L << index; break; + } + + RegisterMask mask = new RegisterMask(intMask, vecMask); + + if (operation.Type == OperationType.LoadLocal) + { + RegInputs |= mask & ~RegOutputs; + } + else + { + RegOutputs |= mask; + } + } + } + else if (operation.Type == OperationType.LoadContext) + { + HasStateLoad = true; + } + + operation.Parent = this; + + _operations.Add(operation); + } + + public static bool IsRegIndex(int index) + { + return (uint)index < RegsCount; + } + + public Operation GetOperation(int index) + { + if ((uint)index >= _operations.Count) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + return _operations[index]; + } + + public Operation GetLastOp() + { + if (Count == 0) + { + return null; + } + + return _operations[Count - 1]; + } + } +} \ No newline at end of file diff --git a/ChocolArm64/IntermediateRepresentation/ILLabel.cs b/ChocolArm64/IntermediateRepresentation/ILLabel.cs new file mode 100644 index 00000000..70c576cd --- /dev/null +++ b/ChocolArm64/IntermediateRepresentation/ILLabel.cs @@ -0,0 +1,4 @@ +namespace ChocolArm64.IntermediateRepresentation +{ + class ILLabel { } +} \ No newline at end of file diff --git a/ChocolArm64/IntermediateRepresentation/Operation.cs b/ChocolArm64/IntermediateRepresentation/Operation.cs new file mode 100644 index 00000000..dcd01bcd --- /dev/null +++ b/ChocolArm64/IntermediateRepresentation/Operation.cs @@ -0,0 +1,112 @@ +using ChocolArm64.State; +using System; +using System.Reflection; +using System.Reflection.Emit; + +namespace ChocolArm64.IntermediateRepresentation +{ + class Operation + { + public BasicBlock Parent { get; set; } + + public OperationType Type { get; } + + private object[] _arguments { get; } + + private Operation(OperationType type, params object[] arguments) + { + Type = type; + _arguments = arguments; + } + + public T GetArg(int index) + { + return (T)GetArg(index); + } + + public object GetArg(int index) + { + if ((uint)index >= _arguments.Length) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + return _arguments[index]; + } + + public static Operation Call(MethodInfo info) + { + return new Operation(OperationType.Call, info); + } + + public static Operation CallVirtual(MethodInfo info) + { + return new Operation(OperationType.CallVirtual, info); + } + + public static Operation IL(OpCode ilOp) + { + return new Operation(OperationType.IL, ilOp); + } + + public static Operation ILBranch(OpCode ilOp, ILLabel target) + { + return new Operation(OperationType.ILBranch, ilOp, target); + } + + public static Operation LoadArgument(int index) + { + return new Operation(OperationType.LoadArgument, index); + } + + public static Operation LoadConstant(int value) + { + return new Operation(OperationType.LoadConstant, value); + } + + public static Operation LoadConstant(long value) + { + return new Operation(OperationType.LoadConstant, value); + } + + public static Operation LoadConstant(float value) + { + return new Operation(OperationType.LoadConstant, value); + } + + public static Operation LoadConstant(double value) + { + return new Operation(OperationType.LoadConstant, value); + } + + public static Operation LoadContext() + { + return new Operation(OperationType.LoadContext); + } + + public static Operation LoadField(FieldInfo info) + { + return new Operation(OperationType.LoadField, info); + } + + public static Operation LoadLocal(int index, RegisterType type, RegisterSize size) + { + return new Operation(OperationType.LoadLocal, index, type, size); + } + + public static Operation MarkLabel(ILLabel label) + { + return new Operation(OperationType.MarkLabel, label); + } + + public static Operation StoreContext() + { + return new Operation(OperationType.StoreContext); + } + + public static Operation StoreLocal(int index, RegisterType type, RegisterSize size) + { + return new Operation(OperationType.StoreLocal, index, type, size); + } + } +} \ No newline at end of file diff --git a/ChocolArm64/IntermediateRepresentation/OperationType.cs b/ChocolArm64/IntermediateRepresentation/OperationType.cs new file mode 100644 index 00000000..644f1716 --- /dev/null +++ b/ChocolArm64/IntermediateRepresentation/OperationType.cs @@ -0,0 +1,18 @@ +namespace ChocolArm64.IntermediateRepresentation +{ + enum OperationType + { + Call, + CallVirtual, + IL, + ILBranch, + LoadArgument, + LoadConstant, + LoadContext, + LoadField, + LoadLocal, + MarkLabel, + StoreContext, + StoreLocal + } +} \ No newline at end of file diff --git a/ChocolArm64/IntermediateRepresentation/RegisterMask.cs b/ChocolArm64/IntermediateRepresentation/RegisterMask.cs new file mode 100644 index 00000000..aea6ab36 --- /dev/null +++ b/ChocolArm64/IntermediateRepresentation/RegisterMask.cs @@ -0,0 +1,56 @@ +using System; + +namespace ChocolArm64.IntermediateRepresentation +{ + struct RegisterMask : IEquatable + { + public long IntMask { get; set; } + public long VecMask { get; set; } + + public RegisterMask(long intMask, long vecMask) + { + IntMask = intMask; + VecMask = vecMask; + } + + public static RegisterMask operator &(RegisterMask x, RegisterMask y) + { + return new RegisterMask(x.IntMask & y.IntMask, x.VecMask & y.VecMask); + } + + public static RegisterMask operator |(RegisterMask x, RegisterMask y) + { + return new RegisterMask(x.IntMask | y.IntMask, x.VecMask | y.VecMask); + } + + public static RegisterMask operator ~(RegisterMask x) + { + return new RegisterMask(~x.IntMask, ~x.VecMask); + } + + public static bool operator ==(RegisterMask x, RegisterMask y) + { + return x.Equals(y); + } + + public static bool operator !=(RegisterMask x, RegisterMask y) + { + return !x.Equals(y); + } + + public override bool Equals(object obj) + { + return obj is RegisterMask regMask && Equals(regMask); + } + + public bool Equals(RegisterMask other) + { + return IntMask == other.IntMask && VecMask == other.VecMask; + } + + public override int GetHashCode() + { + return HashCode.Combine(IntMask, VecMask); + } + } +} diff --git a/ChocolArm64/State/CpuThreadState.cs b/ChocolArm64/State/CpuThreadState.cs index caf73deb..1cbbcf40 100644 --- a/ChocolArm64/State/CpuThreadState.cs +++ b/ChocolArm64/State/CpuThreadState.cs @@ -9,11 +9,11 @@ namespace ChocolArm64.State { public class CpuThreadState { + private const int MinCountForCheck = 40000; + internal const int ErgSizeLog2 = 4; internal const int DczSizeLog2 = 4; - private const int MinInstForCheck = 4000000; - public ulong X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21, X22, X23, @@ -124,13 +124,13 @@ namespace ChocolArm64.State } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal bool Synchronize(int bbWeight) + internal bool Synchronize() { //Firing a interrupt frequently is expensive, so we only //do it after a given number of instructions has executed. - _syncCount += bbWeight; + _syncCount++; - if (_syncCount >= MinInstForCheck) + if (_syncCount >= MinCountForCheck) { CheckInterrupt(); } @@ -138,11 +138,6 @@ namespace ChocolArm64.State return Running; } - internal void RequestInterrupt() - { - _interrupted = true; - } - [MethodImpl(MethodImplOptions.NoInlining)] private void CheckInterrupt() { @@ -156,6 +151,11 @@ namespace ChocolArm64.State } } + internal void RequestInterrupt() + { + _interrupted = true; + } + internal void OnBreak(long position, int imm) { Break?.Invoke(this, new InstExceptionEventArgs(position, imm)); diff --git a/ChocolArm64/State/Register.cs b/ChocolArm64/State/Register.cs index 12c3f5c3..a7a2ead0 100644 --- a/ChocolArm64/State/Register.cs +++ b/ChocolArm64/State/Register.cs @@ -3,7 +3,7 @@ using System.Reflection; namespace ChocolArm64.State { - struct Register + struct Register : IEquatable { public int Index; @@ -22,9 +22,12 @@ namespace ChocolArm64.State public override bool Equals(object obj) { - return obj is Register reg && - reg.Index == Index && - reg.Type == Type; + return obj is Register reg && Equals(reg); + } + + public bool Equals(Register other) + { + return Index == other.Index && Type == other.Type; } public FieldInfo GetField() diff --git a/ChocolArm64/State/RegisterConsts.cs b/ChocolArm64/State/RegisterConsts.cs new file mode 100644 index 00000000..8c34789c --- /dev/null +++ b/ChocolArm64/State/RegisterConsts.cs @@ -0,0 +1,8 @@ +namespace ChocolArm64.State +{ + static class RegisterConsts + { + public const int RegsCount = 32; + public const int RegsMask = RegsCount - 1; + } +} \ No newline at end of file diff --git a/ChocolArm64/Translation/IILEmit.cs b/ChocolArm64/Translation/IILEmit.cs deleted file mode 100644 index 320520eb..00000000 --- a/ChocolArm64/Translation/IILEmit.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace ChocolArm64.Translation -{ - interface IILEmit - { - void Emit(ILMethodBuilder context); - } -} \ No newline at end of file diff --git a/ChocolArm64/Translation/ILBarrier.cs b/ChocolArm64/Translation/ILBarrier.cs deleted file mode 100644 index 37f7558a..00000000 --- a/ChocolArm64/Translation/ILBarrier.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace ChocolArm64.Translation -{ - struct ILBarrier : IILEmit - { - public void Emit(ILMethodBuilder context) { } - } -} \ No newline at end of file diff --git a/ChocolArm64/Translation/ILBlock.cs b/ChocolArm64/Translation/ILBlock.cs deleted file mode 100644 index 12773705..00000000 --- a/ChocolArm64/Translation/ILBlock.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System.Collections.Generic; - -namespace ChocolArm64.Translation -{ - class ILBlock : IILEmit - { - public long IntInputs { get; private set; } - public long IntOutputs { get; private set; } - private long _intAwOutputs; - - public long VecInputs { get; private set; } - public long VecOutputs { get; private set; } - private long _vecAwOutputs; - - public bool HasStateStore { get; private set; } - - private List _emitters; - - public int Count => _emitters.Count; - - public ILBlock Next { get; set; } - public ILBlock Branch { get; set; } - - public ILBlock() - { - _emitters = new List(); - } - - public void Add(IILEmit emitter) - { - if (emitter is ILBarrier) - { - //Those barriers are used to separate the groups of CIL - //opcodes emitted by each ARM instruction. - //We can only consider the new outputs for doing input elimination - //after all the CIL opcodes used by the instruction being emitted. - _intAwOutputs = IntOutputs; - _vecAwOutputs = VecOutputs; - } - else if (emitter is ILOpCodeLoad ld && ILMethodBuilder.IsRegIndex(ld.Index)) - { - switch (ld.VarType) - { - case VarType.Flag: IntInputs |= ((1L << ld.Index) << 32) & ~_intAwOutputs; break; - case VarType.Int: IntInputs |= (1L << ld.Index) & ~_intAwOutputs; break; - case VarType.Vector: VecInputs |= (1L << ld.Index) & ~_vecAwOutputs; break; - } - } - else if (emitter is ILOpCodeStore st && ILMethodBuilder.IsRegIndex(st.Index)) - { - switch (st.VarType) - { - case VarType.Flag: IntOutputs |= (1L << st.Index) << 32; break; - case VarType.Int: IntOutputs |= 1L << st.Index; break; - case VarType.Vector: VecOutputs |= 1L << st.Index; break; - } - } - else if (emitter is ILOpCodeStoreState) - { - HasStateStore = true; - } - - _emitters.Add(emitter); - } - - public void Emit(ILMethodBuilder context) - { - foreach (IILEmit ilEmitter in _emitters) - { - ilEmitter.Emit(context); - } - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Translation/ILEmitterCtx.cs b/ChocolArm64/Translation/ILEmitterCtx.cs index 8804521c..b4360fda 100644 --- a/ChocolArm64/Translation/ILEmitterCtx.cs +++ b/ChocolArm64/Translation/ILEmitterCtx.cs @@ -1,5 +1,6 @@ using ChocolArm64.Decoders; using ChocolArm64.Instructions; +using ChocolArm64.IntermediateRepresentation; using ChocolArm64.Memory; using ChocolArm64.State; using System; @@ -16,16 +17,23 @@ namespace ChocolArm64.Translation private TranslatorCache _cache; private TranslatorQueue _queue; - private Dictionary _labels; - - private long _subPosition; - - private int _opcIndex; - private Block _currBlock; - public Block CurrBlock => _currBlock; - public OpCode64 CurrOp => _currBlock?.OpCodes[_opcIndex]; + public Block CurrBlock + { + get + { + return _currBlock; + } + set + { + _currBlock = value; + + ResetBlockState(); + } + } + + public OpCode64 CurrOp { get; set; } public TranslationTier Tier { get; } @@ -35,13 +43,15 @@ namespace ChocolArm64.Translation public bool HasSlowCall { get; set; } - private Dictionary _visitedBlocks; + private Dictionary _labels; - private Queue _branchTargets; + private Dictionary _irLabels; - private List _ilBlocks; + private List _irBlocks; - private ILBlock _ilBlock; + private BasicBlock _irBlock; + + private bool _needsNewBlock; private OpCode64 _optOpLastCompare; private OpCode64 _optOpLastFlagSet; @@ -72,36 +82,25 @@ namespace ChocolArm64.Translation MemoryManager memory, TranslatorCache cache, TranslatorQueue queue, - TranslationTier tier, - Block graph) + TranslationTier tier) { - Memory = memory ?? throw new ArgumentNullException(nameof(memory)); - _cache = cache ?? throw new ArgumentNullException(nameof(cache)); - _queue = queue ?? throw new ArgumentNullException(nameof(queue)); - _currBlock = graph ?? throw new ArgumentNullException(nameof(graph)); + Memory = memory ?? throw new ArgumentNullException(nameof(memory)); + _cache = cache ?? throw new ArgumentNullException(nameof(cache)); + _queue = queue ?? throw new ArgumentNullException(nameof(queue)); Tier = tier; _labels = new Dictionary(); - _visitedBlocks = new Dictionary(); + _irLabels = new Dictionary(); - _visitedBlocks.Add(graph, new ILBlock()); + _irBlocks = new List(); - _branchTargets = new Queue(); + NewNextBlock(); - _ilBlocks = new List(); + EmitSynchronization(); - _subPosition = graph.Position; - - ResetBlockState(); - - if (AdvanceOpCode()) - { - EmitSynchronization(); - - _ilBlock.Add(new ILOpCodeLoadState(_ilBlock, isSubEntry: true)); - } + EmitLoadContext(); } public static int GetIntTempIndex() @@ -114,96 +113,15 @@ namespace ChocolArm64.Translation return UserVecTempStart + _userVecTempCount++; } - public ILBlock[] GetILBlocks() + public BasicBlock[] GetBlocks() { - EmitAllOpCodes(); - - return _ilBlocks.ToArray(); + return _irBlocks.ToArray(); } - private void EmitAllOpCodes() - { - do - { - EmitOpCode(); - } - while (AdvanceOpCode()); - } - - private void EmitOpCode() - { - if (_currBlock == null) - { - return; - } - - int opcIndex = _opcIndex; - - if (opcIndex == 0) - { - MarkLabel(GetLabel(_currBlock.Position)); - } - - bool isLastOp = opcIndex == CurrBlock.OpCodes.Count - 1; - - if (isLastOp && CurrBlock.Branch != null && - (ulong)CurrBlock.Branch.Position <= (ulong)CurrBlock.Position) - { - EmitSynchronization(); - } - - //On AARCH32 mode, (almost) all instruction can be conditionally - //executed, and the required condition is encoded on the opcode. - //We handle that here, skipping the instruction if the condition - //is not met. We can just ignore it when the condition is "Always", - //because in this case the instruction is always going to be executed. - //Condition "Never" is also ignored because this is a special encoding - //used by some unconditional instructions. - ILLabel lblSkip = null; - - if (CurrOp is OpCode32 op && op.Cond < Condition.Al) - { - lblSkip = new ILLabel(); - - EmitCondBranch(lblSkip, GetInverseCond(op.Cond)); - } - - CurrOp.Emitter(this); - - if (lblSkip != null) - { - MarkLabel(lblSkip); - - //If this is the last op on the block, and there's no "next" block - //after this one, then we have to return right now, with the address - //of the next instruction to be executed (in the case that the condition - //is false, and the branch was not taken, as all basic blocks should end with - //some kind of branch). - if (isLastOp && CurrBlock.Next == null) - { - EmitStoreState(); - EmitLdc_I8(CurrOp.Position + CurrOp.OpCodeSizeInBytes); - - Emit(OpCodes.Ret); - } - } - - _ilBlock.Add(new ILBarrier()); - } - - private static Condition GetInverseCond(Condition cond) - { - //Bit 0 of all conditions is basically a negation bit, so - //inverting this bit has the effect of inverting the condition. - return (Condition)((int)cond ^ 1); - } - - private void EmitSynchronization() + public void EmitSynchronization() { EmitLdarg(TranslatedSub.StateArgIdx); - EmitLdc_I4(_currBlock.OpCodes.Count); - EmitPrivateCall(typeof(CpuThreadState), nameof(CpuThreadState.Synchronize)); EmitLdc_I4(0); @@ -219,83 +137,24 @@ namespace ChocolArm64.Translation MarkLabel(lblContinue); } - private bool AdvanceOpCode() + public void ResetBlockStateForPredicatedOp() { - if (_currBlock == null) + //Check if this is a predicated instruction that modifies flags, + //in this case the value of the flags is unknown as we don't know + //in advance if the instruction is going to be executed or not. + //So, we reset the block state to prevent an invalid optimization. + if (CurrOp == _optOpLastFlagSet) { - return false; - } - - while (++_opcIndex >= _currBlock.OpCodes.Count) - { - if (!AdvanceBlock()) - { - return false; - } - ResetBlockState(); } - - return true; - } - - private bool AdvanceBlock() - { - if (_currBlock.Branch != null) - { - if (_visitedBlocks.TryAdd(_currBlock.Branch, _ilBlock.Branch)) - { - _branchTargets.Enqueue(_currBlock.Branch); - } - } - - if (_currBlock.Next != null) - { - if (_visitedBlocks.TryAdd(_currBlock.Next, _ilBlock.Next)) - { - _currBlock = _currBlock.Next; - - return true; - } - else - { - Emit(OpCodes.Br, GetLabel(_currBlock.Next.Position)); - } - } - - return _branchTargets.TryDequeue(out _currBlock); } private void ResetBlockState() { - _ilBlock = _visitedBlocks[_currBlock]; - - _ilBlocks.Add(_ilBlock); - - _ilBlock.Next = GetOrCreateILBlock(_currBlock.Next); - _ilBlock.Branch = GetOrCreateILBlock(_currBlock.Branch); - - _opcIndex = -1; - _optOpLastFlagSet = null; _optOpLastCompare = null; } - private ILBlock GetOrCreateILBlock(Block block) - { - if (block == null) - { - return null; - } - - if (_visitedBlocks.TryGetValue(block, out ILBlock ilBlock)) - { - return ilBlock; - } - - return new ILBlock(); - } - public void TranslateAhead(long position, ExecutionMode mode = ExecutionMode.Aarch64) { if (_cache.TryGetSubroutine(position, out TranslatedSub sub) && sub.Tier != TranslationTier.Tier0) @@ -320,19 +179,12 @@ namespace ChocolArm64.Translation return false; } - if (!_cache.TryGetSubroutine(op.Imm, out TranslatedSub sub)) + if (!_cache.TryGetSubroutine(op.Imm, out TranslatedSub sub) || sub.Tier != TranslationTier.Tier0) { return false; } - //It's not worth to call a Tier0 method, because - //it contains slow code, rather than the entire function. - if (sub.Tier == TranslationTier.Tier0) - { - return false; - } - - EmitStoreState(sub); + EmitStoreContext(); for (int index = 0; index < TranslatedSub.FixedArgTypes.Length; index++) { @@ -350,8 +202,8 @@ namespace ChocolArm64.Translation InstEmitAluHelper.EmitAluLoadOpers(this); - Stloc(CmpOptTmp2Index, VarType.Int); - Stloc(CmpOptTmp1Index, VarType.Int); + Stloc(CmpOptTmp2Index, RegisterType.Int); + Stloc(CmpOptTmp1Index, RegisterType.Int); } private Dictionary _branchOps = new Dictionary() @@ -375,8 +227,8 @@ namespace ChocolArm64.Translation { if (_optOpLastCompare.Emitter == InstEmit.Subs) { - Ldloc(CmpOptTmp1Index, VarType.Int, _optOpLastCompare.RegisterSize); - Ldloc(CmpOptTmp2Index, VarType.Int, _optOpLastCompare.RegisterSize); + Ldloc(CmpOptTmp1Index, RegisterType.Int, _optOpLastCompare.RegisterSize); + Ldloc(CmpOptTmp2Index, RegisterType.Int, _optOpLastCompare.RegisterSize); Emit(_branchOps[cond], target); @@ -388,17 +240,17 @@ namespace ChocolArm64.Translation && cond != Condition.LeUn) { //There are several limitations that needs to be taken into account for CMN comparisons: - //* The unsigned comparisons are not valid, as they depend on the + //- The unsigned comparisons are not valid, as they depend on the //carry flag value, and they will have different values for addition and //subtraction. For addition, it's carry, and for subtraction, it's borrow. //So, we need to make sure we're not doing a unsigned compare for the CMN case. - //* We can only do the optimization for the immediate variants, + //- We can only do the optimization for the immediate variants, //because when the second operand value is exactly INT_MIN, we can't //negate the value as theres no positive counterpart. //Such invalid values can't be encoded on the immediate encodings. if (_optOpLastCompare is IOpCodeAluImm64 op) { - Ldloc(CmpOptTmp1Index, VarType.Int, _optOpLastCompare.RegisterSize); + Ldloc(CmpOptTmp1Index, RegisterType.Int, _optOpLastCompare.RegisterSize); if (_optOpLastCompare.RegisterSize == RegisterSize.Int32) { @@ -456,9 +308,7 @@ namespace ChocolArm64.Translation break; } - ilOp = (intCond & 1) != 0 - ? OpCodes.Brfalse - : OpCodes.Brtrue; + ilOp = (intCond & 1) != 0 ? OpCodes.Brfalse : OpCodes.Brtrue; } else { @@ -484,17 +334,14 @@ namespace ChocolArm64.Translation bool sz64 = CurrOp.RegisterSize != RegisterSize.Int32; - if (sz64 == (intType == IntType.UInt64 || - intType == IntType.Int64)) + if (sz64 == (intType == IntType.UInt64 || intType == IntType.Int64)) { return; } if (sz64) { - Emit(intType >= IntType.Int8 - ? OpCodes.Conv_I8 - : OpCodes.Conv_U8); + Emit(intType >= IntType.Int8 ? OpCodes.Conv_I8 : OpCodes.Conv_U8); } else { @@ -520,14 +367,14 @@ namespace ChocolArm64.Translation { if (amount > 0) { - Stloc(RorTmpIndex, VarType.Int); - Ldloc(RorTmpIndex, VarType.Int); + Stloc(RorTmpIndex, RegisterType.Int); + Ldloc(RorTmpIndex, RegisterType.Int); EmitLdc_I4(amount); Emit(OpCodes.Shr_Un); - Ldloc(RorTmpIndex, VarType.Int); + Ldloc(RorTmpIndex, RegisterType.Int); EmitLdc_I4(CurrOp.GetBitsCount() - amount); @@ -550,32 +397,60 @@ namespace ChocolArm64.Translation public void MarkLabel(ILLabel label) { - _ilBlock.Add(label); + if (_irLabels.TryGetValue(label, out BasicBlock nextBlock)) + { + nextBlock.Index = _irBlocks.Count; + + _irBlocks.Add(nextBlock); + + NextBlock(nextBlock); + } + else + { + NewNextBlock(); + + _irLabels.Add(label, _irBlock); + } + + AddOperation(Operation.MarkLabel(label)); } public void Emit(OpCode ilOp) { - _ilBlock.Add(new ILOpCode(ilOp)); + AddOperation(Operation.IL(ilOp)); + + if (ilOp == OpCodes.Ret) + { + NextBlock(null); + + _needsNewBlock = true; + } } public void Emit(OpCode ilOp, ILLabel label) { - _ilBlock.Add(new ILOpCodeBranch(ilOp, label)); + AddOperation(Operation.ILBranch(ilOp, label)); + + _needsNewBlock = true; + + if (!_irLabels.TryGetValue(label, out BasicBlock branchBlock)) + { + branchBlock = new BasicBlock(); + + _irLabels.Add(label, branchBlock); + } + + _irBlock.Branch = branchBlock; } - public void EmitFieldLoad(FieldInfo info) + public void EmitLdfld(FieldInfo info) { - _ilBlock.Add(new ILOpCodeLoadField(info)); - } - - public void EmitPrint(string text) - { - _ilBlock.Add(new ILOpCodeLog(text)); + AddOperation(Operation.LoadField(info)); } public void EmitLdarg(int index) { - _ilBlock.Add(new ILOpCodeLoad(index, VarType.Arg)); + AddOperation(Operation.LoadArgument(index)); } public void EmitLdintzr(int index) @@ -602,24 +477,16 @@ namespace ChocolArm64.Translation } } - public void EmitLoadState() + public void EmitLoadContext() { - if (_ilBlock.Next == null) - { - throw new InvalidOperationException("Can't load state for next block, because there's no next block."); - } + _needsNewBlock = true; - _ilBlock.Add(new ILOpCodeLoadState(_ilBlock.Next)); + AddOperation(Operation.LoadContext()); } - public void EmitStoreState() + public void EmitStoreContext() { - _ilBlock.Add(new ILOpCodeStoreState(_ilBlock)); - } - - private void EmitStoreState(TranslatedSub callSub) - { - _ilBlock.Add(new ILOpCodeStoreState(_ilBlock, callSub)); + AddOperation(Operation.StoreContext()); } public void EmitLdtmp() => EmitLdint(IntGpTmp1Index); @@ -637,17 +504,17 @@ namespace ChocolArm64.Translation public void EmitLdvectmp3() => EmitLdvec(VecGpTmp3Index); public void EmitStvectmp3() => EmitStvec(VecGpTmp3Index); - public void EmitLdint(int index) => Ldloc(index, VarType.Int); - public void EmitStint(int index) => Stloc(index, VarType.Int); + public void EmitLdint(int index) => Ldloc(index, RegisterType.Int); + public void EmitStint(int index) => Stloc(index, RegisterType.Int); - public void EmitLdvec(int index) => Ldloc(index, VarType.Vector); - public void EmitStvec(int index) => Stloc(index, VarType.Vector); + public void EmitLdvec(int index) => Ldloc(index, RegisterType.Vector); + public void EmitStvec(int index) => Stloc(index, RegisterType.Vector); - public void EmitLdflg(int index) => Ldloc(index, VarType.Flag); + public void EmitLdflg(int index) => Ldloc(index, RegisterType.Flag); public void EmitStflg(int index) { //Set this only if any of the NZCV flag bits were modified. - //This is used to ensure that, when emiting a direct IL branch + //This is used to ensure that when emiting a direct IL branch //instruction for compare + branch sequences, we're not expecting //to use comparison values from an old instruction, when in fact //the flags were already overwritten by another instruction further along. @@ -656,22 +523,22 @@ namespace ChocolArm64.Translation _optOpLastFlagSet = CurrOp; } - Stloc(index, VarType.Flag); + Stloc(index, RegisterType.Flag); } - private void Ldloc(int index, VarType varType) + private void Ldloc(int index, RegisterType type) { - _ilBlock.Add(new ILOpCodeLoad(index, varType, CurrOp.RegisterSize)); + AddOperation(Operation.LoadLocal(index, type, CurrOp.RegisterSize)); } - private void Ldloc(int index, VarType varType, RegisterSize registerSize) + private void Ldloc(int index, RegisterType type, RegisterSize size) { - _ilBlock.Add(new ILOpCodeLoad(index, varType, registerSize)); + AddOperation(Operation.LoadLocal(index, type, size)); } - private void Stloc(int index, VarType varType) + private void Stloc(int index, RegisterType type) { - _ilBlock.Add(new ILOpCodeStore(index, varType, CurrOp.RegisterSize)); + AddOperation(Operation.StoreLocal(index, type, CurrOp.RegisterSize)); } public void EmitCallPropGet(Type objType, string propName) @@ -726,7 +593,19 @@ namespace ChocolArm64.Translation public void EmitCall(MethodInfo mthdInfo, bool isVirtual = false) { - _ilBlock.Add(new ILOpCodeCall(mthdInfo ?? throw new ArgumentNullException(nameof(mthdInfo)), isVirtual)); + if (mthdInfo == null) + { + throw new ArgumentNullException(nameof(mthdInfo)); + } + + if (isVirtual) + { + AddOperation(Operation.CallVirtual(mthdInfo)); + } + else + { + AddOperation(Operation.Call(mthdInfo)); + } } public void EmitLdc_I(long value) @@ -743,22 +622,22 @@ namespace ChocolArm64.Translation public void EmitLdc_I4(int value) { - _ilBlock.Add(new ILOpCodeConst(value)); + AddOperation(Operation.LoadConstant(value)); } public void EmitLdc_I8(long value) { - _ilBlock.Add(new ILOpCodeConst(value)); + AddOperation(Operation.LoadConstant(value)); } public void EmitLdc_R4(float value) { - _ilBlock.Add(new ILOpCodeConst(value)); + AddOperation(Operation.LoadConstant(value)); } public void EmitLdc_R8(double value) { - _ilBlock.Add(new ILOpCodeConst(value)); + AddOperation(Operation.LoadConstant(value)); } public void EmitZnFlagCheck() @@ -781,5 +660,50 @@ namespace ChocolArm64.Translation EmitStflg(flag); } + + private void AddOperation(Operation operation) + { + if (_needsNewBlock) + { + NewNextBlock(); + } + + _irBlock.Add(operation); + } + + private void NewNextBlock() + { + BasicBlock block = new BasicBlock(_irBlocks.Count); + + _irBlocks.Add(block); + + NextBlock(block); + } + + private void NextBlock(BasicBlock nextBlock) + { + if (_irBlock != null && !EndsWithUnconditional(_irBlock)) + { + _irBlock.Next = nextBlock; + } + + _irBlock = nextBlock; + + _needsNewBlock = false; + } + + private static bool EndsWithUnconditional(BasicBlock block) + { + Operation lastOp = block.GetLastOp(); + + if (lastOp == null || lastOp.Type != OperationType.ILBranch) + { + return false; + } + + OpCode opCode = lastOp.GetArg(0); + + return opCode == OpCodes.Br || opCode == OpCodes.Br_S; + } } } diff --git a/ChocolArm64/Translation/ILGeneratorEx.cs b/ChocolArm64/Translation/ILGeneratorEx.cs index 318098cc..6b1512d2 100644 --- a/ChocolArm64/Translation/ILGeneratorEx.cs +++ b/ChocolArm64/Translation/ILGeneratorEx.cs @@ -117,13 +117,5 @@ namespace ChocolArm64 break; } } - - public static void EmitLdargSeq(this ILGenerator generator, int count) - { - for (int index = 0; index < count; index++) - { - generator.EmitLdarg(index); - } - } } } diff --git a/ChocolArm64/Translation/ILLabel.cs b/ChocolArm64/Translation/ILLabel.cs deleted file mode 100644 index 17a31783..00000000 --- a/ChocolArm64/Translation/ILLabel.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Reflection.Emit; - -namespace ChocolArm64.Translation -{ - class ILLabel : IILEmit - { - private bool _hasLabel; - - private Label _label; - - public void Emit(ILMethodBuilder context) - { - context.Generator.MarkLabel(GetLabel(context)); - } - - public Label GetLabel(ILMethodBuilder context) - { - if (!_hasLabel) - { - _label = context.Generator.DefineLabel(); - - _hasLabel = true; - } - - return _label; - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Translation/ILMethodBuilder.cs b/ChocolArm64/Translation/ILMethodBuilder.cs deleted file mode 100644 index 98b50520..00000000 --- a/ChocolArm64/Translation/ILMethodBuilder.cs +++ /dev/null @@ -1,123 +0,0 @@ -using ChocolArm64.State; -using System; -using System.Collections.Generic; -using System.Reflection.Emit; -using System.Runtime.Intrinsics; - -namespace ChocolArm64.Translation -{ - class ILMethodBuilder - { - private const int RegsCount = 32; - private const int RegsMask = RegsCount - 1; - - public RegisterUsage RegUsage { get; private set; } - - public ILGenerator Generator { get; private set; } - - private Dictionary _locals; - - private ILBlock[] _ilBlocks; - - private string _subName; - - public bool IsAarch64 { get; } - - public bool IsSubComplete { get; } - - private int _localsCount; - - public ILMethodBuilder( - ILBlock[] ilBlocks, - string subName, - bool isAarch64, - bool isSubComplete = false) - { - _ilBlocks = ilBlocks; - _subName = subName; - IsAarch64 = isAarch64; - IsSubComplete = isSubComplete; - } - - public TranslatedSub GetSubroutine(TranslationTier tier, bool isWorthOptimizing) - { - RegUsage = new RegisterUsage(); - - RegUsage.BuildUses(_ilBlocks[0]); - - DynamicMethod method = new DynamicMethod(_subName, typeof(long), TranslatedSub.FixedArgTypes); - - long intNiRegsMask = RegUsage.GetIntNotInputs(_ilBlocks[0]); - long vecNiRegsMask = RegUsage.GetVecNotInputs(_ilBlocks[0]); - - TranslatedSub subroutine = new TranslatedSub( - method, - intNiRegsMask, - vecNiRegsMask, - tier, - isWorthOptimizing); - - _locals = new Dictionary(); - - _localsCount = 0; - - Generator = method.GetILGenerator(); - - foreach (ILBlock ilBlock in _ilBlocks) - { - ilBlock.Emit(this); - } - - subroutine.PrepareMethod(); - - return subroutine; - } - - public int GetLocalIndex(Register reg) - { - if (!_locals.TryGetValue(reg, out int index)) - { - Generator.DeclareLocal(GetFieldType(reg.Type)); - - index = _localsCount++; - - _locals.Add(reg, index); - } - - return index; - } - - private static Type GetFieldType(RegisterType regType) - { - switch (regType) - { - case RegisterType.Flag: return typeof(bool); - case RegisterType.Int: return typeof(ulong); - case RegisterType.Vector: return typeof(Vector128); - } - - throw new ArgumentException(nameof(regType)); - } - - public static Register GetRegFromBit(int bit, RegisterType baseType) - { - if (bit < RegsCount) - { - return new Register(bit, baseType); - } - else if (baseType == RegisterType.Int) - { - return new Register(bit & RegsMask, RegisterType.Flag); - } - else - { - throw new ArgumentOutOfRangeException(nameof(bit)); - } - } - - public static bool IsRegIndex(int index) - { - return (uint)index < RegsCount; - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Translation/ILOpCode.cs b/ChocolArm64/Translation/ILOpCode.cs deleted file mode 100644 index 48645282..00000000 --- a/ChocolArm64/Translation/ILOpCode.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Reflection.Emit; - -namespace ChocolArm64.Translation -{ - struct ILOpCode : IILEmit - { - public OpCode ILOp { get; } - - public ILOpCode(OpCode ilOp) - { - ILOp = ilOp; - } - - public void Emit(ILMethodBuilder context) - { - context.Generator.Emit(ILOp); - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Translation/ILOpCodeBranch.cs b/ChocolArm64/Translation/ILOpCodeBranch.cs deleted file mode 100644 index 9d4e40fa..00000000 --- a/ChocolArm64/Translation/ILOpCodeBranch.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Reflection.Emit; - -namespace ChocolArm64.Translation -{ - struct ILOpCodeBranch : IILEmit - { - public OpCode ILOp { get; } - public ILLabel Label { get; } - - public ILOpCodeBranch(OpCode ilOp, ILLabel label) - { - ILOp = ilOp; - Label = label; - } - - public void Emit(ILMethodBuilder context) - { - context.Generator.Emit(ILOp, Label.GetLabel(context)); - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Translation/ILOpCodeCall.cs b/ChocolArm64/Translation/ILOpCodeCall.cs deleted file mode 100644 index dc20417a..00000000 --- a/ChocolArm64/Translation/ILOpCodeCall.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Reflection; -using System.Reflection.Emit; - -namespace ChocolArm64.Translation -{ - struct ILOpCodeCall : IILEmit - { - public MethodInfo Info { get; } - - public bool IsVirtual { get; } - - public ILOpCodeCall(MethodInfo info, bool isVirtual) - { - Info = info; - IsVirtual = isVirtual; - } - - public void Emit(ILMethodBuilder context) - { - context.Generator.Emit(IsVirtual ? OpCodes.Callvirt : OpCodes.Call, Info); - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Translation/ILOpCodeConst.cs b/ChocolArm64/Translation/ILOpCodeConst.cs deleted file mode 100644 index cd3b58ff..00000000 --- a/ChocolArm64/Translation/ILOpCodeConst.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System.Reflection.Emit; -using System.Runtime.InteropServices; - -namespace ChocolArm64.Translation -{ - class ILOpCodeConst : IILEmit - { - [StructLayout(LayoutKind.Explicit, Size = 8)] - private struct ImmVal - { - [FieldOffset(0)] public int I4; - [FieldOffset(0)] public long I8; - [FieldOffset(0)] public float R4; - [FieldOffset(0)] public double R8; - } - - private ImmVal _value; - - public long Value => _value.I8; - - private enum ConstType - { - Int32, - Int64, - Single, - Double - } - - private ConstType _type; - - private ILOpCodeConst(ConstType type) - { - _type = type; - } - - public ILOpCodeConst(int value) : this(ConstType.Int32) - { - _value = new ImmVal { I4 = value }; - } - - public ILOpCodeConst(long value) : this(ConstType.Int64) - { - _value = new ImmVal { I8 = value }; - } - - public ILOpCodeConst(float value) : this(ConstType.Single) - { - _value = new ImmVal { R4 = value }; - } - - public ILOpCodeConst(double value) : this(ConstType.Double) - { - _value = new ImmVal { R8 = value }; - } - - public void Emit(ILMethodBuilder context) - { - switch (_type) - { - case ConstType.Int32: context.Generator.EmitLdc_I4(_value.I4); break; - case ConstType.Int64: context.Generator.Emit(OpCodes.Ldc_I8, _value.I8); break; - case ConstType.Single: context.Generator.Emit(OpCodes.Ldc_R4, _value.R4); break; - case ConstType.Double: context.Generator.Emit(OpCodes.Ldc_R8, _value.R8); break; - } - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Translation/ILOpCodeLoad.cs b/ChocolArm64/Translation/ILOpCodeLoad.cs deleted file mode 100644 index 0d11eeaa..00000000 --- a/ChocolArm64/Translation/ILOpCodeLoad.cs +++ /dev/null @@ -1,46 +0,0 @@ -using ChocolArm64.State; -using System.Reflection.Emit; - -namespace ChocolArm64.Translation -{ - struct ILOpCodeLoad : IILEmit - { - public int Index { get; } - - public VarType VarType { get; } - - public RegisterSize RegisterSize { get; } - - public ILOpCodeLoad(int index, VarType varType, RegisterSize registerSize = 0) - { - Index = index; - VarType = varType; - RegisterSize = registerSize; - } - - public void Emit(ILMethodBuilder context) - { - switch (VarType) - { - case VarType.Arg: context.Generator.EmitLdarg(Index); break; - - case VarType.Flag: EmitLdloc(context, Index, RegisterType.Flag); break; - case VarType.Int: EmitLdloc(context, Index, RegisterType.Int); break; - case VarType.Vector: EmitLdloc(context, Index, RegisterType.Vector); break; - } - } - - private void EmitLdloc(ILMethodBuilder context, int index, RegisterType registerType) - { - Register reg = new Register(index, registerType); - - context.Generator.EmitLdloc(context.GetLocalIndex(reg)); - - if (registerType == RegisterType.Int && - RegisterSize == RegisterSize.Int32) - { - context.Generator.Emit(OpCodes.Conv_U4); - } - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Translation/ILOpCodeLoadField.cs b/ChocolArm64/Translation/ILOpCodeLoadField.cs deleted file mode 100644 index f0507ac2..00000000 --- a/ChocolArm64/Translation/ILOpCodeLoadField.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Reflection; -using System.Reflection.Emit; - -namespace ChocolArm64.Translation -{ - struct ILOpCodeLoadField : IILEmit - { - public FieldInfo Info { get; } - - public ILOpCodeLoadField(FieldInfo info) - { - Info = info; - } - - public void Emit(ILMethodBuilder context) - { - context.Generator.Emit(OpCodes.Ldfld, Info); - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Translation/ILOpCodeLoadState.cs b/ChocolArm64/Translation/ILOpCodeLoadState.cs deleted file mode 100644 index c23dc943..00000000 --- a/ChocolArm64/Translation/ILOpCodeLoadState.cs +++ /dev/null @@ -1,51 +0,0 @@ -using ChocolArm64.State; -using System.Reflection.Emit; - -namespace ChocolArm64.Translation -{ - struct ILOpCodeLoadState : IILEmit - { - private ILBlock _block; - - private bool _isSubEntry; - - public ILOpCodeLoadState(ILBlock block, bool isSubEntry = false) - { - _block = block; - _isSubEntry = isSubEntry; - } - - public void Emit(ILMethodBuilder context) - { - long intInputs = context.RegUsage.GetIntInputs(_block); - long vecInputs = context.RegUsage.GetVecInputs(_block); - - if (Optimizations.AssumeStrictAbiCompliance && context.IsSubComplete) - { - intInputs = RegisterUsage.ClearCallerSavedIntRegs(intInputs, context.IsAarch64); - vecInputs = RegisterUsage.ClearCallerSavedVecRegs(vecInputs, context.IsAarch64); - } - - LoadLocals(context, intInputs, RegisterType.Int); - LoadLocals(context, vecInputs, RegisterType.Vector); - } - - private void LoadLocals(ILMethodBuilder context, long inputs, RegisterType baseType) - { - for (int bit = 0; bit < 64; bit++) - { - long mask = 1L << bit; - - if ((inputs & mask) != 0) - { - Register reg = ILMethodBuilder.GetRegFromBit(bit, baseType); - - context.Generator.EmitLdarg(TranslatedSub.StateArgIdx); - context.Generator.Emit(OpCodes.Ldfld, reg.GetField()); - - context.Generator.EmitStloc(context.GetLocalIndex(reg)); - } - } - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Translation/ILOpCodeLog.cs b/ChocolArm64/Translation/ILOpCodeLog.cs deleted file mode 100644 index 53846f92..00000000 --- a/ChocolArm64/Translation/ILOpCodeLog.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace ChocolArm64.Translation -{ - struct ILOpCodeLog : IILEmit - { - public string Text { get; } - - public ILOpCodeLog(string text) - { - Text = text; - } - - public void Emit(ILMethodBuilder context) - { - context.Generator.EmitWriteLine(Text); - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Translation/ILOpCodeStore.cs b/ChocolArm64/Translation/ILOpCodeStore.cs deleted file mode 100644 index 7ac78e9a..00000000 --- a/ChocolArm64/Translation/ILOpCodeStore.cs +++ /dev/null @@ -1,46 +0,0 @@ -using ChocolArm64.State; -using System.Reflection.Emit; - -namespace ChocolArm64.Translation -{ - struct ILOpCodeStore : IILEmit - { - public int Index { get; } - - public VarType VarType { get; } - - public RegisterSize RegisterSize { get; } - - public ILOpCodeStore(int index, VarType varType, RegisterSize registerSize = 0) - { - Index = index; - VarType = varType; - RegisterSize = registerSize; - } - - public void Emit(ILMethodBuilder context) - { - switch (VarType) - { - case VarType.Arg: context.Generator.EmitStarg(Index); break; - - case VarType.Flag: EmitStloc(context, Index, RegisterType.Flag); break; - case VarType.Int: EmitStloc(context, Index, RegisterType.Int); break; - case VarType.Vector: EmitStloc(context, Index, RegisterType.Vector); break; - } - } - - private void EmitStloc(ILMethodBuilder context, int index, RegisterType registerType) - { - Register reg = new Register(index, registerType); - - if (registerType == RegisterType.Int && - RegisterSize == RegisterSize.Int32) - { - context.Generator.Emit(OpCodes.Conv_U8); - } - - context.Generator.EmitStloc(context.GetLocalIndex(reg)); - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Translation/ILOpCodeStoreState.cs b/ChocolArm64/Translation/ILOpCodeStoreState.cs deleted file mode 100644 index a587dbfe..00000000 --- a/ChocolArm64/Translation/ILOpCodeStoreState.cs +++ /dev/null @@ -1,60 +0,0 @@ -using ChocolArm64.State; -using System.Reflection.Emit; - -namespace ChocolArm64.Translation -{ - struct ILOpCodeStoreState : IILEmit - { - private ILBlock _block; - - private TranslatedSub _callSub; - - public ILOpCodeStoreState(ILBlock block, TranslatedSub callSub = null) - { - _block = block; - _callSub = callSub; - } - - public void Emit(ILMethodBuilder context) - { - long intOutputs = context.RegUsage.GetIntOutputs(_block); - long vecOutputs = context.RegUsage.GetVecOutputs(_block); - - if (Optimizations.AssumeStrictAbiCompliance && context.IsSubComplete) - { - intOutputs = RegisterUsage.ClearCallerSavedIntRegs(intOutputs, context.IsAarch64); - vecOutputs = RegisterUsage.ClearCallerSavedVecRegs(vecOutputs, context.IsAarch64); - } - - if (_callSub != null) - { - //Those register are assigned on the callee function, without - //reading it's value first. We don't need to write them because - //they are not going to be read on the callee. - intOutputs &= ~_callSub.IntNiRegsMask; - vecOutputs &= ~_callSub.VecNiRegsMask; - } - - StoreLocals(context, intOutputs, RegisterType.Int); - StoreLocals(context, vecOutputs, RegisterType.Vector); - } - - private void StoreLocals(ILMethodBuilder context, long outputs, RegisterType baseType) - { - for (int bit = 0; bit < 64; bit++) - { - long mask = 1L << bit; - - if ((outputs & mask) != 0) - { - Register reg = ILMethodBuilder.GetRegFromBit(bit, baseType); - - context.Generator.EmitLdarg(TranslatedSub.StateArgIdx); - context.Generator.EmitLdloc(context.GetLocalIndex(reg)); - - context.Generator.Emit(OpCodes.Stfld, reg.GetField()); - } - } - } - } -} \ No newline at end of file diff --git a/ChocolArm64/Translation/RegisterUsage.cs b/ChocolArm64/Translation/RegisterUsage.cs index 2e6829d5..fe0c8659 100644 --- a/ChocolArm64/Translation/RegisterUsage.cs +++ b/ChocolArm64/Translation/RegisterUsage.cs @@ -1,269 +1,160 @@ -using System; +using ChocolArm64.IntermediateRepresentation; +using ChocolArm64.State; using System.Collections.Generic; namespace ChocolArm64.Translation { class RegisterUsage { - public const long CallerSavedIntRegistersMask = 0x7fL << 9; - public const long PStateNzcvFlagsMask = 0xfL << 60; + private const long CallerSavedIntRegistersMask = 0x7fL << 9; + private const long PStateNzcvFlagsMask = 0xfL << 60; - public const long CallerSavedVecRegistersMask = 0xffffL << 16; + private const long CallerSavedVecRegistersMask = 0xffffL << 16; - private class PathIo + private RegisterMask[] _inputs; + private RegisterMask[] _outputs; + + public RegisterUsage(BasicBlock entryBlock, int blocksCount) { - private Dictionary _allInputs; - private Dictionary _cmnOutputs; + _inputs = new RegisterMask[blocksCount]; + _outputs = new RegisterMask[blocksCount]; - private long _allOutputs; + HashSet visited = new HashSet(); - public PathIo() + Stack blockStack = new Stack(); + + List postOrderBlocks = new List(blocksCount); + + visited.Add(entryBlock); + + blockStack.Push(entryBlock); + + while (blockStack.TryPop(out BasicBlock block)) { - _allInputs = new Dictionary(); - _cmnOutputs = new Dictionary(); - } - - public void Set(ILBlock entry, long inputs, long outputs) - { - if (!_allInputs.TryAdd(entry, inputs)) + if (block.Next != null && visited.Add(block.Next)) { - _allInputs[entry] |= inputs; + blockStack.Push(block); + blockStack.Push(block.Next); } - - if (!_cmnOutputs.TryAdd(entry, outputs)) + else if (block.Branch != null && visited.Add(block.Branch)) { - _cmnOutputs[entry] &= outputs; + blockStack.Push(block); + blockStack.Push(block.Branch); } - - _allOutputs |= outputs; - } - - public long GetInputs(ILBlock entry) - { - if (_allInputs.TryGetValue(entry, out long inputs)) + else { - //We also need to read the registers that may not be written - //by all paths that can reach a exit point, to ensure that - //the local variable will not remain uninitialized depending - //on the flow path taken. - return inputs | (_allOutputs & ~_cmnOutputs[entry]); - } - - return 0; - } - - public long GetOutputs() - { - return _allOutputs; - } - } - - private Dictionary _intPaths; - private Dictionary _vecPaths; - - private struct BlockIo : IEquatable - { - public ILBlock Block { get; } - public ILBlock Entry { get; } - - public long IntInputs { get; set; } - public long VecInputs { get; set; } - public long IntOutputs { get; set; } - public long VecOutputs { get; set; } - - public BlockIo(ILBlock block, ILBlock entry) - { - Block = block; - Entry = entry; - - IntInputs = IntOutputs = 0; - VecInputs = VecOutputs = 0; - } - - public BlockIo( - ILBlock block, - ILBlock entry, - long intInputs, - long vecInputs, - long intOutputs, - long vecOutputs) : this(block, entry) - { - IntInputs = intInputs; - VecInputs = vecInputs; - IntOutputs = intOutputs; - VecOutputs = vecOutputs; - } - - public override bool Equals(object obj) - { - if (!(obj is BlockIo other)) - { - return false; - } - - return Equals(other); - } - - public bool Equals(BlockIo other) - { - return other.Block == Block && - other.Entry == Entry && - other.IntInputs == IntInputs && - other.VecInputs == VecInputs && - other.IntOutputs == IntOutputs && - other.VecOutputs == VecOutputs; - } - - public override int GetHashCode() - { - return HashCode.Combine(Block, Entry, IntInputs, VecInputs, IntOutputs, VecOutputs); - } - - public static bool operator ==(BlockIo lhs, BlockIo rhs) - { - return lhs.Equals(rhs); - } - - public static bool operator !=(BlockIo lhs, BlockIo rhs) - { - return !(lhs == rhs); - } - } - - public RegisterUsage() - { - _intPaths = new Dictionary(); - _vecPaths = new Dictionary(); - } - - public void BuildUses(ILBlock entry) - { - //This will go through all possible paths on the graph, - //and store all inputs/outputs for each block. A register - //that was previously written to already is not considered an input. - //When a block can be reached by more than one path, then the - //output from all paths needs to be set for this block, and - //only outputs present in all of the parent blocks can be considered - //when doing input elimination. Each block chain has a entry, that's where - //the code starts executing. They are present on the subroutine start point, - //and on call return points too (address written to X30 by BL). - HashSet visited = new HashSet(); - - Queue unvisited = new Queue(); - - void Enqueue(BlockIo block) - { - if (visited.Add(block)) - { - unvisited.Enqueue(block); + postOrderBlocks.Add(block); } } - Enqueue(new BlockIo(entry, entry)); + RegisterMask[] cmnOutputMasks = new RegisterMask[blocksCount]; - while (unvisited.Count > 0) + bool modified; + + bool firstPass = true; + + do { - BlockIo current = unvisited.Dequeue(); + modified = false; - current.IntInputs |= current.Block.IntInputs & ~current.IntOutputs; - current.VecInputs |= current.Block.VecInputs & ~current.VecOutputs; - current.IntOutputs |= current.Block.IntOutputs; - current.VecOutputs |= current.Block.VecOutputs; - - //Check if this is a exit block - //(a block that returns or calls another sub). - if ((current.Block.Next == null && - current.Block.Branch == null) || current.Block.HasStateStore) + for (int blkIndex = postOrderBlocks.Count - 1; blkIndex >= 0; blkIndex--) { - if (!_intPaths.TryGetValue(current.Block, out PathIo intPath)) + BasicBlock block = postOrderBlocks[blkIndex]; + + if (block.Predecessors.Count != 0 && !block.HasStateLoad) { - _intPaths.Add(current.Block, intPath = new PathIo()); + BasicBlock predecessor = block.Predecessors[0]; + + RegisterMask cmnOutputs = predecessor.RegOutputs | cmnOutputMasks[predecessor.Index]; + + RegisterMask outputs = _outputs[predecessor.Index]; + + for (int pIndex = 1; pIndex < block.Predecessors.Count; pIndex++) + { + predecessor = block.Predecessors[pIndex]; + + cmnOutputs &= predecessor.RegOutputs | cmnOutputMasks[predecessor.Index]; + + outputs |= _outputs[predecessor.Index]; + } + + _inputs[block.Index] |= outputs & ~cmnOutputs; + + if (!firstPass) + { + cmnOutputs &= cmnOutputMasks[block.Index]; + } + + if (Exchange(cmnOutputMasks, block.Index, cmnOutputs)) + { + modified = true; + } + + outputs |= block.RegOutputs; + + if (Exchange(_outputs, block.Index, _outputs[block.Index] | outputs)) + { + modified = true; + } + } + else if (Exchange(_outputs, block.Index, block.RegOutputs)) + { + modified = true; + } + } + + firstPass = false; + } + while (modified); + + do + { + modified = false; + + for (int blkIndex = 0; blkIndex < postOrderBlocks.Count; blkIndex++) + { + BasicBlock block = postOrderBlocks[blkIndex]; + + RegisterMask inputs = block.RegInputs; + + if (block.Next != null) + { + inputs |= _inputs[block.Next.Index]; } - if (!_vecPaths.TryGetValue(current.Block, out PathIo vecPath)) + if (block.Branch != null) { - _vecPaths.Add(current.Block, vecPath = new PathIo()); + inputs |= _inputs[block.Branch.Index]; } - intPath.Set(current.Entry, current.IntInputs, current.IntOutputs); - vecPath.Set(current.Entry, current.VecInputs, current.VecOutputs); - } + inputs &= ~cmnOutputMasks[block.Index]; - void EnqueueFromCurrent(ILBlock block, bool retTarget) - { - BlockIo blockIo; - - if (retTarget) + if (Exchange(_inputs, block.Index, _inputs[block.Index] | inputs)) { - blockIo = new BlockIo(block, block); + modified = true; } - else - { - blockIo = new BlockIo( - block, - current.Entry, - current.IntInputs, - current.VecInputs, - current.IntOutputs, - current.VecOutputs); - } - - Enqueue(blockIo); - } - - if (current.Block.Next != null) - { - EnqueueFromCurrent(current.Block.Next, current.Block.HasStateStore); - } - - if (current.Block.Branch != null) - { - EnqueueFromCurrent(current.Block.Branch, false); } } + while (modified); } - public long GetIntInputs(ILBlock entry) => GetInputsImpl(entry, _intPaths.Values); - public long GetVecInputs(ILBlock entry) => GetInputsImpl(entry, _vecPaths.Values); - - private long GetInputsImpl(ILBlock entry, IEnumerable values) + private static bool Exchange(RegisterMask[] masks, int blkIndex, RegisterMask value) { - long inputs = 0; + RegisterMask oldValue = masks[blkIndex]; - foreach (PathIo path in values) - { - inputs |= path.GetInputs(entry); - } + masks[blkIndex] = value; - return inputs; + return oldValue != value; } - public long GetIntNotInputs(ILBlock entry) => GetNotInputsImpl(entry, _intPaths.Values); - public long GetVecNotInputs(ILBlock entry) => GetNotInputsImpl(entry, _vecPaths.Values); + public RegisterMask GetInputs(BasicBlock entryBlock) => _inputs[entryBlock.Index]; - private long GetNotInputsImpl(ILBlock entry, IEnumerable values) - { - //Returns a mask with registers that are written to - //before being read. Only those registers that are - //written in all paths, and is not read before being - //written to on those paths, should be set on the mask. - long mask = -1L; + public RegisterMask GetOutputs(BasicBlock block) => _outputs[block.Index]; - foreach (PathIo path in values) - { - mask &= path.GetOutputs() & ~path.GetInputs(entry); - } - - return mask; - } - - public long GetIntOutputs(ILBlock block) => _intPaths[block].GetOutputs(); - public long GetVecOutputs(ILBlock block) => _vecPaths[block].GetOutputs(); - - public static long ClearCallerSavedIntRegs(long mask, bool isAarch64) + public static long ClearCallerSavedIntRegs(long mask, ExecutionMode mode) { //TODO: ARM32 support. - if (isAarch64) + if (mode == ExecutionMode.Aarch64) { mask &= ~(CallerSavedIntRegistersMask | PStateNzcvFlagsMask); } @@ -271,10 +162,10 @@ namespace ChocolArm64.Translation return mask; } - public static long ClearCallerSavedVecRegs(long mask, bool isAarch64) + public static long ClearCallerSavedVecRegs(long mask, ExecutionMode mode) { //TODO: ARM32 support. - if (isAarch64) + if (mode == ExecutionMode.Aarch64) { mask &= ~CallerSavedVecRegistersMask; } diff --git a/ChocolArm64/Translation/TranslatedSub.cs b/ChocolArm64/Translation/TranslatedSub.cs index 8b599b7a..704e3b47 100644 --- a/ChocolArm64/Translation/TranslatedSub.cs +++ b/ChocolArm64/Translation/TranslatedSub.cs @@ -26,25 +26,15 @@ namespace ChocolArm64.Translation public TranslationTier Tier { get; } - public long IntNiRegsMask { get; } - public long VecNiRegsMask { get; } - - private bool _isWorthOptimizing; + private bool _rejit; private int _callCount; - public TranslatedSub( - DynamicMethod method, - long intNiRegsMask, - long vecNiRegsMask, - TranslationTier tier, - bool isWorthOptimizing) + public TranslatedSub(DynamicMethod method, TranslationTier tier, bool rejit) { - Method = method ?? throw new ArgumentNullException(nameof(method));; - IntNiRegsMask = intNiRegsMask; - VecNiRegsMask = vecNiRegsMask; - _isWorthOptimizing = isWorthOptimizing; - Tier = tier; + Method = method ?? throw new ArgumentNullException(nameof(method));; + Tier = tier; + _rejit = rejit; } static TranslatedSub() @@ -82,9 +72,9 @@ namespace ChocolArm64.Translation return Delegate(threadState, memory); } - public bool IsWorthOptimizing() + public bool Rejit() { - if (!_isWorthOptimizing) + if (!_rejit) { return false; } @@ -94,9 +84,8 @@ namespace ChocolArm64.Translation return false; } - //Only return true once, so that it is - //added to the queue only once. - _isWorthOptimizing = false; + //Only return true once, so that it is added to the queue only once. + _rejit = false; return true; } diff --git a/ChocolArm64/Translation/TranslatedSubBuilder.cs b/ChocolArm64/Translation/TranslatedSubBuilder.cs new file mode 100644 index 00000000..eb69d9ee --- /dev/null +++ b/ChocolArm64/Translation/TranslatedSubBuilder.cs @@ -0,0 +1,274 @@ +using ChocolArm64.IntermediateRepresentation; +using ChocolArm64.State; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Reflection.Emit; +using System.Runtime.Intrinsics; + +using static ChocolArm64.State.RegisterConsts; + +namespace ChocolArm64.Translation +{ + class TranslatedSubBuilder + { + private ExecutionMode _mode; + + private bool _isComplete; + + private Dictionary _locals; + + private RegisterUsage _regUsage; + + public TranslatedSubBuilder(ExecutionMode mode, bool isComplete = false) + { + _mode = mode; + _isComplete = isComplete; + } + + public TranslatedSub Build(BasicBlock[] blocks, string name, TranslationTier tier, bool rejit = true) + { + _regUsage = new RegisterUsage(blocks[0], blocks.Length); + + DynamicMethod method = new DynamicMethod(name, typeof(long), TranslatedSub.FixedArgTypes); + + TranslatedSub subroutine = new TranslatedSub(method, tier, rejit); + + _locals = new Dictionary(); + + Dictionary labels = new Dictionary(); + + ILGenerator generator = method.GetILGenerator(); + + Label GetLabel(ILLabel label) + { + if (!labels.TryGetValue(label, out Label ilLabel)) + { + ilLabel = generator.DefineLabel(); + + labels.Add(label, ilLabel); + } + + return ilLabel; + } + + foreach (BasicBlock block in blocks) + { + for (int index = 0; index < block.Count; index++) + { + Operation operation = block.GetOperation(index); + + switch (operation.Type) + { + case OperationType.Call: + generator.Emit(OpCodes.Call, operation.GetArg(0)); + break; + + case OperationType.CallVirtual: + generator.Emit(OpCodes.Callvirt, operation.GetArg(0)); + break; + + case OperationType.IL: + generator.Emit(operation.GetArg(0)); + break; + + case OperationType.ILBranch: + generator.Emit(operation.GetArg(0), GetLabel(operation.GetArg(1))); + break; + + case OperationType.LoadArgument: + generator.EmitLdarg(operation.GetArg(0)); + break; + + case OperationType.LoadConstant: + EmitLoadConstant(generator, operation.GetArg(0)); + break; + + case OperationType.LoadContext: + EmitLoadContext(generator, operation.Parent); + break; + + case OperationType.LoadField: + generator.Emit(OpCodes.Ldfld, operation.GetArg(0)); + break; + + case OperationType.LoadLocal: + EmitLoadLocal( + generator, + operation.GetArg(0), + operation.GetArg(1), + operation.GetArg(2)); + break; + + case OperationType.MarkLabel: + generator.MarkLabel(GetLabel(operation.GetArg(0))); + break; + + case OperationType.StoreContext: + EmitStoreContext(generator, operation.Parent); + break; + + case OperationType.StoreLocal: + EmitStoreLocal( + generator, + operation.GetArg(0), + operation.GetArg(1), + operation.GetArg(2)); + break; + } + } + } + + subroutine.PrepareMethod(); + + return subroutine; + } + + private static void EmitLoadConstant(ILGenerator generator, object value) + { + switch (value) + { + case int valI4: generator.EmitLdc_I4(valI4); break; + case long valI8: generator.Emit(OpCodes.Ldc_I8, valI8); break; + case float valR4: generator.Emit(OpCodes.Ldc_R4, valR4); break; + case double valR8: generator.Emit(OpCodes.Ldc_R8, valR8); break; + } + } + + private void EmitLoadContext(ILGenerator generator, BasicBlock block) + { + RegisterMask inputs = _regUsage.GetInputs(block); + + long intInputs = inputs.IntMask; + long vecInputs = inputs.VecMask; + + if (Optimizations.AssumeStrictAbiCompliance && _isComplete) + { + intInputs = RegisterUsage.ClearCallerSavedIntRegs(intInputs, _mode); + vecInputs = RegisterUsage.ClearCallerSavedVecRegs(vecInputs, _mode); + } + + LoadLocals(generator, intInputs, RegisterType.Int); + LoadLocals(generator, vecInputs, RegisterType.Vector); + } + + private void LoadLocals(ILGenerator generator, long inputs, RegisterType baseType) + { + for (int bit = 0; bit < 64; bit++) + { + long mask = 1L << bit; + + if ((inputs & mask) != 0) + { + Register reg = GetRegFromBit(bit, baseType); + + generator.EmitLdarg(TranslatedSub.StateArgIdx); + generator.Emit(OpCodes.Ldfld, reg.GetField()); + + generator.EmitStloc(GetLocalIndex(generator, reg)); + } + } + } + + private void EmitStoreContext(ILGenerator generator, BasicBlock block) + { + RegisterMask outputs = _regUsage.GetOutputs(block); + + long intOutputs = outputs.IntMask; + long vecOutputs = outputs.VecMask; + + if (Optimizations.AssumeStrictAbiCompliance && _isComplete) + { + intOutputs = RegisterUsage.ClearCallerSavedIntRegs(intOutputs, _mode); + vecOutputs = RegisterUsage.ClearCallerSavedVecRegs(vecOutputs, _mode); + } + + StoreLocals(generator, intOutputs, RegisterType.Int); + StoreLocals(generator, vecOutputs, RegisterType.Vector); + } + + private void StoreLocals(ILGenerator generator, long outputs, RegisterType baseType) + { + for (int bit = 0; bit < 64; bit++) + { + long mask = 1L << bit; + + if ((outputs & mask) != 0) + { + Register reg = GetRegFromBit(bit, baseType); + + generator.EmitLdarg(TranslatedSub.StateArgIdx); + generator.EmitLdloc(GetLocalIndex(generator, reg)); + + generator.Emit(OpCodes.Stfld, reg.GetField()); + } + } + } + + private void EmitLoadLocal(ILGenerator generator, int index, RegisterType type, RegisterSize size) + { + Register reg = new Register(index, type); + + generator.EmitLdloc(GetLocalIndex(generator, reg)); + + if (type == RegisterType.Int && size == RegisterSize.Int32) + { + generator.Emit(OpCodes.Conv_U4); + } + } + + private void EmitStoreLocal(ILGenerator generator, int index, RegisterType type, RegisterSize size) + { + Register reg = new Register(index, type); + + if (type == RegisterType.Int && size == RegisterSize.Int32) + { + generator.Emit(OpCodes.Conv_U8); + } + + generator.EmitStloc(GetLocalIndex(generator, reg)); + } + + private int GetLocalIndex(ILGenerator generator, Register reg) + { + if (!_locals.TryGetValue(reg, out int index)) + { + generator.DeclareLocal(GetFieldType(reg.Type)); + + index = _locals.Count; + + _locals.Add(reg, index); + } + + return index; + } + + private static Type GetFieldType(RegisterType regType) + { + switch (regType) + { + case RegisterType.Flag: return typeof(bool); + case RegisterType.Int: return typeof(ulong); + case RegisterType.Vector: return typeof(Vector128); + } + + throw new ArgumentException(nameof(regType)); + } + + private static Register GetRegFromBit(int bit, RegisterType baseType) + { + if (bit < RegsCount) + { + return new Register(bit, baseType); + } + else if (baseType == RegisterType.Int) + { + return new Register(bit & RegsMask, RegisterType.Flag); + } + else + { + throw new ArgumentOutOfRangeException(nameof(bit)); + } + } + } +} \ No newline at end of file diff --git a/ChocolArm64/Translation/Translator.cs b/ChocolArm64/Translation/Translator.cs index bda0bca0..2f88aaf9 100644 --- a/ChocolArm64/Translation/Translator.cs +++ b/ChocolArm64/Translation/Translator.cs @@ -1,8 +1,10 @@ using ChocolArm64.Decoders; using ChocolArm64.Events; +using ChocolArm64.IntermediateRepresentation; using ChocolArm64.Memory; using ChocolArm64.State; using System; +using System.Reflection.Emit; using System.Threading; namespace ChocolArm64.Translation @@ -82,7 +84,7 @@ namespace ChocolArm64.Translation sub = TranslateLowCq(position, state.GetExecutionMode()); } - if (sub.IsWorthOptimizing()) + if (sub.Rejit()) { bool isComplete = cs == CallType.Call || cs == CallType.VirtualCall; @@ -124,58 +126,125 @@ namespace ChocolArm64.Translation private TranslatedSub TranslateLowCq(long position, ExecutionMode mode) { - Block block = Decoder.DecodeBasicBlock(_memory, position, mode); + Block[] blocks = Decoder.DecodeBasicBlock(_memory, (ulong)position, mode); - ILEmitterCtx context = new ILEmitterCtx(_memory, _cache, _queue, TranslationTier.Tier0, block); + ILEmitterCtx context = new ILEmitterCtx(_memory, _cache, _queue, TranslationTier.Tier0); - string subName = GetSubroutineName(position); + BasicBlock[] bbs = EmitAndGetBlocks(context, blocks); - bool isAarch64 = mode == ExecutionMode.Aarch64; + TranslatedSubBuilder builder = new TranslatedSubBuilder(mode); - ILMethodBuilder ilMthdBuilder = new ILMethodBuilder(context.GetILBlocks(), subName, isAarch64); + string name = GetSubroutineName(position); - TranslatedSub subroutine = ilMthdBuilder.GetSubroutine(TranslationTier.Tier0, isWorthOptimizing: true); + TranslatedSub subroutine = builder.Build(bbs, name, TranslationTier.Tier0); - return _cache.GetOrAdd(position, subroutine, block.OpCodes.Count); + return _cache.GetOrAdd(position, subroutine, GetOpsCount(bbs)); } private TranslatedSub TranslateHighCq(long position, ExecutionMode mode, bool isComplete) { - Block graph = Decoder.DecodeSubroutine(_memory, position, mode); + Block[] blocks = Decoder.DecodeSubroutine(_memory, (ulong)position, mode); - ILEmitterCtx context = new ILEmitterCtx(_memory, _cache, _queue, TranslationTier.Tier1, graph); + ILEmitterCtx context = new ILEmitterCtx(_memory, _cache, _queue, TranslationTier.Tier1); - ILBlock[] ilBlocks = context.GetILBlocks(); + if (blocks[0].Address != (ulong)position) + { + context.Emit(OpCodes.Br, context.GetLabel(position)); + } - string subName = GetSubroutineName(position); - - bool isAarch64 = mode == ExecutionMode.Aarch64; + BasicBlock[] bbs = EmitAndGetBlocks(context, blocks); isComplete &= !context.HasIndirectJump; - ILMethodBuilder ilMthdBuilder = new ILMethodBuilder(ilBlocks, subName, isAarch64, isComplete); + TranslatedSubBuilder builder = new TranslatedSubBuilder(mode, isComplete); - TranslatedSub subroutine = ilMthdBuilder.GetSubroutine(TranslationTier.Tier1, context.HasSlowCall); + string name = GetSubroutineName(position); - int ilOpCount = 0; - - foreach (ILBlock ilBlock in ilBlocks) - { - ilOpCount += ilBlock.Count; - } + TranslatedSub subroutine = builder.Build(bbs, name, TranslationTier.Tier1, context.HasSlowCall); ForceAheadOfTimeCompilation(subroutine); - _cache.AddOrUpdate(position, subroutine, ilOpCount); + _cache.AddOrUpdate(position, subroutine, GetOpsCount(bbs)); return subroutine; } - private string GetSubroutineName(long position) + private static BasicBlock[] EmitAndGetBlocks(ILEmitterCtx context, Block[] blocks) + { + for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++) + { + Block block = blocks[blkIndex]; + + context.CurrBlock = block; + + context.MarkLabel(context.GetLabel((long)block.Address)); + + for (int opcIndex = 0; opcIndex < block.OpCodes.Count; opcIndex++) + { + OpCode64 opCode = block.OpCodes[opcIndex]; + + context.CurrOp = opCode; + + bool isLastOp = opcIndex == block.OpCodes.Count - 1; + + if (isLastOp && block.Branch != null && block.Branch.Address <= block.Address) + { + context.EmitSynchronization(); + } + + ILLabel lblPredicateSkip = null; + + if (opCode is OpCode32 op && op.Cond < Condition.Al) + { + lblPredicateSkip = new ILLabel(); + + context.EmitCondBranch(lblPredicateSkip, op.Cond.Invert()); + } + + opCode.Emitter(context); + + if (lblPredicateSkip != null) + { + context.MarkLabel(lblPredicateSkip); + + context.ResetBlockStateForPredicatedOp(); + + //If this is the last op on the block, and there's no "next" block + //after this one, then we have to return right now, with the address + //of the next instruction to be executed (in the case that the condition + //is false, and the branch was not taken, as all basic blocks should end + //with some kind of branch). + if (isLastOp && block.Next == null) + { + context.EmitStoreContext(); + context.EmitLdc_I8(opCode.Position + opCode.OpCodeSizeInBytes); + + context.Emit(OpCodes.Ret); + } + } + } + } + + return context.GetBlocks(); + } + + private static string GetSubroutineName(long position) { return $"Sub{position:x16}"; } + private static int GetOpsCount(BasicBlock[] blocks) + { + int opCount = 0; + + foreach (BasicBlock block in blocks) + { + opCount += block.Count; + } + + return opCount; + } + private void ForceAheadOfTimeCompilation(TranslatedSub subroutine) { subroutine.Execute(_dummyThreadState, null); diff --git a/ChocolArm64/Translation/VarType.cs b/ChocolArm64/Translation/VarType.cs deleted file mode 100644 index d671575e..00000000 --- a/ChocolArm64/Translation/VarType.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace ChocolArm64.Translation -{ - enum VarType - { - Arg, - Flag, - Int, - Vector - } -} \ No newline at end of file