Skip to content

fix(bindgen): ensure moving storage pointers by abi size#1452

Merged
vados-cosmonic merged 1 commit into
bytecodealliance:mainfrom
andreiltd:fix/flat-variant-lowering
May 8, 2026
Merged

fix(bindgen): ensure moving storage pointers by abi size#1452
vados-cosmonic merged 1 commit into
bytecodealliance:mainfrom
andreiltd:fix/flat-variant-lowering

Conversation

@andreiltd
Copy link
Copy Markdown
Member

The patch includes lower and lift side fixes. The lower side fix is basically: treat ctx.storagePtr as a canon abi cursor and move it by abi size rather than stopping where nested lift ended.

The general pattern is this:

  • align cursor
  • remember slot start
  • lower nested value
  • advance cursor to at least slot start + abi slot size

The last step is really a gist of it, because nested values can write less than their abi slot size.

For example for records that looks like this:

const originalPtr = ctx.storagePtr;

 for each field:
   align ctx.storagePtr to field align
   fieldPtr = ctx.storagePtr
   lower field
   ctx.storagePtr = Math.max(ctx.storagePtr, fieldPtr + fieldSize)

 ctx.storagePtr = Math.max(ctx.storagePtr, originalPtr + recordSize)
 align ctx.storagePtr to record align

The lifting is basically the same but it accounts for direct params reading so the algorithm annoyingly checks if pointers are not undefined. I think there is a refactor opportunity here: split direct params and memory indirection modes but I wanted to focus on correctness fixes here.

The transpiler fix just ensures that each nested element gets its own abi metadata rather than using parent metadata: so for instance record gets fields meta and tuples get each element meta.

I first across this issue for descriptor types:

 record directory-entry {
   type: variant { directory, file, symlink, unknown, ... },
   name: string,
 }

If the variant case has no payload, old lowering/lifting left the cursor too early, and the following name: string was read/written at the wrong offset.

@andreiltd andreiltd requested a review from vados-cosmonic as a code owner May 8, 2026 08:37
Copy link
Copy Markdown
Collaborator

@vados-cosmonic vados-cosmonic left a comment

Choose a reason for hiding this comment

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

LGTM 🚀

Thanks for fixing this and updating the tests -- glad those bits were easy to extend. Tests look good to me (and feel free to refactor/aggressively cut if any of it awkward)

The patch includes lower and lift side fixes. The lower side fix is
basically: treat ctx.storagePtr as a canon abi cursor and move it by abi
size rather than stopping where nested lift ended.

The general pattern is this:
- align cursor
- remember slot start
- lower nested value
- advance cursor to at least slot start + abi slot size

The last step is really a gist of it, because nested can values can
write less than their abi slot size.

For example for records that looks like this:

```
const originalPtr = ctx.storagePtr;

 for each field:
   align ctx.storagePtr to field align
   fieldPtr = ctx.storagePtr
   lower field
   ctx.storagePtr = Math.max(ctx.storagePtr, fieldPtr + fieldSize)

 ctx.storagePtr = Math.max(ctx.storagePtr, originalPtr + recordSize)
 align ctx.storagePtr to record align
```

The lifting is basically the same but it accounts for direct params
reading so the algorithm annoyingly checks if pointers are not
undefined. I think there is a refactor opportunity here: split direct
params and memory indirection modes but I wanted to focus on correctness
fixes here.

The transpiler fix just ensures that each nested element gets its own
abi metadata rather than using parent metadata: so for instance record
gets fields meta and tuples get each element meta.
Comment thread crates/js-component-bindgen/src/intrinsics/lift.rs Outdated
@andreiltd andreiltd force-pushed the fix/flat-variant-lowering branch from 274c0c1 to 124604e Compare May 8, 2026 08:51
@vados-cosmonic
Copy link
Copy Markdown
Collaborator

NOTE: I'm force merging this as the failures in CI (on only macOS) seem to be unrelated:

dlopen /Users/runner/.cache/puppeteer/chrome/mac_arm-139.0.7258.68/chrome-mac-arm64/Google Chrome for Testing.app/Contents/MacOS/../Frameworks/Google Chrome for Testing Framework.framework/Versions/139.0.7258.68/Google Chrome for Testing Framework: dlopen(/Users/runner/.cache/puppeteer/chrome/mac_arm-139.0.7258.68/chrome-mac-arm64/Google Chrome for Testing.app/Contents/MacOS/../Frameworks/Google Chrome for Testing Framework.framework/Versions/139.0.7258.68/Google Chrome for Testing Framework, 0x0105): tried: '/Users/runner/.cache/puppeteer/chrome/mac_arm-139.0.7258.68/chrome-mac-arm64/Google Chrome for Testing.app/Contents/MacOS/../Frameworks/Google Chrome for Testing Framework.framework/Versions/139.0.7258.68/Google Chrome for Testing Framework' (no such file), '/System/Volumes/Preboot/Cryptexes/OS/Users/runner/.cache/puppeteer/chrome/mac_arm-139.0.7258.68/chrome-mac-arm64/Google Chrome for Testing.app/Contents/MacOS/../Frameworks/Google Chrome for Testing Framework.framework/Versions/139.0.7258.68/Google Chrome for Testing Framework' (no such file), '/Users/runner/.cache/puppeteer/chrome/mac_arm-139.0.7258.68/chrome-mac-arm64/Google Chrome for Testing.app/Contents/MacOS/../Frameworks/Google Chrome for Testing Framework.framework/Versions/139.0.7258.68/Google Chrome for Testing Framework' (no such file).

@vados-cosmonic vados-cosmonic merged commit 18ad87b into bytecodealliance:main May 8, 2026
67 of 73 checks passed
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.

2 participants