Skip to content

Accurately mark CallIndirect and CallRef as calls in Inlining - #9146

Open
gkdn wants to merge 1 commit into
WebAssembly:mainfrom
gkdn:inlining-indirect-calls
Open

gkdn wants to merge 1 commit into
WebAssembly:mainfrom
gkdn:inlining-indirect-calls

Conversation

@gkdn

@gkdn gkdn commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

In Binaryen's Inlining pass, multi-use functions (refs > 1) are eligible for inlining at -O3 only if they have no calls and no loops (!hasCalls && !hasLoops), as leaf inlining eliminates function call overhead without code explosion.

However, FunctionInfoScanner previously omitted CallIndirect and CallRef from hasCalls tracking. In WasmGC / Java, polymorphic method dispatch is compiled as call_ref. Widely-used utilities containing virtual calls (such as String.valueOf(Object), which performs a null-check and an indirect call to x.toString()) were misclassified as leaf functions and aggressively inlined into thousands of call sites. Inlining non-leaf functions with call_ref does not eliminate the indirect call; it only duplicates the caller-side parameter preparation, null checks, and vtable indexing.

This change marks hasCalls = true in visitCallIndirect and visitCallRef so that multi-caller non-leaf functions containing indirect/reference calls are not duplicated across the binary.

In Binaryen's Inlining pass, multi-use functions (refs > 1) are eligible for
inlining at -O3 only if they have no calls and no loops (!hasCalls && !hasLoops),
as leaf inlining eliminates function call overhead without code explosion.

However, FunctionInfoScanner previously omitted CallIndirect and CallRef from
hasCalls tracking. In WasmGC / Java, polymorphic method dispatch is compiled
as call_ref. Widely-used utilities containing virtual calls (such as
String.valueOf(Object), which performs a null-check and an indirect call to
x.toString()) were misclassified as leaf functions and aggressively inlined
into thousands of call sites. Inlining non-leaf functions with call_ref does
not eliminate the indirect call; it only duplicates the caller-side parameter
preparation, null checks, and vtable indexing.

This change marks hasCalls = true in visitCallIndirect and visitCallRef so that
multi-caller non-leaf functions containing indirect/reference calls are not
duplicated across the binary.
@gkdn
gkdn requested a review from a team as a code owner September 23, 2026 04:24
@gkdn
gkdn requested review from tlively and removed request for a team September 23, 2026 04:24

@tlively tlively 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.

This is also strictly beneficial on the Emscripten benchmark suite with a geomean of -0.129% uncompressed and a smaller reduction compressed. Some benchmarks shrink by as much as a few percent uncompressed.

cc @kripken for a second look.

@kripken

kripken commented Sep 23, 2026

Copy link
Copy Markdown
Member

@tlively did you see a speed difference, or just size?

If the speed looks good, lgtm. I believe the old approach seemed to work well at the time (hence the long comment that is now removed), but many things changed in the optimizer since then 😄 so I can easily believe this is the better heuristic now.

This branch has not been deployed

No deployments
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