Skip to content

Detect cold CallTarget invalidation and reset its profile; Limit number of recompilations within a time period #11610

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -544,6 +544,7 @@ public final RootNode getRootNode() {
public final void resetCompilationProfile() {
this.callCount = 0;
this.callAndLoopCount = 0;
this.successfulCompilationsCount = 0;
}

@Override
Expand Down Expand Up @@ -849,8 +850,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 @@ -181,7 +181,7 @@ protected void clearState() {
this.knownMethods = null;
}

private final OptimizedTruffleRuntimeListenerDispatcher listeners = new OptimizedTruffleRuntimeListenerDispatcher();
protected final OptimizedTruffleRuntimeListenerDispatcher listeners = new OptimizedTruffleRuntimeListenerDispatcher();

protected volatile TruffleCompiler truffleCompiler;
protected volatile OptimizedCallTarget initializeCallTarget;
Expand Down Expand Up @@ -847,6 +847,11 @@ private void shutdown() {
protected final void doCompile(OptimizedCallTarget callTarget, AbstractCompilationTask task) {
Objects.requireNonNull(callTarget, "Cannot compile null call target.");
Objects.requireNonNull(task, "Compilation task required.");

if (shouldAbortCompilation(callTarget)) {
return;
}

List<OptimizedCallTarget> oldBlockCompilations = callTarget.blockCompilations;
if (oldBlockCompilations != null) {
for (OptimizedCallTarget blockTarget : oldBlockCompilations) {
Expand Down Expand Up @@ -1478,4 +1483,15 @@ public enum CompilationActivityMode {
protected CompilationActivityMode getCompilationActivityMode() {
return CompilationActivityMode.RUN_COMPILATION;
}

/**
* This method is intended to give subclasses a last say on whether the compilation should be
* aborted or not.
*
* @param callTarget - The OptimizedCallTarget for the compilation.
* @return true if the compilation should be aborted.
*/
protected boolean shouldAbortCompilation(OptimizedCallTarget callTarget) {
return false;
}
}
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 @@ -54,7 +54,7 @@
* {@link TruffleCompilerListener} events to {@link OptimizedTruffleRuntimeListener} events.
*/
@SuppressWarnings("serial")
final class OptimizedTruffleRuntimeListenerDispatcher extends CopyOnWriteArrayList<OptimizedTruffleRuntimeListener> implements OptimizedTruffleRuntimeListener, TruffleCompilerListener {
public final class OptimizedTruffleRuntimeListenerDispatcher extends CopyOnWriteArrayList<OptimizedTruffleRuntimeListener> implements OptimizedTruffleRuntimeListener, TruffleCompilerListener {

@Override
public boolean add(OptimizedTruffleRuntimeListener e) {
Expand Down 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 @@ -118,7 +118,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 @@ -102,7 +102,8 @@ public static void install(OptimizedTruffleRuntime runtime) {
private static final String FAILED_FORMAT = "opt failed " + TARGET_FORMAT + "|" + TIER_FORMAT + "|Time %18s|Reason: %s|UTC %s|Src %s";
private static final String PADDING = " ";
private static final String INV_FORMAT = "opt inval. " + TARGET_FORMAT + " " + PADDING + "|UTC %s|Src %s|Reason %s";
private static final String DEOPT_FORMAT = "opt deopt " + TARGET_FORMAT + "|" + PADDING + "|UTC %s|Src %s";
private static final String DEOPT_FORMAT = "opt deopt " + TARGET_FORMAT + "|" + PADDING + "|UTC %s|Src %s|Reason %s";
private static final String REPROF_FORMAT = "opt reprofile " + TARGET_FORMAT + "|" + PADDING + "|UTC %s|Src %s";
// @formatter:on

@Override
Expand Down Expand Up @@ -222,9 +223,22 @@ 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),
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),
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,35 @@ 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 = initialize(HotSpotNmethod.class, "setSpeculationLog", HotSpotSpeculationLog.class);
invalidateInstalledCodeWithReasonMethodRef = initialize(HotSpotNmethod.class, "invalidate", boolean.class, int.class);
invalidateInstalledCodeWithoutReasonMethodRef = initialize(InstalledCode.class, "invalidate", boolean.class);
getInvalidationReasonMethodRef = initialize(HotSpotNmethod.class, "getInvalidationReason");
getInvalidationReasonDescriptionMethodRef = initialize(HotSpotNmethod.class, "getInvalidationReasonDescription");
}

private static Method initialize(Class<?> clx, String methodName, Class<?>... args) {
try {
method = InstalledCode.class.getDeclaredMethod("invalidate", boolean.class);
return clx.getDeclaredMethod(methodName, args);
} catch (NoSuchMethodException e) {
return null;
}
invalidateInstalledCode = method;
}

/**
Expand All @@ -127,9 +139,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 +211,37 @@ public SpeculationLog getCompilationSpeculationLog() {
return HotSpotTruffleRuntimeServices.getCompilationSpeculationLog(this);
}

@Override
protected void notifyDeoptimized(VirtualFrame frame) {
String reason = null;
try {
if (getInvalidationReasonDescriptionMethodRef != null) {
reason = (String) getInvalidationReasonDescriptionMethodRef.invoke(this.installedCode);
}
} catch (Exception e) {
// ignore
} finally {
runtime().getListener().onCompilationDeoptimized(this, frame, reason);
}
}

/**
* This method is intended only for resetting {@installedCode}, it should only be called on
* CallTargets that are already invalid.
*/
public void resetInstalledCode() {
assert !isValid();
this.installedCode = INVALID_CODE;
}

public int getInvalidationReason() {
try {
if (getInvalidationReasonMethodRef != null) {
return (int) getInvalidationReasonMethodRef.invoke(this.installedCode);
}
} catch (Exception e) {
// ignore
}
return 0;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -176,11 +176,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 @@ -644,6 +657,10 @@ protected int getObjectAlignment() {
return getVMOptionValue("ObjectAlignmentInBytes", Integer.class);
}

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

@Override
protected int getArrayIndexScale(Class<?> componentType) {
MetaAccessProvider meta = getMetaAccess();
Expand Down Expand Up @@ -736,4 +753,25 @@ private static CompilationActivityMode resolveHotSpotActivityMode(int i) {
default -> throw CompilerDirectives.shouldNotReachHere("Invalid CompilationActivityMode " + i);
};
}

/**
* 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
protected boolean shouldAbortCompilation(OptimizedCallTarget callTarget) {
HotSpotOptimizedCallTarget hsCallTarget = (HotSpotOptimizedCallTarget) callTarget;
if (hsCallTarget.getInvalidationReason() == this.coldMethodInvalidationReason) {
hsCallTarget.resetCompilationProfile();
hsCallTarget.resetInstalledCode();
listeners.onProfileReset(hsCallTarget);
return true;
}
return false;
}
}
Loading