All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.javascript.jscomp.LiveVariablesAnalysis Maven / Gradle / Ivy

/*
 * Copyright 2008 The Closure Compiler Authors.
 *
 * 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.
 */

package com.google.javascript.jscomp;

import com.google.common.base.Preconditions;
import com.google.javascript.jscomp.ControlFlowGraph.Branch;
import com.google.javascript.jscomp.graph.DiGraph.DiGraphEdge;
import com.google.javascript.jscomp.graph.LatticeElement;
import com.google.javascript.rhino.Node;
import java.util.BitSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * Compute the "liveness" of all local variables. A variable is "live" at a
 * point of a program if the value it is currently holding might be read later.
 * Otherwise, the variable is considered "dead" if we know for sure that it will
 * no longer be read. Dead variables are candidates for dead assignment
 * elimination and variable name sharing. The worst case safe assumption is to
 * assume that all variables are live. In that case, we will have no opportunity
 * for optimizations. This is especially the case within a TRY block when an
 * assignment is not guaranteed to take place. We bail out by assuming that
 * all variables are live.
 * 

* Due to the possibility of inner functions and closures, certain "local" * variables can escape the function. These variables will be considered as * global and they can be retrieved with {@link #getEscapedLocals()}. * */ class LiveVariablesAnalysis extends DataFlowAnalysis { // 100 = ((# of original Power Rangers) ^ // (# years of Warren Harding in office)) * // (# of Ninja Turtles) static final int MAX_VARIABLES_TO_ANALYZE = 100; public static final String ARGUMENT_ARRAY_ALIAS = "arguments"; private static class LiveVariableJoinOp implements JoinOp { @Override public LiveVariableLattice apply(List in) { LiveVariableLattice result = new LiveVariableLattice(in.get(0)); for (int i = 1; i < in.size(); i++) { result.liveSet.or(in.get(i).liveSet); } return result; } } /** * The lattice that stores the liveness of all local variables at a given * point in the program. The whole lattice is the power set of all local * variables and a variable is live if it is in the set. */ static class LiveVariableLattice implements LatticeElement { private final BitSet liveSet; /** * @param numVars Number of all local variables. */ private LiveVariableLattice(int numVars) { this.liveSet = new BitSet(numVars); } private LiveVariableLattice(LiveVariableLattice other) { Preconditions.checkNotNull(other); this.liveSet = (BitSet) other.liveSet.clone(); } @Override public boolean equals(Object other) { Preconditions.checkNotNull(other); return (other instanceof LiveVariableLattice) && this.liveSet.equals(((LiveVariableLattice) other).liveSet); } public boolean isLive(Var v) { Preconditions.checkNotNull(v); return liveSet.get(v.index); } public boolean isLive(int index) { return liveSet.get(index); } @Override public String toString() { return liveSet.toString(); } @Override public int hashCode() { return liveSet.hashCode(); } } // The scope of the function that we are analyzing. private final Scope jsScope; private final Set escaped; LiveVariablesAnalysis(ControlFlowGraph cfg, Scope jsScope, AbstractCompiler compiler) { super(cfg, new LiveVariableJoinOp()); this.jsScope = jsScope; this.escaped = new HashSet<>(); computeEscaped(jsScope, escaped, compiler); } public Set getEscapedLocals() { return escaped; } public int getVarIndex(String var) { return jsScope.getVar(var).index; } @Override boolean isForward() { return false; } @Override LiveVariableLattice createEntryLattice() { return new LiveVariableLattice(jsScope.getVarCount()); } @Override LiveVariableLattice createInitialEstimateLattice() { return new LiveVariableLattice(jsScope.getVarCount()); } @Override LiveVariableLattice flowThrough(Node node, LiveVariableLattice input) { final BitSet gen = new BitSet(input.liveSet.size()); final BitSet kill = new BitSet(input.liveSet.size()); // Make kills conditional if the node can end abruptly by an exception. boolean conditional = false; List> edgeList = getCfg().getOutEdges(node); for (DiGraphEdge edge : edgeList) { if (Branch.ON_EX.equals(edge.getValue())) { conditional = true; } } computeGenKill(node, gen, kill, conditional); LiveVariableLattice result = new LiveVariableLattice(input); // L_in = L_out - Kill + Gen result.liveSet.andNot(kill); result.liveSet.or(gen); return result; } /** * Computes the GEN and KILL set. * * @param n Root node. * @param gen Local variables that are live because of the instruction at * {@code n} will be added to this set. * @param kill Local variables that are killed because of the instruction at * {@code n} will be added to this set. * @param conditional {@code true} if any assignments encountered are * conditionally executed. These assignments might not kill a variable. */ private void computeGenKill(Node n, BitSet gen, BitSet kill, boolean conditional) { switch (n.getToken()) { case SCRIPT: case BLOCK: case ROOT: case FUNCTION: return; case WHILE: case DO: case IF: computeGenKill(NodeUtil.getConditionExpression(n), gen, kill, conditional); return; case FOR: computeGenKill(NodeUtil.getConditionExpression(n), gen, kill, conditional); return; case FOR_IN: { // for(x in y) {...} Node lhs = n.getFirstChild(); if (lhs.isVar()) { // for(var x in y) {...} lhs = lhs.getLastChild(); } if (lhs.isName()) { addToSetIfLocal(lhs, kill); addToSetIfLocal(lhs, gen); } else { computeGenKill(lhs, gen, kill, conditional); } // rhs is executed only once so we don't go into it every loop. return; } case VAR: for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (c.hasChildren()) { computeGenKill(c.getFirstChild(), gen, kill, conditional); if (!conditional) { addToSetIfLocal(c, kill); } } } return; case AND: case OR: computeGenKill(n.getFirstChild(), gen, kill, conditional); // May short circuit. computeGenKill(n.getLastChild(), gen, kill, true); return; case HOOK: computeGenKill(n.getFirstChild(), gen, kill, conditional); // Assume both sides are conditional. computeGenKill(n.getSecondChild(), gen, kill, true); computeGenKill(n.getLastChild(), gen, kill, true); return; case NAME: if (isArgumentsName(n)) { markAllParametersEscaped(); } else { addToSetIfLocal(n, gen); } return; default: if (NodeUtil.isAssignmentOp(n) && n.getFirstChild().isName()) { Node lhs = n.getFirstChild(); if (!conditional) { addToSetIfLocal(lhs, kill); } if (!n.isAssign()) { // assignments such as a += 1 reads a. addToSetIfLocal(lhs, gen); } computeGenKill(lhs.getNext(), gen, kill, conditional); } else { for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { computeGenKill(c, gen, kill, conditional); } } return; } } private void addToSetIfLocal(Node node, BitSet set) { Preconditions.checkState(node.isName()); String name = node.getString(); if (!jsScope.isDeclared(name, false)) { return; } Var var = jsScope.getVar(name); if (!escaped.contains(var)) { set.set(var.index); } } /** * Give up computing liveness of formal parameter by putting all the parameter * names in the escaped set. */ void markAllParametersEscaped() { Node lp = jsScope.getRootNode().getSecondChild(); for (Node arg = lp.getFirstChild(); arg != null; arg = arg.getNext()) { escaped.add(jsScope.getVar(arg.getString())); } } private boolean isArgumentsName(Node n) { return n.isName() && n.getString().equals(ARGUMENT_ARRAY_ALIAS) && !jsScope.isDeclared(ARGUMENT_ARRAY_ALIAS, false); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy