Skip to content

Commit d1352c3

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

14 files changed

+156
-69
lines changed

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

Lines changed: 79 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -50,35 +50,49 @@
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+
}
92+
}
93+
94+
public boolean requiresSlowPath() {
95+
return slowPathRequired;
8296
}
8397

8498
enum InitState {
@@ -95,6 +109,12 @@ enum InitState {
95109
InitializationError
96110
}
97111

112+
enum ReachedTriState {
113+
UNTRACKED,
114+
NOT_REACHED,
115+
REACHED
116+
}
117+
98118
interface ClassInitializerFunctionPointer extends CFunctionPointer {
99119
@InvokeJavaFunctionPointer
100120
void invoke();
@@ -106,6 +126,11 @@ interface ClassInitializerFunctionPointer extends CFunctionPointer {
106126
*/
107127
private final FunctionPointerHolder classInitializer;
108128

129+
/**
130+
* Describes whether this class has been reached at runtime.
131+
*/
132+
private ReachedTriState reached;
133+
109134
/**
110135
* The current initialization state.
111136
*/
@@ -141,34 +166,37 @@ interface ClassInitializerFunctionPointer extends CFunctionPointer {
141166
private boolean hasInitializer;
142167
private boolean buildTimeInitialized;
143168

144-
private boolean reached;
169+
private boolean slowPathRequired;
145170

146171
public boolean isReached() {
147-
return reached;
172+
return reached == ReachedTriState.REACHED;
148173
}
149174

150175
@Platforms(Platform.HOSTED_ONLY.class)
151-
private ClassInitializationInfo(InitState initState, boolean hasInitializer, boolean buildTimeInitialized) {
152-
this(initState);
176+
private ClassInitializationInfo(InitState initState, boolean hasInitializer, boolean buildTimeInitialized, boolean typeReachedTracked) {
177+
this(initState, typeReachedTracked);
153178
this.hasInitializer = hasInitializer;
154179
this.buildTimeInitialized = buildTimeInitialized;
155-
this.reached = false;
156180
}
157181

158182
@Platforms(Platform.HOSTED_ONLY.class)
159-
private ClassInitializationInfo(InitState initState) {
183+
private ClassInitializationInfo(InitState initState, boolean typeReachedTracked) {
160184
this.classInitializer = null;
161185
this.initState = initState;
162186
this.initLock = initState == InitState.FullyInitialized ? null : new ReentrantLock();
163187
this.hasInitializer = true;
188+
this.reached = typeReachedTracked ? ReachedTriState.NOT_REACHED : ReachedTriState.UNTRACKED;
189+
this.slowPathRequired = reached != ReachedTriState.UNTRACKED || initState != InitState.FullyInitialized;
164190
}
165191

166192
@Platforms(Platform.HOSTED_ONLY.class)
167-
public ClassInitializationInfo(CFunctionPointer classInitializer) {
193+
public ClassInitializationInfo(CFunctionPointer classInitializer, boolean typeReachedTracked) {
168194
this.classInitializer = classInitializer == null || classInitializer.isNull() ? null : new FunctionPointerHolder(classInitializer);
169195
this.initState = InitState.Linked;
170196
this.initLock = new ReentrantLock();
171197
this.hasInitializer = classInitializer != null;
198+
this.reached = typeReachedTracked ? ReachedTriState.NOT_REACHED : ReachedTriState.UNTRACKED;
199+
this.slowPathRequired = true;
172200
}
173201

174202
public boolean hasInitializer() {
@@ -199,18 +227,41 @@ private boolean isReentrantInitialization(IsolateThread thread) {
199227
return thread.equal(initThread);
200228
}
201229

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

211256
private static void reachInterfaces(DynamicHub hub) {
212257
for (DynamicHub superInterface : hub.getInterfaces()) {
213-
superInterface.getClassInitializationInfo().reached = true;
258+
if (superInterface.getClassInitializationInfo().reached == ReachedTriState.REACHED) {
259+
return;
260+
}
261+
262+
if (hub.getClassInitializationInfo().reached != ReachedTriState.UNTRACKED) {
263+
superInterface.getClassInitializationInfo().reached = ReachedTriState.REACHED;
264+
}
214265
reachInterfaces(superInterface);
215266
}
216267
}
@@ -223,10 +274,15 @@ private static void reachInterfaces(DynamicHub hub) {
223274
* https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.5
224275
*/
225276
@SubstrateForeignCallTarget(stubCallingConvention = true)
226-
private static void initialize(ClassInitializationInfo info, DynamicHub hub) {
277+
private static void slowPath(ClassInitializationInfo info, DynamicHub hub) {
227278
IsolateThread self = CurrentIsolate.getCurrentThread();
228279

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

231287
if (info.isInitialized()) {
232288
return;
@@ -431,6 +487,7 @@ private void setInitializationStateAndNotify(InitState state) {
431487
initLock.lock();
432488
try {
433489
this.initState = state;
490+
this.slowPathRequired = false;
434491
this.initThread = WordFactory.nullPointer();
435492
/* Make sure previous stores are all done, notably the initState. */
436493
Unsafe.getUnsafe().storeFence();

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public class EnsureClassInitializedNode extends WithExceptionNode implements Can
5858
@Input(InputType.State) private FrameState stateAfter;
5959

6060
public static boolean intrinsify(GraphBuilderContext b, ValueNode hub) {
61-
b.add(new EnsureClassInitializedNode(b.nullCheckedValue(hub)));
61+
b.add(new EnsureClassInitializedNode(b.nullCheckedValue(hub), true));
6262
return true;
6363
}
6464

@@ -70,8 +70,8 @@ public EnsureClassInitializedNode(ValueNode hub, FrameState stateAfter, boolean
7070
this.stateAfter = stateAfter;
7171
}
7272

73-
public EnsureClassInitializedNode(ValueNode hub) {
74-
this(hub, null, false);
73+
public EnsureClassInitializedNode(ValueNode hub, boolean requiredForTypeReached) {
74+
this(hub, null, requiredForTypeReached);
7575
}
7676

7777
public ValueNode getHub() {

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.requiresSlowPath())) {
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) {

0 commit comments

Comments
 (0)