/*
* Copyright (c) 2013, 2016, 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 org.graalvm.compiler.truffle;
import static org.graalvm.compiler.truffle.TruffleCompilerOptions.TruffleArgumentTypeSpeculation;
import static org.graalvm.compiler.truffle.TruffleCompilerOptions.TruffleCompilationThreshold;
import static org.graalvm.compiler.truffle.TruffleCompilerOptions.TruffleCompileImmediately;
import static org.graalvm.compiler.truffle.TruffleCompilerOptions.TruffleInvalidationReprofileCount;
import static org.graalvm.compiler.truffle.TruffleCompilerOptions.TruffleMinInvokeThreshold;
import static org.graalvm.compiler.truffle.TruffleCompilerOptions.TruffleReplaceReprofileCount;
import static org.graalvm.compiler.truffle.TruffleCompilerOptions.TruffleReturnTypeSpeculation;
import static org.graalvm.compiler.truffle.TruffleCompilerOptions.TruffleTimeThreshold;
import java.util.LinkedHashMap;
import java.util.Map;
import com.oracle.truffle.api.Assumption;
import com.oracle.truffle.api.CompilerAsserts;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import com.oracle.truffle.api.CompilerOptions;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.nodes.ExplodeLoop;
public class OptimizedCompilationProfile {
/**
* Number of times an installed code for this tree was seen invalidated.
*/
private int invalidationCount;
private int deferredCount;
private int interpreterCallCount;
private int interpreterCallAndLoopCount;
private int compilationCallThreshold;
private int compilationCallAndLoopThreshold;
private long timestamp;
@CompilationFinal(dimensions = 1) private Class<?>[] profiledArgumentTypes;
@CompilationFinal private OptimizedAssumption profiledArgumentTypesAssumption;
@CompilationFinal private Class<?> profiledReturnType;
@CompilationFinal private OptimizedAssumption profiledReturnTypeAssumption;
@CompilationFinal private Class<?> exceptionType;
private volatile boolean compilationFailed;
public OptimizedCompilationProfile() {
compilationCallThreshold = TruffleCompilerOptions.getValue(TruffleMinInvokeThreshold);
compilationCallAndLoopThreshold = TruffleCompilerOptions.getValue(TruffleCompilationThreshold);
}
@Override
public String toString() {
return String.format("CompilationProfile(callCount=%d/%d, callAndLoopCount=%d/%d)", interpreterCallCount, compilationCallThreshold, interpreterCallAndLoopCount,
compilationCallAndLoopThreshold);
}
Class<?>[] getProfiledArgumentTypes() {
if (profiledArgumentTypesAssumption == null) {
/*
* We always need an assumption. If this method is called before the profile was
* initialized, we have to be conservative and disable profiling, which is done by
* creating an invalid assumption but leaving the type field null.
*/
CompilerDirectives.transferToInterpreterAndInvalidate();
profiledArgumentTypesAssumption = createAssumption("Profiled Argument Types");
profiledArgumentTypesAssumption.invalidate();
}
if (profiledArgumentTypesAssumption.isValid()) {
return profiledArgumentTypes;
} else {
return null;
}
}
Class<?> getProfiledReturnType() {
if (profiledReturnTypeAssumption == null) {
/*
* We always need an assumption. If this method is called before the profile was
* initialized, we have to be conservative and disable profiling, which is done by
* creating an invalid assumption but leaving the type field null.
*/
CompilerDirectives.transferToInterpreterAndInvalidate();
profiledReturnTypeAssumption = createAssumption("Profiled Return Type");
profiledReturnTypeAssumption.invalidate();
}
if (profiledReturnTypeAssumption.isValid()) {
return profiledReturnType;
} else {
return null;
}
}
@ExplodeLoop
void profileDirectCall(Object[] args) {
Assumption typesAssumption = profiledArgumentTypesAssumption;
if (typesAssumption == null) {
if (CompilerDirectives.inInterpreter()) {
initializeProfiledArgumentTypes(args);
}
} else {
Class<?>[] types = profiledArgumentTypes;
if (types != null) {
if (types.length != args.length) {
CompilerDirectives.transferToInterpreterAndInvalidate();
typesAssumption.invalidate();
profiledArgumentTypes = null;
} else if (typesAssumption.isValid()) {
for (int i = 0; i < types.length; i++) {
Class<?> type = types[i];
Object value = args[i];
if (type != null && (value == null || value.getClass() != type)) {
CompilerDirectives.transferToInterpreterAndInvalidate();
updateProfiledArgumentTypes(args, types);
break;
}
}
}
}
}
}
void profileIndirectCall() {
Assumption argumentTypesAssumption = profiledArgumentTypesAssumption;
if (argumentTypesAssumption != null && argumentTypesAssumption.isValid()) {
// Argument profiling is not possible for targets of indirect calls.
CompilerDirectives.transferToInterpreterAndInvalidate();
argumentTypesAssumption.invalidate();
profiledArgumentTypes = null;
}
}
void profileInlinedCall() {
// nothing to profile for inlined calls by default
}
final void profileReturnValue(Object result) {
Assumption returnTypeAssumption = profiledReturnTypeAssumption;
if (CompilerDirectives.inInterpreter() && returnTypeAssumption == null) {
// we only profile return values in the interpreter as we don't want to deoptimize
// for immediate compiles.
if (TruffleCompilerOptions.getValue(TruffleReturnTypeSpeculation)) {
profiledReturnType = classOf(result);
profiledReturnTypeAssumption = createAssumption("Profiled Return Type");
}
} else if (profiledReturnType != null) {
if (result == null || profiledReturnType != result.getClass()) {
CompilerDirectives.transferToInterpreterAndInvalidate();
profiledReturnType = null;
returnTypeAssumption.invalidate();
}
}
}
@SuppressWarnings("unchecked")
final <E extends Throwable> E profileExceptionType(E ex) {
Class<?> cachedClass = exceptionType;
// if cachedClass is null and we are not in the interpreter we don't want to deoptimize
// This usually happens only if the call target was compiled using compile without ever
// calling it.
if (cachedClass != Object.class) {
if (cachedClass != null && cachedClass == ex.getClass()) {
return (E) cachedClass.cast(ex);
}
CompilerDirectives.transferToInterpreterAndInvalidate();
if (cachedClass == null) {
exceptionType = ex.getClass();
} else {
// object class is not reachable for exceptions
exceptionType = Object.class;
}
}
return ex;
}
final Object[] injectArgumentProfile(Object[] originalArguments) {
Assumption argumentTypesAssumption = profiledArgumentTypesAssumption;
Object[] args = originalArguments;
if (argumentTypesAssumption != null && argumentTypesAssumption.isValid()) {
args = OptimizedCallTarget.unsafeCast(OptimizedCallTarget.castArrayFixedLength(args, profiledArgumentTypes.length), Object[].class, true, true);
args = castArgumentsImpl(args);
}
return args;
}
@ExplodeLoop
private Object[] castArgumentsImpl(Object[] originalArguments) {
Class<?>[] types = profiledArgumentTypes;
Object[] castArguments = new Object[types.length];
for (int i = 0; i < types.length; i++) {
castArguments[i] = types[i] != null ? OptimizedCallTarget.unsafeCast(originalArguments[i], types[i], true, true) : originalArguments[i];
}
return castArguments;
}
final Object injectReturnValueProfile(Object result) {
Class<?> klass = profiledReturnType;
if (klass != null && CompilerDirectives.inCompiledCode() && profiledReturnTypeAssumption.isValid()) {
return OptimizedCallTarget.unsafeCast(result, klass, true, true);
}
return result;
}
final void reportCompilationFailure() {
compilationFailed = true;
}
final void reportLoopCount(int count) {
interpreterCallAndLoopCount += count;
int callsMissing = compilationCallAndLoopThreshold - interpreterCallAndLoopCount;
if (callsMissing <= getTimestampThreshold() && callsMissing + count > getTimestampThreshold()) {
timestamp = System.nanoTime();
}
}
final void reportInvalidated() {
invalidationCount++;
int reprofile = TruffleCompilerOptions.getValue(TruffleInvalidationReprofileCount);
ensureProfiling(reprofile, reprofile);
}
final void reportNodeReplaced() {
// delay compilation until tree is deemed stable enough
int replaceBackoff = TruffleCompilerOptions.getValue(TruffleReplaceReprofileCount);
ensureProfiling(1, replaceBackoff);
}
final void interpreterCall(OptimizedCallTarget callTarget) {
int intCallCount = ++interpreterCallCount;
int intAndLoopCallCount = ++interpreterCallAndLoopCount;
int callsMissing = compilationCallAndLoopThreshold - interpreterCallAndLoopCount;
if (callsMissing == getTimestampThreshold()) {
timestamp = System.nanoTime();
}
if (!callTarget.isCompiling() && !compilationFailed) {
// check if call target is hot enough to get compiled, but took not too long to get hot
if ((intAndLoopCallCount >= compilationCallAndLoopThreshold && intCallCount >= compilationCallThreshold && !isDeferredCompile(callTarget)) ||
TruffleCompilerOptions.getValue(TruffleCompileImmediately)) {
callTarget.compile();
}
}
}
private boolean isDeferredCompile(OptimizedCallTarget target) {
// Workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=440019
int thresholdInt = TruffleCompilerOptions.getValue(TruffleTimeThreshold);
long threshold = thresholdInt;
CompilerOptions compilerOptions = target.getCompilerOptions();
if (compilerOptions instanceof GraalCompilerOptions) {
threshold = Math.max(threshold, ((GraalCompilerOptions) compilerOptions).getMinTimeThreshold());
}
long time = getTimestamp();
if (time == 0) {
return false;
}
long timeElapsed = System.nanoTime() - time;
if (timeElapsed > threshold * 1_000_000) {
// defer compilation
ensureProfiling(0, getTimestampThreshold() + 1);
timestamp = 0;
deferredCount++;
return true;
}
return false;
}
private static int getTimestampThreshold() {
return Math.max(TruffleCompilerOptions.getValue(TruffleCompilationThreshold) / 2, 1);
}
private void initializeProfiledArgumentTypes(Object[] args) {
CompilerAsserts.neverPartOfCompilation();
profiledArgumentTypesAssumption = createAssumption("Profiled Argument Types");
if (TruffleCompilerOptions.getValue(TruffleArgumentTypeSpeculation)) {
Class<?>[] result = new Class<?>[args.length];
for (int i = 0; i < args.length; i++) {
result[i] = classOf(args[i]);
}
profiledArgumentTypes = result;
}
}
private void updateProfiledArgumentTypes(Object[] args, Class<?>[] types) {
CompilerAsserts.neverPartOfCompilation();
profiledArgumentTypesAssumption.invalidate();
for (int j = 0; j < types.length; j++) {
types[j] = joinTypes(types[j], classOf(args[j]));
}
profiledArgumentTypesAssumption = createAssumption("Profiled Argument Types");
}
private static Class<?> classOf(Object arg) {
return arg != null ? arg.getClass() : null;
}
private static Class<?> joinTypes(Class<?> class1, Class<?> class2) {
if (class1 == class2) {
return class1;
} else {
return null;
}
}
private void ensureProfiling(int calls, int callsAndLoop) {
int increaseCallAndLoopThreshold = callsAndLoop - (this.compilationCallAndLoopThreshold - this.interpreterCallAndLoopCount);
if (increaseCallAndLoopThreshold > 0) {
this.compilationCallAndLoopThreshold += increaseCallAndLoopThreshold;
}
int increaseCallsThreshold = calls - (this.compilationCallThreshold - this.interpreterCallCount);
if (increaseCallsThreshold > 0) {
this.compilationCallThreshold += increaseCallsThreshold;
}
}
public Map<String, Object> getDebugProperties() {
Map<String, Object> properties = new LinkedHashMap<>();
String callsThreshold = String.format("%7d/%5d", getInterpreterCallCount(), getCompilationCallThreshold());
String loopsThreshold = String.format("%7d/%5d", getInterpreterCallAndLoopCount(), getCompilationCallAndLoopThreshold());
String invalidations = String.format("%5d", invalidationCount);
properties.put("Calls/Thres", callsThreshold);
properties.put("CallsAndLoop/Thres", loopsThreshold);
properties.put("Inval#", invalidations);
return properties;
}
public int getInvalidationCount() {
return invalidationCount;
}
public int getInterpreterCallAndLoopCount() {
return interpreterCallAndLoopCount;
}
public int getInterpreterCallCount() {
return interpreterCallCount;
}
public int getDeferredCount() {
return deferredCount;
}
public int getCompilationCallAndLoopThreshold() {
return compilationCallAndLoopThreshold;
}
public int getCompilationCallThreshold() {
return compilationCallThreshold;
}
public long getTimestamp() {
return timestamp;
}
public static OptimizedCompilationProfile create() {
return new OptimizedCompilationProfile();
}
private static OptimizedAssumption createAssumption(String name) {
return (OptimizedAssumption) Truffle.getRuntime().createAssumption(name);
}
}