Skip to content

[OptimizeInstructions] Remove redundant sign-exts - #9324

Open
tlively wants to merge 3 commits into
mainfrom
opt-inst-1-3-redundant-sign-ext
Open

tlively wants to merge 3 commits into
mainfrom
opt-inst-1-3-redundant-sign-ext

Conversation

@tlively

@tlively tlively commented Oct 29, 2026

Copy link
Copy Markdown
Member

Relax isSignExted to check signExtBits <= bits, teach getSignExtBits about signed narrow loads and i64.extend_i32_s, remove unary sign-extensions when maxBits < bits, strip wider inner sign-extensions inside narrower outer ones, and fold i64.extend32_s(i64.extend_i32_u(x)) to i64.extend_i32_s(x).

Relax isSignExted to check signExtBits <= bits, teach getSignExtBits about signed narrow loads and i64.extend_i32_s, remove unary sign-extensions when maxBits < bits, strip wider inner sign-extensions inside narrower outer ones, and fold i64.extend32_s(i64.extend_i32_u(x)) to i64.extend_i32_s(x).
@tlively
tlively requested a review from a team as a code owner October 29, 2026 04:56
@tlively
tlively requested review from aheejin and removed request for a team October 29, 2026 04:56

@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 enhances sign-extension optimizations in the OptimizeInstructions pass. It introduces a helper method getExpressionSignExtBits to track sign-extension widths, optimizes nested and redundant sign-extensions (both shift-based and unary), and simplifies expressions where the input's maximum bits are already within the target sign-extend limit. The feedback suggests a minor code cleanup to avoid a redundant cast by utilizing an already-bound pointer variable.

// A narrower outer shift sign-extension can strip a wider inner one.
if (auto* innerExt = Properties::getSignExtValue(ext)) {
if (bits - extraLeftShifts <= Properties::getSignExtBits(ext)) {
curr->left->cast<Binary>()->left = innerExt;

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

Since left is already bound to curr->left as a Binary* in the outer scope of this block, you can directly assign to left->left instead of performing a redundant cast on curr->left.

          left->left = innerExt;

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 does not appear to be correct.

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

It would be easier to understand to have each if case comment in the code has a simple example attached to it.

Comment on lines +92 to +100
if (auto* unary = curr->dynCast<Unary>();
unary && unary->op == ExtendSInt32) {
// i64.extend_i32_s sign-extends from 32 bits, or from a narrower width if
// its i32 input is itself sign-extended (since any i32 sign extension is
// narrower than 32 bits and extending to i64 replicates the same sign
// bit).
bits = 32;
curr = unary->value;
}

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 unwraps an outer i64.extend_i32_s if one exists and we can recover 16 in the if below. But maybe it'd be nice to handle this case too and get 8? Or would this be overkill?

(i64.extend_i32_s
  (i32.extend16_s
    (i32.extend8_s (x))
  )
)

Comment on lines 4512 to 4517
Index getSignExtBits(Expression* curr) {
if (Properties::getSignExtValue(curr)) {
return Properties::getSignExtBits(curr);
if (auto* unary = curr->dynCast<Unary>();
unary && unary->op == ExtendSInt32) {
auto innerBits = getSignExtBits(unary->value);
return innerBits > 0 ? innerBits : 32;
}

@aheejin aheejin Oct 31, 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.

ExtendSInt32 check is also in LocalInfo::getExpressionSignExtBits, so I think we can skip that.

By the way can we merge this, LocalInfo::getExpressionSignExtBits, and Properties::getSignExtBits into one? We have three functions that do one task now. If having LocalInfo or not is a difference, we can provide it as a nullable option. We have a similar precedent already:

binaryen/src/ir/bits.h

Lines 164 to 166 in 677ad31

template<typename LocalInfoProvider = DummyLocalInfoProvider>
Index getMaxBits(Expression* curr,
LocalInfoProvider* localInfoProvider = nullptr) {

Can we merge all these into a single Properties::getSignExtBits?

(By the way, I'm new to this code, and getSignExtBits returning not the amount of sign-extended bits but the amount of 'kept' bits is really confusing... I'm not saying changing this is within the scope of this PR though)

Comment on lines 631 to 632
// 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

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.

It's not identical-sized anymore because isSignExted was relaxed

if (auto* ext = Properties::getSignExtValue(curr->left)) {
// TODO: Reexamine whether replacing unary sign-extend instructions
// (as opposed to shift pairs) with zero-extend masks here is
// worthwhile, since an and with a constant is larger than a unary

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.

Suggested change
// worthwhile, since an and with a constant is larger than a unary
// worthwhile, since an 'and' with a constant is larger than a unary

Nit: This tripped my eyes several times because it looked like a conjunction... The same for other TODOs

Comment on lines +3506 to +3509
// TODO: Reexamine whether replacing unary sign-extend instructions (as
// opposed to shift pairs) with zero-extend masks in boolean contexts
// is worthwhile, since an and with a constant is larger than a unary
// extend.

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.

Suggested change
// TODO: Reexamine whether replacing unary sign-extend instructions (as
// opposed to shift pairs) with zero-extend masks in boolean contexts
// is worthwhile, since an and with a constant is larger than a unary
// extend.

This is within else if (auto* binary = boolean->dynCast<Binary>()) {

@@ -615,10 +635,22 @@
if (isSignExted(ext, bits) && extraLeftShifts == 0) {

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.

Suggested change
if (isSignExted(ext, bits) && extraLeftShifts == 0) {
if (isSignExted(ext, bits - extraLeftShifts)) {

This can unlock more cases, such as (extend8_s(x) << 16) >>_s 8.

Also if we do this, this case and the case below are representing the opposite cases: This case is when the inner is narrower and the case below is when the inner is wider. Would be nice to revise the comments to make it clear that these two cases are essentially the same thing with the inner and outer swapped.

// having a larger sign-extend: if we have an 8-bit value in 32-bit, then
// there are 24 sign bits, and doing a sign-extend to 16 will only affect
// 16 of those 24, and the effect is to leave them as they are.
if (Bits::getMaxBits(ext, this) < bits || isSignExted(ext, bits)) {

@aheejin aheejin Nov 1, 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.

Nit: To match the cases in visitBinary, to me it would be easier to split these to two cases

Comment on lines +1105 to +1112
// Sign-extending the low 32 bits of a zero-extended i32 is a signed i32
// extension.
Unary* inner;
if (matches(curr,
unary(ExtendS32Int64, unary(&inner, ExtendUInt32, any())))) {
inner->op = ExtendSInt32;
return replaceCurrent(inner);
}

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 we add this case to visitBinary too, so that we can do these?

(i64.extend_i32_u(x) << 32) >>_s 32 ⟶ i64.extend_i32_s(x)
(i64.extend_i32_u(x) << 32) >>_s 24 ⟶ i64.extend_i32_s(x) << 8  (when extraLeftShifts > 0)

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