Skip to content

Conversation

@kripken
Copy link
Member

@kripken kripken commented Oct 24, 2025

Defined functions remain exact, but imported ones are inexact.

This is a step along the recent Custom Descriptors spec changes.

  • New RefFunc::finalize and Literal::makeFunc variants get the module, and look up
    the type there.
  • New Builder::makeRefFunc variant gets a Type and applies it. The HeapType
    variant does a lookup on the module (so the Type one is more efficient/applicable
    if the IR is not fully built yet).
  • ReFinalize now updates RefFunc types (following the pattern of a few other places).
  • C and JS APIs now assume RefFuncs are created after imported functions (so we can
    look up the type of the import; see changelog, this seems the least-annoying way to
    update here, avoiding new APIs, and less breakage for users - hopefully none, all our
    tests here pass as is).
  • wasm-split adds a cast when a function becomes an inexact import.
  • Fix GUFA to handle inexact function literals.
  • Update types in passes and fuzzer as needed.

tlively and others added 30 commits October 8, 2025 13:44
Update the Literal constructors for funcrefs to take Type instead of
HeapType to allow them to be given inexact function references types
when the referenced function is an import. Use the new capability to
give references to imported functions inexact types in GUFA. Add a test
where this change fixes a misoptimization as well as tests where this
change simply changes the nature of the misoptimization. Future PRs will
fix these tests.
@kripken
Copy link
Member Author

kripken commented Nov 4, 2025

Fuzzer noticed that we didn't type imported ref.funcs correctly. Last two (non-merge) commits fix and test that.

Comment on lines +651 to +652
// This is imported, so it might be anything of the proper type.
addRoot(curr);
Copy link
Member

Choose a reason for hiding this comment

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

Don't we still want to track that this is a reference to the imported function? It would just have an inexact type.

Copy link
Member Author

Choose a reason for hiding this comment

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

But without knowing the identity, I think we can misoptimize? Imagine we have equality for a second, then ref.func a == ref.func a is definitely 1, but ref.func a == ref.func b is not necessarily 0 (can have duplicate imports).

Copy link
Member

Choose a reason for hiding this comment

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

But functions references cannot be compared for equality, so there is nothing to misoptimize, unless I'm missing something.

Copy link
Member Author

Choose a reason for hiding this comment

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

I did say "imagine" 😆

But, while we don't have ref.eq on functions in wasm userspace, we do have optimizations that compare functions in other ways. E.g. folding an if with ref.eq arms, or GUFA inferences. I admit I don't see an actual bug atm in our optimizer, but a future one is conceivable.

Copy link
Member

Choose a reason for hiding this comment

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

I can see how optimizations might see that two references are the same and e.g. merge two equivalent if arms or something like that, but I still don't see how we could ever have an optimization that does something unsafe when reasoning that two different function references are different. Can we at least consider changes here in a separate PR?

Copy link
Member

Choose a reason for hiding this comment

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

Yes, agreed. I'm not opposed to being conservative here to be on the safe side. But we should keep the less-conservative status quo in this PR to make sure we're not unexpectedly regressing optimizations due to just the introduction of inexact imported functions.

Copy link
Member Author

Choose a reason for hiding this comment

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

Keeping the status quo does mean keeping the known cases of invalid optimization we have today, including the new gufa.wast tests here, like

(module
  (type $func (sub (func)))
  (type $sub (sub $func (func)))

  (import "" "" (func $f (type $func)))

  (func $test (export "test") (result i32)
    (ref.test (ref $sub)
      (ref.func $f)
    )
  )
)

We misoptimize that to 0 before the fix, because we think imported function literals are actual concrete functions. Given such an actual function, we don't need exactness to know that it will fail that test.

The fuzzer can find this after this PR - perhaps because of the new testcases? Or perhaps because of the companion fuzzing PR #7963, which should really land as it increases coverage enough to find those recent vulnerabilities. So while I see your point, our options seem to be

  • Land this PR as is, fixing the misoptimization but potentially regressing optimizations on imported function references.
  • Land this without fixing the misoptimization, which will not regress any opts, and work around it in the fuzzer, maybe not landing Fuzzer: Merge and optimize even with closed world in Two() #7963, maybe marking new tests as non-fuzzable, maybe both.
  • Fix the misoptimization otherwise, e.g., GUFA/possible-contents could special case function literals in various places.

Copy link
Member

Choose a reason for hiding this comment

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

What if GUFA has a function literal, but its type isn't exact?

Copy link
Member Author

Choose a reason for hiding this comment

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

It is still a literal. The code assumes that a literal is an actual identifiable thing, like 42 or the function "foo", and unlike a global "bar" (whose value we don't know).

We could special-case the code to make it treat an inexact funcref as "a literal, but not really; more like a global." But that won't work once we have exact function imports - the same problem would happen with exact ones.

Copy link
Member

Choose a reason for hiding this comment

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

I don't see the problem. Once we have exact imports, then the Literal for the imported function would have an exact type iff the import is exact. GUFA would then look at the literal type to see whether casts would succeed or fail, for example. The changes to support inexact function literals are already in this PR.

@@ -0,0 +1,36 @@
;; Import a function of type $C as type $A, cast to $A, $B, $C. All those casts
;; should succeed. Exact casts, however TODO
Copy link
Member

Choose a reason for hiding this comment

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

Is this still TODO?

Copy link
Member Author

Choose a reason for hiding this comment

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

Oops, just some old text. These casts become exact. Cleaned up now.

Copy link
Member

Choose a reason for hiding this comment

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

Let's add some exact casts to the test to show that they work as expected. I don't think the test currently demonstrates that.

Copy link
Member Author

Choose a reason for hiding this comment

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

Sure, added.

Comment on lines +1232 to +1234
;; CHECK-NEXT: (ref.test (ref (exact $func))
;; CHECK-NEXT: (ref.func $f)
;; CHECK-NEXT: )
Copy link
Member

Choose a reason for hiding this comment

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

The comment suggests we should have been able to optimize this.

Copy link
Member Author

Choose a reason for hiding this comment

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

Good point, I think we need to look at finality in GUFA somehow. I added a TODO.

Copy link
Member

Choose a reason for hiding this comment

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

I suspect this will be fixed by using a non-exact Literal for references to function imports.

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