Skip to content

Commit 75065b8

Browse files
committed
[GR-62449] Ensure that class initialization status is stable between layers.
PullRequest: graal/20758
2 parents 795663f + 5fef8ff commit 75065b8

File tree

11 files changed

+263
-119
lines changed

11 files changed

+263
-119
lines changed

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -363,10 +363,12 @@ private ImageHeapArray createImageHeapObjectArray(JavaConstant constant, Analysi
363363

364364
public void registerBaseLayerValue(ImageHeapConstant constant, Object reason) {
365365
JavaConstant hostedValue = constant.getHostedObject();
366+
AnalysisError.guarantee(hostedValue.isNonNull(), "A relinked constant cannot have a NULL_CONSTANT hosted value.");
366367
Object existingSnapshot = imageHeap.getSnapshot(hostedValue);
367368
if (existingSnapshot != null) {
368369
AnalysisError.guarantee(existingSnapshot == constant || existingSnapshot instanceof AnalysisFuture<?> task && task.ensureDone() == constant,
369-
"Found unexpected snapshot value for base layer value. Reason: %s.", reason);
370+
"Found unexpected snapshot value for base layer value.%nExisting value: %s.%nNew value: %s.%nHosted value: %s.%nReason: %s.",
371+
existingSnapshot, constant, hostedValue, reason);
370372
} else {
371373
imageHeap.setValue(hostedValue, constant);
372374
}

substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/BaseLayerType.java

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ public class BaseLayerType extends BaseLayerElement implements ResolvedJavaType,
5656
private final boolean isInterface;
5757
private final boolean isEnum;
5858
private final boolean isInitialized;
59-
private final boolean isInitializedAtBuildTime;
6059
private final boolean isLinked;
6160
private final String sourceFileName;
6261
private final ResolvedJavaType enclosingType;
@@ -67,7 +66,7 @@ public class BaseLayerType extends BaseLayerElement implements ResolvedJavaType,
6766
private ResolvedJavaField[] instanceFields;
6867
private ResolvedJavaField[] instanceFieldsWithSuper;
6968

70-
public BaseLayerType(String name, int baseLayerId, int modifiers, boolean isInterface, boolean isEnum, boolean isInitialized, boolean initializedAtBuildTime, boolean isLinked,
69+
public BaseLayerType(String name, int baseLayerId, int modifiers, boolean isInterface, boolean isEnum, boolean isInitialized, boolean isLinked,
7170
String sourceFileName, ResolvedJavaType enclosingType, ResolvedJavaType componentType, ResolvedJavaType superClass, ResolvedJavaType[] interfaces, ResolvedJavaType objectType,
7271
Annotation[] annotations) {
7372
super(annotations);
@@ -77,7 +76,6 @@ public BaseLayerType(String name, int baseLayerId, int modifiers, boolean isInte
7776
this.isInterface = isInterface;
7877
this.isEnum = isEnum;
7978
this.isInitialized = isInitialized;
80-
this.isInitializedAtBuildTime = initializedAtBuildTime;
8179
this.isLinked = isLinked;
8280
this.sourceFileName = sourceFileName;
8381
this.enclosingType = enclosingType;
@@ -341,8 +339,4 @@ public ResolvedJavaType unwrapTowardsOriginalType() {
341339
public int getBaseLayerId() {
342340
return baseLayerId;
343341
}
344-
345-
public boolean initializedAtBuildTime() {
346-
return isInitializedAtBuildTime;
347-
}
348342
}

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/imagelayer/NativeImageLayers.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,16 @@ native-image --module-path target/AwesomeLib-1.0-SNAPSHOT.jar --shared
201201
- A shared layer is using the _.so_ extension to conform with the standard OS loader restrictions. However, it is not a
202202
standard shared library file, and it cannot be used with other applications.
203203

204+
### Class Initialization
205+
206+
With Native Image Layers class initialization needs to be coherent between layers.
207+
To achieve this we enforce that the initialization state of types in shared layers stays exactly the same in the
208+
subsequent dependent layers.
209+
More concretely if a class `A` is initialized at build time in a base layer, then it will be automatically
210+
initialized at build time in the extension layers. The same holds for run time initialization.
211+
In the future we plan to relax this restriction and allow run-time initialized types in base layers to be promoted
212+
into build-time initialized types in the extension layers.
213+
204214
## Packaging Native Image Layers
205215

206216
At build time a shared layer is stored in a layer archive that contains the following artifacts:

substratevm/src/com.oracle.svm.hosted/resources/SharedLayerSnapshotCapnProtoSchema.capnp

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# Schema file for the layer snapshot.
2+
# After modifying this file regenerate the schema SharedLayerSnapshotCapnProtoSchemaHolder.java file with:
3+
# mx capnp-compile
4+
15
@0x9eb32e19f86ee174;
26
using Java = import "/capnp/java.capnp";
37
$Java.package("com.oracle.svm.hosted.imagelayer");
@@ -21,35 +25,35 @@ struct PersistedAnalysisType {
2125
# Most of these fields apply only to instances and could be in a union or a separate structure:
2226
isInterface @7 :Bool;
2327
isEnum @8 :Bool;
28+
# True if the type's initialization status was computed as BUILD_TIME. Build-time initialized types are not simulated.
2429
isInitialized @9 :Bool;
25-
isInitializedAtBuildTime @10 :Bool;
26-
isLinked @11 :Bool;
27-
sourceFileName @12 :Text;
28-
enclosingTypeId @13 :TypeId;
29-
componentTypeId @14 :TypeId;
30-
superClassTypeId @15 :TypeId;
31-
isInstantiated @16 :Bool;
32-
isUnsafeAllocated @17 :Bool;
33-
isReachable @18 :Bool;
34-
interfaces @19 :List(TypeId);
35-
instanceFieldIds @20 :List(FieldId);
36-
instanceFieldIdsWithSuper @21 :List(FieldId);
37-
staticFieldIds @22 :List(FieldId);
38-
annotationList @23 :List(Annotation);
39-
classInitializationInfo @24 :ClassInitializationInfo;
40-
hasArrayType @25 :Bool;
41-
subTypes @26 :List(TypeId);
42-
isAnySubtypeInstantiated @27 :Bool;
30+
isLinked @10 :Bool;
31+
sourceFileName @11 :Text;
32+
enclosingTypeId @12 :TypeId;
33+
componentTypeId @13 :TypeId;
34+
superClassTypeId @14 :TypeId;
35+
isInstantiated @15 :Bool;
36+
isUnsafeAllocated @16 :Bool;
37+
isReachable @17 :Bool;
38+
interfaces @18 :List(TypeId);
39+
instanceFieldIds @19 :List(FieldId);
40+
instanceFieldIdsWithSuper @20 :List(FieldId);
41+
staticFieldIds @21 :List(FieldId);
42+
annotationList @22 :List(Annotation);
43+
classInitializationInfo @23 :ClassInitializationInfo;
44+
hasArrayType @24 :Bool;
45+
subTypes @25 :List(TypeId);
46+
isAnySubtypeInstantiated @26 :Bool;
4347
wrappedType :union {
44-
none @28 :Void; # default
48+
none @27 :Void; # default
4549
serializationGenerated :group {
46-
rawDeclaringClass @29 :Text;
47-
rawTargetConstructor @30 :Text;
50+
rawDeclaringClass @28 :Text;
51+
rawTargetConstructor @29 :Text;
4852
}
4953
lambda :group {
50-
capturingClass @31 :Text;
54+
capturingClass @30 :Text;
5155
}
52-
proxyType @32 :Void;
56+
proxyType @31 :Void;
5357
}
5458
}
5559

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -815,7 +815,7 @@ protected boolean runPointsToAnalysis(String imageName, OptionValues options, De
815815
ServiceCatalogSupport.singleton().enableServiceCatalogMapTransformer(config);
816816
featureHandler.forEachFeature(feature -> feature.beforeAnalysis(config));
817817
ServiceCatalogSupport.singleton().seal();
818-
bb.getHostVM().getClassInitializationSupport().setConfigurationSealed(true);
818+
bb.getHostVM().getClassInitializationSupport().sealConfiguration();
819819
if (ImageLayerBuildingSupport.buildingImageLayer()) {
820820
ImageSingletons.lookup(LoadImageSingletonFeature.class).processRegisteredSingletons(aUniverse);
821821
}

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationConfiguration.java

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.graalvm.collections.Pair;
3636

3737
import com.oracle.svm.core.util.UserError;
38+
import com.oracle.svm.core.util.VMError;
3839

3940
/**
4041
* Maintains user and system configuration for class initialization in Native Image.
@@ -52,12 +53,47 @@
5253
*
5354
* Every node tracks a list of reasons for the set configuration. This list helps the users debug
5455
* conflicts in the configuration.
56+
*
57+
* A {@code strict} configuration, as specified by {@link InitializationNode#strict}, defines
58+
* whether an initialization kind, as specified by {@link InitializationNode#kind}, was explicitly
59+
* configured for the corresponding type. A {@code non-strict} configuration is a configuration
60+
* inherited from a parent package configuration.
5561
*/
5662
final class ClassInitializationConfiguration {
5763
private static final String ROOT_QUALIFIER = "";
5864
private static final int MAX_NUMBER_OF_REASONS = 10;
5965

60-
private InitializationNode root = new InitializationNode("", null, null, false);
66+
private final InitializationNode root = new InitializationNode("", null, null, false);
67+
68+
synchronized void updateStrict(String classOrPackage, InitKind expectedKind, InitKind newKind, String reason) {
69+
updateStrictRec(root, qualifierList(classOrPackage), expectedKind, newKind, reason);
70+
}
71+
72+
private void updateStrictRec(InitializationNode node, List<String> classOrPackage, InitKind expectedKind, InitKind newKind, String reason) {
73+
assert !classOrPackage.isEmpty();
74+
assert node.qualifier.equals(classOrPackage.getFirst());
75+
if (classOrPackage.size() == 1) {
76+
VMError.guarantee(node.strict, "Expected a strict initialization policy for %s.", qualifiedName(node), newKind);
77+
VMError.guarantee(newKind != expectedKind, "The initialization policy for %s is already %s.", qualifiedName(node), newKind);
78+
VMError.guarantee(node.kind == expectedKind, "Found unexpected initialization policy for %s: expected %s, found %s.", qualifiedName(node), expectedKind, node.kind);
79+
updateReason(node, reason);
80+
node.kind = newKind;
81+
} else {
82+
List<String> tail = new ArrayList<>(classOrPackage);
83+
tail.removeFirst();
84+
String nextQualifier = tail.getFirst();
85+
VMError.guarantee(node.children.containsKey(nextQualifier), "Expected that initialization node for %s already contains a child for %s.", qualifiedName(node), nextQualifier);
86+
updateStrictRec(node.children.get(nextQualifier), tail, expectedKind, newKind, reason);
87+
}
88+
}
89+
90+
private static void updateReason(InitializationNode node, String reason) {
91+
if (node.reasons.size() < MAX_NUMBER_OF_REASONS) {
92+
node.reasons.add(reason);
93+
} else if (node.reasons.size() == MAX_NUMBER_OF_REASONS) {
94+
node.reasons.add("others");
95+
}
96+
}
6197

6298
public synchronized void insert(String classOrPackage, InitKind kind, String reason, boolean strict) {
6399
assert kind != null;
@@ -93,11 +129,7 @@ private void insertRec(InitializationNode node, List<String> classOrPackage, Ini
93129
node.strict = strict;
94130
node.reasons.add(reason);
95131
} else if (node.kind == kind) {
96-
if (node.reasons.size() < MAX_NUMBER_OF_REASONS) {
97-
node.reasons.add(reason);
98-
} else if (node.reasons.size() == MAX_NUMBER_OF_REASONS) {
99-
node.reasons.add("others");
100-
}
132+
updateReason(node, reason);
101133
} else {
102134
if (node.strict) {
103135
throw UserError.abort("Incompatible change of initialization policy for %s: trying to change %s %s to %s %s",

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationSupport.java

Lines changed: 80 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,18 +44,19 @@
4444
import java.util.function.Function;
4545
import java.util.stream.Collectors;
4646

47-
import jdk.graal.compiler.core.common.ContextClassLoaderScope;
48-
import org.graalvm.nativeimage.libgraal.hosted.LibGraalLoader;
4947
import org.graalvm.collections.EconomicSet;
5048
import org.graalvm.nativeimage.ImageSingletons;
5149
import org.graalvm.nativeimage.impl.RuntimeClassInitializationSupport;
5250
import org.graalvm.nativeimage.impl.clinit.ClassInitializationTracking;
51+
import org.graalvm.nativeimage.libgraal.hosted.LibGraalLoader;
5352

53+
import com.oracle.graal.pointsto.BigBang;
5454
import com.oracle.graal.pointsto.infrastructure.OriginalClassProvider;
5555
import com.oracle.graal.pointsto.meta.AnalysisType;
5656
import com.oracle.graal.pointsto.meta.BaseLayerType;
5757
import com.oracle.graal.pointsto.reports.ReportUtils;
5858
import com.oracle.svm.core.SubstrateOptions;
59+
import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport;
5960
import com.oracle.svm.core.option.AccumulatingLocatableMultiOptionValue;
6061
import com.oracle.svm.core.option.SubstrateOptionsParser;
6162
import com.oracle.svm.core.util.UserError;
@@ -65,6 +66,7 @@
6566
import com.oracle.svm.util.LogUtils;
6667
import com.oracle.svm.util.ModuleSupport;
6768

69+
import jdk.graal.compiler.core.common.ContextClassLoaderScope;
6870
import jdk.graal.compiler.java.LambdaUtils;
6971
import jdk.internal.misc.Unsafe;
7072
import jdk.vm.ci.meta.MetaAccessProvider;
@@ -73,6 +75,38 @@
7375
/**
7476
* The core class for deciding whether a class should be initialized during image building or class
7577
* initialization should be delayed to runtime.
78+
* <p>
79+
* The initialization kind for all classes is encoded in the two registries:
80+
* {@link #classInitializationConfiguration}, the user-configured initialization state, and
81+
* {@link #classInitKinds}, the actual computed initialization state.
82+
* <p>
83+
* If for example the configured initialization kind, as registered in
84+
* {@link #classInitializationConfiguration}, is {@link InitKind#BUILD_TIME} but invoking
85+
* {@link #ensureClassInitialized(Class, boolean)} results in an error, e.g., a
86+
* {@link NoClassDefFoundError}, then the actual initialization kind registered in
87+
* {@link #classInitKinds} may be {@link InitKind#RUN_TIME} depending on the error resolution policy
88+
* dictated by {@link LinkAtBuildTimeSupport#linkAtBuildTime(Class)}.
89+
* <p>
90+
* Classes with a simulated class initializer are neither registered as initialized at
91+
* {@link InitKind#RUN_TIME} nor {@link InitKind#BUILD_TIME}. Instead
92+
* {@link SimulateClassInitializerSupport} queries their initialization state to decide if
93+
* simulation should be tried. If a class has a computed {@link InitKind#BUILD_TIME} initialization
94+
* kind, i.e., its {@link AnalysisType#isInitialized()} returns true, simulation is skipped since
95+
* the type is already considered as starting out as initialized at image run time (see
96+
* {@link SimulateClassInitializerSupport#trySimulateClassInitializer(BigBang, AnalysisType)}). If a
97+
* class was explicitly configured as {@link InitKind#RUN_TIME} initialized this will prevent it
98+
* from being simulated.
99+
* <p>
100+
* There are some similarities and differences between simulated and build-time initialized classes.
101+
* At image execution time they both start out as initialized: there are no run-time class
102+
* initialization checks and the class initializer itself is not even present, it was not AOT
103+
* compiled. However, for a simulated class its initialization status in the hosting VM that runs
104+
* the image generator does not matter; it may or may not have been initialized. Whereas, a
105+
* build-time initialized class is by definition initialized in the hosting VM. Consequently, the
106+
* static fields of a simulated class reference image heap values computed by the class initializer
107+
* simulation, but they do not correspond to hosted objects. In contrast, static fields of a
108+
* build-time initialized class reference image heap values that were copied from the corresponding
109+
* fields in the hosting VM.
76110
*/
77111
public class ClassInitializationSupport implements RuntimeClassInitializationSupport {
78112

@@ -125,7 +159,29 @@ public ClassInitializationSupport(MetaAccessProvider metaAccess, ImageClassLoade
125159
this.loader = loader;
126160
}
127161

128-
public void setConfigurationSealed(boolean sealed) {
162+
/**
163+
* Seal the configuration, blocking if another thread is trying to seal the configuration or an
164+
* unsealed-configuration window is currently open in another thread.
165+
*/
166+
public synchronized void sealConfiguration() {
167+
setConfigurationSealed(true);
168+
}
169+
170+
/**
171+
* Run the action in an unsealed-configuration window, blocking if another thread is trying to
172+
* seal the configuration or an unsealed-configuration window is currently open in another
173+
* thread. The window is reentrant, i.e., it will not block the thread that opened the window
174+
* from trying to reenter the window. Note that if the configuration was not sealed when the
175+
* window was opened this will not affect the seal status.
176+
*/
177+
public synchronized void withUnsealedConfiguration(Runnable action) {
178+
var previouslySealed = configurationSealed;
179+
setConfigurationSealed(false);
180+
action.run();
181+
setConfigurationSealed(previouslySealed);
182+
}
183+
184+
private void setConfigurationSealed(boolean sealed) {
129185
configurationSealed = sealed;
130186
if (configurationSealed && ClassInitializationOptions.PrintClassInitialization.getValue()) {
131187
List<ClassOrPackageConfig> allConfigs = classInitializationConfiguration.allConfigs();
@@ -170,7 +226,7 @@ Set<Class<?>> classesWithKind(InitKind kind) {
170226
*/
171227
public boolean maybeInitializeAtBuildTime(ResolvedJavaType type) {
172228
if (type instanceof AnalysisType analysisType && analysisType.getWrapped() instanceof BaseLayerType baseLayerType) {
173-
return baseLayerType.initializedAtBuildTime();
229+
return baseLayerType.isInitialized();
174230
}
175231
return maybeInitializeAtBuildTime(OriginalClassProvider.getJavaClass(type));
176232
}
@@ -350,8 +406,26 @@ public void forceInitializeHosted(Class<?> clazz, String reason, boolean allowIn
350406
if (clazz == null) {
351407
return;
352408
}
409+
353410
classInitializationConfiguration.insert(clazz.getTypeName(), InitKind.BUILD_TIME, reason, true);
354411
InitKind initKind = ensureClassInitialized(clazz, allowInitializationErrors);
412+
if (initKind == InitKind.RUN_TIME) {
413+
assert allowInitializationErrors || !LinkAtBuildTimeSupport.singleton().linkAtBuildTime(clazz);
414+
if (ImageLayerBuildingSupport.buildingApplicationLayer()) {
415+
/*
416+
* Special case for application layer building. If a base layer class was configured
417+
* with --initialize-at-build-time but its initialization failed, then the computed
418+
* init kind will be RUN_TIME, different from its configured init kind of
419+
* BUILD_TIME. In the app layer the computed init kind from the previous layer is
420+
* registered as the configured init kind, but if the --initialize-at-build-time was
421+
* already processed for the class then it will already have a conflicting
422+
* configured init kind of BUILD_TIME. Update the configuration registry to allow
423+
* the RUN_TIME registration coming from the base layer. (GR-65405)
424+
*/
425+
classInitializationConfiguration.updateStrict(clazz.getTypeName(), InitKind.BUILD_TIME, InitKind.RUN_TIME,
426+
"Allow the registration of the computed run time initialization kind from a previous layer for classes whose build time initialization fails.");
427+
}
428+
}
355429
classInitKinds.put(clazz, initKind);
356430

357431
forceInitializeHosted(clazz.getSuperclass(), "super type of " + clazz.getTypeName(), allowInitializationErrors);
@@ -369,7 +443,8 @@ private void forceInitializeInterfaces(Class<?>[] interfaces, String reason) {
369443
if (metaAccess.lookupJavaType(iface).declaresDefaultMethods()) {
370444
classInitializationConfiguration.insert(iface.getTypeName(), InitKind.BUILD_TIME, reason, true);
371445

372-
ensureClassInitialized(iface, false);
446+
InitKind initKind = ensureClassInitialized(iface, false);
447+
VMError.guarantee(initKind == InitKind.BUILD_TIME, "Initialization of %s failed so all interfaces with default methods must be already initialized.", iface.getTypeName());
373448
classInitKinds.put(iface, InitKind.BUILD_TIME);
374449
}
375450
forceInitializeInterfaces(iface.getInterfaces(), "super type of " + iface.getTypeName());

0 commit comments

Comments
 (0)