Repository navigation
Conversation
Extend Properties::getSignExtValue, getSignExtBits, and getAlmostSignExt to 64-bit integers and unary extend{8,16,32}_s operations. Fold unary and shift-pair sign extensions of narrow loads into signed loads in visitUnary and visitBinary, and generalize Eq/Ne sign-extension comparisons to i64.
There was a problem hiding this comment.
Code Review
This pull request generalizes sign-extension optimizations in Binaryen to support both i32 and i64 types. It updates helper functions in src/ir/properties.h to handle arbitrary integer types and extends various optimizations in src/passes/OptimizeInstructions.cpp (such as folding sign-extensions into narrow loads and simplifying comparisons with constants) to work with i64 operations. Feedback on the changes highlights a critical crash risk in OptimizeInstructions.cpp where getSignExtBits can be called on a non-sign-extend expression, as well as a robustness issue in properties.h where shift amounts are compared literally instead of using their effective modulo values.
| auto signExtBits = getSignExtBits(ext); | ||
| if (signExtBits > 0 && signExtBits <= bits) { | ||
| return replaceCurrent(ext); | ||
| } |
There was a problem hiding this comment.
The call to getSignExtBits(ext) is unsafe because ext (which is curr->value) is not guaranteed to be a sign-extend expression. For example, if curr is (i32.extend8_s (local.get $x)), ext will be (local.get $x). Since LocalGet is neither a Unary nor a Binary sign-extend, getSignExtBits will fall through to the else branch and attempt to perform curr->cast<Binary>() on the LocalGet, resulting in an assertion failure/crash.
To fix this, you should check if ext is actually a sign-extend expression before calling getSignExtBits(ext).
if (Properties::getSignExtValue(ext)) {
auto signExtBits = getSignExtBits(ext);
if (signExtBits > 0 && signExtBits <= bits) {
return replaceCurrent(ext);
}
}|
/review |
| if (LoadUtils::canBeSigned(load) && load->bytes * 8 == bits) { | ||
| // if the value falls through, we can't alter the load, as it might be | ||
| // captured in a tee | ||
| if (load->signed_ || load == ext) { |
There was a problem hiding this comment.
What is handled here that is not handled in the above appearance of this code (new line 614)?
There was a problem hiding this comment.
Here we're optimizing the unary sign extension instructions. The previous location was optimizing shift-based sign extensions and almost-sign-extensions.
There was a problem hiding this comment.
I see. Can they both use a helper, then? it seems like both can use the same logic?
| return replaceCurrent(curr->value); | ||
| if (auto* ext = Properties::getSignExtValue(curr)) { | ||
| auto bits = Properties::getSignExtBits(curr); | ||
| if (trySignExtLoad(ext, bits)) { |
There was a problem hiding this comment.
Can we not use getAlmostSignExtBits like the other call to this helper? If not, why must we be more precise here?
There was a problem hiding this comment.
Two things:
- The other call is guarded by
extraLeftShifts == 0, so it is also looking at an exact sign extension. The only reason there is aProperties::getAlmostSignExtBitscall is because if there are extra left shifts, it falls through and tries other optimizations. ThetrySignExtLoadoptimization only works for exact sign extensions because it is folding the sign extension into a sign-extending load. getAlmostSignExtandgetAlmostSignExtendBitsonly work with shift-based sign extensions right now. We could generalize them, but there wouldn't be any benefit in this PR due to the previous point.
Extend Properties::getSignExtValue, getSignExtBits, and getAlmostSignExt to 64-bit integers and unary extend{8,16,32}_s operations. Fold unary and shift-pair sign extensions of narrow loads into signed loads in visitUnary and visitBinary, and generalize Eq/Ne sign-extension comparisons to i64.