kernel/x86_64: Enable AVX-512 when supported

Check for AVX-512 support and enable on detected CPUs.

Additionally, reserve more bytes in the kernel thread state for the
typical case of AVX + AVX512 + PT when running under a VM.

This additional state will not be copied to the userland structures
until we can determine a more future-proof way of handling x86 extended
state.

The change also includes out the newly supported registers when entering
userland. This is done by issuing a `XRSTOR` instruction based on the
stored initial FPU state.

This change should not have any visible effects on non-AVX-512 CPUs.
It has been tested on an i7-1165g7 VM with .NET's AVX-512 test suite:
https://github.com/dotnet/runtime/blob/c0d836dbe2315b310e7e099afcb50475dce1a521/src/tests/nativeaot/SmokeTests/HardwareIntrinsics/Program.cs#L398-L424

Change-Id: Iad5123e6d13dff39c0fd3957f7e8135202236460
Reviewed-on: https://review.haiku-os.org/c/haiku/+/10853
Reviewed-by: waddlesplash <[email protected]>
Reviewed-by: Adrien Destugues <[email protected]>
Tested-by: Commit checker robot <[email protected]>
This commit is contained in:
Trung Nguyen
2026-05-11 16:07:48 +00:00
committed by waddlesplash
parent 0342b31ff8
commit b244f10615
5 changed files with 51 additions and 21 deletions
@@ -60,10 +60,10 @@ struct arch_thread {
uint8 fpu_state[512] _ALIGNED(16); uint8 fpu_state[512] _ALIGNED(16);
#else #else
// floating point save point - this must be 64 byte aligned for xsave and // floating point save point - this must be 64 byte aligned for xsave and
// have enough space for all the registers, at least 2560 bytes according // have enough space for all the registers.
// to Intel Architecture Instruction Set Extensions Programming Reference, // This is sufficient for AVX, AVX-512 (which we conditionally enable)
// Section 3.2.4, table 3-8 // and PT (may be force-enabled by the host when we are running under VMs).
uint8 user_fpu_state[2560] _ALIGNED(64); uint8 user_fpu_state[2688] _ALIGNED(64);
#endif #endif
addr_t GetFramePointer() const; addr_t GetFramePointer() const;
+9
View File
@@ -6,6 +6,8 @@
#include <asm_defs.h> #include <asm_defs.h>
#include "asm_offsets.h"
.section .rodata .section .rodata
@@ -32,3 +34,10 @@ FUNCTION_END(_xrstor)
FUNCTION(_vzeroall): FUNCTION(_vzeroall):
vzeroall vzeroall
FUNCTION_END(_vzeroall) FUNCTION_END(_vzeroall)
FUNCTION(_xrstor_initial):
movabsq $gInitialState + ARCH_THREAD_user_fpu_state, %rdi
mov $0xFFFFFFFF, %eax
mov $0xFFFFFFFF, %edx
xrstor64 (%rdi)
FUNCTION_END(_xrstor_initial)
+21 -15
View File
@@ -70,7 +70,7 @@ class RestartSyscall : public AbstractTraceEntry {
extern "C" void x86_64_thread_entry(); extern "C" void x86_64_thread_entry();
// Initial thread saved state. // Initial thread saved state.
static arch_thread sInitialState _ALIGNED(64); arch_thread gInitialState _ALIGNED(64);
extern uint64 gFPUSaveLength; extern uint64 gFPUSaveLength;
extern bool gHasXsave; extern bool gHasXsave;
extern bool gHasXsavec; extern bool gHasXsavec;
@@ -172,25 +172,26 @@ arch_thread_init(kernel_args* args)
{ {
// Save one global valid FPU state; it will be copied in the arch dependent // Save one global valid FPU state; it will be copied in the arch dependent
// part of each new thread. // part of each new thread.
// Use 0xFFFFFFFF as the mask to save all supported state components.
if (gHasXsave || gHasXsavec) { if (gHasXsave || gHasXsavec) {
if (gHasXsavec) { if (gHasXsavec) {
asm volatile ( asm volatile (
"clts;" \ "clts;" \
"fninit;" \ "fninit;" \
"fnclex;" \ "fnclex;" \
"movl $0x7,%%eax;" \ "movl $0xFFFFFFFF,%%eax;" \
"movl $0x0,%%edx;" \ "movl $0xFFFFFFFF,%%edx;" \
"xsavec64 %0" "xsavec64 %0"
:: "m" (sInitialState.user_fpu_state)); :: "m" (gInitialState.user_fpu_state));
} else { } else {
asm volatile ( asm volatile (
"clts;" \ "clts;" \
"fninit;" \ "fninit;" \
"fnclex;" \ "fnclex;" \
"movl $0x7,%%eax;" \ "movl $0xFFFFFFFF,%%eax;" \
"movl $0x0,%%edx;" \ "movl $0xFFFFFFFF,%%edx;" \
"xsave64 %0" "xsave64 %0"
:: "m" (sInitialState.user_fpu_state)); :: "m" (gInitialState.user_fpu_state));
} }
} else { } else {
asm volatile ( asm volatile (
@@ -198,15 +199,18 @@ arch_thread_init(kernel_args* args)
"fninit;" \ "fninit;" \
"fnclex;" \ "fnclex;" \
"fxsaveq %0" "fxsaveq %0"
:: "m" (sInitialState.user_fpu_state)); :: "m" (gInitialState.user_fpu_state));
} }
// FNINIT does not affect MXCSR or data registers, so we reset them in the state. // FNINIT does not affect MXCSR or data registers, so we reset them in the state.
savefpu* initialState = ((savefpu*)&sInitialState.user_fpu_state); savefpu* initialState = ((savefpu*)&gInitialState.user_fpu_state);
initialState->fp_fxsave.mxcsr = 0x1F80; // __INITIAL_MXCSR__ initialState->fp_fxsave.mxcsr = 0x1F80; // __INITIAL_MXCSR__
memset(initialState->fp_fxsave.fp, 0, sizeof(initialState->fp_fxsave.fp)); memset(initialState->fp_fxsave.fp, 0, sizeof(initialState->fp_fxsave.fp));
memset(initialState->fp_fxsave.xmm, 0, sizeof(initialState->fp_fxsave.xmm)); memset(initialState->fp_fxsave.xmm, 0, sizeof(initialState->fp_fxsave.xmm));
memset(initialState->fp_ymm, 0, sizeof(initialState->fp_ymm)); // Clear the rest of the state, which should all be data registers.
char* remainingState = (char*)initialState + offsetof(savefpu, fp_ymm);
size_t remainingStateSize = sizeof(gInitialState.user_fpu_state) - offsetof(savefpu, fp_ymm);
memset(remainingState, 0, remainingStateSize);
register_generic_syscall(THREAD_SYSCALLS, arch_thread_control, 1, 0); register_generic_syscall(THREAD_SYSCALLS, arch_thread_control, 1, 0);
return B_OK; return B_OK;
@@ -217,7 +221,7 @@ status_t
arch_thread_init_thread_struct(Thread* thread) arch_thread_init_thread_struct(Thread* thread)
{ {
// Copy the initial saved FPU state to the new thread. // Copy the initial saved FPU state to the new thread.
memcpy(&thread->arch_info, &sInitialState, sizeof(arch_thread)); memcpy(&thread->arch_info, &gInitialState, sizeof(arch_thread));
// Initialise the current thread pointer. // Initialise the current thread pointer.
thread->arch_info.thread = thread; thread->arch_info.thread = thread;
@@ -370,12 +374,14 @@ arch_setup_signal_frame(Thread* thread, struct sigaction* action,
signalFrameData->context.uc_mcontext.rip = frame->ip; signalFrameData->context.uc_mcontext.rip = frame->ip;
signalFrameData->context.uc_mcontext.rflags = frame->flags; signalFrameData->context.uc_mcontext.rflags = frame->flags;
uint64 userFPUSaveLength = min_c(gFPUSaveLength, sizeof(savefpu));
if (frame->fpu != nullptr) { if (frame->fpu != nullptr) {
memcpy((void*)&signalFrameData->context.uc_mcontext.fpu, frame->fpu, memcpy((void*)&signalFrameData->context.uc_mcontext.fpu, frame->fpu,
gFPUSaveLength); userFPUSaveLength);
} else { } else {
memcpy((void*)&signalFrameData->context.uc_mcontext.fpu, memcpy((void*)&signalFrameData->context.uc_mcontext.fpu,
sInitialState.user_fpu_state, gFPUSaveLength); gInitialState.user_fpu_state, userFPUSaveLength);
} }
signalFrameData->context.uc_mcontext.fpu.fp_fxsave.fault_address = x86_read_cr2(); signalFrameData->context.uc_mcontext.fpu.fp_fxsave.fault_address = x86_read_cr2();
@@ -454,14 +460,14 @@ arch_restore_signal_frame(struct signal_frame_data* signalFrameData)
// Note: the error_code and vector fields are not restored. These are provided to the signal // Note: the error_code and vector fields are not restored. These are provided to the signal
// handler for information purposes only, and are not used after the signal handling is // handler for information purposes only, and are not used after the signal handling is
// complete. // complete.
frame->cs = signalFrameData->context.uc_mcontext.fpu.fp_fxsave.cs; frame->cs = signalFrameData->context.uc_mcontext.fpu.fp_fxsave.cs;
frame->ss = signalFrameData->context.uc_mcontext.fpu.fp_fxsave.ss; frame->ss = signalFrameData->context.uc_mcontext.fpu.fp_fxsave.ss;
Thread* thread = thread_get_current_thread(); Thread* thread = thread_get_current_thread();
memcpy(thread->arch_info.user_fpu_state, memcpy(thread->arch_info.user_fpu_state,
(void*)&signalFrameData->context.uc_mcontext.fpu, gFPUSaveLength); (void*)&signalFrameData->context.uc_mcontext.fpu, min_c(gFPUSaveLength, sizeof(savefpu)));
frame->fpu = &thread->arch_info.user_fpu_state; frame->fpu = &thread->arch_info.user_fpu_state;
// The syscall return code overwrites frame->ax with the return value of // The syscall return code overwrites frame->ax with the return value of
+16 -2
View File
@@ -89,6 +89,7 @@ extern addr_t _xsave;
extern addr_t _xsavec; extern addr_t _xsavec;
extern addr_t _xrstor; extern addr_t _xrstor;
extern addr_t _vzeroall; extern addr_t _vzeroall;
extern addr_t _xrstor_initial;
uint64 gXsaveMask; uint64 gXsaveMask;
uint64 gFPUSaveLength = 512; uint64 gFPUSaveLength = 512;
bool gHasXsave = false; bool gHasXsave = false;
@@ -1934,6 +1935,10 @@ arch_cpu_init_post_vm(kernel_args* args)
cpuid_info cpuid; cpuid_info cpuid;
get_current_cpuid(&cpuid, IA32_CPUID_LEAF_XSTATE, 0); get_current_cpuid(&cpuid, IA32_CPUID_LEAF_XSTATE, 0);
gXsaveMask |= (cpuid.regs.eax & IA32_XCR0_AVX); gXsaveMask |= (cpuid.regs.eax & IA32_XCR0_AVX);
if (x86_check_feature(IA32_FEATURE_AVX512F, FEATURE_7_EBX)) {
gXsaveMask |= cpuid.regs.eax
& (IA32_XCR0_OPMASK | IA32_XCR0_ZMM_HI256 | IA32_XCR0_HI16_ZMM);
}
call_all_cpus_sync(&enable_xsavemask, NULL); call_all_cpus_sync(&enable_xsavemask, NULL);
get_current_cpuid(&cpuid, IA32_CPUID_LEAF_XSTATE, 0); get_current_cpuid(&cpuid, IA32_CPUID_LEAF_XSTATE, 0);
gFPUSaveLength = cpuid.regs.ebx; gFPUSaveLength = cpuid.regs.ebx;
@@ -1945,8 +1950,17 @@ arch_cpu_init_post_vm(kernel_args* args)
arch_altcodepatch_replace(ALTCODEPATCH_TAG_XRSTOR, arch_altcodepatch_replace(ALTCODEPATCH_TAG_XRSTOR,
&_xrstor, 4); &_xrstor, 4);
if ((gXsaveMask & IA32_XCR0_AVX) != 0) if ((gXsaveMask & IA32_XCR0_AVX) != 0) {
arch_altcodepatch_replace(ALTCODEPATCH_TAG_CLEAR_FPU, &_vzeroall, 3); if ((gXsaveMask & ~(IA32_XCR0_X87 | IA32_XCR0_SSE | IA32_XCR0_AVX)) == 0) {
// If we are stopping at AVX, VZEROALL should suffice.
arch_altcodepatch_replace(ALTCODEPATCH_TAG_CLEAR_FPU,
&_vzeroall, 3);
} else {
// Otherwise, use XRSTOR to reset every supported state.
arch_altcodepatch_replace(ALTCODEPATCH_TAG_CLEAR_FPU,
&_xrstor_initial, 24);
}
}
dprintf("enable %s 0x%" B_PRIx64 " %" B_PRId64 "\n", dprintf("enable %s 0x%" B_PRIx64 " %" B_PRId64 "\n",
gHasXsavec ? "XSAVEC" : "XSAVE", gXsaveMask, gFPUSaveLength); gHasXsavec ? "XSAVEC" : "XSAVE", gXsaveMask, gFPUSaveLength);
@@ -55,6 +55,7 @@ dummy()
DEFINE_OFFSET_MACRO(ARCH_THREAD, arch_thread, syscall_rsp); DEFINE_OFFSET_MACRO(ARCH_THREAD, arch_thread, syscall_rsp);
DEFINE_OFFSET_MACRO(ARCH_THREAD, arch_thread, user_rsp); DEFINE_OFFSET_MACRO(ARCH_THREAD, arch_thread, user_rsp);
DEFINE_OFFSET_MACRO(ARCH_THREAD, arch_thread, current_stack); DEFINE_OFFSET_MACRO(ARCH_THREAD, arch_thread, current_stack);
DEFINE_OFFSET_MACRO(ARCH_THREAD, arch_thread, user_fpu_state);
#endif #endif
// struct iframe // struct iframe