[A64] Inline LVL/LVR using NEON TBL instead of C helpers

Replace CallNativeSafe round-trips with inline TBL shuffles. LVL uses
a single-register TBL where out-of-range indices naturally produce
zero. LVR uses a 2-register TBL over {zeros, mem} so indices 0-15 map
to zeros and 16-31 map to memory bytes.
This commit is contained in:
Herman S.
2026-03-24 15:48:08 +09:00
parent a1e333223e
commit 2fe20d8d4f

View File

@@ -1824,15 +1824,33 @@ EMITTER_OPCODE_TABLE(OPCODE_UNPACK, UNPACK);
// ============================================================================
struct LVL_V128 : Sequence<LVL_V128, I<OPCODE_LVL, V128Op, I64Op>> {
static void Emit(A64Emitter& e, const EmitArgType& i) {
// Inline LVL using TBL. The bswap-within-lanes base pattern plus the
// address offset gives a TBL control vector. Indices >= 16 naturally
// produce zero from TBL, which is the correct LVL behaviour.
auto addr = ComputeMemoryAddress(e, i.src1);
int d = i.dest.reg().getIdx();
// x1 = host address = membase + guest_addr
e.add(e.x1, e.GetMembaseReg(), addr);
// x2 = result pointer (scratch area)
e.add(e.x2, e.sp, static_cast<uint32_t>(StackLayout::GUEST_SCRATCH));
e.CallNativeSafe(reinterpret_cast<void*>(EmulateLVL));
e.ldr(QReg(d),
ptr(e.sp, static_cast<uint32_t>(StackLayout::GUEST_SCRATCH)));
// x0 = host address
e.add(e.x0, e.GetMembaseReg(), addr);
// w17 = offset within 16-byte block
e.and_(e.w17, e.w0, 0xF);
// Align address down to 16-byte boundary and load.
e.and_(e.x0, e.x0, ~0xFull);
e.ldr(QReg(0), ptr(e.x0));
// Build bswap-within-lanes base pattern in v1:
// {3,2,1,0, 7,6,5,4, 11,10,9,8, 15,14,13,12}
e.mov(e.x0, 0x0405060700010203ull);
e.fmov(DReg(1), e.x0);
e.mov(e.x0, 0x0C0D0E0F08090A0Bull);
e.ins(VReg(1).d2[1], e.x0);
// ctrl = base + offset; TBL gives 0 for ctrl >= 16.
e.dup(VReg(2).b16, e.w17);
e.add(VReg(1).b16, VReg(1).b16, VReg(2).b16);
// Single-register TBL: dest[i] = mem[ctrl[i]] or 0.
e.tbl(VReg(d).b16, VReg(0).b16, 1, VReg(1).b16);
}
};
EMITTER_OPCODE_TABLE(OPCODE_LVL, LVL_V128);
@@ -1842,13 +1860,35 @@ EMITTER_OPCODE_TABLE(OPCODE_LVL, LVL_V128);
// ============================================================================
struct LVR_V128 : Sequence<LVR_V128, I<OPCODE_LVR, V128Op, I64Op>> {
static void Emit(A64Emitter& e, const EmitArgType& i) {
// Inline LVR using a 2-register TBL. Table = {zeros, mem}.
// Indices 0-15 read zeros (from v0), 16-31 read mem (from v1).
// base + offset produces indices > 15 exactly where LVR should output
// the memory bytes, and <= 15 where it should output zero.
// When offset == 0, all indices are 0-15 → all zeros, which is correct.
auto addr = ComputeMemoryAddress(e, i.src1);
int d = i.dest.reg().getIdx();
e.add(e.x1, e.GetMembaseReg(), addr);
e.add(e.x2, e.sp, static_cast<uint32_t>(StackLayout::GUEST_SCRATCH));
e.CallNativeSafe(reinterpret_cast<void*>(EmulateLVR));
e.ldr(QReg(d),
ptr(e.sp, static_cast<uint32_t>(StackLayout::GUEST_SCRATCH)));
// x0 = host address
e.add(e.x0, e.GetMembaseReg(), addr);
// w17 = offset
e.and_(e.w17, e.w0, 0xF);
// Align and load. v0=zeros (table reg 0), v1=mem (table reg 1).
e.movi(VReg(0).d2, 0);
e.and_(e.x0, e.x0, ~0xFull);
e.ldr(QReg(1), ptr(e.x0));
// Build base pattern in v2.
e.mov(e.x0, 0x0405060700010203ull);
e.fmov(DReg(2), e.x0);
e.mov(e.x0, 0x0C0D0E0F08090A0Bull);
e.ins(VReg(2).d2[1], e.x0);
// ctrl = base + offset.
e.dup(VReg(3).b16, e.w17);
e.add(VReg(2).b16, VReg(2).b16, VReg(3).b16);
// 2-register TBL over {v0, v1}.
e.tbl(VReg(d).b16, VReg(0).b16, 2, VReg(2).b16);
}
};
EMITTER_OPCODE_TABLE(OPCODE_LVR, LVR_V128);