private boolean maybeAddReferenceUsingMode( NodeTraversal t, FunctionState fs, Node callNode, JSModule module, InliningMode mode) { // If many functions are inlined into the same function F in the same // inlining round, then the size of F may exceed the max size. // This could be avoided if we bail later, during the inlining phase, eg, // in Inline#visitCallSite. However, that is not safe, because at that // point expression decomposition has already run, and we want to // decompose expressions only for the calls that are actually inlined. if (enforceMaxSizeAfterInlining && targetSizeAfterInlineExceedsLimit(t, fs)) { return false; } Reference candidate = new Reference(callNode, t.getScope(), module, mode); CanInlineResult result = injector.canInlineReferenceToFunction( candidate, fs.getFn().getFunctionNode(), fs.getNamesToAlias(), fs.getReferencesThis(), fs.hasInnerFunctions()); if (result != CanInlineResult.NO) { // Yeah! candidate.setRequiresDecomposition(result == CanInlineResult.AFTER_PREPARATION); fs.addReference(candidate); return true; } return false; }
@Override public void visitCallSite(NodeTraversal t, Node callNode, FunctionState fs) { Preconditions.checkState(fs.hasExistingFunctionDefinition()); if (fs.canInline()) { Reference ref = fs.getReference(callNode); // There are two cases ref can be null: if the call site was introduced // because it was part of a function that was inlined during this pass // or if the call site was trimmed from the list of references because // the function couldn't be inlined at this location. if (ref != null) { if (specializationState != null) { Node containingFunction = getContainingFunction(t); if (containingFunction != null) { // Report that the function was specialized so that // {@link SpecializeModule} can fix it up. specializationState.reportSpecializedFunction(containingFunction); } } inlineFunction(t, ref, fs); // Keep track of references that have been inlined so that // we can verify that none have been missed. ref.inlined = true; } } }
private boolean maybeAddReferenceUsingMode( NodeTraversal t, FunctionState fs, Node callNode, JSModule module, InliningMode mode) { if (specializationState != null) { // If we're specializing, make sure we can fixup // the containing function before inlining Node containingFunction = getContainingFunction(t); if (containingFunction != null && !specializationState.canFixupFunction(containingFunction)) { return false; } } CanInlineResult result = injector.canInlineReferenceToFunction( t, callNode, fs.getFn().getFunctionNode(), fs.getNamesToAlias(), mode, fs.getReferencesThis(), fs.hasInnerFunctions()); if (result != CanInlineResult.NO) { // Yeah! boolean decompose = (result == CanInlineResult.AFTER_PREPARATION); fs.addReference(new Reference(callNode, module, mode, decompose)); return true; } return false; }
/** Remove entries that aren't a valid inline candidates, from the list of encountered names. */ private void trimCandidatesNotMeetingMinimumRequirements() { Iterator<Entry<String, FunctionState>> i; for (i = fns.entrySet().iterator(); i.hasNext(); ) { FunctionState fs = i.next().getValue(); if (!fs.hasExistingFunctionDefinition() || !fs.canInline()) { i.remove(); } } }
/** @see #resolveInlineConflicts */ private void resolveInlineConflictsForFunction(FunctionState fs) { // Functions that aren't referenced don't cause conflicts. if (!fs.hasReferences() || !fs.canInline()) { return; } Node fnNode = fs.getFn().getFunctionNode(); Set<String> names = findCalledFunctions(fnNode); if (!names.isEmpty()) { // Prevent the removal of the referenced functions. for (String name : names) { FunctionState fsCalled = fns.get(name); if (fsCalled != null && fsCalled.canRemove()) { fsCalled.setRemove(false); // For functions that can no longer be removed, check if they should // still be inlined. if (!mimimizeCost(fsCalled)) { // It can't be inlined remove it from the list. fsCalled.setInline(false); } } } // Make a copy of the Node, so it isn't changed by other inlines. fs.setSafeFnNode(fs.getFn().getFunctionNode().cloneTree()); } }
/** Removed inlined functions that no longer have any references. */ void removeInlinedFunctions() { for (FunctionState fs : fns.values()) { if (fs.canRemove()) { Function fn = fs.getFn(); Preconditions.checkState(fs.canInline()); Preconditions.checkState(fn != null); verifyAllReferencesInlined(fs); fn.remove(); } } }
/** 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()); }
/** * 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); } } } } }
/** * Determines if the function is worth inlining and potentially trims references that increase the * cost. * * @return Whether inlining the references lowers the overall cost. */ private boolean mimimizeCost(FunctionState fs) { if (!inliningLowersCost(fs)) { // Try again without Block inlining references if (fs.hasBlockInliningReferences()) { fs.setRemove(false); fs.removeBlockInliningReferences(); if (!fs.hasReferences() || !inliningLowersCost(fs)) { return false; } } else { return false; } } return true; }
/** Removed inlined functions that no longer have any references. */ void removeInlinedFunctions() { for (FunctionState fs : fns.values()) { if (fs.canRemove()) { Function fn = fs.getFn(); Preconditions.checkState(fs.canInline()); Preconditions.checkState(fn != null); verifyAllReferencesInlined(fs); if (specializationState != null) { specializationState.reportRemovedFunction(fn.getFunctionNode(), fn.getDeclaringBlock()); } fn.remove(); } } }
/** Remove entries from the list of candidates that can't be inlined. */ private void trimCandidatesUsingOnCost() { Iterator<Entry<String, FunctionState>> i; for (i = fns.entrySet().iterator(); i.hasNext(); ) { FunctionState fs = i.next().getValue(); if (fs.hasReferences()) { // Only inline function if it decreases the code size. boolean lowersCost = mimimizeCost(fs); if (!lowersCost) { // It shouldn't be inlined; remove it from the list. i.remove(); } } else if (!fs.canRemove()) { // Don't bother tracking functions without references that can't be // removed. i.remove(); } } }
/** Find functions that can be inlined. */ private void checkNameUsage(Node n, Node parent) { Preconditions.checkState(n.isName()); if (isCandidateUsage(n)) { return; } // Other refs to a function name remove its candidacy for inlining String name = n.getString(); FunctionState fs = fns.get(name); if (fs == null) { return; } // Unlike normal call/new parameters, references passed to // JSCompiler_ObjectPropertyString are not aliases of a value, but // a reference to the name itself, as such the value of the name is // unknown and can not be inlined. if (parent.isNew()) { Node target = parent.getFirstChild(); if (target.isName() && target .getString() .equals(ObjectPropertyStringPreprocess.EXTERN_OBJECT_PROPERTY_STRING)) { // This method is going to be replaced so don't inline it anywhere. fs.setInline(false); } } // If the name is being assigned to it can not be inlined. if (parent.isAssign() && parent.getFirstChild() == n) { // e.g. bar = something; <== we can't inline "bar" // so mark the function as uninlinable. // TODO(johnlenz): Should we just remove it from fns here? fs.setInline(false); } else { // e.g. var fn = bar; <== we can't inline "bar" // As this reference can't be inlined mark the function as // unremovable. fs.setRemove(false); } }
/** Sanity check to verify, that expression rewriting didn't make a call inaccessible. */ void verifyAllReferencesInlined(FunctionState fs) { for (Reference ref : fs.getReferences()) { if (!ref.inlined) { throw new IllegalStateException( "Call site missed.\n call: " + ref.callNode.toStringTree() + "\n parent: " + ref.callNode.getParent().toStringTree()); } } }
void maybeAddReference(NodeTraversal t, FunctionState fs, Node callNode, JSModule module) { if (!fs.canInline()) { return; } InliningMode mode = fs.canInlineDirectly() ? InliningMode.DIRECT : InliningMode.BLOCK; boolean referenceAdded = maybeAddReferenceUsingMode(t, fs, callNode, module, mode); if (!referenceAdded && mode == InliningMode.DIRECT && blockFunctionInliningEnabled) { // This reference can not be directly inlined, see if // block replacement inlining is possible. mode = InliningMode.BLOCK; referenceAdded = maybeAddReferenceUsingMode(t, fs, callNode, module, mode); } if (!referenceAdded) { // Don't try to remove a function if we can't inline all // the references. fs.setRemove(false); } }
/** @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()); }
private boolean targetSizeAfterInlineExceedsLimit(NodeTraversal t, FunctionState fs) { Node containingFunction = getContainingFunction(t); // Always inline at the top level, // unless maybeAddFunction has marked fs as not inlinable. if (containingFunction == null) { return false; } Node inlinedFun = fs.getFn().getFunctionNode(); if (isAlwaysInlinable(inlinedFun)) { return false; } int inlinedFunSize = NodeUtil.countAstSizeUpToLimit(NodeUtil.getFunctionBody(inlinedFun), maxSizeAfterInlining); int targetFunSize = NodeUtil.countAstSizeUpToLimit(containingFunction, maxSizeAfterInlining); return inlinedFunSize + targetFunSize > maxSizeAfterInlining; }
/** * 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); } } } }