-
Notifications
You must be signed in to change notification settings - Fork 175
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
Fix internal alignment in frunk
heterogeneous lists
#929
Conversation
762f39b
to
039a590
Compare
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.
nice!
039a590
to
d73bee9
Compare
/// An address for a location in a guest WebAssembly module's memory. | ||
#[derive(Clone, Copy, Debug, Eq, PartialEq)] | ||
pub struct GuestPointer(pub(crate) u32); | ||
|
||
impl GuestPointer { | ||
/// Returns a new address that's the current address advanced to add padding to ensure it's | ||
/// aligned to the `alignment` byte boundary. | ||
pub fn aligned_at(&self, alignment: u32) -> Self { | ||
pub const fn aligned_at(&self, alignment: u32) -> Self { |
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.
Yay for const functions!
/// An address for a location in a guest WebAssembly module's memory. | ||
#[derive(Clone, Copy, Debug, Eq, PartialEq)] | ||
pub struct GuestPointer(pub(crate) u32); | ||
|
||
impl GuestPointer { | ||
/// Returns a new address that's the current address advanced to add padding to ensure it's | ||
/// aligned to the `alignment` byte boundary. | ||
pub fn aligned_at(&self, alignment: u32) -> Self { | ||
pub const fn aligned_at(&self, alignment: u32) -> Self { | ||
let padding = (-(self.0 as i32) & (alignment as i32 - 1)) as u32; |
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.
Maybe comment something like
// Compute `-self.0` modulo `alignment`
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.
Done, and linked to Wikipedia where I first found the formula 😆
|
||
/// Test roundtrip of a heterogeneous list that doesn't need any internal padding. | ||
#[test] | ||
fn hlist_without_padding() { |
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.
Maybe assert the SIZE of the WitType
s?
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.
Good idea. Done inside the test_memory_roundtrip
function.
Allow them to be used in the `const` context that calculates the size of a heterogeneous list.
Link to the Wikipedia source.
Ensure that the non-trivial padding calculation is correct.
Because heterogeneous lists are recursive types, some special handling is needed. Alignment between internal elements must be calculated separately, because otherwise the alignment for the whole list (the tail) is used, which is not the same as the alignment as the next element. The same is true when reading and writing to memory, because the alignment between each element must be considered, and not the alignment for all the remaining elements.
There's only one place where padding is needed.
Make sure that they can be stored in memory and read from memory, and that they can be lowered into its flat layout and lifted back from it. Ensure that their internal element alignment is properly respected.
d73bee9
to
6957f3e
Compare
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.
ship it!
Motivation
The
WitType
,WitLoad
andWitStore
implementations forfrunk
heterogeneous lists (HCons
andHNil
types) weren't respecting the alignment of its elements when reading and writing to memory. This meant that they didn't correctly implement WIT's canonical ABI specification.Proposal
Respecting the alignment for its elements is not simple because the lists are represented by recursive types. The
HCons
type for example can check the alignment of its element (the "head") or of the rest of the list (the "tail"). However, the alignment of a list is defined as the largest alignment of its elements. So using the tail's alignment is not the correct solution. Instead, the alignment of the first element of the tail must be obtained.Also, in order to calculate the size of the list, internal padding must be included. The padding is calculated based on the alignment of each element. The solution is to calculate the padding using the size up until the current element, and the alignment of the next element. Unfortunately, it's not possible to write a
const fn
to calculate the size this way, so instead a workaround must be used. Since the maximum alignment is expected to be 8 bytes (for the largest flat types,i64
andf64
), we can enumerate the size's offset inside that 8-byte window, and implement the size calculation considering each case.A helper
SizeCalculation
trait was added that is used internally to calculate the size and to obtain the first element of the tail.Test Plan
Two unit tests were written to check the roundtrip to memory and to a flat layout of heterogeneous lists that require internal padding and that don't. The test with the list that requires padding would fail before the fix.
Links
This is unexpected work part of #906
Release Plan
Reviewer Checklist