You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
At the moment the current implementation of CpuId and the FamStructWrapper and FamStruct underlying it, are not as simple as they could be. For interop with kvm_bindings I wrote a custom structure which is both safe and has an identical memory layout to kvm_cpuid2, a zero-cost wrapper.
This structure could directly replace CpuId or possibly a more generic variant could be used in place of FamStructWrapper and FamStruct.
It would be good to get some feedback and discussion relating to what areas a change like this may influence.
use serde::{Deserialize,Serialize};/// A rusty mimic of/// [`kvm_cpuid`](https://elixir.bootlin.com/linux/v5.10.129/source/arch/x86/include/uapi/asm/kvm.h#L226)/// .////// [`RawCpuid`] has an identical memory layout to/// [`kvm_cpuid`](https://elixir.bootlin.com/linux/v5.10.129/source/arch/x86/include/uapi/asm/kvm.h#L226)/// .////// This allows [`RawCpuid`] to function as a simpler replacement for [`kvm_bindings::CpuId`]. In/// the future it may replace [`kvm_bindings::CpuId`] fully.////// For implementation details see <https://doc.rust-lang.org/nomicon/vec/vec.html>.#[derive(Debug)]#[repr(C)]pubstructRawCpuid{/// Number of entries.nent:u32,/// Padding.padding:Padding<{size_of::<u32>()}>,/// Pointer to entries.entries:NonNull<RawCpuidEntry>,_marker:PhantomData<RawCpuidEntry>,}// This implementation could be significantly more efficient.implCloneforRawCpuid{fnclone(&self) -> Self{letmut new_raw_cpuid = Self::new();
new_raw_cpuid.resize(self.nentasusize);for i in0..self.nentasusize{
new_raw_cpuid[i] = self[i].clone();}
new_raw_cpuid
}}impl serde::SerializeforRawCpuid{fnserialize<S>(&self,serializer:S) -> Result<S::Ok,S::Error>whereS: serde::Serializer,{use serde::ser::SerializeSeq;letmut seq = serializer.serialize_seq(Some(self.nentasusize))?;for i in0..self.nentasusize{
seq.serialize_element(&self[i])?;}
seq.end()}}structRawCpuidVisitor;impl<'de> serde::de::Visitor<'de>forRawCpuidVisitor{typeValue = RawCpuid;fnexpecting(&self,formatter:&mut fmt::Formatter) -> fmt::Result{
formatter.write_str("Expected sequence of RawCpuidEntry")}fnvisit_seq<A>(self,mutseq:A) -> Result<Self::Value,A::Error>whereA: serde::de::SeqAccess<'de>,{letmut entries = Vec::new();whileletSome(next) = seq.next_element::<RawCpuidEntry>()? {
entries.push(next);}Ok(Self::Value::from(entries))}}impl<'de> serde::Deserialize<'de>forRawCpuid{fndeserialize<D>(deserializer:D) -> Result<Self,D::Error>whereD: serde::Deserializer<'de>,{
deserializer.deserialize_seq(RawCpuidVisitor)}}implPartialEqforRawCpuid{fneq(&self,other:&Self) -> bool{ifself.nent == other.nent{for i in0..self.nentasusize{ifself[i] != other[i]{returnfalse;}}true}else{false}}}implEqforRawCpuid{}unsafeimplSendforRawCpuid{}unsafeimplSyncforRawCpuid{}implRawCpuid{/// Alias for [`RawCpuid::default()`].#[must_use]pubfnnew() -> Self{Self::default()}/// Returns number of elements.#[must_use]pubfnnent(&self) -> u32{self.nent}/// Returns an entry for a given lead (function) and sub-leaf (index).////// Returning `None` if it is not present.#[must_use]pubfnget(&self,leaf:u32,sub_leaf:u32) -> Option<&RawCpuidEntry>{// TODO Would using binary search here for leaf offer much speedup?self.iter().find(|entry| entry.function == leaf && entry.index == sub_leaf)}/// Resizes allocated memory#[allow(clippy::cast_ptr_alignment)]fnresize(&mutself,n:usize){// allocifself.nent == 0 && n > 0{let new_layout = Layout::array::<RawCpuidEntry>(n).unwrap();// Ensure that the new allocation doesn't exceed `isize::MAX` bytes.assert!(isize::try_from(new_layout.size()).is_ok(),
"Allocation too large");let new_ptr = unsafe{ std::alloc::alloc(new_layout)};self.entries = matchNonNull::new(new_ptr.cast::<RawCpuidEntry>()){Some(p) => p,None => std::alloc::handle_alloc_error(new_layout),};}// reallocelseifself.nent > 0 && n > 0{let new_layout = Layout::array::<RawCpuidEntry>(n).unwrap();// Ensure that the new allocation doesn't exceed `isize::MAX` bytes.assert!(isize::try_from(new_layout.size()).is_ok(),
"Allocation too large");let old_layout =
Layout::array::<RawCpuidEntry>(usize::try_from(self.nent).unwrap()).unwrap();let old_ptr = self.entries.as_ptr().cast::<u8>();let new_ptr = unsafe{ std::alloc::realloc(old_ptr, old_layout, new_layout.size())};self.entries = matchNonNull::new(new_ptr.cast::<RawCpuidEntry>()){Some(p) => p,None => std::alloc::handle_alloc_error(new_layout),};}// deallocelseifself.nent > 0 && n == 0{let old_layout =
Layout::array::<RawCpuidEntry>(usize::try_from(self.nent).unwrap()).unwrap();let old_ptr = self.entries.as_ptr().cast::<u8>();unsafe{ std::alloc::dealloc(old_ptr, old_layout)};self.entries = NonNull::dangling();}self.nent = u32::try_from(n).unwrap();}/// Pushes entry onto end.////// # Panics////// On allocation failure.pubfnpush(&mutself,entry:RawCpuidEntry){self.resize(usize::try_from(self.nent).unwrap() + 1);unsafe{
std::ptr::write(self.entries.as_ptr().add(usize::try_from(self.nent).unwrap()),
entry,)}}/// Pops entry from end.////// # Panics////// On allocation failure.pubfnpop(&mutself) -> Option<RawCpuidEntry>{ifself.nent > 0{let u_nent = usize::try_from(self.nent).unwrap();let rtn = unsafe{Some(std::ptr::read(self.entries.as_ptr().add(u_nent)))};self.resize(u_nent - 1);
rtn
}else{None}}}implDefaultforRawCpuid{fndefault() -> Self{Self{nent:0,padding:Padding::default(),entries:NonNull::dangling(),_marker:PhantomData,}}}// We implement custom drop which drops all entries using `self.nent`implDropforRawCpuid{fndrop(&mutself){ifself.nent != 0{unsafe{
std::alloc::dealloc(self.entries.as_ptr().cast::<u8>(),Layout::array::<RawCpuidEntry>(usize::try_from(self.nent).unwrap()).unwrap(),);}}}}implDerefforRawCpuid{typeTarget = [RawCpuidEntry];fnderef(&self) -> &Self::Target{unsafe{
std::slice::from_raw_parts(self.entries.as_ptr(), usize::try_from(self.nent).unwrap())}}}implDerefMutforRawCpuid{fnderef_mut(&mutself) -> &mutSelf::Target{unsafe{
std::slice::from_raw_parts_mut(self.entries.as_ptr(),
usize::try_from(self.nent).unwrap(),)}}}implFrom<kvm_bindings::CpuId>forRawCpuid{fnfrom(value: kvm_bindings::CpuId) -> Self{// As cannot acquire ownership of the underlying slice, we clone it.let cloned = value.as_slice().to_vec();let(ptr, len, _cap) = vec_into_raw_parts(cloned);Self{nent: u32::try_from(len).unwrap(),padding:Padding::default(),entries:NonNull::new(ptr.cast::<RawCpuidEntry>()).unwrap(),_marker:PhantomData,}}}implFrom<Vec<RawCpuidEntry>>forRawCpuid{fnfrom(vec:Vec<RawCpuidEntry>) -> Self{let(ptr, len, _cap) = vec_into_raw_parts(vec);Self{nent: u32::try_from(len).unwrap(),padding:Padding::default(),entries:NonNull::new(ptr.cast::<RawCpuidEntry>()).unwrap(),_marker:PhantomData,}}}implFrom<RawCpuid>for kvm_bindings::CpuId{fnfrom(this:RawCpuid) -> Self{let cpuid_slice = unsafe{
std::slice::from_raw_parts(this.entries.as_ptr(), usize::try_from(this.nent).unwrap())};// println!("cpuid_slice: {:?}",cpuid_slice);#[allow(clippy::transmute_ptr_to_ptr)]let kvm_bindings_slice = unsafe{ std::mem::transmute(cpuid_slice)};
kvm_bindings::CpuId::from_entries(kvm_bindings_slice).unwrap()}}/// Mimic of the currently unstable/// [`Vec::into_raw_parts`](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.into_raw_parts)/// .fnvec_into_raw_parts<T>(v:Vec<T>) -> (*mutT,usize,usize){letmut me = std::mem::ManuallyDrop::new(v);(me.as_mut_ptr(), me.len(), me.capacity())}/// A structure for owning unused memory for padding.////// A wrapper around an uninitialized `N` element array of `u8`s (`MaybeUninit<[u8;N]>` constructed/// with `Self(MaybeUninit::uninit())`).#[derive(Debug,Clone)]#[repr(C)]pubstructPadding<constN:usize>(MaybeUninit<[u8;N]>);impl<constN:usize>DefaultforPadding<N>{fndefault() -> Self{Self(MaybeUninit::uninit())}}impl<constN:usize> serde::SerializeforPadding<N>{fnserialize<S>(&self,serializer:S) -> Result<S::Ok,S::Error>whereS: serde::Serializer,{
serializer.serialize_unit_struct("Padding")}}impl<'de,constN:usize> serde::Deserialize<'de>forPadding<N>{fndeserialize<D>(_deserializer:D) -> Result<Padding<N>,D::Error>whereD: serde::Deserializer<'de>,{Ok(Padding(MaybeUninit::uninit()))}}impl<constN:usize>PartialEqforPadding<N>{fneq(&self,_other:&Self) -> bool{true}}impl<constN:usize>EqforPadding<N>{}/// CPUID entry (a mimic of <https://elixir.bootlin.com/linux/v5.10.129/source/arch/x86/include/uapi/asm/kvm.h#L232>).#[derive(Debug,Clone,Eq,PartialEq,Default,Serialize,Deserialize)]#[repr(C)]pubstructRawCpuidEntry{/// CPUID function (leaf).pubfunction:u32,/// CPUID index (subleaf).pubindex:u32,/// TODOpubflags:u32,/// EAX register.pubeax:u32,/// EBX register.pubebx:u32,/// ECX register.pubecx:u32,/// EDX register.pubedx:u32,/// CPUID entry padding.pubpadding:Padding<{size_of::<[u32;3]>()}>,}implFrom<RawCpuidEntry>for(u32,u32,u32,u32){fnfrom(this:RawCpuidEntry) -> Self{(this.eax, this.ebx, this.ecx, this.edx)}}impl fmt::LowerHexforRawCpuidEntry{fnfmt(&self,f:&mut fmt::Formatter<'_>) -> fmt::Result{
f.debug_struct("RawCpuidEntry").field("function",&format!("{:x}", self.function)).field("index",&format!("{:x}", self.index)).field("eax",&format!("{:x}", self.eax)).field("ebx",&format!("{:x}", self.ebx)).field("ecx",&format!("{:x}", self.ecx)).field("edx",&format!("{:x}", self.edx)).finish()}}
As currently used FamStruct is not a zero-cost abstraction (in this case the size of CpuId is larger than neccessary). This change would make it zero-cost.
I believe this is a plain improvement, simplifying the code, improving readability (in large part due to the simplification) and improving performance (decreasing memory usage), side affects of this change are currently not well known however and require furthers discussion.
I would advocate for the deprecation of FamStruct moving forward.
There are implementations we could use which would not make this a breaking change. I think this change could be well implemented without breaking anything (although to be sure would require implementing it).
The text was updated successfully, but these errors were encountered:
At the moment the current implementation of
CpuId
and theFamStructWrapper
andFamStruct
underlying it, are not as simple as they could be. For interop withkvm_bindings
I wrote a custom structure which is both safe and has an identical memory layout tokvm_cpuid2
, a zero-cost wrapper.This structure could directly replace
CpuId
or possibly a more generic variant could be used in place ofFamStructWrapper
andFamStruct
.It would be good to get some feedback and discussion relating to what areas a change like this may influence.
As currently used
FamStruct
is not a zero-cost abstraction (in this case the size ofCpuId
is larger than neccessary). This change would make it zero-cost.I believe this is a plain improvement, simplifying the code, improving readability (in large part due to the simplification) and improving performance (decreasing memory usage), side affects of this change are currently not well known however and require furthers discussion.
I would advocate for the deprecation of
FamStruct
moving forward.There are implementations we could use which would not make this a breaking change. I think this change could be well implemented without breaking anything (although to be sure would require implementing it).
The text was updated successfully, but these errors were encountered: