diff options
author | Daniel Wirtz <dcode@dcode.io> | 2021-03-03 02:29:13 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-03-03 02:29:13 +0100 |
commit | ecd915ef62cc4b08ad11f9daa9e0b9ff0a7580c1 (patch) | |
tree | 0b65ed18e01b6cf5ba20d26b4279498aaeff3a40 /src/passes/PostAssemblyScript.cpp | |
parent | 47c15838ad4378430b2d216fbf4474b71d3fb66f (diff) | |
download | binaryen-ecd915ef62cc4b08ad11f9daa9e0b9ff0a7580c1.tar.gz binaryen-ecd915ef62cc4b08ad11f9daa9e0b9ff0a7580c1.tar.bz2 binaryen-ecd915ef62cc4b08ad11f9daa9e0b9ff0a7580c1.zip |
Remove PostAssemblyScript passes (#3643)
Diffstat (limited to 'src/passes/PostAssemblyScript.cpp')
-rw-r--r-- | src/passes/PostAssemblyScript.cpp | 640 |
1 files changed, 0 insertions, 640 deletions
diff --git a/src/passes/PostAssemblyScript.cpp b/src/passes/PostAssemblyScript.cpp deleted file mode 100644 index aa9c5cc40..000000000 --- a/src/passes/PostAssemblyScript.cpp +++ /dev/null @@ -1,640 +0,0 @@ -/* - * Copyright 2019 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. - */ - -// -// Misc optimizations that are useful for and/or are only valid for -// AssemblyScript output. -// - -#include "ir/flat.h" -#include "ir/local-graph.h" -#include "pass.h" -#include "wasm-builder.h" -#include "wasm-traversal.h" -#include "wasm.h" -#include <unordered_map> -#include <unordered_set> -#ifdef POST_ASSEMBLYSCRIPT_DEBUG -#include <iostream> -#endif - -namespace wasm { - -namespace PostAssemblyScript { - -static Name RETAIN = Name("~lib/rt/pure/__retain"); -static Name RELEASE = Name("~lib/rt/pure/__release"); -static Name ALLOC = Name("~lib/rt/tlsf/__alloc"); -static Name ALLOCARRAY = Name("~lib/rt/__allocArray"); - -template<typename K, typename V> using Map = std::unordered_map<K, V>; -template<typename T> using Set = std::unordered_set<T>; - -// A variant of LocalGraph that considers only assignments when computing -// influences. -// -// This allows us to find locals aliasing a retain, while ignoring other -// influences. -// -// For example, we are interested in -// -// var a = __retain(X) -// var b = a; -// __release(b); // releases X -// -// but not in -// -// var a = __retain(X); -// var b = someFunction(a); -// __release(b); -// return a; -// -// since the latter releases not 'X' but the reference returned by the call, -// which is usually something else. -struct AliasGraph : LocalGraph { - AliasGraph(Function* func) : LocalGraph(func) {} - void computeInfluences() { - for (auto& pair : locations) { - auto* curr = pair.first; - if (auto* set = curr->dynCast<LocalSet>()) { - if (auto* get = set->value->dynCast<LocalGet>()) { - getInfluences[get].insert(set); - } - } else { - auto* get = curr->cast<LocalGet>(); - for (auto* set : getSetses[get]) { - setInfluences[set].insert(get); - } - } - } - } -}; - -// Tests if the given call calls retain. Note that this differs from what we -// consider a full retain pattern, which must also set a local. -static bool isRetainCall(Call* expr) { - // __retain(...) - return expr->target == RETAIN && expr->type == Type::i32 && - expr->operands.size() == 1 && expr->operands[0]->type == Type::i32; -} - -// Tests if a local.set is considered to be a full retain pattern. -static bool isRetain(LocalSet* expr) { - // local.set(X, __retain(...)) - if (auto* call = expr->value->dynCast<Call>()) { - return isRetainCall(call); - } - return false; -} - -#ifndef NDEBUG -// Tests if the given location is that of a full retain pattern. -static bool isRetainLocation(Expression** expr) { - if (expr != nullptr) { - if (auto localSet = (*expr)->dynCast<LocalSet>()) { - return isRetain(localSet); - } - } - return false; -} -#endif - -// Tests if the given call calls release. Note that this differs from what we -// consider a full release pattern, which must also get a local. -static bool isReleaseCall(Call* expr) { - // __release(...) - return expr->target == RELEASE && expr->type == Type::none && - expr->operands.size() == 1 && expr->operands[0]->type == Type::i32; -} - -// Tests if the given location is that of a full release pattern. Note that -// the local.get is our key when checking for releases to align with -// AliasGraph, and not the outer call, which is also the reason why there is -// no `isRelease` as we can't tell from the local.get alone. -static bool isReleaseLocation(Expression** expr) { - // __release(local.get(X, ...)) - if (expr != nullptr) { - if (auto* call = (*expr)->dynCast<Call>()) { - return isReleaseCall(call) && call->operands[0]->is<LocalGet>(); - } - } - return false; -} - -// Tests if the given call calls any allocation function. -static bool isAllocCall(Call* expr) { - return (expr->target == ALLOC || expr->target == ALLOCARRAY) && - expr->type == Type::i32; -} - -// A pass that eliminates redundant retain and release calls. -// -// Does a cheap traversal first, remembering ARC-style patterns, and goes all-in -// only if it finds any. -// -// This is based on the assumption that the compiler is not allowed to emit -// unbalanced retains or releases, except if -// -// * a value is returned or otherwise escapes in one branch or -// * a branch is being internally unified by the compiler -// -// which we detect below. In turn, we do not have to deal with control -// structures but can instead look for escapes reached (by any alias) using -// AliasGraph. -// -// For example, in code like -// -// var a = __retain(X); -// if (cond) { -// return a; -// } -// __release(a); -// return null; -// -// we cannot eliminate the retain/release pair because the implementation -// dictates that returned references must remain retained for the caller since -// dropping to RC=0 on the boundary would prematurely free the object. -// -// Typical patterns this recognizes are simple pairs of the form -// -// var a = __retain(X); -// __release(a); -// -// retains with balanced releases of the form -// -// var a = __retain(X); -// if (cond) { -// __release(a); -// } else { -// __release(a); -// } -// -// releases with balanced retains of the form -// -// var a; -// if (cond) { -// a = __retain(X); -// } else { -// a = __retain(Y); -// } -// __release(a); -// -// including technically invalid patterns assumed to be not present in compiler -// output, like: -// -// var b = __retain(a); -// if (cond) { -// __release(b); // unbalanced release -// } -// -// To detect the latter, we'd have to follow control structures around, which -// we don't do since it isn't neccessary / to keep the amount of work minimal. -struct OptimizeARC : public WalkerPass<PostWalker<OptimizeARC>> { - - bool isFunctionParallel() override { return true; } - - Pass* create() override { return new OptimizeARC; } - - // Sets that are retains, to location - Map<LocalSet*, Expression**> retains; - - // Gets that are releases, to location - Map<LocalGet*, Expression**> releases; - - // Gets that are escapes, i.e. being returned or thrown - Set<LocalGet*> escapes; - - void visitLocalSet(LocalSet* curr) { - if (isRetain(curr)) { - retains[curr] = getCurrentPointer(); - } - } - - void visitCall(Call* curr) { - auto** currp = getCurrentPointer(); - if (isReleaseLocation(currp)) { - releases[curr->operands[0]->cast<LocalGet>()] = currp; - } - } - - void visitReturn(Return* curr) { - // return(local.get(X, ...)) ? - // indicates that an object is returned from one function and given to - // another, so releasing it would be invalid. - auto* value = curr->value; - if (value) { - if (auto* localGet = value->dynCast<LocalGet>()) { - escapes.insert(localGet); - } - } - } - - void visitThrow(Throw* curr) { - // throw(..., local.get(X, ...), ...) ? - // indicates that an object is thrown in one function and can be caught - // anywhere, like in another function, so releasing it would be invalid. - for (auto* operand : curr->operands) { - if (auto* localGet = operand->dynCast<LocalGet>()) { - escapes.insert(localGet); - break; - } - } - } - - void eliminateRetain(Expression** location) { - assert(isRetainLocation(location)); - auto* localSet = (*location)->cast<LocalSet>(); - localSet->value = localSet->value->cast<Call>()->operands[0]; - } - - void eliminateRelease(Expression** location) { - assert(isReleaseLocation(location)); - Builder builder(*getModule()); - *location = builder.makeNop(); - } - - // Tests if a retain reaches an escape and thus is considered necessary. - bool - testReachesEscape(LocalSet* retain, AliasGraph& graph, Set<LocalSet*>& seen) { - for (auto* localGet : graph.setInfluences[retain]) { - if (releases.find(localGet) != releases.end()) { - continue; - } - if (escapes.find(localGet) != escapes.end()) { - return true; - } - for (auto* localSet : graph.getInfluences[localGet]) { - if (seen.find(localSet) == seen.end()) { - seen.insert(localSet); - if (testReachesEscape(localSet, graph, seen)) { - return true; - } - } - } - } - return false; - } - - bool testReachesEscape(LocalSet* retain, AliasGraph& graph) { - Set<LocalSet*> seen; - return testReachesEscape(retain, graph, seen); - } - - // Collects all reachable releases of a retain. - void collectReleases(LocalSet* retain, - AliasGraph& graph, - Set<Expression**>& found, - Set<LocalSet*>& seen) { - for (auto* localGet : graph.setInfluences[retain]) { - auto foundRelease = releases.find(localGet); - if (foundRelease != releases.end()) { - found.insert(foundRelease->second); - } else { - for (auto* localSet : graph.getInfluences[localGet]) { - if (seen.find(localSet) == seen.end()) { - seen.insert(localSet); - collectReleases(localSet, graph, found, seen); - } - } - } - } - } - - void collectReleases(LocalSet* retain, - AliasGraph& graph, - Set<Expression**>& found) { - Set<LocalSet*> seen; - collectReleases(retain, graph, found, seen); - } - - // Given a retain, gets the retained expression - static Expression* getRetainedExpression(LocalSet* retain) { - assert(isRetain(retain)); - return retain->value->cast<Call>()->operands[0]; - } - - // Tests if a retained value originates at an allocation and thus is - // considered necessary. - bool testRetainsAllocation(Expression* retained, - AliasGraph& graph, - Set<LocalSet*>& seen) { - if (auto* call = retained->dynCast<Call>()) { - if (call->target == ALLOC || call->target == ALLOCARRAY) { - return true; - } - } else { - if (auto* localGet = retained->dynCast<LocalGet>()) { - for (auto* localSet : graph.getSetses[localGet]) { - if (localSet != nullptr) { - if (seen.find(localSet) == seen.end()) { - seen.insert(localSet); - if (testRetainsAllocation(localSet->value, graph, seen)) { - return true; - } - } - } - } - } - } - return false; - } - - bool testRetainsAllocation(Expression* retained, AliasGraph& graph) { - Set<LocalSet*> seen; - return testRetainsAllocation(retained, graph, seen); - } - - // Given a release location, gets the local.get that is our release indicator - static LocalGet* getReleaseByLocation(Expression** releaseLocation) { - assert(isReleaseLocation(releaseLocation)); - return (*releaseLocation)->cast<Call>()->operands[0]->cast<LocalGet>(); - } - - // Tests if a release has balanced retains, that is it is being retained in - // any path leading to the release. For example - // - // var c = somethingElse() || a; - // ... - // - // which compiles to - // - // if (!(b = somethingElse())) { - // b = __retain(a); - // } - // var c = b; - // ... - // __release(c); - // - // is unbalanced since it reaches a retain and something else. Here, the - // compiler inserts the retain call because it must unify the two branches - // since the result of `somethingElse()` is known to be retained for the - // caller and the other branch must yield a retained value as well. - bool testBalancedRetains(LocalGet* release, - AliasGraph& graph, - Map<LocalGet*, bool>& cache, - Set<LocalGet*>& seen) { - auto cached = cache.find(release); - if (cached != cache.end()) { - return cached->second; - } - for (auto* localSet : graph.getSetses[release]) { - if (localSet == nullptr) { - return cache[release] = false; - } - if (retains.find(localSet) == retains.end()) { - if (auto* localGet = localSet->value->dynCast<LocalGet>()) { - if (seen.find(localGet) == seen.end()) { - seen.insert(localGet); - if (!testBalancedRetains(localGet, graph, cache, seen)) { - return cache[release] = false; - } - } else { - return cache[release] = false; - } - } else { - return cache[release] = false; - } - } - } - return cache[release] = true; - } - - bool testBalancedRetains(LocalGet* release, - AliasGraph& graph, - Map<LocalGet*, bool>& cache) { - Set<LocalGet*> seen; - return testBalancedRetains(release, graph, cache, seen); - } - - void doWalkFunction(Function* func) { - Flat::verifyFlatness(func); -#ifdef POST_ASSEMBLYSCRIPT_DEBUG - std::cerr << "[PostAssemblyScript::OptimizeARC] walking " << func->name - << "\n"; -#endif - super::doWalkFunction(func); - if (retains.empty()) { -#ifdef POST_ASSEMBLYSCRIPT_DEBUG - std::cerr << " no ARC code\n"; -#endif - return; - } - - AliasGraph graph(func); - graph.computeInfluences(); - - Set<Expression**> redundantRetains; - Set<Expression**> redundantReleases; - Map<LocalGet*, bool> balancedRetainsCache; - - // For each retain, check that it - // - // * doesn't reach an escape - // * doesn't retain an allocation - // * reaches at least one release - // * reaches only releases with balanced retains - // - for (auto& pair : retains) { - auto* retain = pair.first; - auto** retainLocation = pair.second; - if (!testReachesEscape(retain, graph)) { - if (!testRetainsAllocation(getRetainedExpression(retain), graph)) { - Set<Expression**> releaseLocations; - collectReleases(retain, graph, releaseLocations); - if (!releaseLocations.empty()) { - bool allBalanced = true; - for (auto** releaseLocation : releaseLocations) { - if (!testBalancedRetains(getReleaseByLocation(releaseLocation), - graph, - balancedRetainsCache)) { - allBalanced = false; - break; - } - } - if (allBalanced) { -#ifdef POST_ASSEMBLYSCRIPT_DEBUG - std::cerr << " eliminating "; - std::cerr << *retain << '\n'; - std::cerr << " reaching\n"; -#endif - redundantRetains.insert(retainLocation); - for (auto** getLocation : releaseLocations) { -#ifdef POST_ASSEMBLYSCRIPT_DEBUG - std::cerr << " "; - std::cerr << **getLocation << '\n'; - std::cerr << "\n"; -#endif - redundantReleases.insert(getLocation); - } -#ifdef POST_ASSEMBLYSCRIPT_DEBUG - } else { - std::cerr << " cannot eliminate "; - std::cerr << *retain << '\n'; - std::cerr << " - unbalanced\n"; -#endif - } -#ifdef POST_ASSEMBLYSCRIPT_DEBUG - } else { - std::cerr << " cannot eliminate "; - std::cerr << *retain << '\n'; - std::cerr << " - zero releases\n"; -#endif - } -#ifdef POST_ASSEMBLYSCRIPT_DEBUG - } else { - std::cerr << " cannot eliminate "; - std::cerr << *retain << '\n'; - std::cerr << " - retains allocation\n"; -#endif - } -#ifdef POST_ASSEMBLYSCRIPT_DEBUG - } else { - std::cerr << " cannot eliminate "; - std::cerr << *retain << '\n'; - std::cerr << " - reaches return\n"; -#endif - } - } - for (auto** location : redundantRetains) { - eliminateRetain(location); - } - for (auto** location : redundantReleases) { - eliminateRelease(location); - } -#ifdef POST_ASSEMBLYSCRIPT_DEBUG - std::cerr << " eliminated " << redundantRetains.size() << "/" - << retains.size() << " retains and " << redundantReleases.size() - << "/" << releases.size() << " releases\n"; -#endif - } -}; - -// Eliminating retains and releases makes it more likely that other passes lead -// to collapsed release/retain pairs that are not full retain or release -// patterns, and this pass finalizes such pairs. Typical patterns are entire -// unnecessary allocations of the form -// -// __release(__retain(__alloc(...)); -// -// otherwise unnecessary pairs of the form -// -// __release(__retain(...)); -// -// or retains/releases of constants which indicate data in static memory which -// are unnecessary to refcount: -// -// __retain("staticString"); -// -// __release("staticString"); -// -struct FinalizeARC : public WalkerPass<PostWalker<FinalizeARC>> { - - bool isFunctionParallel() override { return true; } - - Pass* create() override { return new FinalizeARC; } - - uint32_t eliminatedAllocations = 0; - uint32_t eliminatedRetains = 0; - uint32_t eliminatedReleases = 0; - - void visitCall(Call* curr) { - if (isReleaseCall(curr)) { - if (auto* releasedCall = curr->operands[0]->dynCast<Call>()) { - if (isRetainCall(releasedCall)) { - if (auto* retainedCall = releasedCall->operands[0]->dynCast<Call>()) { - if (isAllocCall(retainedCall)) { - // __release(__retain(__alloc(...))) - unnecessary allocation -#ifdef POST_ASSEMBLYSCRIPT_DEBUG - std::cerr << " finalizing "; - std::cerr << *curr << '\n'; - std::cerr << " - unnecessary allocation\n"; -#endif - Builder builder(*getModule()); - replaceCurrent(builder.makeNop()); - ++eliminatedAllocations; - ++eliminatedRetains; - ++eliminatedReleases; - return; - } - } - // __release(__retain(...)) - unnecessary pair -#ifdef POST_ASSEMBLYSCRIPT_DEBUG - std::cerr << " finalizing "; - std::cerr << *curr << '\n'; - std::cerr << " - unnecessary pair\n"; -#endif - Builder builder(*getModule()); - replaceCurrent(builder.makeDrop(releasedCall->operands[0])); - ++eliminatedRetains; - ++eliminatedReleases; - } - } else if (curr->operands[0]->is<Const>()) { - // __release(42) - unnecessary static release -#ifdef POST_ASSEMBLYSCRIPT_DEBUG - std::cerr << " finalizing "; - std::cerr << *curr << '\n'; - std::cerr << " - static release\n"; -#endif - Builder builder(*getModule()); - replaceCurrent(builder.makeNop()); - ++eliminatedReleases; - } - } else if (isRetainCall(curr)) { - if (auto* retainedConst = curr->operands[0]->dynCast<Const>()) { - // __retain(42) - unnecessary static retain -#ifdef POST_ASSEMBLYSCRIPT_DEBUG - std::cerr << " finalizing "; - std::cerr << *curr << '\n'; - std::cerr << " - static retain\n"; -#endif - replaceCurrent(retainedConst); - ++eliminatedRetains; - } - } - } - - void doWalkFunction(Function* func) { -#ifdef POST_ASSEMBLYSCRIPT_DEBUG - std::cerr << "[PostAssemblyScript::FinalizeARC] walking " << func->name - << "\n"; -#endif - super::doWalkFunction(func); -#ifdef POST_ASSEMBLYSCRIPT_DEBUG - if (eliminatedAllocations > 0 || eliminatedRetains > 0 || - eliminatedReleases > 0) { - std::cerr << " finalized " << eliminatedAllocations << " allocations, " - << eliminatedRetains << " retains and" << eliminatedReleases - << " releases\n"; - } else { - std::cerr << " nothing to do\n"; - } -#endif - } -}; - -} // namespace PostAssemblyScript - -// declare passes - -Pass* createPostAssemblyScriptPass() { - return new PostAssemblyScript::OptimizeARC(); -} - -Pass* createPostAssemblyScriptFinalizePass() { - return new PostAssemblyScript::FinalizeARC(); -} - -} // namespace wasm |