Skip to content

Commit 8e2ad55

Browse files
committed
Support build-time initialization of downcall handles
1 parent 0034624 commit 8e2ad55

File tree

7 files changed

+263
-10
lines changed

7 files changed

+263
-10
lines changed

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2023, 2026, 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
@@ -72,6 +72,7 @@
7272
import com.oracle.svm.core.snippets.SnippetRuntime;
7373
import com.oracle.svm.core.snippets.SubstrateForeignCallTarget;
7474
import com.oracle.svm.core.util.ImageHeapMap;
75+
import com.oracle.svm.shared.AlwaysInline;
7576
import com.oracle.svm.shared.Uninterruptible;
7677
import com.oracle.svm.shared.singletons.traits.BuiltinTraits.AllAccess;
7778
import com.oracle.svm.shared.singletons.traits.BuiltinTraits.NoLayeredCallbacks;
@@ -474,6 +475,12 @@ public void onScopeReachable(Object scopeObj, DisallowedObjectReporter reporter)
474475
}
475476
}
476477

478+
@AlwaysInline("method handle interpreter performance")
479+
@Override
480+
public MethodType getMethodTypeFromNativeEntryPoint(Object nativeEntryPoint) {
481+
return ((Target_jdk_internal_foreign_abi_NativeEntryPoint) nativeEntryPoint).type();
482+
}
483+
477484
/**
478485
* Workaround for CapturableState.maskFromName(String) being interruptible.
479486
*/
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
/*
2+
* Copyright (c) 2026, 2026, 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+
26+
package com.oracle.svm.core.foreign;
27+
28+
/*
29+
* Copyright (c) 2026, 2026, Oracle and/or its affiliates. All rights reserved.
30+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
31+
*
32+
* This code is free software; you can redistribute it and/or modify it
33+
* under the terms of the GNU General Public License version 2 only, as
34+
* published by the Free Software Foundation. Oracle designates this
35+
* particular file as subject to the "Classpath" exception as provided
36+
* by Oracle in the LICENSE file that accompanied this code.
37+
*
38+
* This code is distributed in the hope that it will be useful, but WITHOUT
39+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
40+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
41+
* version 2 for more details (a copy is included in the LICENSE file that
42+
* accompanied this code).
43+
*
44+
* You should have received a copy of the GNU General Public License version
45+
* 2 along with this work; if not, write to the Free Software Foundation,
46+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
47+
*
48+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
49+
* or visit www.oracle.com if you need additional information or have any
50+
* questions.
51+
*/
52+
53+
import java.lang.invoke.MethodType;
54+
import java.util.Arrays;
55+
import java.util.Objects;
56+
57+
import org.graalvm.nativeimage.Platform;
58+
import org.graalvm.nativeimage.Platforms;
59+
60+
import com.oracle.svm.shared.util.BasedOnJDKClass;
61+
import com.oracle.svm.shared.util.VMError;
62+
import com.oracle.svm.util.GuestAccess;
63+
import com.oracle.svm.util.GuestElements;
64+
import com.oracle.svm.util.JVMCIReflectionUtil;
65+
66+
import jdk.internal.foreign.abi.NativeEntryPoint;
67+
import jdk.internal.foreign.abi.VMStorage;
68+
import jdk.vm.ci.meta.JavaConstant;
69+
70+
@BasedOnJDKClass(NativeEntryPoint.class)
71+
@BasedOnJDKClass(className = "jdk.internal.foreign.abi.SoftReferenceCache")
72+
@BasedOnJDKClass(className = "jdk.internal.foreign.abi.NativeEntryPoint$CacheKey")
73+
@Platforms(Platform.HOSTED_ONLY.class)
74+
public final class NativeEntryPointHelper {
75+
76+
/**
77+
* Extracts the information (i.e. {@link NativeEntryPointInfo}) required to create the given
78+
* {@link NativeEntryPoint}. This leverages the NativeEntryPoint cache (static field
79+
* {@code NEP_CACHE}) which stores this information as key. Unfortunately, since the cache is
80+
* private, it can only be extracted via guest metadata and constant access.
81+
*
82+
* @param nativeEntryPointConstant The {@link NativeEntryPoint} to extract the information for.
83+
* @return A {@link NativeEntryPointInfo} object encapsulating all information required to
84+
* create the {@link NativeEntryPoint}.
85+
*/
86+
public static NativeEntryPointInfo extractNativeEntryPointInfo(JavaConstant nativeEntryPointConstant) {
87+
GuestAccess access = GuestAccess.get();
88+
GuestElements elements = access.elements;
89+
JavaConstant nepCacheConstant = JVMCIReflectionUtil.readStaticField(elements.jdk_internal_foreign_abi_NativeEntryPoint, "NEP_CACHE");
90+
JavaConstant cacheConstant = JVMCIReflectionUtil.readInstanceField(nepCacheConstant, elements.jdk_internal_foreign_abi_SoftReferenceCache, "cache");
91+
JavaConstant entrySetConstant = access.invoke(elements.java_util_Map_entrySet, cacheConstant);
92+
JavaConstant entriesConstant = access.invoke(elements.java_util_Collection_toArray, entrySetConstant);
93+
Object[] entries = access.asHostObject(Object[].class, entriesConstant);
94+
95+
// We have the value; search the key
96+
JavaConstant cacheKeyConstant = null;
97+
for (Object entry : entries) {
98+
JavaConstant entryConstant = access.getSnippetReflection().forObject(entry);
99+
JavaConstant nodeConstant = access.invoke(elements.java_util_Map_Entry_getValue, entryConstant);
100+
JavaConstant refConstant = JVMCIReflectionUtil.readInstanceField(nodeConstant, elements.jdk_internal_foreign_abi_SoftReferenceCache_Node, "ref");
101+
if (!refConstant.isNull() && access.invoke(elements.java_lang_ref_Reference_refersTo, refConstant, nativeEntryPointConstant).asBoolean()) {
102+
cacheKeyConstant = access.invoke(elements.java_util_Map_Entry_getKey, entryConstant);
103+
break;
104+
}
105+
}
106+
107+
/*
108+
* Any instance of NativeEntryPoint is created via 'NativeEntryPoint.make' and will
109+
* therefore be added to NEP_CACHE. Further, the keys of NEP_CACHE are strongly referenced.
110+
* This means that there must be a cache key for any NativeEntryPoint that exists.
111+
*/
112+
VMError.guarantee(cacheKeyConstant != null, "Cannot extract info for NativeEntryPoint because it is not in NEP_CACHE");
113+
114+
/*
115+
* Field 'CacheKey.abi' is ignored because the ABIDescriptor is JDK's equivalent of our
116+
* Substrate*RegisterConfig.
117+
*/
118+
JavaConstant argMovesListConstant = JVMCIReflectionUtil.readInstanceField(cacheKeyConstant, elements.jdk_internal_foreign_abi_NativeEntryPoint_CacheKey, "argMoves");
119+
JavaConstant returnMovesListConstant = JVMCIReflectionUtil.readInstanceField(cacheKeyConstant, elements.jdk_internal_foreign_abi_NativeEntryPoint_CacheKey, "retMoves");
120+
JavaConstant emptyVmStorageArray = access.asArrayConstant(elements.jdk_internal_foreign_abi_VMStorage);
121+
VMStorage[] argMoves = access.asHostObject(VMStorage[].class,
122+
access.invoke(elements.java_util_Collection_toArray_withArray, argMovesListConstant, emptyVmStorageArray));
123+
VMStorage[] returnMoves = access.asHostObject(VMStorage[].class,
124+
access.invoke(elements.java_util_Collection_toArray_withArray, returnMovesListConstant, emptyVmStorageArray));
125+
MethodType methodType = access.asHostObject(MethodType.class,
126+
access.invoke(elements.jdk_internal_foreign_abi_NativeEntryPoint_type, nativeEntryPointConstant));
127+
boolean needsReturnBuffer = JVMCIReflectionUtil.readInstanceField(cacheKeyConstant, elements.jdk_internal_foreign_abi_NativeEntryPoint_CacheKey, "needsReturnBuffer").asBoolean();
128+
int capturedStateMask = JVMCIReflectionUtil.readInstanceField(cacheKeyConstant, elements.jdk_internal_foreign_abi_NativeEntryPoint_CacheKey, "capturedStateMask").asInt();
129+
boolean needsTransition = JVMCIReflectionUtil.readInstanceField(cacheKeyConstant, elements.jdk_internal_foreign_abi_NativeEntryPoint_CacheKey, "needsTransition").asBoolean();
130+
boolean allowHeapAccess = Arrays.stream(argMoves).anyMatch(Objects::isNull);
131+
132+
return NativeEntryPointInfo.make(argMoves, returnMoves, methodType, needsReturnBuffer, capturedStateMask, needsTransition, allowHeapAccess);
133+
}
134+
}

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

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,30 +26,41 @@
2626

2727
import java.lang.invoke.MethodType;
2828

29+
import org.graalvm.nativeimage.MissingForeignRegistrationError;
30+
import org.graalvm.nativeimage.hosted.FieldValueTransformer;
31+
2932
import com.oracle.svm.core.FunctionPointerHolder;
30-
import com.oracle.svm.core.annotate.Alias;
33+
import com.oracle.svm.core.annotate.RecomputeFieldValue;
3134
import com.oracle.svm.core.annotate.Substitute;
3235
import com.oracle.svm.core.annotate.TargetClass;
36+
import com.oracle.svm.shared.util.VMError;
37+
import com.oracle.svm.util.GuestAccess;
3338

3439
import jdk.internal.foreign.abi.ABIDescriptor;
40+
import jdk.internal.foreign.abi.NativeEntryPoint;
3541
import jdk.internal.foreign.abi.VMStorage;
42+
import jdk.vm.ci.meta.JavaConstant;
3643

3744
/**
3845
* Packs the address of a {@link com.oracle.svm.hosted.foreign.DowncallStub} with some extra
3946
* information.
4047
*/
4148
@SuppressWarnings("javadoc")
42-
@TargetClass(className = "jdk.internal.foreign.abi.NativeEntryPoint", onlyWith = ForeignAPIPredicates.Enabled.class)
49+
@TargetClass(value = NativeEntryPoint.class, onlyWith = ForeignAPIPredicates.Enabled.class)
4350
@Substitute
4451
public final class Target_jdk_internal_foreign_abi_NativeEntryPoint {
4552

46-
@Alias //
47-
final MethodType methodType;
53+
@Substitute //
54+
@RecomputeFieldValue(isFinal = true, kind = RecomputeFieldValue.Kind.Custom, declClass = MethodTypeTransformer.class) //
55+
private MethodType methodType;
4856

57+
@RecomputeFieldValue(isFinal = true, kind = RecomputeFieldValue.Kind.Custom, declClass = DowncallAddressTransformer.class) //
4958
final FunctionPointerHolder downcallStubPointerHolder;
5059

60+
@RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset) //
5161
final int captureMask;
5262

63+
@RecomputeFieldValue(isFinal = true, kind = RecomputeFieldValue.Kind.Custom, declClass = DowncallInvokerAddressTransformer.class) //
5364
final FunctionPointerHolder downcallInvokerPointerHolder;
5465

5566
Target_jdk_internal_foreign_abi_NativeEntryPoint(MethodType methodType, FunctionPointerHolder downcallStubPointerHolder, int captureMask) {
@@ -94,4 +105,40 @@ public static Target_jdk_internal_foreign_abi_NativeEntryPoint make(ABIDescripto
94105
public MethodType type() {
95106
return methodType;
96107
}
108+
109+
static final class MethodTypeTransformer implements FieldValueTransformer {
110+
@Override
111+
public Object transform(Object receiver, Object originalValue) {
112+
VMError.guarantee(receiver.getClass() == NativeEntryPoint.class);
113+
return ((NativeEntryPoint) receiver).type();
114+
}
115+
}
116+
117+
static final class DowncallAddressTransformer implements FieldValueTransformer {
118+
@Override
119+
public Object transform(Object receiver, Object originalValue) {
120+
VMError.guarantee(receiver.getClass() == NativeEntryPoint.class);
121+
try {
122+
JavaConstant nativeEntryPoint = GuestAccess.get().getSnippetReflection().forObject(receiver);
123+
NativeEntryPointInfo nativeEntryPointInfo = NativeEntryPointHelper.extractNativeEntryPointInfo(nativeEntryPoint);
124+
return ForeignFunctionsRuntime.singleton().getDowncallStubPointerHolder(nativeEntryPointInfo);
125+
} catch (MissingForeignRegistrationError e) {
126+
// explicitly catch and rethrow with VMError; otherwise, it may be ignored
127+
throw VMError.shouldNotReachHere(e);
128+
}
129+
}
130+
}
131+
132+
static final class DowncallInvokerAddressTransformer implements FieldValueTransformer {
133+
@Override
134+
public Object transform(Object receiver, Object originalValue) {
135+
VMError.guarantee(receiver.getClass() == NativeEntryPoint.class);
136+
try {
137+
return ForeignFunctionsRuntime.singleton().getDowncallStubInvokerPointerHolder(((NativeEntryPoint) receiver).type());
138+
} catch (MissingForeignRegistrationError e) {
139+
// explicitly catch and rethrow with VMError; otherwise, it may be ignored
140+
throw VMError.shouldNotReachHere(e);
141+
}
142+
}
143+
}
97144
}

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/ForeignSupport.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
import java.lang.annotation.Retention;
2828
import java.lang.annotation.RetentionPolicy;
29+
import java.lang.invoke.MethodType;
2930

3031
import org.graalvm.nativeimage.ImageSingletons;
3132

@@ -50,6 +51,9 @@ static ForeignSupport singleton() {
5051

5152
void onScopeReachable(Object obj, DisallowedObjectReporter reporter);
5253

54+
/** A helper that calls {@code jdk.internal.foreign.abi.NativeEntryPoint#type()}. */
55+
MethodType getMethodTypeFromNativeEntryPoint(Object nativeEntryPoint);
56+
5357
/**
5458
* This annotation is used to mark substitution methods that substitute an
5559
* {@code jdk.internal.misc.ScopedMemoryAccess.Scoped}-annotated method. This will signal the

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/invoke/MethodHandleUtils.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,12 @@
2727
import static com.oracle.svm.shared.util.VMError.shouldNotReachHere;
2828

2929
import java.lang.invoke.MethodHandle;
30+
import java.lang.invoke.MethodType;
3031

31-
import com.oracle.svm.shared.AlwaysInline;
32+
import com.oracle.svm.core.ForeignSupport;
3233
import com.oracle.svm.core.hub.RuntimeClassLoading;
3334
import com.oracle.svm.core.methodhandles.MethodHandleInterpreterUtils;
35+
import com.oracle.svm.shared.AlwaysInline;
3436

3537
import sun.invoke.util.Wrapper;
3638

@@ -64,6 +66,12 @@ public static long longUnbox(Object retVal, Target_java_lang_invoke_MemberName m
6466
return longUnbox(retVal, memberName.getMethodType().returnType());
6567
}
6668

69+
@AlwaysInline("constant fold as much as possible in signature polymorphic wrappers")
70+
public static long longUnboxForeign(Object retVal, Object nativeEntryPoint) {
71+
MethodType methodType = ForeignSupport.singleton().getMethodTypeFromNativeEntryPoint(nativeEntryPoint);
72+
return longUnbox(retVal, methodType.returnType());
73+
}
74+
6775
@AlwaysInline("constant fold as much as possible in signature polymorphic wrappers")
6876
private static long longUnbox(Object retVal, Class<?> returnType) {
6977
switch (Wrapper.forPrimitiveType(returnType)) {
@@ -100,6 +108,12 @@ public static int intUnbox(Object retVal, Target_java_lang_invoke_MemberName mem
100108
return intUnbox(retVal, memberName.getMethodType().returnType());
101109
}
102110

111+
@AlwaysInline("constant fold as much as possible in signature polymorphic wrappers")
112+
public static int intUnboxForeign(Object retVal, Object nativeEntryPoint) {
113+
MethodType methodType = ForeignSupport.singleton().getMethodTypeFromNativeEntryPoint(nativeEntryPoint);
114+
return intUnbox(retVal, methodType.returnType());
115+
}
116+
103117
@AlwaysInline("constant fold as much as possible in signature polymorphic wrappers")
104118
public static int intUnbox(Object retVal, Class<?> returnType) {
105119
switch (Wrapper.forPrimitiveType(returnType)) {
@@ -134,6 +148,12 @@ public static short shortUnbox(Object retVal, Target_java_lang_invoke_MemberName
134148
return shortUnbox(retVal, memberName.getMethodType().returnType());
135149
}
136150

151+
@AlwaysInline("constant fold as much as possible in signature polymorphic wrappers")
152+
public static short shortUnboxForeign(Object retVal, Object nativeEntryPoint) {
153+
MethodType methodType = ForeignSupport.singleton().getMethodTypeFromNativeEntryPoint(nativeEntryPoint);
154+
return shortUnbox(retVal, methodType.returnType());
155+
}
156+
137157
@AlwaysInline("constant fold as much as possible in signature polymorphic wrappers")
138158
public static short shortUnbox(Object retVal, Class<?> returnType) {
139159
switch (Wrapper.forPrimitiveType(returnType)) {

0 commit comments

Comments
 (0)