Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ protected DeoptInvalidateListener(OptimizedTruffleRuntime runtime, OptimizedCall
}

@Override
public void onCompilationDeoptimized(OptimizedCallTarget target, Frame frame) {
public void onCompilationDeoptimized(OptimizedCallTarget target, Frame frame, String reason) {
if (target == focus) {
deoptimized = true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ public void onAssumptionInvalidated(Object source, CharSequence reason) {
boolean wasActive = false;
InstalledCode code = getInstalledCode();
if (code != null && code.isAlive()) {
// No need to set deoptimize or invalidation reason here because the defaults, 'true'
// and 'JVMCI_INVALIDATE' are the appropriate.
code.invalidate();
wasActive = true;
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,7 @@ public final void prepareForCompilation() {
}

@Override
public final boolean prepareForCompilation(boolean rootCompilation, int compilationTier, boolean lastTier) {
public boolean prepareForCompilation(boolean rootCompilation, int compilationTier, boolean lastTier) {
RootNode root = this.rootNode;
if (root == null) {
throw CompilerDirectives.shouldNotReachHere("Initialization call targets cannot be compiled.");
Expand Down Expand Up @@ -548,6 +548,7 @@ public final RootNode getRootNode() {
public final void resetCompilationProfile() {
this.callCount = 0;
this.callAndLoopCount = 0;
this.successfulCompilationsCount = 0;
}

@Override
Expand Down Expand Up @@ -853,8 +854,8 @@ private RuntimeException handleException(VirtualFrame frame, Throwable t) {
throw rethrow(profiledT);
}

private void notifyDeoptimized(VirtualFrame frame) {
runtime().getListener().onCompilationDeoptimized(this, frame);
protected void notifyDeoptimized(VirtualFrame frame) {
runtime().getListener().onCompilationDeoptimized(this, frame, null);
}

protected static OptimizedTruffleRuntime runtime() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,28 @@ default void onCompilationInvalidated(OptimizedCallTarget target, Object source,
* @param target the call target whose compiled code was just deoptimized
* @param frame
*/
@Deprecated(since = "26.0")
default void onCompilationDeoptimized(OptimizedCallTarget target, Frame frame) {
onCompilationDeoptimized(target, frame, null);
}

/**
* Notifies this object when {@code target} has just deoptimized and is now executing in the
* Truffle interpreter instead of executing compiled code.
*
* @param target the call target whose compiled code was just deoptimized
* @param frame
* @param reason optional reason why the deoptimization happened.
*/
default void onCompilationDeoptimized(OptimizedCallTarget target, Frame frame, String reason) {
}

/**
* Notifies this object when {@code target} has its execution profile reset.
*
* @param target the call target whose profile was just reset.
*/
default void onProfileReset(OptimizedCallTarget target) {
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,13 @@ public void onCompilationInvalidated(OptimizedCallTarget target, Object source,
}

@Override
public void onCompilationDeoptimized(OptimizedCallTarget target, Frame frame) {
invokeListeners((l) -> l.onCompilationDeoptimized(target, frame));
public void onCompilationDeoptimized(OptimizedCallTarget target, Frame frame, String reason) {
invokeListeners((l) -> l.onCompilationDeoptimized(target, frame, reason));
}

@Override
public void onProfileReset(OptimizedCallTarget target) {
invokeListeners((l) -> l.onProfileReset(target));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ public void onCompilationStarted(OptimizedCallTarget target, AbstractCompilation
}

@Override
public void onCompilationDeoptimized(OptimizedCallTarget target, Frame frame) {
public void onCompilationDeoptimized(OptimizedCallTarget target, Frame frame, String reason) {
DeoptimizationEvent event = FACTORY.createDeoptimizationEvent();
if (event.isEnabled()) {
event.setRootFunction(target);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ public static void install(OptimizedTruffleRuntime runtime) {
private static final String DEOPT_PADDING = " ";
private static final String INV_FORMAT = "opt inval. " + TARGET_FORMAT + " " + INV_PADDING + "|UTC %s|Src %s|Reason %s";
private static final String DEOPT_FORMAT = "opt deopt " + TARGET_FORMAT + "|Invalidated %5b|" + DEOPT_PADDING + "|UTC %s|Src %s";
private static final String REPROF_FORMAT = "opt reprof " + TARGET_FORMAT + "|" + INV_PADDING + "|UTC %s|Src %s";
// @formatter:on

@Override
Expand Down Expand Up @@ -223,14 +224,27 @@ private void log(OptimizedCallTarget target, String message) {
}

@Override
public void onCompilationDeoptimized(OptimizedCallTarget target, Frame frame) {
public void onCompilationDeoptimized(OptimizedCallTarget target, Frame frame, String reason) {
if (target.engine.traceCompilation || target.engine.traceCompilationDetails) {
log(target, String.format(DEOPT_FORMAT,
target.engineId(),
target.id,
safeTargetName(target),
!target.isValid(),
TIME_FORMATTER.format(ZonedDateTime.now()),
formatSourceSection(safeSourceSection(target)),
reason != null ? reason : "unknown"));
}
}

@Override
public void onProfileReset(OptimizedCallTarget target) {
if (target.engine.traceCompilation || target.engine.traceCompilationDetails) {
log(target, String.format(REPROF_FORMAT,
target.engineId(),
target.id,
safeTargetName(target),
TIME_FORMATTER.format(ZonedDateTime.now()),
formatSourceSection(safeSourceSection(target))));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@

import java.lang.reflect.Method;

import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.compiler.TruffleCompiler;
import com.oracle.truffle.runtime.EngineData;
Expand Down Expand Up @@ -97,24 +98,38 @@ public HotSpotOptimizedCallTarget(EngineData engine) {
private static final Method setSpeculationLog;

/**
* Reflective reference to {@code InstalledCode.invalidate(boolean deoptimize)} so that this
* Reflective reference to
* {@code InstalledCode.invalidate(boolean deoptimize, int invalidationReason)} so that this
* code can be compiled against older JVMCI API.
*/
@SuppressWarnings("unused") private static final Method invalidateInstalledCode;
@SuppressWarnings("unused") private static final Method invalidateInstalledCodeWithReasonMethodRef;
@SuppressWarnings("unused") private static final Method invalidateInstalledCodeWithoutReasonMethodRef;

/**
* Reflective reference to {@code HotSpotNmethod.getInvalidationReason()} and
* {@code HotSpotNmethod.getInvalidationReasonDescription()} so that this code can be compiled
* against older JVMCI API.
*/
@SuppressWarnings("unused") private static final Method getInvalidationReasonMethodRef;
@SuppressWarnings("unused") private static final Method getInvalidationReasonDescriptionMethodRef;

static {
Method method = null;
try {
method = HotSpotNmethod.class.getDeclaredMethod("setSpeculationLog", HotSpotSpeculationLog.class);
} catch (NoSuchMethodException e) {
}
setSpeculationLog = method;
method = null;
setSpeculationLog = findMethod(HotSpotNmethod.class, "setSpeculationLog", true, HotSpotSpeculationLog.class);
invalidateInstalledCodeWithReasonMethodRef = findMethod(HotSpotNmethod.class, "invalidate", false, boolean.class, int.class);
invalidateInstalledCodeWithoutReasonMethodRef = findMethod(InstalledCode.class, "invalidate", true, boolean.class);
getInvalidationReasonMethodRef = findMethod(HotSpotNmethod.class, "getInvalidationReason", false);
getInvalidationReasonDescriptionMethodRef = findMethod(HotSpotNmethod.class, "getInvalidationReasonDescription", false);
}

private static Method findMethod(Class<?> clazz, String methodName, boolean required, Class<?>... args) {
try {
method = InstalledCode.class.getDeclaredMethod("invalidate", boolean.class);
return clazz.getMethod(methodName, args);
} catch (NoSuchMethodException e) {
if (required) {
throw new InternalError(e);
}
return null;
}
invalidateInstalledCode = method;
}

/**
Expand All @@ -127,9 +142,13 @@ public void setInstalledCode(InstalledCode code) {
return;
}

if (oldCode != INVALID_CODE && invalidateInstalledCode != null) {
if (oldCode != INVALID_CODE) {
try {
invalidateInstalledCode.invoke(oldCode, false);
if (invalidateInstalledCodeWithReasonMethodRef != null) {
invalidateInstalledCodeWithReasonMethodRef.invoke(oldCode, false, ((HotSpotTruffleRuntime) runtime()).getJVMCIReplacedMethodInvalidationReason());
} else if (invalidateInstalledCodeWithoutReasonMethodRef != null) {
invalidateInstalledCodeWithoutReasonMethodRef.invoke(oldCode, false);
}
} catch (Error e) {
throw e;
} catch (Throwable throwable) {
Expand Down Expand Up @@ -195,4 +214,56 @@ public SpeculationLog getCompilationSpeculationLog() {
return HotSpotTruffleRuntimeServices.getCompilationSpeculationLog(this);
}

@Override
protected void notifyDeoptimized(VirtualFrame frame) {
String reason = null;
try {
// This method may be called with {@code installedCode ==
// INVALID_CODE} when the call target is not a root compilation and
// the parent compilation was deoptimized.
if (this.installedCode != INVALID_CODE && getInvalidationReasonDescriptionMethodRef != null) {
reason = (String) getInvalidationReasonDescriptionMethodRef.invoke(this.installedCode);
}
} catch (Exception e) {
throw new InternalError(e);
} finally {
runtime().getListener().onCompilationDeoptimized(this, frame, reason);
}
}

private int getInvalidationReason() {
try {
if (getInvalidationReasonMethodRef != null) {
assert installedCode != INVALID_CODE : "Cannot get invalidation reason of INVALID_CODE";
return (int) getInvalidationReasonMethodRef.invoke(this.installedCode);
}
} catch (Exception e) {
throw new InternalError(e);
}
return 0;
}

/**
* When running as part of HotSpot we should pay special attention to CallTargets (CTs) that
* have been flushed from the code cache because they were cold (According to Code Cache's
* heuristics). Truffle's CallTargets' Profile counter don't decay. For that reason, we need
* special handling for cold (according to code cache heuristics) CTs that were flushed from the
* code cache. Otherwise, we can enter a recompilation cycle because Truffle will always see the
* method as hot (because the profile counters never reset). To handle this case we reset the CT
* profile whenever its prior compilation was invalidated because it was cold.
*/
@Override
public boolean prepareForCompilation(boolean rootCompilation, int compilationTier, boolean lastTier) {
if (!super.prepareForCompilation(rootCompilation, compilationTier, lastTier)) {
return false;
}

if (installedCode != INVALID_CODE && getInvalidationReason() == ((HotSpotTruffleRuntime) runtime()).getColdMethodInvalidationReason()) {
resetCompilationProfile();
installedCode = INVALID_CODE;
runtime().getListener().onProfileReset(this);
return false;
}
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -186,11 +186,24 @@ private Lazy lazy() {

private final HotSpotVMConfigAccess vmConfigAccess;

/**
* This constant is used to detect when a method was invalidated by HotSpot because the code
* cache heuristic considered it cold.
*/
private final int coldMethodInvalidationReason;

/**
* This constant is used when Truffle invalidates an installed code.
*/
private final int jvmciReplacedMethodInvalidationReason;

public HotSpotTruffleRuntime(TruffleCompilationSupport compilationSupport) {
super(compilationSupport, Arrays.asList(HotSpotOptimizedCallTarget.class, InstalledCode.class, HotSpotThreadLocalHandshake.class, HotSpotTruffleRuntime.class));
installCallBoundaryMethods(null);

this.vmConfigAccess = new HotSpotVMConfigAccess(HotSpotJVMCIRuntime.runtime().getConfigStore());
this.jvmciReplacedMethodInvalidationReason = vmConfigAccess.getConstant("nmethod::InvalidationReason::JVMCI_REPLACED_WITH_NEW_CODE", Integer.class, -1);
this.coldMethodInvalidationReason = vmConfigAccess.getConstant("nmethod::InvalidationReason::UNLOADING_COLD", Integer.class, -1);

int jvmciReservedReference0Offset = vmConfigAccess.getFieldOffset("JavaThread::_jvmci_reserved_oop0", Integer.class, "oop", -1);
if (jvmciReservedReference0Offset == -1) {
Expand Down Expand Up @@ -656,6 +669,14 @@ protected int getObjectAlignment() {
return getVMOptionValue("ObjectAlignmentInBytes", Integer.class);
}

public int getJVMCIReplacedMethodInvalidationReason() {
return this.jvmciReplacedMethodInvalidationReason;
}

public int getColdMethodInvalidationReason() {
return this.coldMethodInvalidationReason;
}

@Override
protected int getArrayIndexScale(Class<?> componentType) {
MetaAccessProvider meta = getMetaAccess();
Expand Down