Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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