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

Fix internal alignment in frunk heterogeneous lists #929

Merged
merged 6 commits into from
Aug 4, 2023

Conversation

jvff
Copy link
Contributor

@jvff jvff commented Aug 2, 2023

Motivation

The WitType, WitLoad and WitStore implementations for frunk heterogeneous lists (HCons and HNil 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 and f64), 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

  • All good!
  • Need to bump the major/minor version number in the next release of the crates.
  • Need to update the developer manual.
  • This PR is adding or removing Cargo features.
  • Release is blocked and/or tracked by other issues (see links above)

Reviewer Checklist

  • The title and the summary of the PR are short and descriptive.
  • The proposed solution achieves the goals stated in the PR.
  • The test plan is reproducible and provides sufficient coverage.
  • The release plan is adequate.
  • The commits correspond to distinct logical changes.
  • The code follows the coding guidelines.
  • The proposed changes look correct.
  • The CI is passing.
  • All of the above!

@jvff jvff added the bug Something isn't working label Aug 2, 2023
@jvff jvff added this to the Devnet milestone Aug 2, 2023
@jvff jvff requested a review from ma2bd August 2, 2023 14:19
@jvff jvff self-assigned this Aug 2, 2023
@jvff jvff force-pushed the fix-hlist-internal-alignment branch from 762f39b to 039a590 Compare August 2, 2023 23:51
@jvff jvff requested a review from ma2bd August 2, 2023 23:58
Copy link
Contributor

@ma2bd ma2bd left a comment

Choose a reason for hiding this comment

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

nice!

linera-witty/src/type_traits/implementations/frunk.rs Outdated Show resolved Hide resolved
@jvff jvff force-pushed the fix-hlist-internal-alignment branch from 039a590 to d73bee9 Compare August 3, 2023 17:21
@jvff jvff requested a review from ma2bd August 3, 2023 17:21
/// 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 {
Copy link
Contributor

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;
Copy link
Contributor

@ma2bd ma2bd Aug 3, 2023

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`

Copy link
Contributor Author

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() {
Copy link
Contributor

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 WitTypes?

Copy link
Contributor Author

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.

jvff added 6 commits August 3, 2023 21:24
Allow them to be used in the `const` context that calculates the size of
a heterogeneous list.
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.
@jvff jvff force-pushed the fix-hlist-internal-alignment branch from d73bee9 to 6957f3e Compare August 3, 2023 21:24
@jvff jvff requested a review from ma2bd August 3, 2023 23:37
Copy link
Contributor

@ma2bd ma2bd left a comment

Choose a reason for hiding this comment

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

ship it!

@jvff jvff merged commit 074c130 into linera-io:main Aug 4, 2023
@jvff jvff deleted the fix-hlist-internal-alignment branch August 4, 2023 12:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

2 participants