Skip to content

Commit f8888aa

Browse files
committed
[GR-68906] Intercept up-/downcall requests for error reporting.
PullRequest: graal/22219
2 parents 3b06b2d + fd1bd0b commit f8888aa

File tree

6 files changed

+212
-27
lines changed

6 files changed

+212
-27
lines changed

substratevm/mx.substratevm/suite.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -793,6 +793,7 @@
793793
"jdk.internal.foreign.abi.aarch64",
794794
"jdk.internal.foreign.abi.aarch64.macos",
795795
"jdk.internal.foreign.abi.aarch64.linux",
796+
"jdk.internal.foreign.layout",
796797
"jdk.internal.loader",
797798
"jdk.internal.reflect",
798799
],

substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ForeignConfiguration.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public void printJson(JsonWriter writer) throws IOException {
5656
}
5757
}
5858

59-
private record StubDesc(UnresolvedAccessCondition condition, ConfigurationFunctionDescriptor desc, Map<String, Object> linkerOptions) implements JsonPrintable {
59+
public record StubDesc(UnresolvedAccessCondition condition, ConfigurationFunctionDescriptor desc, Map<String, Object> linkerOptions) implements JsonPrintable {
6060
@Override
6161
public void printJson(JsonWriter writer) throws IOException {
6262
writer.appendObjectStart();

substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/AbiUtils.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -48,7 +48,6 @@
4848
import org.graalvm.nativeimage.ImageSingletons;
4949
import org.graalvm.nativeimage.Isolate;
5050
import org.graalvm.nativeimage.Platform;
51-
import org.graalvm.nativeimage.Platform.HOSTED_ONLY;
5251
import org.graalvm.nativeimage.Platforms;
5352
import org.graalvm.word.Pointer;
5453

@@ -525,7 +524,6 @@ public static AbiUtils singleton() {
525524
* This method re-implements a part of the logic from the JDK so that we can get the callee-type
526525
* (i.e. the ABI low-level type) of a function from its descriptor.
527526
*/
528-
@Platforms(HOSTED_ONLY.class)
529527
@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+18/src/java.base/share/classes/jdk/internal/foreign/abi/AbstractLinker.java#L99")
530528
@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+18/src/java.base/share/classes/jdk/internal/foreign/abi/DowncallLinker.java#L71-L85")
531529
public final NativeEntryPointInfo makeNativeEntrypoint(FunctionDescriptor desc, LinkerOptions linkerOptions) {

substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/ForeignFunctionsRuntime.java

Lines changed: 87 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,19 @@
2626

2727
import static jdk.graal.compiler.core.common.spi.ForeignCallDescriptor.CallSideEffect.HAS_SIDE_EFFECT;
2828

29+
import java.io.IOException;
2930
import java.lang.constant.DirectMethodHandleDesc;
3031
import java.lang.foreign.FunctionDescriptor;
3132
import java.lang.foreign.MemorySegment;
3233
import java.lang.foreign.MemorySegment.Scope;
3334
import java.lang.invoke.MethodHandle;
3435
import java.lang.invoke.MethodType;
36+
import java.util.Deque;
3537
import java.util.HashMap;
3638
import java.util.Locale;
3739
import java.util.Map;
3840
import java.util.Set;
41+
import java.util.concurrent.ConcurrentLinkedDeque;
3942
import java.util.function.BiConsumer;
4043

4144
import org.graalvm.collections.EconomicMap;
@@ -71,6 +74,8 @@
7174
import com.oracle.svm.core.util.VMError;
7275

7376
import jdk.graal.compiler.api.replacements.Fold;
77+
import jdk.graal.compiler.util.json.JsonPrintable;
78+
import jdk.graal.compiler.util.json.JsonWriter;
7479
import jdk.graal.compiler.word.Word;
7580
import jdk.internal.foreign.CABI;
7681
import jdk.internal.foreign.MemorySessionImpl;
@@ -85,6 +90,7 @@ public static ForeignFunctionsRuntime singleton() {
8590
return ImageSingletons.lookup(ForeignFunctionsRuntime.class);
8691
}
8792

93+
private final AbiUtils abiUtils;
8894
private final AbiUtils.TrampolineTemplate trampolineTemplate;
8995

9096
private final EconomicMap<NativeEntryPointInfo, FunctionPointerHolder> downcallStubs = ImageHeapMap.create("downcallStubs");
@@ -93,6 +99,14 @@ public static ForeignFunctionsRuntime singleton() {
9399
private final EconomicSet<ResolvedJavaType> neverAccessesSharedArenaTypes = EconomicSet.create();
94100
private final EconomicSet<ResolvedJavaMethod> neverAccessesSharedArenaMethods = EconomicSet.create();
95101

102+
/**
103+
* A thread-safe stack of currently performed link requests (i.e. creating a downcall handle or
104+
* an upcall stub). This stack is used to generate a helpful error message if the link request
105+
* fails because of a missing stub. Since link requests may be created concurrently, we need to
106+
* use a thread-safe collection.
107+
*/
108+
private final Deque<LinkRequest> currentLinkRequests = new ConcurrentLinkedDeque<>();
109+
96110
private final Map<Long, TrampolineSet> trampolines = new HashMap<>();
97111
private TrampolineSet currentTrampolineSet;
98112

@@ -101,6 +115,7 @@ public static ForeignFunctionsRuntime singleton() {
101115

102116
@Platforms(Platform.HOSTED_ONLY.class)
103117
public ForeignFunctionsRuntime(AbiUtils abiUtils) {
118+
this.abiUtils = abiUtils;
104119
this.trampolineTemplate = new TrampolineTemplate(new byte[abiUtils.trampolineSize()]);
105120
}
106121

@@ -178,24 +193,18 @@ public void registerSafeArenaAccessorMethod(ResolvedJavaMethod method) {
178193
neverAccessesSharedArenaMethods.add(method);
179194
}
180195

181-
/**
182-
* We'd rather report the function descriptor than the native method type, but we don't have it
183-
* available here. One could intercept this exception in
184-
* {@link jdk.internal.foreign.abi.DowncallLinker#getBoundMethodHandle} and add information
185-
* about the descriptor there.
186-
*/
187196
CFunctionPointer getDowncallStubPointer(NativeEntryPointInfo nep) {
188197
FunctionPointerHolder holder = downcallStubs.get(nep);
189198
if (holder == null) {
190-
throw MissingForeignRegistrationUtils.reportDowncall(nep);
199+
throw reportMissingDowncall(nep);
191200
}
192201
return holder.functionPointer;
193202
}
194203

195204
CFunctionPointer getUpcallStubPointer(JavaEntryPointInfo jep) {
196205
FunctionPointerHolder holder = upcallStubs.get(jep);
197206
if (holder == null) {
198-
throw MissingForeignRegistrationUtils.reportUpcall(jep);
207+
throw reportMissingUpcall(jep);
199208
}
200209
return holder.functionPointer;
201210
}
@@ -270,34 +279,91 @@ void freeTrampoline(long addr) {
270279
}
271280
}
272281

273-
public static class MissingForeignRegistrationUtils extends MissingRegistrationUtils {
274-
public static MissingForeignRegistrationError reportDowncall(NativeEntryPointInfo nep) {
275-
MissingForeignRegistrationError mfre = new MissingForeignRegistrationError(foreignRegistrationMessage("downcall", nep.methodType()));
276-
report(mfre);
277-
return mfre;
278-
}
279-
280-
public static MissingForeignRegistrationError reportUpcall(JavaEntryPointInfo jep) {
281-
MissingForeignRegistrationError mfre = new MissingForeignRegistrationError(foreignRegistrationMessage("upcall", jep.cMethodType()));
282-
report(mfre);
283-
return mfre;
282+
/**
283+
* Looks for the corresponding {@link #currentLinkRequests link request} by creating a
284+
* {@link NativeEntryPointInfo} for each currently existing link request and comparing to the
285+
* given one. The matching link request then contains the {@link FunctionDescriptor} and
286+
* {@link LinkerOptions} that are required to produce a helpful error message for the user.
287+
*/
288+
private MissingForeignRegistrationError reportMissingDowncall(NativeEntryPointInfo nep) {
289+
LinkRequest currentLinkRequest = null;
290+
for (LinkRequest linkRequest : currentLinkRequests) {
291+
NativeEntryPointInfo nativeEntryPointInfo = abiUtils.makeNativeEntrypoint(linkRequest.functionDescriptor, linkRequest.linkerOptions);
292+
if (nep.equals(nativeEntryPointInfo)) {
293+
currentLinkRequest = linkRequest;
294+
break;
295+
}
284296
}
297+
throw MissingForeignRegistrationUtils.report(false, currentLinkRequest, nep.methodType());
298+
}
285299

286-
private static String foreignRegistrationMessage(String failedAction, MethodType methodType) {
287-
return registrationMessage("perform " + failedAction + " with leaf type", methodType.toString(), "", "", "foreign", "foreign");
300+
/**
301+
* Similar to {@link #reportMissingDowncall} but for upcalls.
302+
*/
303+
private MissingForeignRegistrationError reportMissingUpcall(JavaEntryPointInfo jep) {
304+
LinkRequest currentLinkRequest = null;
305+
for (LinkRequest linkRequest : currentLinkRequests) {
306+
JavaEntryPointInfo javaEntryPointInfo = abiUtils.makeJavaEntryPoint(linkRequest.functionDescriptor, linkRequest.linkerOptions);
307+
if (jep.equals(javaEntryPointInfo)) {
308+
currentLinkRequest = linkRequest;
309+
break;
310+
}
288311
}
312+
throw MissingForeignRegistrationUtils.report(true, currentLinkRequest, jep.handleType());
313+
}
289314

315+
public static class MissingForeignRegistrationUtils extends MissingRegistrationUtils {
290316
private static void report(MissingForeignRegistrationError exception) {
291317
StackTraceElement responsibleClass = getResponsibleClass(exception, foreignEntryPoints);
292318
MissingRegistrationUtils.report(exception, responsibleClass);
293319
}
294320

321+
private static MissingForeignRegistrationError report(boolean upcall, LinkRequest linkRequest, MethodType methodType) {
322+
String json = linkRequest != null ? elementToJSON(linkRequest) : "";
323+
String failedAction = upcall ? "upcall" : "downcall";
324+
String message = registrationMessage("perform " + failedAction + " with leaf type", methodType.toString(), json, "", "foreign", "foreign-function-and-memory-api");
325+
MissingForeignRegistrationError mfre = new MissingForeignRegistrationError(message);
326+
report(mfre);
327+
throw mfre;
328+
}
329+
295330
private static final Map<String, Set<String>> foreignEntryPoints = Map.of(
296331
"jdk.internal.foreign.abi.AbstractLinker", Set.of(
297332
"downcallHandle",
298333
"upcallStub"));
299334
}
300335

336+
record LinkRequest(boolean upcall, FunctionDescriptor functionDescriptor, LinkerOptions linkerOptions) implements AutoCloseable, JsonPrintable {
337+
338+
static LinkRequest create(boolean upcall, FunctionDescriptor functionDescriptor, LinkerOptions linkerOptions) {
339+
LinkRequest linkRequest = new LinkRequest(upcall, functionDescriptor, linkerOptions);
340+
ForeignFunctionsRuntime.singleton().currentLinkRequests.push(linkRequest);
341+
return linkRequest;
342+
}
343+
344+
@Override
345+
public boolean equals(Object obj) {
346+
return this == obj;
347+
}
348+
349+
@Override
350+
public int hashCode() {
351+
return System.identityHashCode(this);
352+
}
353+
354+
@Override
355+
public void close() {
356+
ForeignFunctionsRuntime.singleton().currentLinkRequests.remove(this);
357+
}
358+
359+
@Override
360+
public void printJson(JsonWriter writer) throws IOException {
361+
writer.printValue(upcall ? "upcalls" : "downcalls").appendFieldSeparator().appendArrayStart();
362+
SubstrateForeignUtil.linkRequestToJsonPrintable(this).printJson(writer);
363+
writer.appendArrayEnd();
364+
}
365+
}
366+
301367
/**
302368
* Arguments follow the same structure as described in {@link NativeEntryPointInfo}, with an
303369
* additional {@link Target_jdk_internal_foreign_abi_NativeEntryPoint} (NEP) as the last

substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/SubstrateForeignUtil.java

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,26 @@
2424
*/
2525
package com.oracle.svm.core.foreign;
2626

27+
import java.lang.foreign.GroupLayout;
28+
import java.lang.foreign.MemoryLayout;
29+
import java.lang.foreign.MemorySegment;
30+
import java.lang.foreign.PaddingLayout;
31+
import java.lang.foreign.SequenceLayout;
32+
import java.lang.foreign.StructLayout;
33+
import java.lang.foreign.ValueLayout;
34+
import java.util.HashMap;
35+
import java.util.LinkedList;
36+
import java.util.List;
37+
import java.util.Map;
38+
import java.util.Optional;
39+
40+
import com.oracle.svm.configure.UnresolvedAccessCondition;
41+
import com.oracle.svm.configure.config.ForeignConfiguration.ConfigurationFunctionDescriptor;
42+
import com.oracle.svm.configure.config.ForeignConfiguration.StubDesc;
2743
import com.oracle.svm.core.AlwaysInline;
2844
import com.oracle.svm.core.ArenaIntrinsics;
2945
import com.oracle.svm.core.SubstrateOptions;
46+
import com.oracle.svm.core.foreign.ForeignFunctionsRuntime.LinkRequest;
3047
import com.oracle.svm.core.nodes.foreign.ScopedMemExceptionHandlerClusterNode.ClusterBeginNode;
3148
import com.oracle.svm.core.nodes.foreign.ScopedMemExceptionHandlerClusterNode.ExceptionInputNode;
3249
import com.oracle.svm.core.nodes.foreign.ScopedMemExceptionHandlerClusterNode.ExceptionPathNode;
@@ -35,7 +52,10 @@
3552
import com.oracle.svm.util.LogUtils;
3653

3754
import jdk.graal.compiler.api.directives.GraalDirectives;
55+
import jdk.graal.compiler.util.json.JsonPrintable;
3856
import jdk.internal.foreign.MemorySessionImpl;
57+
import jdk.internal.foreign.abi.LinkerOptions;
58+
import jdk.internal.foreign.layout.AbstractLayout;
3959

4060
/**
4161
* For details on the implementation of shared arenas on substrate see
@@ -119,4 +139,67 @@ public static void sessionExceptionHandler(MemorySessionImpl session, Object bas
119139
// of control flow when searching for this pattern
120140
GraalDirectives.controlFlowAnchor(scope);
121141
}
142+
143+
/**
144+
* Generates a JSON configuration entry that will specify the stub required by the given link
145+
* request. This method is meant to be used to generate a useful error message for the user if a
146+
* stub lookup failed.
147+
*/
148+
static JsonPrintable linkRequestToJsonPrintable(LinkRequest linkRequest) {
149+
List<String> parameterTypes = new LinkedList<>();
150+
for (MemoryLayout argumentLayout : linkRequest.functionDescriptor().argumentLayouts()) {
151+
parameterTypes.add(memoryLayoutToString(argumentLayout));
152+
}
153+
Optional<MemoryLayout> memoryLayout = linkRequest.functionDescriptor().returnLayout();
154+
String returnLayout = memoryLayout.map(SubstrateForeignUtil::memoryLayoutToString).orElse("void");
155+
ConfigurationFunctionDescriptor desc = new ConfigurationFunctionDescriptor(returnLayout, parameterTypes);
156+
157+
Map<String, Object> jsonOptions = Map.of();
158+
if (!LinkerOptions.empty().equals(linkRequest.linkerOptions())) {
159+
jsonOptions = new HashMap<>();
160+
if (linkRequest.linkerOptions().isCritical()) {
161+
jsonOptions.put("critical", Map.of("allowHeapAccess", linkRequest.linkerOptions().allowsHeapAccess()));
162+
}
163+
if (linkRequest.linkerOptions().isVariadicFunction()) {
164+
jsonOptions.put("firstVariadicArg", linkRequest.linkerOptions().firstVariadicArgIndex());
165+
}
166+
if (linkRequest.linkerOptions().hasCapturedCallState()) {
167+
jsonOptions.put("captureCallState", true);
168+
}
169+
}
170+
171+
return new StubDesc(UnresolvedAccessCondition.unconditional(), desc, jsonOptions);
172+
}
173+
174+
/**
175+
* Generates a memory layout description as defined in {@code FFM-API.md} to be used in the
176+
* configuration file (usually {@code reachability-metadata.json}). This method implements the
177+
* reverse operation of {@code com.oracle.svm.hosted.foreign.MemoryLayoutParser}.
178+
*/
179+
private static String memoryLayoutToString(MemoryLayout memoryLayout) {
180+
String layoutString = switch (memoryLayout) {
181+
case ValueLayout valueLayout -> {
182+
Class<?> carrier = valueLayout.carrier();
183+
if (carrier == MemorySegment.class) {
184+
yield "void*";
185+
}
186+
yield "j" + carrier.getName();
187+
}
188+
case GroupLayout structLayout -> {
189+
String[] memberStrings = new String[structLayout.memberLayouts().size()];
190+
int i = 0;
191+
for (MemoryLayout memberLayout : structLayout.memberLayouts()) {
192+
memberStrings[i++] = memoryLayoutToString(memberLayout);
193+
}
194+
String prefix = structLayout instanceof StructLayout ? "struct(" : "union(";
195+
yield prefix + String.join(",", memberStrings) + ")";
196+
}
197+
case SequenceLayout sequenceLayout -> "sequence(" + sequenceLayout.elementCount() + ", " + memoryLayoutToString(sequenceLayout.elementLayout()) + ")";
198+
case PaddingLayout paddingLayout -> "padding(" + paddingLayout.byteSize() + ")";
199+
};
200+
if (memoryLayout instanceof AbstractLayout<?> abstractLayout && !abstractLayout.hasNaturalAlignment()) {
201+
return "align(" + memoryLayout.byteAlignment() + ", " + layoutString + ")";
202+
}
203+
return layoutString;
204+
}
122205
}

0 commit comments

Comments
 (0)