Skip to content

Commit 5fef8ff

Browse files
committed
Ensure AnalysisType initialization status is stable between layers.
1 parent 23d32c0 commit 5fef8ff

File tree

5 files changed

+94
-33
lines changed

5 files changed

+94
-33
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.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: 5 additions & 0 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,6 +25,7 @@ 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;
2530
isLinked @10 :Bool;
2631
sourceFileName @11 :Text;

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

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -409,19 +409,22 @@ public void forceInitializeHosted(Class<?> clazz, String reason, boolean allowIn
409409

410410
classInitializationConfiguration.insert(clazz.getTypeName(), InitKind.BUILD_TIME, reason, true);
411411
InitKind initKind = ensureClassInitialized(clazz, allowInitializationErrors);
412-
if (ImageLayerBuildingSupport.buildingApplicationLayer() && initKind == InitKind.RUN_TIME) {
413-
/*
414-
* Special case for application layer building. If a base layer class was configured
415-
* with --initialize-at-build-time but its initialization failed, then the computed init
416-
* kind will be RUN_TIME, different from its configured init kind of BUILD_TIME. In the
417-
* app layer the computed init kind from the previous layer is registered as the
418-
* configured init kind, but if the --initialize-at-build-time was already processed for
419-
* the class then it will already have a conflicting configured init kind of BUILD_TIME.
420-
* Update the configuration registry to allow the RUN_TIME registration coming from the
421-
* base layer. (GR-65405)
422-
*/
412+
if (initKind == InitKind.RUN_TIME) {
423413
assert allowInitializationErrors || !LinkAtBuildTimeSupport.singleton().linkAtBuildTime(clazz);
424-
classInitializationConfiguration.updateStrict(clazz.getTypeName(), InitKind.BUILD_TIME, InitKind.RUN_TIME, "Build time initialization failed.");
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+
}
425428
}
426429
classInitKinds.put(clazz, initKind);
427430

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/SVMImageLayerLoader.java

Lines changed: 61 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
import com.oracle.graal.pointsto.heap.ImageHeapPrimitiveArray;
7070
import com.oracle.graal.pointsto.heap.ImageHeapRelocatableConstant;
7171
import com.oracle.graal.pointsto.heap.value.ValueSupplier;
72+
import com.oracle.graal.pointsto.infrastructure.OriginalClassProvider;
7273
import com.oracle.graal.pointsto.infrastructure.ResolvedSignature;
7374
import com.oracle.graal.pointsto.meta.AnalysisField;
7475
import com.oracle.graal.pointsto.meta.AnalysisMetaAccess;
@@ -166,6 +167,7 @@ public class SVMImageLayerLoader extends ImageLayerLoader {
166167

167168
private HostedUniverse hostedUniverse;
168169

170+
/** Maps from the previous layer element id to the linked elements in this layer. */
169171
protected final Map<Integer, AnalysisType> types = new ConcurrentHashMap<>();
170172
protected final Map<Integer, AnalysisMethod> methods = new ConcurrentHashMap<>();
171173
protected final Map<Integer, AnalysisField> fields = new ConcurrentHashMap<>();
@@ -679,6 +681,7 @@ private int getBaseLayerTypeId(AnalysisType type) {
679681
/* The type was not reachable in the base image */
680682
return -1;
681683
}
684+
initializeBaseLayerTypeBeforePublishing(type, typeData);
682685
int id = typeData.getId();
683686
int hubIdentityHashCode = typeData.getHubIdentityHashCode();
684687
typeToHubIdentityHashCode.put(id, hubIdentityHashCode);
@@ -696,6 +699,39 @@ protected PersistedAnalysisType.Reader findBaseLayerType(AnalysisType type) {
696699
return findType(typeId);
697700
}
698701

702+
/**
703+
* This method is invoked *before* the {@link AnalysisType} is published in the
704+
* {@link AnalysisUniverse}. The side effects of this method are visible to other threads that
705+
* are consuming the {@link AnalysisType} object.
706+
*/
707+
@SuppressWarnings("try")
708+
private void initializeBaseLayerTypeBeforePublishing(AnalysisType type, PersistedAnalysisType.Reader typeData) {
709+
assert !(type.getWrapped() instanceof BaseLayerType);
710+
/*
711+
* For types reachable in this layer register the *computed* initialization kind extracted
712+
* from the previous layer. This will cause base layer types to have a *strict*
713+
* initialization kind in this layer which will prevent further changes to the
714+
* initialization kind, even in ways that would otherwise be considered compatible, e.g.,
715+
* RUN_TIME -> BUILD_TIME. Similarly, if a different initialization kind was already
716+
* registered in this layer registration will fail.
717+
*
718+
* Note that this is done after the app-layer class initialization specification is applied,
719+
* so we don't have to traverse all types. Moreover, for package-level specification this
720+
* should also be OK, because package-level specification is only a suggestion and the
721+
* base-layer will always win as it is going over user classes.
722+
*/
723+
Class<?> clazz = OriginalClassProvider.getJavaClass(type);
724+
if (typeData.getIsInitialized()) {
725+
classInitializationSupport.withUnsealedConfiguration(() -> classInitializationSupport.initializeAtBuildTime(clazz, "computed in a previous layer"));
726+
} else {
727+
classInitializationSupport.withUnsealedConfiguration(() -> classInitializationSupport.initializeAtRunTime(clazz, "computed in a previous layer"));
728+
}
729+
}
730+
731+
/**
732+
* This method is invoked *after* the {@link AnalysisType} is published in the
733+
* {@link AnalysisUniverse} and it may execute concurrently with other threads using the type.
734+
*/
699735
@Override
700736
public void initializeBaseLayerType(AnalysisType type) {
701737
int id = getBaseLayerTypeId(type);
@@ -1304,6 +1340,19 @@ private ImageHeapConstant getOrCreateConstant(int id, JavaConstant parentReachab
13041340
throw GraalError.shouldNotReachHere("The constant was not reachable in the base image");
13051341
}
13061342

1343+
/*
1344+
* Important: If this is a constant originating from a static final field ensure that the
1345+
* field declaring type is initialized before the field type is accessed below. This is to
1346+
* avoid issue with class initialization execution order in class initialization cycles.
1347+
*/
1348+
if (baseLayerConstant.isObject() && !baseLayerConstant.getIsSimulated()) {
1349+
Relinking.Reader relinking = baseLayerConstant.getObject().getRelinking();
1350+
if (relinking.isFieldConstant()) {
1351+
AnalysisField analysisField = getAnalysisFieldForBaseLayerId(relinking.getFieldConstant().getOriginFieldId());
1352+
VMError.guarantee(analysisField.getDeclaringClass().isInitialized());
1353+
}
1354+
}
1355+
13071356
AnalysisType type = getAnalysisTypeForBaseLayerId(baseLayerConstant.getTypeId());
13081357

13091358
long objectOffset = baseLayerConstant.getObjectOffset();
@@ -1581,31 +1630,18 @@ private JavaConstant lookupHostedObject(PersistedConstant.Reader baseLayerConsta
15811630
} else if (relinking.isFieldConstant()) {
15821631
var fieldConstant = relinking.getFieldConstant();
15831632
AnalysisField analysisField = getAnalysisFieldForBaseLayerId(fieldConstant.getOriginFieldId());
1584-
if (!(analysisField.getWrapped() instanceof BaseLayerField) && !AnnotationAccess.isAnnotationPresent(analysisField, Delete.class)) {
1585-
VMError.guarantee(!baseLayerConstant.getIsSimulated(), "Should not alter the initialization status for simulated constants.");
1633+
if (shouldRelinkField(analysisField)) {
1634+
VMError.guarantee(!baseLayerConstant.getIsSimulated(), "Cannot relink simulated constants.");
15861635
/*
15871636
* The declaring type of relinked fields was already initialized in the previous
15881637
* layer (see SVMImageLayerWriter#shouldRelinkField).
15891638
*/
1590-
if (fieldConstant.getRequiresLateLoading()) {
1591-
/*
1592-
* Fields with a field value transformer are relinked later, after all possible
1593-
* transformers have been registered. *Guarantee* that the declaring type has
1594-
* been initialized by now. Note that reading the field below will prevent a
1595-
* transformer to be installed at a later time.
1596-
*/
1597-
VMError.guarantee(analysisField.getDeclaringClass().isInitialized());
1598-
} else {
1599-
/*
1600-
* All other fields are relinked earlier, before the constant is needed. *Force*
1601-
* the build time initialization of the declaring type before reading the field
1602-
* value.
1603-
*/
1604-
Class<?> fieldDeclaringClass = analysisField.getDeclaringClass().getJavaClass();
1605-
classInitializationSupport.initializeAtBuildTime(fieldDeclaringClass, "Already initialized in base layer.");
1606-
}
1639+
VMError.guarantee(analysisField.getDeclaringClass().isInitialized());
16071640
/* Read fields through the hostedValueProvider and apply object replacement. */
1608-
return hostedValuesProvider.readFieldValueWithReplacement(analysisField, null);
1641+
JavaConstant javaConstant = hostedValuesProvider.readFieldValueWithReplacement(analysisField, null);
1642+
VMError.guarantee(javaConstant.isNonNull(), "Found NULL_CONSTANT when reading the hosted value of relinked field %s. " +
1643+
"Since relinked fields should have a concrete non-null value there may be a class initialization mismatch.", analysisField);
1644+
return javaConstant;
16091645
}
16101646
} else if (clazz.equals(Class.class)) {
16111647
/* DynamicHub corresponding to $$TypeSwitch classes are not relinked */
@@ -1630,6 +1666,11 @@ private JavaConstant lookupHostedObject(PersistedConstant.Reader baseLayerConsta
16301666
return null;
16311667
}
16321668

1669+
private static boolean shouldRelinkField(AnalysisField field) {
1670+
VMError.guarantee(field.isInBaseLayer());
1671+
return !(field.getWrapped() instanceof BaseLayerField) && !AnnotationAccess.isAnnotationPresent(field, Delete.class);
1672+
}
1673+
16331674
@SuppressWarnings("unchecked")
16341675
private Enum<?> getEnumValue(Text.Reader className, Text.Reader name) {
16351676
Class<?> enumClass = imageLayerBuildingSupport.lookupClass(false, className.toString());

0 commit comments

Comments
 (0)