Skip to content

Commit 604b171

Browse files
committed
Always use Set.of for conditions.
1 parent ecaff2c commit 604b171

File tree

12 files changed

+144
-63
lines changed

12 files changed

+144
-63
lines changed

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/classinitialization/ClassInitializationInfo.java

Lines changed: 74 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -50,35 +50,45 @@
5050

5151
/**
5252
* Information about the runtime class initialization state of a {@link DynamicHub class}, and
53-
* {@link #initialize implementation} of class initialization according to the Java VM
54-
* specification.
55-
*
53+
* {@link #slowPath(ClassInitializationInfo, DynamicHub)} implementation} of class initialization
54+
* according to the Java VM specification.
55+
* <p>
5656
* The information is not directly stored in {@link DynamicHub} because 1) the class initialization
5757
* state is mutable while {@link DynamicHub} must be immutable, and 2) few classes require
5858
* initialization at runtime so factoring out the information reduces image size.
5959
*/
6060
@InternalVMMethod
6161
public final class ClassInitializationInfo {
6262

63-
/** Singleton for classes that failed to link during image building. */
64-
public static ClassInitializationInfo createFailedInfo() {
65-
return new ClassInitializationInfo(InitState.InitializationError);
63+
private static final ClassInitializationInfo NO_INITIALIZER_NO_TRACKING = new ClassInitializationInfo(InitState.FullyInitialized, false, true, false);
64+
private static final ClassInitializationInfo INITIALIZED_NO_TRACKING = new ClassInitializationInfo(InitState.FullyInitialized, true, true, false);
65+
66+
public static ClassInitializationInfo createFailedInfo(boolean typeReachedTracked) {
67+
return new ClassInitializationInfo(InitState.InitializationError, typeReachedTracked);
6668
}
6769

6870
/**
6971
* Singleton for classes that are already initialized during image building and do not need
7072
* class initialization at runtime, and don't have {@code <clinit>} methods.
7173
*/
72-
public static ClassInitializationInfo createNoInitializerInfo() {
73-
return new ClassInitializationInfo(InitState.FullyInitialized, false, true);
74+
public static ClassInitializationInfo createNoInitializerInfo(boolean typeReachedTracked) {
75+
if (typeReachedTracked) {
76+
return new ClassInitializationInfo(InitState.FullyInitialized, false, true, typeReachedTracked);
77+
} else {
78+
return NO_INITIALIZER_NO_TRACKING;
79+
}
7480
}
7581

7682
/**
7783
* For classes that are already initialized during image building and do not need class
7884
* initialization at runtime, but have {@code <clinit>} methods.
7985
*/
80-
public static ClassInitializationInfo createInitializedInfo() {
81-
return new ClassInitializationInfo(InitState.FullyInitialized, true, true);
86+
public static ClassInitializationInfo createInitializedInfo(boolean typeReachedTracked) {
87+
if (typeReachedTracked) {
88+
return new ClassInitializationInfo(InitState.FullyInitialized, true, true, typeReachedTracked);
89+
} else {
90+
return INITIALIZED_NO_TRACKING;
91+
}
8292
}
8393

8494
enum InitState {
@@ -95,6 +105,12 @@ enum InitState {
95105
InitializationError
96106
}
97107

108+
enum ReachedTriState {
109+
UNTRACKED,
110+
NOT_REACHED,
111+
REACHED
112+
}
113+
98114
interface ClassInitializerFunctionPointer extends CFunctionPointer {
99115
@InvokeJavaFunctionPointer
100116
void invoke();
@@ -106,6 +122,11 @@ interface ClassInitializerFunctionPointer extends CFunctionPointer {
106122
*/
107123
private final FunctionPointerHolder classInitializer;
108124

125+
/**
126+
* Describes whether this class has been reached at runtime.
127+
*/
128+
private ReachedTriState reached;
129+
109130
/**
110131
* The current initialization state.
111132
*/
@@ -141,34 +162,37 @@ interface ClassInitializerFunctionPointer extends CFunctionPointer {
141162
private boolean hasInitializer;
142163
private boolean buildTimeInitialized;
143164

144-
private boolean reached;
165+
public boolean isReachedOrUntracked() {
166+
return reached == ReachedTriState.REACHED || reached == ReachedTriState.UNTRACKED;
167+
}
145168

146169
public boolean isReached() {
147-
return reached;
170+
return reached == ReachedTriState.REACHED;
148171
}
149172

150173
@Platforms(Platform.HOSTED_ONLY.class)
151-
private ClassInitializationInfo(InitState initState, boolean hasInitializer, boolean buildTimeInitialized) {
152-
this(initState);
174+
private ClassInitializationInfo(InitState initState, boolean hasInitializer, boolean buildTimeInitialized, boolean typeReachedTracked) {
175+
this(initState, typeReachedTracked);
153176
this.hasInitializer = hasInitializer;
154177
this.buildTimeInitialized = buildTimeInitialized;
155-
this.reached = false;
156178
}
157179

158180
@Platforms(Platform.HOSTED_ONLY.class)
159-
private ClassInitializationInfo(InitState initState) {
181+
private ClassInitializationInfo(InitState initState, boolean typeReachedTracked) {
160182
this.classInitializer = null;
161183
this.initState = initState;
162184
this.initLock = initState == InitState.FullyInitialized ? null : new ReentrantLock();
163185
this.hasInitializer = true;
186+
this.reached = typeReachedTracked ? ReachedTriState.NOT_REACHED : ReachedTriState.UNTRACKED;
164187
}
165188

166189
@Platforms(Platform.HOSTED_ONLY.class)
167-
public ClassInitializationInfo(CFunctionPointer classInitializer) {
190+
public ClassInitializationInfo(CFunctionPointer classInitializer, boolean typeReachedTracked) {
168191
this.classInitializer = classInitializer == null || classInitializer.isNull() ? null : new FunctionPointerHolder(classInitializer);
169192
this.initState = InitState.Linked;
170193
this.initLock = new ReentrantLock();
171194
this.hasInitializer = classInitializer != null;
195+
this.reached = typeReachedTracked ? ReachedTriState.NOT_REACHED : ReachedTriState.UNTRACKED;
172196
}
173197

174198
public boolean hasInitializer() {
@@ -199,18 +223,41 @@ private boolean isReentrantInitialization(IsolateThread thread) {
199223
return thread.equal(initThread);
200224
}
201225

202-
private static void reach(DynamicHub hub) {
226+
/**
227+
* Marks the hierarchy of <code>hub</code> as reached. The concurrency primitives are not needed
228+
* as the external readers are always on the separate thread for which the ordering is not
229+
* relevant.
230+
* </p>
231+
* Note: as an optimization we can stop the traversal when a is already reached: The thread that
232+
* wrote the flag has continued up the hierarchy. No need to use memory ordering, in the worst
233+
* case the threads will mark parts of the hierarchy twice. This is guaranteed by the fact that
234+
* the thread that writes always continues up the hierarchy. If the thread reached a reached
235+
* type, that means there is at least one thread that climbed above the current point.
236+
*/
237+
private static void markReached(DynamicHub hub) {
203238
var current = hub;
204239
do {
205-
current.getClassInitializationInfo().reached = true;
240+
if (current.getClassInitializationInfo().reached == ReachedTriState.REACHED) {
241+
break;
242+
}
243+
244+
if (current.getClassInitializationInfo().reached != ReachedTriState.UNTRACKED) {
245+
current.getClassInitializationInfo().reached = ReachedTriState.REACHED;
246+
}
206247
reachInterfaces(current);
207248
current = current.getSuperHub();
208249
} while (current != null);
209250
}
210251

211252
private static void reachInterfaces(DynamicHub hub) {
212253
for (DynamicHub superInterface : hub.getInterfaces()) {
213-
superInterface.getClassInitializationInfo().reached = true;
254+
if (superInterface.getClassInitializationInfo().reached == ReachedTriState.REACHED) {
255+
return;
256+
}
257+
258+
if (hub.getClassInitializationInfo().reached != ReachedTriState.UNTRACKED) {
259+
superInterface.getClassInitializationInfo().reached = ReachedTriState.REACHED;
260+
}
214261
reachInterfaces(superInterface);
215262
}
216263
}
@@ -223,10 +270,15 @@ private static void reachInterfaces(DynamicHub hub) {
223270
* https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.5
224271
*/
225272
@SubstrateForeignCallTarget(stubCallingConvention = true)
226-
private static void initialize(ClassInitializationInfo info, DynamicHub hub) {
273+
private static void slowPath(ClassInitializationInfo info, DynamicHub hub) {
227274
IsolateThread self = CurrentIsolate.getCurrentThread();
228275

229-
reach(hub);
276+
/*
277+
* Types are marked as reached before any initialization is performed. Reason: the results
278+
* should be visible in class initializers of the whole hierarchy as they could use
279+
* reflection.
280+
*/
281+
markReached(hub);
230282

231283
if (info.isInitialized()) {
232284
return;

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/classinitialization/EnsureClassInitializedSnippets.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,10 @@
5454
import jdk.graal.compiler.replacements.Snippets;
5555

5656
public final class EnsureClassInitializedSnippets extends SubstrateTemplates implements Snippets {
57-
private static final SubstrateForeignCallDescriptor INITIALIZE = SnippetRuntime.findForeignCall(ClassInitializationInfo.class, "initialize", HAS_SIDE_EFFECT, LocationIdentity.any());
57+
private static final SubstrateForeignCallDescriptor SLOW_PATH = SnippetRuntime.findForeignCall(ClassInitializationInfo.class, "slowPath", HAS_SIDE_EFFECT, LocationIdentity.any());
5858

5959
public static final SubstrateForeignCallDescriptor[] FOREIGN_CALLS = new SubstrateForeignCallDescriptor[]{
60-
INITIALIZE,
60+
SLOW_PATH,
6161
};
6262

6363
/**
@@ -73,13 +73,13 @@ private static void ensureClassIsInitializedSnippet(@Snippet.NonNullParameter Dy
7373
*/
7474
ClassInitializationInfo infoNonNull = (ClassInitializationInfo) PiNode.piCastNonNull(info, SnippetAnchorNode.anchor());
7575

76-
if (!BranchProbabilityNode.probability(BranchProbabilityNode.EXTREMELY_SLOW_PATH_PROBABILITY, infoNonNull.isInitialized() & infoNonNull.isReached())) {
77-
callInitializationRoutine(INITIALIZE, infoNonNull, DynamicHub.toClass(hub));
76+
if (!BranchProbabilityNode.probability(BranchProbabilityNode.EXTREMELY_SLOW_PATH_PROBABILITY, infoNonNull.isInitialized() & infoNonNull.isReachedOrUntracked())) {
77+
callSlowPath(SLOW_PATH, infoNonNull, DynamicHub.toClass(hub));
7878
}
7979
}
8080

8181
@NodeIntrinsic(value = ForeignCallWithExceptionNode.class)
82-
private static native void callInitializationRoutine(@ConstantNodeParameter ForeignCallDescriptor descriptor, ClassInitializationInfo info, Class<?> clazz);
82+
private static native void callSlowPath(@ConstantNodeParameter ForeignCallDescriptor descriptor, ClassInitializationInfo info, Class<?> clazz);
8383

8484
@SuppressWarnings("unused")
8585
public static void registerLowerings(OptionValues options, Providers providers, Map<Class<? extends Node>, NodeLoweringProvider<?>> lowerings) {

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConditionalRuntimeValue.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,16 @@
2525
package com.oracle.svm.core.configure;
2626

2727
import java.util.Arrays;
28+
import java.util.HashSet;
2829
import java.util.Set;
2930
import java.util.function.Predicate;
3031
import java.util.stream.Collectors;
3132

3233
import org.graalvm.nativeimage.Platform;
3334
import org.graalvm.nativeimage.Platforms;
3435

36+
import com.oracle.svm.core.util.VMError;
37+
3538
public final class ConditionalRuntimeValue<T> {
3639
/*
3740
* Intentionally an array to save space in the image heap.
@@ -40,8 +43,16 @@ public final class ConditionalRuntimeValue<T> {
4043
private boolean satisfied;
4144
volatile T value;
4245

46+
@Platforms(Platform.HOSTED_ONLY.class)
4347
public ConditionalRuntimeValue(Set<Class<?>> conditions, T value) {
44-
elements = conditions.toArray(Class[]::new);
48+
if (!conditions.isEmpty()) {
49+
elements = conditions.toArray(Class[]::new);
50+
} else {
51+
elements = null;
52+
satisfied = true;
53+
}
54+
55+
VMError.guarantee(conditions.stream().noneMatch(c -> c.equals(Object.class)), "java.lang.Object must not be in conditions as it is always true.");
4556
this.value = value;
4657
}
4758

@@ -51,7 +62,11 @@ public T getValueUnconditionally() {
5162
}
5263

5364
public Set<Class<?>> getConditions() {
54-
return Arrays.stream(elements).collect(Collectors.toSet());
65+
if (elements == null) {
66+
return new HashSet<>();
67+
} else {
68+
return Arrays.stream(elements).collect(Collectors.toSet());
69+
}
5570
}
5671

5772
public T getValue(Predicate<Class<?>> conditionSatisfied) {

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ClassForNameSupport.java

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -80,39 +80,48 @@ public void registerClass(ConfigurationCondition condition, Class<?> clazz) {
8080
(currentValue == NEGATIVE_QUERY && ReflectionUtil.lookupClass(true, name) == null),
8181
"Invalid Class.forName value for %s: %s", name, currentValue);
8282

83-
Class<?> conditionClass = condition.getType();
84-
Set<Class<?>> resConditions;
85-
if (conditionalRuntimeValue != null) {
86-
Set<Class<?>> conditions = conditionalRuntimeValue.getConditions();
87-
conditions.add(conditionClass);
88-
resConditions = conditions;
89-
} else {
90-
resConditions = Set.of(conditionClass);
91-
}
92-
83+
var cond = updateConditionalValue(conditionalRuntimeValue, clazz, condition);
9384
if (currentValue == NEGATIVE_QUERY) {
94-
knownClasses.put(name, new ConditionalRuntimeValue<>(resConditions, clazz));
85+
knownClasses.put(name, cond);
9586
} else if (currentValue == null) {
96-
knownClasses.put(name, new ConditionalRuntimeValue<>(resConditions, clazz));
87+
knownClasses.put(name, cond);
9788
} else if (currentValue instanceof Class<?>) {
98-
knownClasses.put(name, new ConditionalRuntimeValue<>(resConditions, clazz));
89+
knownClasses.put(name, cond);
9990
} else {
100-
throw VMError.shouldNotReachHere("Testing");
91+
throw VMError.shouldNotReachHere("Other cases must not happen.");
92+
}
93+
}
94+
95+
private static ConditionalRuntimeValue<Object> updateConditionalValue(ConditionalRuntimeValue<Object> existingConditionalValue, Object newConditionedValue,
96+
ConfigurationCondition additionalCondition) {
97+
Set<Class<?>> resConditions = Set.of();
98+
if (!additionalCondition.isAlwaysTrue()) {
99+
Class<?> conditionClass = additionalCondition.getType();
100+
if (existingConditionalValue != null) {
101+
Set<Class<?>> conditions = existingConditionalValue.getConditions();
102+
conditions.add(conditionClass);
103+
resConditions = Set.of(conditions.toArray(v -> new Class<?>[v]));
104+
} else {
105+
resConditions = Set.of(conditionClass);
106+
}
101107
}
108+
return new ConditionalRuntimeValue<>(resConditions, newConditionedValue);
102109
}
103110

104111
@Platforms(Platform.HOSTED_ONLY.class)
105-
public void registerExceptionForClass(ConfigurationCondition cnd, String className, Throwable t) {
106-
knownClasses.put(className, new ConditionalRuntimeValue<>(Set.of(cnd.getType()), t));
112+
public void registerExceptionForClass(ConfigurationCondition condition, String className, Throwable t) {
113+
Set<Class<?>> typeSet = condition.isAlwaysTrue() ? Set.of() : Set.of(condition.getType());
114+
knownClasses.put(className, new ConditionalRuntimeValue<>(typeSet, t));
107115
}
108116

109117
@Platforms(Platform.HOSTED_ONLY.class)
110-
public void registerNegativeQuery(ConfigurationCondition cnd, String className) {
118+
public void registerNegativeQuery(ConfigurationCondition condition, String className) {
111119
/*
112120
* If the class is not accessible by the builder class loader, but was already registered
113121
* through registerClass(Class<?>), we don't overwrite the actual class or exception.
114122
*/
115-
knownClasses.putIfAbsent(className, new ConditionalRuntimeValue<>(Set.of(cnd.getType()), NEGATIVE_QUERY));
123+
Set<Class<?>> typeSet = condition.isAlwaysTrue() ? Set.of() : Set.of(condition.getType());
124+
knownClasses.putIfAbsent(className, new ConditionalRuntimeValue<>(typeSet, NEGATIVE_QUERY));
116125
}
117126

118127
public Class<?> forNameOrNull(String className, ClassLoader classLoader) {

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/DynamicHubInitializer.java

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
import com.oracle.svm.hosted.ClassLoaderFeature;
5050
import com.oracle.svm.hosted.ExceptionSynthesizer;
5151
import com.oracle.svm.hosted.SVMHost;
52+
import com.oracle.svm.hosted.classinitialization.ClassInitializationSupport;
5253
import com.oracle.svm.hosted.classinitialization.SimulateClassInitializerSupport;
5354
import com.oracle.svm.util.ReflectionUtil;
5455

@@ -191,16 +192,18 @@ private void buildClassInitializationInfo(ImageHeapScanner heapScanner, Analysis
191192
AnalysisError.guarantee(hub.getClassInitializationInfo() == null, "Class initialization info already computed for %s.", type.toJavaName(true));
192193
boolean initializedAtBuildTime = SimulateClassInitializerSupport.singleton().trySimulateClassInitializer(bb, type);
193194
ClassInitializationInfo info;
195+
var typeReachedTracked = ClassInitializationSupport.singleton().requiresInitializationNodeForTypeReached(type);
194196
if (initializedAtBuildTime) {
195-
info = type.getClassInitializer() == null ? ClassInitializationInfo.createNoInitializerInfo() : ClassInitializationInfo.createInitializedInfo();
197+
info = type.getClassInitializer() == null ? ClassInitializationInfo.createNoInitializerInfo(typeReachedTracked)
198+
: ClassInitializationInfo.createInitializedInfo(typeReachedTracked);
196199
} else {
197-
info = buildRuntimeInitializationInfo(type);
200+
info = buildRuntimeInitializationInfo(type, typeReachedTracked);
198201
}
199202
hub.setClassInitializationInfo(info);
200203
heapScanner.rescanField(hub, dynamicHubClassInitializationInfoField);
201204
}
202205

203-
private ClassInitializationInfo buildRuntimeInitializationInfo(AnalysisType type) {
206+
private ClassInitializationInfo buildRuntimeInitializationInfo(AnalysisType type, boolean typeReachedTracked) {
204207
assert !type.isInitialized();
205208
try {
206209
/*
@@ -213,13 +216,13 @@ private ClassInitializationInfo buildRuntimeInitializationInfo(AnalysisType type
213216
/* Synthesize a VerifyError to be thrown at run time. */
214217
AnalysisMethod throwVerifyError = metaAccess.lookupJavaMethod(ExceptionSynthesizer.throwExceptionMethod(VerifyError.class));
215218
bb.addRootMethod(throwVerifyError, true, "Class initialization error, registered in " + DynamicHubInitializer.class);
216-
return new ClassInitializationInfo(new MethodPointer(throwVerifyError));
219+
return new ClassInitializationInfo(new MethodPointer(throwVerifyError), typeReachedTracked);
217220
} catch (Throwable t) {
218221
/*
219222
* All other linking errors will be reported as NoClassDefFoundError when initialization
220223
* is attempted at run time.
221224
*/
222-
return ClassInitializationInfo.createFailedInfo();
225+
return ClassInitializationInfo.createFailedInfo(typeReachedTracked);
223226
}
224227

225228
/*
@@ -234,7 +237,7 @@ private ClassInitializationInfo buildRuntimeInitializationInfo(AnalysisType type
234237
bb.addRootMethod(classInitializer, true, "Class initialization, registered in " + DynamicHubInitializer.class);
235238
classInitializerFunction = new MethodPointer(classInitializer);
236239
}
237-
return new ClassInitializationInfo(classInitializerFunction);
240+
return new ClassInitializationInfo(classInitializerFunction, typeReachedTracked);
238241
}
239242

240243
class InterfacesEncodingKey {

0 commit comments

Comments
 (0)