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

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

/*
 * Copyright 2010 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.collect.ImmutableSet;
import com.google.javascript.jscomp.NodeTraversal.ScopedCallback;
import com.google.javascript.rhino.Node;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;

/**
 * A root pass that container for other passes that should run on
 * with a single call graph (currently a DefinitionUseSiteFinder).
 * Expected passes include:
 *   - optimize parameters (unused and constant parameters)
 *   - optimize returns (unused)
 *
 * @author [email protected] (John Lenz)
 */
class OptimizeCalls implements CompilerPass {
  private final List passes = new ArrayList<>();
  private final AbstractCompiler compiler;

  OptimizeCalls(AbstractCompiler compiler) {
    this.compiler = compiler;
  }

  interface CallGraphCompilerPass {
    void process(Node externs, Node root, ReferenceMap references);
  }

  OptimizeCalls addPass(CallGraphCompilerPass pass) {
    passes.add(pass);
    return this;
  }

  @Override
  public void process(Node externs, Node root) {
    if (!passes.isEmpty()) {
      ReferenceMap refMap = buildPropAndGlobalNameReferenceMap(
          compiler, externs, root);
      for (CallGraphCompilerPass pass : passes) {
        pass.process(externs, root, refMap);
      }
    }
  }

  /**
   * A reference map for global symbols and properties.
   */
  static class ReferenceMap {
    private Scope globalScope;
    private final LinkedHashMap> names = new LinkedHashMap<>();
    private final LinkedHashMap> props = new LinkedHashMap<>();

    private void addReference(LinkedHashMap> data, String name, Node n) {
      ArrayList refs = data.get(name);
      if (refs == null) {
        refs = new ArrayList<>();
        data.put(name, refs);
      }
      refs.add(n);
    }

    void addNameReference(String name, Node n) {
      addReference(names, name, n);
    }

    void addPropReference(String name, Node n) {
      addReference(props, name, n);
    }

    Scope getGlobalScope() {
      return globalScope;
    }

    Iterable>> getNameReferences() {
      return names.entrySet();
    }

    Iterable>> getPropReferences() {
      return props.entrySet();
    }

    /**
     * Given a set of references, returns the set of known definitions. Specifically,
     * those of the form:
     *    function x() { }
     * or
     *    x = ...;
     *
     * As much as possible, functions are collected from conditional definitions. This
     * is useful for optimizations that can be performed when the callers are known but
     * all definitions may not be (unused call results, parameters that are never provided).
     * Examples expressions:
     *
     *   (a(), function() {})
     *   a && function(){}
     *   b || function(){}
     *   a ? function() {} : function() {}
     *
     */
    static ArrayList getFunctionNodes(List references) {
      ArrayList fns = new ArrayList<>();
      for (Node n : references) {
        addDefinitionFunctionNodes(fns, n);
      }
      return fns;
    }

    private static void addDefinitionFunctionNodes(ArrayList fns, Node n) {
      Node parent = n.getParent();
      if (parent != null) {
        switch (parent.getToken()) {
          case FUNCTION:
            fns.add(parent);
            break;
          case CLASS_MEMBERS:
            if (n.isMemberFunctionDef()) {
              fns.add(n.getLastChild());
            }
            break;
          case ASSIGN:
            // Only a candidate if the assign isn't consumed.
            Node target = parent.getFirstChild();
            Node value = parent.getLastChild();
            if (n == target) {
              addValueFunctionNodes(fns, value);
            }
            break;
          case CONST:
          case LET:
          case VAR:
            if (n.isName() && n.hasChildren()) {
              addValueFunctionNodes(fns, n.getFirstChild());
            }
            break;
          default:
            break;
        }
      }
    }

    private static void addValueFunctionNodes(ArrayList fns, Node n) {
      // TODO(johnlenz): add member definitions
      switch (n.getToken()) {
        case FUNCTION:
          fns.add(n);
          break;
        case HOOK:
          addValueFunctionNodes(fns, n.getSecondChild());
          addValueFunctionNodes(fns, n.getLastChild());
          break;
        case OR:
        case AND:
          addValueFunctionNodes(fns, n.getFirstChild());
          addValueFunctionNodes(fns, n.getLastChild());
          break;
        case CAST:
        case COMMA:
          addValueFunctionNodes(fns, n.getLastChild());
          break;

        default:
          // do nothing.
          break;
      }
    }

    /**
     * Whether the provided node acts as the target function in a new or call expression including
     * .call expressions.  For example, returns true for 'x' in 'x.call()'.
     */
    static boolean isCallOrNewTarget(Node n) {
      return isCallTarget(n) || isNewTarget(n);
    }

    /**
     * Whether the provided node acts as the target function in a call expression including
     * .call expressions.  For example, returns true for 'x' in 'x.call()'.
     */
    static boolean isCallTarget(Node n) {
      Node parent = n.getParent();
      return ((parent.getFirstChild() == n) && parent.isCall())
          || (parent.isGetProp()
              && parent.getParent().isCall()
              && parent.getLastChild().getString().equals("call"));
    }

    /**
     * Whether the provided node acts as the target function in a new expression.
     */
    static boolean isNewTarget(Node n) {
      Node parent = n.getParent();
      return parent.isNew() && parent.getFirstChild() == n;
    }

    /**
     * Finds the associated call node for a node for which isCallOrNewTarget returns true.
     */
    static Node getCallOrNewNodeForTarget(Node n) {
      Node maybeCall = n.getParent();
      checkState(maybeCall.getFirstChild() == n);
      if (NodeUtil.isCallOrNew(maybeCall)) {
        return maybeCall;
      } else {
        Node child = maybeCall;
        maybeCall = child.getParent();
        checkState(
            child.isGetProp() && maybeCall.isCall() && maybeCall.getFirstChild() == child, child);
        return maybeCall;
      }
    }

    /**
     * Finds the call argument node matching the first parameter of the called function for a node
     * for which isCallOrNewTarget returns true.  Specifically, corrects for the additional
     * argument provided to .call expressions.
     */
    static Node getFirstArgumentForCallOrNewOrDotCall(Node n) {
      return getArgumentForCallOrNewOrDotCall(n, 0);
    }

    /**
     * Finds the call argument node matching the parameter at the specified index of the called
     * function for a node for which isCallOrNewTarget returns true.  Specifically, corrects for
     * the additional argument provided to .call expressions.
     */
    static Node getArgumentForCallOrNewOrDotCall(Node n, int index) {
      int adjustedIndex = index;
      Node parent = n.getParent();
      if (!(parent.isCall() || parent.isNew())) {
        parent = parent.getParent();
        if (NodeUtil.isFunctionObjectCall(parent)) {
          adjustedIndex++;
        }
      }
      return NodeUtil.getArgumentForCallOrNew(parent, adjustedIndex);
    }

    static boolean isSimpleAssignmentTarget(Node n) {
      Node parent = n.getParent();
      return parent.isAssign() && n == parent.getFirstChild();
    }
  }

  private static Set safeSet(@Nullable Set set) {
    return (set != null) ? ImmutableSet.copyOf(set) : ImmutableSet.of();
  }

  static class ReferenceMapBuildingCallback implements ScopedCallback {
    AbstractCompiler compiler;
    final Set externProps;
    final ReferenceMap references;
    private Scope globalScope;

    /**
     * @param compiler
     * @param references
     */
    public ReferenceMapBuildingCallback(AbstractCompiler compiler, ReferenceMap references) {
      this.compiler = compiler;
      this.externProps = safeSet(compiler.getExternProperties());
      this.references = references;
    }

    @Override
    public void visit(NodeTraversal t, Node n, Node parent) {
      switch (n.getToken()) {
        case NAME:
          maybeAddNameReference(n);
          break;

        case COMPUTED_PROP:
          // TODO(johnlenz): support symbols.
          break;
        case GETELEM:
          // ignore quoted keys.
          break;
        case GETPROP:
          maybeAddPropReference(n.getLastChild().getString(), n);
          break;
        case STRING_KEY:
        case GETTER_DEF:
        case SETTER_DEF:
        case MEMBER_FUNCTION_DEF:
          // ignore quoted keys.
          if (!n.isQuotedString()) {
            maybeAddPropReference(n.getString(), n);
          }
          break;

        // TODO(johnlenz): object destructuring.

        default:
          break;
      }
    }

    private void maybeAddNameReference(Node n) {
      String name = n.getString();
      if (isGlobalNonExternNameReference(name)) {
        references.addNameReference(name, n);
      }
    }

    private void maybeAddPropReference(String name, Node n) {
      if (!externProps.contains(name)) {
        references.addPropReference(name, n);
      }
    }

    // As every name declaration is unique due to normalizations, it is only necessary to build
    // the global scope and ask it if it knows about a name as it can never be shadowed.
    private boolean isGlobalNonExternNameReference(String name) {
      Var v = globalScope.getSlot(name);
      return  v != null && !v.isExtern();
    }

    @Override
    public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
      return !n.isScript() || !t.getInput().isExtern();
    }

    @Override
    public void enterScope(NodeTraversal t) {
      if (t.inGlobalScope()) {
        this.globalScope = t.getScope();
        references.globalScope = this.globalScope;
      }
    }

    @Override
    public void exitScope(NodeTraversal t) {
    }
  }

  static ReferenceMap buildPropAndGlobalNameReferenceMap(
      AbstractCompiler compiler, Node externs, Node root) {
    final ReferenceMap references = new ReferenceMap();
    NodeTraversal.traverseRoots(compiler, new ReferenceMapBuildingCallback(
        compiler, references), externs, root);
    return references;
  }

  /**
   * @return Whether the provide name may be a candidate for
   *    call optimizations.
   */
  static boolean mayBeOptimizableName(AbstractCompiler compiler, String name) {
    if (compiler.getCodingConvention().isExported(name)) {
      return false;
    }

    // Avoid modifying a few special case functions. Specifically, $jscomp.inherits to
    // recognize 'inherits' calls. (b/27244988)
    if (name.equals(NodeUtil.JSC_PROPERTY_NAME_FN)
        || name.equals(NodeUtil.EXTERN_OBJECT_PROPERTY_STRING)
        || name.equals("inherits")
        || name.equals("$jscomp$inherits")
        || name.equals("goog$inherits")) {
      return false;
    }
    return true;
  }

  /**
   * @return Whether the reference is a known non-aliasing reference.
   */
  static boolean isAllowedReference(Node n) {
    Node parent = n.getParent();
    switch (parent.getToken()) {
      case FOR_IN:
      case FOR_OF:
        // inspecting the properties is allowed.
        return parent.getSecondChild() == n;
      case INSTANCEOF:
      case TYPEOF:
      case IN:
        return true;
      case GETELEM:
      case GETPROP:
        // Calls escape the "this" value. a.foo() aliases "a" as "this" but general
        // property references do not.
        Node grandparent = parent.getParent();
        if (n == parent.getFirstChild() && grandparent != null && grandparent.isCall()) {
          return false;
        }
        return true;
      default:
        if (NodeUtil.isNameDeclaration(parent) && !n.hasChildren()) {
          // allow "let x;"
          return true;
        }
    }
    return false;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy