From e8d27ee71f032cb04129240e32397ade377ed563 Mon Sep 17 00:00:00 2001 From: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com> Date: Thu, 9 Apr 2026 10:51:10 -0700 Subject: [PATCH 1/2] mshv: use VP register page for RIP/RAX writes in run_vcpu Replace set_reg() hypercalls with direct VP register page writes for the RIP increment on IO exits and the RAX write on IO-in (hw-interrupts). The VP register page is a shared memory page between userspace and the hypervisor that is picked up automatically on the next DISPATCH_VP, eliminating one hypercall per IO exit. Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com> --- .../src/hypervisor/virtual_machine/mod.rs | 2 + .../hypervisor/virtual_machine/mshv/x86_64.rs | 59 +++++++++++-------- 2 files changed, 38 insertions(+), 23 deletions(-) diff --git a/src/hyperlight_host/src/hypervisor/virtual_machine/mod.rs b/src/hyperlight_host/src/hypervisor/virtual_machine/mod.rs index ecb19a09f..be85d313c 100644 --- a/src/hyperlight_host/src/hypervisor/virtual_machine/mod.rs +++ b/src/hyperlight_host/src/hypervisor/virtual_machine/mod.rs @@ -200,6 +200,8 @@ pub enum RunVcpuError { GetDr6(HypervisorError), #[error("Increment RIP failed: {0}")] IncrementRip(HypervisorError), + #[error("VP register page not available")] + NoVpRegisterPage, #[error("Parse GPA access info failed")] ParseGpaAccessInfo, #[error("Unknown error: {0}")] 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 27f024ca6..c669afe57 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 @@ -26,17 +26,16 @@ use mshv_bindings::LapicState; #[cfg(gdb)] use mshv_bindings::{DebugRegisters, hv_message_type_HVMSG_X64_EXCEPTION_INTERCEPT}; use mshv_bindings::{ - FloatingPointUnit, SpecialRegisters, StandardRegisters, XSave, hv_message_type, - hv_message_type_HVMSG_GPA_INTERCEPT, hv_message_type_HVMSG_UNMAPPED_GPA, + FloatingPointUnit, HV_X64_REGISTER_CLASS_IP, SpecialRegisters, StandardRegisters, XSave, + hv_message_type, hv_message_type_HVMSG_GPA_INTERCEPT, hv_message_type_HVMSG_UNMAPPED_GPA, hv_message_type_HVMSG_X64_HALT, hv_message_type_HVMSG_X64_IO_PORT_INTERCEPT, hv_partition_property_code_HV_PARTITION_PROPERTY_SYNTHETIC_PROC_FEATURES, - hv_partition_synthetic_processor_features, hv_register_assoc, - hv_register_name_HV_X64_REGISTER_RIP, hv_register_value, mshv_create_partition_v2, - mshv_user_mem_region, + hv_partition_synthetic_processor_features, mshv_create_partition_v2, mshv_user_mem_region, }; #[cfg(feature = "hw-interrupts")] use mshv_bindings::{ - hv_interrupt_type_HV_X64_INTERRUPT_TYPE_FIXED, hv_register_name_HV_X64_REGISTER_RAX, + HV_X64_REGISTER_CLASS_GENERAL, hv_interrupt_type_HV_X64_INTERRUPT_TYPE_FIXED, + hv_register_assoc, hv_register_value, }; #[cfg(feature = "hw-interrupts")] use mshv_ioctls::InterruptRequest; @@ -219,16 +218,21 @@ impl VirtualMachine for MshvVm { let instruction_length = io_message.header.instruction_length() as u64; let is_write = io_message.header.intercept_access_type != 0; - // mshv, unlike kvm, does not automatically increment RIP - self.vcpu_fd - .set_reg(&[hv_register_assoc { - name: hv_register_name_HV_X64_REGISTER_RIP, - value: hv_register_value { - reg64: rip + instruction_length, - }, - ..Default::default() - }]) - .map_err(|e| RunVcpuError::IncrementRip(e.into()))?; + // mshv, unlike kvm, does not automatically increment RIP. + { + let page = self + .vcpu_fd + .get_vp_reg_page() + .ok_or(RunVcpuError::NoVpRegisterPage)?; + // SAFETY: The register page is a valid mmap'd page + // from create_vcpu and is always populated after a + // vcpu run returns an intercept message. + unsafe { + (*page.0).__bindgen_anon_1.__bindgen_anon_1.rip = + rip + instruction_length; + (*page.0).dirty |= 1 << HV_X64_REGISTER_CLASS_IP; + } + } // VmAction::Halt always means "I'm done", regardless // of whether a timer is active. @@ -253,13 +257,22 @@ impl VirtualMachine for MshvVm { } else if let Some(val) = super::super::x86_64::hw_interrupts::handle_io_in(port_number) { - self.vcpu_fd - .set_reg(&[hv_register_assoc { - name: hv_register_name_HV_X64_REGISTER_RAX, - value: hv_register_value { reg64: val }, - ..Default::default() - }]) - .map_err(|e| RunVcpuError::Unknown(e.into()))?; + let page = self + .vcpu_fd + .get_vp_reg_page() + .ok_or(RunVcpuError::NoVpRegisterPage)?; + // SAFETY: The register page is a valid mmap'd page + // from create_vcpu and is always populated after a + // vcpu run returns an intercept message. + unsafe { + (*page.0) + .__bindgen_anon_1 + .__bindgen_anon_1 + .__bindgen_anon_1 + .__bindgen_anon_1 + .rax = val; + (*page.0).dirty |= 1 << HV_X64_REGISTER_CLASS_GENERAL; + } continue; } } From 45d09f299c9fc8eb18d237808c706ce1b8fc8216 Mon Sep 17 00:00:00 2001 From: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com> Date: Wed, 29 Apr 2026 12:51:51 -0700 Subject: [PATCH 2/2] Pr feedback Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com> --- .../src/hypervisor/virtual_machine/mod.rs | 2 - .../hypervisor/virtual_machine/mshv/x86_64.rs | 60 ++++++++++++------- 2 files changed, 38 insertions(+), 24 deletions(-) diff --git a/src/hyperlight_host/src/hypervisor/virtual_machine/mod.rs b/src/hyperlight_host/src/hypervisor/virtual_machine/mod.rs index be85d313c..ecb19a09f 100644 --- a/src/hyperlight_host/src/hypervisor/virtual_machine/mod.rs +++ b/src/hyperlight_host/src/hypervisor/virtual_machine/mod.rs @@ -200,8 +200,6 @@ pub enum RunVcpuError { GetDr6(HypervisorError), #[error("Increment RIP failed: {0}")] IncrementRip(HypervisorError), - #[error("VP register page not available")] - NoVpRegisterPage, #[error("Parse GPA access info failed")] ParseGpaAccessInfo, #[error("Unknown error: {0}")] 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 c669afe57..badd4f90d 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 @@ -30,12 +30,14 @@ use mshv_bindings::{ hv_message_type, hv_message_type_HVMSG_GPA_INTERCEPT, hv_message_type_HVMSG_UNMAPPED_GPA, hv_message_type_HVMSG_X64_HALT, hv_message_type_HVMSG_X64_IO_PORT_INTERCEPT, hv_partition_property_code_HV_PARTITION_PROPERTY_SYNTHETIC_PROC_FEATURES, - hv_partition_synthetic_processor_features, mshv_create_partition_v2, mshv_user_mem_region, + hv_partition_synthetic_processor_features, hv_register_assoc, + hv_register_name_HV_X64_REGISTER_RIP, hv_register_value, mshv_create_partition_v2, + mshv_user_mem_region, }; #[cfg(feature = "hw-interrupts")] use mshv_bindings::{ HV_X64_REGISTER_CLASS_GENERAL, hv_interrupt_type_HV_X64_INTERRUPT_TYPE_FIXED, - hv_register_assoc, hv_register_value, + hv_register_name_HV_X64_REGISTER_RAX, set_gp_regs_field_ptr, }; #[cfg(feature = "hw-interrupts")] use mshv_ioctls::InterruptRequest; @@ -219,19 +221,28 @@ impl VirtualMachine for MshvVm { let is_write = io_message.header.intercept_access_type != 0; // mshv, unlike kvm, does not automatically increment RIP. + if let Some(page) = self + .vcpu_fd + .get_vp_reg_page() + .filter(|p| unsafe { (*p.0).isvalid != 0 }) { - let page = self - .vcpu_fd - .get_vp_reg_page() - .ok_or(RunVcpuError::NoVpRegisterPage)?; - // SAFETY: The register page is a valid mmap'd page - // from create_vcpu and is always populated after a - // vcpu run returns an intercept message. + // SAFETY: The register page is valid (isvalid checked + // above) and populated after a vcpu run intercept. unsafe { (*page.0).__bindgen_anon_1.__bindgen_anon_1.rip = rip + instruction_length; (*page.0).dirty |= 1 << HV_X64_REGISTER_CLASS_IP; } + } else { + self.vcpu_fd + .set_reg(&[hv_register_assoc { + name: hv_register_name_HV_X64_REGISTER_RIP, + value: hv_register_value { + reg64: rip + instruction_length, + }, + ..Default::default() + }]) + .map_err(|e| RunVcpuError::IncrementRip(e.into()))?; } // VmAction::Halt always means "I'm done", regardless @@ -257,21 +268,26 @@ impl VirtualMachine for MshvVm { } else if let Some(val) = super::super::x86_64::hw_interrupts::handle_io_in(port_number) { - let page = self + if let Some(page) = self .vcpu_fd .get_vp_reg_page() - .ok_or(RunVcpuError::NoVpRegisterPage)?; - // SAFETY: The register page is a valid mmap'd page - // from create_vcpu and is always populated after a - // vcpu run returns an intercept message. - unsafe { - (*page.0) - .__bindgen_anon_1 - .__bindgen_anon_1 - .__bindgen_anon_1 - .__bindgen_anon_1 - .rax = val; - (*page.0).dirty |= 1 << HV_X64_REGISTER_CLASS_GENERAL; + .filter(|p| unsafe { (*p.0).isvalid != 0 }) + { + let vp_reg_page = page.0; + set_gp_regs_field_ptr!(vp_reg_page, rax, val); + // SAFETY: page is valid (isvalid checked above). + unsafe { + (*vp_reg_page).dirty |= + 1 << HV_X64_REGISTER_CLASS_GENERAL; + } + } else { + self.vcpu_fd + .set_reg(&[hv_register_assoc { + name: hv_register_name_HV_X64_REGISTER_RAX, + value: hv_register_value { reg64: val }, + ..Default::default() + }]) + .map_err(|e| RunVcpuError::Unknown(e.into()))?; } continue; }