/*
* Copyright (c) 2009, 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.max.vm.compiler.target;
import static com.sun.max.vm.VMOptions.*;
import java.util.concurrent.*;
import com.sun.max.annotate.*;
import com.sun.max.vm.*;
import com.sun.max.vm.actor.member.*;
import com.sun.max.vm.compiler.*;
import com.sun.max.vm.compiler.RuntimeCompiler.Nature;
import com.sun.max.vm.heap.*;
import com.sun.max.vm.runtime.*;
import com.sun.max.vm.stack.*;
import com.sun.max.vm.tele.*;
import com.sun.max.vm.thread.*;
/**
* This class represents an ongoing or completed compilation.
*/
public class Compilation {
/**
* Used to detect re-entrant compilation which indicates the boot image closure was not incomplete.
*/
private static final ObjectThreadLocal<Compilation> COMPILATION = new ObjectThreadLocal<Compilation>("COMPILATION", "current compilation");
private static boolean GCOnCompilation;
private static String GCOnCompilationOf;
static {
VMOptions.addFieldOption("-XX:", "GCOnCompilation", Compilation.class, "Perform a GC before every compilation.");
VMOptions.addFieldOption("-XX:", "GCOnCompilationOf", Compilation.class, "Perform a GC before every compilation of a method whose fully qualified name contains <value>.");
}
public static final VMBooleanXXOption TIME_COMPILATION = register(new VMBooleanXXOption("-XX:-TimeCompilation",
"Report time spent in compilation.") {
@Override
protected void beforeExit() {
if (getValue()) {
Log.print("Time spent in compilation: ");
Log.print(compilationTime);
Log.println("ms");
}
}
}, MaxineVM.Phase.STARTING);
@RESET
private static long compilationTime;
public RuntimeCompiler compiler;
public final ClassMethodActor classMethodActor;
public final Compilation parent;
@INSPECTED
public final Compilations prevCompilations;
public Thread compilingThread;
public TargetMethod result;
public final boolean isDeopt;
/**
* State of this compilation. If {@code true}, then this compilation has finished and the target
* method is available.
*/
public boolean done;
public final RuntimeCompiler.Nature nature;
public Compilation(RuntimeCompiler compiler,
ClassMethodActor classMethodActor,
Compilations prevCompilations,
Thread compilingThread, RuntimeCompiler.Nature nature,
boolean isDeopt) {
assert prevCompilations != null;
this.parent = COMPILATION.get();
this.compiler = compiler;
this.classMethodActor = classMethodActor;
this.prevCompilations = prevCompilations;
this.compilingThread = compilingThread;
this.nature = nature;
this.isDeopt = isDeopt;
for (Compilation scope = parent; scope != null; scope = scope.parent) {
if (scope.classMethodActor.equals(classMethodActor) && scope.compiler == compiler) {
FatalError.unexpected("Recursive compilation of " + classMethodActor + " by " + compiler);
}
}
COMPILATION.set(this);
}
/**
* Checks if any compilations are currently running in this thread. Useful to avoid recursive calls
* of the optimizing compiler.
*/
public static boolean isCompilationRunningInCurrentThread() {
return COMPILATION.get() != null;
}
/**
* Cancel this compilation. Ignored.
*/
public boolean cancel(boolean mayInterruptIfRunning) {
return false;
}
/**
* Checks whether this compilation was canceled. This method always returns {@code false}.
*/
public boolean isCancelled() {
return false;
}
/**
* Returns whether this compilation is done.
*/
public boolean isDone() {
synchronized (classMethodActor) {
return done;
}
}
/**
* Gets the result of this compilation, blocking if necessary.
*
* @return the target method that resulted from this compilation
*/
public TargetMethod get() {
synchronized (classMethodActor) {
boolean interrupted = false;
while (!done) {
if (compilingThread == Thread.currentThread()) {
throw new RuntimeException("Compilation of " + classMethodActor.format("%H.%n(%p)") + " is recursive");
}
// the class method actor is used here as the condition variable
try {
classMethodActor.wait();
} catch (InterruptedException ex) {
// Interrupting a thread that is waiting for a compilation to finish means that we still have to continue the waiting.
// After the code is available, we can interrupt() our thread again so that the flag gets passed down to the actual
// application code.
interrupted = true;
}
}
if (interrupted) {
Thread.currentThread().interrupt();
}
assert result != null;
return result;
}
}
/**
* Gets the result of this compilation, blocking for a maximum amount of time.
*
* @return the target method that resulted from this compilation
*/
public TargetMethod get(long timeout, TimeUnit unit) throws InterruptedException {
synchronized (classMethodActor) {
if (!done) {
// the class method actor is used here as the condition variable
classMethodActor.wait(timeout); // TODO: convert timeout to milliseconds
}
return result;
}
}
/**
* Perform the compilation, notifying the specified observers.
*
* @return the target method that is the result of the compilation
*/
public TargetMethod compile() {
RuntimeCompiler compiler = this.compiler;
Throwable error = null;
String methodString = "";
long startCompile = 0;
try {
InspectableCompilationInfo.notifyCompilationEvent(classMethodActor, null);
methodString = logBeforeCompilation(compiler);
if (!MaxineVM.isHosted() && StackReferenceMapPreparer.VerifyRefMaps) {
StackReferenceMapPreparer.verifyReferenceMapsForThisThread();
}
gcIfRequested(classMethodActor, methodString);
if (TIME_COMPILATION.getValue()) {
startCompile = System.currentTimeMillis();
}
result = compiler.compile(classMethodActor, isDeopt, true, null);
if (result == null) {
throw new InternalError(classMethodActor.format("Result of compiling of %H.%n(%p) is null"));
}
InspectableCompilationInfo.notifyCompilationEvent(result.classMethodActor, result);
if (startCompile != 0) {
compilationTime += System.currentTimeMillis() - startCompile;
}
logAfterCompilation(compiler, result, methodString);
} catch (RuntimeException t) {
error = t;
} catch (Error t) {
error = t;
} finally {
// invariant: (result != null) != (error != null)
synchronized (classMethodActor) {
// update the compilation state of the class method actor
if (result != null) {
assert nature != Nature.BASELINE || result.isBaseline() : "a request for a baseline target method failed to produce one";
// compilation succeeded and produced a target method
TargetMethod baseline = prevCompilations.baseline;
TargetMethod optimized = prevCompilations.optimized;
if (result.isBaseline()) {
baseline = result;
} else {
optimized = result;
}
classMethodActor.compiledState = new Compilations(baseline, optimized);
// compilation finished: this must come after the assignment to classMethodActor.compState
done = true;
// notify any waiters on this compilation
classMethodActor.notifyAll();
}
}
COMPILATION.set(parent);
}
if (error != null) {
// an error occurred
logCompilationError(error, compiler, methodString);
} else if (result == null) {
// the compilation didn't produce a target method
FatalError.unexpected("target method should not be null");
}
return result;
}
/**
* Invokes a garbage collection if the {@link #GCOnCompilation} or
* {@link #GCOnCompilationOf} options imply one is requested for
* the compilation of a given method.
*
* @param method the method about to be compiled
* @param methodString the value of {@code method.format("%H.%n(%p)} if it has been pre-computed, {@code null} otherwise
*/
private void gcIfRequested(ClassMethodActor method, String methodString) {
if (Heap.isInitialized()) {
if (GCOnCompilation) {
System.gc();
} else if (GCOnCompilationOf != null) {
if (methodString == null) {
methodString = method.format("%H.%n(%p)");
}
if (methodString.contains(GCOnCompilationOf)) {
System.gc();
}
}
}
}
private void logCompilationError(Throwable error, RuntimeCompiler compiler, String methodString) {
if (verboseOption.verboseCompilation) {
boolean lockDisabledSafepoints = Log.lock();
Log.printCurrentThread(false);
Log.print(": ");
Log.print(compiler.getClass().getSimpleName());
Log.print(": Failed ");
Log.println(methodString);
Log.unlock(lockDisabledSafepoints);
}
if (error instanceof Error) {
throw (Error) error;
}
throw (RuntimeException) error;
}
private String logBeforeCompilation(RuntimeCompiler compiler) {
String methodString = null;
if (verboseOption.verboseCompilation) {
methodString = classMethodActor.format("%H.%n(%p)");
boolean lockDisabledSafepoints = Log.lock();
Log.printCurrentThread(false);
Log.print(": ");
Log.print(compiler.getClass().getSimpleName());
Log.print(prevCompilations == Compilations.EMPTY ? ": Compiling " : ": Recompiling ");
Log.println(methodString);
Log.unlock(lockDisabledSafepoints);
}
return methodString;
}
private void logAfterCompilation(RuntimeCompiler compiler, TargetMethod targetMethod, String methodString) {
if (verboseOption.verboseCompilation) {
boolean lockDisabledSafepoints = Log.lock();
Log.printCurrentThread(false);
Log.print(": ");
Log.print(compiler.getClass().getSimpleName());
Log.print(prevCompilations == Compilations.EMPTY ? ": Compiled " : ": Recompiled ");
Log.print(methodString);
Log.print(" @ ");
Log.print(targetMethod.codeStart());
Log.print(", size = ");
Log.print(targetMethod.codeLength());
Log.println();
Log.unlock(lockDisabledSafepoints);
}
}
}