Skip to content

Commit

Permalink
Simulate class initializer
Browse files Browse the repository at this point in the history
  • Loading branch information
Christian Wimmer committed Jun 10, 2023
1 parent bb4a66d commit 2055cc2
Show file tree
Hide file tree
Showing 39 changed files with 3,079 additions and 791 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ protected void verify(StructuredGraph graph, CoreProviders context) {
"org.graalvm.compiler.core.test.VerifyDebugUsageTest$InvalidDumpUsagePhase.run",
"org.graalvm.compiler.hotspot.SymbolicSnippetEncoder.verifySnippetEncodeDecode",
"com.oracle.graal.pointsto.phases.InlineBeforeAnalysis.decodeGraph",
"com.oracle.svm.hosted.classinitialization.SimulateClassInitializerSupport.decodeGraph",
"com.oracle.svm.hosted.classinitialization.SimulateClassInitializerAbortException.doAbort",
"org.graalvm.compiler.truffle.compiler.phases.inlining.CallTree.dumpBasic",
"org.graalvm.compiler.truffle.compiler.phases.inlining.GraphManager.peRoot"));

Expand Down
1 change: 1 addition & 0 deletions substratevm/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ This changelog summarizes major changes to GraalVM Native Image.
* (GR-35746) Lower the default aligned chunk size from 1 MB to 512 KB for the serial and epsilon GCs, reducing memory usage and image size in many cases.
* (GR-45841) BellSoft added support for the JFR event ThreadCPULoad.
* (GR-45994) Removed the option `-H:EnableSignalAPI`. Please use the runtime option `EnableSignalHandling` if it is necessary to enable or disable signal handling explicitly.
* (GR-39406) Simulation of class initializer: Class initializer of classes that are not marked for initialization at image build time are simulated at image build time to avoid executing them at image run time.

## Version 23.0.0
* (GR-40187) Report invalid use of SVM specific classes on image class- or module-path as error. As a temporary workaround, `-H:+AllowDeprecatedBuilderClassesOnImageClasspath` allows turning the error into a warning.
Expand Down
34 changes: 26 additions & 8 deletions substratevm/mx.substratevm/mx_substratevm.py
Original file line number Diff line number Diff line change
Expand Up @@ -1330,7 +1330,11 @@ def cinterfacetutorial(args):

@mx.command(suite.name, 'clinittest', 'Runs the ')
def clinittest(args):
def build_and_test_clinittest_image(native_image, args=None):
def build_and_test_clinittest_images(native_image, args=None):
build_and_test_clinittest_image(native_image, args, True)
build_and_test_clinittest_image(native_image, args, False)

def build_and_test_clinittest_image(native_image, args, new_class_init_policy):
args = [] if args is None else args
test_cp = classpath('com.oracle.svm.test')
build_dir = join(svmbuild_dir(), 'clinittest')
Expand All @@ -1340,11 +1344,17 @@ def build_and_test_clinittest_image(native_image, args=None):
remove_tree(build_dir)
mkpath(build_dir)

if new_class_init_policy:
policy_args = ['-H:+UseNewExperimentalClassInitialization', '-H:+SimulateClassInitializer', '-H:Features=com.oracle.svm.test.clinit.TestClassInitializationFeatureNewPolicyFeature']
else:
policy_args = ['-H:-UseNewExperimentalClassInitialization', '-H:-SimulateClassInitializer', '-H:Features=com.oracle.svm.test.clinit.TestClassInitializationFeatureOldPolicyFeature']

# Build and run the example
native_image(
['-H:Path=' + build_dir, '-cp', test_cp, '-H:Class=com.oracle.svm.test.clinit.TestClassInitializationMustBeSafeEarly',
'-H:Features=com.oracle.svm.test.clinit.TestClassInitializationMustBeSafeEarlyFeature',
'-H:+PrintClassInitialization', '-H:Name=clinittest', '-H:+ReportExceptionStackTraces'] + args)
['-H:Path=' + build_dir, '-cp', test_cp, '-H:Class=com.oracle.svm.test.clinit.TestClassInitialization',
'-J--add-exports=java.base/jdk.internal.misc=ALL-UNNAMED',
'-J-ea', '-J-esa',
'-H:+PrintClassInitialization', '-H:Name=clinittest', '-H:+ReportExceptionStackTraces'] + policy_args + args)
mx.run([join(build_dir, 'clinittest')])

# Check the reports for initialized classes
Expand All @@ -1358,9 +1368,17 @@ def checkLine(line, marker, init_kind, msg, wrongly_initialized_lines):
"Classes marked with " + marker + " must have init kind " + init_kind + " and message " + msg)]
with open(classes_file) as f:
for line in f:
checkLine(line, "MustBeDelayed", "RUN_TIME", "classes are initialized at run time by default", wrongly_initialized_lines)
checkLine(line, "MustBeSafeEarly", "BUILD_TIME", "class proven as side-effect free before analysis", wrongly_initialized_lines)
checkLine(line, "MustBeSafeLate", "BUILD_TIME", "class proven as side-effect free after analysis", wrongly_initialized_lines)
if new_class_init_policy:
checkLine(line, "MustBeSafeEarly", "SIMULATED", "classes are initialized at run time by default", wrongly_initialized_lines)
checkLine(line, "MustBeSafeLate", "SIMULATED", "classes are initialized at run time by default", wrongly_initialized_lines)
checkLine(line, "MustBeSimulated", "SIMULATED", "classes are initialized at run time by default", wrongly_initialized_lines)
checkLine(line, "MustBeDelayed", "RUN_TIME", "classes are initialized at run time by default", wrongly_initialized_lines)
else:
checkLine(line, "MustBeSafeEarly", "BUILD_TIME", "class proven as side-effect free before analysis", wrongly_initialized_lines)
checkLine(line, "MustBeSafeLate", "BUILD_TIME", "class proven as side-effect free after analysis", wrongly_initialized_lines)
checkLine(line, "MustBeSimulated", "RUN_TIME", "classes are initialized at run time by default", wrongly_initialized_lines)
checkLine(line, "MustBeDelayed", "RUN_TIME", "classes are initialized at run time by default", wrongly_initialized_lines)

if len(wrongly_initialized_lines) > 0:
msg = ""
for (line, error) in wrongly_initialized_lines:
Expand All @@ -1372,7 +1390,7 @@ def checkLine(line, marker, init_kind, msg, wrongly_initialized_lines):

check_class_initialization(all_classes_file)

native_image_context_run(build_and_test_clinittest_image, args)
native_image_context_run(build_and_test_clinittest_images, args)


class SubstrateJvmFuncsFallbacksBuilder(mx.Project):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@
import java.lang.reflect.Array;
import java.util.function.Consumer;

import org.graalvm.compiler.debug.GraalError;

import com.oracle.graal.pointsto.ObjectScanner;
import com.oracle.graal.pointsto.meta.AnalysisType;
import com.oracle.graal.pointsto.util.AnalysisError;
Expand Down Expand Up @@ -97,35 +95,10 @@ public JavaConstant readElementValue(int idx) {

@Override
public void setElement(int idx, JavaConstant value) {
/*
* Constants for sub-integer types are often just integer constants, i.e., we cannot rely on
* the JavaKind of the constant to match the type of the array.
*/
if (array instanceof boolean[] booleanArray) {
booleanArray[idx] = value.asInt() != 0;
} else if (array instanceof byte[] byteArray) {
byte v = (byte) value.asInt();
GraalError.guarantee(v == value.asInt(), "type mismatch");
byteArray[idx] = v;
} else if (array instanceof short[] shortArray) {
short v = (short) value.asInt();
GraalError.guarantee(v == value.asInt(), "type mismatch");
shortArray[idx] = v;
} else if (array instanceof char[] charArray) {
char v = (char) value.asInt();
GraalError.guarantee(v == value.asInt(), "type mismatch");
charArray[idx] = v;
} else if (array instanceof int[] intArray) {
intArray[idx] = value.asInt();
} else if (array instanceof long[] longArray) {
longArray[idx] = value.asLong();
} else if (array instanceof float[] floatArray) {
floatArray[idx] = value.asFloat();
} else if (array instanceof double[] doubleArray) {
doubleArray[idx] = value.asDouble();
} else {
throw AnalysisError.shouldNotReachHere("Unexpected array type: " + array.getClass());
if (value.getJavaKind() != type.getComponentType().getJavaKind()) {
throw AnalysisError.shouldNotReachHere("Cannot store value of kind " + value.getJavaKind() + " into primitive array of type " + type);
}
Array.set(array, idx, value.asBoxedPrimitive());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ void markTypeInstantiated(AnalysisType type, ScanReason reason) {
universe.getBigbang().registerTypeAsInHeap(type, reason);
}

JavaConstant createImageHeapConstant(JavaConstant constant, ScanReason reason) {
public JavaConstant createImageHeapConstant(JavaConstant constant, ScanReason reason) {
if (isNonNullObjectConstant(constant)) {
return getOrCreateImageHeapConstant(constant, reason);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,8 @@ public enum UsageKind {
}

private final AnalysisFuture<Void> onTypeReachableTask;
private final AnalysisFuture<Void> initializeMetaDataTask;

/**
* Additional information that is only available for types that are marked as reachable.
*/
Expand Down Expand Up @@ -302,6 +304,7 @@ public AnalysisType(AnalysisUniverse universe, ResolvedJavaType javaType, JavaKi

/* The registration task initializes the type. */
this.onTypeReachableTask = new AnalysisFuture<>(() -> universe.onTypeReachable(this), null);
this.initializeMetaDataTask = new AnalysisFuture<>(() -> universe.initializeMetaData(this), null);
this.typeData = new AnalysisFuture<>(() -> {
AnalysisError.guarantee(universe.getHeapScanner() != null, "Heap scanner is not available.");
return universe.getHeapScanner().computeTypeData(this);
Expand Down Expand Up @@ -689,6 +692,10 @@ public void ensureOnTypeReachableTaskDone() {
onTypeReachableTask.ensureDone();
}

public AnalysisFuture<Void> getInitializeMetaDataTask() {
return initializeMetaDataTask;
}

public boolean getReachabilityListenerNotified() {
return reachabilityListenerNotified;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,10 @@ public void onTypeReachable(AnalysisType type) {
}
}

public void initializeMetaData(AnalysisType type) {
bb.initializeMetaData(type);
}

public SubstitutionProcessor getSubstitutions() {
return substitutions;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public class InlineBeforeAnalysisGraphDecoder extends PEGraphDecoder {

public class InlineBeforeAnalysisMethodScope extends PEMethodScope {

private final InlineBeforeAnalysisPolicy.AbstractPolicyScope policyScope;
public final InlineBeforeAnalysisPolicy.AbstractPolicyScope policyScope;

private boolean inliningAborted;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@
import com.oracle.svm.hosted.cenum.CEnumCallWrapperSubstitutionProcessor;
import com.oracle.svm.hosted.classinitialization.ClassInitializationFeature;
import com.oracle.svm.hosted.classinitialization.ClassInitializationSupport;
import com.oracle.svm.hosted.classinitialization.SimulateClassInitializerSupport;
import com.oracle.svm.hosted.code.CEntryPointCallStubSupport;
import com.oracle.svm.hosted.code.CEntryPointData;
import com.oracle.svm.hosted.code.CFunctionSubstitutionProcessor;
Expand Down Expand Up @@ -907,6 +908,8 @@ protected void setupNativeImage(OptionValues options, Map<Method, CEntryPointDat
originalProviders, platformConfig, classInitializationSupport, aMetaAccess);
aUniverse.hostVM().initializeProviders(aProviders);

ImageSingletons.add(SimulateClassInitializerSupport.class, ((SVMHost) aUniverse.hostVM()).createSimulateClassInitializerSupport(aMetaAccess));

bb = createBigBang(options, aUniverse, aMetaAccess, aProviders, analysisExecutor, loader.watchdog::recordActivity,
annotationSubstitutions);
aUniverse.setBigBang(bb);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
import com.oracle.graal.pointsto.infrastructure.OriginalClassProvider;
import com.oracle.graal.pointsto.infrastructure.UniverseMetaAccess;
import com.oracle.graal.pointsto.meta.AnalysisField;
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.AnalysisUniverse;
Expand Down Expand Up @@ -118,6 +119,7 @@
import com.oracle.svm.hosted.analysis.SVMParsingSupport;
import com.oracle.svm.hosted.classinitialization.ClassInitializationOptions;
import com.oracle.svm.hosted.classinitialization.ClassInitializationSupport;
import com.oracle.svm.hosted.classinitialization.SimulateClassInitializerSupport;
import com.oracle.svm.hosted.code.InliningUtilities;
import com.oracle.svm.hosted.code.SubstrateCompilationDirectives;
import com.oracle.svm.hosted.code.UninterruptibleAnnotationChecker;
Expand All @@ -127,6 +129,7 @@
import com.oracle.svm.hosted.meta.HostedUniverse;
import com.oracle.svm.hosted.phases.AnalysisGraphBuilderPhase;
import com.oracle.svm.hosted.phases.ImplicitAssertionsPhase;
import com.oracle.svm.hosted.phases.InlineBeforeAnalysisGraphDecoderImpl;
import com.oracle.svm.hosted.phases.InlineBeforeAnalysisPolicyImpl;
import com.oracle.svm.hosted.phases.InlineBeforeAnalysisPolicyUtils;
import com.oracle.svm.hosted.substitute.UnsafeAutomaticSubstitutionProcessor;
Expand Down Expand Up @@ -298,7 +301,7 @@ public void onTypeReachable(AnalysisType analysisType) {
}

/* Decide when the type should be initialized. */
classInitializationSupport.maybeInitializeHosted(analysisType);
classInitializationSupport.maybeInitializeAtBuildTime(analysisType);

/*
* For reachable classes, registering class's package in appropriate class loader.
Expand Down Expand Up @@ -328,10 +331,10 @@ public void onTypeReachable(AnalysisType analysisType) {

@Override
public boolean isInitialized(AnalysisType type) {
boolean shouldInitializeAtRuntime = classInitializationSupport.shouldInitializeAtRuntime(type);
assert shouldInitializeAtRuntime || type.getWrapped().isInitialized() : "Types that are not marked for runtime initializations must have been initialized: " + type;
boolean initializedAtBuildTime = classInitializationSupport.maybeInitializeAtBuildTime(type);
assert !initializedAtBuildTime || type.getWrapped().isInitialized() : "Types that are not marked for runtime initializations must have been initialized: " + type;

return !shouldInitializeAtRuntime;
return initializedAtBuildTime;
}

private final boolean parseOnce = SubstrateOptions.parseOnce();
Expand Down Expand Up @@ -765,7 +768,7 @@ private InlineBeforeAnalysisPolicy inlineBeforeAnalysisPolicy(MultiMethod.MultiM

@Override
public InlineBeforeAnalysisGraphDecoder createInlineBeforeAnalysisGraphDecoder(BigBang bb, AnalysisMethod method, StructuredGraph resultGraph) {
return new InlineBeforeAnalysisGraphDecoder(bb, inlineBeforeAnalysisPolicy(method.getMultiMethodKey()), resultGraph, bb.getProviders(method), null);
return new InlineBeforeAnalysisGraphDecoderImpl(bb, inlineBeforeAnalysisPolicy(method.getMultiMethodKey()), resultGraph, bb.getProviders(method));
}

public static class Options {
Expand Down Expand Up @@ -956,4 +959,8 @@ public Function<AnalysisType, ResolvedJavaType> getStrengthenGraphsToTargetFunct
}
return super.getStrengthenGraphsToTargetFunction(key);
}

public SimulateClassInitializerSupport createSimulateClassInitializerSupport(AnalysisMetaAccess aMetaAccess) {
return new SimulateClassInitializerSupport(aMetaAccess, this);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ static JavaConstant readFieldValue(MetaAccessProvider metaAccess, ClassInitializ
assert readableField.isValueAvailable() : "Field " + readableField.format("%H.%n") + " value not available for reading.";
return readableField.readValue(metaAccess, classInitializationSupport, receiver);

} else if (classInitializationSupport.shouldInitializeAtRuntime(field.getDeclaringClass())) {
} else if (!classInitializationSupport.maybeInitializeAtBuildTime(field.getDeclaringClass())) {
/*
* The class is initialized at image run time. We must not use any field value from the
* image builder VM, even if the class is already initialized there. We need to return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
import com.oracle.svm.core.meta.MethodPointer;
import com.oracle.svm.hosted.ExceptionSynthesizer;
import com.oracle.svm.hosted.SVMHost;
import com.oracle.svm.hosted.classinitialization.SimulateClassInitializerSupport;
import com.oracle.svm.util.ReflectionUtil;

import jdk.vm.ci.meta.ConstantReflectionProvider;
Expand Down Expand Up @@ -113,7 +114,7 @@ public void initializeMetaData(ImageHeapScanner heapScanner, AnalysisType type)
/* Support for Java enumerations. */
if (type.isEnum()) {
AnalysisError.guarantee(hub.shouldInitEnumConstants(), "Enum constants already initialized for %s.", type.toJavaName(true));
if (hostVM.getClassInitializationSupport().shouldInitializeAtRuntime(type)) {
if (!hostVM.getClassInitializationSupport().maybeInitializeAtBuildTime(type)) {
hub.initEnumConstantsAtRuntime(javaClass);
} else {
hub.initEnumConstants(retrieveEnumConstantArray(type, javaClass));
Expand Down Expand Up @@ -164,12 +165,12 @@ private Enum<?>[] retrieveEnumConstantArray(AnalysisType type, Class<?> javaClas

private void buildClassInitializationInfo(ImageHeapScanner heapScanner, AnalysisType type, DynamicHub hub) {
AnalysisError.guarantee(hub.getClassInitializationInfo() == null, "Class initialization info already computed for %s.", type.toJavaName(true));
boolean initializedAtBuildTime = SimulateClassInitializerSupport.singleton().trySimulateClassInitializer(bb, type);
ClassInitializationInfo info;
if (hostVM.getClassInitializationSupport().shouldInitializeAtRuntime(type)) {
info = buildRuntimeInitializationInfo(type);
} else {
assert type.isInitialized();
if (initializedAtBuildTime) {
info = type.getClassInitializer() == null ? ClassInitializationInfo.NO_INITIALIZER_INFO_SINGLETON : ClassInitializationInfo.INITIALIZED_INFO_SINGLETON;
} else {
info = buildRuntimeInitializationInfo(type);
}
hub.setClassInitializationInfo(info);
heapScanner.rescanField(hub, dynamicHubClassInitializationInfoField);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ public void onFieldAccessed(AnalysisField field) {

@Override
public void onTypeReachable(AnalysisType type) {
postTask(d -> initializeMetaData(type));
postTask(d -> type.getInitializeMetaDataTask().ensureDone());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ public void onFieldAccessed(AnalysisField field) {

@Override
public void onTypeReachable(AnalysisType type) {
postTask(d -> initializeMetaData(type));
postTask(d -> type.getInitializeMetaDataTask().ensureDone());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ protected void checkUnsafeOffset(ValueNode base, ValueNode offsetNode) {
LoadFieldNode offsetLoadNode = (LoadFieldNode) offsetNode;
AnalysisField field = (AnalysisField) offsetLoadNode.field();
if (field.isStatic() &&
!getHostVM().getClassInitializationSupport().shouldInitializeAtRuntime(field.getDeclaringClass()) &&
getHostVM().getClassInitializationSupport().maybeInitializeAtBuildTime(field.getDeclaringClass()) &&
!field.getDeclaringClass().unsafeFieldsRecomputed() &&
!(field.wrapped instanceof ComputedValueField) &&
!(base.isConstant() && base.asConstant().isDefaultForKind())) {
Expand Down
Loading

0 comments on commit 2055cc2

Please sign in to comment.