Skip to content

Fix TypeGeneralizing stack corruption and crash#8310

Merged
tlively merged 1 commit into
WebAssembly:mainfrom
sumleo:fix-type-generalizing-stack
Feb 12, 2026
Merged

Fix TypeGeneralizing stack corruption and crash#8310
tlively merged 1 commit into
WebAssembly:mainfrom
sumleo:fix-type-generalizing-stack

Conversation

@sumleo
Copy link
Copy Markdown
Contributor

@sumleo sumleo commented Feb 12, 2026

Summary

Two bugs in the experimental TypeGeneralizing pass's backward analysis:

1. visitStructSet pushes non-ref field types onto the stack

visitStructSet unconditionally pushes the struct field type as a type requirement onto the backward analysis stack (line 690). When the field is a non-reference type (i32, f64, etc.), this corrupts the stack because non-ref producers (like visitConst, visitBinary) are no-ops that don't pop. The spurious non-ref value on the stack causes subsequent pop() calls to retrieve wrong type requirements.

The analogous methods all correctly guard with isRef():

  • visitArraySet (line 792): if (elemType.isRef())
  • visitStructNew (line 620): if (field.type.isRef())
  • handleCall (line 364): if (param.isRef())

Fix: Add if (fieldType.isRef()) guard before pushing.

2. visitRefAs crashes on Type::none from empty stack

When the backward analysis stack is empty (no downstream consumer imposes a type requirement), pop() returns Type::none. visitRefAs then calls type.getHeapType() on Type::none, triggering assert(isRef()) — crashing on any ref.as_non_null whose result is dropped.

Fix: Check for Type::none before accessing heap type, and propagate "no requirement" through.

Both bugs are in the experimental (not-yet-sound) pass and do not affect production optimization pipelines.

Test plan

  • New lit test type-generalizing-fixes.wast covering:
    • struct.set on non-ref field followed by ref field (stack alignment)
    • drop(ref.as_non_null(...)) (empty stack crash)
    • drop(any.convert_extern(extern.convert_any(...))) (empty stack with convert ops)
  • All 309 unit tests pass

Two bugs in the experimental TypeGeneralizing pass:

1. visitStructSet unconditionally pushed the field type onto the
   backward analysis stack, even for non-reference fields (i32,
   f64). Since non-ref producers (visitConst, etc.) are no-ops that
   don't pop, this corrupted the stack alignment, causing subsequent
   local.get visitors to pop wrong type requirements. Add an isRef()
   guard to match the pattern used by visitArraySet, visitStructNew,
   and handleCall.

2. visitRefAs called type.getHeapType() on Type::none (returned by
   pop() when the stack is empty), triggering an assertion failure.
   This happens when ref.as_non_null's result is dropped. Add a
   Type::none check to propagate "no requirement" through RefAs.
@kripken kripken requested a review from tlively February 12, 2026 19:16
Copy link
Copy Markdown
Member

@tlively tlively left a comment

Choose a reason for hiding this comment

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

Thanks!

@tlively tlively merged commit e3adbad into WebAssembly:main Feb 12, 2026
17 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