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:
committed by
waddlesplash
parent
0342b31ff8
commit
b244f10615
@@ -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;
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user