From 7a2062255379c0f5624f7feec46c25b580c3e828 Mon Sep 17 00:00:00 2001 From: Vince Prignano Date: Fri, 2 Oct 2026 10:26:45 -0700 Subject: [PATCH] Add CPU and memory fields to the Cloud Hypervisor VM config The CloudHypervisor types now model these fields of the Cloud Hypervisor OpenAPI spec (present since before v52.0): - CpusConfig.features (CpuFeatures, with amx) - CpusConfig.core_scheduling (CoreSchedulingMode: Vm, Vcpu, Off) - CpusConfig.nested - MemoryConfig.hugepages - MemoryConfig.prefault All are optional and nil by default, and nil fields are not encoded, so the JSON for an existing configuration does not change. A CHInstanceExtension can set them in configureCH. --- Sources/CloudHypervisor/Types/VmConfig.swift | 62 +++++++++++++++++++- Tests/CloudHypervisorTests/TypesTests.swift | 46 +++++++++++++++ 2 files changed, 106 insertions(+), 2 deletions(-) diff --git a/Sources/CloudHypervisor/Types/VmConfig.swift b/Sources/CloudHypervisor/Types/VmConfig.swift index 429b7db95..03dd51f23 100644 --- a/Sources/CloudHypervisor/Types/VmConfig.swift +++ b/Sources/CloudHypervisor/Types/VmConfig.swift @@ -68,6 +68,30 @@ extension CloudHypervisor { // MARK: - CpusConfig + /// Guest CPU feature toggles. + /// + /// Maps to `CpuFeatures` in the Cloud Hypervisor OpenAPI spec. + public struct CpuFeatures: Sendable, Codable, Equatable { + /// Enable AMX tile state for the guest (x86_64 only). + public var amx: Bool? + + public init(amx: Bool? = nil) { + self.amx = amx + } + } + + /// How Cloud Hypervisor groups vCPU threads for Linux core scheduling. + /// + /// Maps to `CoreSchedulingMode` in the Cloud Hypervisor OpenAPI spec. + public enum CoreSchedulingMode: String, Codable, Sendable { + /// All vCPU threads of the VM share one core scheduling cookie. + case Vm + /// Each vCPU thread has its own core scheduling cookie. + case Vcpu + /// No core scheduling. + case Off + } + /// CPU configuration for a VM. /// /// Maps to `CpusConfig` in the Cloud Hypervisor OpenAPI spec. @@ -76,15 +100,34 @@ extension CloudHypervisor { public var bootVcpus: Int /// Maximum number of vCPUs (for hotplug). public var maxVcpus: Int + /// Guest CPU features. Nil leaves the Cloud Hypervisor defaults. + public var features: CpuFeatures? + /// Core scheduling mode. Nil means the Cloud Hypervisor default, `.Vm`. + public var coreScheduling: CoreSchedulingMode? + /// Expose VMX (Intel) or SVM (AMD) to the guest, so that it can run its own + /// VMs. Nil means the Cloud Hypervisor default, true. + public var nested: Bool? - public init(bootVcpus: Int, maxVcpus: Int) { + public init( + bootVcpus: Int, + maxVcpus: Int, + features: CpuFeatures? = nil, + coreScheduling: CoreSchedulingMode? = nil, + nested: Bool? = nil + ) { self.bootVcpus = bootVcpus self.maxVcpus = maxVcpus + self.features = features + self.coreScheduling = coreScheduling + self.nested = nested } enum CodingKeys: String, CodingKey { case bootVcpus = "boot_vcpus" case maxVcpus = "max_vcpus" + case features + case coreScheduling = "core_scheduling" + case nested } } @@ -105,12 +148,25 @@ extension CloudHypervisor { /// CH otherwise rejects `vm.boot` with "Using vhost-user requires /// using shared memory or huge pages". public var shared: Bool? + /// Back guest RAM with hugetlbfs pages of the host's default huge page size. + public var hugepages: Bool? + /// Populate all guest RAM when the VM boots (`MADV_POPULATE_WRITE`). + public var prefault: Bool? - public init(size: UInt64, hotplugSize: UInt64? = nil, mergeable: Bool? = nil, shared: Bool? = nil) { + public init( + size: UInt64, + hotplugSize: UInt64? = nil, + mergeable: Bool? = nil, + shared: Bool? = nil, + hugepages: Bool? = nil, + prefault: Bool? = nil + ) { self.size = size self.hotplugSize = hotplugSize self.mergeable = mergeable self.shared = shared + self.hugepages = hugepages + self.prefault = prefault } enum CodingKeys: String, CodingKey { @@ -118,6 +174,8 @@ extension CloudHypervisor { case hotplugSize = "hotplug_size" case mergeable case shared + case hugepages + case prefault } } diff --git a/Tests/CloudHypervisorTests/TypesTests.swift b/Tests/CloudHypervisorTests/TypesTests.swift index 8826643d1..1b68a5312 100644 --- a/Tests/CloudHypervisorTests/TypesTests.swift +++ b/Tests/CloudHypervisorTests/TypesTests.swift @@ -74,6 +74,52 @@ struct TypesTests { #expect(!jsonString.contains("\"mergeable\"")) } + @Test("CpusConfig encodes features, core scheduling and nested with OpenAPI keys") + func cpusConfigOptionalFields() throws { + let cfg = CloudHypervisor.CpusConfig( + bootVcpus: 2, + maxVcpus: 2, + features: CloudHypervisor.CpuFeatures(amx: true), + coreScheduling: .Vcpu, + nested: false + ) + let encoder = JSONEncoder() + encoder.outputFormatting = [.sortedKeys] + let data = try encoder.encode(cfg) + let jsonString = try #require(String(data: data, encoding: .utf8)) + #expect( + jsonString + == #"{"boot_vcpus":2,"core_scheduling":"Vcpu","features":{"amx":true},"max_vcpus":2,"nested":false}"# + ) + let decoded = try JSONDecoder().decode(CloudHypervisor.CpusConfig.self, from: data) + #expect(decoded == cfg) + } + + @Test("CpusConfig omits nil optional fields from JSON") + func cpusConfigNilOmission() throws { + let data = try JSONEncoder().encode(CloudHypervisor.CpusConfig(bootVcpus: 1, maxVcpus: 1)) + let jsonString = try #require(String(data: data, encoding: .utf8)) + #expect(!jsonString.contains("\"features\"")) + #expect(!jsonString.contains("\"core_scheduling\"")) + #expect(!jsonString.contains("\"nested\"")) + } + + @Test("MemoryConfig encodes hugepages and prefault") + func memoryConfigHugepagesPrefault() throws { + let cfg = CloudHypervisor.MemoryConfig(size: UInt64(1) << 30, hugepages: true, prefault: true) + let data = try JSONEncoder().encode(cfg) + let jsonString = try #require(String(data: data, encoding: .utf8)) + #expect(jsonString.contains("\"hugepages\":true")) + #expect(jsonString.contains("\"prefault\":true")) + let decoded = try JSONDecoder().decode(CloudHypervisor.MemoryConfig.self, from: data) + #expect(decoded == cfg) + + let plain = try JSONEncoder().encode(CloudHypervisor.MemoryConfig(size: UInt64(1) << 30)) + let plainString = try #require(String(data: plain, encoding: .utf8)) + #expect(!plainString.contains("\"hugepages\"")) + #expect(!plainString.contains("\"prefault\"")) + } + @Test("PayloadConfig round-trips through JSON") func payloadConfigRoundTrip() throws { let cfg = CloudHypervisor.PayloadConfig(