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
6161public 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 ;
0 commit comments