Skip to content
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

define wrapper for C FAM structs #4

Merged
merged 1 commit into from
Aug 7, 2019
Merged

define wrapper for C FAM structs #4

merged 1 commit into from
Aug 7, 2019

Conversation

serban300
Copy link

In C an array of unknown size may appear within a struct definition
as the last member (as long as there is at least one other named member).
This is a known as flexible array member.

Pre C99, the same behavior could be achieved using zero length arrays.

Flexible Array Members are the go-to choice for working with large
amounts of data prefixed by header values.

KVM uses this type of structures (e.g., kvm_cpuid2, kvm_msr_list, etc.)

FamStructWrapper provides a common abstraction for this type of
structures and helps in treating them similarly to Rust Vec.

Signed-off-by: Serban Iorga seriorga@amazon.com

let mut mem_allocator = Vec::with_capacity(required_mem_allocator_capacity);
for _ in 0..required_mem_allocator_capacity {
mem_allocator.push(T::default())
}
Copy link
Member

Choose a reason for hiding this comment

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

Should we initialize memory other than first element to zero instead of T::default()? T::default() may generate some non-zero content.

Copy link
Author

Choose a reason for hiding this comment

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

Good catch. Done.

Choose a reason for hiding this comment

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

Well, then we should revert to zero-initialized instead of default-initialized in the method doc comment above.

Copy link
Author

Choose a reason for hiding this comment

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

Done.

Copy link
Member

@alxiord alxiord left a comment

Choose a reason for hiding this comment

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

FYI I can't get coverage with cargo kcov, could you?

test fam_struct_wrapper::tests::test_reserve ... ok
kcov: Process exited with signal 4 (SIGILL) at 0x7ffff6ce00e3

kcov: Illegal instructions are sometimes caused by some GCC versions
kcov: miscompiling C++ headers. If the problem is persistent, try running
kcov: with --verify. For more information, see
kcov: http://github.com/SimonKagstrom/kcov/issues/18

src/fam_struct_wrapper.rs Outdated Show resolved Hide resolved

use std::mem::size_of;

/// Errors associated with the FamStructWrapper struct.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
/// Errors associated with the FamStructWrapper struct.
/// Errors associated with the [`FamStructWrapper`](struct.FamStructWrapper.html) struct.

Copy link
Author

Choose a reason for hiding this comment

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

Fixed

use std::mem::size_of;

/// Errors associated with the FamStructWrapper struct.
#[derive(Debug, Clone)]
Copy link
Member

Choose a reason for hiding this comment

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

nit: order derives alphabetically

Copy link
Author

Choose a reason for hiding this comment

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

Done

type Entry: PartialEq + Copy;

/// Get the array length
///
Copy link
Member

Choose a reason for hiding this comment

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

nit: I don't think the extra newline improves readability here.

Copy link
Author

Choose a reason for hiding this comment

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

Ok. Fixed.

fn as_mut_slice(&mut self) -> &mut [Self::Entry];
}

/// A wrapper that helps in treating a `FamStruct` similarly to an actual `Vec`.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
/// A wrapper that helps in treating a `FamStruct` similarly to an actual `Vec`.
/// A wrapper that helps in treating a [`FamStruct`](trait.FamStruct.html) similarly to an actual `Vec`.

Copy link
Author

Choose a reason for hiding this comment

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

Fixed

/// # Arguments
///
/// * `num_elements` - The number of default-initialized elements of type
/// `FamStruct::Entry` in the initial `FamStructWrapper`
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
/// `FamStruct::Entry` in the initial `FamStructWrapper`
/// [`FamStruct::Entry`](trait.FamStruct.html#associatedtype.Entry) in the initial [`FamStructWrapper`](struct.FamStructWrapper.html)

Copy link
Author

Choose a reason for hiding this comment

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

Fixed

}
}

/// Creates a new `FamStructWrapper` structure based on a supplied slice
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
/// Creates a new `FamStructWrapper` structure based on a supplied slice
/// Creates a new [`FamStructWrapper`](struct.FamStructWrapper.html) structure based on a supplied slice

Copy link
Author

Choose a reason for hiding this comment

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

Fixed

}

/// Creates a new `FamStructWrapper` structure based on a supplied slice
/// of `FamStruct::Entry`.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
/// of `FamStruct::Entry`.
/// of [`FamStruct::Entry`](trait.FamStruct.html#associatedtype.Entry).

Copy link
Author

Choose a reason for hiding this comment

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

Fixed

///
/// # Arguments
///
/// * `entries` - The vector of `FamStruct::Entry` entries.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
/// * `entries` - The vector of `FamStruct::Entry` entries.
/// * `entries` - The vector of [`FamStruct::Entry`](trait.FamStruct.html#associatedtype.Entry) entries.

Copy link
Author

Choose a reason for hiding this comment

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

Fixed

adapter
}

/// Get a reference to the actual `FamStruct` instance.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
/// Get a reference to the actual `FamStruct` instance.
/// Get a reference to the actual [`FamStruct`](trait.FamStruct.html) instance.

This applies to all the following mentions too, I'll stop with the suggestions 😄

Copy link
Author

Choose a reason for hiding this comment

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

Fixed

@andreeaflorescu
Copy link
Member

@alexandruag I know you had some comments on this PR. Can you please add them here in case they weren't already fixed?

I will also check if the comments I had were resolved.

@serban300
Copy link
Author

serban300 commented May 31, 2019

FYI I can't get coverage with cargo kcov, could you?

test fam_struct_wrapper::tests::test_reserve ... ok
kcov: Process exited with signal 4 (SIGILL) at 0x7ffff6ce00e3

kcov: Illegal instructions are sometimes caused by some GCC versions
kcov: miscompiling C++ headers. If the problem is persistent, try running
kcov: with --verify. For more information, see
kcov: http://github.com/SimonKagstrom/kcov/issues/18

Yes. 98.9% coverage for fam_struct_wrapper.rs

jiangliu
jiangliu previously approved these changes Jun 5, 2019
Copy link

@alexandruag alexandruag left a comment

Choose a reason for hiding this comment

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

Sorry about the large number of rename related comments. I think they can make the code easier to use/understand, but I realize this is also a subjective perspective. Looking forward to other ppl's opinion as well.

src/lib.rs Outdated
@@ -10,6 +10,7 @@ pub mod ioctl;

pub mod errno;
pub mod eventfd;
pub mod fam_struct_wrapper;

Choose a reason for hiding this comment

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

What do you think about calling the new module simply fam? fam_struct_wrapper looks a bit ugly to me :(

Copy link
Author

Choose a reason for hiding this comment

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

Sounds good. Done.

src/fam_struct_wrapper.rs Outdated Show resolved Hide resolved
/// ```
///
#[allow(clippy::len_without_is_empty)]
pub trait FamStruct {

Choose a reason for hiding this comment

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

What do ppl think about renaming this to Fam and the wrapper struct to FamWrapper? The shorter names seem a lot nicer to me :(

Copy link
Author

Choose a reason for hiding this comment

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

Using Fam here wouldn't be completely accurate.

FAM = Flexible array member. This is the field in the structure. Not the structure itself.

I would keep it as it is.

// which must be contiguous. Since the entries are of type `FamStruct::Entry` we must
// be careful to convert the desired capacity of the `FamStructWrapper`
// from `FamStruct::Entry` to `T` when reserving or releasing memory.
mem_allocator: Vec<T>,

Choose a reason for hiding this comment

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

How about renaming this to simply allocator? (applying this to other mem_allocator strings/substrings in the rest of the file as well)

Copy link
Author

Choose a reason for hiding this comment

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

I would keep it as it is. I don't see a great benefit in doing this. It would decrease the length of some method names but it would also make the names less suggestive.

/// Get the capacity required by mem_allocator in order to hold
/// the provided number of [`FamStruct::Entry`](trait.FamStruct.html#associatedtype.Entry)
///
fn mem_allocator_len(wrapper_len: usize) -> usize {

Choose a reason for hiding this comment

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

I think reading this function becomes easier if we change the name of the parameter from wrapper_len to something like num_entries.

Copy link
Author

Choose a reason for hiding this comment

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

I renamed it to fam_len

/// [`FamStruct::Entry`](trait.FamStruct.html#associatedtype.Entry) elements.
/// If the capacity is already reserved, this method doesn't do anything.
///
fn reserve(&mut self, additional: usize) {

Choose a reason for hiding this comment

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

It might be worth noting in the docs that calls to reserve (and other methods based on it, for example set_let) eventually lead to Vec::reserve being called, which may end up doing a realloc of the underlying buffer, and that invalidates all previous pointers obtained via methods such as as_ptr, etc.

Copy link
Author

Choose a reason for hiding this comment

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

Ok. I added comments related to this.

/// This is needed after `self.as_ptr()` is supplied as param to an FFI call
/// which may change it's state.
///
pub fn sync(&mut self) -> Result<(), Error> {

Choose a reason for hiding this comment

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

Does this function make sense when new_len > self.capacity? It looks like that should be an error condition, because it means someone effectively wrote past the end of our allocator.

Copy link
Author

Choose a reason for hiding this comment

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

True. Fixed.


impl<T: Default + FamStruct> PartialEq for FamStructWrapper<T> {
fn eq(&self, other: &FamStructWrapper<T>) -> bool {
self.len == other.len && self.as_slice() == other.as_slice()

Choose a reason for hiding this comment

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

Sorry I know I asked this regarding the old PR as well, but I don't remember the conclusion :( Is it a valid occurence to have self.as_slice() == other.as_slice(), but self.len != other.len?

Copy link
Author

Choose a reason for hiding this comment

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

Yes, if sync() isn't called.

It's possible to have for example:

cpuid_1 = CpuId::new(100);
cpuid_2 = CpuId::new(110);

// here the inner fam_struct of cpuid_1 will be modified by kvm. Let's say that it will have len = 50
call_to_kvm(cpuid_1.as_mut_fam_struct_ptr());

// here the inner fam_struct of cpuid_2 will be modified by kvm. Let's say that it will have len = 50
call_to_kvm(cpuid_2.as_mut_fam_struct_ptr());

After this cpuid_1.as_slice() == cpuid_2.as_slice() . 
But cpuid_1.len() = 100 while cpuid_2.len = 110.

///
/// # Error: When len is greater than the max possible len it returns Error::SizeLimitExceeded
///
fn set_len(&mut self, len: usize) -> Result<(), Error> {

Choose a reason for hiding this comment

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

It seems that when set_len causes the length to increase, the newly added elements are based on basically random memory contents (I don't think reserving capacity also initializes the memory to a particular value). If that's true, is this a part of the set_len method contract, or we should try to zero-initialize the new elements like we do for new?

Copy link
Author

Choose a reason for hiding this comment

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

For the moment set_len is used to increase the lenght of the array only when pushing. So it should be safe as it is. I don't know if in the future there will be any case where we would need to zero-initialize the memory.

/// ```
///
#[allow(clippy::len_without_is_empty)]
pub trait FamStruct {

Choose a reason for hiding this comment

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

Also, I think we should mark this trait as unsafe, just so the user gives an explicit sign-off that the implementation adheres to the conventions established by the trait, including that any type implementing this must be valid to initialize from any random bits. Without this assumption,we can't be sure that zero-initializing the memory in new and using Vec::set_len are actually safe to do in the implementation of FamStructWrapper. What do you ppl think?

Copy link
Author

Choose a reason for hiding this comment

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

Makes sense. Done.

jiangliu
jiangliu previously approved these changes Jul 11, 2019
src/fam.rs Show resolved Hide resolved
src/fam.rs Outdated
///
/// This method might trigger reallocations of the underlying buffer.
///
/// # Error: When len is greater than the max possible len it returns Error::SizeLimitExceeded
Copy link
Member

Choose a reason for hiding this comment

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

This will look a bit weird in markdown. You basically have a heading title that is very very long.

Please use as heading title Errors as this is the standard defined in the Rust documentation guidelines (even if the function can return just on type of error). The explanation for the error needs to be in a paragraph.

Copy link
Author

Choose a reason for hiding this comment

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

Done

src/fam.rs Show resolved Hide resolved
@jiangliu
Copy link
Member

jiangliu commented Aug 3, 2019

What's the plan with this PR? I saw this pattern has been repeated in several crates, so it may help to reduce common code.

@serban300
Copy link
Author

What's the plan with this PR? I saw this pattern has been repeated in several crates, so it may help to reduce common code.

We have to discuss the final details related to this comment. I'll try to sync with @alexandruag about this as soon as possible.

@alexandruag
Copy link

We have to discuss the final details related to this comment. I'll try to sync with @alexandruag about this as soon as possible.

I've had another look and for now there are no other pressing concerns from my point of view. I was worried before there were dangerous corner cases when the size of Entry is not a multiple of its alignment, but it looks like the compiler ensures that's never the case via padding bytes when necessary.

/// ```
///
#[allow(clippy::len_without_is_empty)]
pub unsafe trait FamStruct {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could you please add some documentation on this trait? Some references here

pub unsafe trait Terminal {

Copy link
Author

Choose a reason for hiding this comment

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

I added just a couple of lines. I couldn't think of any other details that would need to be specified. Please let me know if anything is not clear.


/// A wrapper that helps in treating a [`FamStruct`](trait.FamStruct.html) similarly to
/// an actual `Vec`.
///
Copy link
Collaborator

@liujing2 liujing2 Aug 6, 2019

Choose a reason for hiding this comment

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

Blank line is useless if no other description.

Copy link
Collaborator

@liujing2 liujing2 Aug 6, 2019

Choose a reason for hiding this comment

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

Could you please kindly separate the documentation into a short summary line of its functionality, and more explanation in a new paragraph? Ditto for others below. EventFd structure in eventfd.rs is a reference for you.

Copy link
Author

Choose a reason for hiding this comment

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

Done

src/fam.rs Outdated
/// * `num_elements` - The number of zero-initialized elements of type
/// [`FamStruct::Entry`](trait.FamStruct.html#associatedtype.Entry)
/// in the initial [`FamStructWrapper`](struct.FamStructWrapper.html)
///
Copy link
Collaborator

Choose a reason for hiding this comment

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

Yep, good to see the detailed #Arguments description. :)

src/fam.rs Outdated
desired_len = 5;
assert!(adapter.set_len(desired_len).is_ok());
assert_eq!(adapter.len(), desired_len);
// check that the capacity
Copy link
Collaborator

Choose a reason for hiding this comment

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

Useless comment?

Copy link
Author

Choose a reason for hiding this comment

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

fixed

Copy link
Collaborator

@liujing2 liujing2 left a comment

Choose a reason for hiding this comment

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

Some comments as mentioned.

In C an array of unknown size may appear within a struct definition
as the last member (as long as there is at least one other named member).
This is a known as flexible array member.

Pre C99, the same behavior could be achieved using zero length arrays.

Flexible Array Members are the go-to choice for working with large
amounts of data prefixed by header values.

KVM uses this type of structures (e.g., kvm_cpuid2, kvm_msr_list, etc.)

FamStructWrapper provides a common abstraction for this type of
structures and helps in treating them similarly to Rust Vec.

Signed-off-by: Serban Iorga <seriorga@amazon.com>
/// pub unsafe fn as_mut_slice(&mut self, len: usize) -> &mut [T] {
/// ::std::slice::from_raw_parts_mut(self.as_mut_ptr(), len)
/// }
/// }
Copy link
Member

Choose a reason for hiding this comment

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

Could the __IncompleteArrayField struct be exported as a pub generic type, seems it only contains generic code.

Copy link
Author

Choose a reason for hiding this comment

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

This struct is generated automatically by bindgen. You can find it for example in the kvm-bindings project. I needed to copy it just for the unit tests and the example. I don't know if it adds any value exporting it.

@liujing2 liujing2 merged commit 54e256b into rust-vmm:master Aug 7, 2019
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.

6 participants