Skip to content

Commit c5acb6b

Browse files
author
Eric Wu
committed
[GR-55222] Enable LazyDeoptimization by default
PullRequest: graal/19518
2 parents 0da527b + ec625b8 commit c5acb6b

File tree

4 files changed

+54
-37
lines changed

4 files changed

+54
-37
lines changed

substratevm/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ This changelog summarizes major changes to GraalVM Native Image.
99
* (GR-59864) Added JVM version check to the Native Image agent. The agent will abort execution if the JVM major version does not match the version it was built with, and warn if the full JVM version is different.
1010
* (GR-59135) Verify if hosted options passed to `native-image` exist prior to starting the builder. Provide suggestions how to fix unknown options early on.
1111
* (GR-61492) The experimental JDWP option is now present in standard GraalVM builds.
12+
* (GR-55222) Enabled lazy deoptimization of runtime-compiled code, which reduces memory used for deoptimization. Can be turned off with `-H:-LazyDeoptimization`.
1213

1314
## GraalVM for JDK 24 (Internal Version 24.2.0)
1415
* (GR-59717) Added `DuringSetupAccess.registerObjectReachabilityHandler` to allow registering a callback that is executed when an object of a specified type is marked as reachable during heap scanning.

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeCodeCache.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ protected void invalidateMethod(CodeInfo info) {
210210
* Deoptimize all invocations that are on the stack. This performs a stack walk, so all
211211
* metadata must be intact (even though the method was already marked as non-invokable).
212212
*/
213-
Deoptimizer.deoptimizeInRange(CodeInfoAccess.getCodeStart(info), CodeInfoAccess.getCodeEnd(info), false);
213+
Deoptimizer.deoptimizeInRange(CodeInfoAccess.getCodeStart(info), CodeInfoAccess.getCodeEnd(info), false, CurrentIsolate.getCurrentThread());
214214

215215
boolean removeNow = !LazyDeoptimization.getValue();
216216
continueInvalidation(info, removeNow);

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/deopt/Deoptimizer.java

Lines changed: 49 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ public static class Options {
279279
* {@code gpReturnValue} as an object reference.
280280
*/
281281
@Option(help = "Enables delayed deoptimization of runtime-compiled code. This slightly enlarges code metadata.")//
282-
public static final HostedOptionKey<Boolean> LazyDeoptimization = new HostedOptionKey<>(false);
282+
public static final HostedOptionKey<Boolean> LazyDeoptimization = new HostedOptionKey<>(true);
283283
}
284284

285285
/**
@@ -437,18 +437,22 @@ private void installDeoptimizedFrame(DeoptimizedFrame deoptimizedFrame) {
437437
*/
438438
@NeverInline("deoptimize must have a separate stack frame")
439439
public static void deoptimizeAll() {
440-
DeoptimizeAllOperation vmOp = new DeoptimizeAllOperation();
440+
VMOperation.guaranteeNotInProgress("With a VM Operation in progress, we cannot determine the thread requesting deoptimization.");
441+
DeoptimizeAllOperation vmOp = new DeoptimizeAllOperation(CurrentIsolate.getCurrentThread());
441442
vmOp.enqueue();
442443
}
443444

444445
private static class DeoptimizeAllOperation extends JavaVMOperation {
445-
DeoptimizeAllOperation() {
446+
private final IsolateThread requestingThread;
447+
448+
DeoptimizeAllOperation(IsolateThread requestingThread) {
446449
super(VMOperationInfos.get(DeoptimizeAllOperation.class, "Deoptimize all", SystemEffect.SAFEPOINT));
450+
this.requestingThread = requestingThread;
447451
}
448452

449453
@Override
450454
protected void operate() {
451-
deoptimizeInRange(Word.zero(), Word.zero(), true);
455+
deoptimizeInRange(Word.zero(), Word.zero(), true, requestingThread);
452456
}
453457
}
454458

@@ -459,42 +463,42 @@ protected void operate() {
459463
* @param toIp The upper address (excluding) of the method's code.
460464
*/
461465
@NeverInline("deoptimize must have a separate stack frame")
462-
public static void deoptimizeInRange(CodePointer fromIp, CodePointer toIp, boolean deoptAll) {
466+
public static void deoptimizeInRange(CodePointer fromIp, CodePointer toIp, boolean deoptAll, IsolateThread requestingThread) {
463467
VMOperation.guaranteeInProgressAtSafepoint("Deoptimization requires a safepoint.");
464-
deoptimizeInRangeOperation(fromIp, toIp, deoptAll);
468+
deoptimizeInRangeOperation(fromIp, toIp, deoptAll, requestingThread);
465469
}
466470

467471
/** Deoptimize a specific method on all thread stacks. */
468472
@NeverInline("Starting a stack walk in the caller frame. " +
469473
"Note that we could start the stack frame also further down the stack, because VM operation frames never need deoptimization. " +
470474
"But we don't store stack frame information for the first frame we would need to process.")
471-
private static void deoptimizeInRangeOperation(CodePointer fromIp, CodePointer toIp, boolean deoptAll) {
475+
private static void deoptimizeInRangeOperation(CodePointer fromIp, CodePointer toIp, boolean deoptAll, IsolateThread requestingThread) {
472476
VMOperation.guaranteeInProgressAtSafepoint("Deoptimizer.deoptimizeInRangeOperation, but not in VMOperation.");
473477
/* Handle my own thread specially, because I do not have a JavaFrameAnchor. */
474478
Pointer sp = KnownIntrinsics.readCallerStackPointer();
475479

476-
StackFrameVisitor currentThreadDeoptVisitor = getStackFrameVisitor((Pointer) fromIp, (Pointer) toIp, deoptAll, CurrentIsolate.getCurrentThread());
480+
StackFrameVisitor currentThreadDeoptVisitor = getStackFrameVisitor((Pointer) fromIp, (Pointer) toIp, deoptAll, CurrentIsolate.getCurrentThread(), requestingThread);
477481
JavaStackWalker.walkCurrentThread(sp, currentThreadDeoptVisitor);
478482

479483
/* Deoptimize this method on all the other stacks. */
480484
for (IsolateThread vmThread = VMThreads.firstThread(); vmThread.isNonNull(); vmThread = VMThreads.nextThread(vmThread)) {
481485
if (vmThread == CurrentIsolate.getCurrentThread()) {
482486
continue;
483487
}
484-
StackFrameVisitor deoptVisitor = getStackFrameVisitor((Pointer) fromIp, (Pointer) toIp, deoptAll, vmThread);
488+
StackFrameVisitor deoptVisitor = getStackFrameVisitor((Pointer) fromIp, (Pointer) toIp, deoptAll, vmThread, requestingThread);
485489
JavaStackWalker.walkThread(vmThread, deoptVisitor);
486490
}
487491
maybeTestGC();
488492
}
489493

490-
private static StackFrameVisitor getStackFrameVisitor(Pointer fromIp, Pointer toIp, boolean deoptAll, IsolateThread targetThread) {
494+
private static StackFrameVisitor getStackFrameVisitor(Pointer fromIp, Pointer toIp, boolean deoptAll, IsolateThread targetThread, IsolateThread requestingThread) {
491495
return new StackFrameVisitor() {
492496
@Override
493497
public boolean visitRegularFrame(Pointer frameSp, CodePointer frameIp, CodeInfo codeInfo) {
494498
Pointer ip = (Pointer) frameIp;
495499
if ((ip.aboveOrEqual(fromIp) && ip.belowThan(toIp)) || deoptAll) {
496500
CodeInfoQueryResult queryResult = CodeInfoTable.lookupCodeInfoQueryResult(codeInfo, frameIp);
497-
Deoptimizer deoptimizer = new Deoptimizer(frameSp, queryResult, targetThread);
501+
Deoptimizer deoptimizer = new Deoptimizer(frameSp, queryResult, targetThread, requestingThread);
498502
deoptimizer.deoptSourceFrameLazily(frameIp, deoptAll);
499503
}
500504
return true;
@@ -544,7 +548,8 @@ private static void deoptimizeFrame0(Pointer sp, boolean ignoreNonDeoptimizable,
544548
return;
545549
}
546550

547-
DeoptimizeFrameOperation vmOp = new DeoptimizeFrameOperation(sp, ignoreNonDeoptimizable, speculation, targetThread, deoptEagerly);
551+
VMOperation.guaranteeNotInProgress("With a VM Operation in progress, we cannot determine the thread requesting deoptimization.");
552+
DeoptimizeFrameOperation vmOp = new DeoptimizeFrameOperation(sp, ignoreNonDeoptimizable, speculation, targetThread, deoptEagerly, CurrentIsolate.getCurrentThread());
548553
vmOp.enqueue();
549554
}
550555

@@ -553,23 +558,26 @@ private static class DeoptimizeFrameOperation extends JavaVMOperation {
553558
private final boolean ignoreNonDeoptimizable;
554559
private final SpeculationReason speculation;
555560
private final IsolateThread targetThread;
561+
private final IsolateThread requestingThread;
556562
private final boolean deoptEagerly;
557563

558-
DeoptimizeFrameOperation(Pointer sourceSp, boolean ignoreNonDeoptimizable, SpeculationReason speculation, IsolateThread targetThread, boolean deoptEagerly) {
564+
DeoptimizeFrameOperation(Pointer sourceSp, boolean ignoreNonDeoptimizable, SpeculationReason speculation, IsolateThread targetThread, boolean deoptEagerly, IsolateThread requestingThread) {
559565
super(VMOperationInfos.get(DeoptimizeFrameOperation.class, "Deoptimize frame", SystemEffect.SAFEPOINT));
560566
this.sourceSp = sourceSp;
561567
this.ignoreNonDeoptimizable = ignoreNonDeoptimizable;
562568
this.speculation = speculation;
563569
this.targetThread = targetThread;
564570
this.deoptEagerly = deoptEagerly;
571+
this.requestingThread = requestingThread;
572+
565573
if (Options.LazyDeoptimization.getValue() && deoptEagerly) {
566574
/*
567575
* If lazy deoptimization is enabled, eager deoptimization is only used for stack
568576
* introspection. We enforce that eager deoptimization cannot be applied to other
569577
* threads, because we do not want an eager deoptimization operation to interrupt
570578
* and interfere with a thread that is undergoing lazy deoptimization.
571579
*/
572-
assert targetThread == CurrentIsolate.getCurrentThread() : "With lazy deoptimization enabled, eager deoptimization cannot be used to deoptimize other threads";
580+
VMError.guarantee(targetThread == requestingThread, "With lazy deoptimization enabled, a thread can request eager deoptimization only on itself.");
573581
}
574582
}
575583

@@ -587,30 +595,33 @@ protected void operate() {
587595
uninstallLazyDeoptStubReturnAddress(sourceSp, targetThread);
588596
ip = FrameAccess.singleton().readReturnAddress(targetThread, sourceSp);
589597
}
590-
deoptimizeFrame(targetThread, sourceSp, ip, ignoreNonDeoptimizable, speculation, deoptEagerly);
598+
deoptimizeFrame(targetThread, sourceSp, ip, ignoreNonDeoptimizable, speculation, deoptEagerly, requestingThread);
591599
}
592600
}
593601

594602
@Uninterruptible(reason = "Prevent the GC from freeing the CodeInfo object.")
595-
private static void deoptimizeFrame(IsolateThread targetThread, Pointer sp, CodePointer ip, boolean ignoreNonDeoptimizable, SpeculationReason speculation, boolean deoptEagerly) {
603+
private static void deoptimizeFrame(IsolateThread targetThread, Pointer sp, CodePointer ip, boolean ignoreNonDeoptimizable, SpeculationReason speculation, boolean deoptEagerly,
604+
IsolateThread requestingThread) {
596605
UntetheredCodeInfo untetheredInfo = CodeInfoTable.lookupCodeInfo(ip);
597606
Object tether = CodeInfoAccess.acquireTether(untetheredInfo);
598607
try {
599608
CodeInfo info = CodeInfoAccess.convert(untetheredInfo, tether);
600-
deoptimize(targetThread, sp, ip, ignoreNonDeoptimizable, speculation, info, deoptEagerly);
609+
deoptimize(targetThread, sp, ip, ignoreNonDeoptimizable, speculation, info, deoptEagerly, requestingThread);
601610
} finally {
602611
CodeInfoAccess.releaseTether(untetheredInfo, tether);
603612
}
604613
}
605614

606615
@Uninterruptible(reason = "Pass the now protected CodeInfo object to interruptible code.", calleeMustBe = false)
607-
private static void deoptimize(IsolateThread targetThread, Pointer sp, CodePointer ip, boolean ignoreNonDeoptimizable, SpeculationReason speculation, CodeInfo info, boolean deoptEagerly) {
608-
deoptimize0(targetThread, sp, ip, ignoreNonDeoptimizable, speculation, info, deoptEagerly);
616+
private static void deoptimize(IsolateThread targetThread, Pointer sp, CodePointer ip, boolean ignoreNonDeoptimizable, SpeculationReason speculation, CodeInfo info, boolean deoptEagerly,
617+
IsolateThread requestingThread) {
618+
deoptimize0(targetThread, sp, ip, ignoreNonDeoptimizable, speculation, info, deoptEagerly, requestingThread);
609619
}
610620

611-
private static void deoptimize0(IsolateThread targetThread, Pointer sp, CodePointer ip, boolean ignoreNonDeoptimizable, SpeculationReason speculation, CodeInfo info, boolean deoptEagerly) {
621+
private static void deoptimize0(IsolateThread targetThread, Pointer sp, CodePointer ip, boolean ignoreNonDeoptimizable, SpeculationReason speculation, CodeInfo info, boolean deoptEagerly,
622+
IsolateThread requestingThread) {
612623
CodeInfoQueryResult queryResult = CodeInfoTable.lookupCodeInfoQueryResult(info, ip);
613-
Deoptimizer deoptimizer = new Deoptimizer(sp, queryResult, targetThread);
624+
Deoptimizer deoptimizer = new Deoptimizer(sp, queryResult, targetThread, requestingThread);
614625
if (deoptEagerly) {
615626
DeoptimizedFrame sourceFrame = deoptimizer.deoptSourceFrameEagerly(ip, ignoreNonDeoptimizable);
616627
if (sourceFrame != null) {
@@ -687,11 +698,13 @@ private static void registerSpeculationFailure(SubstrateInstalledCode installedC
687698
protected int targetContentSize;
688699

689700
private final DeoptState deoptState;
701+
private final IsolateThread requestingThread;
690702

691-
public Deoptimizer(Pointer sourceSp, CodeInfoQueryResult sourceChunk, IsolateThread targetThread) {
703+
public Deoptimizer(Pointer sourceSp, CodeInfoQueryResult sourceChunk, IsolateThread targetThread, IsolateThread requestingThread) {
692704
VMError.guarantee(sourceChunk != null, "Must not be null.");
693705
this.sourceChunk = sourceChunk;
694706
this.deoptState = new DeoptState(sourceSp, targetThread);
707+
this.requestingThread = requestingThread;
695708
}
696709

697710
public DeoptState getDeoptState() {
@@ -889,7 +902,7 @@ private static DeoptimizedFrame constructLazilyDeoptimizedFrameInterruptibly0(Po
889902
maybeTestGC();
890903
CodeInfoQueryResult sourceChunk = CodeInfoTable.lookupCodeInfoQueryResult(info, ip);
891904
maybeTestGC();
892-
Deoptimizer deoptimizer = new Deoptimizer(sourceSp, sourceChunk, CurrentIsolate.getCurrentThread());
905+
Deoptimizer deoptimizer = new Deoptimizer(sourceSp, sourceChunk, CurrentIsolate.getCurrentThread(), CurrentIsolate.getCurrentThread());
893906
maybeTestEagerDeoptInLazyDeoptFatalError(deoptimizer, ip);
894907
DeoptimizedFrame deoptFrame = deoptimizer.doDeoptSourceFrame(ip, true, false);
895908
if (hasException) {
@@ -1021,17 +1034,14 @@ static int savedBasePointerSize() {
10211034
*
10221035
* @param pc A code address inside the source method (= the method to deoptimize)
10231036
*/
1024-
public void deoptSourceFrameLazily(CodePointer pc, boolean ignoreNonDeoptimizable) {
1037+
private void deoptSourceFrameLazily(CodePointer pc, boolean ignoreNonDeoptimizable) {
10251038
assert VMOperation.isInProgressAtSafepoint();
10261039
if (!Options.LazyDeoptimization.getValue()) {
10271040
deoptSourceFrameEagerly(pc, ignoreNonDeoptimizable);
10281041
return;
10291042
}
1030-
if (checkLazyDeoptimized(deoptState.targetThread, deoptState.sourceSp)) {
1031-
// already lazily deoptimized, nothing to do
1032-
return;
1033-
} else if (checkEagerDeoptimized(deoptState.targetThread, deoptState.sourceSp) != null) {
1034-
// if already eagerly deoptimized, don't lazily deoptimize.
1043+
if (checkLazyDeoptimized(deoptState.targetThread, deoptState.sourceSp) || checkEagerDeoptimized(deoptState.targetThread, deoptState.sourceSp) != null) {
1044+
// already deoptimized, nothing to do
10351045
return;
10361046
}
10371047

@@ -1051,7 +1061,7 @@ public void deoptSourceFrameLazily(CodePointer pc, boolean ignoreNonDeoptimizabl
10511061
/**
10521062
* Deoptimizes a source frame eagerly.
10531063
*/
1054-
public DeoptimizedFrame deoptSourceFrameEagerly(CodePointer pc, boolean ignoreNonDeoptimizable) {
1064+
private DeoptimizedFrame deoptSourceFrameEagerly(CodePointer pc, boolean ignoreNonDeoptimizable) {
10551065
if (!canBeDeoptimized(sourceChunk.getFrameInfo())) {
10561066
if (ignoreNonDeoptimizable) {
10571067
return null;
@@ -1065,6 +1075,11 @@ public DeoptimizedFrame deoptSourceFrameEagerly(CodePointer pc, boolean ignoreNo
10651075
return operation.getResult();
10661076
}
10671077

1078+
public DeoptimizedFrame deoptimizeEagerly() {
1079+
VMError.guarantee(requestingThread == CurrentIsolate.getCurrentThread(), "This method should be called by the thread which creates the Deoptimizer.");
1080+
return deoptSourceFrameEagerly(sourceChunk.getIP(), false);
1081+
}
1082+
10681083
@Uninterruptible(reason = "Prevent stack walks from seeing an inconsistent stack.")
10691084
private static void installLazyDeoptStubReturnAddress(boolean returnValueIsObject, Pointer sourceSp, IsolateThread targetThread) {
10701085
assert Options.LazyDeoptimization.getValue();
@@ -1112,7 +1127,7 @@ private static final class EagerDeoptSourceFrameOperation extends JavaVMOperatio
11121127
this.ignoreNonDeoptimizable = ignoreNonDeoptimizable;
11131128
this.result = null;
11141129
if (Options.LazyDeoptimization.getValue()) {
1115-
assert receiver.deoptState.targetThread == CurrentIsolate.getCurrentThread() : "With lazy deoptimization enabled, eager deoptimization cannot be used to deoptimize other threads";
1130+
VMError.guarantee(receiver.deoptState.targetThread == receiver.requestingThread, "With lazy deoptimization enabled, a thread can request eager deoptimization only on itself.");
11161131
}
11171132
}
11181133

@@ -1144,9 +1159,10 @@ private static boolean canBeDeoptimized(FrameInfoQueryResult frame) {
11441159
}
11451160

11461161
private DeoptimizedFrame doDeoptSourceFrame(CodePointer pc, boolean ignoreNonDeoptimizable, boolean isEagerDeopt) {
1147-
assert !Options.LazyDeoptimization.getValue() ||
1148-
deoptState.targetThread == CurrentIsolate.getCurrentThread() : "with lazy deoptimization, this method may only be called for the current thread";
11491162
assert !isEagerDeopt || VMOperation.isInProgressAtSafepoint() : "eager deopts may only happen at a safepoint";
1163+
if (Options.LazyDeoptimization.getValue()) {
1164+
VMError.guarantee(deoptState.targetThread == requestingThread, "With lazy deoptimization enabled, this method may only be called for the requesting thread.");
1165+
}
11501166

11511167
DeoptimizedFrame existing = checkEagerDeoptimized(deoptState.targetThread, deoptState.sourceSp);
11521168
if (existing != null) {

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/stack/SubstrateStackIntrospection.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ class SubstrateInspectedFrame implements InspectedFrame {
214214
private Deoptimizer getDeoptimizer() {
215215
assert virtualFrame == null;
216216
if (deoptimizer == null) {
217-
deoptimizer = new Deoptimizer(sp, codeInfo, CurrentIsolate.getCurrentThread());
217+
deoptimizer = new Deoptimizer(sp, codeInfo, CurrentIsolate.getCurrentThread(), CurrentIsolate.getCurrentThread());
218218
}
219219
return deoptimizer;
220220
}
@@ -314,13 +314,13 @@ private VirtualFrame lookupVirtualFrame() {
314314
public void materializeVirtualObjects(boolean invalidateCode) {
315315
IsolateThread thread = CurrentIsolate.getCurrentThread();
316316
if (virtualFrame == null) {
317-
DeoptimizedFrame deoptimizedFrame = getDeoptimizer().deoptSourceFrameEagerly(ip, false);
317+
DeoptimizedFrame deoptimizedFrame = getDeoptimizer().deoptimizeEagerly();
318318
assert deoptimizedFrame == Deoptimizer.checkEagerDeoptimized(thread, sp);
319319
}
320320

321321
if (invalidateCode) {
322322
/*
323-
* Note that we deoptimize the our frame before invalidating the method, with would also
323+
* Note that we deoptimize our frame before invalidating the method, which would also
324324
* deoptimize our frame. But we would deoptimize it with new materialized objects, i.e.,
325325
* a virtual object that was accessed via a local variable before would now have a
326326
* different value.

0 commit comments

Comments
 (0)