All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.javascript.jscomp.PhaseOptimizer Maven / Gradle / Ivy

Go to download

Closure Compiler is a JavaScript optimizing compiler. It parses your JavaScript, analyzes it, removes dead code and rewrites and minimizes what's left. It also checks syntax, variable references, and types, and warns about common JavaScript pitfalls. It is used in many of Google's JavaScript apps, including Gmail, Google Web Search, Google Maps, and Google Docs.

There is a newer version: v20240317
Show newest version
/*
 * Copyright 2009 The Closure Compiler Authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.javascript.jscomp;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.javascript.rhino.Node;

import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.logging.Logger;

/**
 * Optimizes the order of compiler passes.
 * @author [email protected] (Nick Santos)
 */
class PhaseOptimizer implements CompilerPass {

  // This ordering is computed offline by running with compute_phase_ordering.
  @VisibleForTesting
  static final List OPTIMAL_ORDER = ImmutableList.of(
     "removeUnreachableCode",
     "removeUnusedVars",
     "foldConstants",
     "deadAssignmentsElimination",
     "inlineVariables",
     "inlineFunctions",
     "removeUnusedPrototypeProperties",
     "minimizeExitPoints");

  static final int MAX_LOOPS = 100;
  static final String OPTIMIZE_LOOP_ERROR =
      "Fixed point loop exceeded the maximum number of iterations.";

  private static final Logger logger =
      Logger.getLogger(PhaseOptimizer.class.getName());

  private List passes = Lists.newArrayList();

  private final AbstractCompiler compiler;
  private final PerformanceTracker tracker;
  private final CodeChangeHandler.RecentChange recentChange =
      new CodeChangeHandler.RecentChange();
  private boolean loopMutex = false;
  private Tracer currentTracer = null;
  private String currentPassName = null;
  private PassFactory sanityCheck = null;

  // The following static properties are only used for computing optimal
  // phase orderings. They should not be touched by normal compiler runs.
  private static boolean randomizeLoops = false;
  private static List> loopsRun = Lists.newArrayList();

  PhaseOptimizer(AbstractCompiler compiler, PerformanceTracker tracker) {
    this.compiler = compiler;
    this.tracker = tracker;
    compiler.addChangeHandler(recentChange);
  }

  /**
   * Randomizes loops. This should only be used when computing optimal phase
   * orderings.
   */
  static void randomizeLoops() {
    randomizeLoops = true;
  }

  /**
   * Get the phase ordering of loops during this run.
   * Returns an empty list when the loops are not randomized.
   */
  static List> getLoopsRun() {
    return loopsRun;
  }

  /**
   * Clears the phase ordering of loops during this run.
   */
  static void clearLoopsRun() {
    loopsRun.clear();
  }

  /**
   * Add the passes generated by the given factories to the compile sequence.
   *
   * Automatically pulls multi-run passes into fixed point loops. If there
   * are 2 or more multi-run passes in a row, they will run together in
   * the same fixed point loop. If A and B are in the same fixed point loop,
   * the loop will continue to run both A and B until both are finished
   * making changes.
   *
   * Other than that, the PhaseOptimizer is free to tweak the order and
   * frequency of multi-run passes in a fixed-point loop.
   */
  void consume(List factories) {
    Loop currentLoop = new LoopInternal();
    boolean isCurrentLoopPopulated = false;
    for (PassFactory factory : factories) {
      if (factory.isOneTimePass()) {
        if (isCurrentLoopPopulated) {
          passes.add(currentLoop);

          currentLoop = new LoopInternal();
          isCurrentLoopPopulated = false;
        }
        addOneTimePass(factory);
      } else {
        currentLoop.addLoopedPass(factory);
        isCurrentLoopPopulated = true;
      }
    }

    if (isCurrentLoopPopulated) {
      passes.add(currentLoop);
    }
  }

  /**
   * Add the pass generated by the given factory to the compile sequence.
   * This pass will be run once.
   */
  void addOneTimePass(PassFactory factory) {
    passes.add(new PassFactoryDelegate(compiler, factory));
  }

  /**
   * Add a loop to the compile sequence. This loop will continue running
   * until the AST stops changing.
   * @return The loop structure. Pass suppliers should be added to the loop.
   */
  Loop addFixedPointLoop() {
    Loop loop = new LoopInternal();
    passes.add(loop);
    return loop;
  }

  /**
   * Adds a sanity checker to be run after every pass. Intended for development.
   */
  void setSanityCheck(PassFactory sanityCheck) {
    this.sanityCheck = sanityCheck;
  }

  /**
   * Run all the passes in the optimizer.
   */
  @Override
  public void process(Node externs, Node root) {
    for (CompilerPass pass : passes) {
      pass.process(externs, root);
      if (hasHaltingErrors()) {
        return;
      }
    }
  }

  /**
   * Marks the beginning of a pass.
   */
  private void startPass(String passName) {
    Preconditions.checkState(currentTracer == null && currentPassName == null);
    currentPassName = passName;
    currentTracer = newTracer(passName);
  }

  /**
   * Marks the end of a pass.
   */
  private void endPass(Node externs, Node root) {
    Preconditions.checkState(currentTracer != null && currentPassName != null);

    String passToCheck = currentPassName;
    try {
      stopTracer(currentTracer, currentPassName);
      currentPassName = null;
      currentTracer = null;

      maybeSanityCheck(externs, root);
    } catch (Exception e) {
      // TODO(johnlenz): Remove this once the normalization checks report
      // errors instead of exceptions.
      throw new RuntimeException("Sanity check failed for " + passToCheck, e);
    }
  }

  /**
   * Runs the sanity check if it is available.
   */
  void maybeSanityCheck(Node externs, Node root) {
    if (sanityCheck != null) {
      sanityCheck.create(compiler).process(externs, root);
    }
  }

  private boolean hasHaltingErrors() {
    return compiler.hasHaltingErrors();
  }

  /**
   * Returns a new tracer for the given pass name.
   */
  private Tracer newTracer(String passName) {
    String comment = passName +
        (recentChange.hasCodeChanged() ? " on recently changed AST" : "");
    if (tracker != null) {
      tracker.recordPassStart(passName);
    }
    return new Tracer("JSCompiler", comment);
  }

  private void stopTracer(Tracer t, String passName) {
    long result = t.stop();
    if (tracker != null) {
      tracker.recordPassStop(passName, result);
    }
  }

  /**
   * A single compiler pass.
   */
  private abstract class NamedPass implements CompilerPass {
    private final String name;

    NamedPass(String name) {
      this.name = name;
    }

    @Override
    public void process(Node externs, Node root) {
      logger.info(name);
      startPass(name);
      processInternal(externs, root);
      endPass(externs, root);
    }

    abstract void processInternal(Node externs, Node root);
  }

  /**
   * Delegates to a PassFactory for processing.
   */
  private class PassFactoryDelegate extends NamedPass {
    private final AbstractCompiler myCompiler;
    private final PassFactory factory;

    private PassFactoryDelegate(
        AbstractCompiler myCompiler, PassFactory factory) {
      super(factory.getName());
      this.myCompiler = myCompiler;
      this.factory = factory;
    }

    @Override
    void processInternal(Node externs, Node root) {
      factory.create(myCompiler).process(externs, root);
    }
  }

  /**
   * Runs a set of compiler passes until they reach a fixed point.
   */
  static abstract class Loop implements CompilerPass {
    abstract void addLoopedPass(PassFactory factory);
  }

  /**
   * Runs a set of compiler passes until they reach a fixed point.
   *
   * Notice that this is a non-static class, because it includes the closure
   * of PhaseOptimizer.
   */
  private class LoopInternal extends Loop {
    private final List myPasses = Lists.newArrayList();
    private final Set myNames = Sets.newHashSet();

    @Override
    void addLoopedPass(PassFactory factory) {
      String name = factory.getName();
      Preconditions.checkArgument(
          !myNames.contains(name),
          "Already a pass with name '" + name + "' in this loop");
      myNames.add(factory.getName());
      myPasses.add(new PassFactoryDelegate(compiler, factory));
    }

    /**
     * Gets the pass names, in order.
     */
    private List getPassOrder() {
      List order = Lists.newArrayList();
      for (NamedPass pass : myPasses) {
        order.add(pass.name);
      }
      return order;
    }

    @Override
    public void process(Node externs, Node root) {
      Preconditions.checkState(!loopMutex, "Nested loops are forbidden");
      loopMutex = true;
      if (randomizeLoops) {
        randomizePasses();
      } else {
        optimizePasses();
      }

      try {
        // TODO(nicksantos): Use a smarter algorithm that dynamically adjusts
        // the order that passes are run in.
        int count = 0;
        out: do {
          if (count++ > MAX_LOOPS) {
            compiler.throwInternalError(OPTIMIZE_LOOP_ERROR, null);
          }

          recentChange.reset();  // reset before this round of optimizations

          for (CompilerPass pass : myPasses) {
            pass.process(externs, root);
            if (hasHaltingErrors()) {
              break out;
            }
          }

        } while (recentChange.hasCodeChanged() && !hasHaltingErrors());

        if (randomizeLoops) {
          loopsRun.add(getPassOrder());
        }
      } finally {
        loopMutex = false;
      }
    }

    /** Re-arrange the passes in a random order. */
    private void randomizePasses() {
      List mixedupPasses = Lists.newArrayList();
      Random random = new Random();
      while (myPasses.size() > 0) {
        mixedupPasses.add(
            myPasses.remove(random.nextInt(myPasses.size())));
      }
      myPasses.addAll(mixedupPasses);
    }

    /** Re-arrange the passes in an optimal order. */
    private void optimizePasses() {
      // It's important that this ordering is deterministic, so that
      // multiple compiles with the same input produce exactly the same
      // results.
      //
      // To do this, grab any passes we recognize, and move them to the end
      // in an "optimal" order.
      List optimalPasses = Lists.newArrayList();
      for (String passName : OPTIMAL_ORDER) {
        for (NamedPass pass : myPasses) {
          if (pass.name.equals(passName)) {
            optimalPasses.add(pass);
            break;
          }
        }
      }

      myPasses.removeAll(optimalPasses);
      myPasses.addAll(optimalPasses);
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy