/* * 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. */ // // Computes code at compile time where possible, replacing it with the // computed constant. // // The "propagate" variant of this pass also propagates constants across // sets and gets, which implements a standard constant propagation. // // Possible nondeterminism: WebAssembly NaN signs are nondeterministic, // and this pass may optimize e.g. a float 0 / 0 into +nan while a VM may // emit -nan, which can be a noticeable difference if the bits are // looked at. // #include #include #include #include #include #include #include #include #include namespace wasm { typedef std::unordered_map GetValues; // Precomputes an expression. Errors if we hit anything that can't be // precomputed. Inherits most of its functionality from // ConstantExpressionRunner, which it shares with the C-API, but adds handling // of GetValues computed during the precompute pass. class PrecomputingExpressionRunner : public ConstantExpressionRunner { // Concrete values of gets computed during the pass, which the runner does not // know about since it only records values of sets it visits. GetValues& getValues; // Limit evaluation depth for 2 reasons: first, it is highly unlikely // that we can do anything useful to precompute a hugely nested expression // (we should succed at smaller parts of it first). Second, a low limit is // helpful to avoid platform differences in native stack sizes. static const Index MAX_DEPTH = 50; // Limit loop iterations since loops might be infinite. Since we are going to // replace the expression and must preserve side effects, we limit this to the // very first iteration because a side effect would be necessary to achieve // more than one iteration before becoming concrete. static const Index MAX_LOOP_ITERATIONS = 1; public: PrecomputingExpressionRunner(Module* module, GetValues& getValues, bool replaceExpression) : ConstantExpressionRunner( module, replaceExpression ? FlagValues::PRESERVE_SIDEEFFECTS : FlagValues::DEFAULT, MAX_DEPTH, MAX_LOOP_ITERATIONS), getValues(getValues) {} Flow visitLocalGet(LocalGet* curr) { auto iter = getValues.find(curr); if (iter != getValues.end()) { auto values = iter->second; if (values.isConcrete()) { return Flow(values); } } return ConstantExpressionRunner< PrecomputingExpressionRunner>::visitLocalGet(curr); } // Heap data may be modified in ways we do not see. We would need escape // analysis to avoid that risk. For now, disallow all heap operations. // TODO: immutability might also be good enough Flow visitStructNew(StructNew* curr) { return Flow(NONCONSTANT_FLOW); } Flow visitStructGet(StructGet* curr) { return Flow(NONCONSTANT_FLOW); } Flow visitArrayNew(ArrayNew* curr) { return Flow(NONCONSTANT_FLOW); } Flow visitArrayGet(ArrayGet* curr) { return Flow(NONCONSTANT_FLOW); } Flow visitArrayLen(ArrayLen* curr) { return Flow(NONCONSTANT_FLOW); } Flow visitArrayCopy(ArrayCopy* curr) { return Flow(NONCONSTANT_FLOW); } }; struct Precompute : public WalkerPass< PostWalker>> { bool isFunctionParallel() override { return true; } Pass* create() override { return new Precompute(propagate); } bool propagate = false; Precompute(bool propagate) : propagate(propagate) {} GetValues getValues; bool worked; void doWalkFunction(Function* func) { // if propagating, we may need multiple rounds: each propagation can // lead to the main walk removing code, which might open up more // propagation opportunities do { getValues.clear(); // with extra effort, we can utilize the get-set graph to precompute // things that use locals that are known to be constant. otherwise, // we just look at what is immediately before us if (propagate) { optimizeLocals(func); } // do the main walk over everything worked = false; super::doWalkFunction(func); } while (propagate && worked); } template void reuseConstantNode(T* curr, Flow flow) { if (flow.values.isConcrete()) { // reuse a const / ref.null / ref.func node if there is one if (curr->value && flow.values.size() == 1) { Literal singleValue = flow.getSingleValue(); if (singleValue.type.isNumber()) { if (auto* c = curr->value->template dynCast()) { c->value = singleValue; c->finalize(); curr->finalize(); return; } } else if (singleValue.isNull()) { if (auto* n = curr->value->template dynCast()) { n->finalize(singleValue.type); curr->finalize(); return; } } else if (singleValue.type == Type::funcref) { if (auto* r = curr->value->template dynCast()) { r->func = singleValue.getFunc(); r->finalize(); curr->finalize(); return; } } } curr->value = flow.getConstExpression(*getModule()); } else { curr->value = nullptr; } curr->finalize(); } void visitExpression(Expression* curr) { // TODO: if local.get, only replace with a constant if we don't care about // size...? if (Properties::isConstantExpression(curr) || curr->is()) { return; } // try to evaluate this into a const Flow flow = precomputeExpression(curr); if (!canEmitConstantFor(flow.values)) { return; } if (flow.breaking()) { if (flow.breakTo == NONCONSTANT_FLOW) { return; } if (flow.breakTo == RETURN_FLOW) { // this expression causes a return. if it's already a return, reuse the // node if (auto* ret = curr->dynCast()) { reuseConstantNode(ret, flow); } else { Builder builder(*getModule()); replaceCurrent(builder.makeReturn( flow.values.isConcrete() ? flow.getConstExpression(*getModule()) : nullptr)); } return; } // this expression causes a break, emit it directly. if it's already a br, // reuse the node. if (auto* br = curr->dynCast()) { br->name = flow.breakTo; br->condition = nullptr; reuseConstantNode(br, flow); } else { Builder builder(*getModule()); replaceCurrent(builder.makeBreak( flow.breakTo, flow.values.isConcrete() ? flow.getConstExpression(*getModule()) : nullptr)); } return; } // this was precomputed if (flow.values.isConcrete()) { replaceCurrent(flow.getConstExpression(*getModule())); worked = true; } else { ExpressionManipulator::nop(curr); } } void visitFunction(Function* curr) { // removing breaks can alter types ReFinalize().walkFunctionInModule(curr, getModule()); } private: // Precompute an expression, returning a flow, which may be a constant // (that we can replace the expression with if replaceExpression is set). Flow precomputeExpression(Expression* curr, bool replaceExpression = true) { // Until engines implement v128.const and we have SIMD-aware optimizations // that can break large v128.const instructions into smaller consts and // splats, do not try to precompute v128 expressions. if (curr->type.isVector()) { return Flow(NONCONSTANT_FLOW); } Flow flow; try { flow = PrecomputingExpressionRunner(getModule(), getValues, replaceExpression) .visit(curr); } catch (PrecomputingExpressionRunner::NonconstantException&) { return Flow(NONCONSTANT_FLOW); } // If we are replacing the expression, then the resulting value must be of // a type we can emit a constant for. if (!flow.breaking() && replaceExpression && !canEmitConstantFor(flow.values)) { return Flow(NONCONSTANT_FLOW); } if (flow.getType().hasVector()) { return Flow(NONCONSTANT_FLOW); } return flow; } // Precomputes the value of an expression, as opposed to the expression // itself. This differs from precomputeExpression in that we care about // the value the expression will have, which we cannot necessary replace // the expression with. For example, // (local.tee (i32.const 1)) // will have value 1 which we can optimize here, but in precomputeExpression // we could not do anything. Literals precomputeValue(Expression* curr) { // Note that we set replaceExpression to false, as we just care about // the value here. Flow flow = precomputeExpression(curr, false /* replaceExpression */); if (flow.breaking()) { return {}; } return flow.values; } // Propagates values around. Returns whether we propagated. void optimizeLocals(Function* func) { // using the graph of get-set interactions, do a constant-propagation type // operation: note which sets are assigned locals, then see if that lets us // compute other sets as locals (since some of the gets they read may be // constant). // compute all dependencies LocalGraph localGraph(func); localGraph.computeInfluences(); localGraph.computeSSAIndexes(); // prepare the work list. we add things here that might change to a constant // initially, that means everything std::unordered_set work; for (auto& pair : localGraph.locations) { auto* curr = pair.first; work.insert(curr); } // the constant value, or none if not a constant std::unordered_map setValues; // propagate constant values while (!work.empty()) { auto iter = work.begin(); auto* curr = *iter; work.erase(iter); // see if this set or get is actually a constant value, and if so, // mark it as such and add everything it influences to the work list, // as they may be constant too. if (auto* set = curr->dynCast()) { if (setValues[set].isConcrete()) { continue; // already known constant } // Precompute the value. Note that this executes the code from scratch // each time we reach this point, and so we need to be careful about // repeating side effects if those side effects are expressed *in the // value*. A case where that can happen is GC data (each struct.new // creates a new, unique struct, even if the data is equal), and so // PrecomputingExpressionRunner will return a nonconstant flow for all // GC heap operations. // (Other side effects are fine; if an expression does a call and we // somehow know the entire expression precomputes to a 42, then we can // propagate that 42 along to the users, regardless of whatever the call // did globally.) auto values = precomputeValue(Properties::getFallthrough( set->value, getPassOptions(), getModule()->features)); // Fix up the value. The computation we just did was to look at the // fallthrough, then precompute that; that looks through expressions // that pass through the value. Normally that does not matter here, // for example, (block .. (value)) returns the value unmodified. // However, some things change the type, for example RefAsNonNull has // a non-null type, while its input may be nullable. That does not // matter either, as if we managed to precompute it then the value had // the more specific (in this example, non-nullable) type. But there // is a situation where this can cause an issue: RefCast. An attempt to // perform a "bad" cast, say of a function to a struct, is a case where // the fallthrough value's type is very different than the actually // returned value's type. To handle that, if we precomputed a value and // if it has the wrong type then precompute it again without looking // through to the fallthrough. if (values.isConcrete() && !Type::isSubType(values.getType(), set->value->type)) { values = precomputeValue(set->value); } setValues[set] = values; if (values.isConcrete()) { for (auto* get : localGraph.setInfluences[set]) { work.insert(get); } } } else { auto* get = curr->cast(); if (getValues[get].size() >= 1) { continue; // already known constant } // for this get to have constant value, all sets must agree Literals values; bool first = true; for (auto* set : localGraph.getSetses[get]) { Literals curr; if (set == nullptr) { if (getFunction()->isVar(get->index)) { curr = Literal::makeZeros(getFunction()->getLocalType(get->index)); } else { // it's a param, so it's hopeless values = {}; break; } } else { curr = setValues[set]; } if (curr.isNone()) { // not a constant, give up values = {}; break; } // we found a concrete value. compare with the current one if (first) { values = curr; // this is the first first = false; } else { if (values != curr) { // not the same, give up values = {}; break; } } } // we may have found a value if (values.isConcrete()) { // we did! getValues[get] = values; for (auto* set : localGraph.getInfluences[get]) { work.insert(set); } } } } } bool canEmitConstantFor(const Literals& values) { for (auto& value : values) { if (!canEmitConstantFor(value)) { return false; } } return true; } bool canEmitConstantFor(const Literal& value) { // A null is fine to emit a constant for - we'll emit a RefNull. Otherwise, // see below about references to GC data. if (value.isNull()) { return true; } return canEmitConstantFor(value.type); } bool canEmitConstantFor(Type type) { // A function is fine to emit a constant for - we'll emit a RefFunc, which // is compact and immutable, so there can't be a problem. if (type.isFunction()) { return true; } // All other reference types cannot be precomputed. Even an immutable GC // reference is not currently something this pass can handle, as it will // evaluate and reevaluate code multiple times in e.g. optimizeLocals, see // the comment above. if (type.isRef()) { return false; } // For now, don't try to precompute an Rtt. TODO figure out when that would // be safe and useful. return !type.isRtt(); } }; Pass* createPrecomputePass() { return new Precompute(false); } Pass* createPrecomputePropagatePass() { return new Precompute(true); } } // namespace wasm