| /* | |
| * Copyright 2020 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 "stack-utils.h" | |
| #include "ir/iteration.h" | |
| #include "ir/properties.h" | |
| namespace wasm { | |
| namespace StackUtils { | |
| void removeNops(Block* block) { | |
| size_t newIndex = 0; | |
| for (size_t i = 0, size = block->list.size(); i < size; ++i) { | |
| if (!block->list[i]->is<Nop>()) { | |
| block->list[newIndex++] = block->list[i]; | |
| } | |
| } | |
| block->list.resize(newIndex); | |
| } | |
| bool mayBeUnreachable(Expression* expr) { | |
| if (Properties::isControlFlowStructure(expr)) { | |
| return true; | |
| } | |
| switch (expr->_id) { | |
| case Expression::Id::BreakId: | |
| return expr->cast<Break>()->condition == nullptr; | |
| case Expression::Id::CallId: | |
| return expr->cast<Call>()->isReturn; | |
| case Expression::Id::CallIndirectId: | |
| return expr->cast<CallIndirect>()->isReturn; | |
| case Expression::Id::ReturnId: | |
| case Expression::Id::SwitchId: | |
| case Expression::Id::UnreachableId: | |
| case Expression::Id::ThrowId: | |
| case Expression::Id::RethrowId: | |
| return true; | |
| default: | |
| return false; | |
| } | |
| } | |
| } // namespace StackUtils | |
| StackSignature::StackSignature(Expression* expr) { | |
| std::vector<Type> inputs; | |
| for (auto* child : ValueChildIterator(expr)) { | |
| assert(child->type.isConcrete()); | |
| // Children might be tuple pops, so expand their types | |
| inputs.insert(inputs.end(), child->type.begin(), child->type.end()); | |
| } | |
| params = Type(inputs); | |
| if (expr->type == Type::unreachable) { | |
| kind = Polymorphic; | |
| results = Type::none; | |
| } else { | |
| kind = Fixed; | |
| results = expr->type; | |
| } | |
| } | |
| bool StackSignature::composes(const StackSignature& next) const { | |
| auto checked = std::min(results.size(), next.params.size()); | |
| return std::equal(results.end() - checked, | |
| results.end(), | |
| next.params.end() - checked, | |
| [](const Type& produced, const Type& consumed) { | |
| return Type::isSubType(produced, consumed); | |
| }); | |
| } | |
| StackSignature& StackSignature::operator+=(const StackSignature& next) { | |
| assert(composes(next)); | |
| std::vector<Type> stack(results.begin(), results.end()); | |
| size_t required = next.params.size(); | |
| // Consume stack values according to next's parameters | |
| if (stack.size() >= required) { | |
| stack.resize(stack.size() - required); | |
| } else { | |
| if (kind == Fixed) { | |
| // Prepend the unsatisfied params of `next` to the current params | |
| size_t unsatisfied = required - stack.size(); | |
| std::vector<Type> newParams(next.params.begin(), | |
| next.params.begin() + unsatisfied); | |
| newParams.insert(newParams.end(), params.begin(), params.end()); | |
| params = Type(newParams); | |
| } | |
| stack.clear(); | |
| } | |
| // Add stack values according to next's results | |
| if (next.kind == Polymorphic) { | |
| results = next.results; | |
| kind = Polymorphic; | |
| } else { | |
| stack.insert(stack.end(), next.results.begin(), next.results.end()); | |
| results = Type(stack); | |
| } | |
| return *this; | |
| } | |
| StackSignature StackSignature::operator+(const StackSignature& next) const { | |
| StackSignature sig = *this; | |
| sig += next; | |
| return sig; | |
| } | |
| bool StackSignature::isSubType(StackSignature a, StackSignature b) { | |
| if (a.params.size() > b.params.size() || | |
| a.results.size() > b.results.size()) { | |
| // `a` consumes or produces more values than `b` can provides or expects. | |
| return false; | |
| } | |
| if (a.kind == Fixed && b.kind == Polymorphic) { | |
| // Non-polymorphic sequences cannot be typed as being polymorphic. | |
| return false; | |
| } | |
| bool paramSuffixMatches = | |
| std::equal(a.params.begin(), | |
| a.params.end(), | |
| b.params.end() - a.params.size(), | |
| [](const Type& consumed, const Type& provided) { | |
| return Type::isSubType(provided, consumed); | |
| }); | |
| if (!paramSuffixMatches) { | |
| return false; | |
| } | |
| bool resultSuffixMatches = | |
| std::equal(a.results.begin(), | |
| a.results.end(), | |
| b.results.end() - a.results.size(), | |
| [](const Type& produced, const Type& expected) { | |
| return Type::isSubType(produced, expected); | |
| }); | |
| if (!resultSuffixMatches) { | |
| return false; | |
| } | |
| if (a.kind == Polymorphic) { | |
| // The polymorphism can consume any additional provided params and produce | |
| // any additional expected results, so we are done. | |
| return true; | |
| } | |
| // Any additional provided params will pass through untouched, so they must be | |
| // compatible with the additional produced results. | |
| return std::equal(b.params.begin(), | |
| b.params.end() - a.params.size(), | |
| b.results.begin(), | |
| b.results.end() - a.results.size(), | |
| [](const Type& provided, const Type& expected) { | |
| return Type::isSubType(provided, expected); | |
| }); | |
| } | |
| bool StackSignature::haveLeastUpperBound(StackSignature a, StackSignature b) { | |
| // If a signature is polymorphic, the LUB could extend its params and results | |
| // arbitrarily. Otherwise, the LUB must extend its params and results | |
| // uniformly so that each additional param is a subtype of the corresponding | |
| // additional result. | |
| auto extensionCompatible = [](auto self, auto other) -> bool { | |
| if (self.kind == Polymorphic) { | |
| return true; | |
| } | |
| // If no extension, then no problem. | |
| if (self.params.size() >= other.params.size() && | |
| self.results.size() >= other.results.size()) { | |
| return true; | |
| } | |
| auto extSize = other.params.size() - self.params.size(); | |
| if (extSize != other.results.size() - self.results.size()) { | |
| return false; | |
| } | |
| return std::equal(other.params.begin(), | |
| other.params.begin() + extSize, | |
| other.results.begin(), | |
| other.results.begin() + extSize, | |
| [](const Type& param, const Type& result) { | |
| return Type::isSubType(param, result); | |
| }); | |
| }; | |
| if (!extensionCompatible(a, b) || !extensionCompatible(b, a)) { | |
| return false; | |
| } | |
| auto valsCompatible = [](auto as, auto bs, auto compatible) -> bool { | |
| // Canonicalize so the as are shorter and any unshared prefix is on bs. | |
| if (bs.size() < as.size()) { | |
| std::swap(as, bs); | |
| } | |
| // Check that shared values after the unshared prefix have are compatible. | |
| size_t diff = bs.size() - as.size(); | |
| for (size_t i = 0, shared = as.size(); i < shared; ++i) { | |
| if (!compatible(as[i], bs[i + diff])) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| }; | |
| bool paramsOk = valsCompatible(a.params, b.params, [](Type a, Type b) { | |
| assert(a == b && "TODO: calculate greatest lower bound to handle " | |
| "contravariance correctly"); | |
| return true; | |
| }); | |
| bool resultsOk = valsCompatible(a.results, b.results, [](Type a, Type b) { | |
| return Type::getLeastUpperBound(a, b) != Type::none; | |
| }); | |
| return paramsOk && resultsOk; | |
| } | |
| StackSignature StackSignature::getLeastUpperBound(StackSignature a, | |
| StackSignature b) { | |
| assert(haveLeastUpperBound(a, b)); | |
| auto combineVals = [](auto as, auto bs, auto combine) -> std::vector<Type> { | |
| // Canonicalize so the as are shorter and any unshared prefix is on bs. | |
| if (bs.size() < as.size()) { | |
| std::swap(as, bs); | |
| } | |
| // Combine shared values after the unshared prefix. | |
| size_t diff = bs.size() - as.size(); | |
| std::vector<Type> vals(bs.begin(), bs.begin() + diff); | |
| for (size_t i = 0, shared = as.size(); i < shared; ++i) { | |
| vals.push_back(combine(as[i], bs[i + diff])); | |
| } | |
| return vals; | |
| }; | |
| auto params = combineVals(a.params, b.params, [&](Type a, Type b) { | |
| assert(a == b && "TODO: calculate greatest lower bound to handle " | |
| "contravariance correctly"); | |
| return a; | |
| }); | |
| auto results = combineVals(a.results, b.results, [&](Type a, Type b) { | |
| return Type::getLeastUpperBound(a, b); | |
| }); | |
| Kind kind = | |
| a.kind == Polymorphic && b.kind == Polymorphic ? Polymorphic : Fixed; | |
| return StackSignature{Type(params), Type(results), kind}; | |
| } | |
| StackFlow::StackFlow(Block* block) { | |
| // Encapsulates the logic for treating the block and its children | |
| // uniformly. The end of the block is treated as if it consumed values | |
| // corresponding to the its result type and produced no values, which is why | |
| // the block's result type is used as the params of its processed stack | |
| // signature. | |
| auto processBlock = [&block](auto process) { | |
| // TODO: Once we support block parameters, set up the stack by calling | |
| // `process` before iterating through the block. | |
| for (auto* expr : block->list) { | |
| process(expr, StackSignature(expr)); | |
| } | |
| auto kind = block->type == Type::unreachable ? StackSignature::Polymorphic | |
| : StackSignature::Fixed; | |
| Type params = block->type == Type::unreachable ? Type::none : block->type; | |
| process(block, StackSignature(params, Type::none, kind)); | |
| }; | |
| // We need to make an initial pass through the block to figure out how many | |
| // values each unreachable instruction produces. | |
| std::unordered_map<Expression*, size_t> producedByUnreachable; | |
| { | |
| size_t stackSize = 0; | |
| size_t produced = 0; | |
| Expression* lastUnreachable = nullptr; | |
| processBlock([&](Expression* expr, const StackSignature sig) { | |
| // Consume params | |
| if (sig.params.size() > stackSize) { | |
| // We need more values than are available, so they must come from the | |
| // last unreachable. | |
| assert(lastUnreachable); | |
| produced += sig.params.size() - stackSize; | |
| stackSize = 0; | |
| } else { | |
| stackSize -= sig.params.size(); | |
| } | |
| // Handle unreachable or produce results | |
| if (sig.kind == StackSignature::Polymorphic) { | |
| if (lastUnreachable) { | |
| producedByUnreachable[lastUnreachable] = produced; | |
| produced = 0; | |
| } | |
| assert(produced == 0); | |
| lastUnreachable = expr; | |
| stackSize = 0; | |
| } else { | |
| stackSize += sig.results.size(); | |
| } | |
| }); | |
| // Finish record for final unreachable | |
| if (lastUnreachable) { | |
| producedByUnreachable[lastUnreachable] = produced; | |
| } | |
| } | |
| // Take another pass through the block, recording srcs and dests. | |
| std::vector<Location> values; | |
| Expression* lastUnreachable = nullptr; | |
| processBlock([&](Expression* expr, const StackSignature sig) { | |
| assert((sig.params.size() <= values.size() || lastUnreachable) && | |
| "Block inputs not yet supported"); | |
| // Unreachable instructions consume all available values | |
| size_t consumed = sig.kind == StackSignature::Polymorphic | |
| ? std::max(values.size(), sig.params.size()) | |
| : sig.params.size(); | |
| // We previously calculated how many values unreachable instructions produce | |
| size_t produced = sig.kind == StackSignature::Polymorphic | |
| ? producedByUnreachable[expr] | |
| : sig.results.size(); | |
| srcs[expr] = std::vector<Location>(consumed); | |
| dests[expr] = std::vector<Location>(produced); | |
| // Figure out what kind of unreachable values we have | |
| assert(sig.params.size() <= consumed); | |
| size_t unreachableBeyondStack = 0; | |
| size_t unreachableFromStack = 0; | |
| if (consumed > values.size()) { | |
| assert(consumed == sig.params.size()); | |
| unreachableBeyondStack = consumed - values.size(); | |
| } else if (consumed > sig.params.size()) { | |
| assert(consumed == values.size()); | |
| unreachableFromStack = consumed - sig.params.size(); | |
| } | |
| // Consume values | |
| for (Index i = 0; i < consumed; ++i) { | |
| if (i < unreachableBeyondStack) { | |
| // This value comes from the polymorphic stack of the last unreachable | |
| // because the stack did not have enough values to satisfy this | |
| // instruction. | |
| assert(lastUnreachable); | |
| assert(producedByUnreachable[lastUnreachable] >= | |
| unreachableBeyondStack); | |
| Index destIndex = | |
| producedByUnreachable[lastUnreachable] - unreachableBeyondStack + i; | |
| Type type = sig.params[i]; | |
| srcs[expr][i] = {lastUnreachable, destIndex, type, true}; | |
| dests[lastUnreachable][destIndex] = {expr, i, type, false}; | |
| } else { | |
| // A normal value from the value stack | |
| bool unreachable = i < unreachableFromStack; | |
| auto& src = values[values.size() + i - consumed]; | |
| srcs[expr][i] = src; | |
| dests[src.expr][src.index] = {expr, i, src.type, unreachable}; | |
| } | |
| } | |
| // Update available values | |
| if (unreachableBeyondStack) { | |
| producedByUnreachable[lastUnreachable] -= unreachableBeyondStack; | |
| values.resize(0); | |
| } else { | |
| values.resize(values.size() - consumed); | |
| } | |
| // Produce values | |
| for (Index i = 0; i < sig.results.size(); ++i) { | |
| values.push_back({expr, i, sig.results[i], false}); | |
| } | |
| // Update the last unreachable instruction | |
| if (sig.kind == StackSignature::Polymorphic) { | |
| assert(producedByUnreachable[lastUnreachable] == 0); | |
| lastUnreachable = expr; | |
| } | |
| }); | |
| } | |
| StackSignature StackFlow::getSignature(Expression* expr) { | |
| auto exprSrcs = srcs.find(expr); | |
| auto exprDests = dests.find(expr); | |
| assert(exprSrcs != srcs.end() && exprDests != dests.end()); | |
| std::vector<Type> params, results; | |
| for (auto& src : exprSrcs->second) { | |
| params.push_back(src.type); | |
| } | |
| for (auto& dest : exprDests->second) { | |
| results.push_back(dest.type); | |
| } | |
| auto kind = expr->type == Type::unreachable ? StackSignature::Polymorphic | |
| : StackSignature::Fixed; | |
| return StackSignature(Type(params), Type(results), kind); | |
| } | |
| } // namespace wasm |