blob: ccfee5e65308bef236d2ca89abd1503be081eb75 [file] [log] [blame] [edit]
/*
* 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.
*/
//
// Simplify and optimize globals and their use.
//
// * Turns never-written and unwritable (not imported or exported)
// globals immutable.
// * If an immutable global is a copy of another, use the earlier one,
// to allow removal of the copies later.
// * Apply the constant values of immutable globals.
// * Apply the constant values of previous global.sets, in a linear
// execution trace.
// * Remove writes to globals that are never read from.
// * Remove writes to globals that are always assigned the same value.
// * Remove writes to globals that are only read from in order to write (see
// below, "readOnlyToWrite").
//
// Some globals may not have uses after these changes, which we leave
// to other passes to optimize.
//
// This pass has a "optimize" variant (similar to inlining and DAE)
// that also runs general function optimizations where we managed to replace
// a constant value. That is helpful as such a replacement often opens up
// further optimization opportunities.
//
#include <atomic>
#include "ir/effects.h"
#include "ir/find_all.h"
#include "ir/linear-execution.h"
#include "ir/properties.h"
#include "ir/utils.h"
#include "pass.h"
#include "wasm-builder.h"
#include "wasm.h"
namespace wasm {
namespace {
struct GlobalInfo {
// Whether the global is imported and exported.
bool imported = false;
bool exported = false;
// How many times the global is written and read.
std::atomic<Index> written{0};
std::atomic<Index> read{0};
// Whether the global is written a value different from its initial value.
std::atomic<bool> nonInitWritten{false};
// How many times the global is "read, but only to write", that is, is used in
// something like this pattern:
//
// if (global == X) { global = Y }
//
// The if's condition only uses |global| in order to decide to write to that
// same global, so it is "read, but only to write." If all we have are such
// reads only to write then the global is really not necessary, even though
// there are both reads and writes of it, and regardless of what the written
// values are etc.
//
// This pattern can show up in global initialization code, where in the block
// alongside "global = Y" there was some useful code, but the optimizer
// managed to remove it. For example,
//
// if (global == 0) { global = 1; sideEffect(); }
//
// If the global's initial value is the default 0, and there are no other uses
// of this global, then this code will run sideEffect() the very first time we
// reach here. We therefore need to keep this global and its reads and writes.
// However, if sideEffect() were removed, then we read the global only to
// write it - and nothing else - and so we can optimize away that global
// entirely.
std::atomic<Index> readOnlyToWrite{0};
};
using GlobalInfoMap = std::map<Name, GlobalInfo>;
struct GlobalUseScanner : public WalkerPass<PostWalker<GlobalUseScanner>> {
bool isFunctionParallel() override { return true; }
GlobalUseScanner(GlobalInfoMap* infos) : infos(infos) {}
std::unique_ptr<Pass> create() override {
return std::make_unique<GlobalUseScanner>(infos);
}
void visitGlobalSet(GlobalSet* curr) {
(*infos)[curr->name].written++;
// Check if there is a write of a value that may differ from the initial
// one. If there is anything but identical constants in both the initial
// value and the written value then we must assume that.
auto* global = getModule()->getGlobal(curr->name);
if (global->imported() || !Properties::isConstantExpression(curr->value) ||
!Properties::isConstantExpression(global->init) ||
Properties::getLiterals(curr->value) !=
Properties::getLiterals(global->init)) {
(*infos)[curr->name].nonInitWritten = true;
}
}
void visitGlobalGet(GlobalGet* curr) { (*infos)[curr->name].read++; }
void visitIf(If* curr) {
// We are looking for
//
// if (global == X) { global = Y }
//
// Ignore an if-else, which cannot be that.
if (curr->ifFalse) {
return;
}
auto global = readsGlobalOnlyToWriteIt(curr->condition, curr->ifTrue);
if (global.is()) {
// This is exactly the pattern we sought!
(*infos)[global].readOnlyToWrite++;
}
}
// Given a condition and some code that is executed based on the condition,
// check if the condition reads from some global in order to make the decision
// whether to run that code, and that code only writes to that global, which
// means the global is "read, but only to be written."
//
// The condition may also do other things than read from that global - it may
// compare it to a value, or negate it, or anything else, so long as the value
// of the global is only used to decide to run the code, like this:
//
// if (global % 17 < 4) { global = 1 }
//
// What we want to disallow is using the global to actually do something that
// is noticeeable *aside* from writing the global, like this:
//
// if (global ? foo() : bar()) { .. }
//
// Here ? : is another nested if, and we end up running different code based
// on global, which is noticeable: the global is *not* only read in order to
// write that global, but also for other reasons.
//
// Returns the global name if things like up, or a null name otherwise.
Name readsGlobalOnlyToWriteIt(Expression* condition, Expression* code) {
// See if writing a global is the only effect the code has. (Note that we
// don't need to care about the case where the code has no effects at
// all - other passes would handle that trivial situation.)
EffectAnalyzer codeEffects(getPassOptions(), *getModule(), code);
if (codeEffects.globalsWritten.size() != 1) {
return Name();
}
auto writtenGlobal = *codeEffects.globalsWritten.begin();
codeEffects.globalsWritten.clear();
if (codeEffects.hasAnything()) {
return Name();
}
// See if we read that global in the condition expression.
EffectAnalyzer conditionEffects(getPassOptions(), *getModule(), condition);
if (!conditionEffects.mutableGlobalsRead.count(writtenGlobal)) {
return Name();
}
// If the condition has no other (non-removable) effects other than reading
// that global then we have found what we looked for.
if (!conditionEffects.hasUnremovableSideEffects()) {
return writtenGlobal;
}
// There are unremovable side effects of some form. However, they may not
// be related to the reading of the global, that is, the global's value may
// not flow to anything that uses it in a dangerous way. It *would* be
// dangerous for the global's value to flow into a nested if condition, as
// mentioned in the comment earlier, but if it flows into an if arm for
// example then that is safe, so long as the final place it flows out to is
// the condition.
//
// To check this, find the get of the global in the condition, and look up
// through its parents to see how the global's value is used.
struct FlowScanner
: public ExpressionStackWalker<FlowScanner,
UnifiedExpressionVisitor<FlowScanner>> {
GlobalUseScanner& globalUseScanner;
Name writtenGlobal;
PassOptions& passOptions;
Module& wasm;
FlowScanner(GlobalUseScanner& globalUseScanner,
Name writtenGlobal,
PassOptions& passOptions,
Module& wasm)
: globalUseScanner(globalUseScanner), writtenGlobal(writtenGlobal),
passOptions(passOptions), wasm(wasm) {}
bool ok = true;
void visitExpression(Expression* curr) {
if (auto* get = curr->dynCast<GlobalGet>()) {
if (get->name == writtenGlobal) {
// We found the get of the global. Check where its value flows to,
// and how it is used there.
assert(expressionStack.back() == get);
for (int i = int(expressionStack.size()) - 2; i >= 0; i--) {
// Consider one pair of parent->child, and check if the parent
// causes any problems when the child's value reaches it.
auto* parent = expressionStack[i];
auto* child = expressionStack[i + 1];
EffectAnalyzer parentEffects(passOptions, wasm);
parentEffects.visit(parent);
if (parentEffects.hasUnremovableSideEffects()) {
// The parent has some side effect, and the child's value may
// be used to determine its manner, so this is dangerous.
ok = false;
break;
}
if (auto* iff = parent->dynCast<If>()) {
if (iff->condition == child) {
// The child is used to decide what code to run, which is
// dangerous: check what effects it causes. If it is a nested
// appearance of the pattern, that is one case that we know is
// actually safe.
if (!iff->ifFalse &&
globalUseScanner.readsGlobalOnlyToWriteIt(
iff->condition, iff->ifTrue) == writtenGlobal) {
// This is safe, and we can stop here: the value does not
// flow any further.
break;
}
// Otherwise, we found a problem, and can stop.
ok = false;
break;
}
}
}
}
}
}
};
FlowScanner scanner(*this, writtenGlobal, getPassOptions(), *getModule());
scanner.walk(condition);
return scanner.ok ? writtenGlobal : Name();
}
void visitFunction(Function* curr) {
// We are looking for a function body like this:
//
// if (global == X) return;
// global = Y;
//
// And nothing else at all. Note that this does not overlap with the if
// pattern above (the assignment is in the if body) so we will never have
// overlapping matchings (which would each count as 1, leading to a
// miscount).
if (curr->body->type != Type::none) {
return;
}
auto* block = curr->body->dynCast<Block>();
if (!block) {
return;
}
auto& list = block->list;
if (list.size() != 2) {
return;
}
auto* iff = list[0]->dynCast<If>();
if (!iff || iff->ifFalse || !iff->ifTrue->is<Return>()) {
return;
}
auto global = readsGlobalOnlyToWriteIt(iff->condition, list[1]);
if (global.is()) {
// This is exactly the pattern we sought!
(*infos)[global].readOnlyToWrite++;
}
}
private:
GlobalInfoMap* infos;
};
using NameNameMap = std::map<Name, Name>;
using NameSet = std::set<Name>;
struct GlobalUseModifier : public WalkerPass<PostWalker<GlobalUseModifier>> {
bool isFunctionParallel() override { return true; }
GlobalUseModifier(NameNameMap* copiedParentMap)
: copiedParentMap(copiedParentMap) {}
std::unique_ptr<Pass> create() override {
return std::make_unique<GlobalUseModifier>(copiedParentMap);
}
void visitGlobalGet(GlobalGet* curr) {
auto iter = copiedParentMap->find(curr->name);
if (iter != copiedParentMap->end()) {
auto original = iter->second;
// Only apply this optimization if the global we are switching to has the
// right type for us.
// TODO: We could also allow it to be more refined, but would then need to
// refinalize.
if (getModule()->getGlobal(original)->type == curr->type) {
curr->name = original;
}
}
}
private:
NameNameMap* copiedParentMap;
};
struct ConstantGlobalApplier
: public WalkerPass<
LinearExecutionWalker<ConstantGlobalApplier,
UnifiedExpressionVisitor<ConstantGlobalApplier>>> {
using Super = WalkerPass<
LinearExecutionWalker<ConstantGlobalApplier,
UnifiedExpressionVisitor<ConstantGlobalApplier>>>;
bool isFunctionParallel() override { return true; }
ConstantGlobalApplier(NameSet* constantGlobals, bool optimize)
: constantGlobals(constantGlobals), optimize(optimize) {}
std::unique_ptr<Pass> create() override {
return std::make_unique<ConstantGlobalApplier>(constantGlobals, optimize);
}
// It is ok to look at adjacent blocks together, as if a later part of a block
// is not reached that is fine - changes we make there would not be reached in
// that case.
bool connectAdjacentBlocks = true;
bool refinalize = false;
void replaceCurrent(Expression* rep) {
if (rep->type != getCurrent()->type) {
// This operation will change the type, so refinalize.
refinalize = true;
}
Super::replaceCurrent(rep);
}
void visitExpression(Expression* curr) {
if (auto* set = curr->dynCast<GlobalSet>()) {
if (Properties::isConstantExpression(set->value)) {
currConstantGlobals[set->name] =
getLiteralsFromConstExpression(set->value);
} else {
currConstantGlobals.erase(set->name);
}
return;
} else if (auto* get = curr->dynCast<GlobalGet>()) {
// Check if the global is known to be constant all the time.
if (constantGlobals->count(get->name)) {
auto* global = getModule()->getGlobal(get->name);
assert(Properties::isConstantExpression(global->init));
replaceCurrent(ExpressionManipulator::copy(global->init, *getModule()));
replaced = true;
return;
}
// Check if the global has a known value in this linear trace.
auto iter = currConstantGlobals.find(get->name);
if (iter != currConstantGlobals.end()) {
Builder builder(*getModule());
replaceCurrent(builder.makeConstantExpression(iter->second));
replaced = true;
}
return;
}
// Otherwise, invalidate if we need to. Note that we handled a GlobalSet
// earlier, but also need to handle calls. A general call forces us to
// forget everything, but in some cases we can do better, if we have a call
// and have computed function effects for it.
ShallowEffectAnalyzer effects(getPassOptions(), *getModule(), curr);
if (effects.calls) {
// Forget everything.
currConstantGlobals.clear();
} else {
// Forget just the globals written, if any.
for (auto writtenGlobal : effects.globalsWritten) {
currConstantGlobals.erase(writtenGlobal);
}
}
}
static void doNoteNonLinear(ConstantGlobalApplier* self, Expression** currp) {
self->currConstantGlobals.clear();
}
void visitFunction(Function* curr) {
if (replaced) {
if (refinalize) {
ReFinalize().walkFunctionInModule(curr, this->getModule());
}
if (optimize) {
PassRunner runner(getPassRunner());
runner.addDefaultFunctionOptimizationPasses();
runner.runOnFunction(curr);
}
}
}
private:
NameSet* constantGlobals;
bool optimize;
bool replaced = false;
// The globals currently constant in the linear trace.
std::map<Name, Literals> currConstantGlobals;
};
struct GlobalSetRemover : public WalkerPass<PostWalker<GlobalSetRemover>> {
GlobalSetRemover(const NameSet* toRemove, bool optimize)
: toRemove(toRemove), optimize(optimize) {}
bool isFunctionParallel() override { return true; }
std::unique_ptr<Pass> create() override {
return std::make_unique<GlobalSetRemover>(toRemove, optimize);
}
void visitGlobalSet(GlobalSet* curr) {
if (toRemove->count(curr->name) != 0) {
replaceCurrent(Builder(*getModule()).makeDrop(curr->value));
removed = true;
}
}
void visitFunction(Function* curr) {
if (removed && optimize) {
PassRunner runner(getPassRunner());
runner.addDefaultFunctionOptimizationPasses();
runner.runOnFunction(curr);
}
}
private:
const NameSet* toRemove;
bool optimize;
bool removed = false;
};
} // anonymous namespace
struct SimplifyGlobals : public Pass {
Module* module;
GlobalInfoMap map;
bool optimize;
SimplifyGlobals(bool optimize = false) : optimize(optimize) {}
void run(Module* module_) override {
module = module_;
while (iteration()) {
}
}
bool iteration() {
analyze();
// Fold single uses first, as it is simple to update the info from analyze()
// in this code (and harder to do in the things we do later, which is why we
// call analyze from scratch in each iteration).
foldSingleUses();
// Removing unneeded writes can in some cases lead to more optimizations
// that we need an entire additional iteration to perform, see below.
bool more = removeUnneededWrites();
preferEarlierImports();
propagateConstantsToGlobals();
propagateConstantsToCode();
return more;
}
void analyze() {
map.clear();
// First, find out all the relevant info.
for (auto& global : module->globals) {
auto& info = map[global->name];
if (global->imported()) {
info.imported = true;
}
}
for (auto& ex : module->exports) {
if (ex->kind == ExternalKind::Global) {
map[ex->value].exported = true;
}
}
GlobalUseScanner scanner(&map);
scanner.run(getPassRunner(), module);
scanner.runOnModuleCode(getPassRunner(), module);
// We now know which are immutable in practice.
for (auto& global : module->globals) {
auto& info = map[global->name];
if (global->mutable_ && !info.imported && !info.exported &&
!info.written) {
global->mutable_ = false;
}
}
}
// Removes writes from globals that will never do anything useful with the
// written value anyhow. Returns whether an addition iteration is necessary.
bool removeUnneededWrites() {
bool more = false;
// Globals that are not exports and not read from do not need their sets.
// Likewise, globals that only write their initial value later also do not
// need those writes. And, globals that are only read from in order to write
// to themselves as well. First, find such globals.
NameSet globalsNotNeedingSets;
for (auto& global : module->globals) {
auto& info = map[global->name];
if (!info.written) {
// No writes occur here, so there is nothing for us to remove.
continue;
}
if (info.imported || info.exported) {
// If the global is observable from the outside, we can't do anythng
// here.
//
// TODO: optimize the case of an imported but immutable global, etc.
continue;
}
// We only ever optimize read-only-to-write if all of our reads are done
// in places we identified as read-only-to-write. That is, we have
// eliminated the possibility of any other uses. (Technically, each
// read-to-write location might have more than one read since we did not
// count them, but only verified there was one read or more; but this is
// good enough as the common case has exactly one.)
//
// Note that there might be more writes, if there are additional writes
// besides those in the read-only-to-write locations. But we can ignore
// those, as whatever they write will not be read in order to do anything
// of value.
bool onlyReadOnlyToWrite = (info.read == info.readOnlyToWrite);
// There is at least one write in each read-only-to-write location, unless
// our logic is wrong somewhere.
assert(info.written >= info.readOnlyToWrite);
if (!info.read || !info.nonInitWritten || onlyReadOnlyToWrite) {
globalsNotNeedingSets.insert(global->name);
// We can now mark this global as immutable, and un-written, since we
// are about to remove all the sets on it.
global->mutable_ = false;
info.written = 0;
// Nested only-read-to-write expressions require another full iteration
// to optimize, as we have:
//
// if (a) {
// a = 1;
// if (b) {
// b = 1;
// }
// }
//
// The first iteration can only optimize b, as the outer if's body has
// more effects than we understand. After finishing the first iteration,
// b will no longer exist, removing those effects.
//
// TODO: In principle other situations exist as well where more
// iterations help, like if we remove a set that turns something
// into a read-only-to-write.
if (onlyReadOnlyToWrite) {
more = true;
}
}
}
// Remove all the sets on the unnecessary globals. Later optimizations can
// then see that since the global has no writes, it is a constant, which
// will lead to removal of gets, and after removing them, the global itself
// will be removed as well.
GlobalSetRemover(&globalsNotNeedingSets, optimize)
.run(getPassRunner(), module);
return more;
}
void preferEarlierImports() {
// Optimize uses of immutable globals, prefer the earlier one when there is
// a copy.
NameNameMap copiedParentMap;
for (auto& global : module->globals) {
auto child = global->name;
if (!global->mutable_ && !global->imported()) {
if (auto* get = global->init->dynCast<GlobalGet>()) {
auto parent = get->name;
if (!module->getGlobal(get->name)->mutable_) {
copiedParentMap[child] = parent;
}
}
}
}
if (!copiedParentMap.empty()) {
// Go all the way back.
for (auto& global : module->globals) {
auto child = global->name;
if (copiedParentMap.count(child)) {
while (copiedParentMap.count(copiedParentMap[child])) {
copiedParentMap[child] = copiedParentMap[copiedParentMap[child]];
}
}
}
// Apply to the gets.
GlobalUseModifier modifier(&copiedParentMap);
modifier.run(getPassRunner(), module);
modifier.runOnModuleCode(getPassRunner(), module);
}
}
// Constant propagation part 1: even an mutable global with a constant
// value can have that value propagated to another global that reads it,
// since we do know the value during startup, it can't be modified until
// code runs.
void propagateConstantsToGlobals() {
Builder builder(*module);
// We will note constant globals here as we compute them.
std::map<Name, Literals> constantGlobals;
// Given an init expression (something like the init of a global or a
// segment), see if it is a simple global.get of a constant that we can
// apply.
auto applyGlobals = [&](Expression*& init) {
if (!init) {
// This is the init of a passive segment, which is null.
return;
}
for (auto** getp : FindAllPointers<GlobalGet>(init).list) {
auto* get = (*getp)->cast<GlobalGet>();
auto iter = constantGlobals.find(get->name);
if (iter != constantGlobals.end()) {
*getp = builder.makeConstantExpression(iter->second);
}
}
};
// Go over the list of globals first, and note their constant values as we
// go, as well as applying them where possible.
for (auto& global : module->globals) {
if (!global->imported()) {
// Apply globals to this value, which may turn it into a constant we can
// further propagate, or it may already have been one.
applyGlobals(global->init);
if (Properties::isConstantExpression(global->init)) {
constantGlobals[global->name] =
getLiteralsFromConstExpression(global->init);
}
}
}
// Go over other things with inits and apply globals there.
for (auto& elementSegment : module->elementSegments) {
applyGlobals(elementSegment->offset);
}
for (auto& dataSegment : module->dataSegments) {
applyGlobals(dataSegment->offset);
}
}
// Constant propagation part 2: apply the values of immutable globals
// with constant values to global.gets in the code.
void propagateConstantsToCode() {
NameSet constantGlobals;
for (auto& global : module->globals) {
if (!global->mutable_ && !global->imported() &&
Properties::isConstantExpression(global->init)) {
constantGlobals.insert(global->name);
}
}
ConstantGlobalApplier(&constantGlobals, optimize)
.run(getPassRunner(), module);
// Note that we don't need to run on module code here, since we already
// handle applying constants in globals in propagateConstantsToGlobals (and
// in a more sophisticated manner, which takes into account that no sets of
// globals are possible during global instantiation).
}
// If we have a global that has a single use in the entire program, we can
// fold it into that use, if it is global. For example:
//
// var x = { foo: 5 };
// var y = { bar: x };
//
// This can become:
//
// var y = { bar: { foo: 5 } };
//
// If there is more than one use, or the use is in a function (where it might
// execute more than once) then we can't do this.
void foldSingleUses() {
struct Folder : public PostWalker<Folder> {
Module& wasm;
GlobalInfoMap& infos;
Folder(Module& wasm, GlobalInfoMap& infos) : wasm(wasm), infos(infos) {}
void visitGlobalGet(GlobalGet* curr) {
// If this is a get of a global with a single get and no sets, then we
// can fold that code into here.
auto name = curr->name;
auto& info = infos[name];
if (info.written == 0 && info.read == 1) {
auto* global = wasm.getGlobal(name);
if (global->init) {
// Copy that global's code. For simplicity we copy it as we have to
// keep that global valid for the operations that happen after us,
// even though that global will be removed later (we could remove it
// here, but it would add more complexity than seems worth it).
replaceCurrent(ExpressionManipulator::copy(global->init, wasm));
// Update info for later parts of this pass: we are removing a
// global.get, which is a read, so now there are 0 reads (we also
// have 0 writes, so no other work is needed here, but update to
// avoid confusion when debugging, and for possible future changes).
info.read = 0;
}
}
}
};
Folder folder(*module, map);
for (auto& global : module->globals) {
if (global->init) {
folder.walk(global->init);
}
}
}
};
// A pass mainly useful for testing that only performs the operation to
// propagate constant values between globals.
struct PropagateGlobalsGlobally : public SimplifyGlobals {
void run(Module* module_) override {
module = module_;
propagateConstantsToGlobals();
}
};
Pass* createSimplifyGlobalsPass() { return new SimplifyGlobals(false); }
Pass* createSimplifyGlobalsOptimizingPass() {
return new SimplifyGlobals(true);
}
Pass* createPropagateGlobalsGloballyPass() {
return new PropagateGlobalsGlobally();
}
} // namespace wasm