/** * Determines whether a function can be inlined at a particular call site. - Don't inline if the * calling function contains an inner function and inlining would introduce new globals. */ private boolean callMeetsBlockInliningRequirements( NodeTraversal t, Node callNode, Node fnNode, Set<String> namesToAlias) { // Note: functions that contain function definitions are filtered out // in isCanidateFunction. // TODO(johnlenz): Determining if the called function contains VARs // or if the caller contains inner functions accounts for 20% of the // runtime cost of this pass. // Don't inline functions with var declarations into a scope with inner // functions as the new vars would leak into the inner function and // cause memory leaks. boolean fnContainsVars = NodeUtil.has( NodeUtil.getFunctionBody(fnNode), new NodeUtil.MatchDeclaration(), new NodeUtil.MatchShallowStatement()); boolean callerContainsFunction = false; if (!t.inGlobalScope()) { Node fnCaller = t.getScopeRoot(); Node fnCallerBody = fnCaller.getLastChild(); callerContainsFunction = NodeUtil.containsFunction(fnCallerBody); } if (fnContainsVars && callerContainsFunction) { return false; } // If the caller contains functions, verify we aren't adding any // additional VAR declarations because aliasing is needed. if (callerContainsFunction) { Map<String, Node> args = FunctionArgumentInjector.getFunctionCallParameterMap( fnNode, callNode, this.safeNameIdSupplier); boolean hasArgs = !args.isEmpty(); if (hasArgs) { // Limit the inlining Set<String> allNamesToAlias = Sets.newHashSet(namesToAlias); FunctionArgumentInjector.maybeAddTempsForCallArguments( fnNode, args, allNamesToAlias, compiler.getCodingConvention()); if (!allNamesToAlias.isEmpty()) { return false; } } } return true; }
/** * Updates the FunctionState object for the given function. Checks if the given function matches * the criteria for an inlinable function. */ private void maybeAddFunction(Function fn, JSModule module) { String name = fn.getName(); FunctionState fs = getOrCreateFunctionState(name); // TODO(johnlenz): Maybe "smarten" FunctionState by adding this logic // to it? // If the function has multiple definitions, don't inline it. if (fs.hasExistingFunctionDefinition()) { fs.setInline(false); return; } Node fnNode = fn.getFunctionNode(); if (enforceMaxSizeAfterInlining && !isAlwaysInlinable(fnNode) && maxSizeAfterInlining <= NodeUtil.countAstSizeUpToLimit(fnNode, maxSizeAfterInlining)) { fs.setInline(false); return; } // verify the function hasn't already been marked as "don't inline" if (fs.canInline()) { // store it for use when inlining. fs.setFn(fn); if (FunctionInjector.isDirectCallNodeReplacementPossible(fn.getFunctionNode())) { fs.inlineDirectly(true); } // verify the function meets all the requirements. // TODO(johnlenz): Minimum requirement checks are about 5% of the // run-time cost of this pass. if (!isCandidateFunction(fn)) { // It doesn't meet the requirements. fs.setInline(false); } // Set the module and gather names that need temporaries. if (fs.canInline()) { fs.setModule(module); Set<String> namesToAlias = FunctionArgumentInjector.findModifiedParameters(fnNode); if (!namesToAlias.isEmpty()) { fs.inlineDirectly(false); fs.setNamesToAlias(namesToAlias); } Node block = NodeUtil.getFunctionBody(fnNode); if (NodeUtil.referencesThis(block)) { fs.setReferencesThis(true); } if (NodeUtil.containsFunction(block)) { fs.setHasInnerFunctions(true); // If there are inner functions, we can inline into global scope // if there are no local vars or named functions. // TODO(johnlenz): this can be improved by looking at the possible // values for locals. If there are simple values, or constants // we could still inline. if (!assumeMinimumCapture && hasLocalNames(fnNode)) { fs.setInline(false); } } } // Check if block inlining is allowed. if (fs.canInline() && !fs.canInlineDirectly()) { if (!blockFunctionInliningEnabled) { fs.setInline(false); } } } }