Skip to content

[NFC] Consolidate and rename sign-ext bit helpers - #9350

Merged
tlively merged 4 commits into
mainfrom
sign-ext-kept-bits
Nov 6, 2026
Merged

tlively merged 4 commits into
mainfrom
sign-ext-kept-bits

Conversation

@tlively

@tlively tlively commented Nov 5, 2026

Copy link
Copy Markdown
Member

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.

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.
@tlively
tlively requested a review from a team as a code owner November 5, 2026 01:54
@tlively
tlively requested review from aheejin and stevenfontanella and removed request for a team November 5, 2026 01:54

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

Comment thread src/ir/properties.h Outdated
template<typename LocalInfoProvider = Bits::DummyLocalInfoProvider>
inline Index
getSignExtKeptBits(Expression* curr,
LocalInfoProvider* localInfoProvider = nullptr) {

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.

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

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.

Can you sketch what you have in mind more concretely?

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.

Something like

class ArithmeticInterpreter {
  ArithmeticInterpreter(LocalInfoProvider* localInfoProvider) {}

  Index getMaxBits(Expression* curr) const;
  Index getSignExtKeptBits(Expression* curr) const;
  // maybe more methods in the future
};

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.

Ok, nice idea. I've done that.

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

Are we not gonna delete this?

// Returns the number of bits an expression is sign-extended (or 0 if it is
// not).
Index getSignExtBits(Expression* curr) {
if (Properties::getSignExtValue(curr)) {
return Properties::getSignExtBits(curr);
}
if (auto* get = curr->dynCast<LocalGet>()) {
// Check what we know about the local.
return localInfo[get->index].signExtBits;
}
return 0;
}

Comment thread src/ir/properties.h Outdated
extraLeftShifts =
Bits::getEffectiveShifts(leftShift) - Bits::getEffectiveShifts(rightShift);
return getSignExtBits(curr);
return curr->type.getByteSize() * 8 - Bits::getEffectiveShifts(rightShift);

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.

Can't we use Properties::getSignExtKeptBits 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.

No, because that only returns results for exact sign extensions, not the "almost" sign extensions we are handling here.

@tlively

tlively commented Nov 6, 2026

Copy link
Copy Markdown
Member Author

Hold on, I'm going to try to make the diff smaller.

@tlively
tlively marked this pull request as draft November 6, 2026 21:28
@tlively
tlively marked this pull request as ready for review November 6, 2026 21:49
@tlively

tlively commented Nov 6, 2026

Copy link
Copy Markdown
Member Author

Are we not gonna delete this?

It is deleted now.

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

Comment thread src/ir/bits.h
Comment on lines +266 to +274
class Provider {
const LocalInfoProvider& localInfoProvider;

static inline const DummyLocalInfoProvider dummyLocalInfoProvider{};

public:
Provider(const LocalInfoProvider* localInfoProvider = nullptr)
: localInfoProvider(localInfoProvider ? *localInfoProvider
: dummyLocalInfoProvider) {}

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

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.

Suggested change
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) {}

Comment thread src/ir/bits.h
Comment on lines +570 to +571
} else if (auto* get = curr->dynCast<LocalGet>()) {
return localInfoProvider.getMaxBitsForLocal(get);

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

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");

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.

This seems worse. I verified locally that the performance difference of using virtual calls is negligible.

Comment thread src/ir/bits.h
Comment on lines +629 to +631
if (auto* get = curr->dynCast<LocalGet>()) {
return localInfoProvider.getSignExtKeptBitsForLocal(get);
}

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

Update the LocalGet handling in getSignExtKeptBits to safely handle the null localInfoProvider pointer.

Suggested change
if (auto* get = curr->dynCast<LocalGet>()) {
return localInfoProvider.getSignExtKeptBitsForLocal(get);
}
if (auto* get = curr->dynCast<LocalGet>()) {
return localInfoProvider ? localInfoProvider->getSignExtKeptBitsForLocal(get) : 0;
}

Comment on lines +598 to 616
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));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

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));
      }

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.

Done.

@tlively
tlively enabled auto-merge (squash) November 6, 2026 22:37
@tlively
tlively merged commit 2c85bfc into main Nov 6, 2026
18 checks passed
@tlively
tlively deleted the sign-ext-kept-bits branch November 6, 2026 23:31
@aheejin

aheejin commented Nov 7, 2026

Copy link
Copy Markdown
Member

The code changes got a lot bigger than yesterday. What changed?

@tlively

tlively commented Nov 7, 2026

Copy link
Copy Markdown
Member Author

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.

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