[A64] Fix SET_NJM to update cached fpcr_vmx
This commit is contained in:
@@ -4794,31 +4794,55 @@ EMITTER_OPCODE_TABLE(OPCODE_TO_SINGLE, TOSINGLE);
|
||||
// ============================================================================
|
||||
struct SET_NJM : Sequence<SET_NJM, I<OPCODE_SET_NJM, VoidOp, I8Op>> {
|
||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
||||
// NJM (Non-Java Mode) maps to ARM64 FPCR FZ (Flush-to-Zero) bit (bit 24).
|
||||
// When NJM=1, enable flush-to-zero. When NJM=0, disable.
|
||||
// mrs x0, FPCR (op0=3, op1=3, CRn=4, CRm=4, op2=0)
|
||||
e.mrs(e.x0, 3, 3, 4, 4, 0);
|
||||
// NJM (Non-Java Mode) is a VMX/AltiVec feature (VSCR bit 16) that
|
||||
// controls flush-to-zero for vector operations. It does NOT affect
|
||||
// scalar FPU behaviour. On ARM64 this maps to FPCR.FZ (bit 24) in
|
||||
// the cached fpcr_vmx value, which EmitWithVmxFpcr loads before
|
||||
// each vector FP operation.
|
||||
auto bctx = e.GetBackendCtxReg();
|
||||
|
||||
// Toggle FZ bit in cached fpcr_vmx.
|
||||
e.ldr(e.w0, ptr(bctx, static_cast<uint32_t>(
|
||||
offsetof(A64BackendContext, fpcr_vmx))));
|
||||
if (i.src1.is_constant) {
|
||||
if (i.src1.constant()) {
|
||||
e.orr(e.x0, e.x0, (1u << 24)); // Set FZ bit
|
||||
e.orr(e.w0, e.w0, (1u << 24)); // NJM=1: set FZ
|
||||
} else {
|
||||
e.and_(e.x0, e.x0, ~(1ull << 24)); // Clear FZ bit
|
||||
e.and_(e.w0, e.w0, ~(1u << 24)); // NJM=0: clear FZ
|
||||
}
|
||||
} else {
|
||||
// Dynamic: test src1, set/clear FZ accordingly.
|
||||
auto& set_fz = e.NewCachedLabel();
|
||||
auto& done = e.NewCachedLabel();
|
||||
e.cbnz(i.src1, set_fz);
|
||||
// NJM=0: clear FZ
|
||||
e.and_(e.x0, e.x0, ~(1ull << 24));
|
||||
e.and_(e.w0, e.w0, ~(1u << 24)); // NJM=0: clear FZ
|
||||
e.b(done);
|
||||
e.L(set_fz);
|
||||
// NJM=1: set FZ
|
||||
e.orr(e.x0, e.x0, (1u << 24));
|
||||
e.orr(e.w0, e.w0, (1u << 24)); // NJM=1: set FZ
|
||||
e.L(done);
|
||||
}
|
||||
// msr FPCR, x0 (op0=3, op1=3, CRn=4, CRm=4, op2=0)
|
||||
e.msr(3, 3, 4, 4, 0, e.x0);
|
||||
e.str(e.w0, ptr(bctx, static_cast<uint32_t>(
|
||||
offsetof(A64BackendContext, fpcr_vmx))));
|
||||
|
||||
// Update kA64BackendNJMOn flag.
|
||||
e.ldr(e.w0,
|
||||
ptr(bctx, static_cast<uint32_t>(offsetof(A64BackendContext, flags))));
|
||||
if (i.src1.is_constant) {
|
||||
if (i.src1.constant()) {
|
||||
e.orr(e.w0, e.w0, 1u << kA64BackendNJMOn);
|
||||
} else {
|
||||
e.mov(e.w1, 1u << kA64BackendNJMOn);
|
||||
e.bic(e.w0, e.w0, e.w1);
|
||||
}
|
||||
} else {
|
||||
e.mov(e.w1, 1u << kA64BackendNJMOn);
|
||||
e.bic(e.w0, e.w0, e.w1);
|
||||
e.tst(i.src1, 0xFF);
|
||||
e.csel(e.w1, e.w1, e.wzr, Xbyak_aarch64::Cond::NE);
|
||||
e.orr(e.w0, e.w0, e.w1);
|
||||
}
|
||||
e.str(e.w0,
|
||||
ptr(bctx, static_cast<uint32_t>(offsetof(A64BackendContext, flags))));
|
||||
|
||||
e.ForgetFpcrMode();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -537,6 +537,115 @@ TEST_CASE("NJM_DEFAULT_ON", "[backend]") {
|
||||
memory->SystemHeapFree(stack_address);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// SET_NJM — verify NJM toggle updates backend context correctly
|
||||
// =============================================================================
|
||||
// NJM (Non-Java Mode) is a VMX feature (VSCR bit 16) that controls
|
||||
// flush-to-zero for vector FP operations. We verify that SET_NJM
|
||||
// correctly updates the cached VMX FPCR/MXCSR and the NJM flag.
|
||||
static uint32_t observed_njm_flags_after_set = 0;
|
||||
static uint32_t observed_vmx_fpcr_after_set = 0;
|
||||
|
||||
static void ReadBackendNJMState(ppc::PPCContext* ctx, void* arg0, void* arg1) {
|
||||
#if XE_ARCH_AMD64
|
||||
auto* bctx = reinterpret_cast<xe::cpu::backend::x64::X64BackendContext*>(
|
||||
reinterpret_cast<intptr_t>(ctx) -
|
||||
sizeof(xe::cpu::backend::x64::X64BackendContext));
|
||||
observed_njm_flags_after_set = bctx->flags;
|
||||
observed_vmx_fpcr_after_set = bctx->mxcsr_vmx;
|
||||
#elif XE_ARCH_ARM64
|
||||
auto* bctx = reinterpret_cast<xe::cpu::backend::a64::A64BackendContext*>(
|
||||
reinterpret_cast<intptr_t>(ctx) -
|
||||
sizeof(xe::cpu::backend::a64::A64BackendContext));
|
||||
observed_njm_flags_after_set = bctx->flags;
|
||||
observed_vmx_fpcr_after_set = bctx->fpcr_vmx;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Helper to build and run a SET_NJM test function.
|
||||
static void RunSetNJMTest(int njm_value) {
|
||||
observed_njm_flags_after_set = 0;
|
||||
observed_vmx_fpcr_after_set = 0;
|
||||
|
||||
auto memory = std::make_unique<Memory>();
|
||||
memory->Initialize();
|
||||
|
||||
std::unique_ptr<xe::cpu::backend::Backend> backend;
|
||||
#if XE_ARCH_AMD64
|
||||
backend.reset(new xe::cpu::backend::x64::X64Backend());
|
||||
#elif XE_ARCH_ARM64
|
||||
backend.reset(new xe::cpu::backend::a64::A64Backend());
|
||||
#endif
|
||||
REQUIRE(backend);
|
||||
|
||||
auto processor = std::make_unique<Processor>(memory.get(), nullptr);
|
||||
processor->Setup(std::move(backend));
|
||||
|
||||
auto* builtin_fn = processor->DefineBuiltin(
|
||||
"ReadBackendNJMState", ReadBackendNJMState, nullptr, nullptr);
|
||||
|
||||
auto module = std::make_unique<TestModule>(
|
||||
processor.get(), "Test",
|
||||
[](uint32_t address) { return address == 0x80000000; },
|
||||
[builtin_fn, njm_value](HIRBuilder& b) {
|
||||
b.SetNJM(b.LoadConstantInt8(njm_value ? 1 : 0));
|
||||
b.CallExtern(builtin_fn);
|
||||
b.Return();
|
||||
return true;
|
||||
},
|
||||
/*skip_cf_simplification=*/true);
|
||||
processor->AddModule(std::move(module));
|
||||
processor->backend()->CommitExecutableRange(0x80000000, 0x80010000);
|
||||
|
||||
auto fn = processor->ResolveFunction(0x80000000);
|
||||
REQUIRE(fn != nullptr);
|
||||
|
||||
uint32_t stack_size = 64 * 1024;
|
||||
uint32_t stack_address = memory->SystemHeapAlloc(stack_size);
|
||||
auto thread_state = std::make_unique<ThreadState>(processor.get(), 0x100,
|
||||
stack_address + stack_size);
|
||||
auto ctx = thread_state->context();
|
||||
ctx->lr = 0xBCBCBCBC;
|
||||
|
||||
fn->Call(thread_state.get(), uint32_t(ctx->lr));
|
||||
|
||||
memory->SystemHeapFree(stack_address);
|
||||
}
|
||||
|
||||
TEST_CASE("SET_NJM_ON", "[backend]") {
|
||||
RunSetNJMTest(1);
|
||||
// NJM flag should be set.
|
||||
#if XE_ARCH_AMD64
|
||||
REQUIRE((observed_njm_flags_after_set &
|
||||
(1U << xe::cpu::backend::x64::kX64BackendNJMOn)) != 0);
|
||||
// MXCSR should have FZ and DAZ set.
|
||||
REQUIRE((observed_vmx_fpcr_after_set & (1 << 15)) != 0); // FZ
|
||||
REQUIRE((observed_vmx_fpcr_after_set & (1 << 6)) != 0); // DAZ
|
||||
#elif XE_ARCH_ARM64
|
||||
REQUIRE((observed_njm_flags_after_set &
|
||||
(1U << xe::cpu::backend::a64::kA64BackendNJMOn)) != 0);
|
||||
// FPCR_VMX should have FZ (bit 24) set.
|
||||
REQUIRE((observed_vmx_fpcr_after_set & (1 << 24)) != 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST_CASE("SET_NJM_OFF", "[backend]") {
|
||||
RunSetNJMTest(0);
|
||||
// NJM flag should be cleared.
|
||||
#if XE_ARCH_AMD64
|
||||
REQUIRE((observed_njm_flags_after_set &
|
||||
(1U << xe::cpu::backend::x64::kX64BackendNJMOn)) == 0);
|
||||
// MXCSR should NOT have FZ or DAZ.
|
||||
REQUIRE((observed_vmx_fpcr_after_set & (1 << 15)) == 0);
|
||||
REQUIRE((observed_vmx_fpcr_after_set & (1 << 6)) == 0);
|
||||
#elif XE_ARCH_ARM64
|
||||
REQUIRE((observed_njm_flags_after_set &
|
||||
(1U << xe::cpu::backend::a64::kA64BackendNJMOn)) == 0);
|
||||
// FPCR_VMX should NOT have FZ (bit 24).
|
||||
REQUIRE((observed_vmx_fpcr_after_set & (1 << 24)) == 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Atomic Exchange I32
|
||||
// =============================================================================
|
||||
|
||||
Reference in New Issue
Block a user