diff --git a/src/ir/bits.cpp b/src/ir/bits.cpp index abce4c4cb60..afc2de08590 100644 --- a/src/ir/bits.cpp +++ b/src/ir/bits.cpp @@ -470,6 +470,18 @@ Index Provider::getMaxBits(Expression* curr) const { } Index Provider::getSignExtKeptBits(Expression* curr) const { + if (!curr->type.isInteger()) { + return 0; + } + if (auto* unary = curr->dynCast(); + unary && unary->op == ExtendSInt32 && unary->value->type.isInteger()) { + // 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). + auto innerBits = getSignExtKeptBits(unary->value); + return innerBits > 0 ? innerBits : 32; + } if (getSignExtValue(curr)) { if (auto* unary = curr->dynCast()) { switch (unary->op) { @@ -489,6 +501,11 @@ Index Provider::getSignExtKeptBits(Expression* curr) const { return curr->type.getByteSize() * 8 - getEffectiveShifts(rightShift); } } + if (auto* load = curr->dynCast()) { + if (LoadUtils::isSignRelevant(load) && load->signed_) { + return load->bytes * 8; + } + } if (auto* get = curr->dynCast()) { return localInfoProvider.getSignExtKeptBitsForLocal(get); } diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index 097c2a78159..4b915c263b2 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -135,12 +135,7 @@ struct LocalScanner : PostWalker { info.maxBits = std::min(info.maxBits, getBitsForType(type)); auto keptBits = Bits::getSignExtKeptBits(value); if (keptBits == 0) { - if (auto* load = value->dynCast(); - load && LoadUtils::isSignRelevant(load) && load->signed_) { - keptBits = load->bytes * 8; - } else { - keptBits = LocalInfo::kUnknown; - } + keptBits = LocalInfo::kUnknown; } if (info.signExtKeptBits == 0) { info.signExtKeptBits = keptBits; // first info we see @@ -607,17 +602,39 @@ struct OptimizeInstructions 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) { + // A wider (or equal) outer shift sign-extension is redundant around a + // narrower inner sign-extension: if |ext| is already sign-extended with + // at most |keptBits - extraLeftShifts| kept bits, shifting it left by + // |extraLeftShifts| and sign-extending from |keptBits| bits only + // re-extends bits that are already copies of the sign bit, so we can + // remove the outer sign-extension (leaving any |extraLeftShifts|). + if (hasSignExtKeptBits(ext, keptBits - extraLeftShifts)) { + return replaceCurrent(removeAlmostSignExt(curr)); + } + // Conversely, when the inner sign-extension is wider than the outer shift + // sign-extension, we can strip the redundant inner sign-extension. + if (auto* innerExt = Bits::getSignExtValue(ext)) { + assert(Bits::getSignExtKeptBits(ext) > keptBits - extraLeftShifts); + curr->left->cast()->left = innerExt; + return replaceCurrent(curr); + } + // Sign-extending the low 32 bits of a zero-extended i32 is a signed + // i32 extension (plus any remaining left shifts). + if (auto* unary = ext->dynCast(); + unary && unary->op == ExtendUInt32 && + keptBits - extraLeftShifts == 32) { + unary->op = ExtendSInt32; return replaceCurrent(removeAlmostSignExt(curr)); } } else if (curr->op == EqInt32 || curr->op == NeInt32 || curr->op == EqInt64 || curr->op == NeInt64) { if (auto* c = curr->right->dynCast()) { if (auto* ext = Bits::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 + // extend. + // // We are comparing a sign extend to a constant, which means we can // use a cheaper zero-extend in some cases. That is, // (x << S) >> S ==/!= C => x & T ==/!= C @@ -673,6 +690,10 @@ struct OptimizeInstructions if (Bits::getSignExtKeptBits(curr->right) == bits) { // we are comparing two sign-exts with the same bits, so we may as // well replace both with cheaper zexts + // 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 + // extend. curr->left = makeZeroExt(left, bits); curr->right = makeZeroExt(right, bits); return replaceCurrent(curr); @@ -1044,19 +1065,39 @@ struct OptimizeInstructions } if (auto* ext = Bits::getSignExtValue(curr)) { + using namespace Match; auto currBits = Bits::getSignExtKeptBits(curr); if (trySignExtLoad(ext, currBits)) { return replaceCurrent(ext); } - // Simple sign extends can be removed if the value is already + // If the sign-extend input cannot have a sign bit, we don't need it. + if (bits.getMaxBits(ext) < currBits) { + return replaceCurrent(ext); + } + // Simple sign extends can also be removed if the input 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 extBits = bits.getSignExtKeptBits(ext); - if (extBits > 0 && extBits <= currBits) { + // 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 (hasSignExtKeptBits(ext, currBits)) { return replaceCurrent(ext); } + // A narrower unary sign-extension can strip a wider inner one. Since + // hasSignExtKeptBits(ext, currBits) was false above, any inner + // sign-extension is strictly wider than |currBits|. + if (auto* innerExt = Bits::getSignExtValue(ext)) { + assert(Bits::getSignExtKeptBits(ext) > currBits); + curr->value = innerExt; + return replaceCurrent(curr); + } + // 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); + } } if (Abstract::hasAnyReinterpret(curr->op)) { @@ -1096,6 +1137,9 @@ struct OptimizeInstructions if (auto* ext = Bits::getSignExtValue(curr->value)) { // we are comparing a sign extend to a constant, which means we can // use a cheaper zext + // 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 extend. auto bits = Bits::getSignExtKeptBits(curr->value); curr->value = makeZeroExt(ext, bits); return replaceCurrent(curr); @@ -4449,10 +4493,11 @@ struct OptimizeInstructions return false; } - // Check if an expression is already sign-extended with an exact number of + // Check if an expression is already sign-extended with at most |keptBits| // kept bits. bool hasSignExtKeptBits(Expression* curr, Index keptBits) { - return bits.getSignExtKeptBits(curr) == keptBits; + auto currKeptBits = bits.getSignExtKeptBits(curr); + return currKeptBits > 0 && currKeptBits <= keptBits; } // optimize trivial math operations, given that the right side of a binary diff --git a/test/lit/passes/optimize-instructions-mvp.wast b/test/lit/passes/optimize-instructions-mvp.wast index 6208f178215..442b7fedc9a 100644 --- a/test/lit/passes/optimize-instructions-mvp.wast +++ b/test/lit/passes/optimize-instructions-mvp.wast @@ -5999,6 +5999,237 @@ ) ) ) + + ;; CHECK: (func $redundant-shift-sign-ext (param $x32 i32) (param $x64 i64) (param $addr i32) + ;; CHECK-NEXT: (local $t64 i64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.shr_s + ;; CHECK-NEXT: (i32.shl + ;; CHECK-NEXT: (local.get $x32) + ;; CHECK-NEXT: (i32.const 24) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 24) + ;; 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: (i64.extend_i32_s + ;; CHECK-NEXT: (local.get $x32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.extend_i32_s + ;; CHECK-NEXT: (i32.shr_s + ;; CHECK-NEXT: (i32.shl + ;; CHECK-NEXT: (local.get $x32) + ;; CHECK-NEXT: (i32.const 24) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 24) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $t64 + ;; CHECK-NEXT: (i64.extend_i32_s + ;; CHECK-NEXT: (local.get $x32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $t64) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.shr_s + ;; CHECK-NEXT: (i32.shl + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (local.get $addr) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 24) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 24) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $redundant-shift-sign-ext (param $x32 i32) (param $x64 i64) (param $addr i32) + (local $t64 i64) + ;; Outer shift sign-extension is redundant when the inner value is already + ;; sign-extended to fewer or equal bits. + (drop + (i32.shr_s + (i32.shl + (i32.shr_s (i32.shl (local.get $x32) (i32.const 24)) (i32.const 24)) + (i32.const 16) + ) + (i32.const 16) + ) + ) + ;; Signed narrow load is already sign-extended to 8 bits. + (drop + (i32.shr_s + (i32.shl (i32.load8_s (local.get $addr)) (i32.const 16)) + (i32.const 16) + ) + ) + ;; i64.extend_i32_s is already sign-extended to 32 bits. + (drop + (i64.shr_s + (i64.shl (i64.extend_i32_s (local.get $x32)) (i64.const 32)) + (i64.const 32) + ) + ) + ;; i64.extend_i32_s of an 8-bit sign-extended i32 is sign-extended to 8 bits. + (drop + (i64.shr_s + (i64.shl + (i64.extend_i32_s + (i32.shr_s (i32.shl (local.get $x32) (i32.const 24)) (i32.const 24)) + ) + (i64.const 48) + ) + (i64.const 48) + ) + ) + ;; Local initialized with i64.extend_i32_s is known to be 32-bit sign-extended. + (local.set $t64 (i64.extend_i32_s (local.get $x32))) + (drop + (i64.shr_s + (i64.shl (local.get $t64) (i64.const 32)) + (i64.const 32) + ) + ) + ;; Negative case: full-width load is not sign-extended to 8 bits. + (drop + (i32.shr_s + (i32.shl (i32.load (local.get $addr)) (i32.const 24)) + (i32.const 24) + ) + ) + ) + + ;; CHECK: (func $nested-shift-sign-ext (param $x32 i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.shr_s + ;; CHECK-NEXT: (i32.shl + ;; CHECK-NEXT: (local.get $x32) + ;; CHECK-NEXT: (i32.const 24) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 24) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.shr_s + ;; CHECK-NEXT: (i32.shl + ;; CHECK-NEXT: (local.get $x32) + ;; CHECK-NEXT: (i32.const 26) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 24) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.shl + ;; CHECK-NEXT: (i32.shr_s + ;; CHECK-NEXT: (i32.shl + ;; CHECK-NEXT: (local.get $x32) + ;; CHECK-NEXT: (i32.const 24) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 24) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 8) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.extend_i32_s + ;; CHECK-NEXT: (local.get $x32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.shl + ;; CHECK-NEXT: (i64.extend_i32_s + ;; CHECK-NEXT: (local.get $x32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.const 8) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.shr_s + ;; CHECK-NEXT: (i64.shl + ;; CHECK-NEXT: (i64.extend_i32_u + ;; CHECK-NEXT: (local.get $x32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.const 48) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.const 48) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $nested-shift-sign-ext (param $x32 i32) + ;; Narrower outer shift sign-extension strips wider inner shift sign-extension. + (drop + (i32.shr_s + (i32.shl + (i32.shr_s (i32.shl (local.get $x32) (i32.const 16)) (i32.const 16)) + (i32.const 24) + ) + (i32.const 24) + ) + ) + ;; Also strips when the outer shift is an almost-sign-ext with extra left shifts + ;; that still only read bits inside the inner sign-extended width. + (drop + (i32.shr_s + (i32.shl + (i32.shr_s (i32.shl (local.get $x32) (i32.const 24)) (i32.const 24)) + (i32.const 26) + ) + (i32.const 24) + ) + ) + ;; Conversely, when the outer almost-sign-ext is wider than the inner + ;; sign-extension, the outer sign-extension is stripped (leaving any extra + ;; left shifts). + (drop + (i32.shr_s + (i32.shl + (i32.shr_s (i32.shl (local.get $x32) (i32.const 24)) (i32.const 24)) + (i32.const 16) + ) + (i32.const 8) + ) + ) + ;; Sign-extending the low 32 bits of a zero-extended i32 is a signed i32 extension. + (drop + (i64.shr_s + (i64.shl + (i64.extend_i32_u (local.get $x32)) + (i64.const 32) + ) + (i64.const 32) + ) + ) + ;; Also applies when there are remaining left shifts after shifting the i32 + ;; sign bit into the i64 sign bit. + (drop + (i64.shr_s + (i64.shl + (i64.extend_i32_u (local.get $x32)) + (i64.const 32) + ) + (i64.const 24) + ) + ) + ;; Negative case: shifting by more than 32 bits sign-extends a narrower width. + (drop + (i64.shr_s + (i64.shl + (i64.extend_i32_u (local.get $x32)) + (i64.const 48) + ) + (i64.const 48) + ) + ) + ) ;; CHECK: (func $compare-load-s-sign-extend (param $0 i32) (param $1 i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.eq diff --git a/test/lit/passes/optimize-instructions-sign_ext.wast b/test/lit/passes/optimize-instructions-sign_ext.wast index bf5594569bf..a5361d89782 100644 --- a/test/lit/passes/optimize-instructions-sign_ext.wast +++ b/test/lit/passes/optimize-instructions-sign_ext.wast @@ -365,6 +365,175 @@ ) ) + ;; CHECK: (func $redundant-unary-sign-ext-max-bits (type $2) (param $x32 i32) (param $x64 i64) (param $addr i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (local.get $x32) + ;; CHECK-NEXT: (i32.const 127) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.load8_u + ;; CHECK-NEXT: (local.get $addr) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.and + ;; CHECK-NEXT: (local.get $x64) + ;; CHECK-NEXT: (i64.const 32767) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.load16_u + ;; CHECK-NEXT: (local.get $addr) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.extend8_s + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (local.get $x32) + ;; CHECK-NEXT: (i32.const 255) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $redundant-unary-sign-ext-max-bits (param $x32 i32) (param $x64 i64) (param $addr i32) + ;; Eliminate unary sign-extend when maxBits < bits (the sign bit is known 0). + (drop (i32.extend8_s (i32.and (local.get $x32) (i32.const 0x7f)))) + (drop (i32.extend16_s (i32.load8_u (local.get $addr)))) + (drop (i64.extend16_s (i64.and (local.get $x64) (i64.const 0x7fff)))) + (drop (i64.extend32_s (i64.load16_u (local.get $addr)))) + ;; Negative case: maxBits == bits (the sign bit may be 1). + (drop (i32.extend8_s (i32.and (local.get $x32) (i32.const 0xff)))) + ) + + ;; CHECK: (func $redundant-unary-sign-ext-already-signed (type $4) (param $x32 i32) (param $addr i32) + ;; CHECK-NEXT: (local $t64 i64) + ;; CHECK-NEXT: (local $t64_8 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: (i64.load16_s + ;; CHECK-NEXT: (local.get $addr) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.extend_i32_s + ;; CHECK-NEXT: (local.get $x32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.extend_i32_s + ;; CHECK-NEXT: (i32.extend8_s + ;; CHECK-NEXT: (local.get $x32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $t64 + ;; CHECK-NEXT: (i64.extend_i32_s + ;; CHECK-NEXT: (local.get $x32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $t64) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $t64_8 + ;; CHECK-NEXT: (i64.extend_i32_s + ;; CHECK-NEXT: (i32.extend8_s + ;; CHECK-NEXT: (local.get $x32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $t64_8) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.extend8_s + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (local.get $addr) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $redundant-unary-sign-ext-already-signed (param $x32 i32) (param $addr i32) + (local $t64 i64) + (local $t64_8 i64) + ;; Eliminate unary sign-extend when input is already sign-extended to <= bits + ;; via signed loads, i64.extend_i32_s, or locals. + (drop (i32.extend8_s (i32.load8_s (local.get $addr)))) + (drop (i32.extend16_s (i32.load8_s (local.get $addr)))) + (drop (i64.extend32_s (i64.load16_s (local.get $addr)))) + (drop (i64.extend32_s (i64.extend_i32_s (local.get $x32)))) + (drop (i64.extend16_s (i64.extend_i32_s (i32.extend8_s (local.get $x32))))) + ;; Local initialized with i64.extend_i32_s is known to be 32-bit sign-extended. + (local.set $t64 (i64.extend_i32_s (local.get $x32))) + (drop (i64.extend32_s (local.get $t64))) + ;; Local initialized with i64.extend_i32_s(i32.extend8_s) is 8-bit sign-extended. + (local.set $t64_8 (i64.extend_i32_s (i32.extend8_s (local.get $x32)))) + (drop (i64.extend16_s (local.get $t64_8))) + ;; Negative case: full-width load is not sign-extended. + (drop (i32.extend8_s (i32.load (local.get $addr)))) + ) + + ;; CHECK: (func $nested-unary-sign-ext (type $3) (param $x32 i32) (param $x64 i64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.extend8_s + ;; CHECK-NEXT: (local.get $x32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.extend8_s + ;; CHECK-NEXT: (local.get $x64) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.extend16_s + ;; CHECK-NEXT: (local.get $x64) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.extend_i32_s + ;; CHECK-NEXT: (local.get $x32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.extend16_s + ;; CHECK-NEXT: (i64.extend_i32_u + ;; CHECK-NEXT: (local.get $x32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.shl + ;; CHECK-NEXT: (i32.extend8_s + ;; CHECK-NEXT: (local.get $x32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 8) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $nested-unary-sign-ext (param $x32 i32) (param $x64 i64) + ;; Strip wider inner sign-extension inside narrower outer sign-extension. + (drop (i32.extend8_s (i32.extend16_s (local.get $x32)))) + (drop (i64.extend8_s (i64.extend16_s (local.get $x64)))) + (drop (i64.extend16_s (i64.extend32_s (local.get $x64)))) + ;; Fold i64.extend32_s(i64.extend_i32_u(x)) => i64.extend_i32_s(x). + (drop (i64.extend32_s (i64.extend_i32_u (local.get $x32)))) + ;; Negative case: narrower extend of i64.extend_i32_u cannot be folded to extend_i32_s. + (drop (i64.extend16_s (i64.extend_i32_u (local.get $x32)))) + ;; Strip wider outer almost-sign-ext around narrower inner unary sign-ext, + ;; leaving the extra left shifts. + (drop (i32.shr_s (i32.shl (i32.extend8_s (local.get $x32)) (i32.const 16)) (i32.const 8))) + ) + ;; CHECK: (func $unary-sign-ext-loads (type $1) (param $addr i32) ;; CHECK-NEXT: (local $t32 i32) ;; CHECK-NEXT: (local $t64 i64) @@ -489,7 +658,7 @@ (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: (func $unary-sign-ext-eq-const (type $3) (param $x32 i32) (param $x64 i64) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.eq ;; CHECK-NEXT: (i32.and @@ -563,7 +732,7 @@ (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: (func $unary-sign-ext-eq-both (type $5) (param $x32 i32) (param $y32 i32) (param $x64 i64) (param $y64 i64) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.eq ;; CHECK-NEXT: (i32.and @@ -619,7 +788,7 @@ (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: (func $unary-sign-ext-eq-load (type $2) (param $x32 i32) (param $x64 i64) (param $addr i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.eq ;; CHECK-NEXT: (i32.load16_u @@ -661,7 +830,14 @@ (drop (i64.ne (i64.load8_s (local.get $addr)) (i64.extend8_s (local.get $x64)))) ) - ;; CHECK: (func $unary-sign-ext-newly-unreachable (type $5) (result i32) + ;; CHECK: (func $unary-sign-ext-newly-unreachable (type $6) (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.extend32_s + ;; CHECK-NEXT: (i64.extend_i32_s + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.eqz ;; CHECK-NEXT: (i32.extend8_s ;; CHECK-NEXT: (unreachable) @@ -670,8 +846,17 @@ ;; CHECK-NEXT: ) (func $unary-sign-ext-newly-unreachable (result i32) ;; The array.len traps on null and is replaced with unreachable before - ;; refinalization updates the enclosing i32.extend8_s. Do not treat the - ;; newly-unreachable operand as an integer sign-extended value. + ;; refinalization updates the enclosing sign extensions. Do not treat the + ;; newly-unreachable operands as integer sign-extended values. + (drop + (i64.extend32_s + (i64.extend_i32_s + (array.len + (ref.null array) + ) + ) + ) + ) (i32.eqz (i32.extend8_s (array.len diff --git a/test/passes/optimize-instructions_fuzz-exec.txt b/test/passes/optimize-instructions_fuzz-exec.txt index 141b9a94390..9d01b97fe01 100644 --- a/test/passes/optimize-instructions_fuzz-exec.txt +++ b/test/passes/optimize-instructions_fuzz-exec.txt @@ -262,9 +262,7 @@ (call $log (i32.shr_s (i32.shl - (i32.extend8_s - (local.get $0) - ) + (local.get $0) (i32.const 30) ) (i32.const 24)