Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[GR-54786] Cross-isolate exception dispatch for isolated compilation. #10617

Merged
merged 2 commits into from
Feb 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
import java.lang.annotation.Target;
import java.util.function.Function;

import jdk.graal.compiler.word.Word;
import org.graalvm.nativeimage.c.function.CEntryPoint;
import org.graalvm.word.PointerBase;

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

import jdk.graal.compiler.word.Word;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CEntryPointOptions {
Expand Down Expand Up @@ -151,14 +152,30 @@ final class NoEpilogue implements Epilogue {
}

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

/** Marker interface for {@linkplain #callerEpilogue caller epilogue} classes. */
interface CallerEpilogue {
}

/** Placeholder class for {@link #callerEpilogue()} to omit an epilogue at call sites. */
final class NoCallerEpilogue implements CallerEpilogue {
}

/**
* Specifies a class with epilogue code that is executed by a Java <em>caller</em> of the entry
* point after the call has returned, in the caller's isolate. This code is injected only at
* sites of <em>direct Java calls</em> to the {@link CEntryPoint}-annotated method, but not
* where it is called by its address or symbol, for example from C code. The specified class
* must have exactly one static method with no parameters.
*/
Class<? extends CallerEpilogue> callerEpilogue() default NoCallerEpilogue.class;
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,11 @@

import java.lang.reflect.Array;

import jdk.graal.compiler.core.common.CompressEncoding;
import jdk.graal.compiler.word.Word;
import org.graalvm.nativeimage.StackValue;
import org.graalvm.nativeimage.c.function.CEntryPoint;

import com.oracle.svm.core.SubstrateOptions;
import com.oracle.svm.core.c.function.CEntryPointOptions;
import com.oracle.svm.core.graal.meta.SubstrateMemoryAccessProvider;
import com.oracle.svm.core.hub.DynamicHub;
import com.oracle.svm.core.meta.SubstrateObjectConstant;
Expand All @@ -40,6 +39,8 @@
import com.oracle.svm.graal.meta.SubstrateMemoryAccessProviderImpl;
import com.oracle.svm.graal.meta.SubstrateMetaAccess;

import jdk.graal.compiler.core.common.CompressEncoding;
import jdk.graal.compiler.word.Word;
import jdk.vm.ci.meta.Constant;
import jdk.vm.ci.meta.JavaConstant;
import jdk.vm.ci.meta.JavaKind;
Expand Down Expand Up @@ -84,7 +85,8 @@ private static JavaConstant read(JavaKind kind, Constant base, long displacement
return ConstantDataConverter.toCompiler(resultData);
}

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

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

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

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

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

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

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

@CEntryPoint(include = CEntryPoint.NotIncludedAutomatically.class, publishAs = CEntryPoint.Publish.NotPublished)
@CEntryPoint(exceptionHandler = IsolatedCompileClient.IntExceptionHandler.class, include = CEntryPoint.NotIncludedAutomatically.class, publishAs = CEntryPoint.Publish.NotPublished)
@CEntryPointOptions(callerEpilogue = IsolatedCompileClient.ExceptionRethrowCallerEpilogue.class)
private static int getImageHeapOffset0(@SuppressWarnings("unused") ClientIsolateThread client, ConstantData constantData) {
Constant constant = ConstantDataConverter.toClient(constantData);
return getImageHeapOffsetInternal((SubstrateObjectConstant) constant);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.graalvm.nativeimage.c.function.CEntryPoint;

import com.oracle.svm.core.SubstrateOptions;
import com.oracle.svm.core.c.function.CEntryPointOptions;
import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature;
import com.oracle.svm.core.feature.InternalFeature;
import com.oracle.svm.core.graal.RuntimeCompilation;
Expand Down Expand Up @@ -65,12 +66,14 @@ private static boolean compareIsolatedConstant(IsolatedObjectConstant a, Constan
throw VMError.shouldNotReachHere("Unknown object constant: " + b);
}

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

@CEntryPoint(include = CEntryPoint.NotIncludedAutomatically.class, publishAs = CEntryPoint.Publish.NotPublished)
@CEntryPoint(exceptionHandler = IsolatedCompileClient.BooleanExceptionHandler.class, include = CEntryPoint.NotIncludedAutomatically.class, publishAs = CEntryPoint.Publish.NotPublished)
@CEntryPointOptions(callerEpilogue = IsolatedCompileClient.ExceptionRethrowCallerEpilogue.class)
private static boolean isolatedHandleTargetEqualImageObject(@SuppressWarnings("unused") ClientIsolateThread client, ClientHandle<?> x, ImageHeapRef<?> y) {
return IsolatedCompileClient.get().unhand(x) == ImageHeapObjects.deref(y);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/*
* Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.oracle.svm.graal.isolated;

import java.io.ByteArrayOutputStream;
import java.io.PrintWriter;

import org.graalvm.nativeimage.IsolateThread;
import org.graalvm.nativeimage.c.function.CEntryPoint;
import org.graalvm.nativeimage.c.type.CCharPointer;
import org.graalvm.nativeimage.c.type.CTypeConversion;

import com.oracle.svm.core.NeverInline;
import com.oracle.svm.core.Uninterruptible;
import com.oracle.svm.core.c.function.CEntryPointOptions;
import com.oracle.svm.core.threadlocal.FastThreadLocalFactory;
import com.oracle.svm.core.threadlocal.FastThreadLocalObject;
import com.oracle.svm.hosted.code.CEntryPointCallStubMethod;
import com.oracle.svm.hosted.code.CEntryPointJavaCallStubMethod;

import jdk.graal.compiler.core.common.GraalBailoutException;

/**
* Mechanism for dispatching exceptions across isolates with isolated compilation.
* <p>
* It relies on entry points having a {@link CEntryPoint#exceptionHandler} that invokes
* {@link #handleException}, which then calls into the other isolate (the entry point's caller
* isolate) to record that an exception has occurred. The entry point must further have a
* {@link CEntryPointOptions#callerEpilogue()} which executes in the caller isolate after the
* (original) call has returned and calls {@link #throwPendingException} to check whether an
* exception has been recorded, and if so, throw it. When no exception occurs, this comes at almost
* no extra cost.
* <p>
* Because objects cannot transcend isolate boundaries, exceptions are "rethrown" using a generic
* exception type with most information preserved in string form in their message.
*/
public abstract class IsolatedCompilationExceptionDispatch {
private static final RuntimeException EXCEPTION_WITHOUT_MESSAGE = new GraalBailoutException("[no details because exception allocation failed]");

/**
* An exception to be thrown in the current isolate as a result of it calling another isolate
* during which an exception has been caught.
*/
private static final FastThreadLocalObject<RuntimeException> pendingException = FastThreadLocalFactory.createObject(RuntimeException.class,
"IsolatedCompilationExceptionDispatch.pendingException");

protected static void throwPendingException() {
RuntimeException pending = pendingException.get();
if (pending != null) {
pendingException.set(null);
throw pending;
}
}

/** Provides the isolate to which an exception in the current isolate should be dispatched. */
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
protected abstract IsolateThread getOtherIsolate();

/**
* Dispatches an exception that was caught in an entry point to the isolate which called that
* entry point.
* <p>
* Note that the caller isolate cannot have called from uninterruptible code because
* {@link CEntryPointJavaCallStubMethod} does thread state transitions that require a safepoint
* check, so this method calling it back to dispatch the exception in interruptible code is
* considered acceptable.
* <p>
* Our (callee) entry point might intend to execute only uninterruptible code save for this
* exception handler, but as of writing this, isolated compilation nowhere requires relying on
* that and {@link CEntryPointCallStubMethod} also does state transitions and safepoint checks.
* <p>
* Also note that an exception's stack trace contains all its isolate's frames up until the last
* entry frame, but not another isolate's frames in between. When an exception is propagated
* through several entry points, this can make the output look confusing at first, but it is not
* too difficult to make sense of it.
*/
@Uninterruptible(reason = "Called in exception handler.", calleeMustBe = false)
protected final int handleException(Throwable t) {
boolean done;
try {
done = dispatchExceptionToOtherIsolate(t);
} catch (Throwable another) {
done = false;
}
if (!done) {
// Being uninterruptible, this should never fail:
dispatchExceptionWithoutMessage(getOtherIsolate());
}
return 0;
}

@NeverInline("Ensure that an exception thrown from this method can always be caught.")
private boolean dispatchExceptionToOtherIsolate(Throwable t) {
ByteArrayOutputStream os = new ByteArrayOutputStream();
try (PrintWriter pw = new PrintWriter(os)) {
pw.print("{ ");
t.printStackTrace(pw); // trailing newline
pw.print("}");
}
try (CTypeConversion.CCharPointerHolder cstr = CTypeConversion.toCString(os.toString())) {
return dispatchException(getOtherIsolate(), cstr.get());
}
}

@CEntryPoint(exceptionHandler = ReturnFalseExceptionHandler.class, include = CEntryPoint.NotIncludedAutomatically.class, publishAs = CEntryPoint.Publish.NotPublished)
private static boolean dispatchException(@SuppressWarnings("unused") IsolateThread other, CCharPointer cstr) {
String message = CTypeConversion.toJavaString(cstr);
GraalBailoutException exception = new GraalBailoutException(message);
pendingException.set(exception);
return true;
}

private static final class ReturnFalseExceptionHandler implements CEntryPoint.ExceptionHandler {
@Uninterruptible(reason = "Exception handler")
@SuppressWarnings("unused")
static boolean handle(Throwable t) {
return false;
}
}

@CEntryPoint(include = CEntryPoint.NotIncludedAutomatically.class, publishAs = CEntryPoint.Publish.NotPublished)
@Uninterruptible(reason = "Called from exception handler, should not raise an exception.")
private static void dispatchExceptionWithoutMessage(@SuppressWarnings("unused") IsolateThread other) {
pendingException.set(EXCEPTION_WITHOUT_MESSAGE);
}
}
Loading