| /* | |
| * Copyright 2017 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. | |
| */ | |
| #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" | |
| namespace wasm::Bits { | |
| // Get a mask to keep only the low # of bits. | |
| inline int32_t lowBitMask(int32_t bits) { | |
| uint32_t ret = -1; | |
| if (bits <= 0) { | |
| return 0; | |
| } | |
| if (bits >= 32) { | |
| return ret; | |
| } | |
| return ret >> (32 - bits); | |
| } | |
| inline int64_t lowBitMask64(int64_t bits) { | |
| uint64_t ret = -1; | |
| if (bits <= 0) { | |
| return 0; | |
| } | |
| if (bits >= 64) { | |
| return ret; | |
| } | |
| return ret >> (64 - bits); | |
| } | |
| // checks if the input is a mask of lower bits, i.e., all 1s up to some high | |
| // bit, and all zeros from there. returns the number of masked bits, or 0 if | |
| // this is not such a mask | |
| inline uint32_t getMaskedBits(uint32_t mask) { | |
| if (mask == uint32_t(-1)) { | |
| return 32; // all the bits | |
| } | |
| if (mask == 0) { | |
| return 0; // trivially not a mask | |
| } | |
| // otherwise, see if x & (x + 1) turns this into non-zero value | |
| // 00011111 & (00011111 + 1) => 0 | |
| if (mask & (mask + 1)) { | |
| return 0; | |
| } | |
| // this is indeed a mask | |
| return 32 - countLeadingZeroes(mask); | |
| } | |
| // gets the number of effective shifts a shift operation does. In | |
| // wasm, only 5 bits matter for 32-bit shifts, and 6 for 64. | |
| inline Index getEffectiveShifts(Index amount, Type type) { | |
| if (type == Type::i32) { | |
| return amount & 31; | |
| } else if (type == Type::i64) { | |
| return amount & 63; | |
| } | |
| WASM_UNREACHABLE("unexpected type"); | |
| } | |
| inline Index getEffectiveShifts(Expression* expr) { | |
| auto* amount = expr->cast<Const>(); | |
| if (amount->type == Type::i32) { | |
| return getEffectiveShifts(amount->value.geti32(), Type::i32); | |
| } else if (amount->type == Type::i64) { | |
| return getEffectiveShifts(amount->value.geti64(), Type::i64); | |
| } | |
| 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 | |
| } | |
| } | |
| // 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 | |
| // the get and the field info, if we are doing a signed read of a packed field | |
| // then sign-extend it, or if it is unsigned then truncate. This fixes up cases | |
| // 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))); | |
| } | |
| // 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<Unary>()) { | |
| 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) | |
| 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 | |
| // extra left shifts. | |
| inline Index getAlmostSignExtKeptBits(Expression* curr, | |
| Index& extraLeftShifts) { | |
| auto* leftShift = curr->cast<Binary>()->left->cast<Binary>()->right; | |
| auto* rightShift = curr->cast<Binary>()->right; | |
| extraLeftShifts = | |
| getEffectiveShifts(leftShift) - getEffectiveShifts(rightShift); | |
| return curr->type.getByteSize() * 8 - 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))) && | |
| 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<Binary>()->right->cast<Const>()->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 : LocalInfoProvider { | |
| Index getMaxBitsForLocal(LocalGet* get) const override { | |
| if (get->type == Type::i32) { | |
| return 32; | |
| } else if (get->type == Type::i64) { | |
| return 64; | |
| } | |
| WASM_UNREACHABLE("type has no integer bit size"); | |
| } | |
| Index getSignExtKeptBitsForLocal(LocalGet* get) const override { return 0; } | |
| }; | |
| class Provider { | |
| const LocalInfoProvider& localInfoProvider; | |
| static inline const DummyLocalInfoProvider dummyLocalInfoProvider{}; | |
| public: | |
| Provider(const LocalInfoProvider* localInfoProvider = nullptr) | |
| : localInfoProvider(localInfoProvider ? *localInfoProvider | |
| : dummyLocalInfoProvider) {} | |
| // 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<Const>()) { | |
| 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<Binary>()) { | |
| 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<Const>()) { | |
| 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<Const>()) { | |
| 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<Const>()) { | |
| 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<Const>()) { | |
| 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<Const>()) { | |
| return std::min( | |
| Index(32), getMaxBits(binary->left) + getEffectiveShifts(shifts)); | |
| } | |
| return 32; | |
| } | |
| case ShrUInt32: { | |
| if (auto* shift = binary->right->dynCast<Const>()) { | |
| 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<Const>()) { | |
| 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<Const>()) { | |
| 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<Const>()) { | |
| 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<Const>()) { | |
| 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<Const>()) { | |
| 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<Const>()) { | |
| auto maxBits = getMaxBits(binary->left); | |
| return std::min(Index(64), getEffectiveShifts(shifts) + maxBits); | |
| } | |
| return 64; | |
| } | |
| case ShrUInt64: { | |
| if (auto* shift = binary->right->dynCast<Const>()) { | |
| 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<Const>()) { | |
| 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<Unary>()) { | |
| 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<LocalSet>()) { | |
| // a tee passes through the value | |
| return getMaxBits(set->value); | |
| } else if (auto* get = curr->dynCast<LocalGet>()) { | |
| return localInfoProvider.getMaxBitsForLocal(get); | |
| } else if (auto* load = curr->dynCast<Load>()) { | |
| // 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<Block>()) { | |
| 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 { | |
| if (auto* c = curr->dynCast<Const>()) { | |
| // Constants are simple: the min and max are identical. | |
| return getMaxBits(c); | |
| } | |
| // TODO: everything else | |
| return 0; | |
| } | |
| // 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<Unary>()) { | |
| 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<Binary>()->right; | |
| return curr->type.getByteSize() * 8 - getEffectiveShifts(rightShift); | |
| } | |
| } | |
| if (auto* get = curr->dynCast<LocalGet>()) { | |
| return localInfoProvider.getSignExtKeptBitsForLocal(get); | |
| } | |
| return 0; | |
| } | |
| }; | |
| inline Index getMaxBits(Expression* curr) { | |
| return Provider().getMaxBits(curr); | |
| } | |
| inline Index getMinBits(Expression* curr) { | |
| return Provider().getMinBits(curr); | |
| } | |
| inline Index getSignExtKeptBits(Expression* curr) { | |
| return Provider().getSignExtKeptBits(curr); | |
| } | |
| } // namespace wasm::Bits | |
| #endif // wasm_ir_bits_h |