Skip to content

Commit 7077ad8

Browse files
committed
[GR-54786] Cross-isolate exception dispatch for isolated compilation.
PullRequest: graal/19884
2 parents 5d45f3d + 5d213ef commit 7077ad8

21 files changed

+507
-119
lines changed

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/function/CEntryPointOptions.java

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
import java.lang.annotation.Target;
3131
import java.util.function.Function;
3232

33-
import jdk.graal.compiler.word.Word;
3433
import org.graalvm.nativeimage.c.function.CEntryPoint;
3534
import org.graalvm.word.PointerBase;
3635

@@ -39,6 +38,8 @@
3938
import com.oracle.svm.core.c.function.CEntryPointSetup.EnterPrologue;
4039
import com.oracle.svm.core.c.function.CEntryPointSetup.LeaveEpilogue;
4140

41+
import jdk.graal.compiler.word.Word;
42+
4243
@Retention(RetentionPolicy.RUNTIME)
4344
@Target(ElementType.METHOD)
4445
public @interface CEntryPointOptions {
@@ -151,14 +152,30 @@ final class NoEpilogue implements Epilogue {
151152
}
152153

153154
/**
154-
* Specifies a class with epilogue code that is executed when the entry point method returns to
155-
* C in order to leave the execution context. See {@link CEntryPointSetup} for commonly used
156-
* epilogues.
155+
* Specifies a class with epilogue code that is executed just before the entry point method
156+
* returns to C in order to leave the execution context. See {@link CEntryPointSetup} for
157+
* commonly used epilogues.
157158
* <p>
158159
* The given class must have exactly one static {@link Uninterruptible} method with no
159160
* parameters. Within the epilogue method, {@link CEntryPointActions} can be used to leave the
160161
* execution context.
161162
*/
162163
Class<? extends Epilogue> epilogue() default LeaveEpilogue.class;
163164

165+
/** Marker interface for {@linkplain #callerEpilogue caller epilogue} classes. */
166+
interface CallerEpilogue {
167+
}
168+
169+
/** Placeholder class for {@link #callerEpilogue()} to omit an epilogue at call sites. */
170+
final class NoCallerEpilogue implements CallerEpilogue {
171+
}
172+
173+
/**
174+
* Specifies a class with epilogue code that is executed by a Java <em>caller</em> of the entry
175+
* point after the call has returned, in the caller's isolate. This code is injected only at
176+
* sites of <em>direct Java calls</em> to the {@link CEntryPoint}-annotated method, but not
177+
* where it is called by its address or symbol, for example from C code. The specified class
178+
* must have exactly one static method with no parameters.
179+
*/
180+
Class<? extends CallerEpilogue> callerEpilogue() default NoCallerEpilogue.class;
164181
}

substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/isolated/IsolateAwareConstantReflectionProvider.java

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,11 @@
2626

2727
import java.lang.reflect.Array;
2828

29-
import jdk.graal.compiler.core.common.CompressEncoding;
30-
import jdk.graal.compiler.word.Word;
3129
import org.graalvm.nativeimage.StackValue;
3230
import org.graalvm.nativeimage.c.function.CEntryPoint;
3331

3432
import com.oracle.svm.core.SubstrateOptions;
33+
import com.oracle.svm.core.c.function.CEntryPointOptions;
3534
import com.oracle.svm.core.graal.meta.SubstrateMemoryAccessProvider;
3635
import com.oracle.svm.core.hub.DynamicHub;
3736
import com.oracle.svm.core.meta.SubstrateObjectConstant;
@@ -40,6 +39,8 @@
4039
import com.oracle.svm.graal.meta.SubstrateMemoryAccessProviderImpl;
4140
import com.oracle.svm.graal.meta.SubstrateMetaAccess;
4241

42+
import jdk.graal.compiler.core.common.CompressEncoding;
43+
import jdk.graal.compiler.word.Word;
4344
import jdk.vm.ci.meta.Constant;
4445
import jdk.vm.ci.meta.JavaConstant;
4546
import jdk.vm.ci.meta.JavaKind;
@@ -84,7 +85,8 @@ private static JavaConstant read(JavaKind kind, Constant base, long displacement
8485
return ConstantDataConverter.toCompiler(resultData);
8586
}
8687

87-
@CEntryPoint(include = CEntryPoint.NotIncludedAutomatically.class, publishAs = CEntryPoint.Publish.NotPublished)
88+
@CEntryPoint(exceptionHandler = IsolatedCompileClient.VoidExceptionHandler.class, include = CEntryPoint.NotIncludedAutomatically.class, publishAs = CEntryPoint.Publish.NotPublished)
89+
@CEntryPointOptions(callerEpilogue = IsolatedCompileClient.ExceptionRethrowCallerEpilogue.class)
8890
private static void read0(@SuppressWarnings("unused") ClientIsolateThread client, char kindChar, ConstantData baseData, long displacement,
8991
int primitiveBits, long compressBase, int compressShift, ConstantData resultData) {
9092
JavaConstant base = ConstantDataConverter.toClient(baseData);
@@ -129,7 +131,8 @@ public Integer readArrayLength(JavaConstant array) {
129131
return Array.getLength(arrayObj);
130132
}
131133

132-
@CEntryPoint(include = CEntryPoint.NotIncludedAutomatically.class, publishAs = CEntryPoint.Publish.NotPublished)
134+
@CEntryPoint(exceptionHandler = IsolatedCompileClient.IntExceptionHandler.class, include = CEntryPoint.NotIncludedAutomatically.class, publishAs = CEntryPoint.Publish.NotPublished)
135+
@CEntryPointOptions(callerEpilogue = IsolatedCompileClient.ExceptionRethrowCallerEpilogue.class)
133136
private static int readArrayLength0(@SuppressWarnings("unused") ClientIsolateThread client, ClientHandle<?> arrayHandle) {
134137
Object array = IsolatedCompileClient.get().unhand(arrayHandle);
135138
if (!array.getClass().isArray()) {
@@ -153,7 +156,8 @@ public JavaConstant readArrayElement(JavaConstant array, int index) {
153156
return ConstantDataConverter.toCompiler(resultData);
154157
}
155158

156-
@CEntryPoint(include = CEntryPoint.NotIncludedAutomatically.class, publishAs = CEntryPoint.Publish.NotPublished)
159+
@CEntryPoint(exceptionHandler = IsolatedCompileClient.VoidExceptionHandler.class, include = CEntryPoint.NotIncludedAutomatically.class, publishAs = CEntryPoint.Publish.NotPublished)
160+
@CEntryPointOptions(callerEpilogue = IsolatedCompileClient.ExceptionRethrowCallerEpilogue.class)
157161
private static void readArrayElement0(@SuppressWarnings("unused") ClientIsolateThread client, ConstantData arrayData, int index, ConstantData resultData) {
158162
JavaConstant array = ConstantDataConverter.toClient(arrayData);
159163
Object a = SubstrateObjectConstant.asObject(array);
@@ -181,14 +185,16 @@ public JavaConstant readFieldValue(ResolvedJavaField field, JavaConstant receive
181185
return ConstantDataConverter.toCompiler(resultData);
182186
}
183187

184-
@CEntryPoint(include = CEntryPoint.NotIncludedAutomatically.class, publishAs = CEntryPoint.Publish.NotPublished)
188+
@CEntryPoint(exceptionHandler = IsolatedCompileClient.VoidExceptionHandler.class, include = CEntryPoint.NotIncludedAutomatically.class, publishAs = CEntryPoint.Publish.NotPublished)
189+
@CEntryPointOptions(callerEpilogue = IsolatedCompileClient.ExceptionRethrowCallerEpilogue.class)
185190
private static void readFieldValue0(@SuppressWarnings("unused") ClientIsolateThread client, ImageHeapRef<SubstrateField> fieldRef, ConstantData receiverData, ConstantData resultData) {
186191
JavaConstant receiver = ConstantDataConverter.toClient(receiverData);
187192
Constant result = readFieldValue(ImageHeapObjects.deref(fieldRef), receiver);
188193
ConstantDataConverter.fromClient(result, resultData);
189194
}
190195

191-
@CEntryPoint(include = CEntryPoint.NotIncludedAutomatically.class, publishAs = CEntryPoint.Publish.NotPublished)
196+
@CEntryPoint(exceptionHandler = IsolatedCompileClient.VoidExceptionHandler.class, include = CEntryPoint.NotIncludedAutomatically.class, publishAs = CEntryPoint.Publish.NotPublished)
197+
@CEntryPointOptions(callerEpilogue = IsolatedCompileClient.ExceptionRethrowCallerEpilogue.class)
192198
private static void boxPrimitive0(@SuppressWarnings("unused") ClientIsolateThread client, ConstantData primitiveData, ConstantData resultData) {
193199
JavaConstant primitive = ConstantDataConverter.toClient(primitiveData);
194200
Constant result = SubstrateObjectConstant.forObject(primitive.asBoxedPrimitive());
@@ -208,7 +214,8 @@ public JavaConstant unboxPrimitive(JavaConstant boxed) {
208214
return super.unboxPrimitive(boxed);
209215
}
210216

211-
@CEntryPoint(include = CEntryPoint.NotIncludedAutomatically.class, publishAs = CEntryPoint.Publish.NotPublished)
217+
@CEntryPoint(exceptionHandler = IsolatedCompileClient.VoidExceptionHandler.class, include = CEntryPoint.NotIncludedAutomatically.class, publishAs = CEntryPoint.Publish.NotPublished)
218+
@CEntryPointOptions(callerEpilogue = IsolatedCompileClient.ExceptionRethrowCallerEpilogue.class)
212219
private static void unboxPrimitive0(@SuppressWarnings("unused") ClientIsolateThread client, ConstantData boxedData, ConstantData resultData) {
213220
Constant boxed = ConstantDataConverter.toClient(boxedData);
214221
Constant result = JavaConstant.forBoxedPrimitive(SubstrateObjectConstant.asObject(boxed));
@@ -232,7 +239,8 @@ public ResolvedJavaType asJavaType(Constant hub) {
232239
return super.asJavaType(resolved);
233240
}
234241

235-
@CEntryPoint(include = CEntryPoint.NotIncludedAutomatically.class, publishAs = CEntryPoint.Publish.NotPublished)
242+
@CEntryPoint(exceptionHandler = IsolatedCompileClient.WordExceptionHandler.class, include = CEntryPoint.NotIncludedAutomatically.class, publishAs = CEntryPoint.Publish.NotPublished)
243+
@CEntryPointOptions(callerEpilogue = IsolatedCompileClient.ExceptionRethrowCallerEpilogue.class)
236244
private static ImageHeapRef<DynamicHub> getHubConstantAsImageHeapRef(@SuppressWarnings("unused") ClientIsolateThread client, ConstantData hubData) {
237245
JavaConstant hub = ConstantDataConverter.toClient(hubData);
238246
Object target = SubstrateObjectConstant.asObject(hub);
@@ -260,7 +268,8 @@ public int getImageHeapOffset(JavaConstant constant) {
260268
return super.getImageHeapOffset(constant);
261269
}
262270

263-
@CEntryPoint(include = CEntryPoint.NotIncludedAutomatically.class, publishAs = CEntryPoint.Publish.NotPublished)
271+
@CEntryPoint(exceptionHandler = IsolatedCompileClient.IntExceptionHandler.class, include = CEntryPoint.NotIncludedAutomatically.class, publishAs = CEntryPoint.Publish.NotPublished)
272+
@CEntryPointOptions(callerEpilogue = IsolatedCompileClient.ExceptionRethrowCallerEpilogue.class)
264273
private static int getImageHeapOffset0(@SuppressWarnings("unused") ClientIsolateThread client, ConstantData constantData) {
265274
Constant constant = ConstantDataConverter.toClient(constantData);
266275
return getImageHeapOffsetInternal((SubstrateObjectConstant) constant);

substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/isolated/IsolateAwareObjectConstantEqualityFeature.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.graalvm.nativeimage.c.function.CEntryPoint;
2929

3030
import com.oracle.svm.core.SubstrateOptions;
31+
import com.oracle.svm.core.c.function.CEntryPointOptions;
3132
import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature;
3233
import com.oracle.svm.core.feature.InternalFeature;
3334
import com.oracle.svm.core.graal.RuntimeCompilation;
@@ -65,12 +66,14 @@ private static boolean compareIsolatedConstant(IsolatedObjectConstant a, Constan
6566
throw VMError.shouldNotReachHere("Unknown object constant: " + b);
6667
}
6768

68-
@CEntryPoint(include = CEntryPoint.NotIncludedAutomatically.class, publishAs = CEntryPoint.Publish.NotPublished)
69+
@CEntryPoint(exceptionHandler = IsolatedCompileClient.BooleanExceptionHandler.class, include = CEntryPoint.NotIncludedAutomatically.class, publishAs = CEntryPoint.Publish.NotPublished)
70+
@CEntryPointOptions(callerEpilogue = IsolatedCompileClient.ExceptionRethrowCallerEpilogue.class)
6971
static boolean isolatedConstantHandleTargetsEqual(@SuppressWarnings("unused") ClientIsolateThread client, ClientHandle<?> x, ClientHandle<?> y) {
7072
return IsolatedCompileClient.get().unhand(x) == IsolatedCompileClient.get().unhand(y);
7173
}
7274

73-
@CEntryPoint(include = CEntryPoint.NotIncludedAutomatically.class, publishAs = CEntryPoint.Publish.NotPublished)
75+
@CEntryPoint(exceptionHandler = IsolatedCompileClient.BooleanExceptionHandler.class, include = CEntryPoint.NotIncludedAutomatically.class, publishAs = CEntryPoint.Publish.NotPublished)
76+
@CEntryPointOptions(callerEpilogue = IsolatedCompileClient.ExceptionRethrowCallerEpilogue.class)
7477
private static boolean isolatedHandleTargetEqualImageObject(@SuppressWarnings("unused") ClientIsolateThread client, ClientHandle<?> x, ImageHeapRef<?> y) {
7578
return IsolatedCompileClient.get().unhand(x) == ImageHeapObjects.deref(y);
7679
}
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/*
2+
* Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
package com.oracle.svm.graal.isolated;
26+
27+
import java.io.ByteArrayOutputStream;
28+
import java.io.PrintWriter;
29+
30+
import org.graalvm.nativeimage.IsolateThread;
31+
import org.graalvm.nativeimage.c.function.CEntryPoint;
32+
import org.graalvm.nativeimage.c.type.CCharPointer;
33+
import org.graalvm.nativeimage.c.type.CTypeConversion;
34+
35+
import com.oracle.svm.core.NeverInline;
36+
import com.oracle.svm.core.Uninterruptible;
37+
import com.oracle.svm.core.c.function.CEntryPointOptions;
38+
import com.oracle.svm.core.threadlocal.FastThreadLocalFactory;
39+
import com.oracle.svm.core.threadlocal.FastThreadLocalObject;
40+
import com.oracle.svm.hosted.code.CEntryPointCallStubMethod;
41+
import com.oracle.svm.hosted.code.CEntryPointJavaCallStubMethod;
42+
43+
import jdk.graal.compiler.core.common.GraalBailoutException;
44+
45+
/**
46+
* Mechanism for dispatching exceptions across isolates with isolated compilation.
47+
* <p>
48+
* It relies on entry points having a {@link CEntryPoint#exceptionHandler} that invokes
49+
* {@link #handleException}, which then calls into the other isolate (the entry point's caller
50+
* isolate) to record that an exception has occurred. The entry point must further have a
51+
* {@link CEntryPointOptions#callerEpilogue()} which executes in the caller isolate after the
52+
* (original) call has returned and calls {@link #throwPendingException} to check whether an
53+
* exception has been recorded, and if so, throw it. When no exception occurs, this comes at almost
54+
* no extra cost.
55+
* <p>
56+
* Because objects cannot transcend isolate boundaries, exceptions are "rethrown" using a generic
57+
* exception type with most information preserved in string form in their message.
58+
*/
59+
public abstract class IsolatedCompilationExceptionDispatch {
60+
private static final RuntimeException EXCEPTION_WITHOUT_MESSAGE = new GraalBailoutException("[no details because exception allocation failed]");
61+
62+
/**
63+
* An exception to be thrown in the current isolate as a result of it calling another isolate
64+
* during which an exception has been caught.
65+
*/
66+
private static final FastThreadLocalObject<RuntimeException> pendingException = FastThreadLocalFactory.createObject(RuntimeException.class,
67+
"IsolatedCompilationExceptionDispatch.pendingException");
68+
69+
protected static void throwPendingException() {
70+
RuntimeException pending = pendingException.get();
71+
if (pending != null) {
72+
pendingException.set(null);
73+
throw pending;
74+
}
75+
}
76+
77+
/** Provides the isolate to which an exception in the current isolate should be dispatched. */
78+
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
79+
protected abstract IsolateThread getOtherIsolate();
80+
81+
/**
82+
* Dispatches an exception that was caught in an entry point to the isolate which called that
83+
* entry point.
84+
* <p>
85+
* Note that the caller isolate cannot have called from uninterruptible code because
86+
* {@link CEntryPointJavaCallStubMethod} does thread state transitions that require a safepoint
87+
* check, so this method calling it back to dispatch the exception in interruptible code is
88+
* considered acceptable.
89+
* <p>
90+
* Our (callee) entry point might intend to execute only uninterruptible code save for this
91+
* exception handler, but as of writing this, isolated compilation nowhere requires relying on
92+
* that and {@link CEntryPointCallStubMethod} also does state transitions and safepoint checks.
93+
* <p>
94+
* Also note that an exception's stack trace contains all its isolate's frames up until the last
95+
* entry frame, but not another isolate's frames in between. When an exception is propagated
96+
* through several entry points, this can make the output look confusing at first, but it is not
97+
* too difficult to make sense of it.
98+
*/
99+
@Uninterruptible(reason = "Called in exception handler.", calleeMustBe = false)
100+
protected final int handleException(Throwable t) {
101+
boolean done;
102+
try {
103+
done = dispatchExceptionToOtherIsolate(t);
104+
} catch (Throwable another) {
105+
done = false;
106+
}
107+
if (!done) {
108+
// Being uninterruptible, this should never fail:
109+
dispatchExceptionWithoutMessage(getOtherIsolate());
110+
}
111+
return 0;
112+
}
113+
114+
@NeverInline("Ensure that an exception thrown from this method can always be caught.")
115+
private boolean dispatchExceptionToOtherIsolate(Throwable t) {
116+
ByteArrayOutputStream os = new ByteArrayOutputStream();
117+
try (PrintWriter pw = new PrintWriter(os)) {
118+
pw.print("{ ");
119+
t.printStackTrace(pw); // trailing newline
120+
pw.print("}");
121+
}
122+
try (CTypeConversion.CCharPointerHolder cstr = CTypeConversion.toCString(os.toString())) {
123+
return dispatchException(getOtherIsolate(), cstr.get());
124+
}
125+
}
126+
127+
@CEntryPoint(exceptionHandler = ReturnFalseExceptionHandler.class, include = CEntryPoint.NotIncludedAutomatically.class, publishAs = CEntryPoint.Publish.NotPublished)
128+
private static boolean dispatchException(@SuppressWarnings("unused") IsolateThread other, CCharPointer cstr) {
129+
String message = CTypeConversion.toJavaString(cstr);
130+
GraalBailoutException exception = new GraalBailoutException(message);
131+
pendingException.set(exception);
132+
return true;
133+
}
134+
135+
private static final class ReturnFalseExceptionHandler implements CEntryPoint.ExceptionHandler {
136+
@Uninterruptible(reason = "Exception handler")
137+
@SuppressWarnings("unused")
138+
static boolean handle(Throwable t) {
139+
return false;
140+
}
141+
}
142+
143+
@CEntryPoint(include = CEntryPoint.NotIncludedAutomatically.class, publishAs = CEntryPoint.Publish.NotPublished)
144+
@Uninterruptible(reason = "Called from exception handler, should not raise an exception.")
145+
private static void dispatchExceptionWithoutMessage(@SuppressWarnings("unused") IsolateThread other) {
146+
pendingException.set(EXCEPTION_WITHOUT_MESSAGE);
147+
}
148+
}

0 commit comments

Comments
 (0)