From fcb622bd9fcda76ddaa0f0b8ec6bb21709195054 Mon Sep 17 00:00:00 2001 From: Codrut Stancu Date: Wed, 23 Jul 2025 01:05:33 +0200 Subject: [PATCH] Refactor layered class initialization: load base layer simulation result. --- .../ClassInitializationInfo.java | 12 +- .../SharedLayerSnapshotCapnProtoSchema.capnp | 50 ++++---- .../svm/hosted/NativeImageGenerator.java | 13 +- .../analysis/DynamicHubInitializer.java | 14 ++- .../ClassInitializationFeature.java | 2 +- ...SimulateClassInitializerClusterMember.java | 2 + ...ClassInitializerConstantFieldProvider.java | 2 +- .../SimulateClassInitializerResult.java | 6 + .../SimulateClassInitializerSupport.java | 88 ++++++++++--- .../imagelayer/SVMImageLayerLoader.java | 118 ++++++++++++++---- .../imagelayer/SVMImageLayerWriter.java | 21 +++- ...redLayerSnapshotCapnProtoSchemaHolder.java | 87 ++++++++++--- .../InlineBeforeAnalysisGraphDecoderImpl.java | 4 +- 13 files changed, 322 insertions(+), 97 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/classinitialization/ClassInitializationInfo.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/classinitialization/ClassInitializationInfo.java index 89086581c61e..3e8fa2aa35b7 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/classinitialization/ClassInitializationInfo.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/classinitialization/ClassInitializationInfo.java @@ -27,8 +27,6 @@ import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; -import com.oracle.svm.core.hub.RuntimeClassLoading; -import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.CurrentIsolate; import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.Platform; @@ -39,6 +37,7 @@ import com.oracle.svm.core.c.InvokeJavaFunctionPointer; import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.core.hub.PredefinedClassesSupport; +import com.oracle.svm.core.hub.RuntimeClassLoading; import com.oracle.svm.core.jdk.InternalVMMethod; import com.oracle.svm.core.snippets.SubstrateForeignCallTarget; import com.oracle.svm.core.thread.ContinuationSupport; @@ -46,6 +45,7 @@ import com.oracle.svm.core.thread.Target_jdk_internal_vm_Continuation; import com.oracle.svm.core.util.VMError; +import jdk.graal.compiler.word.Word; import jdk.internal.misc.Unsafe; import jdk.internal.reflect.Reflection; @@ -246,6 +246,14 @@ public ClassInitializationInfo(boolean typeReachedTracked) { this.initLock = new ReentrantLock(); } + public InitState getInitState() { + return initState; + } + + public boolean isSlowPathRequired() { + return slowPathRequired; + } + public boolean hasInitializer() { return hasInitializer; } diff --git a/substratevm/src/com.oracle.svm.hosted/resources/SharedLayerSnapshotCapnProtoSchema.capnp b/substratevm/src/com.oracle.svm.hosted/resources/SharedLayerSnapshotCapnProtoSchema.capnp index 9da49f014238..50bec81d1590 100644 --- a/substratevm/src/com.oracle.svm.hosted/resources/SharedLayerSnapshotCapnProtoSchema.capnp +++ b/substratevm/src/com.oracle.svm.hosted/resources/SharedLayerSnapshotCapnProtoSchema.capnp @@ -29,33 +29,38 @@ struct PersistedAnalysisType { isInitialized @9 :Bool; # True if the type was configured as initialized at BUILD_TIME but initialization failed so it was registered as RUN_TIME. isFailedInitialization @10 :Bool; - isLinked @11 :Bool; - sourceFileName @12 :Text; - enclosingTypeId @13 :TypeId; - componentTypeId @14 :TypeId; - superClassTypeId @15 :TypeId; - isInstantiated @16 :Bool; - isUnsafeAllocated @17 :Bool; - isReachable @18 :Bool; - interfaces @19 :List(TypeId); - instanceFieldIds @20 :List(FieldId); - instanceFieldIdsWithSuper @21 :List(FieldId); - staticFieldIds @22 :List(FieldId); - annotationList @23 :List(Annotation); - classInitializationInfo @24 :ClassInitializationInfo; - hasArrayType @25 :Bool; - subTypes @26 :List(TypeId); - isAnySubtypeInstantiated @27 :Bool; + # Type's initializer simulation succeeded. We'll also persist simulated field values. + isSuccessfulSimulation @11 :Bool; + # Type's initializer simulation failed. + isFailedSimulation @12 :Bool; + isLinked @13 :Bool; + sourceFileName @14 :Text; + enclosingTypeId @15 :TypeId; + componentTypeId @16 :TypeId; + superClassTypeId @17 :TypeId; + isInstantiated @18 :Bool; + isUnsafeAllocated @19 :Bool; + isReachable @20 :Bool; + interfaces @21 :List(TypeId); + instanceFieldIds @22 :List(FieldId); + instanceFieldIdsWithSuper @23 :List(FieldId); + staticFieldIds @24 :List(FieldId); + annotationList @25 :List(Annotation); + classInitializationInfo @26 :ClassInitializationInfo; + hasArrayType @27 :Bool; + hasClassInitInfo @28 :Bool; + subTypes @29 :List(TypeId); + isAnySubtypeInstantiated @30 :Bool; wrappedType :union { - none @28 :Void; # default + none @31 :Void; # default serializationGenerated :group { - rawDeclaringClass @29 :Text; - rawTargetConstructor @30 :Text; + rawDeclaringClass @32 :Text; + rawTargetConstructor @33 :Text; } lambda :group { - capturingClass @31 :Text; + capturingClass @34 :Text; } - proxyType @32 :Void; + proxyType @35 :Void; } } @@ -151,6 +156,7 @@ struct PersistedAnalysisField { name @16 :Text; priorInstalledLayerNum @17 :Int32; assignmentStatus @18 :Int32; + simulatedFieldValue @19 :ConstantReference; } struct CEntryPointLiteralReference { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java index 3f748e95e6cc..f7f645f93a3e 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java @@ -1017,8 +1017,9 @@ protected void setupNativeImage(OptionValues options, Map[] retrieveEnumConstantArray(AnalysisType type, Class javaClas private void buildClassInitializationInfo(ImageHeapScanner heapScanner, AnalysisType type, DynamicHub hub, boolean rescan) { AnalysisError.guarantee(hub.getClassInitializationInfo() == null, "Class initialization info already computed for %s.", type.toJavaName(true)); - boolean initializedAtBuildTime = SimulateClassInitializerSupport.singleton().trySimulateClassInitializer(bb, type); + boolean initializedOrSimulated = SimulateClassInitializerSupport.singleton().trySimulateClassInitializer(bb, type); ClassInitializationInfo info; if (type.getWrapped() instanceof BaseLayerType) { - info = HostedImageLayerBuildingSupport.singleton().getLoader().getClassInitializationInfo(type); + info = layerLoader.getClassInitializationInfo(type); } else { boolean typeReachedTracked = ClassInitializationSupport.singleton().requiresInitializationNodeForTypeReached(type); - if (initializedAtBuildTime) { + if (initializedOrSimulated) { info = type.getClassInitializer() == null ? ClassInitializationInfo.forNoInitializerInfo(typeReachedTracked) : ClassInitializationInfo.forInitializedInfo(typeReachedTracked); } else { info = buildRuntimeInitializationInfo(type, typeReachedTracked); } + VMError.guarantee(!type.isInBaseLayer() || layerLoader.isInitializationInfoStable(type, info)); } hub.setClassInitializationInfo(info); if (rescan) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationFeature.java index c5c3377f5799..8b450418d911 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationFeature.java @@ -290,7 +290,7 @@ private void reportKind(AfterAnalysisAccessImpl access, PrintWriter writer, Init if (kind != BUILD_TIME) { Optional type = access.getMetaAccess().optionalLookupJavaType(clazz); if (type.isPresent()) { - simulated = SimulateClassInitializerSupport.singleton().isClassInitializerSimulated(type.get()); + simulated = SimulateClassInitializerSupport.singleton().isSimulatedOrInitializedAtBuildTime(type.get()); } } if (simulated) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/SimulateClassInitializerClusterMember.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/SimulateClassInitializerClusterMember.java index f447f4ad11b4..00fa8e6c7d87 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/SimulateClassInitializerClusterMember.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/SimulateClassInitializerClusterMember.java @@ -46,7 +46,9 @@ public final class SimulateClassInitializerClusterMember { final AnalysisType type; final EconomicSet dependencies = EconomicSet.create(); + /** Keeps track of why the type could not be simulated as initialized. */ final List notInitializedReasons = new ArrayList<>(); + /** The values resulting from a successful simulation. */ final EconomicMap staticFieldValues = EconomicMap.create(); /** The mutable status field of the cluster member. */ diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/SimulateClassInitializerConstantFieldProvider.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/SimulateClassInitializerConstantFieldProvider.java index 206cd145584c..345eb999a801 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/SimulateClassInitializerConstantFieldProvider.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/SimulateClassInitializerConstantFieldProvider.java @@ -48,6 +48,6 @@ final class SimulateClassInitializerConstantFieldProvider extends AnalysisConsta @Override protected boolean isClassInitialized(ResolvedJavaField field) { - return support.isClassInitializerSimulated((AnalysisType) field.getDeclaringClass()); + return support.isSimulatedOrInitializedAtBuildTime((AnalysisType) field.getDeclaringClass()); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/SimulateClassInitializerResult.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/SimulateClassInitializerResult.java index 3039adf6b63e..2de501f42b13 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/SimulateClassInitializerResult.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/SimulateClassInitializerResult.java @@ -40,10 +40,16 @@ */ final class SimulateClassInitializerResult { + /** We didn't try simulation either because the feature is disabled or type's linking failed. */ static final SimulateClassInitializerResult NOT_SIMULATED_INITIALIZED = new SimulateClassInitializerResult(false, null); + /** We tried simulating the type's initializer but failed. */ + static final SimulateClassInitializerResult FAILED_SIMULATED_INITIALIZED = new SimulateClassInitializerResult(false, null); + /** Type was already initialized in the host VM. We didn't try to simulate it. */ static final SimulateClassInitializerResult INITIALIZED_HOSTED = SimulateClassInitializerResult.forInitialized(EconomicMap.emptyMap()); + /** True if the class initializer was successfully simulated as initialized. */ final boolean simulatedInitialized; + /** The simulated field values published in case of a successful simulation. */ final UnmodifiableEconomicMap staticFieldValues; static SimulateClassInitializerResult forInitialized(EconomicMap staticFieldValues) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/SimulateClassInitializerSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/SimulateClassInitializerSupport.java index f64d94418c43..2e71f5934d8e 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/SimulateClassInitializerSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/SimulateClassInitializerSupport.java @@ -30,6 +30,7 @@ import java.util.concurrent.ConcurrentMap; import java.util.stream.Collectors; +import org.graalvm.collections.EconomicMap; import org.graalvm.collections.EconomicSet; import org.graalvm.nativeimage.ImageSingletons; @@ -40,6 +41,7 @@ import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; import com.oracle.graal.pointsto.meta.AnalysisMethod; import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.graal.pointsto.meta.BaseLayerType; import com.oracle.graal.pointsto.meta.HostedProviders; import com.oracle.graal.pointsto.phases.InlineBeforeAnalysis; import com.oracle.graal.pointsto.phases.InlineBeforeAnalysisGraphDecoder; @@ -49,6 +51,8 @@ import com.oracle.svm.hosted.ameta.AnalysisConstantReflectionProvider; import com.oracle.svm.hosted.ameta.FieldValueInterceptionSupport; import com.oracle.svm.hosted.fieldfolding.MarkStaticFinalFieldInitializedNode; +import com.oracle.svm.hosted.imagelayer.HostedImageLayerBuildingSupport; +import com.oracle.svm.hosted.imagelayer.SVMImageLayerLoader; import com.oracle.svm.hosted.meta.HostedConstantReflectionProvider; import com.oracle.svm.hosted.meta.HostedType; import com.oracle.svm.hosted.phases.InlineBeforeAnalysisGraphDecoderImpl; @@ -56,6 +60,7 @@ import jdk.graal.compiler.core.common.spi.ConstantFieldProvider; import jdk.graal.compiler.debug.DebugContext; +import jdk.graal.compiler.debug.DebugContext.Scope; import jdk.graal.compiler.graph.Node; import jdk.graal.compiler.nodes.BeginNode; import jdk.graal.compiler.nodes.ConstantNode; @@ -180,6 +185,7 @@ public class SimulateClassInitializerSupport { protected final int maxInlineDepth = ClassInitializationOptions.SimulateClassInitializerMaxInlineDepth.getValue(); protected final int maxLoopIterations = ClassInitializationOptions.SimulateClassInitializerMaxLoopIterations.getValue(); protected final int maxAllocatedBytes = ClassInitializationOptions.SimulateClassInitializerMaxAllocatedBytes.getValue(); + private final SVMImageLayerLoader layerLoader; public static SimulateClassInitializerSupport singleton() { return ImageSingletons.lookup(SimulateClassInitializerSupport.class); @@ -189,6 +195,7 @@ public static SimulateClassInitializerSupport singleton() { public SimulateClassInitializerSupport(AnalysisMetaAccess aMetaAccess, SVMHost hostVM) { simulateClassInitializerPolicy = new SimulateClassInitializerPolicy(hostVM, this); simulatedFieldValueConstantFieldProvider = new SimulateClassInitializerConstantFieldProvider(aMetaAccess, hostVM, this); + layerLoader = HostedImageLayerBuildingSupport.singleton().getLoader(); } public boolean isEnabled() { @@ -198,9 +205,12 @@ public boolean isEnabled() { /** * Initiate the simulation of the class initializer, unless there is already a published result * available. - * - * The method returns true if the provided type is either initialized at build time or the - * simulation succeeded, i.e., if the type starts out as "initialized" at image run time. + *

+ * The method returns {@code true} if either the simulation succeeded or the type is initialized + * at build time in the host VM, i.e., {@link AnalysisType#isInitialized()} returns + * {@code true}. In both cases the type starts out as "initialized" at image run time. + *

+ * In layered image builds the simulation result is loaded from previous layers, if available. */ @SuppressWarnings("try") public boolean trySimulateClassInitializer(BigBang bb, AnalysisType type) { @@ -314,7 +324,11 @@ public JavaConstant getSimulatedFieldValue(AnalysisField field) { } } - public boolean isClassInitializerSimulated(AnalysisType type) { + /** + * Returns {@code true} if the type was either successfully simulated or it is initialized at + * build time in the host VM, i.e., {@link AnalysisType#isInitialized()} returns {@code true}. + */ + public boolean isSimulatedOrInitializedAtBuildTime(AnalysisType type) { var existingResult = lookupPublishedSimulateClassInitializerResult(type); if (existingResult != null) { return existingResult.simulatedInitialized; @@ -322,8 +336,24 @@ public boolean isClassInitializerSimulated(AnalysisType type) { return false; } + /** Returns {@code true} iff the type was successfully simulated. */ + public boolean isSuccessfulSimulation(AnalysisType type) { + var existingResult = lookupPublishedSimulateClassInitializerResult(type); + if (existingResult != null && existingResult != SimulateClassInitializerResult.INITIALIZED_HOSTED) { + return existingResult.simulatedInitialized; + } + return false; + } + + /** Returns {@code true} if the type simulation failed. */ + public boolean isFailedSimulation(AnalysisType type) { + var existingResult = lookupPublishedSimulateClassInitializerResult(type); + return existingResult == SimulateClassInitializerResult.FAILED_SIMULATED_INITIALIZED; + } + private SimulateClassInitializerResult lookupPublishedSimulateClassInitializerResult(AnalysisType type) { if (!type.isLinked()) { + /* We try linking the AnalysisType when we create it. This means linking failed. */ return SimulateClassInitializerResult.NOT_SIMULATED_INITIALIZED; } else if (type.isInitialized()) { /* @@ -337,6 +367,22 @@ private SimulateClassInitializerResult lookupPublishedSimulateClassInitializerRe if (!enabled) { return SimulateClassInitializerResult.NOT_SIMULATED_INITIALIZED; } + if (type.isInBaseLayer()) { + if (type.getWrapped() instanceof BaseLayerType) { + return SimulateClassInitializerResult.NOT_SIMULATED_INITIALIZED; + } + /* Record type's simulation result loaded from the previous layer. */ + var sharedLayerSimulation = layerLoader.getSimulationResult(type); + if (sharedLayerSimulation != null) { + if (sharedLayerSimulation.successful()) { + var existingResult = recordSuccessfulSimulation(type, sharedLayerSimulation.staticFieldValues()); + assert existingResult == null || existingResult.simulatedInitialized : "Found unexpected simulation result."; + } else { + var existingResult = recordFailedSimulation(type); + assert existingResult == null || !existingResult.simulatedInitialized : "Found unexpected simulation result."; + } + } + } return analyzedClasses.get(type); } @@ -355,10 +401,10 @@ boolean trySimulateClassInitializer(DebugContext debug, AnalysisType type, Simul } checkStrictlyInitializeAtRunTime(clusterMember); - if (clusterMember.notInitializedReasons.size() == 0 || collectAllReasons) { + if (clusterMember.notInitializedReasons.isEmpty() || collectAllReasons) { addSuperDependencies(debug, clusterMember); } - if (clusterMember.notInitializedReasons.size() == 0 || collectAllReasons) { + if (clusterMember.notInitializedReasons.isEmpty() || collectAllReasons) { addClassInitializerDependencies(clusterMember); } @@ -446,6 +492,7 @@ private void addClassInitializerDependencies(SimulateClassInitializerClusterMemb if (classInitializer == null) { return; } + VMError.guarantee(!classInitializer.isInBaseLayer(), "Trying to simulate a class initializer already simulated in a previous layer."); StructuredGraph graph; try { @@ -473,11 +520,7 @@ private StructuredGraph decodeGraph(SimulateClassInitializerClusterMember cluste .recordInlinedMethods(analysisParsedGraph.getEncodedGraph().isRecordingInlinedMethods()) .build(); - if (classInitializer.isInBaseLayer()) { - throw SimulateClassInitializerAbortException.doAbort(clusterMember, result, "The class initializer was already simulated in the base layer."); - } - - try (var scope = debug.scope("SimulateClassInitializerGraphDecoder", result)) { + try (var scope = debug.scope("GraphDecoderSimulateClassInitializer", result)) { var decoder = createGraphDecoder(clusterMember, bb, result); decoder.decode(classInitializer); @@ -536,6 +579,10 @@ private static void processEffectsOfNode(SimulateClassInitializerClusterMember c clusterMember.notInitializedReasons.add(node); } + /** + * To enable logging use {@code -H:Log=SimulateClassInitializer} to match the {@link Scope} + * opened by {@link #trySimulateClassInitializer(BigBang, AnalysisType)}. + */ private void publishResults(DebugContext debug, boolean simulatedInitialized, EconomicSet transitiveDependencies) { for (var clusterMember : transitiveDependencies) { if (clusterMember.status.published) { @@ -549,7 +596,7 @@ private void publishResults(DebugContext debug, boolean simulatedInitialized, Ec if (debug.isLogEnabled(DebugContext.BASIC_LEVEL)) { debug.log("simulated: %s", clusterMember.type.toJavaName(true)); } - existingResult = analyzedClasses.putIfAbsent(clusterMember.type, SimulateClassInitializerResult.forInitialized(clusterMember.staticFieldValues)); + existingResult = recordSuccessfulSimulation(clusterMember.type, clusterMember.staticFieldValues); clusterMember.status = SimulateClassInitializerStatus.PUBLISHED_AS_INITIALIZED; } else { @@ -560,7 +607,7 @@ private void publishResults(DebugContext debug, boolean simulatedInitialized, Ec .filter(s -> s != null && !s.isEmpty()) .collect(Collectors.joining(System.lineSeparator() + " "))); } - existingResult = analyzedClasses.putIfAbsent(clusterMember.type, SimulateClassInitializerResult.NOT_SIMULATED_INITIALIZED); + existingResult = recordFailedSimulation(clusterMember.type); clusterMember.status = SimulateClassInitializerStatus.PUBLISHED_AS_NOT_INITIALIZED; } if (existingResult != null && simulatedInitialized != existingResult.simulatedInitialized) { @@ -578,6 +625,18 @@ private void publishResults(DebugContext debug, boolean simulatedInitialized, Ec } } + private SimulateClassInitializerResult recordFailedSimulation(AnalysisType type) { + return recordSimulationResult(type, SimulateClassInitializerResult.FAILED_SIMULATED_INITIALIZED); + } + + private SimulateClassInitializerResult recordSuccessfulSimulation(AnalysisType type, EconomicMap staticFieldValues) { + return recordSimulationResult(type, SimulateClassInitializerResult.forInitialized(staticFieldValues)); + } + + private SimulateClassInitializerResult recordSimulationResult(AnalysisType type, SimulateClassInitializerResult simulationResult) { + return analyzedClasses.putIfAbsent(type, simulationResult); + } + private boolean collectTransitiveDependencies(SimulateClassInitializerClusterMember clusterMember, EconomicSet transitiveDependencies) { if (clusterMember.status == SimulateClassInitializerStatus.COLLECTING_DEPENDENCIES) { return true; @@ -604,8 +663,7 @@ private static String reasonToString(HostedProviders providers, Object reason) { if (reason instanceof AnalysisType type) { return "superclass/interface: " + type.toJavaName(true); } else if (reason instanceof EnsureClassInitializedNode node && node.constantTypeOrNull(providers.getConstantReflection()) != null) { - return "class initializer dependency: " + - ((EnsureClassInitializedNode) reason).constantTypeOrNull(providers.getConstantReflection()).toJavaName(true) + + return "class initializer dependency: " + node.constantTypeOrNull(providers.getConstantReflection()).toJavaName(true) + " " + node.getNodeSourcePosition(); } else if (reason instanceof Node node) { if (node instanceof BeginNode || node instanceof ExceptionObjectNode || node instanceof MergeNode || node instanceof EndNode) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/SVMImageLayerLoader.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/SVMImageLayerLoader.java index 374c67ec0b6a..c9db1f9ca390 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/SVMImageLayerLoader.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/SVMImageLayerLoader.java @@ -671,6 +671,7 @@ private static int getBaseLayerTypeId(AnalysisType type) { @SuppressWarnings("try") private void initializeBaseLayerTypeBeforePublishing(AnalysisType type, PersistedAnalysisType.Reader typeData) { assert !(type.getWrapped() instanceof BaseLayerType); + VMError.guarantee(type.isLinked() == typeData.getIsLinked()); /* * For types reachable in this layer register the *computed* initialization kind extracted * from the previous layer. This will cause base layer types to have a *strict* @@ -687,20 +688,25 @@ private void initializeBaseLayerTypeBeforePublishing(AnalysisType type, Persiste Class clazz = OriginalClassProvider.getJavaClass(type); if (typeData.getIsInitialized()) { classInitializationSupport.withUnsealedConfiguration(() -> classInitializationSupport.initializeAtBuildTime(clazz, "computed in a previous layer")); + } else if (typeData.getIsFailedInitialization()) { + /* + * In the previous layer this class was configured with --initialize-at-build-time but + * its initialization failed so it was registered as run time initialized. We attempt to + * init it again in this layer and verify that it fails. This will allow the class to be + * configured again in this layer with --initialize-at-build-time, either before or + * after this step. + */ + classInitializationSupport.withUnsealedConfiguration(() -> classInitializationSupport.initializeAtBuildTime(clazz, "computed in a previous layer")); + VMError.guarantee(classInitializationSupport.isFailedInitialization(clazz), "Expected the initialization to fail for %s, as it has failed in a previous layer.", clazz); + } else if (typeData.getIsSuccessfulSimulation() || typeData.getIsFailedSimulation()) { + /* + * Simulation for this type was tried in a previous layer, and regardless whether it + * succeeded or failed there's nothing to do here. We'll record the result in the + * simulation registry when its simulation state is queried. We can do this lazily since + * there is no API to modify simulation state, unlike for initialization. + */ } else { - if (typeData.getIsFailedInitialization()) { - /* - * In the previous layer this class was configured with --initialize-at-build-time - * but its initialization failed so it was registered as run time initialized. We - * attempt to init it again in this layer and verify that it fails. This will allow - * the class to be configured again in this layer with --initialize-at-build-time, - * either before or after this step. - */ - classInitializationSupport.withUnsealedConfiguration(() -> classInitializationSupport.initializeAtBuildTime(clazz, "computed in a previous layer")); - VMError.guarantee(classInitializationSupport.isFailedInitialization(clazz), "Expected the initialization to fail for %s, as it has failed in a previous layer.", clazz); - } else { - classInitializationSupport.withUnsealedConfiguration(() -> classInitializationSupport.initializeAtRunTime(clazz, "computed in a previous layer")); - } + classInitializationSupport.withUnsealedConfiguration(() -> classInitializationSupport.initializeAtRunTime(clazz, "computed in a previous layer")); } /* Extract and record the base layer identity hashcode for this type. */ @@ -1800,8 +1806,35 @@ private void scanCompanionField(DynamicHub hub) { instance.readFieldValue(metaAccess.lookupJavaField(dynamicHubCompanionField)); } + public record LayeredSimulationResult(boolean successful, EconomicMap staticFieldValues) { + } + + public LayeredSimulationResult getSimulationResult(AnalysisType type) { + PersistedAnalysisType.Reader typeData = findType(getBaseLayerTypeId(type)); + + if (typeData.getIsSuccessfulSimulation()) { + EconomicMap staticFieldValues = EconomicMap.create(); + for (ResolvedJavaField field : type.getStaticFields()) { + AnalysisField aField = (AnalysisField) field; + PersistedAnalysisField.Reader fieldData = getFieldData(aField); + if (fieldData.hasSimulatedFieldValue()) { + JavaConstant simulatedFieldValue = readConstant(fieldData.getSimulatedFieldValue()); + staticFieldValues.put(aField, simulatedFieldValue); + } + } + return new LayeredSimulationResult(true, staticFieldValues); + } else if (typeData.getIsFailedSimulation()) { + return new LayeredSimulationResult(false, null); + } + return null; + } + public ClassInitializationInfo getClassInitializationInfo(AnalysisType aType) { PersistedAnalysisType.Reader typeData = findType(getBaseLayerTypeId(aType)); + if (!typeData.getHasClassInitInfo()) { + /* Type metadata was not initialized in base layer. */ + return null; + } var initInfo = typeData.getClassInitializationInfo(); if (initInfo.getIsNoInitializerNoTracking()) { return ClassInitializationInfo.forNoInitializerInfo(false); @@ -1828,6 +1861,51 @@ public ClassInitializationInfo getClassInitializationInfo(AnalysisType aType) { } } + /** + * Check that the class initialization info reconstructed from the loaded metadata matches the + * info created in this layer. This doesn't do a complete equality check between + * {@link ClassInitializationInfo} objects, just of fields related to the state. + */ + public boolean isInitializationInfoStable(AnalysisType type, ClassInitializationInfo newInfo) { + ClassInitializationInfo previousInfo = getClassInitializationInfo(type); + if (previousInfo == null) { + /* Type metadata was not initialized in base layer. */ + return true; + } + boolean equal = newInfo.getInitState() == previousInfo.getInitState() && + newInfo.isBuildTimeInitialized() == previousInfo.isBuildTimeInitialized() && + newInfo.isSlowPathRequired() == previousInfo.isSlowPathRequired() && + newInfo.hasInitializer() == previousInfo.hasInitializer() && + newInfo.getTypeReached() == previousInfo.getTypeReached(); + if (!equal) { + Function asString = (info) -> "ClassInitializationInfo {" + + ", initState = " + info.getInitState() + + ", buildTimeInit = " + info.isBuildTimeInitialized() + + ", slowPathRequired = " + info.isSlowPathRequired() + + ", hasInitializer = " + info.hasInitializer() + + ", typeReached = " + info.getTypeReached() + '}'; + throw VMError.shouldNotReachHere("Class initialization info not stable between layers for type %s.\nPrevious info: %s.\nNew info: %s", + type, asString.apply(previousInfo), asString.apply(newInfo)); + } + return true; + } + + private JavaConstant readConstant(ConstantReference.Reader constantReference) { + return switch (constantReference.which()) { + case OBJECT_CONSTANT -> { + int id = constantReference.getObjectConstant().getConstantId(); + yield id == 0 ? null : getOrCreateConstant(id); + } + case NULL_POINTER -> JavaConstant.NULL_POINTER; + case PRIMITIVE_VALUE -> { + PrimitiveValue.Reader pv = constantReference.getPrimitiveValue(); + yield JavaConstant.forPrimitive((char) pv.getTypeChar(), pv.getRawValue()); + } + default -> + throw GraalError.shouldNotReachHere("Unexpected constant reference: " + constantReference.which()); + }; + } + public static class JavaConstantSupplier { private final ConstantReference.Reader constantReference; @@ -1836,19 +1914,9 @@ public static class JavaConstantSupplier { } public JavaConstant get(SVMImageLayerLoader imageLayerLoader) { - return switch (constantReference.which()) { - case OBJECT_CONSTANT -> { - int id = constantReference.getObjectConstant().getConstantId(); - yield id == 0 ? null : imageLayerLoader.getOrCreateConstant(id); - } - case NULL_POINTER -> JavaConstant.NULL_POINTER; - case PRIMITIVE_VALUE -> { - PrimitiveValue.Reader pv = constantReference.getPrimitiveValue(); - yield JavaConstant.forPrimitive((char) pv.getTypeChar(), pv.getRawValue()); - } - default -> throw GraalError.shouldNotReachHere("Unexpected constant reference: " + constantReference.which()); - }; + return imageLayerLoader.readConstant(constantReference); } + } public static JavaConstantSupplier getConstant(ConstantReference.Reader constantReference) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/SVMImageLayerWriter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/SVMImageLayerWriter.java index a188ba628ae8..0a281b3a35ef 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/SVMImageLayerWriter.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/SVMImageLayerWriter.java @@ -124,6 +124,7 @@ import com.oracle.svm.hosted.annotation.AnnotationMetadata; import com.oracle.svm.hosted.annotation.CustomSubstitutionType; import com.oracle.svm.hosted.classinitialization.ClassInitializationSupport; +import com.oracle.svm.hosted.classinitialization.SimulateClassInitializerSupport; import com.oracle.svm.hosted.code.CEntryPointCallStubMethod; import com.oracle.svm.hosted.code.CEntryPointCallStubSupport; import com.oracle.svm.hosted.code.FactoryMethod; @@ -210,6 +211,7 @@ public class SVMImageLayerWriter extends ImageLayerWriter { private NativeImageHeap nativeImageHeap; private HostedUniverse hUniverse; private final ClassInitializationSupport classInitializationSupport; + private SimulateClassInitializerSupport simulateClassInitializerSupport; private boolean polymorphicSignatureSealed = false; @@ -292,6 +294,10 @@ public void setAnalysisUniverse(AnalysisUniverse aUniverse) { this.aUniverse = aUniverse; } + public void setSimulateClassInitializerSupport(SimulateClassInitializerSupport simulateClassInitializerSupport) { + this.simulateClassInitializerSupport = simulateClassInitializerSupport; + } + public void setNativeImageHeap(NativeImageHeap nativeImageHeap) { this.nativeImageHeap = nativeImageHeap; } @@ -428,7 +434,12 @@ private void persistType(AnalysisType type, Supplier> builder) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/SharedLayerSnapshotCapnProtoSchemaHolder.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/SharedLayerSnapshotCapnProtoSchemaHolder.java index 4108c17f83ed..d17eb481a718 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/SharedLayerSnapshotCapnProtoSchemaHolder.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/SharedLayerSnapshotCapnProtoSchemaHolder.java @@ -175,13 +175,27 @@ public final void setIsFailedInitialization(boolean value) { _setBooleanField(99, value); } - public final boolean getIsLinked() { + public final boolean getIsSuccessfulSimulation() { return _getBooleanField(100); } - public final void setIsLinked(boolean value) { + public final void setIsSuccessfulSimulation(boolean value) { _setBooleanField(100, value); } + public final boolean getIsFailedSimulation() { + return _getBooleanField(101); + } + public final void setIsFailedSimulation(boolean value) { + _setBooleanField(101, value); + } + + public final boolean getIsLinked() { + return _getBooleanField(102); + } + public final void setIsLinked(boolean value) { + _setBooleanField(102, value); + } + public final boolean hasSourceFileName() { return !_pointerFieldIsNull(4); } @@ -219,24 +233,24 @@ public final void setSuperClassTypeId(int value) { } public final boolean getIsInstantiated() { - return _getBooleanField(101); + return _getBooleanField(103); } public final void setIsInstantiated(boolean value) { - _setBooleanField(101, value); + _setBooleanField(103, value); } public final boolean getIsUnsafeAllocated() { - return _getBooleanField(102); + return _getBooleanField(104); } public final void setIsUnsafeAllocated(boolean value) { - _setBooleanField(102, value); + _setBooleanField(104, value); } public final boolean getIsReachable() { - return _getBooleanField(103); + return _getBooleanField(105); } public final void setIsReachable(boolean value) { - _setBooleanField(103, value); + _setBooleanField(105, value); } public final boolean hasInterfaces() { @@ -309,10 +323,17 @@ public final com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchema return _initPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.ClassInitializationInfo.factory,10, 0); } public final boolean getHasArrayType() { - return _getBooleanField(104); + return _getBooleanField(106); } public final void setHasArrayType(boolean value) { - _setBooleanField(104, value); + _setBooleanField(106, value); + } + + public final boolean getHasClassInitInfo() { + return _getBooleanField(107); + } + public final void setHasClassInitInfo(boolean value) { + _setBooleanField(107, value); } public final boolean hasSubTypes() { @@ -328,10 +349,10 @@ public final com.oracle.svm.shaded.org.capnproto.PrimitiveList.Int.Builder initS return _initPointerField(com.oracle.svm.shaded.org.capnproto.PrimitiveList.Int.factory, 11, size); } public final boolean getIsAnySubtypeInstantiated() { - return _getBooleanField(105); + return _getBooleanField(108); } public final void setIsAnySubtypeInstantiated(boolean value) { - _setBooleanField(105, value); + _setBooleanField(108, value); } public final WrappedType.Builder getWrappedType() { @@ -407,10 +428,18 @@ public final boolean getIsFailedInitialization() { return _getBooleanField(99); } - public final boolean getIsLinked() { + public final boolean getIsSuccessfulSimulation() { return _getBooleanField(100); } + public final boolean getIsFailedSimulation() { + return _getBooleanField(101); + } + + public final boolean getIsLinked() { + return _getBooleanField(102); + } + public boolean hasSourceFileName() { return !_pointerFieldIsNull(4); } @@ -431,15 +460,15 @@ public final int getSuperClassTypeId() { } public final boolean getIsInstantiated() { - return _getBooleanField(101); + return _getBooleanField(103); } public final boolean getIsUnsafeAllocated() { - return _getBooleanField(102); + return _getBooleanField(104); } public final boolean getIsReachable() { - return _getBooleanField(103); + return _getBooleanField(105); } public final boolean hasInterfaces() { @@ -485,7 +514,11 @@ public com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder } public final boolean getHasArrayType() { - return _getBooleanField(104); + return _getBooleanField(106); + } + + public final boolean getHasClassInitInfo() { + return _getBooleanField(107); } public final boolean hasSubTypes() { @@ -496,7 +529,7 @@ public final com.oracle.svm.shaded.org.capnproto.PrimitiveList.Int.Reader getSub } public final boolean getIsAnySubtypeInstantiated() { - return _getBooleanField(105); + return _getBooleanField(108); } public WrappedType.Reader getWrappedType() { @@ -2051,7 +2084,7 @@ public final com.oracle.svm.shaded.org.capnproto.PrimitiveList.Int.Reader getCal public static class PersistedAnalysisField { - public static final com.oracle.svm.shaded.org.capnproto.StructSize STRUCT_SIZE = new com.oracle.svm.shaded.org.capnproto.StructSize((short)5,(short)3); + public static final com.oracle.svm.shaded.org.capnproto.StructSize STRUCT_SIZE = new com.oracle.svm.shaded.org.capnproto.StructSize((short)5,(short)4); public static final class Factory extends com.oracle.svm.shaded.org.capnproto.StructFactory { public Factory() { } @@ -2232,6 +2265,15 @@ public final void setAssignmentStatus(int value) { _setIntField(8, value); } + public final com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.ConstantReference.Builder getSimulatedFieldValue() { + return _getPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.ConstantReference.factory, 3, null, 0); + } + public final void setSimulatedFieldValue(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.ConstantReference.Reader value) { + _setPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.ConstantReference.factory,3, value); + } + public final com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.ConstantReference.Builder initSimulatedFieldValue() { + return _initPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.ConstantReference.factory,3, 0); + } } public static final class Reader extends com.oracle.svm.shaded.org.capnproto.StructReader { @@ -2324,6 +2366,13 @@ public final int getAssignmentStatus() { return _getIntField(8); } + public boolean hasSimulatedFieldValue() { + return !_pointerFieldIsNull(3); + } + public com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.ConstantReference.Reader getSimulatedFieldValue() { + return _getPointerField(com.oracle.svm.hosted.imagelayer.SharedLayerSnapshotCapnProtoSchemaHolder.ConstantReference.factory,3,null, 0); + } + } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisGraphDecoderImpl.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisGraphDecoderImpl.java index 3d33070573e3..4c3356a6330f 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisGraphDecoderImpl.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisGraphDecoderImpl.java @@ -72,7 +72,7 @@ private Node handleEnsureClassInitializedNode(EnsureClassInitializedNode node) { AnalysisType type = (AnalysisType) node.constantTypeOrNull(bb.getConstantReflectionProvider()); if (type != null) { processClassInitializer(type); - if (simulateClassInitializerSupport.isClassInitializerSimulated(type) && !ClassInitializationSupport.singleton().requiresInitializationNodeForTypeReached(type)) { + if (simulateClassInitializerSupport.isSimulatedOrInitializedAtBuildTime(type) && !ClassInitializationSupport.singleton().requiresInitializationNodeForTypeReached(type)) { return null; } } @@ -121,7 +121,7 @@ private Node handleLoadFieldNode(LoadFieldNode node) { private Node handleIsStaticFinalFieldInitializedNode(IsStaticFinalFieldInitializedNode node) { var field = (AnalysisField) node.getField(); - if (simulateClassInitializerSupport.isClassInitializerSimulated(field.getDeclaringClass())) { + if (simulateClassInitializerSupport.isSimulatedOrInitializedAtBuildTime(field.getDeclaringClass())) { return ConstantNode.forBoolean(true); } return node;