Skip to content

Commit

Permalink
[GR-51416] Registering a class as initialize-at-build-time should imm…
Browse files Browse the repository at this point in the history
…ediately trigger initialization.

PullRequest: graal/16626
  • Loading branch information
Christian Wimmer committed Jan 19, 2024
2 parents 616582e + 2d5aa31 commit c9675b1
Show file tree
Hide file tree
Showing 3 changed files with 173 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,20 @@ public static void initializeAtRunTime(Class<?>... classes) {
/**
* Registers the provided classes as eagerly initialized during image-build time.
* <p>
* All static initializers of {@code classes} will be executed during image-build time and
* static fields that are assigned values will be available at runtime. {@code static final}
* All static initializers of {@code classes} will be executed immediately at image-build time
* and static fields that are assigned values will be available at runtime. {@code static final}
* fields will be considered as constant.
* <p>
* It is up to the user to ensure that this behavior makes sense and does not lead to wrong
* application behavior.
* <p>
* After this method returns, all listed classes are initialized in the VM that runs the image
* generator. Therefore, this method can be used to resolve deadlocks and cycles in class
* initializer by starting initialization with a known-good entry class.
* <p>
* All superclasses and superinterfaces that are initialized before any of the listed classes
* are registered for initialization at build time too. Please look at the Java specification
* for the exact rules, especially regarding interfaces that declare default methods.
*
* @since 19.0
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,13 @@

import java.lang.reflect.Proxy;

import jdk.graal.compiler.java.LambdaUtils;

import com.oracle.graal.pointsto.meta.AnalysisMetaAccess;
import com.oracle.graal.pointsto.meta.AnalysisUniverse;
import com.oracle.svm.core.util.UserError;
import com.oracle.svm.core.util.VMError;
import com.oracle.svm.hosted.ImageClassLoader;

import jdk.graal.compiler.java.LambdaUtils;
import jdk.vm.ci.meta.MetaAccessProvider;

class AllowAllHostedUsagesClassInitializationSupport extends ClassInitializationSupport {
Expand All @@ -45,21 +44,7 @@ class AllowAllHostedUsagesClassInitializationSupport extends ClassInitialization
@Override
public void initializeAtBuildTime(Class<?> aClass, String reason) {
UserError.guarantee(!configurationSealed, "The class initialization configuration can be changed only before the phase analysis.");
Class<?> cur = aClass;
do {
classInitializationConfiguration.insert(cur.getTypeName(), InitKind.BUILD_TIME, cur == aClass ? reason : "super type of " + aClass.getTypeName(), true);
initializeInterfacesAtBuildTime(cur.getInterfaces(), "interface of " + aClass.getTypeName());
cur = cur.getSuperclass();
} while (cur != null);
}

private void initializeInterfacesAtBuildTime(Class<?>[] interfaces, String reason) {
for (Class<?> iface : interfaces) {
if (metaAccess.lookupJavaType(iface).declaresDefaultMethods()) {
classInitializationConfiguration.insert(iface.getTypeName(), InitKind.BUILD_TIME, reason, true);
}
initializeInterfacesAtBuildTime(iface.getInterfaces(), reason);
}
forceInitializeHosted(aClass, reason, false);
}

@Override
Expand Down Expand Up @@ -103,7 +88,13 @@ public void forceInitializeHosted(Class<?> clazz, String reason, boolean allowIn
classInitKinds.put(clazz, initKind);

forceInitializeHosted(clazz.getSuperclass(), "super type of " + clazz.getTypeName(), allowInitializationErrors);
forceInitializeInterfaces(clazz.getInterfaces(), "super type of " + clazz.getTypeName());
if (!clazz.isInterface()) {
/*
* Initialization of an interface does not trigger initialization of superinterfaces.
* Regardless whether any of the involved interfaces declare default methods.
*/
forceInitializeInterfaces(clazz.getInterfaces(), "super type of " + clazz.getTypeName());
}
}

private void forceInitializeInterfaces(Class<?>[] interfaces, String reason) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -663,6 +664,100 @@ static synchronized int synchronizedMethod() {
}
}

class InitializationOrder {
static final List<Class<?>> initializationOrder = Collections.synchronizedList(new ArrayList<>());
}

interface Test1_I1 {
default void defaultI1() {
}

int order = add();

static int add() {
InitializationOrder.initializationOrder.add(Test1_I1.class);
return 42;
}
}

interface Test1_I2 extends Test1_I1 {
int order = add();

static int add() {
InitializationOrder.initializationOrder.add(Test1_I2.class);
return 42;
}
}

interface Test1_I3 extends Test1_I2 {
default void defaultI3() {
}

int order = add();

static int add() {
InitializationOrder.initializationOrder.add(Test1_I3.class);
return 42;
}
}

interface Test1_I4 extends Test1_I3 {
int order = add();

static int add() {
InitializationOrder.initializationOrder.add(Test1_I4.class);
return 42;
}
}

class Test1_A implements Test1_I4 {
static {
InitializationOrder.initializationOrder.add(Test1_A.class);
}
}

interface Test2_I1 {
default void defaultI1() {
}

int order = add();

static int add() {
InitializationOrder.initializationOrder.add(Test2_I1.class);
return 42;
}
}

interface Test2_I2 extends Test2_I1 {
int order = add();

static int add() {
InitializationOrder.initializationOrder.add(Test2_I2.class);
return 42;
}
}

interface Test2_I3 extends Test2_I2 {
default void defaultI3() {
}

int order = add();

static int add() {
InitializationOrder.initializationOrder.add(Test2_I3.class);
return 42;
}
}

interface Test2_I4 extends Test2_I3 {
int order = add();

static int add() {
InitializationOrder.initializationOrder.add(Test2_I4.class);
return 42;
}
}

abstract class TestClassInitializationFeature implements Feature {

private void checkClasses(boolean checkSafeEarly, boolean checkSafeLate) {
Expand Down Expand Up @@ -695,6 +790,65 @@ abstract void checkClass(Class<?> checkedClass, boolean checkSafeEarly, boolean
public void afterRegistration(AfterRegistrationAccess access) {
/* We need to access the checkedClasses array both at image build time and run time. */
RuntimeClassInitialization.initializeAtBuildTime(TestClassInitialization.class);

/*
* Initialization of a class first triggers initialization of all superinterfaces that
* declared default methods.
*/
InitializationOrder.initializationOrder.clear();
assertNotInitialized(Test1_I1.class, Test1_I2.class, Test1_I3.class, Test1_I4.class, Test1_A.class);
RuntimeClassInitialization.initializeAtBuildTime(Test1_A.class);
assertNotInitialized(Test1_I2.class, Test1_I4.class);
assertInitialized(Test1_I1.class, Test1_I3.class, Test1_A.class);
assertArraysEqual(new Object[]{Test1_I1.class, Test1_I3.class, Test1_A.class}, InitializationOrder.initializationOrder.toArray());

/*
* The old class initialization policy is wrong regarding interfaces, but we do not want to
* change that now because it will be deleted soon.
*/
if (TestClassInitialization.simulationEnabled) {

/*
* Initialization of an interface does not trigger initialization of superinterfaces.
* Regardless whether any of the involved interfaces declare default methods.
*/
InitializationOrder.initializationOrder.clear();
assertNotInitialized(Test2_I1.class, Test2_I2.class, Test2_I3.class, Test2_I4.class);
RuntimeClassInitialization.initializeAtBuildTime(Test2_I4.class);
assertNotInitialized(Test2_I1.class, Test2_I2.class, Test2_I3.class);
assertInitialized(Test2_I4.class);
assertArraysEqual(new Object[]{Test2_I4.class}, InitializationOrder.initializationOrder.toArray());
RuntimeClassInitialization.initializeAtBuildTime(Test2_I3.class);
assertNotInitialized(Test2_I1.class, Test2_I2.class);
assertInitialized(Test2_I3.class, Test2_I4.class);
assertArraysEqual(new Object[]{Test2_I4.class, Test2_I3.class}, InitializationOrder.initializationOrder.toArray());
RuntimeClassInitialization.initializeAtBuildTime(Test2_I2.class);
assertNotInitialized(Test2_I1.class);
assertInitialized(Test2_I2.class, Test2_I3.class, Test2_I4.class);
assertArraysEqual(new Object[]{Test2_I4.class, Test2_I3.class, Test2_I2.class}, InitializationOrder.initializationOrder.toArray());
}
}

private void assertNotInitialized(Class<?>... classes) {
for (var clazz : classes) {
if (!Unsafe.getUnsafe().shouldBeInitialized(clazz)) {
throw new AssertionError("Already initialized: " + clazz);
}
}
}

private void assertInitialized(Class<?>... classes) {
for (var clazz : classes) {
if (Unsafe.getUnsafe().shouldBeInitialized(clazz)) {
throw new AssertionError("Not initialized: " + clazz);
}
}
}

private static void assertArraysEqual(Object[] expected, Object[] actual) {
if (!Arrays.equals(expected, actual)) {
throw new RuntimeException("expected " + Arrays.toString(expected) + " but found " + Arrays.toString(actual));
}
}

@Override
Expand Down

0 comments on commit c9675b1

Please sign in to comment.