Skip to content

Commit

Permalink
Always use Set.of for conditions.
Browse files Browse the repository at this point in the history
  • Loading branch information
vjovanov committed Mar 5, 2024
1 parent 1d4168b commit 0d9d982
Show file tree
Hide file tree
Showing 12 changed files with 157 additions and 66 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,35 +50,49 @@

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

/** Singleton for classes that failed to link during image building. */
public static ClassInitializationInfo createFailedInfo() {
return new ClassInitializationInfo(InitState.InitializationError);
private static final ClassInitializationInfo NO_INITIALIZER_NO_TRACKING = new ClassInitializationInfo(InitState.FullyInitialized, false, true, false);
private static final ClassInitializationInfo INITIALIZED_NO_TRACKING = new ClassInitializationInfo(InitState.FullyInitialized, true, true, false);

public static ClassInitializationInfo createFailedInfo(boolean typeReachedTracked) {
return new ClassInitializationInfo(InitState.InitializationError, typeReachedTracked);
}

/**
* Singleton for classes that are already initialized during image building and do not need
* class initialization at runtime, and don't have {@code <clinit>} methods.
*/
public static ClassInitializationInfo createNoInitializerInfo() {
return new ClassInitializationInfo(InitState.FullyInitialized, false, true);
public static ClassInitializationInfo createNoInitializerInfo(boolean typeReachedTracked) {
if (typeReachedTracked) {
return new ClassInitializationInfo(InitState.FullyInitialized, false, true, typeReachedTracked);
} else {
return NO_INITIALIZER_NO_TRACKING;
}
}

/**
* For classes that are already initialized during image building and do not need class
* initialization at runtime, but have {@code <clinit>} methods.
*/
public static ClassInitializationInfo createInitializedInfo() {
return new ClassInitializationInfo(InitState.FullyInitialized, true, true);
public static ClassInitializationInfo createInitializedInfo(boolean typeReachedTracked) {
if (typeReachedTracked) {
return new ClassInitializationInfo(InitState.FullyInitialized, true, true, typeReachedTracked);
} else {
return INITIALIZED_NO_TRACKING;
}
}

public boolean requiresSlowPath() {
return slowPathRequired;
}

enum InitState {
Expand All @@ -95,6 +109,12 @@ enum InitState {
InitializationError
}

enum ReachedTriState {
UNTRACKED,
NOT_REACHED,
REACHED
}

interface ClassInitializerFunctionPointer extends CFunctionPointer {
@InvokeJavaFunctionPointer
void invoke();
Expand All @@ -106,6 +126,11 @@ interface ClassInitializerFunctionPointer extends CFunctionPointer {
*/
private final FunctionPointerHolder classInitializer;

/**
* Describes whether this class has been reached at runtime.
*/
private ReachedTriState reached;

/**
* The current initialization state.
*/
Expand Down Expand Up @@ -141,34 +166,37 @@ interface ClassInitializerFunctionPointer extends CFunctionPointer {
private boolean hasInitializer;
private boolean buildTimeInitialized;

private boolean reached;
private boolean slowPathRequired;

public boolean isReached() {
return reached;
return reached == ReachedTriState.REACHED;
}

@Platforms(Platform.HOSTED_ONLY.class)
private ClassInitializationInfo(InitState initState, boolean hasInitializer, boolean buildTimeInitialized) {
this(initState);
private ClassInitializationInfo(InitState initState, boolean hasInitializer, boolean buildTimeInitialized, boolean typeReachedTracked) {
this(initState, typeReachedTracked);
this.hasInitializer = hasInitializer;
this.buildTimeInitialized = buildTimeInitialized;
this.reached = false;
}

@Platforms(Platform.HOSTED_ONLY.class)
private ClassInitializationInfo(InitState initState) {
private ClassInitializationInfo(InitState initState, boolean typeReachedTracked) {
this.classInitializer = null;
this.initState = initState;
this.initLock = initState == InitState.FullyInitialized ? null : new ReentrantLock();
this.hasInitializer = true;
this.reached = typeReachedTracked ? ReachedTriState.NOT_REACHED : ReachedTriState.UNTRACKED;
this.slowPathRequired = reached != ReachedTriState.UNTRACKED || initState != InitState.FullyInitialized;
}

@Platforms(Platform.HOSTED_ONLY.class)
public ClassInitializationInfo(CFunctionPointer classInitializer) {
public ClassInitializationInfo(CFunctionPointer classInitializer, boolean typeReachedTracked) {
this.classInitializer = classInitializer == null || classInitializer.isNull() ? null : new FunctionPointerHolder(classInitializer);
this.initState = InitState.Linked;
this.initLock = new ReentrantLock();
this.hasInitializer = classInitializer != null;
this.reached = typeReachedTracked ? ReachedTriState.NOT_REACHED : ReachedTriState.UNTRACKED;
this.slowPathRequired = true;
}

public boolean hasInitializer() {
Expand Down Expand Up @@ -199,18 +227,41 @@ private boolean isReentrantInitialization(IsolateThread thread) {
return thread.equal(initThread);
}

private static void reach(DynamicHub hub) {
/**
* Marks the hierarchy of <code>hub</code> as reached. The concurrency primitives are not needed
* as the external readers are always on the separate thread for which the ordering is not
* relevant.
* </p>
* Note: as an optimization we can stop the traversal when a is already reached: The thread that
* wrote the flag has continued up the hierarchy. No need to use memory ordering, in the worst
* case the threads will mark parts of the hierarchy twice. This is guaranteed by the fact that
* the thread that writes always continues up the hierarchy. If the thread reached a reached
* type, that means there is at least one thread that climbed above the current point.
*/
private static void markReached(DynamicHub hub) {
var current = hub;
do {
current.getClassInitializationInfo().reached = true;
if (current.getClassInitializationInfo().reached == ReachedTriState.REACHED) {
break;
}

if (current.getClassInitializationInfo().reached != ReachedTriState.UNTRACKED) {
current.getClassInitializationInfo().reached = ReachedTriState.REACHED;
}
reachInterfaces(current);
current = current.getSuperHub();
} while (current != null);
}

private static void reachInterfaces(DynamicHub hub) {
for (DynamicHub superInterface : hub.getInterfaces()) {
superInterface.getClassInitializationInfo().reached = true;
if (superInterface.getClassInitializationInfo().reached == ReachedTriState.REACHED) {
return;
}

if (hub.getClassInitializationInfo().reached != ReachedTriState.UNTRACKED) {
superInterface.getClassInitializationInfo().reached = ReachedTriState.REACHED;
}
reachInterfaces(superInterface);
}
}
Expand All @@ -223,10 +274,15 @@ private static void reachInterfaces(DynamicHub hub) {
* https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.5
*/
@SubstrateForeignCallTarget(stubCallingConvention = true)
private static void initialize(ClassInitializationInfo info, DynamicHub hub) {
private static void slowPath(ClassInitializationInfo info, DynamicHub hub) {
IsolateThread self = CurrentIsolate.getCurrentThread();

reach(hub);
/*
* Types are marked as reached before any initialization is performed. Reason: the results
* should be visible in class initializers of the whole hierarchy as they could use
* reflection.
*/
markReached(hub);

if (info.isInitialized()) {
return;
Expand Down Expand Up @@ -431,6 +487,7 @@ private void setInitializationStateAndNotify(InitState state) {
initLock.lock();
try {
this.initState = state;
this.slowPathRequired = false;
this.initThread = WordFactory.nullPointer();
/* Make sure previous stores are all done, notably the initState. */
Unsafe.getUnsafe().storeFence();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public class EnsureClassInitializedNode extends WithExceptionNode implements Can
@Input(InputType.State) private FrameState stateAfter;

public static boolean intrinsify(GraphBuilderContext b, ValueNode hub) {
b.add(new EnsureClassInitializedNode(b.nullCheckedValue(hub)));
b.add(new EnsureClassInitializedNode(b.nullCheckedValue(hub), true));
return true;
}

Expand All @@ -70,8 +70,8 @@ public EnsureClassInitializedNode(ValueNode hub, FrameState stateAfter, boolean
this.stateAfter = stateAfter;
}

public EnsureClassInitializedNode(ValueNode hub) {
this(hub, null, false);
public EnsureClassInitializedNode(ValueNode hub, boolean requiredForTypeReached) {
this(hub, null, requiredForTypeReached);
}

public ValueNode getHub() {
Expand Down Expand Up @@ -120,6 +120,10 @@ public Node canonical(CanonicalizerTool tool) {
return this;
}

public boolean isRequiredForTypeReached() {
return requiredForTypeReached;
}

/**
* Return true if the type needs to be initialized at run time, i.e., it has not been already
* initialized during image generation or initialization is implied by the declaringClass.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,10 @@
import jdk.graal.compiler.replacements.Snippets;

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

public static final SubstrateForeignCallDescriptor[] FOREIGN_CALLS = new SubstrateForeignCallDescriptor[]{
INITIALIZE,
SLOW_PATH,
};

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

if (!BranchProbabilityNode.probability(BranchProbabilityNode.EXTREMELY_SLOW_PATH_PROBABILITY, infoNonNull.isInitialized() & infoNonNull.isReached())) {
callInitializationRoutine(INITIALIZE, infoNonNull, DynamicHub.toClass(hub));
if (BranchProbabilityNode.probability(BranchProbabilityNode.EXTREMELY_SLOW_PATH_PROBABILITY, infoNonNull.requiresSlowPath())) {
callSlowPath(SLOW_PATH, infoNonNull, DynamicHub.toClass(hub));
}
}

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

@SuppressWarnings("unused")
public static void registerLowerings(OptionValues options, Providers providers, Map<Class<? extends Node>, NodeLoweringProvider<?>> lowerings) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,16 @@
package com.oracle.svm.core.configure;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import org.graalvm.nativeimage.Platform;
import org.graalvm.nativeimage.Platforms;

import com.oracle.svm.core.util.VMError;

public final class ConditionalRuntimeValue<T> {
/*
* Intentionally an array to save space in the image heap.
Expand All @@ -40,8 +43,16 @@ public final class ConditionalRuntimeValue<T> {
private boolean satisfied;
volatile T value;

@Platforms(Platform.HOSTED_ONLY.class)
public ConditionalRuntimeValue(Set<Class<?>> conditions, T value) {
elements = conditions.toArray(Class[]::new);
if (!conditions.isEmpty()) {
elements = conditions.toArray(Class[]::new);
} else {
elements = null;
satisfied = true;
}

VMError.guarantee(conditions.stream().noneMatch(c -> c.equals(Object.class)), "java.lang.Object must not be in conditions as it is always true.");
this.value = value;
}

Expand All @@ -51,7 +62,11 @@ public T getValueUnconditionally() {
}

public Set<Class<?>> getConditions() {
return Arrays.stream(elements).collect(Collectors.toSet());
if (elements == null) {
return new HashSet<>();
} else {
return Arrays.stream(elements).collect(Collectors.toSet());
}
}

public T getValue(Predicate<Class<?>> conditionSatisfied) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,39 +80,48 @@ public void registerClass(ConfigurationCondition condition, Class<?> clazz) {
(currentValue == NEGATIVE_QUERY && ReflectionUtil.lookupClass(true, name) == null),
"Invalid Class.forName value for %s: %s", name, currentValue);

Class<?> conditionClass = condition.getType();
Set<Class<?>> resConditions;
if (conditionalRuntimeValue != null) {
Set<Class<?>> conditions = conditionalRuntimeValue.getConditions();
conditions.add(conditionClass);
resConditions = conditions;
} else {
resConditions = Set.of(conditionClass);
}

var cond = updateConditionalValue(conditionalRuntimeValue, clazz, condition);
if (currentValue == NEGATIVE_QUERY) {
knownClasses.put(name, new ConditionalRuntimeValue<>(resConditions, clazz));
knownClasses.put(name, cond);
} else if (currentValue == null) {
knownClasses.put(name, new ConditionalRuntimeValue<>(resConditions, clazz));
knownClasses.put(name, cond);
} else if (currentValue instanceof Class<?>) {
knownClasses.put(name, new ConditionalRuntimeValue<>(resConditions, clazz));
knownClasses.put(name, cond);
} else {
throw VMError.shouldNotReachHere("Testing");
throw VMError.shouldNotReachHere("Other cases must not happen.");
}
}

private static ConditionalRuntimeValue<Object> updateConditionalValue(ConditionalRuntimeValue<Object> existingConditionalValue, Object newConditionedValue,
ConfigurationCondition additionalCondition) {
Set<Class<?>> resConditions = Set.of();
if (!additionalCondition.isAlwaysTrue()) {
Class<?> conditionClass = additionalCondition.getType();
if (existingConditionalValue != null) {
Set<Class<?>> conditions = existingConditionalValue.getConditions();
conditions.add(conditionClass);
resConditions = Set.of(conditions.toArray(v -> new Class<?>[v]));
} else {
resConditions = Set.of(conditionClass);
}
}
return new ConditionalRuntimeValue<>(resConditions, newConditionedValue);
}

@Platforms(Platform.HOSTED_ONLY.class)
public void registerExceptionForClass(ConfigurationCondition cnd, String className, Throwable t) {
knownClasses.put(className, new ConditionalRuntimeValue<>(Set.of(cnd.getType()), t));
public void registerExceptionForClass(ConfigurationCondition condition, String className, Throwable t) {
Set<Class<?>> typeSet = condition.isAlwaysTrue() ? Set.of() : Set.of(condition.getType());
knownClasses.put(className, new ConditionalRuntimeValue<>(typeSet, t));
}

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

public Class<?> forNameOrNull(String className, ClassLoader classLoader) {
Expand Down
Loading

0 comments on commit 0d9d982

Please sign in to comment.