Skip to content
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
1 change: 1 addition & 0 deletions substratevm/mx.substratevm/suite.py
Original file line number Diff line number Diff line change
Expand Up @@ -788,6 +788,7 @@
"jdk.internal.foreign.abi.aarch64",
"jdk.internal.foreign.abi.aarch64.macos",
"jdk.internal.foreign.abi.aarch64.linux",
"jdk.internal.foreign.layout",
"jdk.internal.loader",
"jdk.internal.reflect",
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public void printJson(JsonWriter writer) throws IOException {
}
}

private record StubDesc(UnresolvedAccessCondition condition, ConfigurationFunctionDescriptor desc, Map<String, Object> linkerOptions) implements JsonPrintable {
public record StubDesc(UnresolvedAccessCondition condition, ConfigurationFunctionDescriptor desc, Map<String, Object> linkerOptions) implements JsonPrintable {
@Override
public void printJson(JsonWriter writer) throws IOException {
writer.appendObjectStart();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, 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
Expand Down Expand Up @@ -48,7 +48,6 @@
import org.graalvm.nativeimage.ImageSingletons;
import org.graalvm.nativeimage.Isolate;
import org.graalvm.nativeimage.Platform;
import org.graalvm.nativeimage.Platform.HOSTED_ONLY;
import org.graalvm.nativeimage.Platforms;
import org.graalvm.word.Pointer;

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

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

import java.io.IOException;
import java.lang.constant.DirectMethodHandleDesc;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.MemorySegment.Scope;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import java.util.Deque;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.function.BiConsumer;

import org.graalvm.collections.EconomicMap;
Expand Down Expand Up @@ -71,6 +74,8 @@
import com.oracle.svm.core.util.VMError;

import jdk.graal.compiler.api.replacements.Fold;
import jdk.graal.compiler.util.json.JsonPrintable;
import jdk.graal.compiler.util.json.JsonWriter;
import jdk.graal.compiler.word.Word;
import jdk.internal.foreign.CABI;
import jdk.internal.foreign.MemorySessionImpl;
Expand All @@ -85,6 +90,7 @@ public static ForeignFunctionsRuntime singleton() {
return ImageSingletons.lookup(ForeignFunctionsRuntime.class);
}

private final AbiUtils abiUtils;
private final AbiUtils.TrampolineTemplate trampolineTemplate;

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

/**
* A thread-safe stack of currently performed link requests (i.e. creating a downcall handle or
* an upcall stub). This stack is used to generate a helpful error message if the link request
* fails because of a missing stub. Since link requests may be created concurrently, we need to
* use a thread-safe collection.
*/
private final Deque<LinkRequest> currentLinkRequests = new ConcurrentLinkedDeque<>();

private final Map<Long, TrampolineSet> trampolines = new HashMap<>();
private TrampolineSet currentTrampolineSet;

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

@Platforms(Platform.HOSTED_ONLY.class)
public ForeignFunctionsRuntime(AbiUtils abiUtils) {
this.abiUtils = abiUtils;
this.trampolineTemplate = new TrampolineTemplate(new byte[abiUtils.trampolineSize()]);
}

Expand Down Expand Up @@ -178,24 +193,18 @@ public void registerSafeArenaAccessorMethod(ResolvedJavaMethod method) {
neverAccessesSharedArenaMethods.add(method);
}

/**
* We'd rather report the function descriptor than the native method type, but we don't have it
* available here. One could intercept this exception in
* {@link jdk.internal.foreign.abi.DowncallLinker#getBoundMethodHandle} and add information
* about the descriptor there.
*/
CFunctionPointer getDowncallStubPointer(NativeEntryPointInfo nep) {
FunctionPointerHolder holder = downcallStubs.get(nep);
if (holder == null) {
throw MissingForeignRegistrationUtils.reportDowncall(nep);
throw reportMissingDowncall(nep);
}
return holder.functionPointer;
}

CFunctionPointer getUpcallStubPointer(JavaEntryPointInfo jep) {
FunctionPointerHolder holder = upcallStubs.get(jep);
if (holder == null) {
throw MissingForeignRegistrationUtils.reportUpcall(jep);
throw reportMissingUpcall(jep);
}
return holder.functionPointer;
}
Expand Down Expand Up @@ -270,34 +279,91 @@ void freeTrampoline(long addr) {
}
}

public static class MissingForeignRegistrationUtils extends MissingRegistrationUtils {
public static MissingForeignRegistrationError reportDowncall(NativeEntryPointInfo nep) {
MissingForeignRegistrationError mfre = new MissingForeignRegistrationError(foreignRegistrationMessage("downcall", nep.methodType()));
report(mfre);
return mfre;
}

public static MissingForeignRegistrationError reportUpcall(JavaEntryPointInfo jep) {
MissingForeignRegistrationError mfre = new MissingForeignRegistrationError(foreignRegistrationMessage("upcall", jep.cMethodType()));
report(mfre);
return mfre;
/**
* Looks for the corresponding {@link #currentLinkRequests link request} by creating a
* {@link NativeEntryPointInfo} for each currently existing link request and comparing to the
* given one. The matching link request then contains the {@link FunctionDescriptor} and
* {@link LinkerOptions} that are required to produce a helpful error message for the user.
*/
private MissingForeignRegistrationError reportMissingDowncall(NativeEntryPointInfo nep) {
LinkRequest currentLinkRequest = null;
for (LinkRequest linkRequest : currentLinkRequests) {
NativeEntryPointInfo nativeEntryPointInfo = abiUtils.makeNativeEntrypoint(linkRequest.functionDescriptor, linkRequest.linkerOptions);
if (nep.equals(nativeEntryPointInfo)) {
currentLinkRequest = linkRequest;
break;
}
}
throw MissingForeignRegistrationUtils.report(false, currentLinkRequest, nep.methodType());
}

private static String foreignRegistrationMessage(String failedAction, MethodType methodType) {
return registrationMessage("perform " + failedAction + " with leaf type", methodType.toString(), "", "", "foreign", "foreign");
/**
* Similar to {@link #reportMissingDowncall} but for upcalls.
*/
private MissingForeignRegistrationError reportMissingUpcall(JavaEntryPointInfo jep) {
LinkRequest currentLinkRequest = null;
for (LinkRequest linkRequest : currentLinkRequests) {
JavaEntryPointInfo javaEntryPointInfo = abiUtils.makeJavaEntryPoint(linkRequest.functionDescriptor, linkRequest.linkerOptions);
if (jep.equals(javaEntryPointInfo)) {
currentLinkRequest = linkRequest;
break;
}
}
throw MissingForeignRegistrationUtils.report(true, currentLinkRequest, jep.handleType());
}

public static class MissingForeignRegistrationUtils extends MissingRegistrationUtils {
private static void report(MissingForeignRegistrationError exception) {
StackTraceElement responsibleClass = getResponsibleClass(exception, foreignEntryPoints);
MissingRegistrationUtils.report(exception, responsibleClass);
}

private static MissingForeignRegistrationError report(boolean upcall, LinkRequest linkRequest, MethodType methodType) {
String json = linkRequest != null ? elementToJSON(linkRequest) : "";
String failedAction = upcall ? "upcall" : "downcall";
String message = registrationMessage("perform " + failedAction + " with leaf type", methodType.toString(), json, "", "foreign", "foreign-function-and-memory-api");
MissingForeignRegistrationError mfre = new MissingForeignRegistrationError(message);
report(mfre);
throw mfre;
}

private static final Map<String, Set<String>> foreignEntryPoints = Map.of(
"jdk.internal.foreign.abi.AbstractLinker", Set.of(
"downcallHandle",
"upcallStub"));
}

record LinkRequest(boolean upcall, FunctionDescriptor functionDescriptor, LinkerOptions linkerOptions) implements AutoCloseable, JsonPrintable {

static LinkRequest create(boolean upcall, FunctionDescriptor functionDescriptor, LinkerOptions linkerOptions) {
LinkRequest linkRequest = new LinkRequest(upcall, functionDescriptor, linkerOptions);
ForeignFunctionsRuntime.singleton().currentLinkRequests.push(linkRequest);
return linkRequest;
}

@Override
public boolean equals(Object obj) {
return this == obj;
}

@Override
public int hashCode() {
return System.identityHashCode(this);
}

@Override
public void close() {
ForeignFunctionsRuntime.singleton().currentLinkRequests.remove(this);
}

@Override
public void printJson(JsonWriter writer) throws IOException {
writer.printValue(upcall ? "upcalls" : "downcalls").appendFieldSeparator().appendArrayStart();
SubstrateForeignUtil.linkRequestToJsonPrintable(this).printJson(writer);
writer.appendArrayEnd();
}
}

/**
* Arguments follow the same structure as described in {@link NativeEntryPointInfo}, with an
* additional {@link Target_jdk_internal_foreign_abi_NativeEntryPoint} (NEP) as the last
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,26 @@
*/
package com.oracle.svm.core.foreign;

import java.lang.foreign.GroupLayout;
import java.lang.foreign.MemoryLayout;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.PaddingLayout;
import java.lang.foreign.SequenceLayout;
import java.lang.foreign.StructLayout;
import java.lang.foreign.ValueLayout;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import com.oracle.svm.configure.UnresolvedAccessCondition;
import com.oracle.svm.configure.config.ForeignConfiguration.ConfigurationFunctionDescriptor;
import com.oracle.svm.configure.config.ForeignConfiguration.StubDesc;
import com.oracle.svm.core.AlwaysInline;
import com.oracle.svm.core.ArenaIntrinsics;
import com.oracle.svm.core.SubstrateOptions;
import com.oracle.svm.core.foreign.ForeignFunctionsRuntime.LinkRequest;
import com.oracle.svm.core.nodes.foreign.ScopedMemExceptionHandlerClusterNode.ClusterBeginNode;
import com.oracle.svm.core.nodes.foreign.ScopedMemExceptionHandlerClusterNode.ExceptionInputNode;
import com.oracle.svm.core.nodes.foreign.ScopedMemExceptionHandlerClusterNode.ExceptionPathNode;
Expand All @@ -35,7 +52,10 @@
import com.oracle.svm.util.LogUtils;

import jdk.graal.compiler.api.directives.GraalDirectives;
import jdk.graal.compiler.util.json.JsonPrintable;
import jdk.internal.foreign.MemorySessionImpl;
import jdk.internal.foreign.abi.LinkerOptions;
import jdk.internal.foreign.layout.AbstractLayout;

/**
* For details on the implementation of shared arenas on substrate see
Expand Down Expand Up @@ -119,4 +139,67 @@ public static void sessionExceptionHandler(MemorySessionImpl session, Object bas
// of control flow when searching for this pattern
GraalDirectives.controlFlowAnchor();
}

/**
* Generates a JSON configuration entry that will specify the stub required by the given link
* request. This method is meant to be used to generate a useful error message for the user if a
* stub lookup failed.
*/
static JsonPrintable linkRequestToJsonPrintable(LinkRequest linkRequest) {
List<String> parameterTypes = new LinkedList<>();
for (MemoryLayout argumentLayout : linkRequest.functionDescriptor().argumentLayouts()) {
parameterTypes.add(memoryLayoutToString(argumentLayout));
}
Optional<MemoryLayout> memoryLayout = linkRequest.functionDescriptor().returnLayout();
String returnLayout = memoryLayout.map(SubstrateForeignUtil::memoryLayoutToString).orElse("void");
ConfigurationFunctionDescriptor desc = new ConfigurationFunctionDescriptor(returnLayout, parameterTypes);

Map<String, Object> jsonOptions = Map.of();
if (!LinkerOptions.empty().equals(linkRequest.linkerOptions())) {
jsonOptions = new HashMap<>();
if (linkRequest.linkerOptions().isCritical()) {
jsonOptions.put("critical", Map.of("allowHeapAccess", linkRequest.linkerOptions().allowsHeapAccess()));
}
if (linkRequest.linkerOptions().isVariadicFunction()) {
jsonOptions.put("firstVariadicArg", linkRequest.linkerOptions().firstVariadicArgIndex());
}
if (linkRequest.linkerOptions().hasCapturedCallState()) {
jsonOptions.put("captureCallState", true);
}
}

return new StubDesc(UnresolvedAccessCondition.unconditional(), desc, jsonOptions);
}

/**
* Generates a memory layout description as defined in {@code FFM-API.md} to be used in the
* configuration file (usually {@code reachability-metadata.json}). This method implements the
* reverse operation of {@code com.oracle.svm.hosted.foreign.MemoryLayoutParser}.
*/
private static String memoryLayoutToString(MemoryLayout memoryLayout) {
String layoutString = switch (memoryLayout) {
case ValueLayout valueLayout -> {
Class<?> carrier = valueLayout.carrier();
if (carrier == MemorySegment.class) {
yield "void*";
}
yield "j" + carrier.getName();
}
case GroupLayout structLayout -> {
String[] memberStrings = new String[structLayout.memberLayouts().size()];
int i = 0;
for (MemoryLayout memberLayout : structLayout.memberLayouts()) {
memberStrings[i++] = memoryLayoutToString(memberLayout);
}
String prefix = structLayout instanceof StructLayout ? "struct(" : "union(";
yield prefix + String.join(",", memberStrings) + ")";
}
case SequenceLayout sequenceLayout -> "sequence(" + sequenceLayout.elementCount() + ", " + memoryLayoutToString(sequenceLayout.elementLayout()) + ")";
case PaddingLayout paddingLayout -> "padding(" + paddingLayout.byteSize() + ")";
};
if (memoryLayout instanceof AbstractLayout<?> abstractLayout && !abstractLayout.hasNaturalAlignment()) {
return "align(" + memoryLayout.byteAlignment() + ", " + layoutString + ")";
}
return layoutString;
}
}
Loading