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

com.google.javascript.jscomp.OptimizeArgumentsArray 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.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSortedMap;
import com.google.javascript.jscomp.NodeTraversal.ScopedCallback;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.Node;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import javax.annotation.Nullable;

/**
 * Optimization for functions that have {@code var_args} or access the
 * arguments array.
 *
 * 

Example: *

 * function() { alert(arguments[0] + argument[1]) }
 * 
* to: *
 * function(a, b) { alert(a, b) }
 * 
* * Each newly inserted variable name will be unique very much like the output * of the AST found after the {@link Normalize} pass. */ class OptimizeArgumentsArray implements CompilerPass, ScopedCallback { // The arguments object as described by ECMAScript version 3 section 10.1.8 private static final String ARGUMENTS = "arguments"; // To ensure that the newly introduced parameter names are unique. We will // use this string as prefix unless the caller specifies a different prefix. private static final String PARAMETER_PREFIX = "JSCompiler_OptimizeArgumentsArray_p"; // The prefix for the newly introduced parameter name. private final String paramPrefix; // To make each parameter name unique in the function we append a unique integer. private int uniqueId = 0; // Reference to the compiler object to notify any changes to source code AST. private final AbstractCompiler compiler; // A stack of arguments access list to the corresponding outer functions. private final Deque> argumentsAccessStack = new ArrayDeque<>(); // The `arguments` access in the current scope. // // The elements are NAME nodes. This initial value is a error-detecting sentinel for the global // scope, which is used because since `ArrayDeque` is null-hostile. private List currentArgumentsAccesses = ImmutableList.of(); /** * Construct this pass and use {@link #PARAMETER_PREFIX} as the prefix for * all parameter names that it introduces. */ OptimizeArgumentsArray(AbstractCompiler compiler) { this(compiler, PARAMETER_PREFIX); } /** * @param paramPrefix the prefix to use for all parameter names that this * pass introduces */ OptimizeArgumentsArray(AbstractCompiler compiler, String paramPrefix) { this.compiler = checkNotNull(compiler); this.paramPrefix = checkNotNull(paramPrefix); } @Override public void process(Node externs, Node root) { NodeTraversal.traverse(compiler, checkNotNull(root), this); } @Override public void enterScope(NodeTraversal traversal) { if (!definesArgumentsVar(traversal.getScopeRoot())) { return; } // Introduces a new access list and stores the access list of the outer scope. argumentsAccessStack.push(currentArgumentsAccesses); currentArgumentsAccesses = new ArrayList<>(); } @Override public void exitScope(NodeTraversal traversal) { if (!definesArgumentsVar(traversal.getScopeRoot())) { return; } // Attempt to replace the argument access and if the AST has been change, // report back to the compiler. tryReplaceArguments(traversal.getScopeRoot()); currentArgumentsAccesses = argumentsAccessStack.pop(); } private static boolean definesArgumentsVar(Node root) { return root.isFunction() && !root.isArrowFunction(); } @Override public boolean shouldTraverse(NodeTraversal unused0, Node unused1, Node unused2) { return true; } @Override public void visit(NodeTraversal traversal, Node node, Node parent) { if (traversal.inGlobalHoistScope()) { return; // Do no rewriting in the global scope. } if (node.isName() && ARGUMENTS.equals(node.getString())) { currentArgumentsAccesses.add(node); // Record all potential references to the arguments array. } } /** * Tries to optimize all the arguments array access in this scope by assigning a name to each * element. * * @param scope scope of the function */ private void tryReplaceArguments(Node scopeRoot) { // Find the number of parameters that can be accessed without using `arguments`. Node parametersList = NodeUtil.getFunctionParameters(scopeRoot); checkState(parametersList.isParamList(), parametersList); int numParameters = parametersList.getChildCount(); // Determine the highest index that is used to make an access on `arguments`. By default, assume // that the value is the number of parameters to the function. int highestIndex = getHighestIndex(numParameters - 1); if (highestIndex < 0) { return; } ImmutableSortedMap argNames = assembleParamNames(parametersList, highestIndex + 1); changeMethodSignature(argNames, parametersList); changeBody(argNames); } /** * Iterate through all the references to arguments array in the * function to determine the real highestIndex. Returns -1 when we should not * be replacing any arguments for this scope - we should exit tryReplaceArguments * * @param highestIndex highest index that has been accessed from the arguments array */ private int getHighestIndex(int highestIndex) { for (Node ref : currentArgumentsAccesses) { Node getElem = ref.getParent(); // Bail on anything but argument[c] access where c is a constant. // TODO(user): We might not need to bail out all the time, there might // be more cases that we can cover. if (!getElem.isGetElem() || ref != getElem.getFirstChild()) { return -1; } Node index = ref.getNext(); // We have something like arguments[x] where x is not a constant. That // means at least one of the access is not known. if (!index.isNumber() || index.getDouble() < 0) { // TODO(user): Its possible not to give up just yet. The type // inference did a 'semi value propagation'. If we know that string // is never a subclass of the type of the index. We'd know that // it is never 'callee'. return -1; // Give up. } //We want to bail out if someone tries to access arguments[0.5] for example if (index.getDouble() != Math.floor(index.getDouble())){ return -1; } Node getElemParent = getElem.getParent(); // When we have argument[0](), replacing it with a() is semantically // different if argument[0] is a function call that refers to 'this' if (getElemParent.isCall() && getElemParent.getFirstChild() == getElem) { // TODO(user): We can consider using .call() if aliasing that // argument allows shorter alias for other arguments. return -1; } // Replace the highest index if we see an access that has a higher index // than all the one we saw before. int value = (int) index.getDouble(); if (value > highestIndex) { highestIndex = value; } } return highestIndex; } /** * Inserts new formal parameters into the method's signature based on the given set of names. * *

Example: function() --> function(r0, r1, r2) * * @param argNames maps param index to param name, if the param with that index has a name. * @param paramList node representing the function signature */ private void changeMethodSignature(ImmutableSortedMap argNames, Node paramList) { ImmutableSortedMap newParams = argNames.tailMap(paramList.getChildCount()); for (String name : newParams.values()) { paramList.addChildToBack(IR.name(name).useSourceInfoIfMissingFrom(paramList)); } if (!newParams.isEmpty()) { compiler.reportChangeToEnclosingScope(paramList); } } /** * Performs the replacement of arguments[x] -> a if x is known. * * @param argNames maps param index to param name, if the param with that index has a name. */ private void changeBody(ImmutableMap argNames) { for (Node ref : currentArgumentsAccesses) { Node index = ref.getNext(); Node parent = ref.getParent(); int value = (int) index.getDouble(); // This was validated earlier. @Nullable String name = argNames.get(value); if (name == null) { continue; } Node newName = IR.name(name).useSourceInfoIfMissingFrom(parent); parent.replaceWith(newName); // TODO(nickreid): See if we can do this fewer times. The accesses may be in different scopes. compiler.reportChangeToEnclosingScope(newName); } } /** * Generates a {@link Map} from argument indices to parameter names. * *

A {@link Map} is used because the sequence may be sparse in the case that there is an * anonymous param, such as a destructuring param. There may also be fewer returned names than * {@code maxCount} if there is a rest param, since no additional params may be synthesized. * * @param maxCount The maximum number of argument names in the returned map. */ private ImmutableSortedMap assembleParamNames(Node paramList, int maxCount) { checkArgument(paramList.isParamList(), paramList); ImmutableSortedMap.Builder builder = ImmutableSortedMap.naturalOrder(); int index = 0; // Collect all existing param names first... for (Node param = paramList.getFirstChild(); param != null; param = param.getNext()) { switch (param.getToken()) { case NAME: builder.put(index, param.getString()); break; case ITER_REST: return builder.build(); case DEFAULT_VALUE: // `arguments` doesn't consider default values. It holds exactly the provided args. case OBJECT_PATTERN: case ARRAY_PATTERN: // Patterns have no names to substitute into the body. break; default: throw new IllegalArgumentException(param.toString()); } index++; } // ... then synthesize any additional param names. for (; index < maxCount; index++) { builder.put(index, paramPrefix + uniqueId++); } return builder.build(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy