From 731402ab0ea7ad1ab975f484cf6465323ed6ce95 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 6 Nov 2026 17:46:46 -0800 Subject: [PATCH 1/3] [NFC] Move larger bits.h functions into bits.cpp Move makeSignExt, makePackedFieldGet, getSignExtValue, getAlmostSignExt, getZeroExtValue, Provider::getMaxBits, and Provider::getSignExtKeptBits out of src/ir/bits.h into src/ir/bits.cpp, and add missing direct #includes to files that previously relied on transitive includes from bits.h. --- src/cfg/cfg-traversal.h | 1 + src/ir/CMakeLists.txt | 1 + src/ir/bits.cpp | 489 ++++++++++++++++++++++++++++++++++ src/ir/bits.h | 475 +-------------------------------- src/passes/CoalesceLocals.cpp | 1 + src/passes/call-utils.h | 2 +- test/gtest/stringify.cpp | 1 + test/gtest/subtype-exprs.cpp | 1 + 8 files changed, 509 insertions(+), 462 deletions(-) create mode 100644 src/ir/bits.cpp diff --git a/src/cfg/cfg-traversal.h b/src/cfg/cfg-traversal.h index 43e2c188d48..7f5b46ce0b4 100644 --- a/src/cfg/cfg-traversal.h +++ b/src/cfg/cfg-traversal.h @@ -31,6 +31,7 @@ #define cfg_traversal_h #include "ir/branch-utils.h" +#include "shared-constants.h" #include "wasm-traversal.h" #include "wasm.h" 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 a894fb75bab..39d4def9636 100644 --- a/src/ir/bits.h +++ b/src/ir/bits.h @@ -17,12 +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 "ir/match.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 { @@ -89,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 @@ -129,81 +97,19 @@ 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; - } - - 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* 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 -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; -} +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) -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; -} +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 @@ -219,20 +125,7 @@ inline Index getAlmostSignExtKeptBits(Expression* curr, // 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))) && - getMaskedBits(mask) != 0) { - return extended; - } - return nullptr; -} +Expression* getZeroExtValue(Expression* curr); // Returns the number of low bits kept by a zero-extend operation. inline Index getZeroExtKeptBits(Expression* curr) { @@ -275,323 +168,7 @@ 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 { - 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 getMaxBits(Expression* curr) const; // As getMaxBits, but returns the minimum amount of bits. Index getMinBits(Expression* curr) const { @@ -606,31 +183,7 @@ 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 { - 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; - } + Index getSignExtKeptBits(Expression* curr) const; }; inline Index getMaxBits(Expression* curr) { diff --git a/src/passes/CoalesceLocals.cpp b/src/passes/CoalesceLocals.cpp index 438528008fb..0d37c6410f2 100644 --- a/src/passes/CoalesceLocals.cpp +++ b/src/passes/CoalesceLocals.cpp @@ -30,6 +30,7 @@ #include #include "cfg/liveness-traversal.h" +#include "ir/literal-utils.h" #include "ir/numbering.h" #include "ir/utils.h" #include "pass.h" diff --git a/src/passes/call-utils.h b/src/passes/call-utils.h index 650c6a4e3ac..83fe087a27d 100644 --- a/src/passes/call-utils.h +++ b/src/passes/call-utils.h @@ -20,7 +20,7 @@ #include #include "ir/debuginfo.h" -#include "ir/type-updating.h" +#include "wasm-builder.h" #include "wasm.h" namespace wasm::CallUtils { diff --git a/test/gtest/stringify.cpp b/test/gtest/stringify.cpp index f464b5fe0f1..f4808a12301 100644 --- a/test/gtest/stringify.cpp +++ b/test/gtest/stringify.cpp @@ -17,6 +17,7 @@ #include #include "passes/stringify-walker.h" +#include "wasm-builder.h" #include "wasm-type.h" #include "wasm.h" #include "gtest/gtest.h" diff --git a/test/gtest/subtype-exprs.cpp b/test/gtest/subtype-exprs.cpp index e3e87ec2d6a..2cf2877f07c 100644 --- a/test/gtest/subtype-exprs.cpp +++ b/test/gtest/subtype-exprs.cpp @@ -15,6 +15,7 @@ */ #include "ir/subtype-exprs.h" +#include "wasm-builder.h" #include "wasm-traversal.h" #include "wasm.h" #include "gtest/gtest.h" From b1ce7b8405c1b6777323332ae693543c98d93d5f Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 6 Nov 2026 18:08:54 -0800 Subject: [PATCH 2/3] more iwyu --- src/cfg/cfg-traversal.h | 12 ++++++++++++ src/ir/bits.cpp | 9 +++++++++ src/ir/bits.h | 3 +++ src/passes/CoalesceLocals.cpp | 9 +++++++++ src/passes/call-utils.h | 4 ++++ test/gtest/stringify.cpp | 5 +++++ test/gtest/subtype-exprs.cpp | 6 ++++++ 7 files changed, 48 insertions(+) diff --git a/src/cfg/cfg-traversal.h b/src/cfg/cfg-traversal.h index 7f5b46ce0b4..290220cc56a 100644 --- a/src/cfg/cfg-traversal.h +++ b/src/cfg/cfg-traversal.h @@ -30,8 +30,20 @@ #ifndef cfg_traversal_h #define cfg_traversal_h +#include +#include +#include +#include +#include +#include +#include +#include + #include "ir/branch-utils.h" +#include "ir/properties.h" #include "shared-constants.h" +#include "support/name.h" +#include "support/utilities.h" #include "wasm-traversal.h" #include "wasm.h" diff --git a/src/ir/bits.cpp b/src/ir/bits.cpp index 10b6cdbb290..abce4c4cb60 100644 --- a/src/ir/bits.cpp +++ b/src/ir/bits.cpp @@ -14,13 +14,22 @@ * limitations under the License. */ +#include +#include +#include + +#include "ir/abstract.h" #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 "support/index.h" +#include "support/utilities.h" #include "wasm-builder.h" +#include "wasm-type.h" +#include "wasm.h" namespace wasm::Bits { diff --git a/src/ir/bits.h b/src/ir/bits.h index 39d4def9636..b70b0ad2bb6 100644 --- a/src/ir/bits.h +++ b/src/ir/bits.h @@ -17,6 +17,9 @@ #ifndef wasm_ir_bits_h #define wasm_ir_bits_h +#include +#include + #include "support/bits.h" #include "support/index.h" #include "support/utilities.h" diff --git a/src/passes/CoalesceLocals.cpp b/src/passes/CoalesceLocals.cpp index 0d37c6410f2..95637a4d766 100644 --- a/src/passes/CoalesceLocals.cpp +++ b/src/passes/CoalesceLocals.cpp @@ -26,17 +26,26 @@ // #include +#include +#include +#include +#include #include +#include #include +#include #include "cfg/liveness-traversal.h" #include "ir/literal-utils.h" +#include "ir/manipulation.h" #include "ir/numbering.h" #include "ir/utils.h" +#include "literal.h" #include "pass.h" #include "support/learning.h" #include "support/permutations.h" #include "support/sparse_square_matrix.h" +#include "wasm-traversal.h" #include "wasm.h" #ifndef CFG_PROFILE diff --git a/src/passes/call-utils.h b/src/passes/call-utils.h index 83fe087a27d..9fb08687f4c 100644 --- a/src/passes/call-utils.h +++ b/src/passes/call-utils.h @@ -17,10 +17,14 @@ #ifndef wasm_passes_call_utils_h #define wasm_passes_call_utils_h +#include #include +#include #include "ir/debuginfo.h" +#include "support/name.h" #include "wasm-builder.h" +#include "wasm-type.h" #include "wasm.h" namespace wasm::CallUtils { diff --git a/test/gtest/stringify.cpp b/test/gtest/stringify.cpp index f4808a12301..3c25511daf6 100644 --- a/test/gtest/stringify.cpp +++ b/test/gtest/stringify.cpp @@ -14,9 +14,14 @@ * limitations under the License. */ +#include +#include +#include #include +#include #include "passes/stringify-walker.h" +#include "support/name.h" #include "wasm-builder.h" #include "wasm-type.h" #include "wasm.h" diff --git a/test/gtest/subtype-exprs.cpp b/test/gtest/subtype-exprs.cpp index 2cf2877f07c..6a4f5f3651c 100644 --- a/test/gtest/subtype-exprs.cpp +++ b/test/gtest/subtype-exprs.cpp @@ -14,9 +14,15 @@ * limitations under the License. */ +#include +#include +#include + #include "ir/subtype-exprs.h" +#include "literal.h" #include "wasm-builder.h" #include "wasm-traversal.h" +#include "wasm-type.h" #include "wasm.h" #include "gtest/gtest.h" From 891b6151d3ff5de8e456613c5ea5aa6a2f776d9c Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 6 Nov 2026 18:54:20 -0800 Subject: [PATCH 3/3] [Bits] Use abs(rhs) in getMaxBits for rem_s Previously, Provider::getMaxBits passed the signed constant divisor directly to ceilLog2 for RemSInt32 and RemSInt64. For negative divisors (other than INT_MIN), converting to unsigned produced a value with the high bit set, causing ceilLog2 to return 32 or 64 and losing precision even when the dividend was known to be non-negative. Take the unsigned absolute value of the divisor before calling ceilLog2 so negative divisors get the same magnitude bit bound as positive ones. --- src/ir/bits.cpp | 8 +- test/example/cpp-unit.cpp | 30 +++ .../lit/passes/optimize-instructions-mvp.wast | 239 ++++++++++++++++++ 3 files changed, 275 insertions(+), 2 deletions(-) diff --git a/src/ir/bits.cpp b/src/ir/bits.cpp index abce4c4cb60..fc6387c35db 100644 --- a/src/ir/bits.cpp +++ b/src/ir/bits.cpp @@ -214,7 +214,9 @@ Index Provider::getMaxBits(Expression* curr) const { if (maxBitsLeft == 32) { return 32; } - auto bitsRight = Index(ceilLog2(c->value.geti32())); + int32_t rhs = c->value.geti32(); + uint32_t absRhs = rhs < 0 ? -uint32_t(rhs) : uint32_t(rhs); + auto bitsRight = Index(ceilLog2(absRhs)); return std::min(maxBitsLeft, bitsRight); } return 32; @@ -310,7 +312,9 @@ Index Provider::getMaxBits(Expression* curr) const { if (maxBitsLeft == 64) { return 64; } - auto bitsRight = Index(ceilLog2(c->value.geti64())); + int64_t rhs = c->value.geti64(); + uint64_t absRhs = rhs < 0 ? -uint64_t(rhs) : uint64_t(rhs); + auto bitsRight = Index(ceilLog2(absRhs)); return std::min(maxBitsLeft, bitsRight); } return 64; diff --git a/test/example/cpp-unit.cpp b/test/example/cpp-unit.cpp index 1a2b9927432..6b35136791b 100644 --- a/test/example/cpp-unit.cpp +++ b/test/example/cpp-unit.cpp @@ -199,6 +199,21 @@ void test_bits() { assert_equal(getMaxBits(&b), 32); c0.value = Literal(int32_t(3)); c1.value = Literal(int32_t(-1)); + assert_equal(getMaxBits(&b), 0); + c0.value = Literal(int32_t(0xFF)); + c1.value = Literal(int32_t(-4)); + assert_equal(getMaxBits(&b), 2); + c0.value = Literal(int32_t(0xFF)); + c1.value = Literal(int32_t(-5)); + assert_equal(getMaxBits(&b), 3); + c0.value = Literal(int32_t(-1)); + c1.value = Literal(int32_t(-4)); + assert_equal(getMaxBits(&b), 32); + c0.value = Literal(int32_t(0x7FFFFFFF)); + c1.value = Literal(int32_t(0x80000000)); + assert_equal(getMaxBits(&b), 31); + c0.value = Literal(int32_t(3)); + c1.value = Literal(int32_t(0x80000000)); assert_equal(getMaxBits(&b), 2); c0.value = Literal(int32_t(0x7FFFFFFF)); c1.value = Literal(int32_t(0x7FFFFFFF)); @@ -412,6 +427,21 @@ void test_bits() { assert_equal(getMaxBits(&b), 64); c0.value = Literal(int64_t(3)); c1.value = Literal(int64_t(-1)); + assert_equal(getMaxBits(&b), 0); + c0.value = Literal(int64_t(0xFF)); + c1.value = Literal(int64_t(-4)); + assert_equal(getMaxBits(&b), 2); + c0.value = Literal(int64_t(0xFF)); + c1.value = Literal(int64_t(-5)); + assert_equal(getMaxBits(&b), 3); + c0.value = Literal(int64_t(-1)); + c1.value = Literal(int64_t(-4)); + assert_equal(getMaxBits(&b), 64); + c0.value = Literal(int64_t(0x7FFFFFFFFFFFFFFF)); + c1.value = Literal(int64_t(0x8000000000000000)); + assert_equal(getMaxBits(&b), 63); + c0.value = Literal(int64_t(3)); + c1.value = Literal(int64_t(0x8000000000000000)); assert_equal(getMaxBits(&b), 2); c0.value = Literal(int64_t(0x7FFFFFFFFFFFFFFF)); c1.value = Literal(int64_t(0x7FFFFFFFFFFFFFFF)); diff --git a/test/lit/passes/optimize-instructions-mvp.wast b/test/lit/passes/optimize-instructions-mvp.wast index 6208f178215..81e7ca5ac4f 100644 --- a/test/lit/passes/optimize-instructions-mvp.wast +++ b/test/lit/passes/optimize-instructions-mvp.wast @@ -9204,6 +9204,245 @@ (i64.const 0x8000000000000000) )) ) + ;; CHECK: (func $rem-s-neg-divisor-max-bits-i32 (param $x i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.rem_s + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 255) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const -5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.rem_s + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 255) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const -4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $x + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.rem_s + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 2147483647) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const -2147483648) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (i32.rem_s + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 255) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const -5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (i32.rem_s + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const -5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rem-s-neg-divisor-max-bits-i32 (param $x i32) + ;; Non-negative dividend with negative constant divisors: getMaxBits uses + ;; abs(divisor), allowing redundant masks and zero-bit remainders to fold. + ;; abs(-5) = 5 -> max 3 bits (remainder in [0, 4]), so masking with 7 is redundant. + (drop + (i32.and + (i32.rem_s + (i32.and (local.get $x) (i32.const 0xff)) + (i32.const -5) + ) + (i32.const 7) + ) + ) + ;; abs(-4) = 4 -> max 2 bits (remainder in [0, 3]), so masking with 3 is redundant. + (drop + (i32.and + (i32.rem_s + (i32.and (local.get $x) (i32.const 0xff)) + (i32.const -4) + ) + (i32.const 3) + ) + ) + ;; Impure non-negative dividend % -1 has 0 max bits and folds to 0 via + ;; replaceZeroBitsWithZero while keeping the local.set side effect. + (drop + (i32.rem_s + (local.tee $x (i32.const 3)) + (i32.const -1) + ) + ) + ;; Minimum signed i32 divisor (-2147483648) has unsigned abs 2^31 (31 bits). + (drop + (i32.and + (i32.rem_s + (i32.and (local.get $x) (i32.const 0x7fffffff)) + (i32.const 0x80000000) + ) + (i32.const 0x7fffffff) + ) + ) + ;; Negative case: mask of 3 is too narrow for remainder in [0, 4]. + (drop + (i32.and + (i32.rem_s + (i32.and (local.get $x) (i32.const 0xff)) + (i32.const -5) + ) + (i32.const 3) + ) + ) + ;; Negative case: dividend may be negative, so remainder may be negative and + ;; masking with 7 is not redundant. + (drop + (i32.and + (i32.rem_s + (local.get $x) + (i32.const -5) + ) + (i32.const 7) + ) + ) + ) + ;; CHECK: (func $rem-s-neg-divisor-max-bits-i64 (param $y i64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.rem_s + ;; CHECK-NEXT: (i64.and + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: (i64.const 255) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.const -5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.rem_s + ;; CHECK-NEXT: (i64.and + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: (i64.const 255) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.const -4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $y + ;; CHECK-NEXT: (i64.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.rem_s + ;; CHECK-NEXT: (i64.and + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: (i64.const 9223372036854775807) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.const -9223372036854775808) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.and + ;; CHECK-NEXT: (i64.rem_s + ;; CHECK-NEXT: (i64.and + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: (i64.const 255) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.const -5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.and + ;; CHECK-NEXT: (i64.rem_s + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: (i64.const -5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.const 7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rem-s-neg-divisor-max-bits-i64 (param $y i64) + ;; Same cases for i64.rem_s. + (drop + (i64.and + (i64.rem_s + (i64.and (local.get $y) (i64.const 0xff)) + (i64.const -5) + ) + (i64.const 7) + ) + ) + (drop + (i64.and + (i64.rem_s + (i64.and (local.get $y) (i64.const 0xff)) + (i64.const -4) + ) + (i64.const 3) + ) + ) + (drop + (i64.rem_s + (local.tee $y (i64.const 3)) + (i64.const -1) + ) + ) + (drop + (i64.and + (i64.rem_s + (i64.and (local.get $y) (i64.const 0x7fffffffffffffff)) + (i64.const 0x8000000000000000) + ) + (i64.const 0x7fffffffffffffff) + ) + ) + ;; Negative case: mask of 3 is too narrow for remainder in [0, 4]. + (drop + (i64.and + (i64.rem_s + (i64.and (local.get $y) (i64.const 0xff)) + (i64.const -5) + ) + (i64.const 3) + ) + ) + ;; Negative case: dividend may be negative, so masking with 7 is needed. + (drop + (i64.and + (i64.rem_s + (local.get $y) + (i64.const -5) + ) + (i64.const 7) + ) + ) + ) ;; CHECK: (func $srem-by-pot-eq-ne-zero (param $x i32) (param $y i64) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.eqz