Repository navigation
[NFC] Consolidate and rename sign-ext bit helpers - #9350
Conversation
Merge OptimizeInstructions::getSignExtBits into Properties::getSignExtBits using an optional LocalInfoProvider (following Bits::getMaxBits), and rename the sign/zero-extension bit width helpers from get*ExtBits to get*ExtKeptBits to clarify that they return the number of low bits kept before extension rather than the number of extended sign/zero bits.
There was a problem hiding this comment.
Code Review
This pull request refactors the tracking of sign-extended and zero-extended values by renaming and updating functions to focus on 'kept bits' (the number of low bits preserved before extension). It introduces a templated LocalInfoProvider mechanism in getSignExtKeptBits to allow querying local variables (local.get) for their sign-extension status. Corresponding updates are made across OptimizeInstructions and PickLoadSigns passes to use the new getSignExtKeptBits, getAlmostSignExtKeptBits, and getZeroExtKeptBits interfaces. I have no feedback to provide on these changes.
| template<typename LocalInfoProvider = Bits::DummyLocalInfoProvider> | ||
| inline Index | ||
| getSignExtKeptBits(Expression* curr, | ||
| LocalInfoProvider* localInfoProvider = nullptr) { |
There was a problem hiding this comment.
I wonder if we considered putting this in a class? Since this and getMaxBits are both taking a dependency that could be mocked out (with DummyLocalInfoProvider). In which case we'd use an abstract class for LocalInfoProvider instead of a template
There was a problem hiding this comment.
Can you sketch what you have in mind more concretely?
There was a problem hiding this comment.
Something like
class ArithmeticInterpreter {
ArithmeticInterpreter(LocalInfoProvider* localInfoProvider) {}
Index getMaxBits(Expression* curr) const;
Index getSignExtKeptBits(Expression* curr) const;
// maybe more methods in the future
};There was a problem hiding this comment.
Ok, nice idea. I've done that.
aheejin
left a comment
There was a problem hiding this comment.
Are we not gonna delete this?
binaryen/src/passes/OptimizeInstructions.cpp
Lines 4451 to 4462 in a9141c8
| extraLeftShifts = | ||
| Bits::getEffectiveShifts(leftShift) - Bits::getEffectiveShifts(rightShift); | ||
| return getSignExtBits(curr); | ||
| return curr->type.getByteSize() * 8 - Bits::getEffectiveShifts(rightShift); |
There was a problem hiding this comment.
Can't we use Properties::getSignExtKeptBits here?
There was a problem hiding this comment.
No, because that only returns results for exact sign extensions, not the "almost" sign extensions we are handling here.
|
Hold on, I'm going to try to make the diff smaller. |
It is deleted now. |
There was a problem hiding this comment.
Code Review
This pull request refactors bit-tracking utilities by moving sign-extend and zero-extend helper functions from Properties to Bits and introducing a Bits::Provider class that utilizes a LocalInfoProvider interface. The review feedback recommends optimizing the Provider class by storing localInfoProvider as a pointer instead of a reference to avoid virtual call overhead on the dummy fallback, adding corresponding null checks when querying the provider, and renaming a shadowed local variable bits in OptimizeInstructions.cpp to improve code readability.
| class Provider { | ||
| const LocalInfoProvider& localInfoProvider; | ||
|
|
||
| static inline const DummyLocalInfoProvider dummyLocalInfoProvider{}; | ||
|
|
||
| public: | ||
| Provider(const LocalInfoProvider* localInfoProvider = nullptr) | ||
| : localInfoProvider(localInfoProvider ? *localInfoProvider | ||
| : dummyLocalInfoProvider) {} |
There was a problem hiding this comment.
Using a reference for localInfoProvider forces a virtual function call on the fallback dummyLocalInfoProvider even when no provider is needed (which is the default and most common case). Changing localInfoProvider to a pointer and checking for nullptr avoids this virtual call overhead in the hot path of getMaxBits and eliminates the need for the static inline const fallback instance.
| class Provider { | |
| const LocalInfoProvider& localInfoProvider; | |
| static inline const DummyLocalInfoProvider dummyLocalInfoProvider{}; | |
| public: | |
| Provider(const LocalInfoProvider* localInfoProvider = nullptr) | |
| : localInfoProvider(localInfoProvider ? *localInfoProvider | |
| : dummyLocalInfoProvider) {} | |
| class Provider { | |
| const LocalInfoProvider* localInfoProvider; | |
| public: | |
| Provider(const LocalInfoProvider* localInfoProvider = nullptr) | |
| : localInfoProvider(localInfoProvider) {} |
| } else if (auto* get = curr->dynCast<LocalGet>()) { | ||
| return localInfoProvider.getMaxBitsForLocal(get); |
There was a problem hiding this comment.
Update the LocalGet handling to check if localInfoProvider is non-null before calling its method, falling back to direct type-based bit width checks to avoid virtual call overhead.
} else if (auto* get = curr->dynCast<LocalGet>()) {
if (localInfoProvider) {
return localInfoProvider->getMaxBitsForLocal(get);
}
if (get->type == Type::i32) {
return 32;
} else if (get->type == Type::i64) {
return 64;
}
WASM_UNREACHABLE("type has no integer bit size");There was a problem hiding this comment.
This seems worse. I verified locally that the performance difference of using virtual calls is negligible.
| if (auto* get = curr->dynCast<LocalGet>()) { | ||
| return localInfoProvider.getSignExtKeptBitsForLocal(get); | ||
| } |
There was a problem hiding this comment.
Update the LocalGet handling in getSignExtKeptBits to safely handle the null localInfoProvider pointer.
| if (auto* get = curr->dynCast<LocalGet>()) { | |
| return localInfoProvider.getSignExtKeptBitsForLocal(get); | |
| } | |
| if (auto* get = curr->dynCast<LocalGet>()) { | |
| return localInfoProvider ? localInfoProvider->getSignExtKeptBitsForLocal(get) : 0; | |
| } |
| if (auto* ext = Bits::getAlmostSignExt(curr)) { | ||
| Index extraLeftShifts; | ||
| auto bits = Properties::getAlmostSignExtBits(curr, extraLeftShifts); | ||
| auto bits = Bits::getAlmostSignExtKeptBits(curr, extraLeftShifts); | ||
| if (extraLeftShifts == 0 && trySignExtLoad(ext, bits)) { | ||
| return replaceCurrent(ext); | ||
| } | ||
| // We can in some cases remove part of a sign extend, that is, | ||
| // (x << A) >> B => x << (A - B) | ||
| // If the sign-extend input cannot have a sign bit, we don't need it. | ||
| if (Bits::getMaxBits(ext, this) + extraLeftShifts < bits) { | ||
| if (this->bits.getMaxBits(ext) + extraLeftShifts < bits) { | ||
| return replaceCurrent(removeAlmostSignExt(curr)); | ||
| } | ||
| // We also don't need it if it already has an identical-sized sign | ||
| // extend applied to it. That is, if it is already a sign-extended | ||
| // value, then another sign extend will do nothing. We do need to be | ||
| // careful of the extra shifts, though. | ||
| if (isSignExted(ext, bits) && extraLeftShifts == 0) { | ||
| if (hasSignExtKeptBits(ext, bits) && extraLeftShifts == 0) { | ||
| return replaceCurrent(removeAlmostSignExt(curr)); | ||
| } |
There was a problem hiding this comment.
The local variable bits shadows the member variable bits of OptimizeInstructions, forcing the use of this->bits. Renaming the local variable to keptBits avoids this shadowing and improves code readability.
if (auto* ext = Bits::getAlmostSignExt(curr)) {
Index extraLeftShifts;
auto keptBits = Bits::getAlmostSignExtKeptBits(curr, extraLeftShifts);
if (extraLeftShifts == 0 && trySignExtLoad(ext, keptBits)) {
return replaceCurrent(ext);
}
// We can in some cases remove part of a sign extend, that is,
// (x << A) >> B => x << (A - B)
// If the sign-extend input cannot have a sign bit, we don't need it.
if (bits.getMaxBits(ext) + extraLeftShifts < keptBits) {
return replaceCurrent(removeAlmostSignExt(curr));
}
// We also don't need it if it already has an identical-sized sign
// extend applied to it. That is, if it is already a sign-extended
// value, then another sign extend will do nothing. We do need to be
// careful of the extra shifts, though.
if (hasSignExtKeptBits(ext, keptBits) && extraLeftShifts == 0) {
return replaceCurrent(removeAlmostSignExt(curr));
}|
The code changes got a lot bigger than yesterday. What changed? |
|
The biggest contributor was that I moved the sign-extension functionality from properties.h to ir/bits.h. @stevenfontanella's idea to use a class with virtual methods instead of templates to provide local information also caused quite a few mechanical changes. |
Merge OptimizeInstructions::getSignExtBits into Properties::getSignExtBits using an optional LocalInfoProvider (following Bits::getMaxBits), and rename the sign/zero-extension bit width helpers from getExtBits to getExtKeptBits to clarify that they return the number of low bits kept before extension rather than the number of extended sign/zero bits.