diff options
| author | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-10-27 11:46:04 -0400 |
|---|---|---|
| committer | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-10-27 11:46:04 -0400 |
| commit | 11aac324010eb2fe3c0fc9dca3c196f33aaef10c (patch) | |
| tree | 92a5fbba6b08ea26c3628b1b7991fcd5ec1b55fc /src/dynarmic | |
| parent | a05231f8782178d5a074de16aaba2ae0b118ccd4 (diff) | |
dynarmic: decode ARMv8.1-8.5 instructions used by Switch 2 code
Switch 2 titles are built for a Cortex-A78C class core. Scanning one title's
.text with dynarmic's own decoder table found ~115k RETAB, ~37k LDAPR,
~25k LSE atomics, ~6.6k CAS, ~2.5k SWP plus a few CASP/XPAC/PAC/STG, none of
which dynarmic decoded (only the declarations existed).
- LSE atomics, SWP, CAS, CASP: exclusive load + op + exclusive store; on
store failure no register is written and the instruction is retried
(CheckBit terminal), so it stays atomic across cores.
- LDAPR: ordered load, like LDAR.
- Pointer authentication: identity (nothing is ever signed). RETAA/RETAB,
BRA*/BLRA*, LDRAA/LDRAB, PAC*/AUT*/XPAC*, PACGA.
- MTE: tags are always 0; STG/ST2G only write back, STZG/STZ2G zero the
granules, STGP stores the pair, IRG/GMI/ADDG/SUBG ignore tags.
tools/nx2/a64_coverage.py reports which encodings in an NSO's .text the
decoder table does not match.
Diffstat (limited to 'src/dynarmic')
6 files changed, 393 insertions, 0 deletions
diff --git a/src/dynarmic/src/dynarmic/frontend/A64/decoder/a64.inc b/src/dynarmic/src/dynarmic/frontend/A64/decoder/a64.inc index 8a3fab7ac1..ee9f0faa18 100644 --- a/src/dynarmic/src/dynarmic/frontend/A64/decoder/a64.inc +++ b/src/dynarmic/src/dynarmic/frontend/A64/decoder/a64.inc @@ -2,6 +2,24 @@ // SPDX-License-Identifier: GPL-3.0-or-later // DO NOT REORDER +// ARMv8.1+ encodings used by Switch 2 code. Kept first so they win over broader patterns. +INST(LDAPR_sz, "LDAPRB, LDAPRH, LDAPR", "zz11100010111111110000nnnnnttttt") +INST(AtomicMemOp, "LDADD/LDCLR/LDEOR/LDSET/LDSMAX/LDSMIN/LDUMAX/LDUMIN/SWP", "zz111000AR1sssssoooo00nnnnnttttt") +INST(CompareAndSwap, "CASB, CASH, CAS", "zz0010001L1sssssO11111nnnnnttttt") +INST(CompareAndSwapPair, "CASP", "0z0010000L1sssssO11111nnnnnttttt") +INST(PACAUT_reg, "PACIA/PACIB/PACDA/PACDB/AUTIA/AUTIB/AUTDA/AUTDB", "110110101100000100Zooonnnnnddddd") +INST(XPAC_1, "XPACI, XPACD", "110110101100000101000D11111ddddd") +INST(PACGA, "PACGA", "10011010110mmmmm001100nnnnnddddd") +INST(RETA, "RETAA, RETAB", "110101100101111100001M1111111111") +INST(BRA_BLRA, "BRAA/BRAB/BLRAA/BLRAB (+Z)", "1101011Z00L1111100001Mnnnnnmmmmm") +INST(LDRA, "LDRAA, LDRAB", "11111000MS1iiiiiiiiiW1nnnnnttttt") +INST(STG_family, "STG/STZG/ST2G/STZ2G/LDG", "11011001oo1iiiiiiiiixxnnnnnttttt") +INST(STGP, "STGP", "0110100xx0iiiiiiiuuuuunnnnnttttt") +INST(IRG, "IRG", "10011010110mmmmm000100nnnnnddddd") +INST(GMI, "GMI", "10011010110mmmmm000101nnnnnddddd") +INST(ADDG, "ADDG", "1001000110iiiiii00ttttnnnnnddddd") +INST(SUBG, "SUBG", "1101000110iiiiii00ttttnnnnnddddd") + INST(FMOV_3, "FMOV (vector, immediate)", "0Q00111100000abc111111defghddddd") INST(FMOV_2, "FMOV (vector, immediate)", "0Qo0111100000abc111101defghddddd") INST(MOVI, "MOVI, MVNI, ORR, BIC (vector, immediate)", "0Qo0111100000abcmmmm01defghddddd") diff --git a/src/dynarmic/src/dynarmic/frontend/A64/translate/a64_translate.cpp b/src/dynarmic/src/dynarmic/frontend/A64/translate/a64_translate.cpp index 7840c63d79..70d5098a65 100644 --- a/src/dynarmic/src/dynarmic/frontend/A64/translate/a64_translate.cpp +++ b/src/dynarmic/src/dynarmic/frontend/A64/translate/a64_translate.cpp @@ -82,6 +82,9 @@ bool TranslateSingleInstruction(IR::Block& block, LocationDescriptor descriptor, #include "dynarmic/frontend/A64/translate/impl/impl.cpp" #include "dynarmic/frontend/A64/translate/impl/impl.h" #include "dynarmic/frontend/A64/translate/impl/load_store_exclusive.cpp" +#include "dynarmic/frontend/A64/translate/impl/load_store_atomic.cpp" +#include "dynarmic/frontend/A64/translate/impl/memory_tagging.cpp" +#include "dynarmic/frontend/A64/translate/impl/pointer_authentication.cpp" #include "dynarmic/frontend/A64/translate/impl/load_store_load_literal.cpp" #include "dynarmic/frontend/A64/translate/impl/load_store_multiple_structures.cpp" #include "dynarmic/frontend/A64/translate/impl/load_store_no_allocate_pair.cpp" diff --git a/src/dynarmic/src/dynarmic/frontend/A64/translate/impl/impl.h b/src/dynarmic/src/dynarmic/frontend/A64/translate/impl/impl.h index c8afef3283..d8e6f2b739 100644 --- a/src/dynarmic/src/dynarmic/frontend/A64/translate/impl/impl.h +++ b/src/dynarmic/src/dynarmic/frontend/A64/translate/impl/impl.h @@ -326,6 +326,17 @@ struct TranslatorVisitor final { bool SWP(bool A, bool R, Reg Rs, Reg Rn, Reg Rt); bool LDAPR(Reg Rn, Reg Rt); + // ARMv8.1+ forms decoded for Switch 2 code (load_store_atomic.cpp, pointer_authentication.cpp, + // memory_tagging.cpp) + bool AtomicMemOp(Imm<2> size, bool A, bool R, Reg Rs, Imm<4> op, Reg Rn, Reg Rt); + bool LDAPR_sz(Imm<2> size, Reg Rn, Reg Rt); + bool CompareAndSwap(Imm<2> size, bool L, Reg Rs, bool o0, Reg Rn, Reg Rt); + bool CompareAndSwapPair(bool sz, bool L, Reg Rs, bool o0, Reg Rn, Reg Rt); + bool PACAUT_reg(bool Z, Imm<3> op, Reg Rn, Reg Rd); + bool BRA_BLRA(bool Z, bool L, bool M, Reg Rn, Reg Rm); + bool STG_family(Imm<2> opc, Imm<9> imm9, Imm<2> mode, Reg Rn, Reg Rt); + bool STGP(Imm<2> mode, Imm<7> imm7, Reg Rt2, Reg Rn, Reg Rt); + // Loads and stores - Load/Store register (register offset) bool STRx_reg(Imm<2> size, Imm<1> opc_1, Reg Rm, Imm<3> option, bool S, Reg Rn, Reg Rt); bool LDRx_reg(Imm<2> size, Imm<1> opc_1, Reg Rm, Imm<3> option, bool S, Reg Rn, Reg Rt); diff --git a/src/dynarmic/src/dynarmic/frontend/A64/translate/impl/load_store_atomic.cpp b/src/dynarmic/src/dynarmic/frontend/A64/translate/impl/load_store_atomic.cpp new file mode 100644 index 0000000000..eafc993988 --- /dev/null +++ b/src/dynarmic/src/dynarmic/frontend/A64/translate/impl/load_store_atomic.cpp @@ -0,0 +1,190 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// ARMv8.1 LSE atomics (LDADD/LDCLR/LDEOR/LDSET/LD{S,U}{MAX,MIN}/SWP, CAS, CASP) and +// ARMv8.3 RCpc LDAPR. +// +// Each read-modify-write is built from an exclusive load and an exclusive store. If the +// store fails (another core touched the monitor in between) no register is changed and the +// same instruction is executed again, so the operation stays atomic with multiple cores. + +#include "dynarmic/frontend/A64/translate/impl/impl.h" + +namespace Dynarmic::A64 { + +static Reg AtomicNextReg(Reg r) { + return static_cast<Reg>(static_cast<size_t>(r) + 1); +} + +static IR::U64 AtomicAddress(TranslatorVisitor& v, Reg Rn) { + return Rn == Reg::SP ? IR::U64{v.SP(64)} : IR::U64{v.X(64, Rn)}; +} + +// All-ones when value == 0, otherwise zero. +static IR::U64 AtomicMaskIfZero(TranslatorVisitor& v, IR::U64 value) { + const IR::U64 nonzero = v.ir.LogicalShiftRight(v.ir.Or(value, v.ir.Sub(v.ir.Imm64(0), value)), v.ir.Imm8(63)); + return v.ir.Sub(nonzero, v.ir.Imm64(1)); +} + +static IR::U64 AtomicSelect(TranslatorVisitor& v, IR::U64 mask, IR::U64 if_set, IR::U64 if_clear) { + return v.ir.Or(v.ir.And(if_set, mask), v.ir.AndNot(if_clear, mask)); +} + +static IR::UAny AtomicTruncate(TranslatorVisitor& v, IR::U64 value, size_t bytes) { + switch (bytes) { + case 1: + return v.ir.LeastSignificantByte(value); + case 2: + return v.ir.LeastSignificantHalf(value); + case 4: + return v.ir.LeastSignificantWord(value); + default: + return value; + } +} + +static IR::U64 AtomicLoadZext(TranslatorVisitor& v, IR::U64 address, size_t bytes, IR::AccType acctype) { + const IR::UAny value = v.ExclusiveMem(address, bytes, acctype); + return bytes == 8 ? IR::U64{value} : v.ir.ZeroExtendToLong(value); +} + +static IR::U64 AtomicSignExtend(TranslatorVisitor& v, IR::U64 value, size_t bytes) { + return bytes == 8 ? value : v.ir.SignExtendToLong(AtomicTruncate(v, value, bytes)); +} + +static IR::U64 AtomicZeroExtendReg(TranslatorVisitor& v, Reg r, size_t bytes) { + const IR::U64 value = v.X(64, r); + return bytes == 8 ? value : v.ir.ZeroExtendToLong(AtomicTruncate(v, value, bytes)); +} + +// Writes `value` to `r` only when the exclusive store succeeded (ok_mask all-ones). +static void AtomicCommitReg(TranslatorVisitor& v, Reg r, IR::U64 ok_mask, IR::U64 value) { + if (r == Reg::ZR) { + return; + } + v.X(64, r, AtomicSelect(v, ok_mask, value, v.X(64, r))); +} + +// Ends the block: continue with the next instruction on success, retry this one on failure. +static bool AtomicFinish(TranslatorVisitor& v, IR::U32 status) { + v.ir.SetCheckBit(v.ir.IsZero(status)); + v.ir.SetTerm(IR::Term::CheckBit{IR::Term::LinkBlock{v.ir.current_location->AdvancePC(4)}, + IR::Term::LinkBlock{*v.ir.current_location}}); + return false; +} + +static IR::AccType AtomicAccType(bool acquire, bool release) { + return acquire || release ? IR::AccType::ORDERED : IR::AccType::ATOMIC; +} + +bool TranslatorVisitor::AtomicMemOp(Imm<2> size, bool A, bool R, Reg Rs, Imm<4> op, Reg Rn, Reg Rt) { + const size_t bytes = size_t{1} << size.ZeroExtend<size_t>(); + const size_t opv = op.ZeroExtend<size_t>(); + if (opv > 0b1000) { + return UnallocatedEncoding(); + } + + const IR::U64 address = AtomicAddress(*this, Rn); + const IR::AccType acctype = AtomicAccType(A, R); + const IR::U64 old = AtomicLoadZext(*this, address, bytes, acctype); + const IR::U64 s = AtomicZeroExtendReg(*this, Rs, bytes); + + IR::U64 result; + switch (opv) { + case 0b0000: // LDADD + result = ir.Add(old, s); + break; + case 0b0001: // LDCLR + result = ir.AndNot(old, s); + break; + case 0b0010: // LDEOR + result = ir.Eor(old, s); + break; + case 0b0011: // LDSET + result = ir.Or(old, s); + break; + case 0b0100: // LDSMAX + result = ir.MaxSigned(AtomicSignExtend(*this, old, bytes), AtomicSignExtend(*this, s, bytes)); + break; + case 0b0101: // LDSMIN + result = ir.MinSigned(AtomicSignExtend(*this, old, bytes), AtomicSignExtend(*this, s, bytes)); + break; + case 0b0110: // LDUMAX + result = ir.MaxUnsigned(old, s); + break; + case 0b0111: // LDUMIN + result = ir.MinUnsigned(old, s); + break; + default: // SWP + result = s; + break; + } + + const IR::U32 status = ExclusiveMem(address, bytes, acctype, AtomicTruncate(*this, result, bytes)); + const IR::U64 ok = AtomicMaskIfZero(*this, ir.ZeroExtendToLong(status)); + AtomicCommitReg(*this, Rt, ok, old); + return AtomicFinish(*this, status); +} + +bool TranslatorVisitor::LDAPR_sz(Imm<2> size, Reg Rn, Reg Rt) { + const size_t bytes = size_t{1} << size.ZeroExtend<size_t>(); + const size_t regsize = bytes == 8 ? 64 : 32; + const IR::UAny data = Mem(AtomicAddress(*this, Rn), bytes, IR::AccType::ORDERED); + X(regsize, Rt, ZeroExtend(data, regsize)); + return true; +} + +bool TranslatorVisitor::CompareAndSwap(Imm<2> size, bool L, Reg Rs, bool o0, Reg Rn, Reg Rt) { + const size_t bytes = size_t{1} << size.ZeroExtend<size_t>(); + const IR::U64 address = AtomicAddress(*this, Rn); + const IR::AccType acctype = AtomicAccType(L, o0); + + const IR::U64 old = AtomicLoadZext(*this, address, bytes, acctype); + const IR::U64 expected = AtomicZeroExtendReg(*this, Rs, bytes); + const IR::U64 desired = AtomicZeroExtendReg(*this, Rt, bytes); + const IR::U64 equal = AtomicMaskIfZero(*this, ir.Eor(old, expected)); + const IR::U64 result = AtomicSelect(*this, equal, desired, old); + + const IR::U32 status = ExclusiveMem(address, bytes, acctype, AtomicTruncate(*this, result, bytes)); + const IR::U64 ok = AtomicMaskIfZero(*this, ir.ZeroExtendToLong(status)); + AtomicCommitReg(*this, Rs, ok, old); + return AtomicFinish(*this, status); +} + +bool TranslatorVisitor::CompareAndSwapPair(bool sz, bool L, Reg Rs, bool o0, Reg Rn, Reg Rt) { + if ((static_cast<size_t>(Rs) & 1) != 0 || (static_cast<size_t>(Rt) & 1) != 0) { + return UnallocatedEncoding(); + } + const Reg Rs2 = AtomicNextReg(Rs); + const Reg Rt2 = AtomicNextReg(Rt); + const IR::U64 address = AtomicAddress(*this, Rn); + const IR::AccType acctype = AtomicAccType(L, o0); + + if (!sz) { + // Two 32-bit words, accessed as one 64-bit exclusive. + const IR::U64 old = ExclusiveMem(address, 8, acctype); + const IR::U64 expected = ir.Pack2x32To1x64(X(32, Rs), X(32, Rs2)); + const IR::U64 desired = ir.Pack2x32To1x64(X(32, Rt), X(32, Rt2)); + const IR::U64 equal = AtomicMaskIfZero(*this, ir.Eor(old, expected)); + const IR::U32 status = ExclusiveMem(address, 8, acctype, AtomicSelect(*this, equal, desired, old)); + const IR::U64 ok = AtomicMaskIfZero(*this, ir.ZeroExtendToLong(status)); + AtomicCommitReg(*this, Rs, ok, ir.ZeroExtendToLong(ir.LeastSignificantWord(old))); + AtomicCommitReg(*this, Rs2, ok, ir.LogicalShiftRight(old, ir.Imm8(32))); + return AtomicFinish(*this, status); + } + + const IR::U128 old = ExclusiveMem(address, 16, acctype); + const IR::U64 old_lo = ir.VectorGetElement(64, old, 0); + const IR::U64 old_hi = ir.VectorGetElement(64, old, 1); + const IR::U64 diff = ir.Or(ir.Eor(old_lo, X(64, Rs)), ir.Eor(old_hi, X(64, Rs2))); + const IR::U64 equal = AtomicMaskIfZero(*this, diff); + const IR::U64 new_lo = AtomicSelect(*this, equal, X(64, Rt), old_lo); + const IR::U64 new_hi = AtomicSelect(*this, equal, X(64, Rt2), old_hi); + const IR::U32 status = ExclusiveMem(address, 16, acctype, ir.Pack2x64To1x128(new_lo, new_hi)); + const IR::U64 ok = AtomicMaskIfZero(*this, ir.ZeroExtendToLong(status)); + AtomicCommitReg(*this, Rs, ok, old_lo); + AtomicCommitReg(*this, Rs2, ok, old_hi); + return AtomicFinish(*this, status); +} + +} // namespace Dynarmic::A64 diff --git a/src/dynarmic/src/dynarmic/frontend/A64/translate/impl/memory_tagging.cpp b/src/dynarmic/src/dynarmic/frontend/A64/translate/impl/memory_tagging.cpp new file mode 100644 index 0000000000..9c6bcb7872 --- /dev/null +++ b/src/dynarmic/src/dynarmic/frontend/A64/translate/impl/memory_tagging.cpp @@ -0,0 +1,98 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// ARMv8.5 memory tagging, with tags ignored: every allocation tag is 0, tag stores only do +// their address writeback, and the STZG forms still zero the granules they cover. + +#include "dynarmic/frontend/A64/translate/impl/impl.h" + +namespace Dynarmic::A64 { + +static IR::U64 TagBase(TranslatorVisitor& v, Reg Rn) { + return Rn == Reg::SP ? IR::U64{v.SP(64)} : IR::U64{v.X(64, Rn)}; +} + +static void TagSetBase(TranslatorVisitor& v, Reg Rn, IR::U64 value) { + if (Rn == Reg::SP) { + v.SP(64, value); + } else { + v.X(64, Rn, value); + } +} + +bool TranslatorVisitor::STG_family(Imm<2> opc, Imm<9> imm9, Imm<2> mode, Reg Rn, Reg Rt) { + const size_t opcv = opc.ZeroExtend<size_t>(); + const size_t modev = mode.ZeroExtend<size_t>(); + if (modev == 0b00) { + // LDG (opc 01) leaves Xt as-is since tags are 0; STZGM/STGM/LDGM are EL1-only. + if (opcv == 0b01) { + return true; + } + return UnallocatedEncoding(); + } + + const s64 offset = imm9.SignExtend<s64>() << 4; + const IR::U64 base = TagBase(*this, Rn); + const IR::U64 offset_address = ir.Add(base, ir.Imm64(static_cast<u64>(offset))); + const IR::U64 address = modev == 0b01 ? base : offset_address; // 01 = post-index + + const bool zero = (opcv & 1) != 0; // STZG, STZ2G + const size_t granules = (opcv & 2) ? 2 : 1; // ST2G, STZ2G + if (zero) { + const IR::U64 zero64 = ir.Imm64(0); + for (size_t i = 0; i < granules * 2; ++i) { + Mem(ir.Add(address, ir.Imm64(i * 8)), 8, IR::AccType::NORMAL, zero64); + } + } + (void)Rt; + if (modev != 0b10) { + TagSetBase(*this, Rn, offset_address); + } + return true; +} + +bool TranslatorVisitor::STGP(Imm<2> mode, Imm<7> imm7, Reg Rt2, Reg Rn, Reg Rt) { + const size_t modev = mode.ZeroExtend<size_t>(); + if (modev == 0b00) { + return UnallocatedEncoding(); + } + const s64 offset = imm7.SignExtend<s64>() << 4; + const IR::U64 base = TagBase(*this, Rn); + const IR::U64 offset_address = ir.Add(base, ir.Imm64(static_cast<u64>(offset))); + const IR::U64 address = modev == 0b01 ? base : offset_address; + Mem(address, 8, IR::AccType::NORMAL, X(64, Rt)); + Mem(ir.Add(address, ir.Imm64(8)), 8, IR::AccType::NORMAL, X(64, Rt2)); + if (modev != 0b10) { + TagSetBase(*this, Rn, offset_address); + } + return true; +} + +bool TranslatorVisitor::IRG(Reg Rm, Reg Rn, Reg Rd) { + (void)Rm; + TagSetBase(*this, Rd, TagBase(*this, Rn)); + return true; +} + +bool TranslatorVisitor::GMI(Reg Rm, Reg Rn, Reg Rd) { + (void)Rn; + // Tag of Xn is always 0, so the exclusion mask gains bit 0. + X(64, Rd, ir.Or(X(64, Rm), ir.Imm64(1))); + return true; +} + +bool TranslatorVisitor::ADDG(Imm<6> offset_imm, Imm<4> tag_offset, Reg Rn, Reg Rd) { + (void)tag_offset; + const u64 offset = offset_imm.ZeroExtend<u64>() << 4; + TagSetBase(*this, Rd, ir.Add(TagBase(*this, Rn), ir.Imm64(offset))); + return true; +} + +bool TranslatorVisitor::SUBG(Imm<6> offset_imm, Imm<4> tag_offset, Reg Rn, Reg Rd) { + (void)tag_offset; + const u64 offset = offset_imm.ZeroExtend<u64>() << 4; + TagSetBase(*this, Rd, ir.Sub(TagBase(*this, Rn), ir.Imm64(offset))); + return true; +} + +} // namespace Dynarmic::A64 diff --git a/src/dynarmic/src/dynarmic/frontend/A64/translate/impl/pointer_authentication.cpp b/src/dynarmic/src/dynarmic/frontend/A64/translate/impl/pointer_authentication.cpp new file mode 100644 index 0000000000..e36942c942 --- /dev/null +++ b/src/dynarmic/src/dynarmic/frontend/A64/translate/impl/pointer_authentication.cpp @@ -0,0 +1,73 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// ARMv8.3 pointer authentication, implemented as the identity: PAC* never adds a code and +// AUT*/XPAC* have nothing to strip, so signed pointers are just plain pointers. The HINT-space +// forms (PACIASP, AUTIBSP, ...) already decode as HINT/NOP. + +#include "dynarmic/frontend/A64/translate/impl/impl.h" + +namespace Dynarmic::A64 { + +bool TranslatorVisitor::PACAUT_reg(bool Z, Imm<3> op, Reg Rn, Reg Rd) { + (void)op; + (void)Rd; + if (Z && Rn != Reg::R31) { + return UnallocatedEncoding(); + } + return true; +} + +bool TranslatorVisitor::XPAC_1(bool D, Reg Rd) { + (void)D; + (void)Rd; + return true; +} + +bool TranslatorVisitor::PACGA(Reg Rm, Reg Rn, Reg Rd) { + // Any deterministic value works, since the code is only ever compared to a recomputation. + const IR::U64 m = Rm == Reg::SP ? IR::U64{SP(64)} : IR::U64{X(64, Rm)}; + X(64, Rd, ir.And(ir.Eor(X(64, Rn), m), ir.Imm64(0xFFFFFFFF00000000ULL))); + return true; +} + +bool TranslatorVisitor::RETA(bool M) { + (void)M; + ir.SetPC(X(64, Reg::R30)); + ir.SetTerm(IR::Term::PopRSBHint{}); + return false; +} + +bool TranslatorVisitor::BRA_BLRA(bool Z, bool L, bool M, Reg Rn, Reg Rm) { + (void)M; + if (!Z && Rm != Reg::R31) { + return UnallocatedEncoding(); + } + const auto target = X(64, Rn); + if (L) { + X(64, Reg::R30, ir.Imm64(ir.PC() + 4)); + ir.PushRSB(ir.current_location->AdvancePC(4)); + } + ir.SetPC(target); + ir.SetTerm(IR::Term::FastDispatchHint{}); + return false; +} + +bool TranslatorVisitor::LDRA(bool M, bool S, Imm<9> imm9, bool W, Reg Rn, Reg Rt) { + (void)M; + const s64 offset = concatenate(Imm<1>{S ? 1u : 0u}, imm9).SignExtend<s64>() << 3; + const IR::U64 base = Rn == Reg::SP ? IR::U64{SP(64)} : IR::U64{X(64, Rn)}; + const IR::U64 address = ir.Add(base, ir.Imm64(static_cast<u64>(offset))); + const IR::U64 data = Mem(address, 8, IR::AccType::NORMAL); + if (W) { + if (Rn == Reg::SP) { + SP(64, address); + } else { + X(64, Rn, address); + } + } + X(64, Rt, data); + return true; +} + +} // namespace Dynarmic::A64 |
