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);
        }
      }
    }
  }