-
Notifications
You must be signed in to change notification settings - Fork 157
FEATURE: add paravirtualized clock support for guest time access #1173
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
978cfdc to
e51fca1
Compare
Hyperlight guests can now read time without expensive VM exits by using a paravirtualized clock shared between host and guest. This enables high-frequency timing operations like benchmarking, rate limiting, and timestamping with minimal overhead. Paravirtualized clocks work by having the hypervisor populate a shared memory page with clock calibration data. The guest reads this data along with the CPU's TSC to compute the current time entirely in userspace, avoiding the cost of a VM exit. Reference: https://docs.kernel.org/virt/kvm/x86/msr.html#pvclock The implementation uses the native mechanism for each hypervisor: - KVM: pvclock (MSR 0x4b564d01) - MSHV: Hyper-V Reference TSC page - WHP: Hyper-V Reference TSC page Guests have access to: - Monotonic time: nanoseconds since sandbox creation, guaranteed to never go backwards - Wall-clock time: UTC nanoseconds since Unix epoch - Local time: wall-clock adjusted for host timezone captured at sandbox creation Rust API (hyperlight_guest_bin::time): - SystemTime/Instant types mirroring std::time - DateTime type for human-readable date/time formatting - Weekday/Month enums with name() and short_name() methods C API (hyperlight_guest_capi): - POSIX-compatible: clock_gettime, gettimeofday, time - Broken-down time: gmtime_r, localtime_r, mktime, timegm - Formatting: strftime with common format specifiers The feature is gated behind `guest_time` (enabled by default) and documented in docs/guest-time.md. Note: The timezone offset is a snapshot from sandbox creation and does not update for DST transitions during the sandbox lifetime. Signed-off-by: Simon Davies <[email protected]>
e51fca1 to
324dfda
Compare
ludfjig
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First round of reviews, looks very good to me! Question: maybe we can split out all the time-related math + formatting into a separate crate?
Haven't looked in detail into everythign yet
| #[cfg(test)] | ||
| mod tests { | ||
| use core::mem::size_of; | ||
|
|
||
| use super::*; | ||
|
|
||
| #[test] | ||
| fn test_kvm_pvclock_size() { | ||
| // KVM pvclock struct must be exactly 32 bytes | ||
| assert_eq!(size_of::<KvmPvclockVcpuTimeInfo>(), 32); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_hv_reference_tsc_size() { | ||
| // Hyper-V reference TSC page must be exactly 4KB | ||
| assert_eq!(size_of::<HvReferenceTscPage>(), 4096); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_guest_clock_region_size() { | ||
| // GuestClockRegion should be 32 bytes (4 x u64 equivalent: 3 x u64 + i32 + u32) | ||
| assert_eq!(size_of::<GuestClockRegion>(), 32); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_clock_type_conversion() { | ||
| assert_eq!(ClockType::from(0u64), ClockType::None); | ||
| assert_eq!(ClockType::from(1u64), ClockType::KvmPvclock); | ||
| assert_eq!(ClockType::from(2u64), ClockType::HyperVReferenceTsc); | ||
| assert_eq!(ClockType::from(99u64), ClockType::None); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_guest_clock_region_default() { | ||
| let region = GuestClockRegion::default(); | ||
| assert!(!region.is_available()); | ||
| assert_eq!(region.get_clock_type(), ClockType::None); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think most of these can be const-time assertions instead of tests
| #[inline] | ||
| fn rdtsc() -> u64 { | ||
| #[cfg(target_arch = "x86_64")] | ||
| { | ||
| let lo: u32; | ||
| let hi: u32; | ||
| // SAFETY: RDTSC is always available on x86_64 | ||
| unsafe { | ||
| core::arch::asm!( | ||
| "rdtsc", | ||
| out("eax") lo, | ||
| out("edx") hi, | ||
| options(nostack, nomem, preserves_flags) | ||
| ); | ||
| } | ||
| ((hi as u64) << 32) | (lo as u64) | ||
| } | ||
| #[cfg(not(target_arch = "x86_64"))] | ||
| { | ||
| 0 // TSC not available on non-x86_64 architectures | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can replace this with https://doc.rust-lang.org/core/arch/x86/fn._rdtsc.html
| Err(crate::new_error!( | ||
| "Paravirtualized clock setup not implemented for this hypervisor", | ||
| )) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: should this default implementation be removed?
|
|
||
| /// Returns true if a clock is configured. | ||
| pub fn is_available(&self) -> bool { | ||
| self.clock_page_ptr != 0 && self.clock_type != ClockType::None as u64 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's my understand that this is always available, so do we really need is_available and is_clock_available? Correct me if I am wrong.
Hyperlight guests can now read time without expensive VM exits by using a paravirtualized clock shared between host and guest. This enables high-frequency timing operations like benchmarking, rate limiting, and timestamping with minimal overhead.
Paravirtualized clocks work by having the hypervisor populate a shared memory page with clock calibration data. The guest reads this data along with the CPU's TSC to compute the current time entirely in userspace, avoiding the cost of a VM exit.
Reference: https://docs.kernel.org/virt/kvm/x86/msr.html#pvclock
The implementation uses the native mechanism for each hypervisor:
Guests have access to:
Rust API (hyperlight_guest_bin::time):
C API (hyperlight_guest_capi):
The feature is gated behind
guest_time(enabled by default) and documented in docs/guest-time.md.Note: The timezone offset is a snapshot from sandbox creation and does not update for DST transitions during the sandbox lifetime.