Skip to content

Fix i32 sign extension in MemoryPacking pass#8368

Open
sumleo wants to merge 2 commits intoWebAssembly:mainfrom
sumleo:fix-memory-packing-sign-extension
Open

Fix i32 sign extension in MemoryPacking pass#8368
sumleo wants to merge 2 commits intoWebAssembly:mainfrom
sumleo:fix-memory-packing-sign-extension

Conversation

@sumleo
Copy link
Contributor

@sumleo sumleo commented Feb 24, 2026

Summary

  • Fix sign extension of geti32() results when assigned to uint64_t/size_t in MemoryPacking pass
  • In visitMemoryInit (line 465-466): offsetVal and sizeVal sign-extend for values >= 0x80000000, causing valid memory.init instructions to be incorrectly replaced with traps
  • In createSplitSegments (line 713-714): same pattern corrupts start/end range calculation for segment splitting

The fix casts through uint32_t before widening, consistent with the pattern already used at lines 458 and 461 in the same function.

Test plan

  • Build succeeds with no warnings
  • All 309 GTest unit tests pass
  • All wasm-opt tests pass (MemoryPacking pass is exercised there)

if (offset && size) {
uint64_t offsetVal(offset->value.geti32());
uint64_t sizeVal(size->value.geti32());
uint64_t offsetVal(uint32_t(offset->value.geti32()));
Copy link
Member

Choose a reason for hiding this comment

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

These can all be in the form auto x = value->getUnsigned(), which would be simpler.

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 — switched to getUnsigned() for all four occurrences. Also cleaned up the existing uint32_t() casts on lines 458/461 to use getUnsigned() consistently.

Use getUnsigned() to properly zero-extend i32 values when computing
memory.init offset and size. Previously, geti32() returned a signed
int32_t that sign-extended when stored as uint64_t, causing values
>= 0x80000000 to produce incorrect overflow detection and range
calculations.

Also clean up existing uint32_t() casts in the same function to
use getUnsigned() consistently.
@sumleo sumleo force-pushed the fix-memory-packing-sign-extension branch from 0a19127 to a3eb99c Compare February 25, 2026 01:14
Copy link
Member

@kripken kripken 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!

Though before landing, I have a question for @tlively - how did we not see crashes with running this pass on memory64? All the places that assume a segment offset is 32-bit were wrong, weren't they?

And we do run this pass on memory64, each time e.g. emscripten compiles memory64 code?

@tlively
Copy link
Member

tlively commented Feb 26, 2026

My guess would be that most programs do not have static data with addresses at or above 0x8000000, so there is no difference between the incorrect sign extension and the correct zero extension.

@sumleo, would it be possible to add a regression test for this fix?

Add a test covering memory64 data segment offsets with the high bit set
(>= 0x80000000) to verify they are treated as unsigned values and not
sign-extended to 64 bits.
@sumleo
Copy link
Contributor Author

sumleo commented Feb 26, 2026

Added a regression test in test/lit/passes/memory-packing_memory64-high-addr.wast with 6 modules covering high-bit offsets (0x80000000, 0xFFFFFFFF) in memory64 contexts. The test exercises both active segment offset handling and passive segment memory.init operations where sign extension of i32 values would produce incorrect results.

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.

3 participants