/** @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());
    }
  }
    @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;
        }
      }
    }
 /** 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();
     }
   }
 }
 /**
  * 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);
         }
       }
     }
   }
 }
 /** 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();
     }
   }
 }
  /** 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();
      }
    }
  }
    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);
      }
    }
  /**
   * 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);
        }
      }
    }
  }