From fc81cac1a8a5e353a377dcd2cee400d9f5a3c2f3 Mon Sep 17 00:00:00 2001 From: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com> Date: Thu, 8 Oct 2026 15:12:26 -0700 Subject: [PATCH] Read XCOMP_BV once when creating an MSHV vCPU vCPU reset writes a zeroed XSAVE area that carries the partition's XCOMP_BV. The hypervisor derives XCOMP_BV from the partition's XSAVE features, which are fixed when the partition is created. MshvVm::new reads it once, and every reset reuses it. This removes one get_xsave hypercall from every restore. On mshv3 (AMD EPYC 7763, Linux 6.6 MSHV), snapshots/restore/default drops from 88 us to 66 us (-25%). Sandbox creation pays the read once, about +3% for create_initialized/default. Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com> --- CHANGELOG.md | 2 ++ .../src/hypervisor/virtual_machine/mod.rs | 4 +-- .../hypervisor/virtual_machine/mshv/x86_64.rs | 25 ++++++++----------- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17ce641ff..489e0a431 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). architecture-independent exception handling hooks. The new interfaces are significantly more liminited, but more portable; they will be extended in the future. +* MSHV: vCPU creation reads the partition's XSAVE format once. This removes + one hypercall from every restore. ### Removed * `RunPool` and the run-specific `AllocError::InvalidAlign` variant. diff --git a/src/hyperlight_host/src/hypervisor/virtual_machine/mod.rs b/src/hyperlight_host/src/hypervisor/virtual_machine/mod.rs index 9d116f3c5..e1922a1ec 100644 --- a/src/hyperlight_host/src/hypervisor/virtual_machine/mod.rs +++ b/src/hyperlight_host/src/hypervisor/virtual_machine/mod.rs @@ -110,9 +110,9 @@ pub(crate) enum HypervisorType { } /// Minimum XSAVE buffer size: 512 bytes legacy region + 64 bytes header. -/// Only used by MSHV and WHP which use compacted XSAVE format and need to +/// Only used by WHP which uses compacted XSAVE format and needs to /// validate buffer size before accessing XCOMP_BV. -#[cfg(all(target_arch = "x86_64", any(mshv3, target_os = "windows")))] +#[cfg(all(target_arch = "x86_64", target_os = "windows"))] pub(crate) const XSAVE_MIN_SIZE: usize = 576; /// Standard XSAVE buffer size (4KB) used by KVM and MSHV. diff --git a/src/hyperlight_host/src/hypervisor/virtual_machine/mshv/x86_64.rs b/src/hyperlight_host/src/hypervisor/virtual_machine/mshv/x86_64.rs index 482adc300..1407fceaf 100644 --- a/src/hyperlight_host/src/hypervisor/virtual_machine/mshv/x86_64.rs +++ b/src/hyperlight_host/src/hypervisor/virtual_machine/mshv/x86_64.rs @@ -48,7 +48,7 @@ use crate::hypervisor::virtual_machine::XSAVE_BUFFER_SIZE; use crate::hypervisor::virtual_machine::x86_64::hw_interrupts::TimerThread; use crate::hypervisor::virtual_machine::{ CreateVmError, MapMemoryError, RegisterError, RunVcpuError, UnmapMemoryError, VirtualMachine, - VmExit, XSAVE_MIN_SIZE, + VmExit, }; use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags}; #[cfg(feature = "trace_guest")] @@ -155,6 +155,8 @@ pub(crate) struct MshvVm { #[cfg(not(feature = "hw-interrupts"))] vm_fd: VmFd, vcpu_fd: VcpuFd, + /// XCOMP_BV from the XSAVE header. Fixed for the life of the partition. + xcomp_bv: [u8; 8], /// Handle to the background timer (if started). #[cfg(feature = "hw-interrupts")] timer: Option, @@ -209,12 +211,19 @@ impl MshvVm { #[cfg(feature = "hw-interrupts")] Self::init_lapic(&vcpu_fd)?; + let xsave = vcpu_fd + .get_xsave() + .map_err(|e| CreateVmError::InitializeVm(e.into()))?; + let mut xcomp_bv = [0u8; 8]; + xcomp_bv.copy_from_slice(&xsave.buffer[520..528]); + Ok(Self { #[cfg(feature = "hw-interrupts")] vm_fd: Arc::new(vm_fd), #[cfg(not(feature = "hw-interrupts"))] vm_fd, vcpu_fd, + xcomp_bv, #[cfg(feature = "hw-interrupts")] timer: None, }) @@ -569,22 +578,10 @@ impl VirtualMachine for MshvVm { } fn reset_xsave(&self) -> std::result::Result<(), RegisterError> { - let current_xsave = self - .vcpu_fd - .get_xsave() - .map_err(|e| RegisterError::GetXsave(e.into()))?; - if current_xsave.buffer.len() < XSAVE_MIN_SIZE { - // Minimum: 512 legacy + 64 header - return Err(RegisterError::XsaveSizeMismatch { - expected: XSAVE_MIN_SIZE as u32, - actual: current_xsave.buffer.len() as u32, - }); - } - let mut buf = XSave::default(); // default is zeroed 4KB buffer // Copy XCOMP_BV (offset 520-527) - preserves feature mask + compacted bit - buf.buffer[520..528].copy_from_slice(¤t_xsave.buffer[520..528]); + buf.buffer[520..528].copy_from_slice(&self.xcomp_bv); // XSAVE area layout from Intel SDM Vol. 1 Section 13.4.1: // - Bytes 0-1: FCW (x87 FPU Control Word)