Skip to content

Fix Unsubtyping and GTO for configureAll#8267

Merged
tlively merged 6 commits into
mainfrom
fix-gto-unsubypting-configure-all
Feb 10, 2026
Merged

Fix Unsubtyping and GTO for configureAll#8267
tlively merged 6 commits into
mainfrom
fix-gto-unsubypting-configure-all

Conversation

@tlively
Copy link
Copy Markdown
Member

@tlively tlively commented Feb 5, 2026

Take into account the implicit casts and conversions that happen on the
boundary with JS as well as the fact that JS can essentially read the
first field on descriptors with configured prototypes.

@tlively tlively marked this pull request as draft February 5, 2026 07:14
Take into account the implicit casts and conversions that happen on the
boundary with JS as well as the fact that JS can essentially read the
first field on descriptors with configured prototypes.
@tlively tlively force-pushed the fix-gto-unsubypting-configure-all branch from 3d31e7e to 224c6c9 Compare February 9, 2026 23:54
@tlively tlively marked this pull request as ready for review February 9, 2026 23:55
@tlively tlively requested a review from kripken February 9, 2026 23:55
@tlively tlively changed the title [WIP] Fix Unsubtyping and GTO for configureAll Fix Unsubtyping and GTO for configureAll Feb 10, 2026
Comment thread src/wasm-type.h Outdated

// Whether this is a struct type whose first field is immutable and a subtype
// of externref.
bool hasPossibleJSPrototypeField() const;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This feels like a somewhat specific property beyond the core type system - perhaps it can go in struct-utils.h?

Comment thread src/passes/GlobalTypeOptimization.cpp Outdated
if (!curr->value->type.isRef()) {
return;
}
auto exact = curr->value->type.getExactness();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

can move this into the if.

return;
}
for (auto func : Intrinsics(wasm).getConfigureAllFunctions()) {
for (auto type : wasm.getFunction(func)->getResults()) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Perhaps add a comment here as to why only the results matter?

// This descriptor will expose a prototype to JS, so we must keep
// it.
noteDescriptor(heapType, *desc);
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why is the cast symmetric in params and results, but the prototype only appears for the results?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The parameters are values flowing from JS to Wasm and the results are values flowing from Wasm to JS. Only JS->Wasm involves an implicit cast, only Wasm->JS involves an implict extern.convert_any, and only Wasm->JS makes a configured prototype visible to JS.

collectedInfo.casts.insert({src, dst});
}
dsts.clear();
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this an unrelated fix?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

No. We now have casts that were noted before the parallel function analysis. This is merging them into the parallel function analysis sets so we don't end up with unnecessary duplicated entries.


(module
(rec
;; We can optimize out the externref field on the field because it is never
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
;; We can optimize out the externref field on the field because it is never
;; We can optimize out the externref field on the struct because it is never

Comment on lines +293 to +294
;; Externalizing a reference may in general make it available to JS and
;; and force us to keep fields holding exposed prototypes.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
;; Externalizing a reference may in general make it available to JS and
;; and force us to keep fields holding exposed prototypes.

unneeded duplicate from above

(type $struct-not-sent (descriptor $desc-not-sent) (struct))
(type $desc-not-sent (describes $struct-not-sent) (struct (field externref)))

;; Receiving a type from JS as means it is implicitly cast from any.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What implications does that have? Unlike the above comments, it's not clear what to expect in the output here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

From the test function where this type is used:

    ;; There is an implicit cast from any to $super-param on the boundary. This
    ;; combined with $sub-param flowing into any here means that the subtyping
    ;; must be maintained.

Do you have a preference for what information we duplicate (if any) between the comments on the types and the comments in the functions?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It might be nice to keep comments near the test outputs. So in this case, up here. I don't feel strongly though if it's a lot of work.

@tlively tlively enabled auto-merge (squash) February 10, 2026 20:35
@tlively tlively merged commit e103d6d into main Feb 10, 2026
17 checks passed
@tlively tlively deleted the fix-gto-unsubypting-configure-all branch February 10, 2026 21:14
tlively added a commit that referenced this pull request Feb 11, 2026
@kripken
Copy link
Copy Markdown
Member

kripken commented Feb 11, 2026

I see fuzzer errors every few thousand iterations here, all when ./lit/passes/gto-jsinterop.wast is picked up, all complaining about shared-everything. The errors are on generating the wasm, so they can't be reduced. E.g.:

[wasm-validator error in function func] unexpected false: all used types should be allowed, on 
func
[wasm-validator error in function func_21] unexpected false: ref.null requires additional features , on 
(ref.null (shared noextern))
[--enable-shared-everything]
[wasm-validator error in function func_21] unexpected false: ref.null requires additional features , on 
(ref.null (shared noextern))
[--enable-shared-everything]
[wasm-validator error in function func_21] unexpected false: Block type requires additional features, on 
(block (result (ref $desc))
 (local.set $22
  (pop (ref eq))
 )
 (ref.cast (ref $desc)
  (global.get $global$)
 )
)
[--enable-shared-everything]

If you want I can bundle up one of the input.dat binary files for a full testcase?

@kripken
Copy link
Copy Markdown
Member

kripken commented Feb 11, 2026

E.g. seeds 17631794481705257384, 13075011481627043541 - hopefully one of those works for you @tlively ?

The fuzzer disables shared-everthing but somehow that initial content is still picked up?

@tlively
Copy link
Copy Markdown
Member Author

tlively commented Feb 11, 2026

Thanks, will take a look.

tlively added a commit that referenced this pull request Feb 11, 2026
@tlively
Copy link
Copy Markdown
Member Author

tlively commented Feb 11, 2026

Those seeds don't repro for me locally, but let's see if my fuzzer finds anything while I'm in meetings.

@kripken
Copy link
Copy Markdown
Member

kripken commented Feb 11, 2026

Ah, I think I see what's going on. You added this testcase:

(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $struct (descriptor $desc) (struct))
    (type $struct (descriptor $desc) (struct))
    ;; Maybe we will support shared prototypes someday.
    ;; CHECK:       (type $desc (describes $struct) (struct (field (ref null (shared extern)))))
    (type $desc (describes $struct) (struct (field (ref null (shared extern)))))
  )

  ;; CHECK:      (type $2 (func))

  ;; CHECK:      (func $externalize (type $2)
  ;; CHECK-NEXT:  (local $struct (ref null $struct))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (extern.convert_any
  ;; CHECK-NEXT:    (local.get $struct)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $externalize
    (local $struct (ref null $struct))
    (drop
      (extern.convert_any
        (local.get $struct)
      )
    )
  )
)

That only uses shared-everything in a struct field, and we don't seem to validate that. No error here, but there should be:

$ bin/wasm-opt initial.wat -all --disable-shared-everything
warning: no passes specified, not doing any work
warning: no output file specified, not emitting output

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