From 298000e978b575e9750c00ac84e675bf5487fa51 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 20 Oct 2026 21:40:31 -0700 Subject: [PATCH 1/5] [OptimizeInstructions] Generalize sign-ext folding 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. --- src/ir/properties.h | 54 +-- src/passes/OptimizeInstructions.cpp | 68 ++-- .../passes/optimize-instructions-atomics.wast | 35 ++ .../lit/passes/optimize-instructions-mvp.wast | 326 +++++++++++++++++- .../optimize-instructions-sign_ext.wast | 296 ++++++++++++++++ 5 files changed, 726 insertions(+), 53 deletions(-) diff --git a/src/ir/properties.h b/src/ir/properties.h index a7ede167cde..ba00cee7d8e 100644 --- a/src/ir/properties.h +++ b/src/ir/properties.h @@ -151,24 +151,32 @@ inline Literals getLiterals(const Expression* curr) { // Check if an expression is a sign-extend, and if so, returns the value // that is extended, otherwise nullptr inline Expression* getSignExtValue(Expression* curr) { - // We only care about i32s here, and ignore i64s, unreachables, etc. - if (curr->type != Type::i32) { + // We only care about integers here, and ignore unreachables, etc. + if (!curr->type.isInteger()) { return nullptr; } if (auto* unary = curr->dynCast()) { - if (unary->op == ExtendS8Int32 || unary->op == ExtendS16Int32) { - return unary->value; + switch (unary->op) { + case ExtendS8Int32: + case ExtendS16Int32: + case ExtendS8Int64: + case ExtendS16Int64: + case ExtendS32Int64: + return unary->value; + default: + return nullptr; } - return nullptr; } + using namespace Abstract; using namespace Match; - int32_t leftShift = 0, rightShift = 0; + int64_t leftShift = 0, rightShift = 0; Expression* extended = nullptr; if (matches(curr, - binary(ShrSInt32, - binary(ShlInt32, any(&extended), i32(&leftShift)), - i32(&rightShift))) && - leftShift == rightShift && leftShift != 0) { + binary(ShrS, + binary(Shl, any(&extended), ival(&leftShift)), + ival(&rightShift))) && + leftShift == rightShift && + Bits::getEffectiveShifts(leftShift, curr->type) != 0) { return extended; } return nullptr; @@ -176,19 +184,23 @@ inline Expression* getSignExtValue(Expression* curr) { // gets the size of the sign-extended value inline Index getSignExtBits(Expression* curr) { - assert(curr->type == Type::i32); + assert(curr->type.isInteger()); if (auto* unary = curr->dynCast()) { switch (unary->op) { case ExtendS8Int32: + case ExtendS8Int64: return 8; case ExtendS16Int32: + case ExtendS16Int64: return 16; + case ExtendS32Int64: + return 32; default: WASM_UNREACHABLE("invalid unary operation"); } } else { auto* rightShift = curr->cast()->right; - return 32 - Bits::getEffectiveShifts(rightShift); + return curr->type.getByteSize() * 8 - Bits::getEffectiveShifts(rightShift); } } @@ -196,17 +208,19 @@ inline Index getSignExtBits(Expression* curr) { // is too large. We can split the shifts in that case, which is sometimes // useful (e.g. if we can remove the signext) inline Expression* getAlmostSignExt(Expression* curr) { + using namespace Abstract; using namespace Match; - int32_t leftShift = 0, rightShift = 0; + int64_t leftShift = 0, rightShift = 0; Expression* extended = nullptr; if (matches(curr, - binary(ShrSInt32, - binary(ShlInt32, any(&extended), i32(&leftShift)), - i32(&rightShift))) && - Bits::getEffectiveShifts(rightShift, Type::i32) <= - Bits::getEffectiveShifts(leftShift, Type::i32) && - rightShift != 0) { - return extended; + binary(ShrS, + binary(Shl, any(&extended), ival(&leftShift)), + ival(&rightShift)))) { + auto rightShifts = Bits::getEffectiveShifts(rightShift, curr->type); + if (rightShifts <= Bits::getEffectiveShifts(leftShift, curr->type)) { + assert(rightShifts != 0); + return extended; + } } return nullptr; } diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index feb9af5bc17..7a7d2915d06 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -608,12 +608,10 @@ struct OptimizeInstructions if (auto* load = getFallthrough(ext)->dynCast()) { // pattern match a load of 8 bits and a sign extend using a shl of // 24 then shr_s of 24 as well, etc. - if (LoadUtils::canBeSigned(load) && - ((load->bytes == 1 && bits == 8) || - (load->bytes == 2 && bits == 16))) { + 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_ == true || load == ext) { + if (load->signed_ || load == ext) { load->signed_ = true; return replaceCurrent(ext); } @@ -633,7 +631,8 @@ struct OptimizeInstructions if (isSignExted(ext, bits) && extraLeftShifts == 0) { return replaceCurrent(removeAlmostSignExt(curr)); } - } else if (curr->op == EqInt32 || curr->op == NeInt32) { + } else if (curr->op == EqInt32 || curr->op == NeInt32 || + curr->op == EqInt64 || curr->op == NeInt64) { if (auto* c = curr->right->dynCast()) { if (auto* ext = Properties::getSignExtValue(curr->left)) { // We are comparing a sign extend to a constant, which means we can @@ -649,10 +648,10 @@ struct OptimizeInstructions // the upper bits, which means that the relevant bits on the left // side are either all 0, or all 1. auto bits = Properties::getSignExtBits(curr->left); - uint32_t right = c->value.geti32(); - uint32_t numRelevantBits = 32 - bits + 1; - uint32_t setRelevantBits = - Bits::popCount(right >> uint32_t(bits - 1)); + uint64_t right = c->value.getUnsigned(); + Index totalBits = c->type.getByteSize() * 8; + Index numRelevantBits = totalBits - bits + 1; + Index setRelevantBits = Bits::popCount(right >> (bits - 1)); // If all the relevant bits on C are zero // then we can mask off the high bits instead of sign-extending x. // This is valid because if x is negative, then the comparison was @@ -670,14 +669,17 @@ struct OptimizeInstructions // identical, then the upper bits don't matter, so masking them // off both sides is fine. curr->left = makeZeroExt(ext, bits); - c->value = c->value.and_(Literal(Bits::lowBitMask(bits))); + c->value = c->value.and_( + Literal::makeFromInt64(Bits::lowBitMask64(bits), c->type)); return replaceCurrent(curr); } else { // Otherwise, C's relevant bits are mixed, and then the two sides // can never be equal, as the left side's bits cannot be mixed. Builder builder(*getModule()); // The result is either always true, or always false. - c->value = Literal::makeFromInt32(curr->op == NeInt32, c->type); + c->value = Literal::makeFromInt32( + curr->op == NeInt32 || curr->op == NeInt64, Type::i32); + c->type = Type::i32; return replaceCurrent( builder.makeSequence(builder.makeDrop(ext), c)); } @@ -1058,19 +1060,26 @@ struct OptimizeInstructions } } - // Simple sign extends can be removed if the value is already sign-extended. - auto signExtBits = getSignExtBits(curr->value); - if (signExtBits > 0) { - // Note that we can handle the case of |curr| 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 ((curr->op == ExtendS8Int32 && signExtBits <= 8) || - (curr->op == ExtendS16Int32 && signExtBits <= 16) || - (curr->op == ExtendS8Int64 && signExtBits <= 8) || - (curr->op == ExtendS16Int64 && signExtBits <= 16) || - (curr->op == ExtendS32Int64 && signExtBits <= 32)) { - return replaceCurrent(curr->value); + if (auto* ext = Properties::getSignExtValue(curr)) { + auto bits = Properties::getSignExtBits(curr); + if (auto* load = getFallthrough(ext)->dynCast()) { + 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) { + load->signed_ = true; + return replaceCurrent(ext); + } + } + } + // Simple sign extends can be removed if the value is already + // sign-extended. Note that we can handle the case of |curr| 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. + auto signExtBits = getSignExtBits(ext); + if (signExtBits > 0 && signExtBits <= bits) { + return replaceCurrent(ext); } } @@ -4415,8 +4424,10 @@ struct OptimizeInstructions Expression* makeZeroExt(Expression* curr, int32_t bits) { Builder builder(*getModule()); - return builder.makeBinary( - AndInt32, curr, builder.makeConst(Literal(Bits::lowBitMask(bits)))); + return builder.makeBinary(Abstract::getBinary(curr->type, Abstract::And), + curr, + builder.makeConst(Literal::makeFromInt64( + Bits::lowBitMask64(bits), curr->type))); } // given an "almost" sign extend - either a proper one, or it @@ -5398,11 +5409,6 @@ struct OptimizeInstructions // unaryOp(unaryOp(x)) ==> unaryOp(x) return unaryInner; } - case ExtendS8Int32: - case ExtendS16Int32: { - assert(getModule()->features.hasSignExt()); - return unaryInner; - } case EqZInt32: { // eqz(eqz(bool(x))) ==> bool(x) if (Bits::getMaxBits(unaryInner->value, this) == 1) { diff --git a/test/lit/passes/optimize-instructions-atomics.wast b/test/lit/passes/optimize-instructions-atomics.wast index 3086fe26896..b03651b0ba5 100644 --- a/test/lit/passes/optimize-instructions-atomics.wast +++ b/test/lit/passes/optimize-instructions-atomics.wast @@ -13,8 +13,22 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.atomic.load8_u + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.extend8_s + ;; CHECK-NEXT: (i32.atomic.load8_u + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $x + ;; An 8-bit shift sign-extend cannot be folded into an atomic load, which + ;; cannot be signed, so it is only canonicalized to i32.extend8_s. (drop (i32.shr_s (i32.shl @@ -26,6 +40,27 @@ (i32.const 24) ) ) + ;; A 12-bit shift sign-extend on an 8-bit unsigned atomic load is a no-op + ;; because the upper bits of the 12-bit value are already zero. + (drop + (i32.shr_s + (i32.shl + (i32.atomic.load8_u ;; can't be signed + (i32.const 100) + ) + (i32.const 20) + ) + (i32.const 20) + ) + ) + ;; A unary sign-extend also cannot be folded into an atomic load. + (drop + (i32.extend8_s + (i32.atomic.load8_u ;; can't be signed + (i32.const 100) + ) + ) + ) ) ;; CHECK: (func $dont_simplify_reinterpret_atomic_load_store (param $x i32) (param $y f32) diff --git a/test/lit/passes/optimize-instructions-mvp.wast b/test/lit/passes/optimize-instructions-mvp.wast index 875a238468f..28c5a2a9f82 100644 --- a/test/lit/passes/optimize-instructions-mvp.wast +++ b/test/lit/passes/optimize-instructions-mvp.wast @@ -5024,6 +5024,108 @@ ) ) ) + ;; CHECK: (func $i64-sign-ext-load (param $x i32) + ;; CHECK-NEXT: (local $t i64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.load8_s + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.load16_s + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.load32_s + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.load32_s + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $t + ;; CHECK-NEXT: (i64.load32_s + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.shr_s + ;; CHECK-NEXT: (i64.shl + ;; CHECK-NEXT: (local.tee $t + ;; CHECK-NEXT: (i64.load32_u + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.const 32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.const 32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.shr_s + ;; CHECK-NEXT: (i64.shl + ;; CHECK-NEXT: (i64.load16_u + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.const 56) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.const 56) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $i64-sign-ext-load (param $x i32) + (local $t i64) + ;; Fold shift-based sign extensions into narrow i64 loads. + (drop + (i64.shr_s + (i64.shl (i64.load8_u (local.get $x)) (i64.const 56)) + (i64.const 56) + ) + ) + (drop + (i64.shr_s + (i64.shl (i64.load16_u (local.get $x)) (i64.const 48)) + (i64.const 48) + ) + ) + (drop + (i64.shr_s + (i64.shl (i64.load32_u (local.get $x)) (i64.const 32)) + (i64.const 32) + ) + ) + (drop + (i64.shr_s + (i64.shl (i64.load32_s (local.get $x)) (i64.const 32)) + (i64.const 32) + ) + ) + ;; Signed load falling through a tee can still drop the outer sign-extend. + (drop + (i64.shr_s + (i64.shl (local.tee $t (i64.load32_s (local.get $x))) (i64.const 32)) + (i64.const 32) + ) + ) + ;; Negative cases: unsigned load captured in a tee, or mismatched width. + (drop + (i64.shr_s + (i64.shl (local.tee $t (i64.load32_u (local.get $x))) (i64.const 32)) + (i64.const 32) + ) + ) + (drop + (i64.shr_s + (i64.shl (i64.load16_u (local.get $x)) (i64.const 56)) + (i64.const 56) + ) + ) + ) ;; CHECK: (func $mask-bits (param $0 i32) (param $1 i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $0 @@ -6063,6 +6165,45 @@ ) ) ) + ;; CHECK: (func $compare-i64-load-s-sign-extend (param $addr i32) (param $x i64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.eq + ;; CHECK-NEXT: (i64.load32_u + ;; CHECK-NEXT: (local.get $addr) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.and + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i64.const 4294967295) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.ne + ;; CHECK-NEXT: (i64.load16_u + ;; CHECK-NEXT: (local.get $addr) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.and + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i64.const 65535) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $compare-i64-load-s-sign-extend (param $addr i32) (param $x i64) + ;; Comparing i64 sign-extension against signed load on right or left. + (drop + (i64.eq + (i64.shr_s (i64.shl (local.get $x) (i64.const 32)) (i64.const 32)) + (i64.load32_s (local.get $addr)) + ) + ) + (drop + (i64.ne + (i64.load16_s (local.get $addr)) + (i64.shr_s (i64.shl (local.get $x) (i64.const 48)) (i64.const 48)) + ) + ) + ) ;; CHECK: (func $unsign-diff-sizes (param $x i32) (param $y i32) (result i32) ;; CHECK-NEXT: (i32.ne ;; CHECK-NEXT: (i32.shr_s @@ -6153,6 +6294,54 @@ ) ) ) + ;; CHECK: (func $unsign-i64-sizes (param $x i64) (param $y i64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.eq + ;; CHECK-NEXT: (i64.and + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i64.const 65535) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.and + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: (i64.const 65535) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.eq + ;; CHECK-NEXT: (i64.shr_s + ;; CHECK-NEXT: (i64.shl + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i64.const 56) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.const 56) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.shr_s + ;; CHECK-NEXT: (i64.shl + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: (i64.const 48) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.const 48) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $unsign-i64-sizes (param $x i64) (param $y i64) + ;; Comparing two i64 sign-extensions of the same width -> both zero-extend. + (drop + (i64.eq + (i64.shr_s (i64.shl (local.get $x) (i64.const 48)) (i64.const 48)) + (i64.shr_s (i64.shl (local.get $y) (i64.const 48)) (i64.const 48)) + ) + ) + ;; Mismatched sign-extension widths cannot be converted to zero-extensions. + (drop + (i64.eq + (i64.shr_s (i64.shl (local.get $x) (i64.const 56)) (i64.const 56)) + (i64.shr_s (i64.shl (local.get $y) (i64.const 48)) (i64.const 48)) + ) + ) + ) ;; CHECK: (func $fuzz-almost-sign-ext ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.shr_s @@ -6204,6 +6393,26 @@ ) ) ) + ;; CHECK: (func $almost-sign-ext-i64 (param $y i64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.shl + ;; CHECK-NEXT: (i64.and + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: (i64.const 127) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.const 8) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $almost-sign-ext-i64 (param $y i64) + ;; Almost sign-extend removal when max bits fit. + (drop + (i64.shr_s + (i64.shl (i64.and (local.get $y) (i64.const 0x7f)) (i64.const 40)) + (i64.const 32) + ) + ) + ) ;; CHECK: (func $fuzz-comp-impossible (param $x i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) @@ -6350,6 +6559,102 @@ ) ) ) + ;; CHECK: (func $i64-sign-ext-eq-const (param $x i64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.eq + ;; CHECK-NEXT: (i64.and + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i64.const 255) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.const 100) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.ne + ;; CHECK-NEXT: (i64.and + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i64.const 4294967295) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.const 2147483647) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.eq + ;; CHECK-NEXT: (i64.and + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i64.const 255) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.const 251) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.ne + ;; CHECK-NEXT: (i64.and + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i64.const 4294967295) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.const 4294867296) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $i64-sign-ext-eq-const (param $x i64) + ;; Comparing i64 shift sign-extend against constants: + ;; 1. Positive constant with all relevant bits 0 -> zero-extend. + (drop + (i64.eq + (i64.shr_s (i64.shl (local.get $x) (i64.const 56)) (i64.const 56)) + (i64.const 100) + ) + ) + (drop + (i64.ne + (i64.shr_s (i64.shl (local.get $x) (i64.const 32)) (i64.const 32)) + (i64.const 0x7fffffff) + ) + ) + ;; 2. Negative constant with all relevant bits 1 -> zero-extend both sides. + (drop + (i64.eq + (i64.shr_s (i64.shl (local.get $x) (i64.const 56)) (i64.const 56)) + (i64.const -5) + ) + ) + (drop + (i64.ne + (i64.shr_s (i64.shl (local.get $x) (i64.const 32)) (i64.const 32)) + (i64.const -100000) + ) + ) + ;; 3. Out-of-range constant with mixed relevant bits -> constant result. + (drop + (i64.eq + (i64.shr_s (i64.shl (local.get $x) (i64.const 56)) (i64.const 56)) + (i64.const 200) + ) + ) + (drop + (i64.ne + (i64.shr_s (i64.shl (local.get $x) (i64.const 32)) (i64.const 32)) + (i64.const 0x80000000) + ) + ) + ) ;; CHECK: (func $if-parallel (param $0 i32) (param $1 i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.add @@ -6628,7 +6933,11 @@ ) ) - ;; CHECK: (func $zero-shifts-is-not-sign-ext + ;; CHECK: (func $zero-shifts-is-not-sign-ext (param $y i64) + ;; CHECK-NEXT: (local $t i64) + ;; CHECK-NEXT: (local.set $t + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.eq ;; CHECK-NEXT: (i32.load16_s align=1 @@ -6649,7 +6958,20 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $zero-shifts-is-not-sign-ext + (func $zero-shifts-is-not-sign-ext (param $y i64) + (local $t i64) + ;; Also test in a local.set so LocalScanner::visitLocalSet's call to + ;; getSignExtValue sees the initial zero shifts before visitBinary folds + ;; them away. + (local.set $t + (i64.shr_s + (i64.shl + (local.get $y) + (i64.const 0) + ) + (i64.const 0) + ) + ) (drop (i32.eq (i32.const -5431187) diff --git a/test/lit/passes/optimize-instructions-sign_ext.wast b/test/lit/passes/optimize-instructions-sign_ext.wast index 19e6c45c828..a90c5d53e70 100644 --- a/test/lit/passes/optimize-instructions-sign_ext.wast +++ b/test/lit/passes/optimize-instructions-sign_ext.wast @@ -364,4 +364,300 @@ ) ) ) + + ;; CHECK: (func $unary-sign-ext-loads (type $1) (param $addr i32) + ;; CHECK-NEXT: (local $t32 i32) + ;; CHECK-NEXT: (local $t64 i64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.load8_s + ;; CHECK-NEXT: (local.get $addr) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.load8_s + ;; CHECK-NEXT: (local.get $addr) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.load16_s + ;; CHECK-NEXT: (local.get $addr) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.load16_s + ;; CHECK-NEXT: (local.get $addr) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.load8_s + ;; CHECK-NEXT: (local.get $addr) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.load8_s + ;; CHECK-NEXT: (local.get $addr) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.load16_s + ;; CHECK-NEXT: (local.get $addr) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.load16_s + ;; CHECK-NEXT: (local.get $addr) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.load32_s + ;; CHECK-NEXT: (local.get $addr) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.load32_s + ;; CHECK-NEXT: (local.get $addr) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $t32 + ;; CHECK-NEXT: (i32.load8_s + ;; CHECK-NEXT: (local.get $addr) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $t64 + ;; CHECK-NEXT: (i64.load32_s + ;; CHECK-NEXT: (local.get $addr) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.extend8_s + ;; CHECK-NEXT: (local.tee $t32 + ;; CHECK-NEXT: (i32.load8_u + ;; CHECK-NEXT: (local.get $addr) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.extend32_s + ;; CHECK-NEXT: (local.tee $t64 + ;; CHECK-NEXT: (i64.load32_u + ;; CHECK-NEXT: (local.get $addr) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.extend8_s + ;; CHECK-NEXT: (i32.load16_u + ;; CHECK-NEXT: (local.get $addr) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.extend16_s + ;; CHECK-NEXT: (i64.load32_u + ;; CHECK-NEXT: (local.get $addr) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $unary-sign-ext-loads (param $addr i32) + (local $t32 i32) + (local $t64 i64) + ;; Fold unary sign-extend instructions into narrow loads. + (drop (i32.extend8_s (i32.load8_u (local.get $addr)))) + (drop (i32.extend8_s (i32.load8_s (local.get $addr)))) + (drop (i32.extend16_s (i32.load16_u (local.get $addr)))) + (drop (i32.extend16_s (i32.load16_s (local.get $addr)))) + (drop (i64.extend8_s (i64.load8_u (local.get $addr)))) + (drop (i64.extend8_s (i64.load8_s (local.get $addr)))) + (drop (i64.extend16_s (i64.load16_u (local.get $addr)))) + (drop (i64.extend16_s (i64.load16_s (local.get $addr)))) + (drop (i64.extend32_s (i64.load32_u (local.get $addr)))) + (drop (i64.extend32_s (i64.load32_s (local.get $addr)))) + ;; Signed load falling through a tee can drop the redundant unary extend. + (drop (i32.extend8_s (local.tee $t32 (i32.load8_s (local.get $addr))))) + (drop (i64.extend32_s (local.tee $t64 (i64.load32_s (local.get $addr))))) + ;; Negative cases: unsigned load in a tee, or mismatched load width. + (drop (i32.extend8_s (local.tee $t32 (i32.load8_u (local.get $addr))))) + (drop (i64.extend32_s (local.tee $t64 (i64.load32_u (local.get $addr))))) + (drop (i32.extend8_s (i32.load16_u (local.get $addr)))) + (drop (i64.extend16_s (i64.load32_u (local.get $addr)))) + ) + + ;; CHECK: (func $unary-sign-ext-eq-const (type $2) (param $x32 i32) (param $x64 i64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (local.get $x32) + ;; CHECK-NEXT: (i32.const 255) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.ne + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (local.get $x32) + ;; CHECK-NEXT: (i32.const 65535) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 65494) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $x32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.eq + ;; CHECK-NEXT: (i64.and + ;; CHECK-NEXT: (local.get $x64) + ;; CHECK-NEXT: (i64.const 255) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.ne + ;; CHECK-NEXT: (i64.and + ;; CHECK-NEXT: (local.get $x64) + ;; CHECK-NEXT: (i64.const 65535) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.const 65494) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.eq + ;; CHECK-NEXT: (i64.and + ;; CHECK-NEXT: (local.get $x64) + ;; CHECK-NEXT: (i64.const 4294967295) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.const 4294867296) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $x64) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $unary-sign-ext-eq-const (param $x32 i32) (param $x64 i64) + ;; Comparing unary sign-extensions against constants. + (drop (i32.eq (i32.extend8_s (local.get $x32)) (i32.const 42))) + (drop (i32.ne (i32.extend16_s (local.get $x32)) (i32.const -42))) + (drop (i32.eq (i32.extend8_s (local.get $x32)) (i32.const 200))) + (drop (i64.eq (i64.extend8_s (local.get $x64)) (i64.const 42))) + (drop (i64.ne (i64.extend16_s (local.get $x64)) (i64.const -42))) + (drop (i64.eq (i64.extend32_s (local.get $x64)) (i64.const -100000))) + (drop (i64.ne (i64.extend32_s (local.get $x64)) (i64.const 0x80000000))) + ) + + ;; CHECK: (func $unary-sign-ext-eq-both (type $3) (param $x32 i32) (param $y32 i32) (param $x64 i64) (param $y64 i64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (local.get $x32) + ;; CHECK-NEXT: (i32.const 255) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (local.get $y32) + ;; CHECK-NEXT: (i32.const 255) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.eq + ;; CHECK-NEXT: (i64.and + ;; CHECK-NEXT: (local.get $x64) + ;; CHECK-NEXT: (i64.const 65535) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.and + ;; CHECK-NEXT: (local.get $y64) + ;; CHECK-NEXT: (i64.const 65535) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.ne + ;; CHECK-NEXT: (i64.and + ;; CHECK-NEXT: (local.get $x64) + ;; CHECK-NEXT: (i64.const 4294967295) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.and + ;; CHECK-NEXT: (local.get $y64) + ;; CHECK-NEXT: (i64.const 4294967295) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.eq + ;; CHECK-NEXT: (i64.extend8_s + ;; CHECK-NEXT: (local.get $x64) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.extend16_s + ;; CHECK-NEXT: (local.get $y64) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $unary-sign-ext-eq-both (param $x32 i32) (param $y32 i32) (param $x64 i64) (param $y64 i64) + ;; Comparing two unary sign-extensions. + (drop (i32.eq (i32.extend8_s (local.get $x32)) (i32.extend8_s (local.get $y32)))) + (drop (i64.eq (i64.extend16_s (local.get $x64)) (i64.extend16_s (local.get $y64)))) + (drop (i64.ne (i64.extend32_s (local.get $x64)) (i64.extend32_s (local.get $y64)))) + (drop (i64.eq (i64.extend8_s (local.get $x64)) (i64.extend16_s (local.get $y64)))) + ) + + ;; CHECK: (func $unary-sign-ext-eq-load (type $4) (param $x32 i32) (param $x64 i64) (param $addr i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (i32.load16_u + ;; CHECK-NEXT: (local.get $addr) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (local.get $x32) + ;; CHECK-NEXT: (i32.const 65535) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.eq + ;; CHECK-NEXT: (i64.load32_u + ;; CHECK-NEXT: (local.get $addr) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.and + ;; CHECK-NEXT: (local.get $x64) + ;; CHECK-NEXT: (i64.const 4294967295) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.ne + ;; CHECK-NEXT: (i64.load8_u + ;; CHECK-NEXT: (local.get $addr) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.and + ;; CHECK-NEXT: (local.get $x64) + ;; CHECK-NEXT: (i64.const 255) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $unary-sign-ext-eq-load (param $x32 i32) (param $x64 i64) (param $addr i32) + ;; Comparing unary sign-extension with signed loads. + (drop (i32.eq (i32.extend16_s (local.get $x32)) (i32.load16_s (local.get $addr)))) + (drop (i64.eq (i64.extend32_s (local.get $x64)) (i64.load32_s (local.get $addr)))) + (drop (i64.ne (i64.load8_s (local.get $addr)) (i64.extend8_s (local.get $x64)))) + ) ) From 87176a628235349724df9a0b31999e0d2b812bb4 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 22 Oct 2026 19:12:40 -0700 Subject: [PATCH 2/5] leftShifts --- src/ir/properties.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ir/properties.h b/src/ir/properties.h index ba00cee7d8e..bba322275e6 100644 --- a/src/ir/properties.h +++ b/src/ir/properties.h @@ -216,8 +216,9 @@ inline Expression* getAlmostSignExt(Expression* curr) { binary(ShrS, binary(Shl, any(&extended), ival(&leftShift)), ival(&rightShift)))) { + auto leftShifts = Bits::getEffectiveShifts(leftShift, curr->type); auto rightShifts = Bits::getEffectiveShifts(rightShift, curr->type); - if (rightShifts <= Bits::getEffectiveShifts(leftShift, curr->type)) { + if (rightShifts <= leftShifts) { assert(rightShifts != 0); return extended; } From 4c89c0121c1b990cc606b4550b48ffc22979a3ee Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 22 Oct 2026 19:31:50 -0700 Subject: [PATCH 3/5] fixes --- src/ir/properties.h | 13 ++-- .../lit/passes/optimize-instructions-mvp.wast | 66 +++++++++++++++++++ 2 files changed, 73 insertions(+), 6 deletions(-) diff --git a/src/ir/properties.h b/src/ir/properties.h index bba322275e6..b78a4168929 100644 --- a/src/ir/properties.h +++ b/src/ir/properties.h @@ -174,10 +174,12 @@ inline Expression* getSignExtValue(Expression* curr) { if (matches(curr, binary(ShrS, binary(Shl, any(&extended), ival(&leftShift)), - ival(&rightShift))) && - leftShift == rightShift && - Bits::getEffectiveShifts(leftShift, curr->type) != 0) { - return extended; + ival(&rightShift)))) { + auto leftShifts = Bits::getEffectiveShifts(leftShift, curr->type); + auto rightShifts = Bits::getEffectiveShifts(rightShift, curr->type); + if (leftShifts != 0 && leftShifts == rightShifts) { + return extended; + } } return nullptr; } @@ -218,8 +220,7 @@ inline Expression* getAlmostSignExt(Expression* curr) { ival(&rightShift)))) { auto leftShifts = Bits::getEffectiveShifts(leftShift, curr->type); auto rightShifts = Bits::getEffectiveShifts(rightShift, curr->type); - if (rightShifts <= leftShifts) { - assert(rightShifts != 0); + if (rightShifts != 0 && rightShifts <= leftShifts) { return extended; } } diff --git a/test/lit/passes/optimize-instructions-mvp.wast b/test/lit/passes/optimize-instructions-mvp.wast index 28c5a2a9f82..520ddc7631a 100644 --- a/test/lit/passes/optimize-instructions-mvp.wast +++ b/test/lit/passes/optimize-instructions-mvp.wast @@ -7007,6 +7007,72 @@ ) ) ) + + ;; CHECK: (func $almost-sign-ext-zero-shifts (param $x i32) (param $y i64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.shl + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 16) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.shl + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: (i64.const 16) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $almost-sign-ext-zero-shifts (param $x i32) (param $y i64) + ;; Right shifts with effective shift 0 should not be recognized as almost + ;; sign-extends. + (drop + (i32.shr_s + (i32.shl (local.get $x) (i32.const 16)) + (i32.const 32) + ) + ) + (drop + (i64.shr_s + (i64.shl (local.get $y) (i64.const 16)) + (i64.const 64) + ) + ) + ) + + ;; CHECK: (func $sign-ext-unnormalized-shifts (param $x i32) (param $y i64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 2147483647) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.eqz + ;; CHECK-NEXT: (i64.and + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: (i64.const 9223372036854775807) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $sign-ext-unnormalized-shifts (param $x i32) (param $y i64) + ;; Sign extensions with unnormalized shift amounts that have the same + ;; non-zero effective shift modulo the bitwidth. + (drop + (i32.eq + (i32.shr_s (i32.shl (local.get $x) (i32.const 33)) (i32.const 1)) + (i32.const 0) + ) + ) + (drop + (i64.eq + (i64.shr_s (i64.shl (local.get $y) (i64.const 65)) (i64.const 1)) + (i64.const 0) + ) + ) + ) ;; CHECK: (func $zero-ops (result i32) ;; CHECK-NEXT: (return ;; CHECK-NEXT: (i32.eq From 307b8feefba20d7f2803a7ca33ed8bcae923973f Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 23 Oct 2026 15:38:30 -0700 Subject: [PATCH 4/5] refactor to helper --- src/passes/OptimizeInstructions.cpp | 44 ++++++++++++++--------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index 7a7d2915d06..7c55a1557ee 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -604,19 +604,8 @@ struct OptimizeInstructions if (auto* ext = Properties::getAlmostSignExt(curr)) { Index extraLeftShifts; auto bits = Properties::getAlmostSignExtBits(curr, extraLeftShifts); - if (extraLeftShifts == 0) { - if (auto* load = getFallthrough(ext)->dynCast()) { - // pattern match a load of 8 bits and a sign extend using a shl of - // 24 then shr_s of 24 as well, etc. - 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) { - load->signed_ = true; - return replaceCurrent(ext); - } - } - } + 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) @@ -1062,15 +1051,8 @@ struct OptimizeInstructions if (auto* ext = Properties::getSignExtValue(curr)) { auto bits = Properties::getSignExtBits(curr); - if (auto* load = getFallthrough(ext)->dynCast()) { - 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) { - load->signed_ = true; - return replaceCurrent(ext); - } - } + if (trySignExtLoad(ext, bits)) { + return replaceCurrent(ext); } // Simple sign extends can be removed if the value is already // sign-extended. Note that we can handle the case of |curr| having a @@ -4447,6 +4429,24 @@ struct OptimizeInstructions return inner; } + // Given `ext`, the value being sign-extended by a parent sign-extension from + // `bits` low bits (e.g. 8 for `extend8_s` or `(x << 24) >>_s 24`), fold the + // sign-extension into a narrow load if possible. + bool trySignExtLoad(Expression* ext, Index bits) { + if (auto* load = getFallthrough(ext)->dynCast()) { + // Pattern match a load of 8 bits and a sign extend to 8 bits, etc. + 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) { + load->signed_ = true; + return true; + } + } + } + return false; + } + // Check if an expression is already sign-extended to an exact number of bits. bool isSignExted(Expression* curr, Index bits) { return getSignExtBits(curr) == bits; From 10bb05b54b03edf14ea68648855ad5a0f3e92243 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 26 Oct 2026 16:41:28 -0700 Subject: [PATCH 5/5] test comment --- test/lit/passes/optimize-instructions-mvp.wast | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/lit/passes/optimize-instructions-mvp.wast b/test/lit/passes/optimize-instructions-mvp.wast index 520ddc7631a..6208f178215 100644 --- a/test/lit/passes/optimize-instructions-mvp.wast +++ b/test/lit/passes/optimize-instructions-mvp.wast @@ -6190,7 +6190,9 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $compare-i64-load-s-sign-extend (param $addr i32) (param $x i64) - ;; Comparing i64 sign-extension against signed load on right or left. + ;; Comparing i64 sign-extension against signed load on right or left. The + ;; upper 32 bits are sign bits, and equal iff the lower bits are, so we can + ;; just do an unsigned comparison on the lower 32. (drop (i64.eq (i64.shr_s (i64.shl (local.get $x) (i64.const 32)) (i64.const 32))