Skip to content

Commit

Permalink
Remove the old class initialization strategy
Browse files Browse the repository at this point in the history
  • Loading branch information
christianwimmer committed Feb 27, 2024
1 parent 0ffae37 commit e163fd3
Show file tree
Hide file tree
Showing 26 changed files with 466 additions and 1,891 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
Expand Down Expand Up @@ -46,11 +46,25 @@ public interface RuntimeClassInitializationSupport {

void initializeAtBuildTime(String name, String reason);

void rerunInitialization(String name, String reason);
@Deprecated
default void rerunInitialization(String name, String reason) {
/*
* There is no more difference between initializing a class at run-time and re-running the
* class initializer at run time.
*/
initializeAtRunTime(name, reason);
}

void initializeAtRunTime(Class<?> aClass, String reason);

void rerunInitialization(Class<?> aClass, String reason);
@Deprecated
default void rerunInitialization(Class<?> aClass, String reason) {
/*
* There is no more difference between initializing a class at run-time and re-running the
* class initializer at run time.
*/
initializeAtRunTime(aClass, reason);
}

void initializeAtBuildTime(Class<?> aClass, String reason);
}
1 change: 1 addition & 0 deletions substratevm/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
This changelog summarizes major changes to GraalVM Native Image.

## GraalVM for JDK 23 (Internal Version 24.1.0)
* (GR-51520) The old class initialization strategy, which was deprecated in GraalVM for JDK 22, is removed. The option `StrictImageHeap` no longer has any effect.
* (GR-51106) Fields that are accessed via a `VarHandle` or `MethodHandle` are no longer marked as "unsafe accessed" when the `VarHandle`/`MethodHandle` can be fully intrinsified.
* (GR-49996) Ensure explicitly set image name (e.g., via `-o imagename`) is not accidentally overwritten by `-jar jarfile` option.
* (GR-48683) Together with Red Hat, we added partial support for the JFR event `OldObjectSample`.
Expand Down
28 changes: 6 additions & 22 deletions substratevm/mx.substratevm/mx_substratevm.py
Original file line number Diff line number Diff line change
Expand Up @@ -1549,11 +1549,7 @@ def cinterfacetutorial(args):

@mx.command(suite.name, 'clinittest', 'Runs the ')
def clinittest(args):
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):
def build_and_test_clinittest_image(native_image, args):
args = [] if args is None else args
test_cp = classpath('com.oracle.svm.test')
build_dir = join(svmbuild_dir(), 'clinittest')
Expand All @@ -1563,11 +1559,6 @@ def build_and_test_clinittest_image(native_image, args, new_class_init_policy):
mx.rmtree(build_dir)
mx.ensure_dir_exists(build_dir)

if new_class_init_policy:
policy_args = svm_experimental_options(['-H:+SimulateClassInitializer']) + ['--features=com.oracle.svm.test.clinit.TestClassInitializationFeatureNewPolicyFeature']
else:
policy_args = svm_experimental_options(['-H:-StrictImageHeap', '-H:-SimulateClassInitializer']) + ['--features=com.oracle.svm.test.clinit.TestClassInitializationFeatureOldPolicyFeature']

# Build and run the example
binary_path = join(build_dir, 'clinittest')
native_image([
Expand All @@ -1577,9 +1568,10 @@ def build_and_test_clinittest_image(native_image, args, new_class_init_policy):
'-o', binary_path,
'-H:+ReportExceptionStackTraces',
'-H:Class=com.oracle.svm.test.clinit.TestClassInitialization',
'--features=com.oracle.svm.test.clinit.TestClassInitializationFeature',
] + svm_experimental_options([
'-H:+PrintClassInitialization',
]) + policy_args + args)
]) + args)
mx.run([binary_path])

# Check the reports for initialized classes
Expand All @@ -1593,16 +1585,8 @@ 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:
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)
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)

if len(wrongly_initialized_lines) > 0:
msg = ""
Expand All @@ -1615,7 +1599,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_images, args)
native_image_context_run(build_and_test_clinittest_image, args)


class SubstrateJvmFuncsFallbacksBuilder(mx.Project):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,10 @@ protected static boolean isWindows() {
return Platform.includedIn(Platform.WINDOWS.class);
}

protected static void rerunClassInit(FeatureAccess access, String... classNames) {
protected static void initializeAtRunTime(FeatureAccess access, String... classNames) {
RuntimeClassInitializationSupport classInitSupport = ImageSingletons.lookup(RuntimeClassInitializationSupport.class);
for (String className : classNames) {
classInitSupport.rerunInitialization(clazz(access, className), "for JDK native code support via JNI");
classInitSupport.initializeAtRunTime(clazz(access, className), "for JDK native code support via JNI");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public void afterRegistration(AfterRegistrationAccess access) {
public void duringSetup(DuringSetupAccess access) {
RuntimeClassInitializationSupport rci = ImageSingletons.lookup(RuntimeClassInitializationSupport.class);
rci.initializeAtRunTime("jdk.internal.net.http", "for reading properties at run time");
rci.rerunInitialization("jdk.internal.net.http.websocket.OpeningHandshake", "contains a SecureRandom reference");
rci.initializeAtRunTime("jdk.internal.net.http.websocket.OpeningHandshake", "contains a SecureRandom reference");
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ private void processFieldUpdater(Object updater) {
class InnocuousForkJoinWorkerThreadFeature implements InternalFeature {
@Override
public void duringSetup(DuringSetupAccess access) {
ImageSingletons.lookup(RuntimeClassInitializationSupport.class).rerunInitialization(access.findClassByName("java.util.concurrent.ForkJoinWorkerThread$InnocuousForkJoinWorkerThread"),
ImageSingletons.lookup(RuntimeClassInitializationSupport.class).initializeAtRunTime(access.findClassByName("java.util.concurrent.ForkJoinWorkerThread$InnocuousForkJoinWorkerThread"),
"innocuousThreadGroup must be initialized at run time");
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -896,7 +896,7 @@ protected void setupNativeImage(OptionValues options, Map<Method, CEntryPointDat
ImageSingletons.add(LinkAtBuildTimeSupport.class, new LinkAtBuildTimeSupport(loader, classLoaderSupport));
ImageSingletons.add(ObservableImageHeapMapProvider.class, new ObservableImageHeapMapProviderImpl());

ClassInitializationSupport classInitializationSupport = ClassInitializationSupport.create(originalMetaAccess, loader);
ClassInitializationSupport classInitializationSupport = new ClassInitializationSupport(originalMetaAccess, loader);
ImageSingletons.add(RuntimeClassInitializationSupport.class, classInitializationSupport);
ClassInitializationFeature.processClassInitializationOptions(classInitializationSupport);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,9 @@
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
Expand Down Expand Up @@ -77,11 +75,9 @@
import com.oracle.svm.core.SubstrateOptions.OptimizationLevel;
import com.oracle.svm.core.annotate.InjectAccessors;
import com.oracle.svm.core.c.CGlobalData;
import com.oracle.svm.core.classinitialization.EnsureClassInitializedNode;
import com.oracle.svm.core.graal.meta.SubstrateForeignCallLinkage;
import com.oracle.svm.core.graal.meta.SubstrateForeignCallsProvider;
import com.oracle.svm.core.graal.stackvalue.StackValueNode;
import com.oracle.svm.core.graal.thread.VMThreadLocalAccess;
import com.oracle.svm.core.heap.StoredContinuation;
import com.oracle.svm.core.heap.Target_java_lang_ref_Reference;
import com.oracle.svm.core.heap.UnknownClass;
Expand Down Expand Up @@ -131,11 +127,8 @@
import jdk.graal.compiler.nodes.StaticDeoptimizingNode;
import jdk.graal.compiler.nodes.StructuredGraph;
import jdk.graal.compiler.nodes.ValueNode;
import jdk.graal.compiler.nodes.extended.UnsafeAccessNode;
import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration;
import jdk.graal.compiler.nodes.graphbuilderconf.IntrinsicContext;
import jdk.graal.compiler.nodes.java.AccessFieldNode;
import jdk.graal.compiler.nodes.java.AccessMonitorNode;
import jdk.graal.compiler.options.Option;
import jdk.graal.compiler.options.OptionKey;
import jdk.graal.compiler.options.OptionValues;
Expand Down Expand Up @@ -171,8 +164,6 @@ public class SVMHost extends HostVM {
* need to keep the whole graphs alive.
*/
private final ConcurrentMap<AnalysisMethod, Boolean> containsStackValueNode = new ConcurrentHashMap<>();
private final ConcurrentMap<AnalysisMethod, Boolean> classInitializerSideEffect = new ConcurrentHashMap<>();
private final ConcurrentMap<AnalysisMethod, Set<AnalysisType>> initializedClasses = new ConcurrentHashMap<>();
private final ConcurrentMap<AnalysisMethod, Boolean> analysisTrivialMethods = new ConcurrentHashMap<>();

private final Set<AnalysisField> finalFieldsInitializedOutsideOfConstructor = ConcurrentHashMap.newKeySet();
Expand Down Expand Up @@ -649,53 +640,6 @@ public void methodBeforeTypeFlowCreationHook(BigBang bb, AnalysisMethod method,
} else if (n instanceof ReachabilityRegistrationNode node) {
bb.postTask(debug -> node.getRegistrationTask().ensureDone());
}
checkClassInitializerSideEffect(method, n);
}
}

/**
* Classes are only safe for automatic initialization if the class initializer has no side
* effect on other classes and cannot be influenced by other classes. Otherwise there would be
* observable side effects. For example, if a class initializer of class A writes a static field
* B.f in class B, then someone could rely on reading the old value of B.f before triggering
* initialization of A. Similarly, if a class initializer of class A reads a static field B.f,
* then an early automatic initialization of class A could read a non-yet-set value of B.f.
*
* Note that it is not necessary to disallow instance field accesses: Objects allocated by the
* class initializer itself can always be accessed because they are independent from other
* initializers; all other objects must be loaded transitively from a static field.
*
* Currently, we are conservative and mark all methods that access static fields as unsafe for
* automatic class initialization (unless the class initializer itself accesses a static field
* of its own class - the common way of initializing static fields). The check could be relaxed
* by tracking the call chain, i.e., allowing static field accesses when the root method of the
* call chain is the class initializer. But this does not fit well into the current approach
* where each method has a `Safety` flag.
*/
private void checkClassInitializerSideEffect(AnalysisMethod method, Node n) {
if (n instanceof AccessFieldNode) {
ResolvedJavaField field = ((AccessFieldNode) n).field();
if (field.isStatic() && (!method.isClassInitializer() || !field.getDeclaringClass().equals(method.getDeclaringClass()))) {
classInitializerSideEffect.put(method, true);
}
} else if (n instanceof UnsafeAccessNode || n instanceof VMThreadLocalAccess) {
/*
* Unsafe memory access nodes are rare, so it does not pay off to check what kind of
* field they are accessing.
*
* Methods that access a thread-local value cannot be initialized at image build time
* because such values are not available yet.
*/
classInitializerSideEffect.put(method, true);
} else if (n instanceof EnsureClassInitializedNode) {
ResolvedJavaType type = ((EnsureClassInitializedNode) n).constantTypeOrNull(getProviders(method.getMultiMethodKey()).getConstantReflection());
if (type != null) {
initializedClasses.computeIfAbsent(method, k -> new HashSet<>()).add((AnalysisType) type);
} else {
classInitializerSideEffect.put(method, true);
}
} else if (n instanceof AccessMonitorNode) {
classInitializerSideEffect.put(method, true);
}
}

Expand All @@ -714,19 +658,6 @@ public boolean containsStackValueNode(AnalysisMethod method) {
return containsStackValueNode.containsKey(method);
}

public boolean hasClassInitializerSideEffect(AnalysisMethod method) {
return classInitializerSideEffect.containsKey(method);
}

public Set<AnalysisType> getInitializedClasses(AnalysisMethod method) {
Set<AnalysisType> result = initializedClasses.get(method);
if (result != null) {
return result;
} else {
return Collections.emptySet();
}
}

public boolean isAnalysisTrivialMethod(AnalysisMethod method) {
return analysisTrivialMethods.containsKey(method);
}
Expand Down
Loading

0 comments on commit e163fd3

Please sign in to comment.