| /* |
| * Copyright 2016 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 "ast_utils.h" |
| #include "support/hash.h" |
| |
| |
| namespace wasm { |
| // Given a stack of expressions, checks if the topmost is used as a result. |
| // For example, if the parent is a block and the node is before the last position, |
| // it is not used. |
| bool ExpressionAnalyzer::isResultUsed(std::vector<Expression*> stack, Function* func) { |
| for (int i = int(stack.size()) - 2; i >= 0; i--) { |
| auto* curr = stack[i]; |
| auto* above = stack[i + 1]; |
| // only if and block can drop values (pre-drop expression was added) FIXME |
| if (curr->is<Block>()) { |
| auto* block = curr->cast<Block>(); |
| for (size_t j = 0; j < block->list.size() - 1; j++) { |
| if (block->list[j] == above) return false; |
| } |
| assert(block->list.back() == above); |
| // continue down |
| } else if (curr->is<If>()) { |
| auto* iff = curr->cast<If>(); |
| if (above == iff->condition) return true; |
| if (!iff->ifFalse) return false; |
| assert(above == iff->ifTrue || above == iff->ifFalse); |
| // continue down |
| } else { |
| if (curr->is<Drop>()) return false; |
| return true; // all other node types use the result |
| } |
| } |
| // The value might be used, so it depends on if the function returns |
| return func->result != none; |
| } |
| |
| // Checks if a value is dropped. |
| bool ExpressionAnalyzer::isResultDropped(std::vector<Expression*> stack) { |
| for (int i = int(stack.size()) - 2; i >= 0; i--) { |
| auto* curr = stack[i]; |
| auto* above = stack[i + 1]; |
| if (curr->is<Block>()) { |
| auto* block = curr->cast<Block>(); |
| for (size_t j = 0; j < block->list.size() - 1; j++) { |
| if (block->list[j] == above) return false; |
| } |
| assert(block->list.back() == above); |
| // continue down |
| } else if (curr->is<If>()) { |
| auto* iff = curr->cast<If>(); |
| if (above == iff->condition) return false; |
| if (!iff->ifFalse) return false; |
| assert(above == iff->ifTrue || above == iff->ifFalse); |
| // continue down |
| } else { |
| if (curr->is<Drop>()) return true; // dropped |
| return false; // all other node types use the result |
| } |
| } |
| return false; |
| } |
| |
| |
| bool ExpressionAnalyzer::flexibleEqual(Expression* left, Expression* right, ExprComparer comparer) { |
| std::vector<Name> nameStack; |
| std::map<Name, std::vector<Name>> rightNames; // for each name on the left, the stack of names on the right (a stack, since names are scoped and can nest duplicatively |
| Nop popNameMarker; |
| std::vector<Expression*> leftStack; |
| std::vector<Expression*> rightStack; |
| |
| auto noteNames = [&](Name left, Name right) { |
| if (left.is() != right.is()) return false; |
| if (left.is()) { |
| nameStack.push_back(left); |
| rightNames[left].push_back(right); |
| leftStack.push_back(&popNameMarker); |
| rightStack.push_back(&popNameMarker); |
| } |
| return true; |
| }; |
| auto checkNames = [&](Name left, Name right) { |
| auto iter = rightNames.find(left); |
| if (iter == rightNames.end()) return left == right; // non-internal name |
| return iter->second.back() == right; |
| }; |
| auto popName = [&]() { |
| auto left = nameStack.back(); |
| nameStack.pop_back(); |
| rightNames[left].pop_back(); |
| }; |
| |
| leftStack.push_back(left); |
| rightStack.push_back(right); |
| |
| while (leftStack.size() > 0 && rightStack.size() > 0) { |
| left = leftStack.back(); |
| leftStack.pop_back(); |
| right = rightStack.back(); |
| rightStack.pop_back(); |
| if (!left != !right) return false; |
| if (!left) continue; |
| if (left == &popNameMarker) { |
| popName(); |
| continue; |
| } |
| if (comparer(left, right)) continue; // comparison hook, before all the rest |
| // continue with normal structural comparison |
| if (left->_id != right->_id) return false; |
| #define PUSH(clazz, what) \ |
| leftStack.push_back(left->cast<clazz>()->what); \ |
| rightStack.push_back(right->cast<clazz>()->what); |
| #define CHECK(clazz, what) \ |
| if (left->cast<clazz>()->what != right->cast<clazz>()->what) return false; |
| switch (left->_id) { |
| case Expression::Id::BlockId: { |
| if (!noteNames(left->cast<Block>()->name, right->cast<Block>()->name)) return false; |
| CHECK(Block, list.size()); |
| for (Index i = 0; i < left->cast<Block>()->list.size(); i++) { |
| PUSH(Block, list[i]); |
| } |
| break; |
| } |
| case Expression::Id::IfId: { |
| PUSH(If, condition); |
| PUSH(If, ifTrue); |
| PUSH(If, ifFalse); |
| break; |
| } |
| case Expression::Id::LoopId: { |
| if (!noteNames(left->cast<Loop>()->name, right->cast<Loop>()->name)) return false; |
| PUSH(Loop, body); |
| break; |
| } |
| case Expression::Id::BreakId: { |
| if (!checkNames(left->cast<Break>()->name, right->cast<Break>()->name)) return false; |
| PUSH(Break, condition); |
| PUSH(Break, value); |
| break; |
| } |
| case Expression::Id::SwitchId: { |
| CHECK(Switch, targets.size()); |
| for (Index i = 0; i < left->cast<Switch>()->targets.size(); i++) { |
| if (!checkNames(left->cast<Switch>()->targets[i], right->cast<Switch>()->targets[i])) return false; |
| } |
| if (!checkNames(left->cast<Switch>()->default_, right->cast<Switch>()->default_)) return false; |
| PUSH(Switch, condition); |
| PUSH(Switch, value); |
| break; |
| } |
| case Expression::Id::CallId: { |
| CHECK(Call, target); |
| CHECK(Call, operands.size()); |
| for (Index i = 0; i < left->cast<Call>()->operands.size(); i++) { |
| PUSH(Call, operands[i]); |
| } |
| break; |
| } |
| case Expression::Id::CallImportId: { |
| CHECK(CallImport, target); |
| CHECK(CallImport, operands.size()); |
| for (Index i = 0; i < left->cast<CallImport>()->operands.size(); i++) { |
| PUSH(CallImport, operands[i]); |
| } |
| break; |
| } |
| case Expression::Id::CallIndirectId: { |
| PUSH(CallIndirect, target); |
| CHECK(CallIndirect, fullType); |
| CHECK(CallIndirect, operands.size()); |
| for (Index i = 0; i < left->cast<CallIndirect>()->operands.size(); i++) { |
| PUSH(CallIndirect, operands[i]); |
| } |
| break; |
| } |
| case Expression::Id::GetLocalId: { |
| CHECK(GetLocal, index); |
| break; |
| } |
| case Expression::Id::SetLocalId: { |
| CHECK(SetLocal, index); |
| CHECK(SetLocal, type); // for tee/set |
| PUSH(SetLocal, value); |
| break; |
| } |
| case Expression::Id::GetGlobalId: { |
| CHECK(GetGlobal, name); |
| break; |
| } |
| case Expression::Id::SetGlobalId: { |
| CHECK(SetGlobal, name); |
| PUSH(SetGlobal, value); |
| break; |
| } |
| case Expression::Id::LoadId: { |
| CHECK(Load, bytes); |
| CHECK(Load, signed_); |
| CHECK(Load, offset); |
| CHECK(Load, align); |
| PUSH(Load, ptr); |
| break; |
| } |
| case Expression::Id::StoreId: { |
| CHECK(Store, bytes); |
| CHECK(Store, offset); |
| CHECK(Store, align); |
| CHECK(Store, valueType); |
| PUSH(Store, ptr); |
| PUSH(Store, value); |
| break; |
| } |
| case Expression::Id::ConstId: { |
| CHECK(Const, value); |
| break; |
| } |
| case Expression::Id::UnaryId: { |
| CHECK(Unary, op); |
| PUSH(Unary, value); |
| break; |
| } |
| case Expression::Id::BinaryId: { |
| CHECK(Binary, op); |
| PUSH(Binary, left); |
| PUSH(Binary, right); |
| break; |
| } |
| case Expression::Id::SelectId: { |
| PUSH(Select, ifTrue); |
| PUSH(Select, ifFalse); |
| PUSH(Select, condition); |
| break; |
| } |
| case Expression::Id::DropId: { |
| PUSH(Drop, value); |
| break; |
| } |
| case Expression::Id::ReturnId: { |
| PUSH(Return, value); |
| break; |
| } |
| case Expression::Id::HostId: { |
| CHECK(Host, op); |
| CHECK(Host, nameOperand); |
| CHECK(Host, operands.size()); |
| for (Index i = 0; i < left->cast<Host>()->operands.size(); i++) { |
| PUSH(Host, operands[i]); |
| } |
| break; |
| } |
| case Expression::Id::NopId: { |
| break; |
| } |
| case Expression::Id::UnreachableId: { |
| break; |
| } |
| default: WASM_UNREACHABLE(); |
| } |
| #undef CHECK |
| #undef PUSH |
| } |
| if (leftStack.size() > 0 || rightStack.size() > 0) return false; |
| return true; |
| } |
| |
| |
| // hash an expression, ignoring superficial details like specific internal names |
| uint32_t ExpressionAnalyzer::hash(Expression* curr) { |
| uint32_t digest = 0; |
| |
| auto hash = [&digest](uint32_t hash) { |
| digest = rehash(digest, hash); |
| }; |
| auto hash64 = [&digest](uint64_t hash) { |
| digest = rehash(rehash(digest, hash >> 32), uint32_t(hash)); |
| }; |
| |
| std::vector<Name> nameStack; |
| Index internalCounter = 0; |
| std::map<Name, std::vector<Index>> internalNames; // for each internal name, a vector if unique ids |
| Nop popNameMarker; |
| std::vector<Expression*> stack; |
| |
| auto noteName = [&](Name curr) { |
| if (curr.is()) { |
| nameStack.push_back(curr); |
| internalNames[curr].push_back(internalCounter++); |
| stack.push_back(&popNameMarker); |
| } |
| return true; |
| }; |
| auto hashName = [&](Name curr) { |
| auto iter = internalNames.find(curr); |
| if (iter == internalNames.end()) hash64(uint64_t(curr.str)); |
| else hash(iter->second.back()); |
| }; |
| auto popName = [&]() { |
| auto curr = nameStack.back(); |
| nameStack.pop_back(); |
| internalNames[curr].pop_back(); |
| }; |
| |
| stack.push_back(curr); |
| |
| while (stack.size() > 0) { |
| curr = stack.back(); |
| stack.pop_back(); |
| if (!curr) continue; |
| if (curr == &popNameMarker) { |
| popName(); |
| continue; |
| } |
| hash(curr->_id); |
| // we often don't need to hash the type, as it is tied to other values |
| // we are hashing anyhow, but there are exceptions: for example, a |
| // get_local's type is determined by the function, so if we are |
| // hashing only expression fragments, then two from different |
| // functions may turn out the same even if the type differs. Likewise, |
| // if we hash between modules, then we need to take int account |
| // call_imports type, etc. The simplest thing is just to hash the |
| // type for all of them. |
| hash(curr->type); |
| |
| #define PUSH(clazz, what) \ |
| stack.push_back(curr->cast<clazz>()->what); |
| #define HASH(clazz, what) \ |
| hash(curr->cast<clazz>()->what); |
| #define HASH64(clazz, what) \ |
| hash64(curr->cast<clazz>()->what); |
| #define HASH_NAME(clazz, what) \ |
| hash64(uint64_t(curr->cast<clazz>()->what.str)); |
| #define HASH_PTR(clazz, what) \ |
| hash64(uint64_t(curr->cast<clazz>()->what)); |
| switch (curr->_id) { |
| case Expression::Id::BlockId: { |
| noteName(curr->cast<Block>()->name); |
| HASH(Block, list.size()); |
| for (Index i = 0; i < curr->cast<Block>()->list.size(); i++) { |
| PUSH(Block, list[i]); |
| } |
| break; |
| } |
| case Expression::Id::IfId: { |
| PUSH(If, condition); |
| PUSH(If, ifTrue); |
| PUSH(If, ifFalse); |
| break; |
| } |
| case Expression::Id::LoopId: { |
| noteName(curr->cast<Loop>()->name); |
| PUSH(Loop, body); |
| break; |
| } |
| case Expression::Id::BreakId: { |
| hashName(curr->cast<Break>()->name); |
| PUSH(Break, condition); |
| PUSH(Break, value); |
| break; |
| } |
| case Expression::Id::SwitchId: { |
| HASH(Switch, targets.size()); |
| for (Index i = 0; i < curr->cast<Switch>()->targets.size(); i++) { |
| hashName(curr->cast<Switch>()->targets[i]); |
| } |
| hashName(curr->cast<Switch>()->default_); |
| PUSH(Switch, condition); |
| PUSH(Switch, value); |
| break; |
| } |
| case Expression::Id::CallId: { |
| HASH_NAME(Call, target); |
| HASH(Call, operands.size()); |
| for (Index i = 0; i < curr->cast<Call>()->operands.size(); i++) { |
| PUSH(Call, operands[i]); |
| } |
| break; |
| } |
| case Expression::Id::CallImportId: { |
| HASH_NAME(CallImport, target); |
| HASH(CallImport, operands.size()); |
| for (Index i = 0; i < curr->cast<CallImport>()->operands.size(); i++) { |
| PUSH(CallImport, operands[i]); |
| } |
| break; |
| } |
| case Expression::Id::CallIndirectId: { |
| PUSH(CallIndirect, target); |
| HASH_NAME(CallIndirect, fullType); |
| HASH(CallIndirect, operands.size()); |
| for (Index i = 0; i < curr->cast<CallIndirect>()->operands.size(); i++) { |
| PUSH(CallIndirect, operands[i]); |
| } |
| break; |
| } |
| case Expression::Id::GetLocalId: { |
| HASH(GetLocal, index); |
| break; |
| } |
| case Expression::Id::SetLocalId: { |
| HASH(SetLocal, index); |
| PUSH(SetLocal, value); |
| break; |
| } |
| case Expression::Id::GetGlobalId: { |
| HASH_NAME(GetGlobal, name); |
| break; |
| } |
| case Expression::Id::SetGlobalId: { |
| HASH_NAME(SetGlobal, name); |
| PUSH(SetGlobal, value); |
| break; |
| } |
| case Expression::Id::LoadId: { |
| HASH(Load, bytes); |
| HASH(Load, signed_); |
| HASH(Load, offset); |
| HASH(Load, align); |
| PUSH(Load, ptr); |
| break; |
| } |
| case Expression::Id::StoreId: { |
| HASH(Store, bytes); |
| HASH(Store, offset); |
| HASH(Store, align); |
| HASH(Store, valueType); |
| PUSH(Store, ptr); |
| PUSH(Store, value); |
| break; |
| } |
| case Expression::Id::ConstId: { |
| HASH(Const, value.type); |
| HASH64(Const, value.getBits()); |
| break; |
| } |
| case Expression::Id::UnaryId: { |
| HASH(Unary, op); |
| PUSH(Unary, value); |
| break; |
| } |
| case Expression::Id::BinaryId: { |
| HASH(Binary, op); |
| PUSH(Binary, left); |
| PUSH(Binary, right); |
| break; |
| } |
| case Expression::Id::SelectId: { |
| PUSH(Select, ifTrue); |
| PUSH(Select, ifFalse); |
| PUSH(Select, condition); |
| break; |
| } |
| case Expression::Id::DropId: { |
| PUSH(Drop, value); |
| break; |
| } |
| case Expression::Id::ReturnId: { |
| PUSH(Return, value); |
| break; |
| } |
| case Expression::Id::HostId: { |
| HASH(Host, op); |
| HASH_NAME(Host, nameOperand); |
| HASH(Host, operands.size()); |
| for (Index i = 0; i < curr->cast<Host>()->operands.size(); i++) { |
| PUSH(Host, operands[i]); |
| } |
| break; |
| } |
| case Expression::Id::NopId: { |
| break; |
| } |
| case Expression::Id::UnreachableId: { |
| break; |
| } |
| default: WASM_UNREACHABLE(); |
| } |
| #undef HASH |
| #undef PUSH |
| } |
| return digest; |
| } |
| } // namespace wasm |