aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/Compiler.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/Compiler.cs')
-rw-r--r--src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/Compiler.cs789
1 files changed, 789 insertions, 0 deletions
diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/Compiler.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/Compiler.cs
new file mode 100644
index 00000000..1e8a8915
--- /dev/null
+++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/Compiler.cs
@@ -0,0 +1,789 @@
+using ARMeilleure.Common;
+using ARMeilleure.Memory;
+using Ryujinx.Cpu.LightningJit.CodeGen;
+using Ryujinx.Cpu.LightningJit.CodeGen.Arm64;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Numerics;
+
+namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
+{
+ static class Compiler
+ {
+ public const uint UsableGprsMask = 0x7fff;
+ public const uint UsableFpSimdMask = 0xffff;
+ public const uint UsablePStateMask = 0xf0000000;
+
+ private const int Encodable26BitsOffsetLimit = 0x2000000;
+
+ private readonly struct Context
+ {
+ public readonly CodeWriter Writer;
+ public readonly RegisterAllocator RegisterAllocator;
+ public readonly MemoryManagerType MemoryManagerType;
+ public readonly TailMerger TailMerger;
+ public readonly AddressTable<ulong> FuncTable;
+ public readonly IntPtr DispatchStubPointer;
+
+ private readonly RegisterSaveRestore _registerSaveRestore;
+ private readonly IntPtr _pageTablePointer;
+
+ public Context(
+ CodeWriter writer,
+ RegisterAllocator registerAllocator,
+ MemoryManagerType mmType,
+ TailMerger tailMerger,
+ AddressTable<ulong> funcTable,
+ RegisterSaveRestore registerSaveRestore,
+ IntPtr dispatchStubPointer,
+ IntPtr pageTablePointer)
+ {
+ Writer = writer;
+ RegisterAllocator = registerAllocator;
+ MemoryManagerType = mmType;
+ TailMerger = tailMerger;
+ FuncTable = funcTable;
+ _registerSaveRestore = registerSaveRestore;
+ DispatchStubPointer = dispatchStubPointer;
+ _pageTablePointer = pageTablePointer;
+ }
+
+ public readonly int GetReservedStackOffset()
+ {
+ return _registerSaveRestore.GetReservedStackOffset();
+ }
+
+ public readonly void WritePrologueAt(int instructionPointer)
+ {
+ CodeWriter writer = new();
+ Assembler asm = new(writer);
+
+ _registerSaveRestore.WritePrologue(ref asm);
+
+ // If needed, set up the fixed registers with the pointers we will use.
+ // First one is the context pointer (passed as first argument),
+ // second one is the page table or address space base, it is at a fixed memory location and considered constant.
+
+ if (RegisterAllocator.FixedContextRegister != 0)
+ {
+ asm.Mov(Register(RegisterAllocator.FixedContextRegister), Register(0));
+ }
+
+ asm.Mov(Register(RegisterAllocator.FixedPageTableRegister), (ulong)_pageTablePointer);
+
+ LoadFromContext(ref asm);
+
+ // Write the prologue at the specified position in our writer.
+ Writer.WriteInstructionsAt(instructionPointer, writer);
+ }
+
+ public readonly void WriteEpilogueWithoutContext()
+ {
+ Assembler asm = new(Writer);
+
+ _registerSaveRestore.WriteEpilogue(ref asm);
+ }
+
+ public void LoadFromContext()
+ {
+ Assembler asm = new(Writer);
+
+ LoadFromContext(ref asm);
+ }
+
+ private void LoadFromContext(ref Assembler asm)
+ {
+ LoadGprFromContext(ref asm, RegisterAllocator.UsedGprsMask & UsableGprsMask, NativeContextOffsets.GprBaseOffset);
+ LoadFpSimdFromContext(ref asm, RegisterAllocator.UsedFpSimdMask & UsableFpSimdMask, NativeContextOffsets.FpSimdBaseOffset);
+ LoadPStateFromContext(ref asm, UsablePStateMask, NativeContextOffsets.FlagsBaseOffset);
+ }
+
+ public void StoreToContext()
+ {
+ Assembler asm = new(Writer);
+
+ StoreToContext(ref asm);
+ }
+
+ private void StoreToContext(ref Assembler asm)
+ {
+ StoreGprToContext(ref asm, RegisterAllocator.UsedGprsMask & UsableGprsMask, NativeContextOffsets.GprBaseOffset);
+ StoreFpSimdToContext(ref asm, RegisterAllocator.UsedFpSimdMask & UsableFpSimdMask, NativeContextOffsets.FpSimdBaseOffset);
+ StorePStateToContext(ref asm, UsablePStateMask, NativeContextOffsets.FlagsBaseOffset);
+ }
+
+ private void LoadGprFromContext(ref Assembler asm, uint mask, int baseOffset)
+ {
+ Operand contextPtr = Register(RegisterAllocator.FixedContextRegister);
+
+ while (mask != 0)
+ {
+ int reg = BitOperations.TrailingZeroCount(mask);
+ int offset = baseOffset + reg * 8;
+
+ if (reg < 31 && (mask & (2u << reg)) != 0 && offset < RegisterSaveRestore.Encodable9BitsOffsetLimit)
+ {
+ mask &= ~(3u << reg);
+
+ asm.LdpRiUn(Register(reg), Register(reg + 1), contextPtr, offset);
+ }
+ else
+ {
+ mask &= ~(1u << reg);
+
+ asm.LdrRiUn(Register(reg), contextPtr, offset);
+ }
+ }
+ }
+
+ private void LoadFpSimdFromContext(ref Assembler asm, uint mask, int baseOffset)
+ {
+ Operand contextPtr = Register(RegisterAllocator.FixedContextRegister);
+
+ while (mask != 0)
+ {
+ int reg = BitOperations.TrailingZeroCount(mask);
+ int offset = baseOffset + reg * 16;
+
+ mask &= ~(1u << reg);
+
+ asm.LdrRiUn(Register(reg, OperandType.V128), contextPtr, offset);
+ }
+ }
+
+ private void LoadPStateFromContext(ref Assembler asm, uint mask, int baseOffset)
+ {
+ if (mask == 0)
+ {
+ return;
+ }
+
+ Operand contextPtr = Register(RegisterAllocator.FixedContextRegister);
+
+ using ScopedRegister tempRegister = RegisterAllocator.AllocateTempGprRegisterScoped();
+
+ asm.LdrRiUn(tempRegister.Operand, contextPtr, baseOffset);
+ asm.MsrNzcv(tempRegister.Operand);
+ }
+
+ private void StoreGprToContext(ref Assembler asm, uint mask, int baseOffset)
+ {
+ Operand contextPtr = Register(RegisterAllocator.FixedContextRegister);
+
+ while (mask != 0)
+ {
+ int reg = BitOperations.TrailingZeroCount(mask);
+ int offset = baseOffset + reg * 8;
+
+ if (reg < 31 && (mask & (2u << reg)) != 0 && offset < RegisterSaveRestore.Encodable9BitsOffsetLimit)
+ {
+ mask &= ~(3u << reg);
+
+ asm.StpRiUn(Register(reg), Register(reg + 1), contextPtr, offset);
+ }
+ else
+ {
+ mask &= ~(1u << reg);
+
+ asm.StrRiUn(Register(reg), contextPtr, offset);
+ }
+ }
+ }
+
+ private void StoreFpSimdToContext(ref Assembler asm, uint mask, int baseOffset)
+ {
+ Operand contextPtr = Register(RegisterAllocator.FixedContextRegister);
+
+ while (mask != 0)
+ {
+ int reg = BitOperations.TrailingZeroCount(mask);
+ int offset = baseOffset + reg * 16;
+
+ mask &= ~(1u << reg);
+
+ asm.StrRiUn(Register(reg, OperandType.V128), contextPtr, offset);
+ }
+ }
+
+ private void StorePStateToContext(ref Assembler asm, uint mask, int baseOffset)
+ {
+ if (mask == 0)
+ {
+ return;
+ }
+
+ Operand contextPtr = Register(RegisterAllocator.FixedContextRegister);
+
+ using ScopedRegister tempRegister = RegisterAllocator.AllocateTempGprRegisterScoped();
+ using ScopedRegister tempRegister2 = RegisterAllocator.AllocateTempGprRegisterScoped();
+
+ asm.LdrRiUn(tempRegister.Operand, contextPtr, baseOffset);
+ asm.MrsNzcv(tempRegister2.Operand);
+ asm.And(tempRegister.Operand, tempRegister.Operand, InstEmitCommon.Const(0xfffffff));
+ asm.Orr(tempRegister.Operand, tempRegister.Operand, tempRegister2.Operand);
+ asm.StrRiUn(tempRegister.Operand, contextPtr, baseOffset);
+ }
+ }
+
+ public static CompiledFunction Compile(CpuPreset cpuPreset, IMemoryManager memoryManager, ulong address, AddressTable<ulong> funcTable, IntPtr dispatchStubPtr, bool isThumb)
+ {
+ MultiBlock multiBlock = Decoder<InstEmit>.DecodeMulti(cpuPreset, memoryManager, address, isThumb);
+
+ Dictionary<ulong, int> targets = new();
+
+ CodeWriter writer = new();
+ RegisterAllocator regAlloc = new();
+ Assembler asm = new(writer);
+ CodeGenContext cgContext = new(writer, asm, regAlloc, memoryManager.Type, isThumb);
+ ArmCondition lastCondition = ArmCondition.Al;
+ int lastConditionIp = 0;
+
+ // Required for load/store to context.
+ regAlloc.EnsureTempGprRegisters(2);
+
+ ulong pc = address;
+
+ for (int blockIndex = 0; blockIndex < multiBlock.Blocks.Count; blockIndex++)
+ {
+ Block block = multiBlock.Blocks[blockIndex];
+
+ Debug.Assert(block.Address == pc);
+
+ targets.Add(pc, writer.InstructionPointer);
+
+ for (int index = 0; index < block.Instructions.Count; index++)
+ {
+ InstInfo instInfo = block.Instructions[index];
+
+ if (index < block.Instructions.Count - 1)
+ {
+ cgContext.SetNextInstruction(block.Instructions[index + 1]);
+ }
+ else
+ {
+ cgContext.SetNextInstruction(default);
+ }
+
+ SetConditionalStart(cgContext, ref lastCondition, ref lastConditionIp, instInfo.Name, instInfo.Flags, instInfo.Encoding);
+
+ if (block.IsLoopEnd && index == block.Instructions.Count - 1)
+ {
+ // If this is a loop, the code might run for a long time uninterrupted.
+ // We insert a "sync point" here to ensure the loop can be interrupted if needed.
+
+ cgContext.AddPendingSyncPoint();
+
+ asm.B(0);
+ }
+
+ cgContext.SetPc((uint)pc);
+
+ instInfo.EmitFunc(cgContext, instInfo.Encoding);
+
+ if (cgContext.ConsumeNzcvModified())
+ {
+ ForceConditionalEnd(cgContext, ref lastCondition, lastConditionIp);
+ }
+
+ cgContext.UpdateItState();
+
+ pc += instInfo.Flags.HasFlag(InstFlags.Thumb16) ? 2UL : 4UL;
+ }
+
+ if (Decoder<InstEmit>.WritesToPC(block.Instructions[^1].Encoding, block.Instructions[^1].Name, block.Instructions[^1].Flags, block.IsThumb))
+ {
+ // If the block ends with a PC register write, then we have a branch from register.
+
+ InstEmitCommon.SetThumbFlag(cgContext, regAlloc.RemapGprRegister(RegisterUtils.PcRegister));
+
+ cgContext.AddPendingIndirectBranch(block.Instructions[^1].Name, RegisterUtils.PcRegister);
+
+ asm.B(0);
+ }
+
+ ForceConditionalEnd(cgContext, ref lastCondition, lastConditionIp);
+ }
+
+ RegisterSaveRestore rsr = new(
+ regAlloc.UsedGprsMask & AbiConstants.GprCalleeSavedRegsMask,
+ regAlloc.UsedFpSimdMask & AbiConstants.FpSimdCalleeSavedRegsMask,
+ OperandType.FP64,
+ multiBlock.HasHostCall,
+ multiBlock.HasHostCall ? CalculateStackSizeForCallSpill(regAlloc.UsedGprsMask, regAlloc.UsedFpSimdMask, UsablePStateMask) : 0);
+
+ TailMerger tailMerger = new();
+
+ Context context = new(writer, regAlloc, memoryManager.Type, tailMerger, funcTable, rsr, dispatchStubPtr, memoryManager.PageTablePointer);
+
+ InstInfo lastInstruction = multiBlock.Blocks[^1].Instructions[^1];
+ bool lastInstIsConditional = GetCondition(lastInstruction, isThumb) != ArmCondition.Al;
+
+ if (multiBlock.IsTruncated || lastInstIsConditional || lastInstruction.Name.IsCall() || IsConditionalBranch(lastInstruction))
+ {
+ WriteTailCallConstant(context, ref asm, (uint)pc);
+ }
+
+ IEnumerable<PendingBranch> pendingBranches = cgContext.GetPendingBranches();
+
+ foreach (PendingBranch pendingBranch in pendingBranches)
+ {
+ RewriteBranchInstructionWithTarget(context, pendingBranch, targets);
+ }
+
+ tailMerger.WriteReturn(writer, context.WriteEpilogueWithoutContext);
+
+ context.WritePrologueAt(0);
+
+ return new(writer.AsByteSpan(), (int)(pc - address));
+ }
+
+ private static int CalculateStackSizeForCallSpill(uint gprUseMask, uint fpSimdUseMask, uint pStateUseMask)
+ {
+ // Note that we don't discard callee saved FP/SIMD register because only the lower 64 bits is callee saved,
+ // so if the function is using the full register, that won't be enough.
+ // We could do better, but it's likely not worth it since this case happens very rarely in practice.
+
+ return BitOperations.PopCount(gprUseMask & ~AbiConstants.GprCalleeSavedRegsMask) * 8 +
+ BitOperations.PopCount(fpSimdUseMask) * 16 +
+ (pStateUseMask != 0 ? 8 : 0);
+ }
+
+ private static void SetConditionalStart(
+ CodeGenContext context,
+ ref ArmCondition condition,
+ ref int instructionPointer,
+ InstName name,
+ InstFlags flags,
+ uint encoding)
+ {
+ if (!context.ConsumeItCondition(out ArmCondition currentCond))
+ {
+ currentCond = GetCondition(name, flags, encoding, context.IsThumb);
+ }
+
+ if (currentCond != condition)
+ {
+ WriteConditionalEnd(context, condition, instructionPointer);
+
+ condition = currentCond;
+
+ if (currentCond != ArmCondition.Al)
+ {
+ instructionPointer = context.CodeWriter.InstructionPointer;
+ context.Arm64Assembler.B(currentCond.Invert(), 0);
+ }
+ }
+ }
+
+ private static bool IsConditionalBranch(in InstInfo instInfo)
+ {
+ return instInfo.Name == InstName.B && (ArmCondition)(instInfo.Encoding >> 28) != ArmCondition.Al;
+ }
+
+ private static ArmCondition GetCondition(in InstInfo instInfo, bool isThumb)
+ {
+ return GetCondition(instInfo.Name, instInfo.Flags, instInfo.Encoding, isThumb);
+ }
+
+ private static ArmCondition GetCondition(InstName name, InstFlags flags, uint encoding, bool isThumb)
+ {
+ // For branch, we handle conditional execution on the instruction itself.
+ bool hasCond = flags.HasFlag(InstFlags.Cond) && !CanHandleConditionalInstruction(name, encoding, isThumb);
+
+ return hasCond ? (ArmCondition)(encoding >> 28) : ArmCondition.Al;
+ }
+
+ private static bool CanHandleConditionalInstruction(InstName name, uint encoding, bool isThumb)
+ {
+ if (name == InstName.B)
+ {
+ return true;
+ }
+
+ // We can use CSEL for conditional MOV from registers, as long the instruction is not setting flags.
+ // We don't handle thumb right now because the condition comes from the IT block which would be more complicated to handle.
+ if (name == InstName.MovR && !isThumb && (encoding & (1u << 20)) == 0)
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ private static void ForceConditionalEnd(CodeGenContext context, ref ArmCondition condition, int instructionPointer)
+ {
+ WriteConditionalEnd(context, condition, instructionPointer);
+
+ condition = ArmCondition.Al;
+ }
+
+ private static void WriteConditionalEnd(CodeGenContext context, ArmCondition condition, int instructionPointer)
+ {
+ if (condition != ArmCondition.Al)
+ {
+ int delta = context.CodeWriter.InstructionPointer - instructionPointer;
+ uint branchInst = context.CodeWriter.ReadInstructionAt(instructionPointer) | (((uint)delta & 0x7ffff) << 5);
+ Debug.Assert((int)((branchInst & ~0x1fu) << 8) >> 11 == delta * 4);
+
+ context.CodeWriter.WriteInstructionAt(instructionPointer, branchInst);
+ }
+ }
+
+ private static void RewriteBranchInstructionWithTarget(in Context context, in PendingBranch pendingBranch, Dictionary<ulong, int> targets)
+ {
+ switch (pendingBranch.BranchType)
+ {
+ case BranchType.Branch:
+ RewriteBranchInstructionWithTarget(context, pendingBranch.Name, pendingBranch.TargetAddress, pendingBranch.WriterPointer, targets);
+ break;
+ case BranchType.Call:
+ RewriteCallInstructionWithTarget(context, pendingBranch.TargetAddress, pendingBranch.NextAddress, pendingBranch.WriterPointer);
+ break;
+ case BranchType.IndirectBranch:
+ RewriteIndirectBranchInstructionWithTarget(context, pendingBranch.Name, pendingBranch.TargetAddress, pendingBranch.WriterPointer);
+ break;
+ case BranchType.TableBranchByte:
+ case BranchType.TableBranchHalfword:
+ RewriteTableBranchInstructionWithTarget(
+ context,
+ pendingBranch.BranchType == BranchType.TableBranchHalfword,
+ pendingBranch.TargetAddress,
+ pendingBranch.NextAddress,
+ pendingBranch.WriterPointer);
+ break;
+ case BranchType.IndirectCall:
+ RewriteIndirectCallInstructionWithTarget(context, pendingBranch.TargetAddress, pendingBranch.NextAddress, pendingBranch.WriterPointer);
+ break;
+ case BranchType.SyncPoint:
+ case BranchType.SoftwareInterrupt:
+ case BranchType.ReadCntpct:
+ RewriteHostCall(context, pendingBranch.Name, pendingBranch.BranchType, pendingBranch.TargetAddress, pendingBranch.NextAddress, pendingBranch.WriterPointer);
+ break;
+ default:
+ Debug.Fail($"Invalid branch type '{pendingBranch.BranchType}'");
+ break;
+ }
+ }
+
+ private static void RewriteBranchInstructionWithTarget(in Context context, InstName name, uint targetAddress, int branchIndex, Dictionary<ulong, int> targets)
+ {
+ CodeWriter writer = context.Writer;
+ Assembler asm = new(writer);
+
+ int delta;
+ int targetIndex;
+ uint encoding = writer.ReadInstructionAt(branchIndex);
+
+ if (encoding == 0x14000000)
+ {
+ // Unconditional branch.
+
+ if (targets.TryGetValue(targetAddress, out targetIndex))
+ {
+ delta = targetIndex - branchIndex;
+
+ if (delta >= -Encodable26BitsOffsetLimit && delta < Encodable26BitsOffsetLimit)
+ {
+ writer.WriteInstructionAt(branchIndex, encoding | (uint)(delta & 0x3ffffff));
+
+ return;
+ }
+ }
+
+ targetIndex = writer.InstructionPointer;
+ delta = targetIndex - branchIndex;
+
+ writer.WriteInstructionAt(branchIndex, encoding | (uint)(delta & 0x3ffffff));
+ WriteTailCallConstant(context, ref asm, targetAddress);
+ }
+ else
+ {
+ // Conditional branch.
+
+ uint branchMask = 0x7ffff;
+ int branchMax = (int)(branchMask + 1) / 2;
+
+ if (targets.TryGetValue(targetAddress, out targetIndex))
+ {
+ delta = targetIndex - branchIndex;
+
+ if (delta >= -branchMax && delta < branchMax)
+ {
+ writer.WriteInstructionAt(branchIndex, encoding | (uint)((delta & branchMask) << 5));
+
+ return;
+ }
+ }
+
+ targetIndex = writer.InstructionPointer;
+ delta = targetIndex - branchIndex;
+
+ if (delta >= -branchMax && delta < branchMax)
+ {
+ writer.WriteInstructionAt(branchIndex, encoding | (uint)((delta & branchMask) << 5));
+ WriteTailCallConstant(context, ref asm, targetAddress);
+ }
+ else
+ {
+ // If the branch target is too far away, we use a regular unconditional branch
+ // instruction instead which has a much higher range.
+ // We branch directly to the end of the function, where we put the conditional branch,
+ // and then branch back to the next instruction or return the branch target depending
+ // on the branch being taken or not.
+
+ uint branchInst = 0x14000000u | ((uint)delta & 0x3ffffff);
+ Debug.Assert((int)(branchInst << 6) >> 4 == delta * 4);
+
+ writer.WriteInstructionAt(branchIndex, branchInst);
+
+ int movedBranchIndex = writer.InstructionPointer;
+
+ writer.WriteInstruction(0u); // Placeholder
+ asm.B((branchIndex + 1 - writer.InstructionPointer) * 4);
+
+ delta = writer.InstructionPointer - movedBranchIndex;
+
+ writer.WriteInstructionAt(movedBranchIndex, encoding | (uint)((delta & branchMask) << 5));
+ WriteTailCallConstant(context, ref asm, targetAddress);
+ }
+ }
+
+ Debug.Assert(name == InstName.B || name == InstName.Cbnz, $"Unknown branch instruction \"{name}\".");
+ }
+
+ private static void RewriteCallInstructionWithTarget(in Context context, uint targetAddress, uint nextAddress, int branchIndex)
+ {
+ CodeWriter writer = context.Writer;
+ Assembler asm = new(writer);
+
+ WriteBranchToCurrentPosition(context, branchIndex);
+
+ asm.Mov(context.RegisterAllocator.RemapGprRegister(RegisterUtils.LrRegister), nextAddress);
+
+ context.StoreToContext();
+ InstEmitFlow.WriteCallWithGuestAddress(
+ writer,
+ ref asm,
+ context.RegisterAllocator,
+ context.TailMerger,
+ context.WriteEpilogueWithoutContext,
+ context.FuncTable,
+ context.DispatchStubPointer,
+ context.GetReservedStackOffset(),
+ nextAddress,
+ InstEmitCommon.Const((int)targetAddress));
+ context.LoadFromContext();
+
+ // Branch back to the next instruction (after the call).
+ asm.B((branchIndex + 1 - writer.InstructionPointer) * 4);
+ }
+
+ private static void RewriteIndirectBranchInstructionWithTarget(in Context context, InstName name, uint targetRegister, int branchIndex)
+ {
+ CodeWriter writer = context.Writer;
+ Assembler asm = new(writer);
+
+ WriteBranchToCurrentPosition(context, branchIndex);
+
+ using ScopedRegister target = context.RegisterAllocator.AllocateTempGprRegisterScoped();
+
+ asm.And(target.Operand, context.RegisterAllocator.RemapGprRegister((int)targetRegister), InstEmitCommon.Const(~1));
+
+ context.StoreToContext();
+
+ if ((name == InstName.Bx && targetRegister == RegisterUtils.LrRegister) ||
+ name == InstName.Ldm ||
+ name == InstName.Ldmda ||
+ name == InstName.Ldmdb ||
+ name == InstName.Ldmib)
+ {
+ // Arm32 does not have a return instruction, instead returns are implemented
+ // either using BX LR (for leaf functions), or POP { ... PC }.
+
+ asm.Mov(Register(0), target.Operand);
+
+ context.TailMerger.AddUnconditionalReturn(writer, asm);
+ }
+ else
+ {
+ InstEmitFlow.WriteCallWithGuestAddress(
+ writer,
+ ref asm,
+ context.RegisterAllocator,
+ context.TailMerger,
+ context.WriteEpilogueWithoutContext,
+ context.FuncTable,
+ context.DispatchStubPointer,
+ context.GetReservedStackOffset(),
+ 0u,
+ target.Operand,
+ isTail: true);
+ }
+ }
+
+ private static void RewriteTableBranchInstructionWithTarget(in Context context, bool halfword, uint rn, uint rm, int branchIndex)
+ {
+ CodeWriter writer = context.Writer;
+ Assembler asm = new(writer);
+
+ WriteBranchToCurrentPosition(context, branchIndex);
+
+ using ScopedRegister target = context.RegisterAllocator.AllocateTempGprRegisterScoped();
+
+ asm.Add(
+ target.Operand,
+ context.RegisterAllocator.RemapGprRegister((int)rn),
+ context.RegisterAllocator.RemapGprRegister((int)rm),
+ ArmShiftType.Lsl,
+ halfword ? 1 : 0);
+
+ InstEmitMemory.WriteAddressTranslation(context.MemoryManagerType, context.RegisterAllocator, asm, target.Operand, target.Operand);
+
+ if (halfword)
+ {
+ asm.LdrhRiUn(target.Operand, target.Operand, 0);
+ }
+ else
+ {
+ asm.LdrbRiUn(target.Operand, target.Operand, 0);
+ }
+
+ asm.Add(target.Operand, context.RegisterAllocator.RemapGprRegister(RegisterUtils.PcRegister), target.Operand, ArmShiftType.Lsl, 1);
+
+ context.StoreToContext();
+
+ InstEmitFlow.WriteCallWithGuestAddress(
+ writer,
+ ref asm,
+ context.RegisterAllocator,
+ context.TailMerger,
+ context.WriteEpilogueWithoutContext,
+ context.FuncTable,
+ context.DispatchStubPointer,
+ context.GetReservedStackOffset(),
+ 0u,
+ target.Operand,
+ isTail: true);
+ }
+
+ private static void RewriteIndirectCallInstructionWithTarget(in Context context, uint targetRegister, uint nextAddress, int branchIndex)
+ {
+ CodeWriter writer = context.Writer;
+ Assembler asm = new(writer);
+
+ WriteBranchToCurrentPosition(context, branchIndex);
+
+ using ScopedRegister target = context.RegisterAllocator.AllocateTempGprRegisterScoped();
+
+ asm.And(target.Operand, context.RegisterAllocator.RemapGprRegister((int)targetRegister), InstEmitCommon.Const(~1));
+ asm.Mov(context.RegisterAllocator.RemapGprRegister(RegisterUtils.LrRegister), nextAddress);
+
+ context.StoreToContext();
+ InstEmitFlow.WriteCallWithGuestAddress(
+ writer,
+ ref asm,
+ context.RegisterAllocator,
+ context.TailMerger,
+ context.WriteEpilogueWithoutContext,
+ context.FuncTable,
+ context.DispatchStubPointer,
+ context.GetReservedStackOffset(),
+ nextAddress & ~1u,
+ target.Operand);
+ context.LoadFromContext();
+
+ // Branch back to the next instruction (after the call).
+ asm.B((branchIndex + 1 - writer.InstructionPointer) * 4);
+ }
+
+ private static void RewriteHostCall(in Context context, InstName name, BranchType type, uint imm, uint pc, int branchIndex)
+ {
+ CodeWriter writer = context.Writer;
+ Assembler asm = new(writer);
+
+ uint encoding = writer.ReadInstructionAt(branchIndex);
+ int targetIndex = writer.InstructionPointer;
+ int delta = targetIndex - branchIndex;
+
+ writer.WriteInstructionAt(branchIndex, encoding | (uint)(delta & 0x3ffffff));
+
+ switch (type)
+ {
+ case BranchType.SyncPoint:
+ InstEmitSystem.WriteSyncPoint(context.Writer, context.RegisterAllocator, context.TailMerger, context.GetReservedStackOffset());
+ break;
+ case BranchType.SoftwareInterrupt:
+ context.StoreToContext();
+ switch (name)
+ {
+ case InstName.Bkpt:
+ InstEmitSystem.WriteBkpt(context.Writer, context.RegisterAllocator, context.TailMerger, context.GetReservedStackOffset(), pc, imm);
+ break;
+ case InstName.Svc:
+ InstEmitSystem.WriteSvc(context.Writer, context.RegisterAllocator, context.TailMerger, context.GetReservedStackOffset(), pc, imm);
+ break;
+ case InstName.Udf:
+ InstEmitSystem.WriteUdf(context.Writer, context.RegisterAllocator, context.TailMerger, context.GetReservedStackOffset(), pc, imm);
+ break;
+ }
+ context.LoadFromContext();
+ break;
+ case BranchType.ReadCntpct:
+ InstEmitSystem.WriteReadCntpct(context.Writer, context.RegisterAllocator, context.GetReservedStackOffset(), (int)imm, (int)pc);
+ break;
+ default:
+ Debug.Fail($"Invalid branch type '{type}'");
+ break;
+ }
+
+ // Branch back to the next instruction.
+ asm.B((branchIndex + 1 - writer.InstructionPointer) * 4);
+ }
+
+ private static void WriteBranchToCurrentPosition(in Context context, int branchIndex)
+ {
+ CodeWriter writer = context.Writer;
+
+ int targetIndex = writer.InstructionPointer;
+
+ if (branchIndex + 1 == targetIndex)
+ {
+ writer.RemoveLastInstruction();
+ }
+ else
+ {
+ uint encoding = writer.ReadInstructionAt(branchIndex);
+ int delta = targetIndex - branchIndex;
+
+ writer.WriteInstructionAt(branchIndex, encoding | (uint)(delta & 0x3ffffff));
+ }
+ }
+
+ private static void WriteTailCallConstant(in Context context, ref Assembler asm, uint address)
+ {
+ context.StoreToContext();
+ InstEmitFlow.WriteCallWithGuestAddress(
+ context.Writer,
+ ref asm,
+ context.RegisterAllocator,
+ context.TailMerger,
+ context.WriteEpilogueWithoutContext,
+ context.FuncTable,
+ context.DispatchStubPointer,
+ context.GetReservedStackOffset(),
+ 0u,
+ InstEmitCommon.Const((int)address),
+ isTail: true);
+ }
+
+ private static Operand Register(int register, OperandType type = OperandType.I64)
+ {
+ return new Operand(register, RegisterType.Integer, type);
+ }
+
+ public static void PrintStats()
+ {
+ }
+ }
+}