/*
* This file is part of the X10 project (http://x10-lang.org).
*
* This file is licensed to You under the Eclipse Public License (EPL);
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.opensource.org/licenses/eclipse-1.0.php
*
* This file was originally derived from the Polyglot extensible compiler framework.
*
* (C) Copyright 2000-2007 Polyglot project group, Cornell University
* (C) Copyright IBM Corporation 2007-2012.
*/
package polyglot.frontend;
import java.util.*;
import polyglot.ast.Node;
import polyglot.frontend.Compiler;
import polyglot.frontend.Goal.Status;
import polyglot.main.Options;
import polyglot.main.Reporter;
import polyglot.types.*;
import polyglot.util.InternalCompilerError;
import polyglot.util.Option;
import polyglot.util.CollectionUtil; import x10.types.X10ClassDef;
import x10.util.CollectionFactory;
import polyglot.visit.PostCompiled;
/**
* The <code>Scheduler</code> manages <code>Goal</code>s and runs
* <code>Pass</code>es.
*
* The basic idea is to have the scheduler try to satisfy goals. To reach a
* goal, a pass is run. The pass could modify an AST or it could, for example,
* initialize the members of a class loaded from a class file.
*
* Goals are processed via a worklist. A goal may have <i>prerequisite</i>
* dependencies. All prerequisites must be reached before the goal is attempted.
* The compilation completes when the EndAll goal is reached. A goal will be
* attempted at most once. If it fails, all goals dependent on it are
* unreachable.
*
* Passes are allowed to spawn other passes. A <i>reentrant pass</i> is allowed
* to spawn itself.
*
* Passes are (mostly) transactional. If a pass fails, its effects on the AST
* and on the system resolver are rolled back.
*
* @author nystrom
*/
public abstract class Scheduler {
protected ExtensionInfo extInfo;
/** map used for interning goals. */
protected Map<Goal,Goal> internCache = CollectionFactory.newHashMap(); // note that is not a cache in the sense that if you clear it the correctness of the compiler is compromised.
// TODO: remove this, we only need to intern the goal status, not the goal itself.
// Actually, the lazy ref to the goal status is the goal. The run() method is the resolver for the lazy ref.
public Goal intern(Goal goal) {
x10.ExtensionInfo x10Info = (x10.ExtensionInfo) extInfo;
x10Info.stats.incrFrequency("intern", 1);
x10Info.stats.incrFrequency("intern:"
+ (goal instanceof VisitorGoal ? ((VisitorGoal) goal).v.getClass().getName() : goal.getClass().getName()), 1);
Goal g = internCache.get(goal);
if (g == null) {
g = goal;
internCache.put(g, g);
}
else {
assert goal.getClass() == g.getClass();
}
return g;
}
/**
* A map from <code>Source</code>s to <code>Job</code>s or to
* the <code>COMPLETED_JOB</code> object if the Job previously
* existed
* but has now finished. The map contains entries for all
* <code>Source</code>s that have had <code>Job</code>s added for them.
*/
protected Map<Source, Option<Job>> jobs;
protected Collection<Job> commandLineJobs = Collections.emptyList();
/** True if any pass has failed. */
protected boolean failed;
/** True if the client requested that this scheduler be canceled */
private boolean canceled = false;
/** Return true if this scheduler has been canceled (so any compilation will fail). */
public boolean canceled() { return canceled; }
/** Cancel the current compilation. */
public void cancel() { canceled = true; }
private static class CompilationAbortedException extends RuntimeException {
private static final long serialVersionUID = -5353770769730560033L;
}
protected static final Option<Job> COMPLETED_JOB = Option.<Job>None();
/** The currently running pass, or null if no pass is running. */
protected Goal currentGoal;
public Scheduler(ExtensionInfo extInfo) {
this.extInfo = extInfo;
this.jobs = new LinkedHashMap<Source, Option<Job>>();
this.currentGoal = null;
}
public ExtensionInfo extensionInfo() {
return this.extInfo;
}
public Collection<Job> commandLineJobs() {
return this.commandLineJobs;
}
public Collection<Source> sources;
public void clearAll(Collection<Source> sources) {
this.sources = sources;
setFailed(false);
}
public void setCommandLineJobs(Collection<Job> c) {
this.commandLineJobs = Collections.unmodifiableCollection(c);
}
public boolean reached(Goal goal) {
return goal.hasBeenReached();
}
protected void completeJob(Job job) {
if (job != null) {
job.setCompleted(true);
jobs.put(job.source(), COMPLETED_JOB);
Reporter reporter = extInfo.getOptions().reporter;
if (reporter.should_report(Reporter.frontend, 1)) {
reporter.report(1, "Completed job " + job);
}
}
}
public List<Goal> prerequisites(Goal goal) {
return goal.prereqs();
}
Goal EndAll;
public Goal End(Job job) {
return new SourceGoal_c("End", job) {
private static final long serialVersionUID = 8093041848879587834L;
public boolean runTask() {
// The job has finished. Let's remove it from the job map
// so it can be garbage collected, and free up the AST.
completeJob(job);
return true;
}
}.intern(this);
}
Collection<Job> shouldCompile = new LinkedHashSet<Job>();
public boolean shouldCompile(Job job) {
if (commandLineJobs().contains(job))
return true;
if (extInfo.getOptions().compile_command_line_only)
return false;
return shouldCompile.contains(job);
}
protected Goal PostCompiled() {
return new PostCompiled(extInfo).intern(this);
}
protected Goal EndAll() {
if (EndAll == null)
EndAll = PostCompiled();
return EndAll;
}
protected Goal EndCommandLine() {
return EndAll();
}
public boolean runToCompletion() {
boolean okay;
okay = runToCompletion(EndAll());
return okay;
}
/**
* Attempt to complete all goals in the worklist (and any subgoals they
* have). This method returns <code>true</code> if all passes were
* successfully run and all goals in the worklist were reached. The worklist
* should be empty at return.
*/
public boolean runToCompletion(Goal endGoal) {
boolean okay = false;
try {
okay = attempt(endGoal);
}
catch (CyclicDependencyException e) {
}
catch (CompilationAbortedException e) {
}
Reporter reporter = extInfo.getOptions().reporter;
if (reporter.should_report(Reporter.frontend, 1))
reporter.report(1, "Finished all passes for " + this.getClass().getName() + " -- " +
(okay ? "okay" : "failed"));
return okay;
}
/**
* Load a source file and create a job for it. Optionally add a goal
* to compile the job to Java.
*
* @param source The source file to load.
* @param compile True if the compile goal should be added for the new job.
* @return The new job or null if the job has already completed.
*/
public Job loadSource(FileSource source, boolean compile) {
// Add a new Job for the given source. If a Job for the source
// already exists, then we will be given the existing job.
Job job = addJob(source);
if (job == null) {
// addJob returns null if the job has already been completed, in
// which case we can just ignore the request to read in the
// source.
return null;
}
// Create a goal for the job; this will set up dependencies for
// the goal, even if the goal isn't to be added to the work list.
addDependenciesForJob(job, compile);
return job;
}
public boolean sourceHasJob(Source s) {
return jobs.get(s) != null;
}
public Goal currentGoal() {
return currentGoal;
}
public Job currentJob() {
if (currentGoal instanceof SourceGoal_c)
return ((SourceGoal_c) currentGoal).job();
return null;
}
/**
* Run passes until the <code>goal</code> is attempted. Returns true iff the goal is reached.
* @throws CyclicDependencyException
*/
public boolean attempt(Goal goal) throws CyclicDependencyException {
assert currentGoal() == null
|| currentGoal().getCached() == Goal.Status.RUNNING
|| currentGoal().getCached() == Goal.Status.RUNNING_RECURSIVE
|| currentGoal().getCached() == Goal.Status.RUNNING_WILL_FAIL : "goal " + currentGoal() + " state " + currentGoal().state();
Status state = goal.get();
return state == Goal.Status.SUCCESS;
}
public static class State {
SystemResolver resolver;
State(SystemResolver resolver) {
this.resolver = resolver;
}
}
public State pushGlobalState(Goal goal) {
TypeSystem ts = extInfo.typeSystem();
// SystemResolver resolver = ts.saveSystemResolver();
SystemResolver resolver = ts.systemResolver();
return new State(resolver);
}
public void popGlobalState(Goal goal, State s) {
//TypeSystem ts = Globals.TS();
if (reached(goal)) {
// try {
// s.resolver.putAll(ts.systemResolver());
// }
// catch (SemanticException e) {
// ts.restoreSystemResolver(s.resolver);
// goal.setState(Goal.Status.FAIL);
// }
}
else {
// ts.restoreSystemResolver(s.resolver);
}
}
protected static class Complete extends RuntimeException {
private static final long serialVersionUID = 868126407122946251L;
protected Goal goal;
Complete(Goal goal) {
this.goal = goal;
}
}
/**
* Run the pass <code>pass</code>. All subgoals of the pass's goal
* required to start the pass should be satisfied. Running the pass
* may not satisfy the goal, forcing it to be retried later with new
* subgoals.
*/
protected boolean runPass(Goal goal) throws CyclicDependencyException {
Job job = goal instanceof SourceGoal ? ((SourceGoal) goal).job() : null;
Options options = extInfo.getOptions();
Reporter reporter = options.reporter;
if (canceled) {
if (reporter.should_report(Reporter.frontend, 1))
reporter.report(1, "Compilation canceled; skipping pass " + goal);
goal.update(Goal.Status.FAIL);
throw new CompilationAbortedException();
}
if (options.disable_passes.contains(goal.name())) {
if (reporter.should_report(Reporter.frontend, 1))
reporter.report(1, "Skipping pass " + goal);
goal.update(Goal.Status.SUCCESS);
return true;
}
if (reporter.should_report(Reporter.frontend, 1))
reporter.report(1, "Running pass for " + goal);
if (reached(goal)) {
throw new InternalCompilerError("Cannot run a pass for completed goal " + goal);
}
boolean result = false;
if (true || job == null || job.status()) {
reporter.start_reporting(goal.name());
if (job != null) {
// We're starting to run the pass.
// Record the initial error count.
job.initialErrorCount = job.compiler().errorQueue().errorCount();
}
Goal oldGoal = currentGoal;
currentGoal = goal;
String key = goal.toString();
x10.ExtensionInfo x10Info = (x10.ExtensionInfo) extInfo;
x10Info.stats.startTiming(goal.name(), key);
x10Info.stats.incrFrequency(key + " attempts", 1);
x10Info.stats.incrFrequency("total goal attempts", 1);
try {
result = goal.runTask();
if (result && goal.getCached() == Goal.Status.RUNNING) {
x10Info.stats.incrFrequency(key + " reached", 1);
x10Info.stats.incrFrequency("total goal reached", 1);
goal.update(Status.SUCCESS);
if (reporter.should_report(Reporter.frontend, 1))
reporter.report(1, "Completed pass for " + goal);
}
else {
x10Info.stats.incrFrequency(key + " unreached", 1);
x10Info.stats.incrFrequency("total goal unreached", 1);
if (reporter.should_report(Reporter.frontend, 1))
reporter.report(1, "Completed (unreached) pass for " + goal);
}
}
finally {
currentGoal = oldGoal;
if (job != null) {
// We've stopped running a pass.
// Check if the error count changed.
int errorCount = job.compiler().errorQueue().errorCount();
if (errorCount > job.initialErrorCount) {
job.reportedErrors = true;
}
}
reporter.stop_reporting(goal.name());
x10Info.stats.stopTiming();
}
// pretty-print this pass if we need to.
if (job != null &&
(extInfo.getOptions().print_ast.contains(goal.name()) ||
extInfo.getOptions().print_ast.contains("printall"))) {
System.err.println("--------------------------------" +
"--------------------------------");
System.err.println("Pretty-printing AST for " + job +
" after " + goal.name());
job.ast().prettyPrint(System.err);
}
// dump this pass if we need to.
if (job != null &&
(extInfo.getOptions().dump_ast.contains(goal.name()) ||
extInfo.getOptions().dump_ast.contains("dumpall"))) {
System.err.println("--------------------------------" +
"--------------------------------");
System.err.println("Dumping AST for " + job +
" after " + goal.name());
job.ast().dump(System.err);
}
}
if (! result) {
failed = true;
}
// Record the progress made before running the pass and then update
// the current progress.
if (reporter.should_report(Reporter.frontend, 1)) {
reporter.report(1, "Finished " + goal +
" status=" + statusString(result));
}
if (job != null) {
job.updateStatus(result);
}
return result;
}
/** FIXME: TEMPRORARY Inliner hack: Errors in speculative compilation for inlining should not be fatal
* @depricated DO NOT USE
*/
public boolean getFailed() {
return failed;
}
/** FIXME: TEMPRORARY Inliner hack: Errors in speculative compilation for inlining should not be fatal
* @depricated DO NOT USE
*/
public void setFailed (boolean b) {
failed = b;
}
/** FIXME: TEMPRORARY Inliner hack: Errors in speculative compilation for inlining should not be fatal
* @depricated DO NOT USE
*/
public void clearFailed () {
if (failed)
setFailed(false);
}
protected static String statusString(boolean okay) {
if (okay) {
return "done";
}
else {
return "failed";
}
}
/** Return all compilation units currently being compiled. */
public Collection<Job> jobs() {
ArrayList<Job> l = new ArrayList<Job>(jobs.size());
for (Iterator<Option<Job>> i = jobs.values().iterator(); i.hasNext(); ) {
Option<Job> o = i.next();
if (o != COMPLETED_JOB) {
l.add(o.get());
}
}
return l;
}
/**
* Add a new <code>Job</code> for the <code>Source source</code>.
* A new job will be created if
* needed. If the <code>Source source</code> has already been processed,
* and its job discarded to release resources, then <code>null</code>
* will be returned.
*/
public Job addJob(Source source) {
return addJob(source, null);
}
/**
* Add a new <code>Job</code> for the <code>Source source</code>,
* with AST <code>ast</code>.
* A new job will be created if
* needed. If the <code>Source source</code> has already been processed,
* and its job discarded to release resources, then <code>null</code>
* will be returned.
*/
public Job addJob(Source source, Node ast) {
Option<Job> o = jobs.get(source);
Job job = null;
if (o == COMPLETED_JOB) {
// the job has already been completed.
// We don't need to add a job
return null;
}
else if (o == null) {
// No appropriate job yet exists, we will create one.
job = this.createSourceJob(source, ast);
// record the job in the map and the worklist.
jobs.put(source, new Option.Some<Job>(job));
Reporter reporter = extInfo.getOptions().reporter;
if (reporter.should_report(Reporter.frontend, 4)) {
reporter.report(4, "Adding job for " + source + " at the " +
"request of goal " + currentGoal);
}
}
else {
job = o.get();
}
return job;
}
/** Get the goals for a particular job. This creates the dependencies between them. The list must include End(job). */
public abstract List<Goal> goals(Job job);
public void addDependenciesForJob(Job job, boolean compile) {
ExtensionInfo extInfo = this.extInfo;
List<Goal> goals = goals(job);
Goal prev = null;
// Be careful: the list might include goals already run.
for (Goal goal : goals) {
// assert goal instanceof SourceGoal;
if (prev != null) {
goal.addPrereq(prev);
}
if (! goal.hasBeenReached()) {
prev = goal;
}
}
assert prev == End(job);
if (compile) {
shouldCompile.add(job);
EndAll().addPrereq(prev);
}
}
/**
* Create a new <code>Job</code> for the given source and AST.
* In general, this method should only be called by <code>addJob</code>.
*/
protected Job createSourceJob(Source source, Node ast) {
return new Job(extInfo, extInfo.jobExt(), source, ast);
}
public String toString() {
return getClass().getName();
}
public abstract Goal Parsed(Job job);
public abstract Goal ImportTableInitialized(Job job);
public abstract Goal TypesInitialized(Job job);
protected abstract Goal constructTypesInitialized(Job job);
public abstract Goal TypesInitializedForCommandLineBarrier();
public abstract Goal PreTypeCheck(Job job);
public abstract Goal TypeChecked(Job job);
public abstract Goal ReachabilityChecked(Job job);
public abstract Goal ExceptionsChecked(Job job);
public abstract Goal ExitPathsChecked(Job job);
public abstract Goal InitializationsChecked(Job job);
public abstract Goal ConstructorCallsChecked(Job job);
public abstract Goal ForwardReferencesChecked(Job job);
public abstract Goal Serialized(Job job);
public abstract Goal CodeGenerated(Job job);
public abstract Goal LookupGlobalType(LazyRef<Type> sym);
public abstract Goal LookupGlobalTypeDef(LazyRef<X10ClassDef> sym, QName name);
public abstract Goal LookupGlobalTypeDefAndSetFlags(LazyRef<X10ClassDef> sym, QName name, Flags flags);
}