using ARMeilleure.Memory; using Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64; using Ryujinx.Cpu.LightningJit.CodeGen.Arm64; using System.Collections.Generic; using System.Diagnostics; namespace Ryujinx.Cpu.LightningJit.Arm32 { static class Decoder where T : IInstEmit { public static MultiBlock DecodeMulti(CpuPreset cpuPreset, IMemoryManager memoryManager, ulong address, bool isThumb) { List blocks = new(); List branchTargets = new(); while (true) { Block block = Decode(cpuPreset, memoryManager, address, isThumb); if (!block.IsTruncated && TryGetBranchTarget(block, out ulong targetAddress)) { branchTargets.Add(targetAddress); } blocks.Add(block); if (block.IsTruncated || !HasNextBlock(block, block.EndAddress - 4UL, branchTargets)) { break; } address = block.EndAddress; } branchTargets.Sort(); SplitBlocks(blocks, branchTargets); return new(blocks); } private static bool TryGetBranchTarget(Block block, out ulong targetAddress) { // PC is 2 instructions ahead, since the end address is already one instruction after the last one, we just need to add // another instruction. ulong pc = block.EndAddress + (block.IsThumb ? 2UL : 4UL); return TryGetBranchTarget(block.Instructions[^1].Name, block.Instructions[^1].Flags, pc, block.Instructions[^1].Encoding, block.IsThumb, out targetAddress); } private static bool TryGetBranchTarget(InstName name, InstFlags flags, ulong pc, uint encoding, bool isThumb, out ulong targetAddress) { int originalOffset; switch (name) { case InstName.B: if (isThumb) { if (flags.HasFlag(InstFlags.Thumb16)) { if ((encoding & (1u << 29)) != 0) { InstImm11b16w11 inst = new(encoding); originalOffset = ImmUtils.ExtractT16SImm11Times2(inst.Imm11); } else { InstCondb24w4Imm8b16w8 inst = new(encoding); originalOffset = ImmUtils.ExtractT16SImm8Times2(inst.Imm8); } } else { if ((encoding & (1u << 12)) != 0) { InstSb26w1Imm10b16w10J1b13w1J2b11w1Imm11b0w11 inst = new(encoding); originalOffset = ImmUtils.CombineSImm24Times2(inst.Imm11, inst.Imm10, inst.J1, inst.J2, inst.S); } else { InstSb26w1Condb22w4Imm6b16w6J1b13w1J2b11w1Imm11b0w11 inst = new(encoding); originalOffset = ImmUtils.CombineSImm20Times2(inst.Imm11, inst.Imm6, inst.J1, inst.J2, inst.S); } } } else { originalOffset = ImmUtils.ExtractSImm24Times4(encoding); } targetAddress = pc + (ulong)originalOffset; Debug.Assert((targetAddress & 1) == 0); return true; case InstName.Cbnz: originalOffset = ImmUtils.ExtractT16UImm5Times2(encoding); targetAddress = pc + (ulong)originalOffset; Debug.Assert((targetAddress & 1) == 0); return true; } targetAddress = 0; return false; } private static void SplitBlocks(List blocks, List branchTargets) { int btIndex = 0; while (btIndex < branchTargets.Count) { for (int blockIndex = 0; blockIndex < blocks.Count && btIndex < branchTargets.Count; blockIndex++) { Block block = blocks[blockIndex]; ulong currentBranchTarget = branchTargets[btIndex]; while (currentBranchTarget >= block.Address && currentBranchTarget < block.EndAddress) { if (block.Address != currentBranchTarget) { (Block leftBlock, Block rightBlock) = block.SplitAtAddress(currentBranchTarget); if (leftBlock != null && rightBlock != null) { blocks.Insert(blockIndex, leftBlock); blocks[blockIndex + 1] = rightBlock; block = leftBlock; } else { // Split can only fail in thumb mode, where the instruction size is not fixed. Debug.Assert(block.IsThumb); } } btIndex++; while (btIndex < branchTargets.Count && branchTargets[btIndex] == currentBranchTarget) { btIndex++; } if (btIndex >= branchTargets.Count) { break; } currentBranchTarget = branchTargets[btIndex]; } } Debug.Assert(btIndex < int.MaxValue); btIndex++; } } private static bool HasNextBlock(in Block block, ulong pc, List branchTargets) { InstFlags lastInstFlags = block.Instructions[^1].Flags; // Thumb has separate encodings for conditional and unconditional branch instructions. if (lastInstFlags.HasFlag(InstFlags.Cond) && (block.IsThumb || (ArmCondition)(block.Instructions[^1].Encoding >> 28) < ArmCondition.Al)) { return true; } switch (block.Instructions[^1].Name) { case InstName.B: return branchTargets.Contains(pc + 4UL) || (TryGetBranchTarget(block, out ulong targetAddress) && targetAddress >= pc && targetAddress < pc + 0x1000); case InstName.Bx: case InstName.Bxj: return branchTargets.Contains(pc + 4UL); case InstName.Cbnz: case InstName.BlI: case InstName.BlxR: return true; } if (WritesToPC(block.Instructions[^1].Encoding, block.Instructions[^1].Name, lastInstFlags, block.IsThumb)) { return branchTargets.Contains(pc + 4UL); } return !block.EndsWithBranch; } private static Block Decode(CpuPreset cpuPreset, IMemoryManager memoryManager, ulong address, bool isThumb) { ulong startAddress = address; List insts = new(); uint encoding; InstMeta meta; InstFlags extraFlags = InstFlags.None; bool hasHostCall = false; bool hasHostCallSkipContext = false; bool isTruncated = false; do { if (!memoryManager.IsMapped(address)) { encoding = 0; meta = default; isTruncated = true; break; } if (isThumb) { encoding = (uint)memoryManager.Read(address) << 16; address += 2UL; extraFlags = InstFlags.Thumb16; if (!InstTableT16.TryGetMeta(encoding, cpuPreset.Version, cpuPreset.Features, out meta)) { encoding |= memoryManager.Read(address); if (InstTableT32.TryGetMeta(encoding, cpuPreset.Version, cpuPreset.Features, out meta)) { address += 2UL; extraFlags = InstFlags.None; } } } else { encoding = memoryManager.Read(address); address += 4UL; meta = InstTableA32.GetMeta(encoding, cpuPreset.Version, cpuPreset.Features); } if (meta.Name.IsSystemOrCall()) { if (!hasHostCall) { hasHostCall = InstEmitSystem.NeedsCall(meta.Name); } if (!hasHostCallSkipContext) { hasHostCallSkipContext = meta.Name.IsCall() || InstEmitSystem.NeedsCallSkipContext(meta.Name); } } insts.Add(new(encoding, meta.Name, meta.EmitFunc, meta.Flags | extraFlags)); } while (!IsControlFlow(encoding, meta.Name, meta.Flags | extraFlags, isThumb)); bool isLoopEnd = false; if (!isTruncated && IsBackwardsBranch(meta.Name, encoding)) { isLoopEnd = true; hasHostCallSkipContext = true; } return new( startAddress, address, insts, !isTruncated, hasHostCall, hasHostCallSkipContext, isTruncated, isLoopEnd, isThumb); } private static bool IsControlFlow(uint encoding, InstName name, InstFlags flags, bool isThumb) { switch (name) { case InstName.B: case InstName.BlI: case InstName.BlxR: case InstName.Bx: case InstName.Bxj: case InstName.Cbnz: case InstName.Tbb: return true; } return WritesToPC(encoding, name, flags, isThumb); } public static bool WritesToPC(uint encoding, InstName name, InstFlags flags, bool isThumb) { return (GetRegisterWriteMask(encoding, name, flags, isThumb) & (1u << RegisterUtils.PcRegister)) != 0; } private static uint GetRegisterWriteMask(uint encoding, InstName name, InstFlags flags, bool isThumb) { uint mask = 0; if (isThumb) { if (flags.HasFlag(InstFlags.Thumb16)) { if (flags.HasFlag(InstFlags.Rdn)) { mask |= 1u << RegisterUtils.ExtractRdn(flags, encoding); } if (flags.HasFlag(InstFlags.Rd)) { mask |= 1u << RegisterUtils.ExtractRdT16(flags, encoding); } Debug.Assert(!flags.HasFlag(InstFlags.RdHi)); if (IsRegisterWrite(flags, InstFlags.Rt)) { mask |= 1u << RegisterUtils.ExtractRtT16(flags, encoding); } Debug.Assert(!flags.HasFlag(InstFlags.Rt2)); if (IsRegisterWrite(flags, InstFlags.Rlist)) { mask |= (byte)(encoding >> 16); if (name == InstName.Push) { mask |= (encoding >> 10) & 0x4000; // LR } else if (name == InstName.Pop) { mask |= (encoding >> 9) & 0x8000; // PC } } Debug.Assert(!flags.HasFlag(InstFlags.WBack)); } else { if (flags.HasFlag(InstFlags.Rd)) { mask |= 1u << RegisterUtils.ExtractRdT32(flags, encoding); } if (flags.HasFlag(InstFlags.RdLo)) { mask |= 1u << RegisterUtils.ExtractRdLoT32(encoding); } if (flags.HasFlag(InstFlags.RdHi)) { mask |= 1u << RegisterUtils.ExtractRdHiT32(encoding); } if (IsRegisterWrite(flags, InstFlags.Rt) && IsRtWrite(name, encoding) && !IsR15RtEncodingSpecial(name, encoding)) { mask |= 1u << RegisterUtils.ExtractRtT32(encoding); } if (IsRegisterWrite(flags, InstFlags.Rt2) && IsRtWrite(name, encoding)) { mask |= 1u << RegisterUtils.ExtractRt2T32(encoding); } if (IsRegisterWrite(flags, InstFlags.Rlist)) { mask |= (ushort)encoding; } if (flags.HasFlag(InstFlags.WBack) && HasWriteBackT32(name, encoding)) { mask |= 1u << RegisterUtils.ExtractRn(encoding); // This is at the same bit position as A32. } } } else { if (flags.HasFlag(InstFlags.Rd)) { mask |= 1u << RegisterUtils.ExtractRd(flags, encoding); } if (flags.HasFlag(InstFlags.RdHi)) { mask |= 1u << RegisterUtils.ExtractRdHi(encoding); } if (IsRegisterWrite(flags, InstFlags.Rt) && IsRtWrite(name, encoding) && !IsR15RtEncodingSpecial(name, encoding)) { mask |= 1u << RegisterUtils.ExtractRt(encoding); } if (IsRegisterWrite(flags, InstFlags.Rt2) && IsRtWrite(name, encoding)) { mask |= 1u << RegisterUtils.ExtractRt2(encoding); } if (IsRegisterWrite(flags, InstFlags.Rlist)) { mask |= (ushort)encoding; } if (flags.HasFlag(InstFlags.WBack) && HasWriteBack(name, encoding)) { mask |= 1u << RegisterUtils.ExtractRn(encoding); } } return mask; } private static bool IsRtWrite(InstName name, uint encoding) { // Some instructions can move GPR to FP/SIMD or FP/SIMD to GPR depending on the encoding. // Detect those cases so that we can tell if we're actually doing a register write. switch (name) { case InstName.VmovD: case InstName.VmovH: case InstName.VmovS: case InstName.VmovSs: return (encoding & (1u << 20)) != 0; } return true; } private static bool HasWriteBack(InstName name, uint encoding) { if (IsLoadStoreMultiple(name)) { return (encoding & (1u << 21)) != 0; } if (IsVLDnVSTn(name)) { return (encoding & 0xf) != RegisterUtils.PcRegister; } bool w = (encoding & (1u << 21)) != 0; bool p = (encoding & (1u << 24)) != 0; return !p || w; } private static bool HasWriteBackT32(InstName name, uint encoding) { if (IsLoadStoreMultiple(name)) { return (encoding & (1u << 21)) != 0; } if (IsVLDnVSTn(name)) { return (encoding & 0xf) != RegisterUtils.PcRegister; } return (encoding & (1u << 8)) != 0; } private static bool IsLoadStoreMultiple(InstName name) { switch (name) { case InstName.Ldm: case InstName.Ldmda: case InstName.Ldmdb: case InstName.LdmE: case InstName.Ldmib: case InstName.LdmU: case InstName.Stm: case InstName.Stmda: case InstName.Stmdb: case InstName.Stmib: case InstName.StmU: case InstName.Fldmx: case InstName.Fstmx: case InstName.Vldm: case InstName.Vstm: return true; } return false; } private static bool IsVLDnVSTn(InstName name) { switch (name) { case InstName.Vld11: case InstName.Vld1A: case InstName.Vld1M: case InstName.Vld21: case InstName.Vld2A: case InstName.Vld2M: case InstName.Vld31: case InstName.Vld3A: case InstName.Vld3M: case InstName.Vld41: case InstName.Vld4A: case InstName.Vld4M: case InstName.Vst11: case InstName.Vst1M: case InstName.Vst21: case InstName.Vst2M: case InstName.Vst31: case InstName.Vst3M: case InstName.Vst41: case InstName.Vst4M: return true; } return false; } private static bool IsR15RtEncodingSpecial(InstName name, uint encoding) { if (name == InstName.Vmrs) { return ((encoding >> 16) & 0xf) == 1; } return false; } private static bool IsRegisterWrite(InstFlags flags, InstFlags testFlag) { return flags.HasFlag(testFlag) && !flags.HasFlag(InstFlags.ReadRd); } private static bool IsBackwardsBranch(InstName name, uint encoding) { if (name == InstName.B) { return ImmUtils.ExtractSImm24Times4(encoding) < 0; } return false; } } }