/** Checks if the given function matches the criteria for an inlinable function. */ private boolean isCandidateFunction(Function fn) { // Don't inline exported functions. String fnName = fn.getName(); if (compiler.getCodingConvention().isExported(fnName)) { // TODO(johnlenz): Should we allow internal references to be inlined? // An exported name can be replaced externally, any inlined instance // would not reflect this change. // To allow inlining we need to be able to distinguish between exports // that are used in a read-only fashion and those that can be replaced // by external definitions. return false; } // Don't inline this special function if (RenameProperties.RENAME_PROPERTY_FUNCTION_NAME.equals(fnName)) { return false; } // Don't inline if we are specializing and the function can't be fixed up if (specializationState != null && !specializationState.canFixupFunction(fn.getFunctionNode())) { return false; } Node fnNode = fn.getFunctionNode(); return injector.doesFunctionMeetMinimumRequirements(fnName, fnNode); }
@Override public void process(Node externs, Node root) { Preconditions.checkState(compiler.getLifeCycleStage().isNormalized()); NodeTraversal.traverseEs6(compiler, root, new FindCandidateFunctions()); if (fns.isEmpty()) { return; // Nothing left to do. } NodeTraversal.traverseEs6(compiler, root, new FindCandidatesReferences(fns, anonFns)); trimCandidatesNotMeetingMinimumRequirements(); if (fns.isEmpty()) { return; // Nothing left to do. } // Store the set of function names eligible for inlining and use this to // prevent function names from being moved into temporaries during // expression decomposition. If this movement were allowed it would prevent // the Inline callback from finding the function calls. // // This pass already assumes these are constants, so this is safe for anyone // using function inlining. // Set<String> fnNames = new HashSet<>(fns.keySet()); injector.setKnownConstants(fnNames); trimCandidatesUsingOnCost(); if (fns.isEmpty()) { return; // Nothing left to do. } resolveInlineConflicts(); decomposeExpressions(); NodeTraversal.traverseEs6(compiler, root, new CallVisitor(fns, anonFns, new Inline(injector))); removeInlinedFunctions(); }
/** @return Whether inlining the function reduces code size. */ private boolean inliningLowersCost(FunctionState fs) { return injector.inliningLowersCost( fs.getModule(), fs.getFn().getFunctionNode(), fs.getReferences(), fs.getNamesToAlias(), fs.canRemove(), fs.getReferencesThis()); }
/** * For any call-site that needs it, prepare the call-site for inlining by rewriting the containing * expression. */ private void decomposeExpressions() { for (FunctionState fs : fns.values()) { if (fs.canInline()) { for (Reference ref : fs.getReferences()) { if (ref.requiresDecomposition) { injector.maybePrepareCall(ref); } } } } }
/** Inline a function into the call site. */ private void inlineFunction(NodeTraversal t, Reference ref, FunctionState fs) { Function fn = fs.getFn(); String fnName = fn.getName(); Node fnNode = fs.getSafeFnNode(); Node newExpr = injector.inline(ref, fnName, fnNode); if (!newExpr.isEquivalentTo(ref.callNode)) { t.getCompiler().reportChangeToEnclosingScope(newExpr); } t.getCompiler().addToDebugLog("Inlined function: " + fn.getName()); }
/** * 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); } } } }