diff options
Diffstat (limited to 'src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/Compiler.cs')
| -rw-r--r-- | src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/Compiler.cs | 789 |
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() + { + } + } +} |
