| /* |
| * 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 "ir/iteration.h" |
| #include "ir/load-utils.h" |
| #include "ir/utils.h" |
| #include "shared-constants.h" |
| #include "support/hash.h" |
| #include "support/small_vector.h" |
| #include "wasm-traversal.h" |
| #include "wasm.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(ExpressionStack& 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->getResults() != Type::none; |
| } |
| |
| // Checks if a value is dropped. |
| bool ExpressionAnalyzer::isResultDropped(ExpressionStack& 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) { |
| struct Comparer { |
| // for each name on the left, the corresponding name on the right |
| std::map<Name, Name> rightNames; |
| std::vector<Expression*> leftStack; |
| std::vector<Expression*> rightStack; |
| |
| bool noteNames(Name left, Name right) { |
| if (left.is() != right.is()) { |
| return false; |
| } |
| if (left.is()) { |
| assert(rightNames.find(left) == rightNames.end()); |
| rightNames[left] = right; |
| } |
| return true; |
| } |
| |
| bool compare(Expression* left, Expression* right, ExprComparer comparer) { |
| // The empty name is the same on both sides. |
| rightNames[Name()] = Name(); |
| |
| 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; |
| } |
| // There are actual expressions to compare here. Start with the custom |
| // comparer function that was provided. |
| if (comparer(left, right)) { |
| continue; |
| } |
| if (left->type != right->type) { |
| return false; |
| } |
| // Do the actual comparison, updating the names and stacks accordingly. |
| if (!compareNodes(left, right)) { |
| return false; |
| } |
| } |
| if (leftStack.size() > 0 || rightStack.size() > 0) { |
| return false; |
| } |
| return true; |
| } |
| |
| bool compareNodes(Expression* left, Expression* right) { |
| if (left->_id != right->_id) { |
| return false; |
| } |
| |
| #define DELEGATE_ID left->_id |
| |
| // Create cast versions of it for later operations. |
| #define DELEGATE_START(id) \ |
| [[maybe_unused]] auto* castLeft = left->cast<id>(); \ |
| [[maybe_unused]] auto* castRight = right->cast<id>(); |
| |
| // Handle each type of field, comparing it appropriately. |
| #define DELEGATE_FIELD_CHILD(id, field) \ |
| leftStack.push_back(castLeft->field); \ |
| rightStack.push_back(castRight->field); |
| |
| #define DELEGATE_FIELD_CHILD_VECTOR(id, field) \ |
| if (castLeft->field.size() != castRight->field.size()) { \ |
| return false; \ |
| } \ |
| for (auto* child : castLeft->field) { \ |
| leftStack.push_back(child); \ |
| } \ |
| for (auto* child : castRight->field) { \ |
| rightStack.push_back(child); \ |
| } |
| |
| #define COMPARE_FIELD(field) \ |
| if (castLeft->field != castRight->field) { \ |
| return false; \ |
| } |
| |
| #define DELEGATE_FIELD_INT(id, field) COMPARE_FIELD(field) |
| #define DELEGATE_FIELD_LITERAL(id, field) COMPARE_FIELD(field) |
| #define DELEGATE_FIELD_NAME(id, field) COMPARE_FIELD(field) |
| #define DELEGATE_FIELD_TYPE(id, field) COMPARE_FIELD(field) |
| #define DELEGATE_FIELD_HEAPTYPE(id, field) COMPARE_FIELD(field) |
| #define DELEGATE_FIELD_ADDRESS(id, field) COMPARE_FIELD(field) |
| |
| #define COMPARE_LIST(field) \ |
| if (castLeft->field.size() != castRight->field.size()) { \ |
| return false; \ |
| } \ |
| for (Index i = 0; i < castLeft->field.size(); i++) { \ |
| if (castLeft->field[i] != castRight->field[i]) { \ |
| return false; \ |
| } \ |
| } |
| |
| #define DELEGATE_FIELD_INT_ARRAY(id, field) COMPARE_LIST(field) |
| #define DELEGATE_FIELD_INT_VECTOR(id, field) COMPARE_LIST(field) |
| #define DELEGATE_FIELD_NAME_VECTOR(id, field) COMPARE_LIST(field) |
| #define DELEGATE_FIELD_TYPE_VECTOR(id, field) COMPARE_LIST(field) |
| |
| #define DELEGATE_FIELD_SCOPE_NAME_DEF(id, field) \ |
| if (castLeft->field.is() != castRight->field.is()) { \ |
| return false; \ |
| } \ |
| rightNames[castLeft->field] = castRight->field; |
| |
| #define DELEGATE_FIELD_SCOPE_NAME_USE(id, field) \ |
| if (!compareNames(castLeft->field, castRight->field)) { \ |
| return false; \ |
| } |
| |
| #define DELEGATE_FIELD_SCOPE_NAME_USE_VECTOR(id, field) \ |
| if (castLeft->field.size() != castRight->field.size()) { \ |
| return false; \ |
| } \ |
| for (Index i = 0; i < castLeft->field.size(); i++) { \ |
| if (!compareNames(castLeft->field[i], castRight->field[i])) { \ |
| return false; \ |
| } \ |
| } |
| |
| #include "wasm-delegations-fields.def" |
| |
| return true; |
| } |
| |
| bool compareNames(Name left, Name right) { |
| auto iter = rightNames.find(left); |
| // If it's not found, that means it was defined out of the expression |
| // being compared, in which case we can just treat it literally - it |
| // must be exactly identical. |
| if (iter != rightNames.end()) { |
| left = iter->second; |
| } |
| return left == right; |
| } |
| }; |
| |
| return Comparer().compare(left, right, comparer); |
| } |
| |
| namespace { |
| |
| struct Hasher { |
| bool visitChildren; |
| |
| size_t digest = wasm::hash(0); |
| |
| Index internalCounter = 0; |
| // for each internal name, its unique id |
| std::map<Name, Index> internalNames; |
| ExpressionStack stack; |
| |
| Hasher(Expression* curr, |
| bool visitChildren, |
| ExpressionAnalyzer::ExprHasher custom) |
| : visitChildren(visitChildren) { |
| stack.push_back(curr); |
| // DELEGATE_CALLER_TARGET is a fake target used to denote delegating to |
| // the caller. Add it here to prevent the unknown name error. |
| noteScopeName(DELEGATE_CALLER_TARGET); |
| |
| while (stack.size() > 0) { |
| curr = stack.back(); |
| stack.pop_back(); |
| if (!curr) { |
| // This was an optional child that was not present. Hash a 0 to |
| // represent that. |
| rehash(digest, 0); |
| continue; |
| } |
| rehash(digest, 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 |
| // local.get'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. |
| rehash(digest, curr->type.getID()); |
| // If the custom hasher handled this expr, then we have nothing to do. |
| if (custom(curr, digest)) { |
| continue; |
| } |
| // Hash the contents of the expression normally. |
| hashExpression(curr); |
| } |
| } |
| |
| void hashExpression(Expression* curr) { |
| |
| #define DELEGATE_ID curr->_id |
| |
| // Create cast versions of it for later operations. |
| #define DELEGATE_START(id) [[maybe_unused]] auto* cast = curr->cast<id>(); |
| |
| // Handle each type of field, comparing it appropriately. |
| #define DELEGATE_GET_FIELD(id, field) cast->field |
| |
| #define DELEGATE_FIELD_CHILD(id, field) \ |
| if (visitChildren) { \ |
| stack.push_back(cast->field); \ |
| } |
| |
| #define HASH_FIELD(field) rehash(digest, cast->field); |
| |
| #define DELEGATE_FIELD_INT(id, field) HASH_FIELD(field) |
| #define DELEGATE_FIELD_LITERAL(id, field) HASH_FIELD(field) |
| |
| #define DELEGATE_FIELD_NAME(id, field) visitNonScopeName(cast->field); |
| #define DELEGATE_FIELD_TYPE(id, field) visitType(cast->field); |
| #define DELEGATE_FIELD_HEAPTYPE(id, field) visitHeapType(cast->field); |
| #define DELEGATE_FIELD_ADDRESS(id, field) visitAddress(cast->field); |
| |
| // Note that we only note the scope name, but do not also visit it. That means |
| // that (block $x) and (block) get the same hash. In other words, we only change |
| // the hash based on uses of scope names, that is when there is a noticeable |
| // difference in break targets. |
| #define DELEGATE_FIELD_SCOPE_NAME_DEF(id, field) noteScopeName(cast->field); |
| |
| #define DELEGATE_FIELD_SCOPE_NAME_USE(id, field) visitScopeName(cast->field); |
| |
| #include "wasm-delegations-fields.def" |
| } |
| |
| void noteScopeName(Name curr) { |
| if (curr.is()) { |
| internalNames[curr] = internalCounter++; |
| } |
| } |
| void visitScopeName(Name curr) { |
| // We consider 3 cases here, and prefix a hash value of 0, 1, or 2 to |
| // maximally differentiate them. |
| |
| // Try's delegate target can be null. |
| if (!curr.is()) { |
| rehash(digest, 0); |
| return; |
| } |
| // Names are relative, we give the same hash for |
| // (block $x (br $x)) |
| // (block $y (br $y)) |
| // But if the name is not known to us, hash the absolute one. |
| if (!internalNames.count(curr)) { |
| rehash(digest, 1); |
| // Perform the same hashing as a generic name. |
| visitNonScopeName(curr); |
| return; |
| } |
| rehash(digest, 2); |
| rehash(digest, internalNames[curr]); |
| } |
| void visitNonScopeName(Name curr) { rehash(digest, curr); } |
| void visitType(Type curr) { rehash(digest, curr.getID()); } |
| void visitHeapType(HeapType curr) { rehash(digest, curr.getID()); } |
| void visitAddress(Address curr) { rehash(digest, curr.addr); } |
| }; |
| |
| } // anonymous namespace |
| |
| size_t ExpressionAnalyzer::flexibleHash(Expression* curr, |
| ExpressionAnalyzer::ExprHasher custom) { |
| return Hasher(curr, true, custom).digest; |
| } |
| |
| size_t ExpressionAnalyzer::shallowHash(Expression* curr) { |
| return Hasher(curr, false, ExpressionAnalyzer::nothingHasher).digest; |
| } |
| |
| } // namespace wasm |