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);
|
||||
#else
|
||||
// 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
|
||||
// to Intel Architecture Instruction Set Extensions Programming Reference,
|
||||
// Section 3.2.4, table 3-8
|
||||
uint8 user_fpu_state[2560] _ALIGNED(64);
|
||||
// have enough space for all the registers.
|
||||
// This is sufficient for AVX, AVX-512 (which we conditionally enable)
|
||||
// and PT (may be force-enabled by the host when we are running under VMs).
|
||||
uint8 user_fpu_state[2688] _ALIGNED(64);
|
||||
#endif
|
||||
|
||||
addr_t GetFramePointer() const;
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
#include <asm_defs.h>
|
||||
|
||||
#include "asm_offsets.h"
|
||||
|
||||
|
||||
.section .rodata
|
||||
|
||||
@@ -32,3 +34,10 @@ FUNCTION_END(_xrstor)
|
||||
FUNCTION(_vzeroall):
|
||||
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();
|
||||
|
||||
// Initial thread saved state.
|
||||
static arch_thread sInitialState _ALIGNED(64);
|
||||
arch_thread gInitialState _ALIGNED(64);
|
||||
extern uint64 gFPUSaveLength;
|
||||
extern bool gHasXsave;
|
||||
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
|
||||
// part of each new thread.
|
||||
// Use 0xFFFFFFFF as the mask to save all supported state components.
|
||||
if (gHasXsave || gHasXsavec) {
|
||||
if (gHasXsavec) {
|
||||
asm volatile (
|
||||
"clts;" \
|
||||
"fninit;" \
|
||||
"fnclex;" \
|
||||
"movl $0x7,%%eax;" \
|
||||
"movl $0x0,%%edx;" \
|
||||
"movl $0xFFFFFFFF,%%eax;" \
|
||||
"movl $0xFFFFFFFF,%%edx;" \
|
||||
"xsavec64 %0"
|
||||
:: "m" (sInitialState.user_fpu_state));
|
||||
:: "m" (gInitialState.user_fpu_state));
|
||||
} else {
|
||||
asm volatile (
|
||||
"clts;" \
|
||||
"fninit;" \
|
||||
"fnclex;" \
|
||||
"movl $0x7,%%eax;" \
|
||||
"movl $0x0,%%edx;" \
|
||||
"movl $0xFFFFFFFF,%%eax;" \
|
||||
"movl $0xFFFFFFFF,%%edx;" \
|
||||
"xsave64 %0"
|
||||
:: "m" (sInitialState.user_fpu_state));
|
||||
:: "m" (gInitialState.user_fpu_state));
|
||||
}
|
||||
} else {
|
||||
asm volatile (
|
||||
@@ -198,15 +199,18 @@ arch_thread_init(kernel_args* args)
|
||||
"fninit;" \
|
||||
"fnclex;" \
|
||||
"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.
|
||||
savefpu* initialState = ((savefpu*)&sInitialState.user_fpu_state);
|
||||
savefpu* initialState = ((savefpu*)&gInitialState.user_fpu_state);
|
||||
initialState->fp_fxsave.mxcsr = 0x1F80; // __INITIAL_MXCSR__
|
||||
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_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);
|
||||
return B_OK;
|
||||
@@ -217,7 +221,7 @@ status_t
|
||||
arch_thread_init_thread_struct(Thread* 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.
|
||||
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.rflags = frame->flags;
|
||||
|
||||
uint64 userFPUSaveLength = min_c(gFPUSaveLength, sizeof(savefpu));
|
||||
|
||||
if (frame->fpu != nullptr) {
|
||||
memcpy((void*)&signalFrameData->context.uc_mcontext.fpu, frame->fpu,
|
||||
gFPUSaveLength);
|
||||
userFPUSaveLength);
|
||||
} else {
|
||||
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();
|
||||
@@ -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
|
||||
// handler for information purposes only, and are not used after the signal handling is
|
||||
// complete.
|
||||
|
||||
|
||||
frame->cs = signalFrameData->context.uc_mcontext.fpu.fp_fxsave.cs;
|
||||
frame->ss = signalFrameData->context.uc_mcontext.fpu.fp_fxsave.ss;
|
||||
|
||||
Thread* thread = thread_get_current_thread();
|
||||
|
||||
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;
|
||||
|
||||
// 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 _xrstor;
|
||||
extern addr_t _vzeroall;
|
||||
extern addr_t _xrstor_initial;
|
||||
uint64 gXsaveMask;
|
||||
uint64 gFPUSaveLength = 512;
|
||||
bool gHasXsave = false;
|
||||
@@ -1934,6 +1935,10 @@ arch_cpu_init_post_vm(kernel_args* args)
|
||||
cpuid_info cpuid;
|
||||
get_current_cpuid(&cpuid, IA32_CPUID_LEAF_XSTATE, 0);
|
||||
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);
|
||||
get_current_cpuid(&cpuid, IA32_CPUID_LEAF_XSTATE, 0);
|
||||
gFPUSaveLength = cpuid.regs.ebx;
|
||||
@@ -1945,8 +1950,17 @@ arch_cpu_init_post_vm(kernel_args* args)
|
||||
arch_altcodepatch_replace(ALTCODEPATCH_TAG_XRSTOR,
|
||||
&_xrstor, 4);
|
||||
|
||||
if ((gXsaveMask & IA32_XCR0_AVX) != 0)
|
||||
arch_altcodepatch_replace(ALTCODEPATCH_TAG_CLEAR_FPU, &_vzeroall, 3);
|
||||
if ((gXsaveMask & IA32_XCR0_AVX) != 0) {
|
||||
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",
|
||||
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, user_rsp);
|
||||
DEFINE_OFFSET_MACRO(ARCH_THREAD, arch_thread, current_stack);
|
||||
DEFINE_OFFSET_MACRO(ARCH_THREAD, arch_thread, user_fpu_state);
|
||||
#endif
|
||||
|
||||
// struct iframe
|
||||
|
||||
Reference in New Issue
Block a user