Skip to content

[StackIR] Drop a set/get pair across a symmetric operation - #9162

Open
artemkulyk wants to merge 1 commit into
WebAssembly:mainfrom
artemkulyk:stackir-commutative-operand
Open

artemkulyk wants to merge 1 commit into
WebAssembly:mainfrom
artemkulyk:stackir-commutative-operand

Conversation

@artemkulyk

@artemkulyk artemkulyk commented Sep 26, 2026 •

Copy link
Copy Markdown

local2Stack gives up when a value sits between a local.set and the matching
local.get. If that value and the get are consumed by the same operation whose
operands can be swapped, the pair can be removed anyway.

This also removes the one size regression of the late optimize-instructions
round (4 bytes on a float test module), and is a small win on average.

@artemkulyk
artemkulyk requested a review from a team as a code owner September 26, 2026 13:05
@artemkulyk
artemkulyk requested review from tlively and removed request for a team September 26, 2026 13:05
@artemkulyk
artemkulyk force-pushed the stackir-commutative-operand branch 2 times, most recently from 1e4946a to f259b19 Compare September 26, 2026 13:25

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

Nice! Just a couple comments.

Comment thread src/wasm/wasm-stack-opts.cpp Outdated

// Whether a stack instruction is a binary operation on integers that gives the
// same result whichever operand order it is computed in.
static bool isCommutativeIntBinary(StackInst* inst) {

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.

Please move this to src/ir/properties.h.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done. Added an integer-only Properties::isSymmetricInt and made isSymmetric use it. Kept it integer-only because swapping float min/max operands can change NaN bits.

Comment thread src/wasm/wasm-stack-opts.cpp Outdated
auto setIndex = values[j];
if (setIndex == null) {
break;
if (intervening) {

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 function is already enormous. Would it be possible to factor out the new logic into a helper function?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done, factored it into isConsumedWithInterveningValue.

@artemkulyk
artemkulyk force-pushed the stackir-commutative-operand branch from f259b19 to 7b0986a Compare September 28, 2026 17:42
Comment thread src/wasm/wasm-stack-opts.cpp Outdated
}
auto* binary = inst->origin->dynCast<Binary>();
return binary && Properties::isSymmetricInt(binary) &&
getNumConsumedValues(inst) == 2;

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 line is unneeded: a Binary always consumes 2 values.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done, removed the redundant check.

Comment thread src/ir/properties.h Outdated
// Whether a binary operation on integers is symmetric, that is, whether it
// gives the same result whichever operand order it is computed in. This does
// not include floating-point operations: swapping their operands can change
// the NaN bits of the 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.

Wait, is this actually true for floats? If it is, then OptimizeInstruction's (ancient) call to isSymmetrical is faulty.

@tlively tlively Sep 28, 2026 •

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.

Implementations are allowed but not required to make it so that swapping the NaN operands of a floating point operation could affect the output bits. Since the spec makes no guarantees that depend on the order of the inputs, OptimizeInstructions is fine and we could be more aggressive here.

https://webassembly.github.io/spec/core/exec/numerics.html#nan-propagation

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.

Sounds good, then let's do this for floats here as well.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good catch. Regular Wasm min/max is symmetric here, so I removed isSymmetricInt and use the existing isSymmetric.

auto* binary = inst->origin->dynCast<Binary>();
return binary && Properties::isSymmetricInt(binary) &&
getNumConsumedValues(inst) == 2;
}

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 can also check for RefEq

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Added RefEq and a test for it.

@artemkulyk
artemkulyk force-pushed the stackir-commutative-operand branch 2 times, most recently from c2d7d24 to 0f9b07b Compare September 28, 2026 21:40
Comment thread src/wasm/wasm-stack-opts.cpp Outdated
// Whether the local.get at getIndex is consumed together with exactly one
// intervening value by an operation whose operands are interchangeable, so
// that removing the set/get pair only swaps them.
bool StackIROptimizer::isConsumedWithInterveningValue(Index getIndex) {

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 function doesn't look at the intervening value, though, right? A better name might be isConsumedBySymmetricOp or something like that.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good point. Renamed it to isConsumedBySymmetricOp and updated the comment. I also added coverage for a float symmetric op and the two-intervening-values case.

local2Stack gives up when a value sits between a local.set and the matching
local.get. If that value and the get are consumed by the same operation
whose operands can be swapped, the pair can be removed anyway.

This also removes the one size regression of the late optimize-instructions
round (4 bytes on a float test module), and is a small win on average.
@artemkulyk
artemkulyk force-pushed the stackir-commutative-operand branch from 0f9b07b to 900f480 Compare September 28, 2026 22:10
@artemkulyk artemkulyk changed the title [StackIR] Drop a set/get pair across a commutative operand [StackIR] Drop a set/get pair across a symmetric operation Sep 28, 2026

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

LGTM! Can you run the fuzzer for a few thousand iterations to make sure it doesn't find any problems? (scripts/fuzz_opt.py or scripts/monitor_fuzz.py -j --max-iters=5000)

@artemkulyk

Copy link
Copy Markdown
Author

Ran 10000 fuzz iterations (8 workers, monitor_fuzz.py), no problems found.

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