Skip to content

[IRBuilder] Use multiple scratch locals for inputs - #9336

Open
tlively wants to merge 3 commits into
mainfrom
ir-builder-input-locals
Open

tlively wants to merge 3 commits into
mainfrom
ir-builder-input-locals

Conversation

@tlively

@tlively tlively commented Nov 1, 2026

Copy link
Copy Markdown
Member

Instead of using a single tuple-typed scratch local for multivalue control flow inputs, allocate one scalar scratch local per input value. This avoids eagerly packaging scalar inputs into a tuple on scope entry. It also avoids allocating extra tuple scratch locals to extract elements when inputs are consumed inside the scope.

Scalar input locals do have some downsides. Scopes that pass all of their inputs through untouched must now repackage them with tuple.make. Multivalue loop trampolines in fixLoopWithInput also still need a tuple scratch local to unpack branch values into the scalar input locals. However, real parameterized scopes usually consume their inputs as individual values. Scalar input locals can also be coalesced directly by standard local passes without tuple optimization, and they will allow individual parameters to be tunneled across nested scopes with different arities in a future commit.

Instead of using a single tuple-typed scratch local for multivalue control flow inputs, allocate one scalar scratch local per input value. This avoids eagerly packaging scalar inputs into a tuple on scope entry. It also avoids allocating extra tuple scratch locals to extract elements when inputs are consumed inside the scope.

Scalar input locals do have some downsides. Scopes that pass all of their inputs through untouched must now repackage them with tuple.make. Multivalue loop trampolines in fixLoopWithInput also still need a tuple scratch local to unpack branch values into the scalar input locals. However, real parameterized scopes usually consume their inputs as individual values. Scalar input locals can also be coalesced directly by standard local passes without tuple optimization, and they will allow individual parameters to be tunneled across nested scopes with different arities in a future commit.
@tlively
tlively requested a review from a team as a code owner November 1, 2026 07:30
@tlively
tlively requested review from stevenfontanella and removed request for a team November 1, 2026 07:30

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request updates the WebAssembly IR builder to support multi-value (tuple) control flow inputs by transitioning from a single scratch local to a vector of scratch locals (inputLocals). This allows proper lowering and extraction of tuple elements. The feedback suggests robustly handling potential allocation failures in fixLoopWithInput by changing its return type to Result<> and propagating errors using CHECK_ERR.

Comment thread src/wasm-ir-builder.h
Comment on lines +774 to +776
void fixLoopWithInput(Loop* loop,
Type inputType,
const std::vector<Index>& scratch);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Since fixLoopWithInput now allocates a new scratch local using addScratchLocal (which returns a Result<Index>), it can fail. To handle this failure robustly and avoid unchecked dereferencing of the Result, change the return type of fixLoopWithInput from void to Result<>.

Suggested change
void fixLoopWithInput(Loop* loop,
Type inputType,
const std::vector<Index>& scratch);
Result<> fixLoopWithInput(Loop* loop,
Type inputType,
const std::vector<Index>& scratch);

// the branch value to the appropriate scratch local.
fixLoopWithInput(loop, scope.inputType, scope.inputLocal);
// the branch value to the appropriate scratch locals.
fixLoopWithInput(loop, scope.inputType, scope.inputLocals);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Propagate the error returned by fixLoopWithInput using CHECK_ERR to ensure any allocation failures are handled safely.

      CHECK_ERR(fixLoopWithInput(loop, scope.inputType, scope.inputLocals));

Comment thread src/wasm/wasm-ir-builder.cpp Outdated
return popConstrainedChildren(children);
}

Result<> popInputs(Type inputType, std::vector<Expression*>& inputs) {

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 it necessary to mutate the inputs that the caller passed in? Can we return Result<std::vector<Expression*>> instead and remove the param? It looks like the only caller here passes an empty vector anyway.

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.

Yeah, good idea. The current interface is consistent with popConstrainedChildren, but that's not a public API like this is.

Comment thread src/wasm/wasm-ir-builder.cpp Outdated
if (inputType.isSingle()) {
list.push_back(builder.makeLocalSet(scratch[0], inner));
} else {
auto tupleScratch = *addScratchLocal(inputType);

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.

As Gemini mentioned we should check the error 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.

I'll add an assertion. It's impossible for addScratchLocal to fail in this case because we cannot have gotten to the point where we are fixing up loop inputs if we are not inside a function context.

@stevenfontanella

Copy link
Copy Markdown
Member

Scalar input locals do have some downsides. Scopes that pass all of their inputs through untouched must now repackage them with tuple.make. Multivalue loop trampolines in fixLoopWithInput also still need a tuple scratch local to unpack branch values into the scalar input locals.

Since there are tradeoffs involved here, can we benchmark this against a binary that uses block params?

@tlively

tlively commented Nov 2, 2026

Copy link
Copy Markdown
Member Author

Scalar input locals do have some downsides. Scopes that pass all of their inputs through untouched must now repackage them with tuple.make. Multivalue loop trampolines in fixLoopWithInput also still need a tuple scratch local to unpack branch values into the scalar input locals.

Since there are tradeoffs involved here, can we benchmark this against a binary that uses block params?

Maybe, but only by stacking #9300 and a not-yet-published follow-on PR on top of this and the following PRs, so it would be a little annoying. #9337 (the PR after this one) is the most important preliminary for landing #9300, and it becomes significantly more flexible in conjunction with this change.

Regarding the downsides: We won't ever have reason to produce scopes that pass their operands through. We will also likely skip producing loops with input parameters, and even if we did produce them, there would be very few of them in real-world modules (see #9300 (comment)). So the downsides only show up in very rare or contrived cases.

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.

2 participants