From ede79b6ec4373c2503eac8e15028e782d54d0f53 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 4 Nov 2026 08:18:23 -0800 Subject: [PATCH 1/4] [NFC] Consolidate and rename sign-ext bit helpers 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. --- src/ir/bits.h | 5 +- src/ir/properties.h | 63 +++++++++++++-------- src/passes/OptimizeInstructions.cpp | 85 ++++++++++++++--------------- src/passes/PickLoadSigns.cpp | 4 +- 4 files changed, 86 insertions(+), 71 deletions(-) diff --git a/src/ir/bits.h b/src/ir/bits.h index 7f3c1f4b2cf..a4493e79930 100644 --- a/src/ir/bits.h +++ b/src/ir/bits.h @@ -145,7 +145,8 @@ inline Expression* makePackedFieldGet(Expression* value, return builder.makeBinary(AndInt32, value, builder.makeConst(int32_t(mask))); } -// getMaxBits() helper that has pessimistic results for the bits used in locals. +// LocalInfoProvider helper that has pessimistic results for the bits used in +// locals. struct DummyLocalInfoProvider { Index getMaxBitsForLocal(LocalGet* get) { if (get->type == Type::i32) { @@ -155,6 +156,8 @@ struct DummyLocalInfoProvider { } WASM_UNREACHABLE("type has no integer bit size"); } + + Index getSignExtKeptBitsForLocal(LocalGet* get) { return 0; } }; // Returns the maximum amount of bits used in an integer expression diff --git a/src/ir/properties.h b/src/ir/properties.h index 96d9786960f..790f0551f62 100644 --- a/src/ir/properties.h +++ b/src/ir/properties.h @@ -186,26 +186,39 @@ inline Expression* getSignExtValue(Expression* curr) { return nullptr; } -// gets the size of the sign-extended value -inline Index getSignExtBits(Expression* curr) { - 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"); +// If |curr| is known to be sign-extended, returns the number of low bits kept +// before sign-extending into the upper bits, or 0 otherwise. LocalInfoProvider +// is an optional class that can provide answers about local.get. +template +inline Index +getSignExtKeptBits(Expression* curr, + LocalInfoProvider* localInfoProvider = nullptr) { + if (getSignExtValue(curr)) { + 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 curr->type.getByteSize() * 8 - + Bits::getEffectiveShifts(rightShift); + } + } + if (auto* get = curr->dynCast()) { + if (localInfoProvider) { + return localInfoProvider->getSignExtKeptBitsForLocal(get); } - } else { - auto* rightShift = curr->cast()->right; - return curr->type.getByteSize() * 8 - Bits::getEffectiveShifts(rightShift); } + return 0; } // Check if an expression is almost a sign-extend: perhaps the inner shift @@ -229,14 +242,16 @@ inline Expression* getAlmostSignExt(Expression* curr) { return nullptr; } -// gets the size of the almost sign-extended value, as well as the -// extra shifts, if any -inline Index getAlmostSignExtBits(Expression* curr, Index& extraLeftShifts) { +// Returns the number of low bits kept by an almost-sign-extend (including any +// extra left-shifted zero bits), and sets |extraLeftShifts| to the number of +// extra left shifts. +inline Index getAlmostSignExtKeptBits(Expression* curr, + Index& extraLeftShifts) { auto* leftShift = curr->cast()->left->cast()->right; auto* rightShift = curr->cast()->right; extraLeftShifts = Bits::getEffectiveShifts(leftShift) - Bits::getEffectiveShifts(rightShift); - return getSignExtBits(curr); + return curr->type.getByteSize() * 8 - Bits::getEffectiveShifts(rightShift); } // Check if an expression is a zero-extend, and if so, returns the value @@ -256,8 +271,8 @@ inline Expression* getZeroExtValue(Expression* curr) { return nullptr; } -// gets the size of the sign-extended value -inline Index getZeroExtBits(Expression* curr) { +// Returns the number of low bits kept by a zero-extend operation. +inline Index getZeroExtKeptBits(Expression* curr) { assert(curr->type == Type::i32); int32_t mask = curr->cast()->right->cast()->value.geti32(); return Bits::getMaskedBits(mask); diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index cfeb478acaa..79702ec3597 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -83,7 +83,7 @@ struct LocalInfo { static const Index kUnknown = Index(-1); Index maxBits = -1; - Index signExtBits = 0; + Index signExtKeptBits = 0; }; struct LocalScanner : PostWalker { @@ -101,9 +101,10 @@ struct LocalScanner : PostWalker { auto& info = localInfo[i]; if (func->isParam(i)) { info.maxBits = getBitsForType(func->getLocalType(i)); // worst-case - info.signExtBits = LocalInfo::kUnknown; // we will never know anything + // We will never know anything. + info.signExtKeptBits = LocalInfo::kUnknown; } else { - info.maxBits = info.signExtBits = 0; // we are open to learning + info.maxBits = info.signExtKeptBits = 0; // we are open to learning } } // walk @@ -111,8 +112,8 @@ struct LocalScanner : PostWalker { // finalize for (Index i = 0; i < func->getNumLocals(); i++) { auto& info = localInfo[i]; - if (info.signExtBits == LocalInfo::kUnknown) { - info.signExtBits = 0; + if (info.signExtKeptBits == LocalInfo::kUnknown) { + info.signExtKeptBits = 0; } } } @@ -132,19 +133,20 @@ struct LocalScanner : PostWalker { auto& info = localInfo[curr->index]; info.maxBits = std::max(info.maxBits, Bits::getMaxBits(value, this)); info.maxBits = std::min(info.maxBits, getBitsForType(type)); - auto signExtBits = LocalInfo::kUnknown; - if (Properties::getSignExtValue(value)) { - signExtBits = Properties::getSignExtBits(value); - } else if (auto* load = value->dynCast()) { - if (LoadUtils::isSignRelevant(load) && load->signed_) { - signExtBits = load->bytes * 8; + auto keptBits = Properties::getSignExtKeptBits(value); + if (keptBits == 0) { + if (auto* load = value->dynCast(); + load && LoadUtils::isSignRelevant(load) && load->signed_) { + keptBits = load->bytes * 8; + } else { + keptBits = LocalInfo::kUnknown; } } - if (info.signExtBits == 0) { - info.signExtBits = signExtBits; // first info we see - } else if (info.signExtBits != signExtBits) { + if (info.signExtKeptBits == 0) { + info.signExtKeptBits = keptBits; // first info we see + } else if (info.signExtKeptBits != keptBits) { // contradictory information, give up - info.signExtBits = LocalInfo::kUnknown; + info.signExtKeptBits = LocalInfo::kUnknown; } } @@ -598,7 +600,7 @@ struct OptimizeInstructions } if (auto* ext = Properties::getAlmostSignExt(curr)) { Index extraLeftShifts; - auto bits = Properties::getAlmostSignExtBits(curr, extraLeftShifts); + auto bits = Properties::getAlmostSignExtKeptBits(curr, extraLeftShifts); if (extraLeftShifts == 0 && trySignExtLoad(ext, bits)) { return replaceCurrent(ext); } @@ -612,7 +614,7 @@ struct OptimizeInstructions // 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)); } } else if (curr->op == EqInt32 || curr->op == NeInt32 || @@ -631,7 +633,7 @@ struct OptimizeInstructions // sign-extended, that is, its effective sign bit is spread to all // 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); + auto bits = Properties::getSignExtKeptBits(curr->left); uint64_t right = c->value.getUnsigned(); Index totalBits = c->type.getByteSize() * 8; Index numRelevantBits = totalBits - bits + 1; @@ -670,8 +672,8 @@ struct OptimizeInstructions } } else if (auto* left = Properties::getSignExtValue(curr->left)) { if (auto* right = Properties::getSignExtValue(curr->right)) { - auto bits = Properties::getSignExtBits(curr->left); - if (Properties::getSignExtBits(curr->right) == bits) { + auto bits = Properties::getSignExtKeptBits(curr->left); + if (Properties::getSignExtKeptBits(curr->right) == bits) { // we are comparing two sign-exts with the same bits, so we may as // well replace both with cheaper zexts curr->left = makeZeroExt(left, bits); @@ -681,7 +683,7 @@ struct OptimizeInstructions } else if (auto* load = curr->right->dynCast()) { // we are comparing a load to a sign-ext, we may be able to switch // to zext - auto leftBits = Properties::getSignExtBits(curr->left); + auto leftBits = Properties::getSignExtKeptBits(curr->left); if (load->signed_ && leftBits == load->bytes * 8) { load->signed_ = false; curr->left = makeZeroExt(left, leftBits); @@ -692,7 +694,7 @@ struct OptimizeInstructions if (auto* right = Properties::getSignExtValue(curr->right)) { // we are comparing a load to a sign-ext, we may be able to switch // to zext - auto rightBits = Properties::getSignExtBits(curr->right); + auto rightBits = Properties::getSignExtKeptBits(curr->right); if (load->signed_ && rightBits == load->bytes * 8) { load->signed_ = false; curr->right = makeZeroExt(right, rightBits); @@ -1045,7 +1047,7 @@ struct OptimizeInstructions } if (auto* ext = Properties::getSignExtValue(curr)) { - auto bits = Properties::getSignExtBits(curr); + auto bits = Properties::getSignExtKeptBits(curr); if (trySignExtLoad(ext, bits)) { return replaceCurrent(ext); } @@ -1054,8 +1056,8 @@ struct OptimizeInstructions // 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) { + auto keptBits = Properties::getSignExtKeptBits(ext, this); + if (keptBits > 0 && keptBits <= bits) { return replaceCurrent(ext); } } @@ -1097,7 +1099,7 @@ struct OptimizeInstructions if (auto* ext = Properties::getSignExtValue(curr->value)) { // we are comparing a sign extend to a constant, which means we can // use a cheaper zext - auto bits = Properties::getSignExtBits(curr->value); + auto bits = Properties::getSignExtKeptBits(curr->value); curr->value = makeZeroExt(ext, bits); return replaceCurrent(curr); } @@ -1313,7 +1315,7 @@ struct OptimizeInstructions // if sign extending the exact bit size we store, we can skip the // extension if extending something bigger, then we just alter bits we // don't save anyhow - if (Properties::getSignExtBits(binary) >= Index(bytes) * 8) { + if (Properties::getSignExtKeptBits(binary) >= Index(bytes) * 8) { value = ext; } } @@ -2990,6 +2992,13 @@ struct OptimizeInstructions return getBitsForType(get->type); } + Index getSignExtKeptBitsForLocal(LocalGet* get) { + if (get->index < localInfo.size()) { + return localInfo[get->index].signExtKeptBits; + } + return 0; + } + private: // Information about our locals std::vector localInfo; @@ -3449,7 +3458,7 @@ struct OptimizeInstructions if (auto* ext = Properties::getSignExtValue(binary)) { // use a cheaper zero-extent, we just care about the boolean value // anyhow - return makeZeroExt(ext, Properties::getSignExtBits(binary)); + return makeZeroExt(ext, Properties::getSignExtKeptBits(binary)); } } else if (auto* block = boolean->dynCast()) { if (block->type == Type::i32 && block->list.size() > 0) { @@ -4443,22 +4452,10 @@ struct OptimizeInstructions 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; - } - - // 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()) { - // Check what we know about the local. - return localInfo[get->index].signExtBits; - } - return 0; + // Check if an expression is already sign-extended with an exact number of + // kept bits. + bool hasSignExtKeptBits(Expression* curr, Index bits) { + return Properties::getSignExtKeptBits(curr, this) == bits; } // optimize trivial math operations, given that the right side of a binary diff --git a/src/passes/PickLoadSigns.cpp b/src/passes/PickLoadSigns.cpp index f04f8451ca0..f419ef7a8eb 100644 --- a/src/passes/PickLoadSigns.cpp +++ b/src/passes/PickLoadSigns.cpp @@ -74,7 +74,7 @@ struct PickLoadSigns : public WalkerPass> { auto* parent = expressionStack[expressionStack.size() - i]; if (Properties::getZeroExtValue(parent) == curr) { - auto bits = Properties::getZeroExtBits(parent); + auto bits = Properties::getZeroExtKeptBits(parent); if (usage.unsignedUsages == 0) { usage.unsignedBits = bits; } else if (usage.unsignedBits != bits) { @@ -82,7 +82,7 @@ struct PickLoadSigns : public WalkerPass> { } usage.unsignedUsages++; } else if (Properties::getSignExtValue(parent) == curr) { - auto bits = Properties::getSignExtBits(parent); + auto bits = Properties::getSignExtKeptBits(parent); if (usage.signedUsages == 0) { usage.signedBits = bits; } else if (usage.signedBits != bits) { From 51eca14575cd37eacd421aca87e0e8e4ee65da73 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 6 Nov 2026 13:26:06 -0800 Subject: [PATCH 2/4] Use provider class and move to .cpp --- src/ir/CMakeLists.txt | 1 + src/ir/bits.cpp | 489 ++++++++++++++++++++++++++++ src/ir/bits.h | 461 +++++--------------------- src/ir/properties.h | 130 -------- src/passes/OptimizeInstructions.cpp | 103 +++--- src/passes/PickLoadSigns.cpp | 10 +- 6 files changed, 624 insertions(+), 570 deletions(-) create mode 100644 src/ir/bits.cpp diff --git a/src/ir/CMakeLists.txt b/src/ir/CMakeLists.txt index 4bedfc886ec..abf1bbc0d4d 100644 --- a/src/ir/CMakeLists.txt +++ b/src/ir/CMakeLists.txt @@ -1,6 +1,7 @@ FILE(GLOB ir_HEADERS *.h) set(ir_SOURCES abstract.cpp + bits.cpp ExpressionAnalyzer.cpp ExpressionManipulator.cpp constraint.cpp diff --git a/src/ir/bits.cpp b/src/ir/bits.cpp new file mode 100644 index 00000000000..10b6cdbb290 --- /dev/null +++ b/src/ir/bits.cpp @@ -0,0 +1,489 @@ +/* + * Copyright 2026 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ir/bits.h" +#include "ir/boolean.h" +#include "ir/literal-utils.h" +#include "ir/load-utils.h" +#include "ir/match.h" +#include "support/bits.h" +#include "wasm-builder.h" + +namespace wasm::Bits { + +Expression* makeSignExt(Expression* value, Index bytes, Module& wasm) { + if (value->type == Type::i32) { + if (bytes == 1 || bytes == 2) { + auto shifts = bytes == 1 ? 24 : 16; + Builder builder(wasm); + return builder.makeBinary( + ShrSInt32, + builder.makeBinary( + ShlInt32, + value, + LiteralUtils::makeFromInt32(shifts, Type::i32, wasm)), + LiteralUtils::makeFromInt32(shifts, Type::i32, wasm)); + } + assert(bytes == 4); + return value; // nothing to do + } else { + assert(value->type == Type::i64); + if (bytes == 1 || bytes == 2 || bytes == 4) { + auto shifts = bytes == 1 ? 56 : (bytes == 2 ? 48 : 32); + Builder builder(wasm); + return builder.makeBinary( + ShrSInt64, + builder.makeBinary( + ShlInt64, + value, + LiteralUtils::makeFromInt32(shifts, Type::i64, wasm)), + LiteralUtils::makeFromInt32(shifts, Type::i64, wasm)); + } + assert(bytes == 8); + return value; // nothing to do + } +} + +Expression* makePackedFieldGet(Expression* value, + const Field& field, + bool signed_, + Module& wasm) { + if (!field.isPacked()) { + return value; + } + + if (signed_) { + return makeSignExt(value, field.getByteSize(), wasm); + } + + Builder builder(wasm); + auto mask = Bits::lowBitMask(field.getByteSize() * 8); + return builder.makeBinary(AndInt32, value, builder.makeConst(int32_t(mask))); +} + +Expression* getSignExtValue(Expression* curr) { + // We only care about integers here, and ignore unreachables, etc. + if (!curr->type.isInteger()) { + return nullptr; + } + if (auto* unary = curr->dynCast()) { + switch (unary->op) { + case ExtendS8Int32: + case ExtendS16Int32: + case ExtendS8Int64: + case ExtendS16Int64: + case ExtendS32Int64: + // The operand may have become unreachable before refinalization updates + // `curr->type`. + return unary->value->type.isInteger() ? unary->value : nullptr; + default: + return nullptr; + } + } + using namespace Abstract; + using namespace Match; + int64_t leftShift = 0, rightShift = 0; + Expression* extended = nullptr; + if (matches(curr, + binary(ShrS, + binary(Shl, any(&extended), ival(&leftShift)), + ival(&rightShift)))) { + auto leftShifts = getEffectiveShifts(leftShift, curr->type); + auto rightShifts = getEffectiveShifts(rightShift, curr->type); + if (leftShifts != 0 && leftShifts == rightShifts) { + return extended; + } + } + return nullptr; +} + +Expression* getAlmostSignExt(Expression* curr) { + using namespace Abstract; + using namespace Match; + int64_t leftShift = 0, rightShift = 0; + Expression* extended = nullptr; + if (matches(curr, + binary(ShrS, + binary(Shl, any(&extended), ival(&leftShift)), + ival(&rightShift)))) { + auto leftShifts = getEffectiveShifts(leftShift, curr->type); + auto rightShifts = getEffectiveShifts(rightShift, curr->type); + if (rightShifts != 0 && rightShifts <= leftShifts) { + return extended; + } + } + return nullptr; +} + +Expression* getZeroExtValue(Expression* curr) { + // We only care about i32s here, and ignore i64s, unreachables, etc. + if (curr->type != Type::i32) { + return nullptr; + } + using namespace Match; + int32_t mask = 0; + Expression* extended = nullptr; + if (matches(curr, binary(AndInt32, any(&extended), i32(&mask))) && + getMaskedBits(mask) != 0) { + return extended; + } + return nullptr; +} + +Index Provider::getMaxBits(Expression* curr) const { + if (Properties::emitsBoolean(curr)) { + return 1; + } + if (auto* c = curr->dynCast()) { + switch (curr->type.getBasic()) { + case Type::i32: + return 32 - c->value.countLeadingZeroes().geti32(); + case Type::i64: + return 64 - c->value.countLeadingZeroes().geti64(); + default: + WASM_UNREACHABLE("invalid type"); + } + } else if (auto* binary = curr->dynCast()) { + switch (binary->op) { + // 32-bit + case RotLInt32: + case RotRInt32: + case SubInt32: + return 32; + case AddInt32: { + auto maxBitsLeft = getMaxBits(binary->left); + auto maxBitsRight = getMaxBits(binary->right); + return std::min(Index(32), std::max(maxBitsLeft, maxBitsRight) + 1); + } + case MulInt32: { + auto maxBitsRight = getMaxBits(binary->right); + auto maxBitsLeft = getMaxBits(binary->left); + return std::min(Index(32), maxBitsLeft + maxBitsRight); + } + case DivSInt32: { + if (auto* c = binary->right->dynCast()) { + int32_t maxBitsLeft = getMaxBits(binary->left); + // If either side might be negative, then the result will be + // negative + if (maxBitsLeft == 32 || c->value.geti32() < 0) { + return 32; + } + int32_t bitsRight = getMaxBits(c); + // Apply std::min: Result bits cannot exceed dividend bits + return std::min(maxBitsLeft, + std::max(0, maxBitsLeft - bitsRight + 1)); + } + return 32; + } + case DivUInt32: { + int32_t maxBitsLeft = getMaxBits(binary->left); + if (auto* c = binary->right->dynCast()) { + int32_t bitsRight = getMaxBits(c); + // Apply std::min: Result bits cannot exceed dividend bits + return std::min(maxBitsLeft, + std::max(0, maxBitsLeft - bitsRight + 1)); + } + return maxBitsLeft; + } + case RemSInt32: { + if (auto* c = binary->right->dynCast()) { + auto maxBitsLeft = getMaxBits(binary->left); + // if left may be negative, the result may be negative + if (maxBitsLeft == 32) { + return 32; + } + auto bitsRight = Index(ceilLog2(c->value.geti32())); + return std::min(maxBitsLeft, bitsRight); + } + return 32; + } + case RemUInt32: { + if (auto* c = binary->right->dynCast()) { + auto maxBitsLeft = getMaxBits(binary->left); + auto bitsRight = Index(ceilLog2(c->value.geti32())); + return std::min(maxBitsLeft, bitsRight); + } + return 32; + } + case AndInt32: { + return std::min(getMaxBits(binary->left), getMaxBits(binary->right)); + } + case OrInt32: + case XorInt32: { + return std::max(getMaxBits(binary->left), getMaxBits(binary->right)); + } + case ShlInt32: { + if (auto* shifts = binary->right->dynCast()) { + return std::min( + Index(32), getMaxBits(binary->left) + getEffectiveShifts(shifts)); + } + return 32; + } + case ShrUInt32: { + if (auto* shift = binary->right->dynCast()) { + auto maxBits = getMaxBits(binary->left); + auto shifts = + std::min(Index(getEffectiveShifts(shift)), + maxBits); // can ignore more shifts than zero us out + return std::max(Index(0), maxBits - shifts); + } + return 32; + } + case ShrSInt32: { + if (auto* shift = binary->right->dynCast()) { + auto maxBits = getMaxBits(binary->left); + // if left may be negative, the result may be negative + if (maxBits == 32) { + return 32; + } + auto shifts = + std::min(Index(getEffectiveShifts(shift)), + maxBits); // can ignore more shifts than zero us out + return std::max(Index(0), maxBits - shifts); + } + return 32; + } + case RotLInt64: + case RotRInt64: + case SubInt64: + return 64; + case AddInt64: { + auto maxBitsLeft = getMaxBits(binary->left); + auto maxBitsRight = getMaxBits(binary->right); + return std::min(Index(64), std::max(maxBitsLeft, maxBitsRight) + 1); + } + case MulInt64: { + auto maxBitsRight = getMaxBits(binary->right); + auto maxBitsLeft = getMaxBits(binary->left); + return std::min(Index(64), maxBitsLeft + maxBitsRight); + } + case DivSInt64: { + if (auto* c = binary->right->dynCast()) { + int32_t maxBitsLeft = getMaxBits(binary->left); + // if left or right const value is negative + if (maxBitsLeft == 64 || c->value.geti64() < 0) { + return 64; + } + int32_t bitsRight = getMaxBits(c); + // Apply std::min: Result bits cannot exceed dividend bits + return std::min(maxBitsLeft, + std::max(0, maxBitsLeft - bitsRight + 1)); + } + return 64; + } + case DivUInt64: { + int32_t maxBitsLeft = getMaxBits(binary->left); + if (auto* c = binary->right->dynCast()) { + int32_t bitsRight = getMaxBits(c); + // Apply std::min: Result bits cannot exceed dividend bits + return std::min(maxBitsLeft, + std::max(0, maxBitsLeft - bitsRight + 1)); + } + return maxBitsLeft; + } + case RemSInt64: { + if (auto* c = binary->right->dynCast()) { + auto maxBitsLeft = getMaxBits(binary->left); + // if left may be negative, the result may be negative + if (maxBitsLeft == 64) { + return 64; + } + auto bitsRight = Index(ceilLog2(c->value.geti64())); + return std::min(maxBitsLeft, bitsRight); + } + return 64; + } + case RemUInt64: { + if (auto* c = binary->right->dynCast()) { + auto maxBitsLeft = getMaxBits(binary->left); + auto bitsRight = Index(ceilLog2(c->value.geti64())); + return std::min(maxBitsLeft, bitsRight); + } + return 64; + } + case AndInt64: { + return std::min(getMaxBits(binary->left), getMaxBits(binary->right)); + } + case OrInt64: + case XorInt64: { + return std::max(getMaxBits(binary->left), getMaxBits(binary->right)); + } + case ShlInt64: { + if (auto* shifts = binary->right->dynCast()) { + auto maxBits = getMaxBits(binary->left); + return std::min(Index(64), getEffectiveShifts(shifts) + maxBits); + } + return 64; + } + case ShrUInt64: { + if (auto* shift = binary->right->dynCast()) { + auto maxBits = getMaxBits(binary->left); + auto shifts = + std::min(Index(getEffectiveShifts(shift)), + maxBits); // can ignore more shifts than zero us out + return std::max(Index(0), maxBits - shifts); + } + return 64; + } + case ShrSInt64: { + if (auto* shift = binary->right->dynCast()) { + auto maxBits = getMaxBits(binary->left); + // if left may be negative, the result may be negative + if (maxBits == 64) { + return 64; + } + auto shifts = + std::min(Index(getEffectiveShifts(shift)), + maxBits); // can ignore more shifts than zero us out + return std::max(Index(0), maxBits - shifts); + } + return 64; + } + // comparisons + case EqInt32: + case NeInt32: + case LtSInt32: + case LtUInt32: + case LeSInt32: + case LeUInt32: + case GtSInt32: + case GtUInt32: + case GeSInt32: + case GeUInt32: + + case EqInt64: + case NeInt64: + case LtSInt64: + case LtUInt64: + case LeSInt64: + case LeUInt64: + case GtSInt64: + case GtUInt64: + case GeSInt64: + case GeUInt64: + + case EqFloat32: + case NeFloat32: + case LtFloat32: + case LeFloat32: + case GtFloat32: + case GeFloat32: + + case EqFloat64: + case NeFloat64: + case LtFloat64: + case LeFloat64: + case GtFloat64: + case GeFloat64: + WASM_UNREACHABLE("relationals handled before"); + default: { + } + } + } else if (auto* unary = curr->dynCast()) { + switch (unary->op) { + case ClzInt32: + case CtzInt32: + case PopcntInt32: + return 6; + case ClzInt64: + case CtzInt64: + case PopcntInt64: + return 7; + case EqZInt32: + case EqZInt64: + WASM_UNREACHABLE("relationals handled before"); + case WrapInt64: + case ExtendUInt32: + return std::min(Index(32), getMaxBits(unary->value)); + case ExtendS8Int32: { + auto maxBits = getMaxBits(unary->value); + return maxBits >= 8 ? Index(32) : maxBits; + } + case ExtendS16Int32: { + auto maxBits = getMaxBits(unary->value); + return maxBits >= 16 ? Index(32) : maxBits; + } + case ExtendS8Int64: { + auto maxBits = getMaxBits(unary->value); + return maxBits >= 8 ? Index(64) : maxBits; + } + case ExtendS16Int64: { + auto maxBits = getMaxBits(unary->value); + return maxBits >= 16 ? Index(64) : maxBits; + } + case ExtendS32Int64: + case ExtendSInt32: { + auto maxBits = getMaxBits(unary->value); + return maxBits >= 32 ? Index(64) : maxBits; + } + default: { + } + } + } else if (auto* set = curr->dynCast()) { + // a tee passes through the value + return getMaxBits(set->value); + } else if (auto* get = curr->dynCast()) { + return localInfoProvider.getMaxBitsForLocal(get); + } else if (auto* load = curr->dynCast()) { + // if signed, then the sign-extension might fill all the bits + // if unsigned, then we have a limit + if (LoadUtils::isSignRelevant(load) && !load->signed_) { + return 8 * load->bytes; + } + } else if (auto* block = curr->dynCast()) { + if (!block->name.is() && !block->list.empty() && block->type.isConcrete()) { + return getMaxBits(block->list.back()); + } + } + switch (curr->type.getBasic()) { + case Type::i32: + return 32; + case Type::i64: + return 64; + case Type::unreachable: + return 64; // not interesting, but don't crash + default: + WASM_UNREACHABLE("invalid type"); + } +} + +Index Provider::getSignExtKeptBits(Expression* curr) const { + if (getSignExtValue(curr)) { + 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 curr->type.getByteSize() * 8 - getEffectiveShifts(rightShift); + } + } + if (auto* get = curr->dynCast()) { + return localInfoProvider.getSignExtKeptBitsForLocal(get); + } + return 0; +} + +} // namespace wasm::Bits diff --git a/src/ir/bits.h b/src/ir/bits.h index a4493e79930..39d4def9636 100644 --- a/src/ir/bits.h +++ b/src/ir/bits.h @@ -17,11 +17,11 @@ #ifndef wasm_ir_bits_h #define wasm_ir_bits_h -#include "ir/boolean.h" -#include "ir/literal-utils.h" -#include "ir/load-utils.h" #include "support/bits.h" -#include "wasm-builder.h" +#include "support/index.h" +#include "support/utilities.h" +#include "wasm-type.h" +#include "wasm.h" namespace wasm::Bits { @@ -88,38 +88,7 @@ inline Index getEffectiveShifts(Expression* expr) { WASM_UNREACHABLE("unexpected type"); } -inline Expression* makeSignExt(Expression* value, Index bytes, Module& wasm) { - if (value->type == Type::i32) { - if (bytes == 1 || bytes == 2) { - auto shifts = bytes == 1 ? 24 : 16; - Builder builder(wasm); - return builder.makeBinary( - ShrSInt32, - builder.makeBinary( - ShlInt32, - value, - LiteralUtils::makeFromInt32(shifts, Type::i32, wasm)), - LiteralUtils::makeFromInt32(shifts, Type::i32, wasm)); - } - assert(bytes == 4); - return value; // nothing to do - } else { - assert(value->type == Type::i64); - if (bytes == 1 || bytes == 2 || bytes == 4) { - auto shifts = bytes == 1 ? 56 : (bytes == 2 ? 48 : 32); - Builder builder(wasm); - return builder.makeBinary( - ShrSInt64, - builder.makeBinary( - ShlInt64, - value, - LiteralUtils::makeFromInt32(shifts, Type::i64, wasm)), - LiteralUtils::makeFromInt32(shifts, Type::i64, wasm)); - } - assert(bytes == 8); - return value; // nothing to do - } -} +Expression* makeSignExt(Expression* value, Index bytes, Module& wasm); // Given a value that is read from a field, as a replacement for a // StructGet/ArrayGet that we inferred the value of, and given the signedness of @@ -128,27 +97,54 @@ inline Expression* makeSignExt(Expression* value, Index bytes, Module& wasm) { // where we can replace the StructGet/ArrayGet with the value we know must be // there (without making any assumptions about |value|, that is, we do not // assume it has been truncated already). -inline Expression* makePackedFieldGet(Expression* value, - const Field& field, - bool signed_, - Module& wasm) { - if (!field.isPacked()) { - return value; - } +Expression* makePackedFieldGet(Expression* value, + const Field& field, + bool signed_, + Module& wasm); + +// Check if an expression is a sign-extend, and if so, returns the value +// that is extended, otherwise nullptr +Expression* getSignExtValue(Expression* curr); + +// Check if an expression is almost a sign-extend: perhaps the inner shift +// is too large. We can split the shifts in that case, which is sometimes +// useful (e.g. if we can remove the signext) +Expression* getAlmostSignExt(Expression* curr); + +// Returns the number of low bits kept by an almost-sign-extend (including any +// extra left-shifted zero bits), and sets |extraLeftShifts| to the number of +// extra left shifts. +inline Index getAlmostSignExtKeptBits(Expression* curr, + Index& extraLeftShifts) { + auto* leftShift = curr->cast()->left->cast()->right; + auto* rightShift = curr->cast()->right; + extraLeftShifts = + getEffectiveShifts(leftShift) - getEffectiveShifts(rightShift); + return curr->type.getByteSize() * 8 - getEffectiveShifts(rightShift); +} - if (signed_) { - return makeSignExt(value, field.getByteSize(), wasm); - } +// Check if an expression is a zero-extend, and if so, returns the value +// that is extended, otherwise nullptr +Expression* getZeroExtValue(Expression* curr); - Builder builder(wasm); - auto mask = Bits::lowBitMask(field.getByteSize() * 8); - return builder.makeBinary(AndInt32, value, builder.makeConst(int32_t(mask))); +// Returns the number of low bits kept by a zero-extend operation. +inline Index getZeroExtKeptBits(Expression* curr) { + assert(curr->type == Type::i32); + int32_t mask = curr->cast()->right->cast()->value.geti32(); + return getMaskedBits(mask); } +// Interface for providing known bit properties of locals. +struct LocalInfoProvider { + virtual ~LocalInfoProvider() = default; + virtual Index getMaxBitsForLocal(LocalGet* get) const = 0; + virtual Index getSignExtKeptBitsForLocal(LocalGet* get) const = 0; +}; + // LocalInfoProvider helper that has pessimistic results for the bits used in // locals. -struct DummyLocalInfoProvider { - Index getMaxBitsForLocal(LocalGet* get) { +struct DummyLocalInfoProvider : LocalInfoProvider { + Index getMaxBitsForLocal(LocalGet* get) const override { if (get->type == Type::i32) { return 32; } else if (get->type == Type::i64) { @@ -157,348 +153,49 @@ struct DummyLocalInfoProvider { WASM_UNREACHABLE("type has no integer bit size"); } - Index getSignExtKeptBitsForLocal(LocalGet* get) { return 0; } + Index getSignExtKeptBitsForLocal(LocalGet* get) const override { return 0; } }; -// Returns the maximum amount of bits used in an integer expression -// not extremely precise (doesn't look into add operands, etc.) -// LocalInfoProvider is an optional class that can provide answers about -// local.get. -template -Index getMaxBits(Expression* curr, - LocalInfoProvider* localInfoProvider = nullptr) { - if (Properties::emitsBoolean(curr)) { - return 1; - } - if (auto* c = curr->dynCast()) { - switch (curr->type.getBasic()) { - case Type::i32: - return 32 - c->value.countLeadingZeroes().geti32(); - case Type::i64: - return 64 - c->value.countLeadingZeroes().geti64(); - default: - WASM_UNREACHABLE("invalid type"); - } - } else if (auto* binary = curr->dynCast()) { - switch (binary->op) { - // 32-bit - case RotLInt32: - case RotRInt32: - case SubInt32: - return 32; - case AddInt32: { - auto maxBitsLeft = getMaxBits(binary->left, localInfoProvider); - auto maxBitsRight = getMaxBits(binary->right, localInfoProvider); - return std::min(Index(32), std::max(maxBitsLeft, maxBitsRight) + 1); - } - case MulInt32: { - auto maxBitsRight = getMaxBits(binary->right, localInfoProvider); - auto maxBitsLeft = getMaxBits(binary->left, localInfoProvider); - return std::min(Index(32), maxBitsLeft + maxBitsRight); - } - case DivSInt32: { - if (auto* c = binary->right->dynCast()) { - int32_t maxBitsLeft = getMaxBits(binary->left, localInfoProvider); - // If either side might be negative, then the result will be negative - if (maxBitsLeft == 32 || c->value.geti32() < 0) { - return 32; - } - int32_t bitsRight = getMaxBits(c); - // Apply std::min: Result bits cannot exceed dividend bits - return std::min(maxBitsLeft, - std::max(0, maxBitsLeft - bitsRight + 1)); - } - return 32; - } - case DivUInt32: { - int32_t maxBitsLeft = getMaxBits(binary->left, localInfoProvider); - if (auto* c = binary->right->dynCast()) { - int32_t bitsRight = getMaxBits(c); - // Apply std::min: Result bits cannot exceed dividend bits - return std::min(maxBitsLeft, - std::max(0, maxBitsLeft - bitsRight + 1)); - } - return maxBitsLeft; - } - case RemSInt32: { - if (auto* c = binary->right->dynCast()) { - auto maxBitsLeft = getMaxBits(binary->left, localInfoProvider); - // if left may be negative, the result may be negative - if (maxBitsLeft == 32) { - return 32; - } - auto bitsRight = Index(ceilLog2(c->value.geti32())); - return std::min(maxBitsLeft, bitsRight); - } - return 32; - } - case RemUInt32: { - if (auto* c = binary->right->dynCast()) { - auto maxBitsLeft = getMaxBits(binary->left, localInfoProvider); - auto bitsRight = Index(ceilLog2(c->value.geti32())); - return std::min(maxBitsLeft, bitsRight); - } - return 32; - } - case AndInt32: { - return std::min(getMaxBits(binary->left, localInfoProvider), - getMaxBits(binary->right, localInfoProvider)); - } - case OrInt32: - case XorInt32: { - return std::max(getMaxBits(binary->left, localInfoProvider), - getMaxBits(binary->right, localInfoProvider)); - } - case ShlInt32: { - if (auto* shifts = binary->right->dynCast()) { - return std::min(Index(32), - getMaxBits(binary->left, localInfoProvider) + - Bits::getEffectiveShifts(shifts)); - } - return 32; - } - case ShrUInt32: { - if (auto* shift = binary->right->dynCast()) { - auto maxBits = getMaxBits(binary->left, localInfoProvider); - auto shifts = - std::min(Index(Bits::getEffectiveShifts(shift)), - maxBits); // can ignore more shifts than zero us out - return std::max(Index(0), maxBits - shifts); - } - return 32; - } - case ShrSInt32: { - if (auto* shift = binary->right->dynCast()) { - auto maxBits = getMaxBits(binary->left, localInfoProvider); - // if left may be negative, the result may be negative - if (maxBits == 32) { - return 32; - } - auto shifts = - std::min(Index(Bits::getEffectiveShifts(shift)), - maxBits); // can ignore more shifts than zero us out - return std::max(Index(0), maxBits - shifts); - } - return 32; - } - case RotLInt64: - case RotRInt64: - case SubInt64: - return 64; - case AddInt64: { - auto maxBitsLeft = getMaxBits(binary->left, localInfoProvider); - auto maxBitsRight = getMaxBits(binary->right, localInfoProvider); - return std::min(Index(64), std::max(maxBitsLeft, maxBitsRight) + 1); - } - case MulInt64: { - auto maxBitsRight = getMaxBits(binary->right, localInfoProvider); - auto maxBitsLeft = getMaxBits(binary->left, localInfoProvider); - return std::min(Index(64), maxBitsLeft + maxBitsRight); - } - case DivSInt64: { - if (auto* c = binary->right->dynCast()) { - int32_t maxBitsLeft = getMaxBits(binary->left, localInfoProvider); - // if left or right const value is negative - if (maxBitsLeft == 64 || c->value.geti64() < 0) { - return 64; - } - int32_t bitsRight = getMaxBits(c); - // Apply std::min: Result bits cannot exceed dividend bits - return std::min(maxBitsLeft, - std::max(0, maxBitsLeft - bitsRight + 1)); - } - return 64; - } - case DivUInt64: { - int32_t maxBitsLeft = getMaxBits(binary->left, localInfoProvider); - if (auto* c = binary->right->dynCast()) { - int32_t bitsRight = getMaxBits(c); - // Apply std::min: Result bits cannot exceed dividend bits - return std::min(maxBitsLeft, - std::max(0, maxBitsLeft - bitsRight + 1)); - } - return maxBitsLeft; - } - case RemSInt64: { - if (auto* c = binary->right->dynCast()) { - auto maxBitsLeft = getMaxBits(binary->left, localInfoProvider); - // if left may be negative, the result may be negative - if (maxBitsLeft == 64) { - return 64; - } - auto bitsRight = Index(ceilLog2(c->value.geti64())); - return std::min(maxBitsLeft, bitsRight); - } - return 64; - } - case RemUInt64: { - if (auto* c = binary->right->dynCast()) { - auto maxBitsLeft = getMaxBits(binary->left, localInfoProvider); - auto bitsRight = Index(ceilLog2(c->value.geti64())); - return std::min(maxBitsLeft, bitsRight); - } - return 64; - } - case AndInt64: { - return std::min(getMaxBits(binary->left, localInfoProvider), - getMaxBits(binary->right, localInfoProvider)); - } - case OrInt64: - case XorInt64: { - return std::max(getMaxBits(binary->left, localInfoProvider), - getMaxBits(binary->right, localInfoProvider)); - } - case ShlInt64: { - if (auto* shifts = binary->right->dynCast()) { - auto maxBits = getMaxBits(binary->left, localInfoProvider); - return std::min(Index(64), - Bits::getEffectiveShifts(shifts) + maxBits); - } - return 64; - } - case ShrUInt64: { - if (auto* shift = binary->right->dynCast()) { - auto maxBits = getMaxBits(binary->left, localInfoProvider); - auto shifts = - std::min(Index(Bits::getEffectiveShifts(shift)), - maxBits); // can ignore more shifts than zero us out - return std::max(Index(0), maxBits - shifts); - } - return 64; - } - case ShrSInt64: { - if (auto* shift = binary->right->dynCast()) { - auto maxBits = getMaxBits(binary->left, localInfoProvider); - // if left may be negative, the result may be negative - if (maxBits == 64) { - return 64; - } - auto shifts = - std::min(Index(Bits::getEffectiveShifts(shift)), - maxBits); // can ignore more shifts than zero us out - return std::max(Index(0), maxBits - shifts); - } - return 64; - } - // comparisons - case EqInt32: - case NeInt32: - case LtSInt32: - case LtUInt32: - case LeSInt32: - case LeUInt32: - case GtSInt32: - case GtUInt32: - case GeSInt32: - case GeUInt32: +class Provider { + const LocalInfoProvider& localInfoProvider; - case EqInt64: - case NeInt64: - case LtSInt64: - case LtUInt64: - case LeSInt64: - case LeUInt64: - case GtSInt64: - case GtUInt64: - case GeSInt64: - case GeUInt64: + static inline const DummyLocalInfoProvider dummyLocalInfoProvider{}; - case EqFloat32: - case NeFloat32: - case LtFloat32: - case LeFloat32: - case GtFloat32: - case GeFloat32: +public: + Provider(const LocalInfoProvider* localInfoProvider = nullptr) + : localInfoProvider(localInfoProvider ? *localInfoProvider + : dummyLocalInfoProvider) {} - case EqFloat64: - case NeFloat64: - case LtFloat64: - case LeFloat64: - case GtFloat64: - case GeFloat64: - WASM_UNREACHABLE("relationals handled before"); - default: { - } - } - } else if (auto* unary = curr->dynCast()) { - switch (unary->op) { - case ClzInt32: - case CtzInt32: - case PopcntInt32: - return 6; - case ClzInt64: - case CtzInt64: - case PopcntInt64: - return 7; - case EqZInt32: - case EqZInt64: - WASM_UNREACHABLE("relationals handled before"); - case WrapInt64: - case ExtendUInt32: - return std::min(Index(32), getMaxBits(unary->value, localInfoProvider)); - case ExtendS8Int32: { - auto maxBits = getMaxBits(unary->value, localInfoProvider); - return maxBits >= 8 ? Index(32) : maxBits; - } - case ExtendS16Int32: { - auto maxBits = getMaxBits(unary->value, localInfoProvider); - return maxBits >= 16 ? Index(32) : maxBits; - } - case ExtendS8Int64: { - auto maxBits = getMaxBits(unary->value, localInfoProvider); - return maxBits >= 8 ? Index(64) : maxBits; - } - case ExtendS16Int64: { - auto maxBits = getMaxBits(unary->value, localInfoProvider); - return maxBits >= 16 ? Index(64) : maxBits; - } - case ExtendS32Int64: - case ExtendSInt32: { - auto maxBits = getMaxBits(unary->value, localInfoProvider); - return maxBits >= 32 ? Index(64) : maxBits; - } - default: { - } - } - } else if (auto* set = curr->dynCast()) { - // a tee passes through the value - return getMaxBits(set->value, localInfoProvider); - } else if (auto* get = curr->dynCast()) { - // TODO: Should this be optional? - assert(localInfoProvider); - return localInfoProvider->getMaxBitsForLocal(get); - } else if (auto* load = curr->dynCast()) { - // if signed, then the sign-extension might fill all the bits - // if unsigned, then we have a limit - if (LoadUtils::isSignRelevant(load) && !load->signed_) { - return 8 * load->bytes; - } - } else if (auto* block = curr->dynCast()) { - if (!block->name.is() && !block->list.empty() && block->type.isConcrete()) { - return getMaxBits(block->list.back(), localInfoProvider); + // Returns the maximum amount of bits used in an integer expression + // not extremely precise (doesn't look into add operands, etc.) + Index getMaxBits(Expression* curr) const; + + // As getMaxBits, but returns the minimum amount of bits. + Index getMinBits(Expression* curr) const { + if (auto* c = curr->dynCast()) { + // Constants are simple: the min and max are identical. + return getMaxBits(c); } + + // TODO: everything else + return 0; } - switch (curr->type.getBasic()) { - case Type::i32: - return 32; - case Type::i64: - return 64; - case Type::unreachable: - return 64; // not interesting, but don't crash - default: - WASM_UNREACHABLE("invalid type"); - } + + // If |curr| is known to be sign-extended, returns the number of low bits kept + // before sign-extending into the upper bits, or 0 otherwise. + Index getSignExtKeptBits(Expression* curr) const; +}; + +inline Index getMaxBits(Expression* curr) { + return Provider().getMaxBits(curr); } -// As getMaxBits, but returns the minimum amount of bits. inline Index getMinBits(Expression* curr) { - if (auto* c = curr->dynCast()) { - // Constants are simple: the min and max are identical. - return getMaxBits(c); - } + return Provider().getMinBits(curr); +} - // TODO: everything else - return 0; +inline Index getSignExtKeptBits(Expression* curr) { + return Provider().getSignExtKeptBits(curr); } } // namespace wasm::Bits diff --git a/src/ir/properties.h b/src/ir/properties.h index 790f0551f62..3067b57518c 100644 --- a/src/ir/properties.h +++ b/src/ir/properties.h @@ -148,136 +148,6 @@ 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 integers here, and ignore unreachables, etc. - if (!curr->type.isInteger()) { - return nullptr; - } - if (auto* unary = curr->dynCast()) { - switch (unary->op) { - case ExtendS8Int32: - case ExtendS16Int32: - case ExtendS8Int64: - case ExtendS16Int64: - case ExtendS32Int64: - // The operand may have become unreachable before refinalization updates - // `curr->type`. - return unary->value->type.isInteger() ? unary->value : nullptr; - default: - return nullptr; - } - } - using namespace Abstract; - using namespace Match; - int64_t leftShift = 0, rightShift = 0; - Expression* extended = nullptr; - if (matches(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 (leftShifts != 0 && leftShifts == rightShifts) { - return extended; - } - } - return nullptr; -} - -// If |curr| is known to be sign-extended, returns the number of low bits kept -// before sign-extending into the upper bits, or 0 otherwise. LocalInfoProvider -// is an optional class that can provide answers about local.get. -template -inline Index -getSignExtKeptBits(Expression* curr, - LocalInfoProvider* localInfoProvider = nullptr) { - if (getSignExtValue(curr)) { - 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 curr->type.getByteSize() * 8 - - Bits::getEffectiveShifts(rightShift); - } - } - if (auto* get = curr->dynCast()) { - if (localInfoProvider) { - return localInfoProvider->getSignExtKeptBitsForLocal(get); - } - } - return 0; -} - -// Check if an expression is almost a sign-extend: perhaps the inner shift -// 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; - int64_t leftShift = 0, rightShift = 0; - Expression* extended = nullptr; - if (matches(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 != 0 && rightShifts <= leftShifts) { - return extended; - } - } - return nullptr; -} - -// Returns the number of low bits kept by an almost-sign-extend (including any -// extra left-shifted zero bits), and sets |extraLeftShifts| to the number of -// extra left shifts. -inline Index getAlmostSignExtKeptBits(Expression* curr, - Index& extraLeftShifts) { - auto* leftShift = curr->cast()->left->cast()->right; - auto* rightShift = curr->cast()->right; - extraLeftShifts = - Bits::getEffectiveShifts(leftShift) - Bits::getEffectiveShifts(rightShift); - return curr->type.getByteSize() * 8 - Bits::getEffectiveShifts(rightShift); -} - -// Check if an expression is a zero-extend, and if so, returns the value -// that is extended, otherwise nullptr -inline Expression* getZeroExtValue(Expression* curr) { - // We only care about i32s here, and ignore i64s, unreachables, etc. - if (curr->type != Type::i32) { - return nullptr; - } - using namespace Match; - int32_t mask = 0; - Expression* extended = nullptr; - if (matches(curr, binary(AndInt32, any(&extended), i32(&mask))) && - Bits::getMaskedBits(mask) != 0) { - return extended; - } - return nullptr; -} - -// Returns the number of low bits kept by a zero-extend operation. -inline Index getZeroExtKeptBits(Expression* curr) { - assert(curr->type == Type::i32); - int32_t mask = curr->cast()->right->cast()->value.geti32(); - return Bits::getMaskedBits(mask); -} - // Returns a falling-through value, that is, it looks through a local.tee // and other operations that receive a value and let it flow through them. If // there is no value falling through, returns the node itself (as that is the diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index 79702ec3597..dd837974e98 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -131,9 +131,9 @@ struct LocalScanner : PostWalker { auto* value = Properties::getFallthrough(curr->value, passOptions, *getModule()); auto& info = localInfo[curr->index]; - info.maxBits = std::max(info.maxBits, Bits::getMaxBits(value, this)); + info.maxBits = std::max(info.maxBits, Bits::getMaxBits(value)); info.maxBits = std::min(info.maxBits, getBitsForType(type)); - auto keptBits = Properties::getSignExtKeptBits(value); + auto keptBits = Bits::getSignExtKeptBits(value); if (keptBits == 0) { if (auto* load = value->dynCast(); load && LoadUtils::isSignRelevant(load) && load->signed_) { @@ -149,10 +149,6 @@ struct LocalScanner : PostWalker { info.signExtKeptBits = LocalInfo::kUnknown; } } - - // define this for the templated getMaxBits method. we know nothing here yet - // about locals, so return the maxes - Index getMaxBitsForLocal(LocalGet* get) { return getBitsForType(get->type); } }; namespace { @@ -221,7 +217,8 @@ template struct Match::Internal::MatchSelf> { // Main pass class struct OptimizeInstructions - : public WalkerPass> { + : public WalkerPass>, + public Bits::LocalInfoProvider { bool isFunctionParallel() override { return true; } std::unique_ptr create() override { @@ -336,7 +333,7 @@ struct OptimizeInstructions // We should never be called with a constant. assert(!curr->is()); - if (!curr->type.isInteger() || Bits::getMaxBits(curr, this) != 0) { + if (!curr->type.isInteger() || bits.getMaxBits(curr) != 0) { return nullptr; } @@ -598,16 +595,16 @@ struct OptimizeInstructions } } } - if (auto* ext = Properties::getAlmostSignExt(curr)) { + if (auto* ext = Bits::getAlmostSignExt(curr)) { Index extraLeftShifts; - auto bits = Properties::getAlmostSignExtKeptBits(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 @@ -620,7 +617,7 @@ struct OptimizeInstructions } 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)) { + if (auto* ext = Bits::getSignExtValue(curr->left)) { // 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 @@ -633,7 +630,7 @@ struct OptimizeInstructions // sign-extended, that is, its effective sign bit is spread to all // the upper bits, which means that the relevant bits on the left // side are either all 0, or all 1. - auto bits = Properties::getSignExtKeptBits(curr->left); + auto bits = Bits::getSignExtKeptBits(curr->left); uint64_t right = c->value.getUnsigned(); Index totalBits = c->type.getByteSize() * 8; Index numRelevantBits = totalBits - bits + 1; @@ -670,10 +667,10 @@ struct OptimizeInstructions builder.makeSequence(builder.makeDrop(ext), c)); } } - } else if (auto* left = Properties::getSignExtValue(curr->left)) { - if (auto* right = Properties::getSignExtValue(curr->right)) { - auto bits = Properties::getSignExtKeptBits(curr->left); - if (Properties::getSignExtKeptBits(curr->right) == bits) { + } else if (auto* left = Bits::getSignExtValue(curr->left)) { + if (auto* right = Bits::getSignExtValue(curr->right)) { + auto bits = Bits::getSignExtKeptBits(curr->left); + 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 curr->left = makeZeroExt(left, bits); @@ -683,7 +680,7 @@ struct OptimizeInstructions } else if (auto* load = curr->right->dynCast()) { // we are comparing a load to a sign-ext, we may be able to switch // to zext - auto leftBits = Properties::getSignExtKeptBits(curr->left); + auto leftBits = Bits::getSignExtKeptBits(curr->left); if (load->signed_ && leftBits == load->bytes * 8) { load->signed_ = false; curr->left = makeZeroExt(left, leftBits); @@ -691,10 +688,10 @@ struct OptimizeInstructions } } } else if (auto* load = curr->left->dynCast()) { - if (auto* right = Properties::getSignExtValue(curr->right)) { + if (auto* right = Bits::getSignExtValue(curr->right)) { // we are comparing a load to a sign-ext, we may be able to switch // to zext - auto rightBits = Properties::getSignExtKeptBits(curr->right); + auto rightBits = Bits::getSignExtKeptBits(curr->right); if (load->signed_ && rightBits == load->bytes * 8) { load->signed_ = false; curr->right = makeZeroExt(right, rightBits); @@ -742,7 +739,7 @@ struct OptimizeInstructions return replaceCurrent(left); } } - auto lowMask = Bits::lowBitMask64(Bits::getMaxBits(left, this)); + auto lowMask = Bits::lowBitMask64(bits.getMaxBits(left)); if ((mask & lowMask) == lowMask) { // A mask of more bits than can be present is not needed. return replaceCurrent(left); @@ -763,7 +760,7 @@ struct OptimizeInstructions // faster on VMs. Also this opens more possibilities for further // simplifications afterwards. if (c >= 0 && (op = makeUnsignedBinaryOp(curr->op)) != InvalidBinary && - Bits::getMaxBits(curr->left, this) <= 31) { + bits.getMaxBits(curr->left) <= 31) { curr->op = op; } if (c < 0 && c > std::numeric_limits::min() && @@ -793,7 +790,7 @@ struct OptimizeInstructions int64_t c = right->value.geti64(); // See description above for Type::i32 if (c >= 0 && (op = makeUnsignedBinaryOp(curr->op)) != InvalidBinary && - Bits::getMaxBits(curr->left, this) <= 63) { + bits.getMaxBits(curr->left) <= 63) { curr->op = op; } if (getPassOptions().shrinkLevel == 0 && c < 0 && @@ -955,7 +952,7 @@ struct OptimizeInstructions Unary* inner; Expression* x; if (matches(curr, unary(EqZInt32, unary(&inner, WrapInt64, any(&x)))) && - Bits::getMaxBits(x, this) <= 32) { + bits.getMaxBits(x) <= 32) { inner->op = EqZInt64; return replaceCurrent(inner); } @@ -986,7 +983,7 @@ struct OptimizeInstructions UnaryOp unaryOp; if (matches(curr, unary(&unaryOp, unary(WrapInt64, any(&x))))) { if (unaryOp == ExtendSInt32 || unaryOp == ExtendUInt32) { - auto maxBits = Bits::getMaxBits(x, this); + auto maxBits = bits.getMaxBits(x); if ((unaryOp == ExtendSInt32 && maxBits <= 31) || (unaryOp == ExtendUInt32 && maxBits <= 32)) { return replaceCurrent(x); @@ -1046,8 +1043,8 @@ struct OptimizeInstructions } } - if (auto* ext = Properties::getSignExtValue(curr)) { - auto bits = Properties::getSignExtKeptBits(curr); + if (auto* ext = Bits::getSignExtValue(curr)) { + auto bits = Bits::getSignExtKeptBits(curr); if (trySignExtLoad(ext, bits)) { return replaceCurrent(ext); } @@ -1056,7 +1053,7 @@ struct OptimizeInstructions // 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 keptBits = Properties::getSignExtKeptBits(ext, this); + auto keptBits = this->bits.getSignExtKeptBits(ext); if (keptBits > 0 && keptBits <= bits) { return replaceCurrent(ext); } @@ -1096,10 +1093,10 @@ struct OptimizeInstructions } } // eqz of a sign extension can be of zero-extension - if (auto* ext = Properties::getSignExtValue(curr->value)) { + if (auto* ext = Bits::getSignExtValue(curr->value)) { // we are comparing a sign extend to a constant, which means we can // use a cheaper zext - auto bits = Properties::getSignExtKeptBits(curr->value); + auto bits = Bits::getSignExtKeptBits(curr->value); curr->value = makeZeroExt(ext, bits); return replaceCurrent(curr); } @@ -1311,11 +1308,11 @@ struct OptimizeInstructions } } } - } else if (auto* ext = Properties::getSignExtValue(binary)) { + } else if (auto* ext = Bits::getSignExtValue(binary)) { // if sign extending the exact bit size we store, we can skip the // extension if extending something bigger, then we just alter bits we // don't save anyhow - if (Properties::getSignExtKeptBits(binary) >= Index(bytes) * 8) { + if (Bits::getSignExtKeptBits(binary) >= Index(bytes) * 8) { value = ext; } } @@ -2982,7 +2979,7 @@ struct OptimizeInstructions } } - Index getMaxBitsForLocal(LocalGet* get) { + Index getMaxBitsForLocal(LocalGet* get) const override { // check what we know about the local (we may know nothing, if this local // was added after the pass scanned for locals; in that case, full // optimization may require another cycle) @@ -2992,7 +2989,7 @@ struct OptimizeInstructions return getBitsForType(get->type); } - Index getSignExtKeptBitsForLocal(LocalGet* get) { + Index getSignExtKeptBitsForLocal(LocalGet* get) const override { if (get->index < localInfo.size()) { return localInfo[get->index].signExtKeptBits; } @@ -3002,6 +2999,7 @@ struct OptimizeInstructions private: // Information about our locals std::vector localInfo; + Bits::Provider bits{this}; // Checks if the first is a local.tee and the second a local.get of that same // index. This is useful in the methods right below us, as it is a common @@ -3455,10 +3453,10 @@ struct OptimizeInstructions } } } - if (auto* ext = Properties::getSignExtValue(binary)) { + if (auto* ext = Bits::getSignExtValue(binary)) { // use a cheaper zero-extent, we just care about the boolean value // anyhow - return makeZeroExt(ext, Properties::getSignExtKeptBits(binary)); + return makeZeroExt(ext, Bits::getSignExtKeptBits(binary)); } } else if (auto* block = boolean->dynCast()) { if (block->type == Type::i32 && block->list.size() > 0) { @@ -3581,10 +3579,9 @@ struct OptimizeInstructions } } } - if (curr->type == Type::i32 && - Bits::getMaxBits(curr->condition, this) <= 1 && - Bits::getMaxBits(curr->ifTrue, this) <= 1 && - Bits::getMaxBits(curr->ifFalse, this) <= 1) { + if (curr->type == Type::i32 && bits.getMaxBits(curr->condition) <= 1 && + bits.getMaxBits(curr->ifTrue) <= 1 && + bits.getMaxBits(curr->ifFalse) <= 1) { // The condition and both arms are i32 booleans, which allows us to do // boolean optimizations. Expression* x; @@ -4102,7 +4099,7 @@ struct OptimizeInstructions auto* right = curr->right; // Check left's max bits and right is constant. - auto leftMaxBits = Bits::getMaxBits(left, this); + auto leftMaxBits = bits.getMaxBits(left); uint64_t maskLeft; // leftMaxBits may be greater than size if the lhs is unreachable but has // not been refinalized yet. @@ -4454,8 +4451,8 @@ struct OptimizeInstructions // Check if an expression is already sign-extended with an exact number of // kept bits. - bool hasSignExtKeptBits(Expression* curr, Index bits) { - return Properties::getSignExtKeptBits(curr, this) == bits; + bool hasSignExtKeptBits(Expression* curr, Index keptBits) { + return bits.getSignExtKeptBits(curr) == keptBits; } // optimize trivial math operations, given that the right side of a binary @@ -4529,24 +4526,24 @@ struct OptimizeInstructions // i32(bool(x)) != 0 ==> i32(bool(x)) if ((matches(curr, binary(EqInt32, any(&left), i32(1))) || matches(curr, binary(NeInt32, any(&left), i32(0)))) && - Bits::getMaxBits(left, this) == 1) { + bits.getMaxBits(left) == 1) { return left; } // i64(bool(x)) == 1 ==> i32(bool(x)) // i64(bool(x)) != 0 ==> i32(bool(x)) if ((matches(curr, binary(EqInt64, any(&left), i64(1))) || matches(curr, binary(NeInt64, any(&left), i64(0)))) && - Bits::getMaxBits(left, this) == 1) { + bits.getMaxBits(left) == 1) { return builder.makeUnary(WrapInt64, left); } // bool(x) != 1 ==> !bool(x) if (matches(curr, binary(Ne, any(&left), ival(1))) && - Bits::getMaxBits(left, this) == 1) { + bits.getMaxBits(left) == 1) { return builder.makeUnary(Abstract::getUnary(type, EqZ), left); } // bool(x) ^ 1 ==> !bool(x) if (matches(curr, binary(Xor, any(&left), ival(1))) && - Bits::getMaxBits(left, this) == 1) { + bits.getMaxBits(left) == 1) { auto* result = builder.makeUnary(Abstract::getUnary(type, EqZ), left); if (left->type == Type::i64) { // Xor's result is also an i64 in this case, but EqZ returns i32, so we @@ -4560,7 +4557,7 @@ struct OptimizeInstructions } // bool(x) | 1 ==> 1 if (matches(curr, binary(Or, any(&left), ival(1))) && - Bits::getMaxBits(left, this) == 1) { + bits.getMaxBits(left) == 1) { return getDroppedChildrenAndAppend(curr, right); } @@ -4808,8 +4805,8 @@ struct OptimizeInstructions // If we know nothing about a limit on the amount of bits on either side, // give up. auto typeMaxBits = getBitsForType(binary->type); - auto leftMaxBits = Bits::getMaxBits(binary->left, this); - auto rightMaxBits = Bits::getMaxBits(binary->right, this); + auto leftMaxBits = bits.getMaxBits(binary->left); + auto rightMaxBits = bits.getMaxBits(binary->right); if (std::max(leftMaxBits, rightMaxBits) == typeMaxBits) { return true; } @@ -5248,11 +5245,11 @@ struct OptimizeInstructions { // Check if there is a nontrivial amount of bits on the left, which may // provide enough to optimize. - auto leftMaxBits = Bits::getMaxBits(curr->left, this); + auto leftMaxBits = bits.getMaxBits(curr->left); auto type = curr->left->type; if (leftMaxBits < getBitsForType(type)) { using namespace Abstract; - auto rightMinBits = Bits::getMinBits(curr->right); + auto rightMinBits = bits.getMinBits(curr->right); auto rightIsNegative = rightMinBits == getBitsForType(type); if (leftMaxBits < rightMinBits) { // There are not enough bits on the left for it to be equal to the @@ -5404,7 +5401,7 @@ struct OptimizeInstructions } case EqZInt32: { // eqz(eqz(bool(x))) ==> bool(x) - if (Bits::getMaxBits(unaryInner->value, this) == 1) { + if (bits.getMaxBits(unaryInner->value) == 1) { return unaryInner->value; } break; diff --git a/src/passes/PickLoadSigns.cpp b/src/passes/PickLoadSigns.cpp index f419ef7a8eb..1b4feb6d46e 100644 --- a/src/passes/PickLoadSigns.cpp +++ b/src/passes/PickLoadSigns.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include +#include "ir/bits.h" #include #include @@ -73,16 +73,16 @@ struct PickLoadSigns : public WalkerPass> { } auto* parent = expressionStack[expressionStack.size() - i]; - if (Properties::getZeroExtValue(parent) == curr) { - auto bits = Properties::getZeroExtKeptBits(parent); + if (Bits::getZeroExtValue(parent) == curr) { + auto bits = Bits::getZeroExtKeptBits(parent); if (usage.unsignedUsages == 0) { usage.unsignedBits = bits; } else if (usage.unsignedBits != bits) { usage.unsignedBits = 0; } usage.unsignedUsages++; - } else if (Properties::getSignExtValue(parent) == curr) { - auto bits = Properties::getSignExtKeptBits(parent); + } else if (Bits::getSignExtValue(parent) == curr) { + auto bits = Bits::getSignExtKeptBits(parent); if (usage.signedUsages == 0) { usage.signedBits = bits; } else if (usage.signedBits != bits) { From 81e701e2b1ce8360f3c3eaf269d7f1d6de018f02 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 6 Nov 2026 13:47:58 -0800 Subject: [PATCH 3/4] undo moving large amounts of code --- src/ir/CMakeLists.txt | 1 - src/ir/bits.cpp | 489 ------------------------------------------ src/ir/bits.h | 475 ++++++++++++++++++++++++++++++++++++++-- 3 files changed, 461 insertions(+), 504 deletions(-) delete mode 100644 src/ir/bits.cpp diff --git a/src/ir/CMakeLists.txt b/src/ir/CMakeLists.txt index abf1bbc0d4d..4bedfc886ec 100644 --- a/src/ir/CMakeLists.txt +++ b/src/ir/CMakeLists.txt @@ -1,7 +1,6 @@ FILE(GLOB ir_HEADERS *.h) set(ir_SOURCES abstract.cpp - bits.cpp ExpressionAnalyzer.cpp ExpressionManipulator.cpp constraint.cpp diff --git a/src/ir/bits.cpp b/src/ir/bits.cpp deleted file mode 100644 index 10b6cdbb290..00000000000 --- a/src/ir/bits.cpp +++ /dev/null @@ -1,489 +0,0 @@ -/* - * Copyright 2026 WebAssembly Community Group participants - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "ir/bits.h" -#include "ir/boolean.h" -#include "ir/literal-utils.h" -#include "ir/load-utils.h" -#include "ir/match.h" -#include "support/bits.h" -#include "wasm-builder.h" - -namespace wasm::Bits { - -Expression* makeSignExt(Expression* value, Index bytes, Module& wasm) { - if (value->type == Type::i32) { - if (bytes == 1 || bytes == 2) { - auto shifts = bytes == 1 ? 24 : 16; - Builder builder(wasm); - return builder.makeBinary( - ShrSInt32, - builder.makeBinary( - ShlInt32, - value, - LiteralUtils::makeFromInt32(shifts, Type::i32, wasm)), - LiteralUtils::makeFromInt32(shifts, Type::i32, wasm)); - } - assert(bytes == 4); - return value; // nothing to do - } else { - assert(value->type == Type::i64); - if (bytes == 1 || bytes == 2 || bytes == 4) { - auto shifts = bytes == 1 ? 56 : (bytes == 2 ? 48 : 32); - Builder builder(wasm); - return builder.makeBinary( - ShrSInt64, - builder.makeBinary( - ShlInt64, - value, - LiteralUtils::makeFromInt32(shifts, Type::i64, wasm)), - LiteralUtils::makeFromInt32(shifts, Type::i64, wasm)); - } - assert(bytes == 8); - return value; // nothing to do - } -} - -Expression* makePackedFieldGet(Expression* value, - const Field& field, - bool signed_, - Module& wasm) { - if (!field.isPacked()) { - return value; - } - - if (signed_) { - return makeSignExt(value, field.getByteSize(), wasm); - } - - Builder builder(wasm); - auto mask = Bits::lowBitMask(field.getByteSize() * 8); - return builder.makeBinary(AndInt32, value, builder.makeConst(int32_t(mask))); -} - -Expression* getSignExtValue(Expression* curr) { - // We only care about integers here, and ignore unreachables, etc. - if (!curr->type.isInteger()) { - return nullptr; - } - if (auto* unary = curr->dynCast()) { - switch (unary->op) { - case ExtendS8Int32: - case ExtendS16Int32: - case ExtendS8Int64: - case ExtendS16Int64: - case ExtendS32Int64: - // The operand may have become unreachable before refinalization updates - // `curr->type`. - return unary->value->type.isInteger() ? unary->value : nullptr; - default: - return nullptr; - } - } - using namespace Abstract; - using namespace Match; - int64_t leftShift = 0, rightShift = 0; - Expression* extended = nullptr; - if (matches(curr, - binary(ShrS, - binary(Shl, any(&extended), ival(&leftShift)), - ival(&rightShift)))) { - auto leftShifts = getEffectiveShifts(leftShift, curr->type); - auto rightShifts = getEffectiveShifts(rightShift, curr->type); - if (leftShifts != 0 && leftShifts == rightShifts) { - return extended; - } - } - return nullptr; -} - -Expression* getAlmostSignExt(Expression* curr) { - using namespace Abstract; - using namespace Match; - int64_t leftShift = 0, rightShift = 0; - Expression* extended = nullptr; - if (matches(curr, - binary(ShrS, - binary(Shl, any(&extended), ival(&leftShift)), - ival(&rightShift)))) { - auto leftShifts = getEffectiveShifts(leftShift, curr->type); - auto rightShifts = getEffectiveShifts(rightShift, curr->type); - if (rightShifts != 0 && rightShifts <= leftShifts) { - return extended; - } - } - return nullptr; -} - -Expression* getZeroExtValue(Expression* curr) { - // We only care about i32s here, and ignore i64s, unreachables, etc. - if (curr->type != Type::i32) { - return nullptr; - } - using namespace Match; - int32_t mask = 0; - Expression* extended = nullptr; - if (matches(curr, binary(AndInt32, any(&extended), i32(&mask))) && - getMaskedBits(mask) != 0) { - return extended; - } - return nullptr; -} - -Index Provider::getMaxBits(Expression* curr) const { - if (Properties::emitsBoolean(curr)) { - return 1; - } - if (auto* c = curr->dynCast()) { - switch (curr->type.getBasic()) { - case Type::i32: - return 32 - c->value.countLeadingZeroes().geti32(); - case Type::i64: - return 64 - c->value.countLeadingZeroes().geti64(); - default: - WASM_UNREACHABLE("invalid type"); - } - } else if (auto* binary = curr->dynCast()) { - switch (binary->op) { - // 32-bit - case RotLInt32: - case RotRInt32: - case SubInt32: - return 32; - case AddInt32: { - auto maxBitsLeft = getMaxBits(binary->left); - auto maxBitsRight = getMaxBits(binary->right); - return std::min(Index(32), std::max(maxBitsLeft, maxBitsRight) + 1); - } - case MulInt32: { - auto maxBitsRight = getMaxBits(binary->right); - auto maxBitsLeft = getMaxBits(binary->left); - return std::min(Index(32), maxBitsLeft + maxBitsRight); - } - case DivSInt32: { - if (auto* c = binary->right->dynCast()) { - int32_t maxBitsLeft = getMaxBits(binary->left); - // If either side might be negative, then the result will be - // negative - if (maxBitsLeft == 32 || c->value.geti32() < 0) { - return 32; - } - int32_t bitsRight = getMaxBits(c); - // Apply std::min: Result bits cannot exceed dividend bits - return std::min(maxBitsLeft, - std::max(0, maxBitsLeft - bitsRight + 1)); - } - return 32; - } - case DivUInt32: { - int32_t maxBitsLeft = getMaxBits(binary->left); - if (auto* c = binary->right->dynCast()) { - int32_t bitsRight = getMaxBits(c); - // Apply std::min: Result bits cannot exceed dividend bits - return std::min(maxBitsLeft, - std::max(0, maxBitsLeft - bitsRight + 1)); - } - return maxBitsLeft; - } - case RemSInt32: { - if (auto* c = binary->right->dynCast()) { - auto maxBitsLeft = getMaxBits(binary->left); - // if left may be negative, the result may be negative - if (maxBitsLeft == 32) { - return 32; - } - auto bitsRight = Index(ceilLog2(c->value.geti32())); - return std::min(maxBitsLeft, bitsRight); - } - return 32; - } - case RemUInt32: { - if (auto* c = binary->right->dynCast()) { - auto maxBitsLeft = getMaxBits(binary->left); - auto bitsRight = Index(ceilLog2(c->value.geti32())); - return std::min(maxBitsLeft, bitsRight); - } - return 32; - } - case AndInt32: { - return std::min(getMaxBits(binary->left), getMaxBits(binary->right)); - } - case OrInt32: - case XorInt32: { - return std::max(getMaxBits(binary->left), getMaxBits(binary->right)); - } - case ShlInt32: { - if (auto* shifts = binary->right->dynCast()) { - return std::min( - Index(32), getMaxBits(binary->left) + getEffectiveShifts(shifts)); - } - return 32; - } - case ShrUInt32: { - if (auto* shift = binary->right->dynCast()) { - auto maxBits = getMaxBits(binary->left); - auto shifts = - std::min(Index(getEffectiveShifts(shift)), - maxBits); // can ignore more shifts than zero us out - return std::max(Index(0), maxBits - shifts); - } - return 32; - } - case ShrSInt32: { - if (auto* shift = binary->right->dynCast()) { - auto maxBits = getMaxBits(binary->left); - // if left may be negative, the result may be negative - if (maxBits == 32) { - return 32; - } - auto shifts = - std::min(Index(getEffectiveShifts(shift)), - maxBits); // can ignore more shifts than zero us out - return std::max(Index(0), maxBits - shifts); - } - return 32; - } - case RotLInt64: - case RotRInt64: - case SubInt64: - return 64; - case AddInt64: { - auto maxBitsLeft = getMaxBits(binary->left); - auto maxBitsRight = getMaxBits(binary->right); - return std::min(Index(64), std::max(maxBitsLeft, maxBitsRight) + 1); - } - case MulInt64: { - auto maxBitsRight = getMaxBits(binary->right); - auto maxBitsLeft = getMaxBits(binary->left); - return std::min(Index(64), maxBitsLeft + maxBitsRight); - } - case DivSInt64: { - if (auto* c = binary->right->dynCast()) { - int32_t maxBitsLeft = getMaxBits(binary->left); - // if left or right const value is negative - if (maxBitsLeft == 64 || c->value.geti64() < 0) { - return 64; - } - int32_t bitsRight = getMaxBits(c); - // Apply std::min: Result bits cannot exceed dividend bits - return std::min(maxBitsLeft, - std::max(0, maxBitsLeft - bitsRight + 1)); - } - return 64; - } - case DivUInt64: { - int32_t maxBitsLeft = getMaxBits(binary->left); - if (auto* c = binary->right->dynCast()) { - int32_t bitsRight = getMaxBits(c); - // Apply std::min: Result bits cannot exceed dividend bits - return std::min(maxBitsLeft, - std::max(0, maxBitsLeft - bitsRight + 1)); - } - return maxBitsLeft; - } - case RemSInt64: { - if (auto* c = binary->right->dynCast()) { - auto maxBitsLeft = getMaxBits(binary->left); - // if left may be negative, the result may be negative - if (maxBitsLeft == 64) { - return 64; - } - auto bitsRight = Index(ceilLog2(c->value.geti64())); - return std::min(maxBitsLeft, bitsRight); - } - return 64; - } - case RemUInt64: { - if (auto* c = binary->right->dynCast()) { - auto maxBitsLeft = getMaxBits(binary->left); - auto bitsRight = Index(ceilLog2(c->value.geti64())); - return std::min(maxBitsLeft, bitsRight); - } - return 64; - } - case AndInt64: { - return std::min(getMaxBits(binary->left), getMaxBits(binary->right)); - } - case OrInt64: - case XorInt64: { - return std::max(getMaxBits(binary->left), getMaxBits(binary->right)); - } - case ShlInt64: { - if (auto* shifts = binary->right->dynCast()) { - auto maxBits = getMaxBits(binary->left); - return std::min(Index(64), getEffectiveShifts(shifts) + maxBits); - } - return 64; - } - case ShrUInt64: { - if (auto* shift = binary->right->dynCast()) { - auto maxBits = getMaxBits(binary->left); - auto shifts = - std::min(Index(getEffectiveShifts(shift)), - maxBits); // can ignore more shifts than zero us out - return std::max(Index(0), maxBits - shifts); - } - return 64; - } - case ShrSInt64: { - if (auto* shift = binary->right->dynCast()) { - auto maxBits = getMaxBits(binary->left); - // if left may be negative, the result may be negative - if (maxBits == 64) { - return 64; - } - auto shifts = - std::min(Index(getEffectiveShifts(shift)), - maxBits); // can ignore more shifts than zero us out - return std::max(Index(0), maxBits - shifts); - } - return 64; - } - // comparisons - case EqInt32: - case NeInt32: - case LtSInt32: - case LtUInt32: - case LeSInt32: - case LeUInt32: - case GtSInt32: - case GtUInt32: - case GeSInt32: - case GeUInt32: - - case EqInt64: - case NeInt64: - case LtSInt64: - case LtUInt64: - case LeSInt64: - case LeUInt64: - case GtSInt64: - case GtUInt64: - case GeSInt64: - case GeUInt64: - - case EqFloat32: - case NeFloat32: - case LtFloat32: - case LeFloat32: - case GtFloat32: - case GeFloat32: - - case EqFloat64: - case NeFloat64: - case LtFloat64: - case LeFloat64: - case GtFloat64: - case GeFloat64: - WASM_UNREACHABLE("relationals handled before"); - default: { - } - } - } else if (auto* unary = curr->dynCast()) { - switch (unary->op) { - case ClzInt32: - case CtzInt32: - case PopcntInt32: - return 6; - case ClzInt64: - case CtzInt64: - case PopcntInt64: - return 7; - case EqZInt32: - case EqZInt64: - WASM_UNREACHABLE("relationals handled before"); - case WrapInt64: - case ExtendUInt32: - return std::min(Index(32), getMaxBits(unary->value)); - case ExtendS8Int32: { - auto maxBits = getMaxBits(unary->value); - return maxBits >= 8 ? Index(32) : maxBits; - } - case ExtendS16Int32: { - auto maxBits = getMaxBits(unary->value); - return maxBits >= 16 ? Index(32) : maxBits; - } - case ExtendS8Int64: { - auto maxBits = getMaxBits(unary->value); - return maxBits >= 8 ? Index(64) : maxBits; - } - case ExtendS16Int64: { - auto maxBits = getMaxBits(unary->value); - return maxBits >= 16 ? Index(64) : maxBits; - } - case ExtendS32Int64: - case ExtendSInt32: { - auto maxBits = getMaxBits(unary->value); - return maxBits >= 32 ? Index(64) : maxBits; - } - default: { - } - } - } else if (auto* set = curr->dynCast()) { - // a tee passes through the value - return getMaxBits(set->value); - } else if (auto* get = curr->dynCast()) { - return localInfoProvider.getMaxBitsForLocal(get); - } else if (auto* load = curr->dynCast()) { - // if signed, then the sign-extension might fill all the bits - // if unsigned, then we have a limit - if (LoadUtils::isSignRelevant(load) && !load->signed_) { - return 8 * load->bytes; - } - } else if (auto* block = curr->dynCast()) { - if (!block->name.is() && !block->list.empty() && block->type.isConcrete()) { - return getMaxBits(block->list.back()); - } - } - switch (curr->type.getBasic()) { - case Type::i32: - return 32; - case Type::i64: - return 64; - case Type::unreachable: - return 64; // not interesting, but don't crash - default: - WASM_UNREACHABLE("invalid type"); - } -} - -Index Provider::getSignExtKeptBits(Expression* curr) const { - if (getSignExtValue(curr)) { - 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 curr->type.getByteSize() * 8 - getEffectiveShifts(rightShift); - } - } - if (auto* get = curr->dynCast()) { - return localInfoProvider.getSignExtKeptBitsForLocal(get); - } - return 0; -} - -} // namespace wasm::Bits diff --git a/src/ir/bits.h b/src/ir/bits.h index 39d4def9636..a894fb75bab 100644 --- a/src/ir/bits.h +++ b/src/ir/bits.h @@ -17,11 +17,12 @@ #ifndef wasm_ir_bits_h #define wasm_ir_bits_h +#include "ir/boolean.h" +#include "ir/literal-utils.h" +#include "ir/load-utils.h" +#include "ir/match.h" #include "support/bits.h" -#include "support/index.h" -#include "support/utilities.h" -#include "wasm-type.h" -#include "wasm.h" +#include "wasm-builder.h" namespace wasm::Bits { @@ -88,7 +89,38 @@ inline Index getEffectiveShifts(Expression* expr) { WASM_UNREACHABLE("unexpected type"); } -Expression* makeSignExt(Expression* value, Index bytes, Module& wasm); +inline Expression* makeSignExt(Expression* value, Index bytes, Module& wasm) { + if (value->type == Type::i32) { + if (bytes == 1 || bytes == 2) { + auto shifts = bytes == 1 ? 24 : 16; + Builder builder(wasm); + return builder.makeBinary( + ShrSInt32, + builder.makeBinary( + ShlInt32, + value, + LiteralUtils::makeFromInt32(shifts, Type::i32, wasm)), + LiteralUtils::makeFromInt32(shifts, Type::i32, wasm)); + } + assert(bytes == 4); + return value; // nothing to do + } else { + assert(value->type == Type::i64); + if (bytes == 1 || bytes == 2 || bytes == 4) { + auto shifts = bytes == 1 ? 56 : (bytes == 2 ? 48 : 32); + Builder builder(wasm); + return builder.makeBinary( + ShrSInt64, + builder.makeBinary( + ShlInt64, + value, + LiteralUtils::makeFromInt32(shifts, Type::i64, wasm)), + LiteralUtils::makeFromInt32(shifts, Type::i64, wasm)); + } + assert(bytes == 8); + return value; // nothing to do + } +} // Given a value that is read from a field, as a replacement for a // StructGet/ArrayGet that we inferred the value of, and given the signedness of @@ -97,19 +129,81 @@ Expression* makeSignExt(Expression* value, Index bytes, Module& wasm); // where we can replace the StructGet/ArrayGet with the value we know must be // there (without making any assumptions about |value|, that is, we do not // assume it has been truncated already). -Expression* makePackedFieldGet(Expression* value, - const Field& field, - bool signed_, - Module& wasm); +inline Expression* makePackedFieldGet(Expression* value, + const Field& field, + bool signed_, + Module& wasm) { + if (!field.isPacked()) { + return value; + } + + if (signed_) { + return makeSignExt(value, field.getByteSize(), wasm); + } + + Builder builder(wasm); + auto mask = Bits::lowBitMask(field.getByteSize() * 8); + return builder.makeBinary(AndInt32, value, builder.makeConst(int32_t(mask))); +} // Check if an expression is a sign-extend, and if so, returns the value // that is extended, otherwise nullptr -Expression* getSignExtValue(Expression* curr); +inline Expression* getSignExtValue(Expression* curr) { + // We only care about integers here, and ignore unreachables, etc. + if (!curr->type.isInteger()) { + return nullptr; + } + if (auto* unary = curr->dynCast()) { + switch (unary->op) { + case ExtendS8Int32: + case ExtendS16Int32: + case ExtendS8Int64: + case ExtendS16Int64: + case ExtendS32Int64: + // The operand may have become unreachable before refinalization updates + // `curr->type`. + return unary->value->type.isInteger() ? unary->value : nullptr; + default: + return nullptr; + } + } + using namespace Abstract; + using namespace Match; + int64_t leftShift = 0, rightShift = 0; + Expression* extended = nullptr; + if (matches(curr, + binary(ShrS, + binary(Shl, any(&extended), ival(&leftShift)), + ival(&rightShift)))) { + auto leftShifts = getEffectiveShifts(leftShift, curr->type); + auto rightShifts = getEffectiveShifts(rightShift, curr->type); + if (leftShifts != 0 && leftShifts == rightShifts) { + return extended; + } + } + return nullptr; +} // Check if an expression is almost a sign-extend: perhaps the inner shift // is too large. We can split the shifts in that case, which is sometimes // useful (e.g. if we can remove the signext) -Expression* getAlmostSignExt(Expression* curr); +inline Expression* getAlmostSignExt(Expression* curr) { + using namespace Abstract; + using namespace Match; + int64_t leftShift = 0, rightShift = 0; + Expression* extended = nullptr; + if (matches(curr, + binary(ShrS, + binary(Shl, any(&extended), ival(&leftShift)), + ival(&rightShift)))) { + auto leftShifts = getEffectiveShifts(leftShift, curr->type); + auto rightShifts = getEffectiveShifts(rightShift, curr->type); + if (rightShifts != 0 && rightShifts <= leftShifts) { + return extended; + } + } + return nullptr; +} // Returns the number of low bits kept by an almost-sign-extend (including any // extra left-shifted zero bits), and sets |extraLeftShifts| to the number of @@ -125,7 +219,20 @@ inline Index getAlmostSignExtKeptBits(Expression* curr, // Check if an expression is a zero-extend, and if so, returns the value // that is extended, otherwise nullptr -Expression* getZeroExtValue(Expression* curr); +inline Expression* getZeroExtValue(Expression* curr) { + // We only care about i32s here, and ignore i64s, unreachables, etc. + if (curr->type != Type::i32) { + return nullptr; + } + using namespace Match; + int32_t mask = 0; + Expression* extended = nullptr; + if (matches(curr, binary(AndInt32, any(&extended), i32(&mask))) && + getMaskedBits(mask) != 0) { + return extended; + } + return nullptr; +} // Returns the number of low bits kept by a zero-extend operation. inline Index getZeroExtKeptBits(Expression* curr) { @@ -168,7 +275,323 @@ class Provider { // Returns the maximum amount of bits used in an integer expression // not extremely precise (doesn't look into add operands, etc.) - Index getMaxBits(Expression* curr) const; + Index getMaxBits(Expression* curr) const { + if (Properties::emitsBoolean(curr)) { + return 1; + } + if (auto* c = curr->dynCast()) { + switch (curr->type.getBasic()) { + case Type::i32: + return 32 - c->value.countLeadingZeroes().geti32(); + case Type::i64: + return 64 - c->value.countLeadingZeroes().geti64(); + default: + WASM_UNREACHABLE("invalid type"); + } + } else if (auto* binary = curr->dynCast()) { + switch (binary->op) { + // 32-bit + case RotLInt32: + case RotRInt32: + case SubInt32: + return 32; + case AddInt32: { + auto maxBitsLeft = getMaxBits(binary->left); + auto maxBitsRight = getMaxBits(binary->right); + return std::min(Index(32), std::max(maxBitsLeft, maxBitsRight) + 1); + } + case MulInt32: { + auto maxBitsRight = getMaxBits(binary->right); + auto maxBitsLeft = getMaxBits(binary->left); + return std::min(Index(32), maxBitsLeft + maxBitsRight); + } + case DivSInt32: { + if (auto* c = binary->right->dynCast()) { + int32_t maxBitsLeft = getMaxBits(binary->left); + // If either side might be negative, then the result will be + // negative + if (maxBitsLeft == 32 || c->value.geti32() < 0) { + return 32; + } + int32_t bitsRight = getMaxBits(c); + // Apply std::min: Result bits cannot exceed dividend bits + return std::min(maxBitsLeft, + std::max(0, maxBitsLeft - bitsRight + 1)); + } + return 32; + } + case DivUInt32: { + int32_t maxBitsLeft = getMaxBits(binary->left); + if (auto* c = binary->right->dynCast()) { + int32_t bitsRight = getMaxBits(c); + // Apply std::min: Result bits cannot exceed dividend bits + return std::min(maxBitsLeft, + std::max(0, maxBitsLeft - bitsRight + 1)); + } + return maxBitsLeft; + } + case RemSInt32: { + if (auto* c = binary->right->dynCast()) { + auto maxBitsLeft = getMaxBits(binary->left); + // if left may be negative, the result may be negative + if (maxBitsLeft == 32) { + return 32; + } + auto bitsRight = Index(ceilLog2(c->value.geti32())); + return std::min(maxBitsLeft, bitsRight); + } + return 32; + } + case RemUInt32: { + if (auto* c = binary->right->dynCast()) { + auto maxBitsLeft = getMaxBits(binary->left); + auto bitsRight = Index(ceilLog2(c->value.geti32())); + return std::min(maxBitsLeft, bitsRight); + } + return 32; + } + case AndInt32: { + return std::min(getMaxBits(binary->left), getMaxBits(binary->right)); + } + case OrInt32: + case XorInt32: { + return std::max(getMaxBits(binary->left), getMaxBits(binary->right)); + } + case ShlInt32: { + if (auto* shifts = binary->right->dynCast()) { + return std::min( + Index(32), getMaxBits(binary->left) + getEffectiveShifts(shifts)); + } + return 32; + } + case ShrUInt32: { + if (auto* shift = binary->right->dynCast()) { + auto maxBits = getMaxBits(binary->left); + auto shifts = + std::min(Index(getEffectiveShifts(shift)), + maxBits); // can ignore more shifts than zero us out + return std::max(Index(0), maxBits - shifts); + } + return 32; + } + case ShrSInt32: { + if (auto* shift = binary->right->dynCast()) { + auto maxBits = getMaxBits(binary->left); + // if left may be negative, the result may be negative + if (maxBits == 32) { + return 32; + } + auto shifts = + std::min(Index(getEffectiveShifts(shift)), + maxBits); // can ignore more shifts than zero us out + return std::max(Index(0), maxBits - shifts); + } + return 32; + } + case RotLInt64: + case RotRInt64: + case SubInt64: + return 64; + case AddInt64: { + auto maxBitsLeft = getMaxBits(binary->left); + auto maxBitsRight = getMaxBits(binary->right); + return std::min(Index(64), std::max(maxBitsLeft, maxBitsRight) + 1); + } + case MulInt64: { + auto maxBitsRight = getMaxBits(binary->right); + auto maxBitsLeft = getMaxBits(binary->left); + return std::min(Index(64), maxBitsLeft + maxBitsRight); + } + case DivSInt64: { + if (auto* c = binary->right->dynCast()) { + int32_t maxBitsLeft = getMaxBits(binary->left); + // if left or right const value is negative + if (maxBitsLeft == 64 || c->value.geti64() < 0) { + return 64; + } + int32_t bitsRight = getMaxBits(c); + // Apply std::min: Result bits cannot exceed dividend bits + return std::min(maxBitsLeft, + std::max(0, maxBitsLeft - bitsRight + 1)); + } + return 64; + } + case DivUInt64: { + int32_t maxBitsLeft = getMaxBits(binary->left); + if (auto* c = binary->right->dynCast()) { + int32_t bitsRight = getMaxBits(c); + // Apply std::min: Result bits cannot exceed dividend bits + return std::min(maxBitsLeft, + std::max(0, maxBitsLeft - bitsRight + 1)); + } + return maxBitsLeft; + } + case RemSInt64: { + if (auto* c = binary->right->dynCast()) { + auto maxBitsLeft = getMaxBits(binary->left); + // if left may be negative, the result may be negative + if (maxBitsLeft == 64) { + return 64; + } + auto bitsRight = Index(ceilLog2(c->value.geti64())); + return std::min(maxBitsLeft, bitsRight); + } + return 64; + } + case RemUInt64: { + if (auto* c = binary->right->dynCast()) { + auto maxBitsLeft = getMaxBits(binary->left); + auto bitsRight = Index(ceilLog2(c->value.geti64())); + return std::min(maxBitsLeft, bitsRight); + } + return 64; + } + case AndInt64: { + return std::min(getMaxBits(binary->left), getMaxBits(binary->right)); + } + case OrInt64: + case XorInt64: { + return std::max(getMaxBits(binary->left), getMaxBits(binary->right)); + } + case ShlInt64: { + if (auto* shifts = binary->right->dynCast()) { + auto maxBits = getMaxBits(binary->left); + return std::min(Index(64), getEffectiveShifts(shifts) + maxBits); + } + return 64; + } + case ShrUInt64: { + if (auto* shift = binary->right->dynCast()) { + auto maxBits = getMaxBits(binary->left); + auto shifts = + std::min(Index(getEffectiveShifts(shift)), + maxBits); // can ignore more shifts than zero us out + return std::max(Index(0), maxBits - shifts); + } + return 64; + } + case ShrSInt64: { + if (auto* shift = binary->right->dynCast()) { + auto maxBits = getMaxBits(binary->left); + // if left may be negative, the result may be negative + if (maxBits == 64) { + return 64; + } + auto shifts = + std::min(Index(getEffectiveShifts(shift)), + maxBits); // can ignore more shifts than zero us out + return std::max(Index(0), maxBits - shifts); + } + return 64; + } + // comparisons + case EqInt32: + case NeInt32: + case LtSInt32: + case LtUInt32: + case LeSInt32: + case LeUInt32: + case GtSInt32: + case GtUInt32: + case GeSInt32: + case GeUInt32: + + case EqInt64: + case NeInt64: + case LtSInt64: + case LtUInt64: + case LeSInt64: + case LeUInt64: + case GtSInt64: + case GtUInt64: + case GeSInt64: + case GeUInt64: + + case EqFloat32: + case NeFloat32: + case LtFloat32: + case LeFloat32: + case GtFloat32: + case GeFloat32: + + case EqFloat64: + case NeFloat64: + case LtFloat64: + case LeFloat64: + case GtFloat64: + case GeFloat64: + WASM_UNREACHABLE("relationals handled before"); + default: { + } + } + } else if (auto* unary = curr->dynCast()) { + switch (unary->op) { + case ClzInt32: + case CtzInt32: + case PopcntInt32: + return 6; + case ClzInt64: + case CtzInt64: + case PopcntInt64: + return 7; + case EqZInt32: + case EqZInt64: + WASM_UNREACHABLE("relationals handled before"); + case WrapInt64: + case ExtendUInt32: + return std::min(Index(32), getMaxBits(unary->value)); + case ExtendS8Int32: { + auto maxBits = getMaxBits(unary->value); + return maxBits >= 8 ? Index(32) : maxBits; + } + case ExtendS16Int32: { + auto maxBits = getMaxBits(unary->value); + return maxBits >= 16 ? Index(32) : maxBits; + } + case ExtendS8Int64: { + auto maxBits = getMaxBits(unary->value); + return maxBits >= 8 ? Index(64) : maxBits; + } + case ExtendS16Int64: { + auto maxBits = getMaxBits(unary->value); + return maxBits >= 16 ? Index(64) : maxBits; + } + case ExtendS32Int64: + case ExtendSInt32: { + auto maxBits = getMaxBits(unary->value); + return maxBits >= 32 ? Index(64) : maxBits; + } + default: { + } + } + } else if (auto* set = curr->dynCast()) { + // a tee passes through the value + return getMaxBits(set->value); + } else if (auto* get = curr->dynCast()) { + return localInfoProvider.getMaxBitsForLocal(get); + } else if (auto* load = curr->dynCast()) { + // if signed, then the sign-extension might fill all the bits + // if unsigned, then we have a limit + if (LoadUtils::isSignRelevant(load) && !load->signed_) { + return 8 * load->bytes; + } + } else if (auto* block = curr->dynCast()) { + if (!block->name.is() && !block->list.empty() && + block->type.isConcrete()) { + return getMaxBits(block->list.back()); + } + } + switch (curr->type.getBasic()) { + case Type::i32: + return 32; + case Type::i64: + return 64; + case Type::unreachable: + return 64; // not interesting, but don't crash + default: + WASM_UNREACHABLE("invalid type"); + } + } // As getMaxBits, but returns the minimum amount of bits. Index getMinBits(Expression* curr) const { @@ -183,7 +606,31 @@ class Provider { // If |curr| is known to be sign-extended, returns the number of low bits kept // before sign-extending into the upper bits, or 0 otherwise. - Index getSignExtKeptBits(Expression* curr) const; + Index getSignExtKeptBits(Expression* curr) const { + if (getSignExtValue(curr)) { + 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 curr->type.getByteSize() * 8 - getEffectiveShifts(rightShift); + } + } + if (auto* get = curr->dynCast()) { + return localInfoProvider.getSignExtKeptBitsForLocal(get); + } + return 0; + } }; inline Index getMaxBits(Expression* curr) { From 2f420d5e086d6cbd699ab1a909a9249cffd864e5 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 6 Nov 2026 14:00:40 -0800 Subject: [PATCH 4/4] no this->bits --- src/passes/OptimizeInstructions.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index dd837974e98..097c2a78159 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -597,21 +597,21 @@ struct OptimizeInstructions } if (auto* ext = Bits::getAlmostSignExt(curr)) { Index extraLeftShifts; - auto bits = Bits::getAlmostSignExtKeptBits(curr, extraLeftShifts); - if (extraLeftShifts == 0 && trySignExtLoad(ext, bits)) { + 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 (this->bits.getMaxBits(ext) + extraLeftShifts < bits) { + 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, bits) && extraLeftShifts == 0) { + if (hasSignExtKeptBits(ext, keptBits) && extraLeftShifts == 0) { return replaceCurrent(removeAlmostSignExt(curr)); } } else if (curr->op == EqInt32 || curr->op == NeInt32 || @@ -1044,8 +1044,8 @@ struct OptimizeInstructions } if (auto* ext = Bits::getSignExtValue(curr)) { - auto bits = Bits::getSignExtKeptBits(curr); - if (trySignExtLoad(ext, bits)) { + auto currBits = Bits::getSignExtKeptBits(curr); + if (trySignExtLoad(ext, currBits)) { return replaceCurrent(ext); } // Simple sign extends can be removed if the value is already @@ -1053,8 +1053,8 @@ struct OptimizeInstructions // 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 keptBits = this->bits.getSignExtKeptBits(ext); - if (keptBits > 0 && keptBits <= bits) { + auto extBits = bits.getSignExtKeptBits(ext); + if (extBits > 0 && extBits <= currBits) { return replaceCurrent(ext); } }