Skip to content

Add a MakeSharedObjects pass - #8953

Merged
tlively merged 10 commits into
WebAssembly:mainfrom
tlively:make-shared
Jul 30, 2026
Merged

Add a MakeSharedObjects pass#8953
tlively merged 10 commits into
WebAssembly:mainfrom
tlively:make-shared

Conversation

@tlively

@tlively tlively commented Jul 29, 2026

Copy link
Copy Markdown
Member

This pass makes all functions unshared and all structs and arrays shared. It avoids validation errors from storing unshared function references in shared structs and arrays by replacing all function references with i31 references holding indices into a function table. Indirect calls and casts are updated to extract their function reference from the table.

This new pass will make it easier to convert unshared Wasm GC programs to run on the shared heap for testing purposes. It will also lower shared functions emitted by toolchains to allow them to run on prototypes that do not support shared functions.

This pass makes all functions unshared and all structs and arrays shared. It avoids validation errors from storing unshared function references in shared structs and arrays by replacing all function references with i31 references holding indices into a function table. Indirect calls and casts are updated to extract their function reference from the table.

This new pass will make it easier to convert unshared Wasm GC programs to run on the shared heap for testing purposes. It will also lower shared functions emitted by toolchains to allow them to run on prototypes that do not support shared functions.
@tlively
tlively requested a review from a team as a code owner July 29, 2026 21:08
@tlively
tlively requested review from kripken and removed request for a team July 29, 2026 21:08
@tlively

tlively commented Jul 29, 2026

Copy link
Copy Markdown
Member Author

Looks like there's a determinism bug with the type renaming when multiple types get merged. Will look into that tomorrow.

Comment thread src/passes/MakeSharedObjects.cpp
Comment thread src/wasm-traversal.h
Comment thread src/wasm-type.h
(exact == Exact && !heapType.isBasic() ? ExactMask : 0)) {
assert(!(heapType.getID() &
(TupleMask | NullMask | (heapType.isBasic() ? 0 : ExactMask))));
assert(!heapType.isBasic() || exact == Inexact);

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 relax this?

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 when we map old function types to i31, the general type updating code tries to update the heap type but keep exactness. There would be other ways to solve this, but this seemed simple enough.

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.

I ended up reverting this and checking whether the new type is basic before trying to make it exact.

Comment thread src/wasm-type.h Outdated
if (auto super = type.getDeclaredSuperType()) {
setSubType(i, map(*super));
if (auto mapped = map(*super); !mapped.isBasic()) {
setSubType(i, map(*super));

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
setSubType(i, map(*super));
setSubType(i, mapped);

Or is there some reason not to?

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.

Oh yeah, that's an oversight.

Comment thread src/wasm-type.h Outdated
if (auto desc = type.getDescriptorType()) {
setDescriptor(i, map(*desc));
if (auto mapped = map(*desc); !mapped.isBasic()) {
setDescriptor(i, map(*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.

Maybe add a comment? I assume this can happen if both of a descriptor-described pair are mapped to a basic type?

@tlively
tlively requested a review from kripken July 30, 2026 04:44

@kripken kripken left a comment

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.

lgtm % comments and also I am only 25% through the tests

Comment thread src/ir/type-updating.cpp
// Multiple old type indices are being merged into the same new type.
// Deterministically keep the lesser index.
newIt->second = std::min(newIt->second, it->second);
}

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.

Maybe split this out from this PR?

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.

Sure, I'll split it out once I've verified that the fix is correct by having the tests pass here.


GlobalTypeRewriter rewriter(*getModule(), getPassOptions().worldMode);
rewriter.mapTypes(oldToNew);
rewriter.mapTypeNamesAndIndices(oldToNew);

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.

We discussed having a generic helper for this type of rewriting - which could be useful in e.g. StringLowering as well. Is that feasible?

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.

We can definitely create a helper, but I'm not totally clear on where the boundaries of the shared logic should be. What do you have in mind, specifically?

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.

Basically, you are replacing function types with i31, and StringLowering wants to replace string types with extern. Both can use an API similar to the current TypeMapper, but that preserves rec group shapes (rather than the default TypeMapper logic which creates a new singleton rec group). So this new API just replaces types "in place", without reshaping rec groups.

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.

Do you mind if we do this refactoring as an immediate follow-up?

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.

sgtm, no rush

Comment thread src/wasm-type.h
map(t.getHeapType()), t.getNullability(), t.getExactness());
auto mapped = map(t.getHeapType());
auto null = t.getNullability();
auto exact = mapped.isBasic() ? Inexact : t.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.

Suggested change
auto exact = mapped.isBasic() ? Inexact : t.getExactness();
// Basic types cannot be exact.
auto exact = mapped.isBasic() ? Inexact : t.getExactness();

;; TODO: Handle exnref, contref.
)

(module

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
(module
;; Test recursive function references.
(module

I think?

;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $multiple-ref-func
;; Mapping of functions to i31 values should be consistent.

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.

Shouldn't a table be generated that maps these indices to functions?

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.

We only add the table if it was ever actually used. I could add a call or cast to this module to make sure the table gets generated, if you want.

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.

If later tests show the table then there is no need, but maybe add a comment.

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.

Done

)
)

(module

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
(module
;; Test function indexing in global locations
(module

I think?

;; CHECK: (elem $funcs (i32.const 0))

;; CHECK: (func $ref-test-func (type $1) (param $0 (ref null (shared i31))) (result i32)
;; CHECK-NEXT: (ref.test (ref (shared i31))

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.

Wait, is this sound? This would also accept an i31 that wasn't a funcref in a past life.

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.

Yes, thanks to the magic of separate type hierarchies. It's impossible for an i31 to reach a cast on the function hierarchy in the original module, so all the i31s that reach these transformed casts must represent function references.

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.

Ah, right.

(module
(type $sig (func (param i32) (result i32)))

;; Test that types in if, loop, result

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.

wip comment?


;; Test that types in if, loop, result

;; Test that function types in globals, tables, element segments, and tags are updated.

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.

Wasn't this tested above?

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.

Kind of. There was a previous test checking that the instructions were updated, but this one is checking that the types are updated. The previous test covered that for the case of funcref, but this one covers it for the case of defined function types.

@kripken kripken left a comment

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.

lgtm % comments

@tlively
tlively enabled auto-merge (squash) July 30, 2026 22:34
@tlively
tlively merged commit 26d46ed into WebAssembly:main Jul 30, 2026
16 checks passed
@tlively
tlively deleted the make-shared branch July 30, 2026 23:02
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