Skip to content

Commit b0f5591

Browse files
committed
[GR-42996] More heap scanning refactoring.
PullRequest: graal/15529
2 parents 1447977 + 39feec1 commit b0f5591

File tree

5 files changed

+67
-34
lines changed

5 files changed

+67
-34
lines changed

substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/AbstractAnalysisEngine.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,20 @@ public void runAnalysis(DebugContext debugContext, Function<AnalysisUniverse, Bo
207207
private boolean analysisModified() {
208208
boolean analysisModified;
209209
try (Timer.StopTimer ignored = verifyHeapTimer.start()) {
210-
analysisModified = universe.getHeapVerifier().checkHeapSnapshot(metaAccess, executor, "after analysis", true);
210+
/*
211+
* After the analysis reaches a stable state check if the shadow heap contains all
212+
* objects reachable from roots. If this leads to analysis state changes, an additional
213+
* analysis iteration will be run.
214+
*
215+
* We reuse the analysis executor, which at this point should be in before-start state:
216+
* the analysis finished and it re-initialized the executor for the next iteration. The
217+
* verifier controls the life cycle of the executor: it starts it and then waits until
218+
* all operations are completed. The same executor is implicitly used by the shadow heap
219+
* scanner and the verifier also passes it to the root scanner, so when
220+
* checkHeapSnapshot returns all heap scanning and verification tasks are completed.
221+
*/
222+
assert executor.isBeforeStart();
223+
analysisModified = universe.getHeapVerifier().checkHeapSnapshot(metaAccess, executor, "during analysis", true);
211224
}
212225
/* Initialize for the next iteration. */
213226
executor.init(getTiming());

substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/HeapSnapshotVerifier.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,12 @@ public HeapSnapshotVerifier(BigBang bb, ImageHeap imageHeap, ImageHeapScanner sc
7373
verbosity = Options.HeapVerifierVerbosity.getValue(bb.getOptions());
7474
}
7575

76+
/**
77+
* Heap verification does a complete scan from roots (static fields and embedded constant) and
78+
* compares the object graph against the shadow heap. If any new reachable objects or primitive
79+
* values are found then the verifier automatically patches the shadow heap. If this is during
80+
* analysis then the heap scanner will also notify the analysis of the new objects.
81+
*/
7682
public boolean checkHeapSnapshot(UniverseMetaAccess metaAccess, CompletionExecutor executor, String phase, boolean forAnalysis) {
7783
info("Verifying the heap snapshot %s%s ...", phase, (forAnalysis ? ", iteration " + iterations : ""));
7884
analysisModified = false;

substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapScanner.java

Lines changed: 42 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -307,9 +307,7 @@ private Optional<JavaConstant> maybeReplace(JavaConstant constant, ScanReason re
307307
} else if (unwrapped instanceof ImageHeapConstant) {
308308
throw GraalError.shouldNotReachHere(formatReason("Double wrapping of constant. Most likely, the reachability analysis code itself is seen as reachable.", reason)); // ExcludeFromJacocoGeneratedReport
309309
}
310-
if (unwrapped instanceof String || unwrapped instanceof Enum<?>) {
311-
forceHashCodeComputation(unwrapped);
312-
}
310+
maybeForceHashCodeComputation(unwrapped);
313311

314312
/* Run all registered object replacers. */
315313
if (constant.getJavaKind() == JavaKind.Object) {
@@ -328,12 +326,26 @@ private Optional<JavaConstant> maybeReplace(JavaConstant constant, ScanReason re
328326
return Optional.empty();
329327
}
330328

329+
public static void maybeForceHashCodeComputation(Object constant) {
330+
if (constant instanceof String stringConstant) {
331+
forceHashCodeComputation(stringConstant);
332+
} else if (constant instanceof Enum<?> enumConstant) {
333+
/*
334+
* Starting with JDK 21, Enum caches the identity hash code in a separate hash field. We
335+
* want to allow Enum values to be manually marked as immutable objects, so we eagerly
336+
* initialize the hash field. This is safe because Enum.hashCode() is a final method,
337+
* i.e., cannot be overwritten by the user.
338+
*/
339+
forceHashCodeComputation(enumConstant);
340+
}
341+
}
342+
331343
/**
332344
* For immutable Strings and other objects in the native image heap, force eager computation of
333345
* the hash field.
334346
*/
335347
@SuppressFBWarnings(value = "RV_RETURN_VALUE_IGNORED", justification = "eager hash field computation")
336-
public static void forceHashCodeComputation(Object object) {
348+
private static void forceHashCodeComputation(Object object) {
337349
object.hashCode();
338350
}
339351

@@ -522,24 +534,6 @@ protected boolean skipScanning() {
522534
return false;
523535
}
524536

525-
/**
526-
* When a re-scanning is triggered while the analysis is running in parallel, it is necessary to
527-
* do the re-scanning in a separate executor task to avoid deadlocks. For example,
528-
* lookupJavaField might need to wait for the reachability handler to be finished that actually
529-
* triggered the re-scanning.
530-
*
531-
* In the (legacy) Feature.duringAnalysis state, the executor is not running and we must not
532-
* schedule new tasks, because that would be treated as "the analysis has not finished yet". So
533-
* in that case we execute the task directly.
534-
*/
535-
private void maybeRunInExecutor(CompletionExecutor.DebugContextRunnable task) {
536-
if (bb.executorIsStarted()) {
537-
bb.postTask(task);
538-
} else {
539-
task.run(null);
540-
}
541-
}
542-
543537
public void rescanRoot(Field reflectionField) {
544538
if (skipScanning()) {
545539
return;
@@ -714,7 +708,31 @@ protected AnalysisField lookupJavaField(String className, String fieldName) {
714708
return metaAccess.lookupJavaField(ReflectionUtil.lookupField(getClass(className), fieldName));
715709
}
716710

717-
public void postTask(Runnable task) {
718-
universe.getBigbang().postTask(debug -> task.run());
711+
/**
712+
* When a re-scanning is triggered while the analysis is running in parallel, it is necessary to
713+
* do the re-scanning in a separate executor task to avoid deadlocks. For example,
714+
* lookupJavaField might need to wait for the reachability handler to be finished that actually
715+
* triggered the re-scanning. We reuse the analysis executor, whose lifetime is controlled by
716+
* the analysis engine.
717+
*
718+
* In the (legacy) Feature.duringAnalysis state, the executor is not running and we must not
719+
* schedule new tasks, because that would be treated as "the analysis has not finished yet". So
720+
* in that case we execute the task directly.
721+
*/
722+
private void maybeRunInExecutor(CompletionExecutor.DebugContextRunnable task) {
723+
if (bb.executorIsStarted()) {
724+
bb.postTask(task);
725+
} else {
726+
task.run(null);
727+
}
728+
}
729+
730+
/**
731+
* Post the task to the analysis executor. Its lifetime is controlled by the analysis engine or
732+
* the heap verifier such that all heap scanning tasks are also completed when analysis reaches
733+
* a stable state or heap verification is completed.
734+
*/
735+
private void postTask(Runnable task) {
736+
bb.postTask(debug -> task.run());
719737
}
720738
}

substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/util/CompletionExecutor.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,10 @@ public void shutdown() {
277277
setState(State.UNUSED);
278278
}
279279

280+
public boolean isBeforeStart() {
281+
return state.get() == State.BEFORE_START;
282+
}
283+
280284
public boolean isStarted() {
281285
return state.get() == State.STARTED;
282286
}

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageHeap.java

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -352,16 +352,9 @@ public void addConstant(final JavaConstant constant, boolean immutableFromParent
352352
VMError.guarantee(identityHashCode != 0, "0 is used as a marker value for 'hash code not yet computed'");
353353

354354
Object objectConstant = hUniverse.getSnippetReflection().asObject(Object.class, uncompressed);
355+
ImageHeapScanner.maybeForceHashCodeComputation(objectConstant);
355356
if (objectConstant instanceof String stringConstant) {
356357
handleImageString(stringConstant);
357-
} else if (objectConstant instanceof Enum<?> enumConstant) {
358-
/*
359-
* Starting with JDK 21, Enum caches the identity hash code in a separate hash field. We
360-
* want to allow Enum values to be manually marked as immutable objects, so we eagerly
361-
* initialize the hash field. This is safe because Enum.hashCode() is a final method,
362-
* i.e., cannot be overwritten by the user.
363-
*/
364-
ImageHeapScanner.forceHashCodeComputation(enumConstant);
365358
}
366359

367360
final ObjectInfo existing = objects.get(uncompressed);
@@ -457,7 +450,6 @@ private boolean assertFillerObjectSizes() {
457450
}
458451

459452
private void handleImageString(final String str) {
460-
ImageHeapScanner.forceHashCodeComputation(str);
461453
if (HostedStringDeduplication.isInternedString(str)) {
462454
/* The string is interned by the host VM, so it must also be interned in our image. */
463455
assert internedStrings.containsKey(str) || internStringsPhase.isAllowed() : "Should not intern string during phase " + internStringsPhase.toString();

0 commit comments

Comments
 (0)