From db4c5e71f63a3fda6f91c50f063d81dd6e1ee204 Mon Sep 17 00:00:00 2001 From: Tom Shull Date: Fri, 18 Jul 2025 17:22:24 +0200 Subject: [PATCH] Install all GCCauses in initial layer. --- .../core/genscavenge/GenScavengeGCCause.java | 19 +++ .../src/com/oracle/svm/core/heap/GCCause.java | 136 ++++++++++++++++-- 2 files changed, 145 insertions(+), 10 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GenScavengeGCCause.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GenScavengeGCCause.java index 6db07ba2fb21..27fe62a6c77a 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GenScavengeGCCause.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GenScavengeGCCause.java @@ -24,7 +24,10 @@ */ package com.oracle.svm.core.genscavenge; +import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; +import com.oracle.svm.core.feature.InternalFeature; import com.oracle.svm.core.heap.GCCause; +import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport; final class GenScavengeGCCause extends GCCause { public static final GCCause OnAllocation = new GenScavengeGCCause("Collect on allocation", 10); @@ -33,3 +36,19 @@ private GenScavengeGCCause(String name, int id) { super(name, id); } } + +/** + * For layered builds we must eagerly register all GCCauses in the initial layer. + */ +@AutomaticallyRegisteredFeature +class GenScavengeGCCauseRegistration implements InternalFeature { + @Override + public boolean isInConfiguration(IsInConfigurationAccess access) { + return ImageLayerBuildingSupport.buildingInitialLayer(); + } + + @Override + public void duringSetup(DuringSetupAccess access) { + GCCause.registerGCCause(GenScavengeGCCause.OnAllocation); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/GCCause.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/GCCause.java index 13edc3851000..cac994d73f34 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/GCCause.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/GCCause.java @@ -24,7 +24,10 @@ */ package com.oracle.svm.core.heap; +import java.util.Collections; +import java.util.EnumSet; import java.util.List; +import java.util.function.Function; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; @@ -34,10 +37,19 @@ import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; import com.oracle.svm.core.feature.InternalFeature; +import com.oracle.svm.core.imagelayer.BuildingImageLayerPredicate; +import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport; +import com.oracle.svm.core.layeredimagesingleton.ImageSingletonLoader; +import com.oracle.svm.core.layeredimagesingleton.ImageSingletonWriter; +import com.oracle.svm.core.layeredimagesingleton.InitialLayerOnlyImageSingleton; +import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingleton; +import com.oracle.svm.core.layeredimagesingleton.LayeredImageSingletonBuilderFlags; import com.oracle.svm.core.util.DuplicatedInNativeCode; import com.oracle.svm.core.util.ImageHeapList; import com.oracle.svm.core.util.VMError; +import jdk.graal.compiler.debug.Assertions; + /** * This class holds garbage collection causes that are common and therefore shared between different * garbage collector implementations. @@ -77,34 +89,138 @@ public static GCCause fromId(int causeId) { public static List getGCCauses() { return ImageSingletons.lookup(GCCauseSupport.class).gcCauses; } + + @Platforms(Platform.HOSTED_ONLY.class) + public static void registerGCCause(GCCause cause) { + ImageSingletons.lookup(GCCauseSupport.class).installGCCause(cause); + } } @AutomaticallyRegisteredImageSingleton -class GCCauseSupport { +class GCCauseSupport implements InitialLayerOnlyImageSingleton { final List gcCauses = ImageHeapList.create(GCCause.class, null); @Platforms(Platform.HOSTED_ONLY.class) Object collectGCCauses(Object obj) { if (obj instanceof GCCause gcCause) { synchronized (gcCauses) { - int id = gcCause.getId(); - while (gcCauses.size() <= id) { - gcCauses.add(null); - } - var existing = gcCauses.set(id, gcCause); - if (existing != null && existing != gcCause) { - throw VMError.shouldNotReachHere("Two GCCause objects have the same id " + id + ": " + gcCause.getName() + ", " + existing.getName()); - } + installGCCause(gcCause); } } return obj; } + + @Platforms(Platform.HOSTED_ONLY.class) + void installGCCause(GCCause gcCause) { + int id = gcCause.getId(); + while (gcCauses.size() <= id) { + gcCauses.add(null); + } + var existing = gcCauses.set(id, gcCause); + if (existing != null && existing != gcCause) { + throw VMError.shouldNotReachHere("Two GCCause objects have the same id " + id + ": " + gcCause.getName() + ", " + existing.getName()); + } + } + + @Override + public boolean accessibleInFutureLayers() { + return true; + } + + @Override + public EnumSet getImageBuilderFlags() { + return LayeredImageSingletonBuilderFlags.ALL_ACCESS; + } } @AutomaticallyRegisteredFeature class GCCauseFeature implements InternalFeature { + @Override public void duringSetup(DuringSetupAccess access) { - access.registerObjectReplacer(ImageSingletons.lookup(GCCauseSupport.class)::collectGCCauses); + if (!ImageLayerBuildingSupport.buildingImageLayer()) { + /* + * In traditional builds we lazily register GCCauses as they become visible to the + * native-image generator. + */ + access.registerObjectReplacer(ImageSingletons.lookup(GCCauseSupport.class)::collectGCCauses); + } else { + /* + * For layered builds we eagerly register all GCCauses in the initial layer. In all + * layers, via an object replacer, we then validate all referenced GCCauses have been + * registered. + */ + Function idToGCCauseName; + if (ImageLayerBuildingSupport.buildingInitialLayer()) { + GCCauseSupport support = ImageSingletons.lookup(GCCauseSupport.class); + support.installGCCause(GCCause.JavaLangSystemGC); + support.installGCCause(GCCause.UnitTest); + support.installGCCause(GCCause.TestGCInDeoptimizer); + support.installGCCause(GCCause.HintedGC); + support.installGCCause(GCCause.JvmtiForceGC); + support.installGCCause(GCCause.HeapDump); + support.installGCCause(GCCause.DiagnosticCommand); + + var gcCauseList = GCCause.getGCCauses(); + idToGCCauseName = (idx) -> gcCauseList.get(idx).getName(); + } else { + var gcCauseNames = LayeredGCCauseTracker.getRegisteredGCCauses(); + idToGCCauseName = gcCauseNames::get; + } + access.registerObjectReplacer(obj -> { + if (obj instanceof GCCause gcCause) { + if (!idToGCCauseName.apply(gcCause.getId()).equals(gcCause.getName())) { + var id = gcCause.getId(); + VMError.shouldNotReachHere("Mismatch in GCCause name for id %s: %s %s", id, idToGCCauseName.apply(id), gcCause.getName()); + } + } + return obj; + }); + } + } +} + +/** + * In layered builds all {@link GCCause}s are registered and installed in the initial layer. Here we + * track which {@link GCCause}s were installed in the initial layer to detect issues. + */ +@AutomaticallyRegisteredImageSingleton(onlyWith = BuildingImageLayerPredicate.class) +class LayeredGCCauseTracker implements LayeredImageSingleton { + List registeredGCCauses; + + public static List getRegisteredGCCauses() { + assert ImageLayerBuildingSupport.buildingExtensionLayer(); + return ImageSingletons.lookup(LayeredGCCauseTracker.class).registeredGCCauses; + } + + @Override + public EnumSet getImageBuilderFlags() { + return LayeredImageSingletonBuilderFlags.BUILDTIME_ACCESS_ONLY; + } + + @Override + public PersistFlags preparePersist(ImageSingletonWriter writer) { + List gcCauses; + if (ImageLayerBuildingSupport.buildingInitialLayer()) { + gcCauses = GCCause.getGCCauses().stream().map(gcCause -> { + if (gcCause == null) { + return ""; + } else { + assert !gcCause.getName().isEmpty() : Assertions.errorMessage("Empty string is reserved for non-existent GCCauses", gcCause); + return gcCause.getName(); + } + }).toList(); + } else { + gcCauses = registeredGCCauses; + } + writer.writeStringList("registeredGCCauses", gcCauses); + return PersistFlags.CREATE; + } + + @SuppressWarnings("unused") + public static Object createFromLoader(ImageSingletonLoader loader) { + var causeTracker = new LayeredGCCauseTracker(); + causeTracker.registeredGCCauses = Collections.unmodifiableList(loader.readStringList("registeredGCCauses")); + return causeTracker; } }