@Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { // Function calls case Token.CALL: Node child = n.getFirstChild(); String name = null; // NOTE: The normalization pass insures that local names do not // collide with global names. if (child.isName()) { name = child.getString(); } else if (child.isFunction()) { name = anonFunctionMap.get(child); } else if (NodeUtil.isFunctionObjectCall(n)) { Preconditions.checkState(NodeUtil.isGet(child)); Node fnIdentifingNode = child.getFirstChild(); if (fnIdentifingNode.isName()) { name = fnIdentifingNode.getString(); } else if (fnIdentifingNode.isFunction()) { name = anonFunctionMap.get(fnIdentifingNode); } } if (name != null) { FunctionState fs = functionMap.get(name); // Only visit call-sites for functions that can be inlined. if (fs != null) { callback.visitCallSite(t, n, fs); } } break; } }
/** * Returns the nth argument node given a usage site for a direct function call or for a * func.call() node. */ private static Node getArgumentForCallOrNewOrDotCall(UseSite site, final int argIndex) { int adjustedArgIndex = argIndex; Node parent = site.node.getParent(); if (NodeUtil.isFunctionObjectCall(parent)) { adjustedArgIndex++; } return NodeUtil.getArgumentForCallOrNew(parent, adjustedArgIndex); }
/** * Determines whether a function can be inlined at a particular call site. There are several * criteria that the function and reference must hold in order for the functions to be inlined: 1) * If a call's arguments have side effects, the corresponding argument in the function must only * be referenced once. For instance, this will not be inlined: * * <pre> * function foo(a) { return a + a } * x = foo(i++); * </pre> */ private CanInlineResult canInlineReferenceDirectly(Node callNode, Node fnNode) { if (!isDirectCallNodeReplacementPossible(fnNode)) { return CanInlineResult.NO; } Node block = fnNode.getLastChild(); // CALL NODE: [ NAME, ARG1, ARG2, ... ] Node cArg = callNode.getFirstChild().getNext(); // Functions called via 'call' and 'apply' have a this-object as // the first parameter, but this is not part of the called function's // parameter list. if (callNode.getFirstChild().getType() != Token.NAME) { if (NodeUtil.isFunctionObjectCall(callNode)) { // TODO(johnlenz): Support replace this with a value. Preconditions.checkNotNull(cArg); Preconditions.checkState(cArg.getType() == Token.THIS); cArg = cArg.getNext(); } else { // ".apply" call should be filtered before this. Preconditions.checkState(!NodeUtil.isFunctionObjectApply(callNode)); } } // FUNCTION NODE -> LP NODE: [ ARG1, ARG2, ... ] Node fnParam = NodeUtil.getFnParameters(fnNode).getFirstChild(); while (cArg != null || fnParam != null) { // For each named parameter check if a mutable argument use more than one. if (fnParam != null) { if (cArg != null) { // Check for arguments that are evaluated more than once. // Note: Unlike block inlining, there it is not possible that a // parameter reference will be in a loop. if (NodeUtil.mayEffectMutableState(cArg) && NodeUtil.getNameReferenceCount(block, fnParam.getString()) > 1) { return CanInlineResult.NO; } } // Move to the next name. fnParam = fnParam.getNext(); } // For every call argument check for side-effects, even if there // isn't a named parameter to match. if (cArg != null) { if (NodeUtil.mayHaveSideEffects(cArg)) { return CanInlineResult.NO; } cArg = cArg.getNext(); } } return CanInlineResult.YES; }
/** * Only ".call" calls and direct calls to functions are supported. * * @param callNode The call evaluate. * @return Whether the call is of a type that is supported. */ private boolean isSupportedCallType(Node callNode) { if (callNode.getFirstChild().getType() != Token.NAME) { if (NodeUtil.isFunctionObjectCall(callNode)) { Node thisValue = callNode.getFirstChild().getNext(); if (thisValue == null || thisValue.getType() != Token.THIS) { return false; } } else if (NodeUtil.isFunctionObjectApply(callNode)) { return false; } } return true; }
/** * @return Whether the definitionSite represents a function whose call signature can be * modified. */ private boolean canChangeSignature(Node function) { Definition definition = getFunctionDefinition(function); CodingConvention convention = compiler.getCodingConvention(); Preconditions.checkState(!definition.isExtern()); Collection<UseSite> useSites = defFinder.getUseSites(definition); for (UseSite site : useSites) { Node parent = site.node.getParent(); // This was a use site removed by something else before we run. // 1. By another pass before us which means the definition graph is // no updated properly. // 2. By the continuations algorithm above. if (parent == null) { continue; // Ignore it. } // Ignore references within goog.inherits calls. if (NodeUtil.isCall(parent) && convention.getClassesDefinedByCall(parent) != null) { continue; } // Accessing the property directly prevents rewrite. if (!SimpleDefinitionFinder.isCallOrNewSite(site)) { if (!(NodeUtil.isGetProp(parent) && NodeUtil.isFunctionObjectCall(parent.getParent()))) { return false; } } if (NodeUtil.isFunctionObjectApply(parent)) { return false; } // TODO(johnlenz): support specialization // Multiple definitions prevent rewrite. // Attempt to validate the state of the simple definition finder. Node nameNode = site.node; Collection<Definition> singleSiteDefinitions = defFinder.getDefinitionsReferencedAt(nameNode); Preconditions.checkState(singleSiteDefinitions.size() == 1); Preconditions.checkState(singleSiteDefinitions.contains(definition)); } return true; }
/** * @param t The traversal use to reach the call site. * @param callNode The CALL node. * @param fnNode The function to evaluate for inlining. * @param needAliases A set of function parameter names that can not be used without aliasing. * Returned by getUnsafeParameterNames(). * @param mode Inlining mode to be used. * @param referencesThis Whether fnNode contains references to its this object. * @param containsFunctions Whether fnNode contains inner functions. * @return Whether the inlining can occur. */ CanInlineResult canInlineReferenceToFunction( NodeTraversal t, Node callNode, Node fnNode, Set<String> needAliases, InliningMode mode, boolean referencesThis, boolean containsFunctions) { // TODO(johnlenz): This function takes too many parameter, without // context. Modify the API to take a structure describing the function. // Allow direct function calls or "fn.call" style calls. if (!isSupportedCallType(callNode)) { return CanInlineResult.NO; } // Limit where functions that contain functions can be inline. Introducing // an inner function into another function can capture a variable and cause // a memory leak. This isn't a problem in the global scope as those values // last until explicitly cleared. if (containsFunctions && !t.inGlobalScope()) { // TODO(johnlenz): Allow inlining into any scope without local names or // inner functions. return CanInlineResult.NO; } // TODO(johnlenz): Add support for 'apply' if (referencesThis && !NodeUtil.isFunctionObjectCall(callNode)) { // TODO(johnlenz): Allow 'this' references to be replaced with a // global 'this' object. return CanInlineResult.NO; } if (mode == InliningMode.DIRECT) { return canInlineReferenceDirectly(callNode, fnNode); } else { return canInlineReferenceAsStatementBlock(t, callNode, fnNode, needAliases); } }
/** * Find function expressions that are called directly in the form of * (function(a,b,...){...})(a,b,...) or (function(a,b,...){...}).call(this,a,b, ...) */ public void findFunctionExpressions(NodeTraversal t, Node n) { switch (n.getType()) { // Functions expressions in the form of: // (function(){})(); case Token.CALL: Node fnNode = null; if (n.getFirstChild().isFunction()) { fnNode = n.getFirstChild(); } else if (NodeUtil.isFunctionObjectCall(n)) { Node fnIdentifingNode = n.getFirstChild().getFirstChild(); if (fnIdentifingNode.isFunction()) { fnNode = fnIdentifingNode; } } // If a interesting function was discovered, add it. if (fnNode != null) { Function fn = new FunctionExpression(fnNode, callsSeen++); maybeAddFunction(fn, t.getModule()); anonFns.put(fnNode, fn.getName()); } break; } }