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

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

/*
 * Copyright 2009 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 static com.google.common.base.Preconditions.checkState;

import com.google.common.annotations.VisibleForTesting;
import com.google.javascript.jscomp.OptimizeCalls.ReferenceMap;
import com.google.javascript.jscomp.diagnostic.LogFile;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.Node;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;

/**
 * A compiler pass to optimize function return results. Currently this pass looks for results that
 * are completely unused and rewrites them as: "return x()" --> "x(); return"
 *
 * 

Future work: expand this to look for use context to avoid unneeded type coercion: \ * *

"return x.toString()" --> "return x" \ * *

"return !!x" --> "return x" */ class OptimizeReturns implements OptimizeCalls.CallGraphCompilerPass, CompilerPass { private final AbstractCompiler compiler; // Allocated & cleaned up by process() private LogFile decisionsLog; OptimizeReturns(AbstractCompiler compiler) { this.compiler = compiler; } @Override @VisibleForTesting public void process(Node externs, Node root) { OptimizeCalls.builder() .setCompiler(compiler) .setConsiderExterns(false) .addPass(this) .build() .process(externs, root); } @Override public void process(Node externs, Node root, ReferenceMap definitions) { try (LogFile logFile = compiler.createOrReopenIndexedLog(this.getClass(), "decisions.log")) { decisionsLog = logFile; // avoid passing the log file through a bunch of methods // Find all function nodes whose callers ignore the return values. List> toOptimize = new ArrayList<>(); // Find all the candidates before modifying the AST. for (Entry> entry : definitions.getNameReferences()) { String key = entry.getKey(); ArrayList refs = entry.getValue(); if (isCandidate(key, refs)) { decisionsLog.log("name %s\tremoving return value", key); toOptimize.add(refs); } } for (Entry> entry : definitions.getPropReferences()) { String key = entry.getKey(); ArrayList refs = entry.getValue(); if (isCandidate(key, refs)) { decisionsLog.log("property %s\tremoving return value", key); toOptimize.add(refs); } } // Now modify the AST for (ArrayList refs : toOptimize) { for (Node fn : ReferenceMap.getFunctionNodes(refs).values()) { rewriteReturns(fn); } } } finally { decisionsLog = null; } } /** * This reference set is a candidate for return-value-removal if: - if the all call sites are * known (not aliased, not exported) - if all call sites do not use the return value - if there is * at least one known function definition - if there is at least one use NOTE: unknown definitions * are allowed, as only known definitions will be removed. */ private boolean isCandidate(String name, List refs) { if (!OptimizeCalls.mayBeOptimizableName(compiler, name)) { decisionsLog.log("%s\tnot an optimizable name", name); return false; } boolean seenCandidateDefiniton = false; boolean seenUse = false; for (Node n : refs) { // Assume indirect definitions references use the result if (ReferenceMap.isCallTarget(n) || ReferenceMap.isOptChainCallTarget(n)) { Node callNode = ReferenceMap.getCallOrNewNodeForTarget(n); if (NodeUtil.isExpressionResultUsed(callNode)) { // At least one call site uses the return value, this // is not a candidate. decisionsLog.log("%s\treturn value used: %s", name, callNode.getLocation()); return false; } seenUse = true; } else if (isCandidateDefinition(n)) { // NOTE: While is is possible to optimize calls to functions for which we know // only some of the definition are candidates but to keep things simple, only // optimize if all of the definitions are known. seenCandidateDefiniton = true; } else { // If this isn't an non-aliasing reference (typeof, instanceof, etc) // then there is nothing that can be done. if (!OptimizeCalls.isAllowedReference(n)) { decisionsLog.log("%s\tdisallowed reference: %s", name, n.getLocation()); return false; } } } if (!seenUse) { decisionsLog.log("%s\tno usage seen", name); return false; } if (!seenCandidateDefiniton) { decisionsLog.log("%s\tno definition seen", name); return false; } return true; } private boolean isCandidateDefinition(Node n) { Node parent = n.getParent(); if (parent.isFunction() && NodeUtil.isFunctionDeclaration(parent)) { return true; } else if (ReferenceMap.isSimpleAssignmentTarget(n)) { if (isCandidateFunction(parent.getLastChild())) { return true; } } else if (n.isName()) { if (n.hasChildren() && isCandidateFunction(n.getFirstChild())) { return true; } } else if (isClassMemberDefinition(n)) { return true; } return false; } private boolean isClassMemberDefinition(Node n) { return n.isMemberFunctionDef() && n.getParent().isClassMembers(); } private static boolean isCandidateFunction(Node n) { switch (n.getToken()) { case FUNCTION: // Named function expression can be recursive, this creates an alias of the name, meaning // it might be used in an unexpected way. return !NodeUtil.isNamedFunctionExpression(n); case COMMA: case CAST: return isCandidateFunction(n.getLastChild()); case HOOK: return isCandidateFunction(n.getSecondChild()) && isCandidateFunction(n.getLastChild()); case OR: case AND: case COALESCE: return isCandidateFunction(n.getFirstChild()) && isCandidateFunction(n.getLastChild()); default: return false; } } /** * For the supplied function node, rewrite all the return expressions so that: return foo(); * becomes: foo(); return; Useless return will be removed later by the peephole optimization * passes. */ private void rewriteReturns(Node fnNode) { checkState(fnNode.isFunction()); final Node body = fnNode.getLastChild(); NodeUtil.visitPostOrder( body, new NodeUtil.Visitor() { @Override public void visit(Node n) { if (n.isReturn() && n.hasOneChild()) { Node result = n.getFirstChild(); boolean keepValue = !isRemovableValue(result); result.detach(); if (keepValue) { n.getParent().addChildBefore(IR.exprResult(result).srcref(result), n); } else { NodeUtil.markFunctionsDeleted(result, compiler); } compiler.reportChangeToEnclosingScope(body); } } }, new NodeUtil.MatchShallowStatement()); } // Just remove objects that don't reference properties (object literals) or names (functions) // So we don't need to update the graph. private boolean isRemovableValue(Node n) { switch (n.getToken()) { case TEMPLATELIT: case ARRAYLIT: for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if ((!child.isEmpty()) && !isRemovableValue(child)) { return false; } } return true; case REGEXP: case STRING: case NUMBER: case NULL: case TRUE: case FALSE: case TEMPLATELIT_STRING: return true; case TEMPLATELIT_SUB: case CAST: case NOT: case VOID: case NEG: return isRemovableValue(n.getFirstChild()); default: return false; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy