Skip to content

WIP: CPU Profiles/Templates#7562

Draft
olivereanderson wants to merge 14 commits intocloud-hypervisor:mainfrom
olivereanderson:CPU-profiles-CPUID
Draft

WIP: CPU Profiles/Templates#7562
olivereanderson wants to merge 14 commits intocloud-hypervisor:mainfrom
olivereanderson:CPU-profiles-CPUID

Conversation

@olivereanderson
Copy link
Contributor

@olivereanderson olivereanderson commented Dec 11, 2025

This PR introduces the concept of a CPU profile, a mechanism to opt-in to restricting CPU features a guest may use which then in turn makes live migration between hosts running on different hardware more tractable. In other words we are trying to introduce Cloud Hypervisor's analogue of QEMU's CPU models.

We are looking for early feedback from the maintainers whether this seems to be heading in the right direction. Please also note that while there are a lot of lines of code here, most of it is either auto generated or static tables with CPUID descriptions.

We restrict our scope to enforcing CPUID compliance for now and leave MSR restrictions for later.

[EDIT: MSR restrictions are also work in progress on this branch, but we are not that far along with that aspect, hope to get it done within a week though].

I encourage reviewers to read the longer explanation below before starting your review. When you have read the description in its entirety you can start reviewing one commit at a time (which I have tried to make as nice as I can, although there are probably some things that could have been done better with fancy git magic).

The feature in more detail

Why do we even need this?

Recall that software is usually developed to run on a variety of processors with various features. In order for the software to dynamically discover which hardware features may be utilized one typically uses the CPUID instruction to query the CPU for information. In some cases one can also obtain CPU information via so called MSRs (model specific registers), but we will leave those out of this discussion for the time being.

Consider now the case that a guest is running some workload on host A which then gets migrated to host B where host B has a different processor than A. If this is a live migration (i.e. it is performed while the software is running), then the guest's workload can easily run into a time of check to time of use error.

Luckily hypervisors are able to manipulate what CPUID returns to guests when called which we can and will take advantage of in order to make live migration safer. Indeed if there is a subset of CPU features and properties that
are shared by all CPUs in a cluster, then if all hosts restrict themselves to that subset then live migration suddenly becomes a lot less problematic (although still not an entirely solved problem).

CPU profiles at a very high level

Using an existing CPU profile

In this PR we patch cloud hypervisor so that users may specify a CPU profile on the command line. This will in turn restrict what CPUID values the guest obtains and may thus affect functionality and performance, but may still be the best tool in the box when live migration to (subtly) different hardware is desired.

From a user perspective one simply includes the profile=<desired cpu profile> argument to the cpus parameter e.g.

--cpus boot=8,profile=sapphire-rapids

in order to utilize the Intel Sapphire rapids server profile. If the host has hardware considered to be compatible with the chosen profile, then cloud hypervisor should work just like before, with the exception that guest's may see certain other values when inspecting CPUID.

Producing a new CPU profile

New CPU profiles can be generated in a semi automated fashion. All you need to do is to run the newly introduced binary

cargo run --release -p arch --bin generate-cpu-profile --features="cpu_profile_generation" "name of my profile" > ./arch/src/x86_64/cpu_profiles/my-profile.json

on the host you are interested in being compatible with (the compatibility target) and then you need to manually edit the arch::x86_64::cpu_profile::CpuProfile enum by adding another variant and updating the data method to deserialize the pre-generated json.

The resulting CPU profile should expose a lot of the same functionality as the host where it was generated, but we apply a few extra restrictions to functionality that is either inherently incompatible with live migration, or should not be used in a cloud setting for other reasons. There is currently no way for users to opt-out of these extra restrictions neither during profile generation, nor later when the profile is loaded on a new host.

Note that we also currently only support Intel CPUs and KVM as the hypervisor, but a lot of the logic is agnostic of these things and it should be relatively easy to lift these limitations (more on that later).

The design in a bit of detail

The implementation is based on the understanding that we do not only need to work with bit sets, but we also need to manipulate some multi-bit values. Indeed, some CPUID output values indicate how many leaves there are (such as for instance leaf 0, sub-leaf 0, EAX), while others may tell you the number of sub-leaves, or the size of a state component for instance. Note that if the CPUID instruction is executed with an invalid leaf, some processors will return the data for the highest basic information leaf. In a live-migration setting this could easily lead to a time of check to time of use error!

Since we cannot simply deal with bit sets (at least to the best of our understanding) we unfortunately end up with a rather more complex solution than what we had initially hoped for.

CPU Profile policies

The idea is that we have a static list describing all known values one can obtain from CPUID (on an Intel CPU) and also such a list of the values defined by KVM (within the leaf range reserved for hypervisors).

Although AMDs CPUID descriptions mostly agree with those from Intel, there are exceptions, such as for instance which XSAVE state components corresponds to avx-512 register state. Hence in order to not complicate matters more we decided to focus on Intel for now. If/when we are to include support for AMD I recommend having a separate list describing AMDs values, even if it ends up having say 80% overlap with Intel.

Now for each of these described CPUID values we decide on a CPU profile policy. These (currently) include:

  • Overwriting the value with a set value (usually 0) for all CPU models/profiles (except host)
  • Inheriting the value from the compatibility target
  • Ignoring the value/just passing on whatever the host has to guests

From these policies we generate and serialize CPU profile data which is then later loaded and utilized to adjust
a guest's CPUID whenever the profile is in use.

Relationship with CPUID compatibility checks

A reasonable question to ask is why don't the CPU profiles only contain data relevant to whether the existing check_cpuid_compatibility checks are satisfied and let everything else just be copied over from the host?

First of all the CPUID compatibility checks only check a fixed set of CPUID entries. While it is certainly true that these checks can be extended as new CPU features appear over time, it is still a problem if you are running an older version of Cloud hypervisor (that is unaware of said new features) on a very modern CPU.

We argue that the implementation of CPU profiles as presented here is more future proof in that sense. This is because any CPUID entry/value not known to the profile will get zeroed out when the CPU profile is applied.

Furthermore we suspect that the already existing CPUID compatibility checks could be improved. CPUID leaf 0xa (Architectural Performance Monitoring Leaf) is just one example of a leaf that is not accounted for by the existing checks, but probably should be. We have verified that non-trivial values in this leaf are indeed visible to guests, but it is unlikely that this can work in the context of live migrations. Note that QEMU only makes performance counters available when the host profile is selected (this is also the case for the CPU profiles introduced in this PR).

Immediate Follow up tasks

Ideas on how to incorporate MSR restrictions

There are really many MSRs and we would prefer to avoid creating a table describing all of them (like we did for CPUID).
Instead we propose doing the following:

  1. Record all the indices returned from KVM_GET_MSR_INDEX_LIST in the CPU profile data. Then when the CPU profile is applied we use KVM_X86_SET_MSR_FILTER to deny access to the MSRs that are not among those listed in the CPU profile data.
  2. There are a few MSRs that contain CPU feature information and the relevant indices can be obtained with KVM_GET_MSR_FEATURE_INDEX_LIST. From what I have observed this returns around 21 indices which is small enough that we can manage them in a similar manner to how we dealt with CPUID. In other words we create a table describing the bits within these MSRs, together with profile policies and extend the CPU profile generation tool to record adjustments according to our specification. When the generated profile is later used, we adjust the feature MSRs accordingly.
  3. We incorporate checks that the host has MSRs compatible with the profile. With respect to the first point listed above we need to check that what KVM_GET_MSR_INDEX_LIST returns contains the indices we extracted from the profile. We can consider allowing some exceptions, in the cases where the MSRs are irrelevant due to CPUID indicating that the MSR in question is not valid anyway. We will also need a check for the feature MSRs. This latter check needs to be more thorough, but as there are not that many values in this case, we hope that this should be rather manageable.

Part of #7068.

@olivereanderson olivereanderson force-pushed the CPU-profiles-CPUID branch 3 times, most recently from 8079bee to 434f9b7 Compare December 12, 2025 16:46
@olivereanderson olivereanderson changed the title WIP: CPU Profiles/Templates WIP: arch: CPU Profiles/Templates Dec 15, 2025
@olivereanderson olivereanderson changed the title WIP: arch: CPU Profiles/Templates WIP: CPU Profiles/Templates Dec 15, 2025
Since enabling AMX tile state components affect the result returned by
`Hypervisor::get_supported_cpuid` we want this enabled prior to checking
CPUID compatibility between the source and destination VMs.

Although this is not required today, it is necessary in order for the
upcoming CPU profiles correctly, and it will also be necessary once the
check_cpuid_compatibility checks are extended to take state components
into account.

Signed-off-by: Oliver Anderson <oliver.anderson@cyberus-technology.de>
On-behalf-of: SAP oliver.anderson@sap.com
These data structures are required to define CPU profiles.

Signed-off-by: Oliver Anderson <oliver.anderson@cyberus-technology.de>
On-behalf-of: SAP oliver.anderson@sap.com
We want CPU profiles to keep a record of the hypervisor type and
cpu vendor that they are intended to work with. This is made more
convenient if all of these types implement common traits (used for
serialization).

Signed-Off-by: Oliver Anderson <oliver.anderson@cyberus-technology.de>
On-behalf-of: SAP oliver.anderson@sap.com
We introduce essential data structures together with basic
functionality that is necessary to apply a CPU profile to a host.

Signed-off-by: Oliver Anderson <oliver.anderson@cyberus-technology.de>
On-behalf-of: SAP oliver.anderson@sap.com
We integrate the CPU profile into the various configs that
ultimately get set by the user.

This quickly ends up involving multiple files, luckily Rust
helps us find which ones via compilation errors.

Signed-Off-by: Oliver Anderson <oliver.anderson@cyberus-technology.de>
On-behalf-of: SAP oliver.anderson@sap.com
If a CPU profile is configured it should result in guests seeing
a restricted subset of CPUID. This is what we finally achieve in
this commit.

Signed-off-by: Oliver Anderson <oliver.anderson@cyberus-technology.de>
On-behalf-of: SAP oliver.anderson@sap.com
We include CPU profiles corresponding to Intel Skylake and Sapphire
rapids server that we generated using our WIP CPU profile generation
tool.

Signed-of-by: Oliver Anderson <oliver.anderson@cyberus-technology.de>
On-behalf-of: SAP oliver.anderson@sap.com
We introduce data structures to describe values within the registers
modified by the CPUID instruction. These data structures will later be
used by the upcoming CPU profile generation tool.

Signed-off-by: Oliver Anderson <oliver.anderson@cyberus-technology.de>
On-behalf-of: SAP oliver.anderson@sap.com
We introduce CPUID definitions for Intel CPUs that will be utilized by
the upcoming CPU Profile generation tool.

Signed-off-by: Oliver Anderson <oliver.anderson@cyberus-technology.de>
On-behalf-of: SAP oliver.anderson@sap.com
We introduce CPUID definitions defined for the KVM hypervisor. These
definitions will later be utilized by the upcoming CPU profile
generation tool.

Signed-off-by: Oliver Anderson <oliver.anderson@cyberus-technology.de>
On-behalf-of: SAP oliver.anderson@sap.com
We use the Intel CPUID definitions to provide more information when
CPUID compatibility checks fail (when both the source and destination
VM run on Intel CPUs).

Signed-off-by: Oliver Anderson <oliver.anderson@cyberus-technology.de>
On-behalf-of: SAP oliver.anderson@sap.com
In order to generate CPU profiles we also need definitions and policies
for MSR-based features, as some CPU features are exposed through MSRs
rather than CPUID.

This commit introduces the MSR analogues of the data structures we
previously introduced for CPUID definitions.

Signed-off-by: Oliver Anderson <oliver.anderson@cyberus-technology.de>
On-behalf-of: SAP oliver.anderson@sap.com
We introduce MSR-based feature definitions for Intel CPUs that will be
utilized by the upcoming CPU profile generation tool.

Signed-off-by: Oliver Anderson <oliver.anderson@cyberus-technology.de>
On-behalf-of: SAP oliver.anderson@sap.com
This commit introduces a CLI for generating a CPU profile closely
matching the CPU of the machine the CLI is executed on. The idea is to
have a simple way to add more CPU profiles corresponding to physical
CPUs.

Note however that with the current setup one still needs a little bit
of manual work to integrate the generated CPU profile data into cloud
hypervisor itself.

Signed-off-by: Oliver Anderson <oliver.anderson@cyberus-technology.de>
On-behalf-of: SAP oliver.anderson@sap.com
@phip1611
Copy link
Member

phip1611 commented Jan 9, 2026

Hi @rbradford, happy new year :)
Friendly reminder: it would be great to get some early feedback before we deploy this at scale for a customer. We're mainly looking for feedback on the overall direction, not the details.

@olivereanderson olivereanderson marked this pull request as ready for review January 12, 2026 14:02
@olivereanderson olivereanderson requested a review from a team as a code owner January 12, 2026 14:02
@@ -0,0 +1,34 @@
#![cfg(all(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

random thought. We should also update https://github.com/cloud-hypervisor/cloud-hypervisor/blob/main/docs/cpu.md in this PR eventually

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not random at all. This is indeed crucial. I will add it to the follow up/TODO list 👍

Copy link
Member

@likebreath likebreath left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I took a detailed look at commit arch: Apply CPU profile (if any) when generating common CPUID, as that's how the cpu profile feature is plugged into the code base. Some comments below to make the change less intrusive and cleaner.

Couple of other quick comments:

  1. Need some documentation around the CLI tool cpu-profile-generation;
  2. Better to include commit for the CLI tool first before introducing the two supported CPU profile json files;

With that, the overall design looks good to me. Thank you for the good work.

Comment on lines 733 to 887
// Update some existing CPUID
for entry in cpuid.as_mut_slice().iter_mut() {
match entry.function {
// Clear AMX related bits if the AMX feature is not enabled
0x7 => {
if !config.amx {
if entry.index == 0 {
entry.edx &= !((1 << AMX_BF16) | (1 << AMX_TILE) | (1 << AMX_INT8));
}
if entry.index == 1 {
entry.eax &= !(1 << AMX_FP16);
entry.edx &= !(1 << AMX_COMPLEX);
}
}
}
}
0xd =>
{
#[cfg(feature = "tdx")]
if let Some(caps) = &tdx_capabilities {
let xcr0_mask: u64 = 0x82ff;
let xss_mask: u64 = !xcr0_mask;
if entry.index == 0 {
entry.eax &= (caps.xfam_fixed0 as u32) & (xcr0_mask as u32);
entry.eax |= (caps.xfam_fixed1 as u32) & (xcr0_mask as u32);
entry.edx &= ((caps.xfam_fixed0 & xcr0_mask) >> 32) as u32;
entry.edx |= ((caps.xfam_fixed1 & xcr0_mask) >> 32) as u32;
} else if entry.index == 1 {
entry.ecx &= (caps.xfam_fixed0 as u32) & (xss_mask as u32);
entry.ecx |= (caps.xfam_fixed1 as u32) & (xss_mask as u32);
entry.edx &= ((caps.xfam_fixed0 & xss_mask) >> 32) as u32;
entry.edx |= ((caps.xfam_fixed1 & xss_mask) >> 32) as u32;
0xd =>
{
#[cfg(feature = "tdx")]
if let Some(caps) = &tdx_capabilities {
let xcr0_mask: u64 = 0x82ff;
let xss_mask: u64 = !xcr0_mask;
if entry.index == 0 {
entry.eax &= (caps.xfam_fixed0 as u32) & (xcr0_mask as u32);
entry.eax |= (caps.xfam_fixed1 as u32) & (xcr0_mask as u32);
entry.edx &= ((caps.xfam_fixed0 & xcr0_mask) >> 32) as u32;
entry.edx |= ((caps.xfam_fixed1 & xcr0_mask) >> 32) as u32;
} else if entry.index == 1 {
entry.ecx &= (caps.xfam_fixed0 as u32) & (xss_mask as u32);
entry.ecx |= (caps.xfam_fixed1 as u32) & (xss_mask as u32);
entry.edx &= ((caps.xfam_fixed0 & xss_mask) >> 32) as u32;
entry.edx |= ((caps.xfam_fixed1 & xss_mask) >> 32) as u32;
}
}
}
}
0x1d => {
// Tile Information (purely AMX related).
if !config.amx {
entry.eax = 0;
entry.ebx = 0;
entry.ecx = 0;
entry.edx = 0;

0x1d => {
// Tile Information (purely AMX related).
if !config.amx {
entry.eax = 0;
entry.ebx = 0;
entry.ecx = 0;
entry.edx = 0;
}
}
}
0x1e => {
// TMUL information (purely AMX related)
if !config.amx {
entry.eax = 0;
entry.ebx = 0;
entry.ecx = 0;
entry.edx = 0;
0x1e => {
// TMUL information (purely AMX related)
if !config.amx {
entry.eax = 0;
entry.ebx = 0;
entry.ecx = 0;
entry.edx = 0;
}
}
}

// Copy host L1 cache details if not populated by KVM
0x8000_0005 => {
if entry.eax == 0 && entry.ebx == 0 && entry.ecx == 0 && entry.edx == 0 {
#[allow(unused_unsafe)]
// SAFETY: cpuid called with valid leaves
if unsafe { std::arch::x86_64::__cpuid(0x8000_0000).eax } >= 0x8000_0005 {
// Copy host L1 cache details if not populated by KVM
0x8000_0005 => {
if entry.eax == 0 && entry.ebx == 0 && entry.ecx == 0 && entry.edx == 0 {
#[allow(unused_unsafe)]
// SAFETY: cpuid called with valid leaves
let leaf = unsafe { std::arch::x86_64::__cpuid(0x8000_0005) };
entry.eax = leaf.eax;
entry.ebx = leaf.ebx;
entry.ecx = leaf.ecx;
entry.edx = leaf.edx;
if unsafe { std::arch::x86_64::__cpuid(0x8000_0000).eax } >= 0x8000_0005 {
#[allow(unused_unsafe)]
// SAFETY: cpuid called with valid leaves
let leaf = unsafe { std::arch::x86_64::__cpuid(0x8000_0005) };
entry.eax = leaf.eax;
entry.ebx = leaf.ebx;
entry.ecx = leaf.ecx;
entry.edx = leaf.edx;
}
}
}
}
// Copy host L2 cache details if not populated by KVM
0x8000_0006 => {
if entry.eax == 0 && entry.ebx == 0 && entry.ecx == 0 && entry.edx == 0 {
#[allow(unused_unsafe)]
// SAFETY: cpuid called with valid leaves
if unsafe { std::arch::x86_64::__cpuid(0x8000_0000).eax } >= 0x8000_0006 {
// Copy host L2 cache details if not populated by KVM
0x8000_0006 => {
if entry.eax == 0 && entry.ebx == 0 && entry.ecx == 0 && entry.edx == 0 {
#[allow(unused_unsafe)]
// SAFETY: cpuid called with valid leaves
let leaf = unsafe { std::arch::x86_64::__cpuid(0x8000_0006) };
entry.eax = leaf.eax;
entry.ebx = leaf.ebx;
entry.ecx = leaf.ecx;
entry.edx = leaf.edx;
if unsafe { std::arch::x86_64::__cpuid(0x8000_0000).eax } >= 0x8000_0006 {
#[allow(unused_unsafe)]
// SAFETY: cpuid called with valid leaves
let leaf = unsafe { std::arch::x86_64::__cpuid(0x8000_0006) };
entry.eax = leaf.eax;
entry.ebx = leaf.ebx;
entry.ecx = leaf.ecx;
entry.edx = leaf.edx;
}
}
}
}
// Set CPU physical bits
0x8000_0008 => {
entry.eax = (entry.eax & 0xffff_ff00) | (config.phys_bits as u32 & 0xff);
}
0x4000_0001 => {
// Enable KVM_FEATURE_MSI_EXT_DEST_ID. This allows the guest to target
// device interrupts to cpus with APIC IDs > 254 without interrupt remapping.
entry.eax |= 1 << KVM_FEATURE_MSI_EXT_DEST_ID;

// These features are not supported by TDX
#[cfg(feature = "tdx")]
if config.tdx {
entry.eax &= !((1 << KVM_FEATURE_CLOCKSOURCE_BIT)
| (1 << KVM_FEATURE_CLOCKSOURCE2_BIT)
| (1 << KVM_FEATURE_CLOCKSOURCE_STABLE_BIT)
| (1 << KVM_FEATURE_ASYNC_PF_BIT)
| (1 << KVM_FEATURE_ASYNC_PF_VMEXIT_BIT)
| (1 << KVM_FEATURE_STEAL_TIME_BIT));
// Set CPU physical bits
0x8000_0008 => {
entry.eax = (entry.eax & 0xffff_ff00) | (config.phys_bits as u32 & 0xff);
}
0x4000_0001 => {
// Enable KVM_FEATURE_MSI_EXT_DEST_ID. This allows the guest to target
// device interrupts to cpus with APIC IDs > 254 without interrupt remapping.
entry.eax |= 1 << KVM_FEATURE_MSI_EXT_DEST_ID;

// These features are not supported by TDX
#[cfg(feature = "tdx")]
if config.tdx {
entry.eax &= !((1 << KVM_FEATURE_CLOCKSOURCE_BIT)
| (1 << KVM_FEATURE_CLOCKSOURCE2_BIT)
| (1 << KVM_FEATURE_CLOCKSOURCE_STABLE_BIT)
| (1 << KVM_FEATURE_ASYNC_PF_BIT)
| (1 << KVM_FEATURE_ASYNC_PF_VMEXIT_BIT)
| (1 << KVM_FEATURE_STEAL_TIME_BIT))
}
}
_ => {}
}
_ => {}
}
}

// Copy CPU identification string
for i in 0x8000_0002..=0x8000_0004 {
cpuid.retain(|c| c.function != i);
// SAFETY: call cpuid with valid leaves
#[allow(unused_unsafe)]
let leaf = unsafe { std::arch::x86_64::__cpuid(i) };
cpuid.push(CpuIdEntry {
function: i,
eax: leaf.eax,
ebx: leaf.ebx,
ecx: leaf.ecx,
edx: leaf.edx,
..Default::default()
});
}

if config.kvm_hyperv {
// Remove conflicting entries
cpuid.retain(|c| c.function != 0x4000_0000);
cpuid.retain(|c| c.function != 0x4000_0001);
// See "Hypervisor Top Level Functional Specification" for details
// Compliance with "Hv#1" requires leaves up to 0x4000_000a
cpuid.push(CpuIdEntry {
function: 0x40000000,
eax: 0x4000000a, // Maximum cpuid leaf
ebx: 0x756e694c, // "Linu"
ecx: 0x564b2078, // "x KV"
edx: 0x7648204d, // "M Hv"
..Default::default()
});
cpuid.push(CpuIdEntry {
function: 0x40000001,
eax: 0x31237648, // "Hv#1"
..Default::default()
});
cpuid.push(CpuIdEntry {
function: 0x40000002,
eax: 0x3839, // "Build number"
ebx: 0xa0000, // "Version"
..Default::default()
});
cpuid.push(CpuIdEntry {
function: 0x4000_0003,
eax: (1 << 1) // AccessPartitionReferenceCounter
if config.kvm_hyperv {
// Remove conflicting entries
cpuid.retain(|c| c.function != 0x4000_0000);
cpuid.retain(|c| c.function != 0x4000_0001);
// See "Hypervisor Top Level Functional Specification" for details
// Compliance with "Hv#1" requires leaves up to 0x4000_000a
cpuid.push(CpuIdEntry {
function: 0x40000000,
eax: 0x4000000a, // Maximum cpuid leaf
ebx: 0x756e694c, // "Linu"
ecx: 0x564b2078, // "x KV"
edx: 0x7648204d, // "M Hv"
..Default::default()
});
cpuid.push(CpuIdEntry {
function: 0x40000001,
eax: 0x31237648, // "Hv#1"
..Default::default()
});
cpuid.push(CpuIdEntry {
function: 0x40000002,
eax: 0x3839, // "Build number"
ebx: 0xa0000, // "Version"
..Default::default()
});
cpuid.push(CpuIdEntry {
function: 0x4000_0003,
eax: (1 << 1) // AccessPartitionReferenceCounter
| (1 << 2) // AccessSynicRegs
| (1 << 3) // AccessSyntheticTimerRegs
| (1 << 9), // AccessPartitionReferenceTsc
edx: 1 << 3, // CPU dynamic partitioning
..Default::default()
});
cpuid.push(CpuIdEntry {
function: 0x4000_0004,
eax: 1 << 5, // Recommend relaxed timing
..Default::default()
});
for i in 0x4000_0005..=0x4000_000a {
edx: 1 << 3, // CPU dynamic partitioning
..Default::default()
});
cpuid.push(CpuIdEntry {
function: i,
function: 0x4000_0004,
eax: 1 << 5, // Recommend relaxed timing
..Default::default()
});
for i in 0x4000_0005..=0x4000_000a {
cpuid.push(CpuIdEntry {
function: i,
..Default::default()
});
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest you split such part to a separate function so that you can reuse for patching either host_cpuid or host_adjusted_to_profile.

Comment on lines +695 to +731
let mut host_adjusted_to_profile =
host_adjusted_to_profile.map_err(Error::MissingExpectedCpuidEntry)?;

// There should be relatively few cases where live migration can succeed between hosts from different
// CPU vendors and making our checks account for that possibility would complicate things substantially.
// We thus require that the host's cpu vendor matches the one used to generate the CPU profile.
if let Some(cpu_vendor_profile) = profile_cpu_vendor
&& let cpu_vendor_host = hypervisor.get_cpu_vendor()
&& cpu_vendor_profile != cpu_vendor_host
{
return Err(Error::CpuProfileVendorIncompatibility {
cpu_vendor_profile,
cpu_vendor_host,
}
.into());
}
// We now make the modifications according to the config parameters to each of the cpuid entries
// declared above and then perform a compatibility check.
for cpuid_optiion in [Some(&mut host_cpuid), host_adjusted_to_profile.as_mut()] {
let Some(cpuid) = cpuid_optiion else {
break;
};
CpuidPatch::patch_cpuid(cpuid, &cpuid_patches);

// Update some existing CPUID
for entry in cpuid.as_mut_slice().iter_mut() {
match entry.function {
// Clear AMX related bits if the AMX feature is not enabled
0x7 => {
if !config.amx {
if entry.index == 0 {
entry.edx &= !((1 << AMX_BF16) | (1 << AMX_TILE) | (1 << AMX_INT8));
}
if entry.index == 1 {
entry.eax &= !(1 << AMX_FP16);
entry.edx &= !(1 << AMX_COMPLEX);
#[cfg(feature = "tdx")]
let tdx_capabilities = if config.tdx {
if use_custom_profile {
return Err(Error::CpuProfileTdxIncompatibility.into());
}
let caps = hypervisor
.tdx_capabilities()
.map_err(Error::TdxCapabilities)?;
info!("TDX capabilities {:#?}", caps);
Some(caps)
} else {
None
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By splitting the function as mentioned above, you can completely decouple the code paths (where CPU profiles are used vs. where they are not). This makes the changes cleaner and less intrusive.

Comment on lines +890 to +901
if !use_custom_profile {
Ok(host_cpuid)
} else {
// Final compatibility checks to ensure that the CPUID values we return are compatible both with the CPU profile and the host we are currently running on.
let host_adjusted_to_profile = host_adjusted_to_profile.expect("The profile adjusted cpuid entries should exist as we checked that we have a custom CPU profile");

// Check that the host's cpuid is indeed compatible with the adjusted profile. This is not by construction.
info!("checking compatibility between host adjusted to profile and the host itself");
CpuidFeatureEntry::check_cpuid_compatibility(&host_adjusted_to_profile, &host_cpuid)
.map_err(|_| Error::CpuProfileCpuidIncompatibility)?;
Ok(host_adjusted_to_profile)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Along the same lines as the previous comment: this is essentially how you can reuse and handle the two different code paths independently.

@likebreath likebreath marked this pull request as draft January 15, 2026 18:54
@likebreath
Copy link
Member

Converted it to a draft since this is mostly for architectural review. Feel free to reopen or open a new one when it is ready for full review. Thank you.

@phip1611 phip1611 linked an issue Jan 16, 2026 that may be closed by this pull request
);
let guard = self.config.lock().unwrap();
let amx = guard.cpus.features.amx;
let phys_bits = physical_bits(&self.hypervisor, guard.cpus.max_phys_bits);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe here should be:
let phys_bits = physical_bits(self.hypervisor.as_ref(), guard.cpus.max_phys_bits);

@up2wing
Copy link
Contributor

up2wing commented Jan 26, 2026

Good Job!
Two little suggestion:

  • Can we allows per-feature toggles (-cpu Skylake,+avx512,-sha)?
  • How about support some profiles for live migration between Intel and AMD?

@olivereanderson
Copy link
Contributor Author

Good Job! Two little suggestion:

* Can we allows per-feature toggles (`-cpu Skylake,+avx512,-sha`)?

* How about support some profiles for live migration between Intel and AMD?

Thank you for the encouraging comment 🙂

  • Can we allows per-feature toggles (-cpu Skylake,+avx512,-sha)?

I think we could indeed repurpose the already existing CpuFeatures struct to accommodate for this possibility, but I would prefer to do that in a follow PR. It should be easier to see which features may be toggled individually once we have also made CPU profiles for AMD processors (I will be working on that next!).

  • How about support some profiles for live migration between Intel and AMD?

I unfortunately don't think that will be physically possible for any realistic CPU profile. It might work with an extremely limited minimal profile, but I don't think that would be suitable for workloads intended to be used in production.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support for CPU templates/CPU types/CPU profiles

4 participants