diff --git a/compiler/mx.compiler/suite.py b/compiler/mx.compiler/suite.py index b000c64bf1c2..399742657036 100644 --- a/compiler/mx.compiler/suite.py +++ b/compiler/mx.compiler/suite.py @@ -623,7 +623,7 @@ "jdk.unsupported" # sun.misc.Unsafe ], "exports" : [ - """* to com.oracle.graal.graal_enterprise,org.graalvm.nativeimage.pointsto,org.graalvm.nativeimage.builder,org.graalvm.nativeimage.llvm,com.oracle.svm.svm_enterprise,com.oracle.svm_enterprise.ml_dataset,org.graalvm.nativeimage.base, + """* to com.oracle.graal.graal_enterprise,org.graalvm.nativeimage.pointsto,org.graalvm.nativeimage.builder,org.graalvm.nativeimage.foreign,org.graalvm.nativeimage.llvm,com.oracle.svm.svm_enterprise,com.oracle.svm_enterprise.ml_dataset,org.graalvm.nativeimage.base, org.graalvm.extraimage.builder,com.oracle.svm.extraimage_enterprise""", "org.graalvm.compiler.java to org.graalvm.nativeimage.agent.tracing,org.graalvm.nativeimage.configure", "org.graalvm.compiler.core.common to org.graalvm.nativeimage.agent.tracing,org.graalvm.nativeimage.objectfile", diff --git a/docs/reference-manual/native-image/BuildOptions.md b/docs/reference-manual/native-image/BuildOptions.md index b09d4ac4b4e6..604b0865e398 100644 --- a/docs/reference-manual/native-image/BuildOptions.md +++ b/docs/reference-manual/native-image/BuildOptions.md @@ -17,6 +17,7 @@ Depending on the GraalVM version, the options to the `native-image` builder may * `-J`: pass an option directly to the JVM running the `native-image` builder * `--diagnostics-mode`: enable diagnostics output which includes class initialization, substitutions, etc. * `--enable-preview`: allow classes to depend on preview features of this release +* `--enable-native-access [,...]` modules that are permitted to perform restricted native operations. `` can also be `ALL-UNNAMED`. * `--verbose`: enable verbose output * `--version`: print the product version and exit * `--help`: print this help message diff --git a/sdk/mx.sdk/suite.py b/sdk/mx.sdk/suite.py index 3ad81153d087..3885b1cfeb7f 100644 --- a/sdk/mx.sdk/suite.py +++ b/sdk/mx.sdk/suite.py @@ -411,7 +411,7 @@ "org.graalvm.word", "org.graalvm.polyglot.impl to org.graalvm.truffle, com.oracle.graal.graal_enterprise", "org.graalvm.word.impl to jdk.internal.vm.compiler", - "org.graalvm.nativeimage.impl to org.graalvm.nativeimage.pointsto,org.graalvm.nativeimage.base,org.graalvm.nativeimage.builder,org.graalvm.nativeimage.configure,com.oracle.svm.svm_enterprise,org.graalvm.extraimage.builder", + "org.graalvm.nativeimage.impl to org.graalvm.nativeimage.pointsto,org.graalvm.nativeimage.base,org.graalvm.nativeimage.builder,org.graalvm.nativeimage.configure,com.oracle.svm.svm_enterprise,org.graalvm.extraimage.builder,org.graalvm.nativeimage.foreign", "org.graalvm.nativeimage.impl.clinit to org.graalvm.nativeimage.builder", ], "uses" : [ diff --git a/sdk/src/org.graalvm.nativeimage/snapshot.sigtest b/sdk/src/org.graalvm.nativeimage/snapshot.sigtest index 18bc6198f524..046dad975dff 100644 --- a/sdk/src/org.graalvm.nativeimage/snapshot.sigtest +++ b/sdk/src/org.graalvm.nativeimage/snapshot.sigtest @@ -1077,6 +1077,10 @@ meth public !varargs static void initializeAtRunTime(java.lang.Class[]) meth public !varargs static void initializeAtRunTime(java.lang.String[]) supr java.lang.Object +CLSS public final org.graalvm.nativeimage.hosted.RuntimeForeignAccess +meth public !varargs static void registerForDowncall(java.lang.Object,java.lang.Object[]) +supr java.lang.Object + CLSS public final org.graalvm.nativeimage.hosted.RuntimeJNIAccess meth public !varargs static void register(java.lang.Class[]) meth public !varargs static void register(java.lang.reflect.Executable[]) diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeForeignAccess.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeForeignAccess.java new file mode 100644 index 000000000000..1894f6243a1f --- /dev/null +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeForeignAccess.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.graalvm.nativeimage.hosted; + +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.impl.ConfigurationCondition; +import org.graalvm.nativeimage.impl.RuntimeForeignAccessSupport; + +@Platforms(Platform.HOSTED_ONLY.class) +public final class RuntimeForeignAccess { + + /** + * Registers the provided descriptor and options pair for foreign downcalls at runtime. Needed + * in order to get a method handle using downcallHandle + *

+ * Since the foreign functions feature is currently a preview of the Java platform (as part of + * Project Panama), this API is itself a + * preview subject to changes following changes in the feature itself. This also means that this + * feature must be manually enabled, using (on a JDK greater or equal to 21) + * + *

+     * {@code
+     *          --enable-preview -H:+ForeignFunctions
+     * }
+     * 
+ * + * Even though this method is weakly typed for compatibility reasons, runtime checks will be + * performed to ensure that the arguments have the expected type. It will be deprecated in favor + * of strongly typed variant as soon as possible. + * + * @param desc A {@link java.lang.foreign.FunctionDescriptor} to register for downcalls. + * @param options An array of {@link java.lang.foreign.Linker.Option} used for the downcalls. + * + * @since 23.1 + */ + public static void registerForDowncall(Object desc, Object... options) { + ImageSingletons.lookup(RuntimeForeignAccessSupport.class).registerForDowncall(ConfigurationCondition.alwaysTrue(), desc, options); + } + + private RuntimeForeignAccess() { + } +} diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeForeignAccessSupport.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeForeignAccessSupport.java new file mode 100644 index 000000000000..6db57cdb81ab --- /dev/null +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeForeignAccessSupport.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.graalvm.nativeimage.impl; + +public interface RuntimeForeignAccessSupport { + void registerForDowncall(ConfigurationCondition condition, Object desc, Object... options); +} diff --git a/substratevm/mx.substratevm/mx_substratevm.py b/substratevm/mx.substratevm/mx_substratevm.py index b664b6e34e0d..ee07602a147b 100644 --- a/substratevm/mx.substratevm/mx_substratevm.py +++ b/substratevm/mx.substratevm/mx_substratevm.py @@ -1105,6 +1105,22 @@ def _native_image_launcher_extra_jvm_args(): if llvm_supported: mx_sdk_vm.register_graalvm_component(ce_llvm_backend) +if mx.get_jdk(tag='default').javaCompliance >= '21': + mx_sdk_vm.register_graalvm_component(mx_sdk_vm.GraalVmJreComponent( + suite=suite, + name='SubstrateVM Foreign API Preview Feature', + short_name='panama', + dir_name='svm-preview', + installable_id='native-image', + license_files=[], + third_party_license_files=[], + dependencies=['SubstrateVM'], + builder_jar_distributions=['substratevm:SVM_FOREIGN'], + installable=False, + jlink=False, + )) + + mx_sdk_vm.register_graalvm_component(mx_sdk_vm.GraalVmJreComponent( suite=suite, diff --git a/substratevm/mx.substratevm/suite.py b/substratevm/mx.substratevm/suite.py index 813f14b37b8a..0f647a000bb7 100644 --- a/substratevm/mx.substratevm/suite.py +++ b/substratevm/mx.substratevm/suite.py @@ -654,6 +654,63 @@ ], }, + "com.oracle.svm.core.foreign": { + "subDir": "src", + "sourceDirs": ["src"], + "dependencies": [ + "com.oracle.svm.core" + ], + "requiresConcealed": { + "java.base": [ + "jdk.internal.loader", + "jdk.internal.foreign", + "jdk.internal.foreign.abi", + "jdk.internal.foreign.abi.x64", + "jdk.internal.foreign.abi.x64.sysv", + "jdk.internal.foreign.abi.x64.windows", + ], + "jdk.internal.vm.ci" : [ + ], + }, + "javaCompliance" : "21+", + "javaPreviewNeeded": "21+", + "annotationProcessors": [ + "compiler:GRAAL_PROCESSOR", + "SVM_PROCESSOR", + ], + "javac.lint.overrides": "-preview", + "checkstyle": "com.oracle.svm.hosted", + "workingSets": "SVM", + "jacoco" : "include", + }, + + "com.oracle.svm.hosted.foreign": { + "subDir": "src", + "sourceDirs": ["src"], + "dependencies": [ + "com.oracle.svm.hosted", + "com.oracle.svm.core.foreign" + ], + "requiresConcealed": { + "java.base": [ + "jdk.internal.foreign.abi", + ], + "jdk.internal.vm.ci" : [ + "jdk.vm.ci.code" + ], + }, + "javaCompliance" : "21+", + "javaPreviewNeeded": "21+", + "annotationProcessors": [ + "compiler:GRAAL_PROCESSOR", + "SVM_PROCESSOR", + ], + "javac.lint.overrides": "-preview", + "checkstyle": "com.oracle.svm.hosted", + "workingSets": "SVM", + "jacoco" : "include", + }, + # Native libraries below explicitly set _FORTIFY_SOURCE to 0. This constant controls how glibc handles some # functions that can cause a stack overflow like snprintf. If set to 1 or 2, it causes glibc to use internal # functions with extra checking that are not available in all libc implementations. Different distros use @@ -1310,7 +1367,7 @@ "exports" : [ "com.oracle.svm.hosted to java.base", "com.oracle.svm.truffle.api to org.graalvm.truffle", - "* to org.graalvm.nativeimage.base,jdk.internal.vm.compiler,org.graalvm.nativeimage.driver,org.graalvm.nativeimage.configure,org.graalvm.nativeimage.librarysupport,org.graalvm.nativeimage.junitsupport,org.graalvm.nativeimage.llvm,org.graalvm.nativeimage.agent.jvmtibase,org.graalvm.nativeimage.agent.tracing,org.graalvm.nativeimage.agent.diagnostics,com.oracle.svm.svm_enterprise,com.oracle.svm.svm_enterprise.llvm,com.oracle.svm_enterprise.ml_dataset,org.graalvm.extraimage.builder,com.oracle.svm.extraimage_enterprise", + "* to org.graalvm.nativeimage.base,jdk.internal.vm.compiler,org.graalvm.nativeimage.driver,org.graalvm.nativeimage.configure,org.graalvm.nativeimage.librarysupport,org.graalvm.nativeimage.junitsupport,org.graalvm.nativeimage.llvm,org.graalvm.nativeimage.agent.jvmtibase,org.graalvm.nativeimage.agent.tracing,org.graalvm.nativeimage.agent.diagnostics,com.oracle.svm.svm_enterprise,com.oracle.svm.svm_enterprise.llvm,com.oracle.svm_enterprise.ml_dataset,org.graalvm.extraimage.builder,com.oracle.svm.extraimage_enterprise,org.graalvm.nativeimage.foreign", ], "opens" : [ "com.oracle.svm.core to jdk.internal.vm.compiler", @@ -1637,9 +1694,9 @@ "moduleInfo" : { "name" : "org.graalvm.nativeimage.base", "exports" : [ - "com.oracle.svm.util to org.graalvm.nativeimage.pointsto,org.graalvm.nativeimage.builder,org.graalvm.nativeimage.librarysupport,org.graalvm.nativeimage.driver,org.graalvm.nativeimage.llvm,org.graalvm.nativeimage.agent.jvmtibase,org.graalvm.nativeimage.agent.tracing,org.graalvm.nativeimage.agent.diagnostics,org.graalvm.nativeimage.junitsupport,com.oracle.svm.svm_enterprise,com.oracle.svm_enterprise.ml_dataset,org.graalvm.extraimage.builder,com.oracle.svm.extraimage_enterprise,org.graalvm.extraimage.librarysupport", - "com.oracle.svm.common.meta to org.graalvm.nativeimage.pointsto,org.graalvm.nativeimage.builder", - "com.oracle.svm.common.option to org.graalvm.nativeimage.pointsto,org.graalvm.nativeimage.builder,org.graalvm.nativeimage.driver", + "com.oracle.svm.util to org.graalvm.nativeimage.pointsto,org.graalvm.nativeimage.builder,org.graalvm.nativeimage.librarysupport,org.graalvm.nativeimage.driver,org.graalvm.nativeimage.llvm,org.graalvm.nativeimage.agent.jvmtibase,org.graalvm.nativeimage.agent.tracing,org.graalvm.nativeimage.agent.diagnostics,org.graalvm.nativeimage.junitsupport,com.oracle.svm.svm_enterprise,com.oracle.svm_enterprise.ml_dataset,org.graalvm.extraimage.builder,com.oracle.svm.extraimage_enterprise,org.graalvm.extraimage.librarysupport,org.graalvm.nativeimage.foreign", + "com.oracle.svm.common.meta to org.graalvm.nativeimage.pointsto,org.graalvm.nativeimage.builder,org.graalvm.nativeimage.foreign", + "com.oracle.svm.common.option to org.graalvm.nativeimage.pointsto,org.graalvm.nativeimage.builder,org.graalvm.nativeimage.driver,org.graalvm.nativeimage.foreign", ], } }, @@ -1870,6 +1927,42 @@ }, }, + "SVM_FOREIGN": { + "subDir": "src", + "description" : "SubstrateVM support for the Foreign API", + "dependencies": [ + "com.oracle.svm.hosted.foreign", + ], + "distDependencies": [ + "compiler:GRAAL", + "SVM" + ], + "moduleInfo" : { + "name" : "org.graalvm.nativeimage.foreign", + "requires" : [ + "org.graalvm.nativeimage.builder" + ], + "exports" : [ + "* to org.graalvm.nativeimage.builder", + ], + "requiresConcealed": { + "jdk.internal.vm.ci" : [ + "jdk.vm.ci.meta", + "jdk.vm.ci.code", + "jdk.vm.ci.amd64", + ], + "java.base": [ + "jdk.internal.foreign", + "jdk.internal.foreign.abi", + "jdk.internal.foreign.abi.x64", + "jdk.internal.foreign.abi.x64.sysv", + "jdk.internal.foreign.abi.x64.windows", + ], + }, + }, + "maven" : False, + }, + "SVM_LLVM" : { "subDir" : "src", "description" : "LLVM backend for Native Image", diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapScanner.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapScanner.java index 16eebf834315..5980d4a99e62 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapScanner.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapScanner.java @@ -359,6 +359,7 @@ protected JavaConstant createFieldValue(AnalysisField field, ImageHeapInstance r ObjectScanner.unsupportedFeatureDuringFieldScan(universe.getBigbang(), field, receiver, e, reason); transformedValue = JavaConstant.NULL_POINTER; } + assert transformedValue != null : field.getDeclaringClass().toJavaName() + "::" + field.getName(); return createImageHeapConstant(transformedValue, reason); } diff --git a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/AbiUtils.java b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/AbiUtils.java new file mode 100644 index 000000000000..1606250bc2d2 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/AbiUtils.java @@ -0,0 +1,432 @@ +/* + * Copyright (c) 2023, 2023, 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.core.foreign; + +import static com.oracle.svm.core.util.VMError.unsupportedFeature; + +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.Linker; +import java.lang.invoke.MethodType; +import java.util.Arrays; +import java.util.stream.Stream; + +import org.graalvm.compiler.api.replacements.Fold; +import org.graalvm.compiler.nodes.ValueNode; +import org.graalvm.compiler.nodes.calc.ReinterpretNode; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +import com.oracle.svm.core.SubstrateTargetDescription; +import com.oracle.svm.core.SubstrateUtil; +import com.oracle.svm.core.graal.code.AssignedLocation; +import com.oracle.svm.core.headers.LibC; +import com.oracle.svm.core.headers.WindowsAPIs; +import com.oracle.svm.core.util.VMError; + +import jdk.internal.foreign.CABI; +import jdk.internal.foreign.abi.Binding; +import jdk.internal.foreign.abi.CallingSequence; +import jdk.internal.foreign.abi.LinkerOptions; +import jdk.internal.foreign.abi.VMStorage; +import jdk.internal.foreign.abi.x64.X86_64Architecture; +import jdk.vm.ci.amd64.AMD64; +import jdk.vm.ci.code.Register; +import jdk.vm.ci.meta.JavaKind; +import jdk.vm.ci.meta.PlatformKind; + +/** + * Utils for ABI specific functionalities in the context of the Java Foreign API. Provides methods + * to transform JDK-internal data-structures into SubstrateVM ones. + */ +public abstract class AbiUtils { + + @Platforms(Platform.HOSTED_ONLY.class) + public abstract static class Adaptation { + public abstract Class apply(Class parameter); + + public abstract ValueNode apply(ValueNode parameter); + } + + @Platforms(Platform.HOSTED_ONLY.class) + public static final class Reinterpret extends Adaptation { + public final JavaKind to; + + public Reinterpret(JavaKind to) { + this.to = to; + } + + @Override + public Class apply(Class parameter) { + return to.toJavaClass(); + } + + @Override + public ValueNode apply(ValueNode parameter) { + return ReinterpretNode.reinterpret(to, parameter); + } + } + + @Platforms(Platform.HOSTED_ONLY.class) + public static AbiUtils create() { + return switch (CABI.current()) { + case SYS_V -> new ABIs.SysV(); + case WIN_64 -> new ABIs.Win64(); + default -> new ABIs.Unsupported(CABI.current().name()); + }; + } + + @Fold + public static AbiUtils singleton() { + return ImageSingletons.lookup(AbiUtils.class); + } + + public abstract void methodTypeMatchAssignment(int savedValueMask, MethodType methodType, AssignedLocation[] assignments, AssignedLocation[] returnAssignment, FunctionDescriptor fd, + Linker.Option... options); + + /** + * 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. + */ + public abstract NativeEntryPointInfo makeEntrypoint(FunctionDescriptor desc, Linker.Option... options); + + /** + * Generate a register allocation for SubstrateVM from the one generated by and for HotSpot. + */ + public abstract AssignedLocation[] toMemoryAssignment(VMStorage[] moves, boolean forReturn); + + /** + * Generate additional argument adaptations which are not done by HotSpot. Note that, unlike + * {@link AbiUtils#toMemoryAssignment}, this information is not part of + * {@link NativeEntryPointInfo}. The reason for that being that this information is not relevant + * at runtime, so we don't want ot include it in {@link NativeEntryPointInfo}, which lives at + * runtime. This information is only useful when generating the + * {@link com.oracle.svm.hosted.foreign.DowncallStub}. + *

+ * For convenience, there is an adaptation for every argument (except the NEP itself) to the + * entrypoint, i.e. including the special prefix arguments (call address, return buffer, call + * state buffer), even though these should most likely never be adapted. + */ + @Platforms(Platform.HOSTED_ONLY.class) + public abstract Adaptation[] adaptArguments(NativeEntryPointInfo nep); + + @Platforms(Platform.HOSTED_ONLY.class) + public abstract void checkLibrarySupport(); +} + +class ABIs { + static final class Unsupported extends AbiUtils { + private final String name; + + Unsupported(String name) { + this.name = name; + } + + private Z fail() { + throw unsupportedFeature(name()); + } + + private String name() { + return "Unsupported ABI: " + name; + } + + @Override + public void methodTypeMatchAssignment(int savedValueMask, MethodType methodType, AssignedLocation[] assignments, AssignedLocation[] returnAssignment, FunctionDescriptor fd, + Linker.Option... options) { + fail(); + } + + @Override + public NativeEntryPointInfo makeEntrypoint(FunctionDescriptor desc, Linker.Option... options) { + return fail(); + } + + @Override + public AssignedLocation[] toMemoryAssignment(VMStorage[] moves, boolean forReturn) { + return fail(); + } + + @Override + public Adaptation[] adaptArguments(NativeEntryPointInfo nep) { + return fail(); + } + + @Override + public void checkLibrarySupport() { + fail(); + } + } + + private abstract static class X86_64 extends AbiUtils { + protected static Stream argMoveBindingsStream(CallingSequence callingSequence) { + return callingSequence.argumentBindings() + .filter(Binding.VMStore.class::isInstance) + .map(Binding.VMStore.class::cast); + } + + protected static Stream retMoveBindingsStream(CallingSequence callingSequence) { + return callingSequence.returnBindings().stream() + .filter(Binding.VMLoad.class::isInstance) + .map(Binding.VMLoad.class::cast); + } + + protected static Binding.VMLoad[] retMoveBindings(CallingSequence callingSequence) { + return retMoveBindingsStream(callingSequence).toArray(Binding.VMLoad[]::new); + } + + protected VMStorage[] toStorageArray(Binding.Move[] moves) { + return Arrays.stream(moves).map(Binding.Move::storage).toArray(VMStorage[]::new); + } + + protected abstract CallingSequence makeCallingSequence(MethodType type, FunctionDescriptor desc, boolean forUpcall, LinkerOptions options); + + protected boolean typeMatchRegister(AMD64 target, Class type, Register register, boolean isVararg) { + Register.RegisterCategory rc = register.getRegisterCategory(); + PlatformKind kind = target.getPlatformKind(JavaKind.fromJavaClass(type)); + return target.canStoreValue(rc, kind); + } + + @Override + public void methodTypeMatchAssignment(int savedValueMask, MethodType methodType, AssignedLocation[] assignments, AssignedLocation[] returnAssignment, FunctionDescriptor fd, + Linker.Option... options) { + if (!SubstrateUtil.assertionsEnabled()) { + return; + } + + int firstActualArgument = 0; + if (methodType.parameterType(firstActualArgument++) != long.class) { + throw new AssertionError("Address expected as first param: " + methodType); + } + if (returnAssignment != null && methodType.parameterType(firstActualArgument++) != long.class) { + throw new AssertionError("Return buffer address expected: " + methodType); + } + if (savedValueMask != 0 && methodType.parameterType(firstActualArgument++) != long.class) { + throw new AssertionError("Capture buffer address expected: " + methodType); + } + + /* + * This assertion can fail if, the entrypoint is supposed to capture the call state, but + * the provided mask is 0, i.e. a capture call state option is provided but with an + * empty capture list. + */ + /* + * TODO: check for capture more uniformly; either always rely on the presence of the + * option (even if empty), or on the non-zeroness of the mask. Note that part of this + * mismatch might also be coming from JDK code, in which case we cannot do much. + */ + assert firstActualArgument + assignments.length == methodType.parameterCount() : assignments.length + " " + methodType.parameterCount(); + AMD64 target = (AMD64) ImageSingletons.lookup(SubstrateTargetDescription.class).arch; + LinkerOptions optionsSet = LinkerOptions.forDowncall(fd, options); + + for (int i = 0; i < assignments.length; ++i) { + var type = methodType.parameterType(firstActualArgument + i); + var assignment = assignments[i]; + assert !assignment.assignsToRegister() || + typeMatchRegister(target, type, assignment.register(), + optionsSet.isVarargsIndex(i)) : "Cannot put %s in %s.\nDescriptor & options: %s %s\nAssignment: %s\nMethod type (placeholders: %d): %s" + .formatted(type, assignment.register(), fd, Arrays.toString(options), Arrays.toString(assignments), firstActualArgument, methodType); + } + + assert returnAssignment == null || methodType.returnType().equals(void.class); + } + + @Override + public NativeEntryPointInfo makeEntrypoint(FunctionDescriptor desc, Linker.Option... options) { + // Linker.downcallHandle implemented in + // AbstractLinker.downcallHandle + + // AbstractLinker.downcallHandle0 + LinkerOptions optionSet = LinkerOptions.forDowncall(desc, options); + MethodType type = desc.toMethodType(); + + /* OS SPECIFIC BEGINS */ + // AbstractLinker.arrangeDowncall implemented in + // SysVx64Linker.arrangeDowncall or Windowsx64Linker.arrangeDowncall + + // CallArranger.arrangeDowncall + var callingSequence = makeCallingSequence(type, desc, false, optionSet); + /* OS SPECIFIC ENDS */ + + // DowncallLinker.getBoundMethodHandle + var argMoves = toStorageArray(argMoveBindingsStream(callingSequence).toArray(Binding.VMStore[]::new)); + var returnMoves = toStorageArray(retMoveBindings(callingSequence)); + var boundaryType = callingSequence.calleeMethodType(); + var needsReturnBuffer = callingSequence.needsReturnBuffer(); + + // NativeEntrypoint.make + var parametersAssignment = toMemoryAssignment(argMoves, false); + var returnBuffering = needsReturnBuffer ? toMemoryAssignment(returnMoves, true) : null; + AbiUtils.singleton().methodTypeMatchAssignment(callingSequence.capturedStateMask(), boundaryType, parametersAssignment, returnBuffering, desc, options); + return NativeEntryPointInfo.make(argMoves, returnMoves, boundaryType, needsReturnBuffer, callingSequence.capturedStateMask(), callingSequence.needsTransition()); + } + + @Override + public AssignedLocation[] toMemoryAssignment(VMStorage[] argMoves, boolean forReturn) { + int size = 0; + for (VMStorage move : argMoves) { + if (move.type() != X86_64Architecture.StorageType.PLACEHOLDER) { + // Placeholders are ignored; they will be handled further down the line + ++size; + } else { + // Placeholders are expected to be prefix arguments + assert size == 0 : "Placeholders are expected to be prefix arguments"; + } + + if (move.type() == X86_64Architecture.StorageType.X87) { + throw unsupportedFeature("Unsupported register kind: X87"); + } else if (move.type() == X86_64Architecture.StorageType.STACK && forReturn) { + throw unsupportedFeature("Unsupported register kind for return: STACK"); + } + } + + AssignedLocation[] storages = new AssignedLocation[size]; + int i = 0; + for (VMStorage move : argMoves) { + if (move.type() != X86_64Architecture.StorageType.PLACEHOLDER) { + storages[i++] = switch (move.type()) { + case X86_64Architecture.StorageType.INTEGER -> { + Register reg = AMD64.cpuRegisters[move.indexOrOffset()]; + assert reg.name.equals(move.debugName()); + assert reg.getRegisterCategory().equals(AMD64.CPU); + yield AssignedLocation.forRegister(reg); + } + case X86_64Architecture.StorageType.VECTOR -> { + /* + * Only the first four xmm registers should ever be used; in particular, + * this means we never need to index in xmmRegistersAVX512 + */ + Register reg = AMD64.xmmRegistersSSE[move.indexOrOffset()]; + assert reg.name.equals(move.debugName()); + assert reg.getRegisterCategory().equals(AMD64.XMM); + yield AssignedLocation.forRegister(reg); + } + case X86_64Architecture.StorageType.STACK -> AssignedLocation.forStack(move.indexOrOffset()); + default -> throw unsupportedFeature("Unhandled VMStorage: " + move); + }; + } + } + assert i == storages.length; + + return storages; + } + } + + static final class SysV extends X86_64 { + @Override + protected CallingSequence makeCallingSequence(MethodType type, FunctionDescriptor desc, boolean forUpcall, LinkerOptions options) { + return jdk.internal.foreign.abi.x64.sysv.CallArranger.getBindings(type, desc, forUpcall, options).callingSequence(); + } + + @Override + @Platforms(Platform.HOSTED_ONLY.class) + public Adaptation[] adaptArguments(NativeEntryPointInfo nep) { + // No adaptations needed + return new Adaptation[nep.linkMethodType().parameterCount()]; + } + + @Override + @Platforms(Platform.HOSTED_ONLY.class) + public void checkLibrarySupport() { + String name = "SystemV (Linux AMD64)"; + VMError.guarantee(LibC.isSupported(), "Foreign functions feature requires LibC support on " + name); + } + }; + + static final class Win64 extends X86_64 { + @Override + protected CallingSequence makeCallingSequence(MethodType type, FunctionDescriptor desc, boolean forUpcall, LinkerOptions options) { + return jdk.internal.foreign.abi.x64.windows.CallArranger.getBindings(type, desc, forUpcall, options).callingSequence(); + } + + @Override + protected boolean typeMatchRegister(AMD64 target, Class type, Register register, boolean isVararg) { + Register.RegisterCategory rc = register.getRegisterCategory(); + JavaKind kind = JavaKind.fromJavaClass(type); + PlatformKind platformKind = target.getPlatformKind(kind); + return target.canStoreValue(rc, platformKind) || (isVararg && + register.getRegisterCategory().equals(AMD64.CPU) && (kind.equals(JavaKind.Float) || kind.equals(JavaKind.Double))); + } + + /** + * The Win64 ABI allows one mismatch between register and value type: When a variadic + * floating-point argument is among the first four parameters of the function, the argument + * should be passed in both the XMM and CPU register. + * + * This method is slightly cheating: technically, we only ever want to adapt the + * cpu-register-assigned copy of a "register assigned floating point vararg parameter" from + * floating-point to long. This method assumes that this case will be the only source + * assignments of float/double parameters to a cpu register. + */ + @Override + @Platforms(Platform.HOSTED_ONLY.class) + public Adaptation[] adaptArguments(NativeEntryPointInfo nep) { + /* + * This method also performs some sanity checks about the generated entrypoint. + */ + MethodType methodType = nep.linkMethodType(); + AssignedLocation[] assignments = nep.parametersAssignment(); + + int firstActualArgument = 0; + // Note that the following ifs do increment firstActualArgument ! + if (methodType.parameterType(firstActualArgument++) != long.class && SubstrateUtil.assertionsEnabled()) { + throw new AssertionError("Address expected as first param: " + methodType); + } + if (nep.returnsAssignment() != null && methodType.parameterType(firstActualArgument++) != long.class && SubstrateUtil.assertionsEnabled()) { + throw new AssertionError("Return buffer address expected: " + methodType); + } + if (nep.capturesCallState() && methodType.parameterType(firstActualArgument++) != long.class && SubstrateUtil.assertionsEnabled()) { + throw new AssertionError("Capture buffer address expected: " + methodType); + } + + assert firstActualArgument + assignments.length == methodType.parameterCount() : assignments.length + " " + methodType.parameterCount(); + AMD64 target = (AMD64) ImageSingletons.lookup(SubstrateTargetDescription.class).arch; + Adaptation[] adaptations = new Adaptation[methodType.parameterCount()]; + + for (int i = firstActualArgument; i < adaptations.length; ++i) { + Class type = methodType.parameterType(i); + AssignedLocation assignment = assignments[i - firstActualArgument]; + + if (assignment.assignsToRegister()) { + Register.RegisterCategory rc = assignment.register().getRegisterCategory(); + PlatformKind kind = target.getPlatformKind(JavaKind.fromJavaClass(type)); + if (!(target.canStoreValue(rc, kind))) { + assert rc.equals(AMD64.CPU) && (kind.equals(target.getPlatformKind(JavaKind.Float)) || kind.equals(target.getPlatformKind(JavaKind.Double))); + adaptations[i] = new Reinterpret(JavaKind.Long); + } + } + } + + return adaptations; + } + + @Override + @Platforms(Platform.HOSTED_ONLY.class) + public void checkLibrarySupport() { + String name = "Win64 (Windows AMD64)"; + VMError.guarantee(LibC.isSupported(), "Foreign functions feature requires LibC support on" + name); + VMError.guarantee(WindowsAPIs.isSupported(), "Foreign functions feature requires Windows APIs support on" + name); + } + }; +} diff --git a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/DowncallStubsHolder.java b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/DowncallStubsHolder.java new file mode 100644 index 000000000000..80405242b6f9 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/DowncallStubsHolder.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2023, 2023, 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.core.foreign; + +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +import com.oracle.svm.core.SubstrateUtil; +import com.oracle.svm.core.jdk.InternalVMMethod; + +import jdk.vm.ci.meta.ConstantPool; +import jdk.vm.ci.meta.JavaKind; +import jdk.vm.ci.meta.MetaAccessProvider; + +/** Downcall stubs will be synthesized in this class. */ +@InternalVMMethod +public final class DowncallStubsHolder { + @Platforms(Platform.HOSTED_ONLY.class) + public static ConstantPool getConstantPool(MetaAccessProvider metaAccess) { + return metaAccess.lookupJavaType(DowncallStubsHolder.class).getDeclaredConstructors()[0].getConstantPool(); + } + + /** + * Generate the name used by the stub associated to the provided {@link NativeEntryPointInfo}. + * + * Naming scheme: + * + *

+     *  downcall_(*)_[_]>
+     * 
+ */ + @Platforms(Platform.HOSTED_ONLY.class) + public static String stubName(NativeEntryPointInfo nep) { + StringBuilder builder = new StringBuilder("downcall_("); + for (var param : nep.nativeMethodType().parameterArray()) { + builder.append(JavaKind.fromJavaClass(param).getTypeChar()); + } + builder.append(")"); + builder.append(JavaKind.fromJavaClass(nep.nativeMethodType().returnType()).getTypeChar()); + + if (nep.returnsAssignment() != null) { + builder.append("_r"); + } + if (nep.capturesCallState()) { + builder.append("_c"); + } + if (nep.skipsTransition()) { + builder.append("_t"); + } + + StringBuilder assignmentsBuilder = new StringBuilder(); + for (var assignment : nep.parametersAssignment()) { + assignmentsBuilder.append(assignment); + } + + if (nep.returnsAssignment() != null) { + assignmentsBuilder.append('_'); + for (var assignment : nep.returnsAssignment()) { + assignmentsBuilder.append(assignment); + } + } + + builder.append('_'); + builder.append(SubstrateUtil.digest(assignmentsBuilder.toString())); + + return builder.toString(); + } + + private DowncallStubsHolder() { + } +} diff --git a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/ForeignFunctionsRuntime.java b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/ForeignFunctionsRuntime.java new file mode 100644 index 000000000000..7bc362a7bda1 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/ForeignFunctionsRuntime.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2023, 2023, 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.core.foreign; + +import org.graalvm.collections.EconomicMap; +import org.graalvm.compiler.api.replacements.Fold; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.c.function.CFunctionPointer; +import org.graalvm.nativeimage.c.function.CodePointer; +import org.graalvm.nativeimage.c.type.CIntPointer; +import org.graalvm.word.LocationIdentity; + +import com.oracle.svm.core.FunctionPointerHolder; +import com.oracle.svm.core.OS; +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.headers.LibC; +import com.oracle.svm.core.headers.WindowsAPIs; +import com.oracle.svm.core.snippets.SnippetRuntime; +import com.oracle.svm.core.snippets.SubstrateForeignCallTarget; +import com.oracle.svm.core.util.VMError; + +import jdk.internal.foreign.abi.CapturableState; + +public class ForeignFunctionsRuntime { + @Fold + public static ForeignFunctionsRuntime singleton() { + return ImageSingletons.lookup(ForeignFunctionsRuntime.class); + } + + private final EconomicMap stubs = EconomicMap.create(); + + public ForeignFunctionsRuntime() { + } + + @Platforms(Platform.HOSTED_ONLY.class) + public void addStubPointer(NativeEntryPointInfo nepi, CFunctionPointer ptr) { + VMError.guarantee(!stubs.containsKey(nepi), "Seems like multiple stubs were generated for " + nepi); + stubs.put(nepi, new FunctionPointerHolder(ptr)); + } + + /** + * We'd rather report the function descriptor rather 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. + */ + public CodePointer getStubPointer(NativeEntryPointInfo nep) { + FunctionPointerHolder pointer = stubs.get(nep); + if (pointer == null) { + throw new UnregisteredDowncallStubException(nep); + } else { + return pointer.functionPointer; + } + } + + @SuppressWarnings("serial") + public static class UnregisteredDowncallStubException extends RuntimeException { + private final NativeEntryPointInfo nep; + + UnregisteredDowncallStubException(NativeEntryPointInfo nep) { + super(generateMessage(nep)); + this.nep = nep; + } + + private static String generateMessage(NativeEntryPointInfo nep) { + return "Cannot perform downcall with leaf type " + nep.nativeMethodType() + " as it was not registered at compilation time."; + } + } + + /** + * Workaround for CapturableState.mask() being interruptible. + */ + @Fold + public static int getMask(CapturableState state) { + return state.mask(); + } + + @Fold + public static boolean isWindows() { + return OS.WINDOWS.isCurrent(); + } + + /** + * Note that the states must be captured in the same order as in the JDK: GET_LAST_ERROR, + * WSA_GET_LAST_ERROR, ERRNO. + * + * Violation of the assertions should have already been caught in + * {@link AbiUtils#checkLibrarySupport()}, which is called when registering the feature. + */ + @Uninterruptible(reason = "Interruptions might change call state.") + @SubstrateForeignCallTarget(stubCallingConvention = false, fullyUninterruptible = true) + public static void captureCallState(int statesToCapture, CIntPointer captureBuffer) { + assert statesToCapture != 0; + assert captureBuffer.isNonNull(); + + int i = 0; + if (isWindows()) { + assert WindowsAPIs.isSupported() : "Windows APIs should be supported on Windows OS"; + + if ((statesToCapture & getMask(CapturableState.GET_LAST_ERROR)) != 0) { + captureBuffer.write(i, WindowsAPIs.getLastError()); + } + ++i; + if ((statesToCapture & getMask(CapturableState.WSA_GET_LAST_ERROR)) != 0) { + captureBuffer.write(i, WindowsAPIs.wsaGetLastError()); + } + ++i; + } + + assert LibC.isSupported() : "LibC should always be supported"; + if ((statesToCapture & getMask(CapturableState.ERRNO)) != 0) { + captureBuffer.write(i, LibC.errno()); + } + ++i; + } + + public static final SnippetRuntime.SubstrateForeignCallDescriptor CAPTURE_CALL_STATE = SnippetRuntime.findForeignCall(ForeignFunctionsRuntime.class, + "captureCallState", false, LocationIdentity.any()); +} diff --git a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/LinkToNativeSupportImpl.java b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/LinkToNativeSupportImpl.java new file mode 100644 index 000000000000..a96af26671d0 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/LinkToNativeSupportImpl.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2023, 2023, 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.core.foreign; + +import org.graalvm.nativeimage.c.function.CFunctionPointer; +import org.graalvm.word.WordFactory; + +import com.oracle.svm.core.LinkToNativeSupport; +import com.oracle.svm.core.c.InvokeJavaFunctionPointer; + +public final class LinkToNativeSupportImpl implements LinkToNativeSupport { + /** + * Arguments follow the same structure as described in {@link NativeEntryPointInfo}, with an + * additional {@link Target_jdk_internal_foreign_abi_NativeEntryPoint} (NEP) as the last + * argument, i.e. + * + *
+     * {@code
+     *      [return buffer address]  [capture state address]   ... 
+     * }
+     * 
+ * + * where s are the arguments which end up being passed to the C native function + */ + @Override + public Object linkToNative(Object... args) throws Throwable { + Target_jdk_internal_foreign_abi_NativeEntryPoint nep = (Target_jdk_internal_foreign_abi_NativeEntryPoint) args[args.length - 1]; + StubPointer pointer = WordFactory.pointer(nep.downcallStubAddress); + /* The nep argument will be dropped in the invoked function */ + return pointer.invoke(args); + } +} + +interface StubPointer extends CFunctionPointer { + @InvokeJavaFunctionPointer + Object invoke(Object... args); +} diff --git a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/NativeEntryPointInfo.java b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/NativeEntryPointInfo.java new file mode 100644 index 000000000000..837c96c7dda1 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/NativeEntryPointInfo.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2023, 2023, 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.core.foreign; + +import java.lang.invoke.MethodType; +import java.util.Arrays; +import java.util.Objects; + +import com.oracle.svm.core.graal.code.AssignedLocation; + +import jdk.internal.foreign.abi.ABIDescriptor; +import jdk.internal.foreign.abi.VMStorage; + +/** + * Carries information about an entrypoint for foreign function calls. + * {@link ForeignFunctionsRuntime#getStubPointer} allows getting the associated function pointer at + * runtime (if it exists). + *

+ * {@link NativeEntryPointInfo#linkMethodType} is of the form (<>: argument; []: optional argument) + * + *

+ * {@code
+ *      [return buffer address]  [capture state address]   ...
+ * }
+ * 
+ * + * where {@code }s are the arguments which end up passed to the C native function. + */ +public final class NativeEntryPointInfo { + private final MethodType methodType; + private final AssignedLocation[] parameterAssignments; + private final AssignedLocation[] returnBuffering; + private final boolean capturesState; + private final boolean needsTransition; + + private NativeEntryPointInfo(MethodType methodType, AssignedLocation[] cc, AssignedLocation[] returnBuffering, boolean capturesState, boolean needsTransition) { + this.methodType = methodType; + this.parameterAssignments = cc; + this.returnBuffering = returnBuffering; + this.capturesState = capturesState; + this.needsTransition = needsTransition; + } + + public static NativeEntryPointInfo make( + VMStorage[] argMoves, VMStorage[] returnMoves, + MethodType methodType, + boolean needsReturnBuffer, + int capturedStateMask, + boolean needsTransition) { + if (returnMoves.length > 1 != needsReturnBuffer) { + throw new AssertionError("Multiple register return, but needsReturnBuffer was false"); + } + + AssignedLocation[] parametersAssignment = AbiUtils.singleton().toMemoryAssignment(argMoves, false); + AssignedLocation[] returnBuffering = needsReturnBuffer ? AbiUtils.singleton().toMemoryAssignment(returnMoves, true) : null; + return new NativeEntryPointInfo(methodType, parametersAssignment, returnBuffering, capturedStateMask != 0, needsTransition); + } + + public static Target_jdk_internal_foreign_abi_NativeEntryPoint makeEntryPoint( + @SuppressWarnings("unused") ABIDescriptor ignoreAbi, + VMStorage[] argMoves, VMStorage[] returnMoves, + MethodType methodType, + boolean needsReturnBuffer, + int capturedStateMask, + boolean needsTransition) { + var info = make(argMoves, returnMoves, methodType, needsReturnBuffer, capturedStateMask, needsTransition); + long addr = ForeignFunctionsRuntime.singleton().getStubPointer(info).rawValue(); + return new Target_jdk_internal_foreign_abi_NativeEntryPoint(info.linkMethodType(), addr, capturedStateMask); + } + + public int callAddressIndex() { + return needsReturnBuffer() ? 1 : 0; + } + + public int captureAddressIndex() { + if (!capturesCallState()) { + throw new IllegalArgumentException(this + " doesn't have a capture state argument"); + } + return callAddressIndex() + 1; + } + + /** + * Method type without any of the special arguments. + */ + public MethodType nativeMethodType() { + if (capturesCallState()) { + return this.methodType.dropParameterTypes(0, captureAddressIndex() + 1); + } else { + return this.methodType.dropParameterTypes(0, callAddressIndex() + 1); + } + } + + /** + * Method type with all special arguments. + */ + public MethodType linkMethodType() { + return this.methodType; + } + + public boolean needsReturnBuffer() { + return this.returnBuffering != null; + } + + public boolean capturesCallState() { + return capturesState; + } + + public AssignedLocation[] parametersAssignment() { + assert parameterAssignments.length == this.nativeMethodType().parameterCount() : Arrays.toString(parameterAssignments) + " ; " + nativeMethodType(); + return parameterAssignments; + } + + public AssignedLocation[] returnsAssignment() { + return returnBuffering; + } + + public boolean skipsTransition() { + return !needsTransition; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + NativeEntryPointInfo that = (NativeEntryPointInfo) o; + return capturesState == that.capturesState && needsTransition == that.needsTransition && Objects.equals(methodType, that.methodType) && + Arrays.equals(parameterAssignments, that.parameterAssignments) && Arrays.equals(returnBuffering, that.returnBuffering); + } + + @Override + public int hashCode() { + return Objects.hash(methodType, capturesState, needsTransition, Arrays.hashCode(parameterAssignments), Arrays.hashCode(returnBuffering)); + } +} diff --git a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_java_lang_foreign_SymbolLookup.java b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_java_lang_foreign_SymbolLookup.java new file mode 100644 index 000000000000..7b24231605d7 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_java_lang_foreign_SymbolLookup.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2023, 2023, 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.core.foreign; + +import static com.oracle.svm.core.util.VMError.unsupportedFeature; + +import java.lang.foreign.Arena; +import java.lang.foreign.Linker; +import java.lang.foreign.SymbolLookup; +import java.util.function.BiFunction; + +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; + +import jdk.internal.loader.NativeLibrary; +import jdk.internal.loader.RawNativeLibraries; + +/** + * Interface allowing looking up symbols from libraries. Currently, only + * {@link java.lang.foreign.SymbolLookup#loaderLookup} is supported. Support for the other lookups + * ({@link java.lang.foreign.SymbolLookup#libraryLookup} and {@link Linker#defaultLookup()}) will + * come later. + *

+ * Note that {@link java.lang.foreign.SymbolLookup#loaderLookup} will have a behavior which is + * slightly different from the one in the JDK: like loadLibrary, the lookup is classloader agnostic, + * which means that loading a library in a classloader and then looking it up from another one will + * succeed. See + * {@link com.oracle.svm.core.jdk.Target_java_lang_ClassLoader#loadLibrary(java.lang.Class, java.lang.String)} + */ +@TargetClass(className = "java.lang.foreign.SymbolLookup") +public final class Target_java_lang_foreign_SymbolLookup { + + @Substitute + private static SymbolLookup libraryLookup(Z libDesc, BiFunction loadLibraryFunc, Arena libArena) { + throw unsupportedFeature("Library/system lookups are not supported."); + } +} diff --git a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_foreign_SystemLookup.java b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_foreign_SystemLookup.java new file mode 100644 index 000000000000..c722f87c47aa --- /dev/null +++ b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_foreign_SystemLookup.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2023, 2023, 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.core.foreign; + +import static com.oracle.svm.core.util.VMError.unsupportedFeature; + +import com.oracle.svm.core.annotate.Delete; +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; + +/** + * System lookups allow to search for symbols in a fixed set of OS-dependent, standard, "curated" + * libraries. The provided libraries are not really defined in the documentation. + * + * System lookups are currently unsupported, but this would be possible and might be useful to do + * so. There would be two issues to solve; other than that, the JDK's implementation can be reused: + *

    + *
  • The library path(s): there might not be a JDK on the machine running the native image (on + * linux64, the loaded libraries are {libc, libm, libdl})
  • + *
  • Library loading: libraries are currently loaded in "the global scope", which is not exactly + * the correct behavior
  • + *
+ */ +@TargetClass(className = "jdk.internal.foreign.SystemLookup") +@Substitute +public final class Target_jdk_internal_foreign_SystemLookup { + @Substitute + public static Target_jdk_internal_foreign_SystemLookup getInstance() { + throw unsupportedFeature("Default lookup is not supported."); + } +} + +@TargetClass(className = "jdk.internal.foreign.SystemLookup", innerClass = "WindowsFallbackSymbols") +@Delete +final class Target_jdk_internal_foreign_SystemLookup_WindowsFallbackSymbols { +} diff --git a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_foreign_abi_NativeEntryPoint.java b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_foreign_abi_NativeEntryPoint.java new file mode 100644 index 000000000000..75e848d8e771 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_foreign_abi_NativeEntryPoint.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2023, 2023, 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.core.foreign; + +import java.lang.invoke.MethodType; + +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; + +import jdk.internal.foreign.abi.ABIDescriptor; +import jdk.internal.foreign.abi.VMStorage; + +/** + * Packs the address of a {@link com.oracle.svm.hosted.foreign.DowncallStub} with some extra + * information. + */ +@TargetClass(className = "jdk.internal.foreign.abi.NativeEntryPoint") +@Substitute +public final class Target_jdk_internal_foreign_abi_NativeEntryPoint { + + private final MethodType methodType; + public final long downcallStubAddress; + public final int captureMask; + + Target_jdk_internal_foreign_abi_NativeEntryPoint(MethodType methodType, long downcallStubAddress, int captureMask) { + this.methodType = methodType; + this.downcallStubAddress = downcallStubAddress; + this.captureMask = captureMask; + } + + @Substitute + public static Target_jdk_internal_foreign_abi_NativeEntryPoint make(ABIDescriptor abi, + VMStorage[] argMoves, VMStorage[] returnMoves, + MethodType methodType, + boolean needsReturnBuffer, + int capturedStateMask, + boolean needsTransition) { + return NativeEntryPointInfo.makeEntryPoint(abi, argMoves, returnMoves, methodType, needsReturnBuffer, capturedStateMask, needsTransition); + } + + @Substitute + public MethodType type() { + return methodType; + } +} diff --git a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_misc_ScopedMemoryAccess.java b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_misc_ScopedMemoryAccess.java new file mode 100644 index 000000000000..22773d118b17 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_misc_ScopedMemoryAccess.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2023, 2023, 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.core.foreign; + +import static com.oracle.svm.core.util.VMError.unsupportedFeature; + +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; + +import jdk.internal.foreign.MemorySessionImpl; + +/** + * Gracefully handle unsupported features. + *

+ * It seems like this could be easily supported once thread-local handshakes are supported. + */ +@TargetClass(className = "jdk.internal.misc.ScopedMemoryAccess") +public final class Target_jdk_internal_misc_ScopedMemoryAccess { + @Substitute + static void registerNatives() { + } + + /** + * Performs a thread-local handshake + * + *

+     * {@code
+     * JVM_ENTRY(jboolean, ScopedMemoryAccess_closeScope(JNIEnv *env, jobject receiver, jobject deopt, jobject exception))
+     *   CloseScopedMemoryClosure cl(deopt, exception);
+     *   Handshake::execute(&cl);
+     *   return !cl._found;
+     * JVM_END
+     * }
+     * 
+ * + * CloseScopedMemoryClosure can be summarised as follows: Each thread checks the + * last max_critical_stack_depth (fixed to 10) frames of its own stack trace. If it + * contains any @Scoped-annotated method called on the sessions being freed, it + * sets _found to true. + *

+ * See scopedMemoryAccess.cpp in HotSpot. + *

+ * As one might notice, what is not supported is not creating shared arenas, but closing them. + */ + @Substitute + boolean closeScope0(MemorySessionImpl session) { + throw unsupportedFeature("Arena.ofShared is not supported."); + } +} diff --git a/substratevm/src/com.oracle.svm.core.graal.aarch64/src/com/oracle/svm/core/graal/aarch64/SubstrateAArch64RegisterConfig.java b/substratevm/src/com.oracle.svm.core.graal.aarch64/src/com/oracle/svm/core/graal/aarch64/SubstrateAArch64RegisterConfig.java index 501663512d6e..22291ee19295 100755 --- a/substratevm/src/com.oracle.svm.core.graal.aarch64/src/com/oracle/svm/core/graal/aarch64/SubstrateAArch64RegisterConfig.java +++ b/substratevm/src/com.oracle.svm.core.graal.aarch64/src/com/oracle/svm/core/graal/aarch64/SubstrateAArch64RegisterConfig.java @@ -25,6 +25,7 @@ package com.oracle.svm.core.graal.aarch64; import static com.oracle.svm.core.util.VMError.shouldNotReachHereUnexpectedInput; +import static com.oracle.svm.core.util.VMError.unsupportedFeature; import static jdk.vm.ci.aarch64.AArch64.allRegisters; import static jdk.vm.ci.aarch64.AArch64.r0; import static jdk.vm.ci.aarch64.AArch64.r1; @@ -277,6 +278,10 @@ private static int darwinNativeStackParameterAssignment(ValueKindFactory valu @Override public CallingConvention getCallingConvention(Type t, JavaType returnType, JavaType[] parameterTypes, ValueKindFactory valueKindFactory) { SubstrateCallingConventionType type = (SubstrateCallingConventionType) t; + if (type.fixedParameterAssignment != null || type.returnSaving != null) { + throw unsupportedFeature("Fixed parameter assignments and return saving are not yet supported on this platform."); + } + boolean isEntryPoint = type.nativeABI() && !type.outgoing; AllocatableValue[] locations = new AllocatableValue[parameterTypes.length]; diff --git a/substratevm/src/com.oracle.svm.core.graal.amd64/src/com/oracle/svm/core/graal/amd64/SubstrateAMD64Backend.java b/substratevm/src/com.oracle.svm.core.graal.amd64/src/com/oracle/svm/core/graal/amd64/SubstrateAMD64Backend.java index e3a4192f0495..2040937a82ee 100644 --- a/substratevm/src/com.oracle.svm.core.graal.amd64/src/com/oracle/svm/core/graal/amd64/SubstrateAMD64Backend.java +++ b/substratevm/src/com.oracle.svm.core.graal.amd64/src/com/oracle/svm/core/graal/amd64/SubstrateAMD64Backend.java @@ -27,6 +27,8 @@ import static com.oracle.svm.core.graal.code.SubstrateBackend.SubstrateMarkId.PROLOGUE_DECD_RSP; import static com.oracle.svm.core.graal.code.SubstrateBackend.SubstrateMarkId.PROLOGUE_END; import static com.oracle.svm.core.util.VMError.shouldNotReachHere; +import static com.oracle.svm.core.util.VMError.unsupportedFeature; +import static jdk.vm.ci.amd64.AMD64.r10; import static jdk.vm.ci.amd64.AMD64.rax; import static jdk.vm.ci.amd64.AMD64.rbp; import static jdk.vm.ci.amd64.AMD64.rsp; @@ -140,6 +142,7 @@ import com.oracle.svm.core.cpufeature.Stubs; import com.oracle.svm.core.deopt.Deoptimizer; import com.oracle.svm.core.graal.RuntimeCompilation; +import com.oracle.svm.core.graal.code.AssignedLocation; import com.oracle.svm.core.graal.code.PatchConsumerFactory; import com.oracle.svm.core.graal.code.StubCallingConvention; import com.oracle.svm.core.graal.code.SubstrateBackend; @@ -850,6 +853,35 @@ protected void emitInvoke(LoweredCallTargetNode callTarget, Value[] parameters, } else { super.emitInvoke(callTarget, parameters, callState, result); } + + var cc = (SubstrateCallingConventionType) callTarget.callType(); + if (cc.returnSaving != null) { + // The pointer to the return buffer is passed as first argument + Value baseSaveLocation = parameters[0]; + // Could be any x86 scratch register + RegisterValue scratch = r10.asValue(parameters[0].getValueKind()); + gen.emitMove(scratch, baseSaveLocation); + long offset = 0; + for (AssignedLocation ret : cc.returnSaving) { + Value saveLocation = gen.getArithmetic().emitAdd(scratch, gen.emitJavaConstant(JavaConstant.forLong(offset)), false); + if (ret.assignsToStack()) { + throw unsupportedFeature("Return should never happen on stack."); + } + var register = ret.register(); + LIRKind kind; + // There might a better/more natural way to check this + if (register.getRegisterCategory().equals(AMD64.CPU)) { + kind = gen.getValueKind(JavaKind.Long); + offset += 8; + } else if (register.getRegisterCategory().equals(AMD64.XMM)) { + kind = gen.getValueKind(JavaKind.Double); + offset += 16; + } else { + throw unsupportedFeature("Cannot use register " + register + " for return."); + } + gen.getArithmetic().emitStore(kind, saveLocation, gen.emitReadRegister(register, kind), callState, MemoryOrderMode.PLAIN); + } + } } @Override diff --git a/substratevm/src/com.oracle.svm.core.graal.amd64/src/com/oracle/svm/core/graal/amd64/SubstrateAMD64RegisterConfig.java b/substratevm/src/com.oracle.svm.core.graal.amd64/src/com/oracle/svm/core/graal/amd64/SubstrateAMD64RegisterConfig.java index 80b5dfb40dae..ddaee0d1193b 100644 --- a/substratevm/src/com.oracle.svm.core.graal.amd64/src/com/oracle/svm/core/graal/amd64/SubstrateAMD64RegisterConfig.java +++ b/substratevm/src/com.oracle.svm.core.graal.amd64/src/com/oracle/svm/core/graal/amd64/SubstrateAMD64RegisterConfig.java @@ -24,6 +24,7 @@ */ package com.oracle.svm.core.graal.amd64; +import static com.oracle.svm.core.util.VMError.unsupportedFeature; import static jdk.vm.ci.amd64.AMD64.k1; import static jdk.vm.ci.amd64.AMD64.k2; import static jdk.vm.ci.amd64.AMD64.k3; @@ -63,6 +64,8 @@ import static jdk.vm.ci.amd64.AMD64.xmm9; import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; import org.graalvm.nativeimage.Platform; @@ -70,6 +73,7 @@ import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.config.ObjectLayout; import com.oracle.svm.core.graal.RuntimeCompilation; +import com.oracle.svm.core.graal.code.AssignedLocation; import com.oracle.svm.core.graal.code.SubstrateCallingConvention; import com.oracle.svm.core.graal.code.SubstrateCallingConventionKind; import com.oracle.svm.core.graal.code.SubstrateCallingConventionType; @@ -252,72 +256,140 @@ public CallingConvention getCallingConvention(Type t, JavaType returnType, JavaT SubstrateCallingConventionType type = (SubstrateCallingConventionType) t; boolean isEntryPoint = type.nativeABI() && !type.outgoing; - AllocatableValue[] locations = new AllocatableValue[parameterTypes.length]; - - int currentGeneral = 0; - int currentXMM = 0; - /* * We have to reserve a slot between return address and outgoing parameters for the deopt * frame handle. Exception: calls to native methods. */ int currentStackOffset = (type.nativeABI() ? nativeParamsStackOffset : target.wordSize); + AllocatableValue[] locations = new AllocatableValue[parameterTypes.length]; + int firstActualArgument = 0; + JavaKind[] kinds = new JavaKind[locations.length]; - for (int i = 0; i < parameterTypes.length; i++) { - JavaKind kind = ObjectLayout.getCallSignatureKind(isEntryPoint, (ResolvedJavaType) parameterTypes[i], metaAccess, target); - kinds[i] = kind; - - if (type.nativeABI() && Platform.includedIn(Platform.WINDOWS.class)) { - // Strictly positional: float parameters consume a general register and vice versa - currentGeneral = i; - currentXMM = i; + + if (type.returnSaving != null) { + /* + * returnSaving implies an additional (prefix) parameter pointing to the buffer to use + * for saving. We pretend it is the first stack argument to the function: this means the + * function will safely ignore it, but we will be able to access it right after the call + * concludes. + */ + firstActualArgument = 1; + /* + * The actual allocation is done after allocating all other parameters, as it must be + * done last (it needs to be placed first on the stack, and this is done in reverse + * order) + */ + } + + if (type.fixedParameterAssignment == null) { + int currentGeneral = 0; + int currentXMM = 0; + + for (int i = firstActualArgument; i < parameterTypes.length; i++) { + JavaKind kind = ObjectLayout.getCallSignatureKind(isEntryPoint, (ResolvedJavaType) parameterTypes[i], metaAccess, target); + kinds[i] = kind; + + if (type.nativeABI() && Platform.includedIn(Platform.WINDOWS.class)) { + // Strictly positional: float parameters consume a general register and vice + // versa + currentGeneral = i; + currentXMM = i; + } + Register register = null; + if (type.kind == SubstrateCallingConventionKind.ForwardReturnValue) { + VMError.guarantee(i == 0, "Method with calling convention ForwardReturnValue cannot have more than one parameter"); + register = getReturnRegister(kind); + } else { + switch (kind) { + case Byte: + case Boolean: + case Short: + case Char: + case Int: + case Long: + case Object: + RegisterArray registers = type.nativeABI() ? nativeGeneralParameterRegs : javaGeneralParameterRegs; + if (currentGeneral < registers.size()) { + register = registers.get(currentGeneral++); + } + break; + case Float: + case Double: + if (currentXMM < xmmParameterRegs.size()) { + register = xmmParameterRegs.get(currentXMM++); + } + break; + default: + throw VMError.shouldNotReachHereUnexpectedInput(kind); // ExcludeFromJacocoGeneratedReport + } + } + + /* + * The AMD64 ABI does not specify whether subword (i.e., boolean, byte, char, short) + * values should be extended to 32 bits. Hence, for incoming native calls, we can + * only assume the bits sizes as specified in the standard. + * + * Since within the graal compiler subwords are already extended to 32 bits, we save + * extended values in outgoing calls. Note that some compilers also expect arguments + * to be extended + * (https://reviews.llvm.org/rG1db979bae832563efde2523bb36ddabad43293d8). + */ + ValueKind paramValueKind = valueKindFactory.getValueKind(isEntryPoint ? kind : kind.getStackKind()); + if (register != null) { + locations[i] = register.asValue(paramValueKind); + } else { + locations[i] = StackSlot.get(paramValueKind, currentStackOffset, !type.outgoing); + currentStackOffset += Math.max(paramValueKind.getPlatformKind().getSizeInBytes(), target.wordSize); + } } - Register register = null; - if (type.kind == SubstrateCallingConventionKind.ForwardReturnValue) { - VMError.guarantee(i == 0, "Method with calling convention ForwardReturnValue cannot have more than one parameter"); - register = getReturnRegister(kind); - } else { - switch (kind) { - case Byte: - case Boolean: - case Short: - case Char: - case Int: - case Long: - case Object: - RegisterArray registers = type.nativeABI() ? nativeGeneralParameterRegs : javaGeneralParameterRegs; - if (currentGeneral < registers.size()) { - register = registers.get(currentGeneral++); - } - break; - case Float: - case Double: - if (currentXMM < xmmParameterRegs.size()) { - register = xmmParameterRegs.get(currentXMM++); - } - break; - default: - throw VMError.shouldNotReachHereUnexpectedInput(kind); // ExcludeFromJacocoGeneratedReport + } else { + final int baseStackOffset = currentStackOffset; + Set usedRegisters = new HashSet<>(); + VMError.guarantee(parameterTypes.length == type.fixedParameterAssignment.length + firstActualArgument, + "Parameters/assignments size mismatch."); + + for (int i = firstActualArgument; i < parameterTypes.length; i++) { + JavaKind kind = ObjectLayout.getCallSignatureKind(isEntryPoint, (ResolvedJavaType) parameterTypes[i], metaAccess, target); + kinds[i] = kind; + + ValueKind paramValueKind = valueKindFactory.getValueKind(isEntryPoint ? kind : kind.getStackKind()); + + int actualArgumentIndex = i - firstActualArgument; + AssignedLocation storage = type.fixedParameterAssignment[actualArgumentIndex]; + if (storage.assignsToRegister()) { + if (!kind.isNumericInteger() && !kind.isNumericFloat()) { + throw unsupportedFeature("Unsupported storage/kind pair - Storage: " + storage + " ; Kind: " + kind); + } + Register reg = storage.register(); + VMError.guarantee(target.arch.canStoreValue(reg.getRegisterCategory(), + paramValueKind.getPlatformKind()), "Cannot assign value to register."); + locations[i] = reg.asValue(paramValueKind); + VMError.guarantee(!usedRegisters.contains(reg), + "Register was already used."); + usedRegisters.add(reg); + } else { + /* + * There should be no "empty spaces" between arguments on the stack. This + * assertion checks so, but assumes that stack arguments are encountered + * "in order". This assumption might not be necessary, but simplifies the check + * tremendously. + */ + VMError.guarantee(currentStackOffset == baseStackOffset + storage.stackOffset(), + "Potential stack ``completeness'' violation."); + locations[i] = StackSlot.get(paramValueKind, currentStackOffset, !type.outgoing); + currentStackOffset += Math.max(paramValueKind.getPlatformKind().getSizeInBytes(), target.wordSize); } } + } - /* - * The AMD64 ABI does not specify whether subword (i.e., boolean, byte, char, short) - * values should be extended to 32 bits. Hence, for incoming native calls, we can only - * assume the bits sizes as specified in the standard. - * - * Since within the graal compiler subwords are already extended to 32 bits, we save - * extended values in outgoing calls. Note that some compilers also expect arguments to - * be extended (https://reviews.llvm.org/rG1db979bae832563efde2523bb36ddabad43293d8). - */ + if (type.returnSaving != null) { + assert type.fixedParameterAssignment == null || type.fixedParameterAssignment.length + 1 == locations.length; + assert parameterTypes[0].getJavaKind() == JavaKind.Long; + JavaKind kind = ObjectLayout.getCallSignatureKind(isEntryPoint, (ResolvedJavaType) parameterTypes[0], metaAccess, target); ValueKind paramValueKind = valueKindFactory.getValueKind(isEntryPoint ? kind : kind.getStackKind()); - if (register != null) { - locations[i] = register.asValue(paramValueKind); - } else { - locations[i] = StackSlot.get(paramValueKind, currentStackOffset, !type.outgoing); - currentStackOffset += Math.max(paramValueKind.getPlatformKind().getSizeInBytes(), target.wordSize); - } + locations[0] = StackSlot.get(paramValueKind, currentStackOffset, !type.outgoing); + currentStackOffset += paramValueKind.getPlatformKind().getSizeInBytes(); } JavaKind returnKind = returnType == null ? JavaKind.Void : ObjectLayout.getCallSignatureKind(isEntryPoint, (ResolvedJavaType) returnType, metaAccess, target); diff --git a/substratevm/src/com.oracle.svm.core.graal.riscv64/src/com/oracle/svm/core/graal/riscv64/SubstrateRISCV64RegisterConfig.java b/substratevm/src/com.oracle.svm.core.graal.riscv64/src/com/oracle/svm/core/graal/riscv64/SubstrateRISCV64RegisterConfig.java index d964c88473a4..fbd838f468c8 100644 --- a/substratevm/src/com.oracle.svm.core.graal.riscv64/src/com/oracle/svm/core/graal/riscv64/SubstrateRISCV64RegisterConfig.java +++ b/substratevm/src/com.oracle.svm.core.graal.riscv64/src/com/oracle/svm/core/graal/riscv64/SubstrateRISCV64RegisterConfig.java @@ -26,6 +26,7 @@ import static com.oracle.svm.core.util.VMError.intentionallyUnimplemented; import static com.oracle.svm.core.util.VMError.shouldNotReachHereUnexpectedInput; +import static com.oracle.svm.core.util.VMError.unsupportedFeature; import static org.graalvm.compiler.core.riscv64.ShadowedRISCV64.allRegisters; import static org.graalvm.compiler.core.riscv64.ShadowedRISCV64.f10; import static org.graalvm.compiler.core.riscv64.ShadowedRISCV64.f11; @@ -218,6 +219,10 @@ private int javaStackParameterAssignment(ValueKindFactory valueKindFactory, A @Override public CallingConvention getCallingConvention(Type t, JavaType returnType, JavaType[] parameterTypes, ValueKindFactory valueKindFactory) { SubstrateCallingConventionType type = (SubstrateCallingConventionType) t; + if (type.fixedParameterAssignment != null || type.returnSaving != null) { + throw unsupportedFeature("Fixed parameter assignments and return saving are not yet supported on this platform."); + } + boolean isEntryPoint = type.nativeABI() && !type.outgoing; AllocatableValue[] locations = new AllocatableValue[parameterTypes.length]; diff --git a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsAPIsSupport.java b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsAPIsSupport.java new file mode 100644 index 000000000000..9550d0ce0d2e --- /dev/null +++ b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsAPIsSupport.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2023, 2023, 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.core.windows; + +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; +import com.oracle.svm.core.windows.headers.WinBase; +import com.oracle.svm.core.windows.headers.WinSock; + +@AutomaticallyRegisteredImageSingleton(com.oracle.svm.core.headers.WindowsAPIsSupport.class) +public class WindowsAPIsSupport implements com.oracle.svm.core.headers.WindowsAPIsSupport { + @Override + @Uninterruptible(reason = Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public int getLastError() { + return WinBase.GetLastError(); + } + + @Override + @Uninterruptible(reason = Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public int wsaGetLastError() { + return WinSock.WSAGetLastError(); + } +} diff --git a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/headers/WinSock.java b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/headers/WinSock.java index cce844622d1a..cec0df5ed2ca 100644 --- a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/headers/WinSock.java +++ b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/headers/WinSock.java @@ -42,6 +42,9 @@ @Platforms(Platform.WINDOWS.class) public class WinSock { + @CFunction(transition = Transition.NO_TRANSITION) + public static native int WSAGetLastError(); + /** * Structure containing information about the WinSock implementation */ diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/LinkToNativeSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/LinkToNativeSupport.java new file mode 100644 index 000000000000..30498134dc18 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/LinkToNativeSupport.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2023, 2023, 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.core; + +import org.graalvm.compiler.api.replacements.Fold; +import org.graalvm.nativeimage.ImageSingletons; + +public interface LinkToNativeSupport { + @Fold + static boolean isAvailable() { + return ImageSingletons.contains(LinkToNativeSupport.class); + } + + @Fold + static LinkToNativeSupport singleton() { + return ImageSingletons.lookup(LinkToNativeSupport.class); + } + + Object linkToNative(Object... args) throws Throwable; +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java index 23e0de2ea169..7d2aa9eb158e 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java @@ -434,6 +434,8 @@ public static void updateMaxJavaStackTraceDepth(EconomicMap, Object @Option(help = "Export Invocation API symbols.", type = OptionType.User)// public static final HostedOptionKey JNIExportSymbols = new HostedOptionKey<>(true); + @Option(help = "Enable Foreign Functions support.") public static final HostedOptionKey ForeignFunctions = new HostedOptionKey<>(false); + @Option(help = "Alignment of AOT and JIT compiled code in bytes.")// public static final HostedOptionKey CodeAlignment = new HostedOptionKey<>(16); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFile.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFile.java index 049680e8e383..98dac61e3b0c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFile.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFile.java @@ -30,6 +30,7 @@ public enum ConfigurationFile { DYNAMIC_PROXY("proxy", true), RESOURCES("resource", true), JNI("jni", true), + FOREIGN("foreign", false), REFLECTION("reflect", true), SERIALIZATION("serialization", true), SERIALIZATION_DENY("serialization-deny", false), diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFiles.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFiles.java index be86da7c13a3..82f3774c9b25 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFiles.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFiles.java @@ -93,6 +93,12 @@ public static final class Options { @Option(help = "Resources describing program elements to be made accessible via JNI (see JNIConfigurationFiles).", type = OptionType.User)// public static final HostedOptionKey JNIConfigurationResources = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); + @Option(help = "Files describing stubs allowing foreign calls.", type = OptionType.User)// + @BundleMember(role = BundleMember.Role.Input)// + public static final HostedOptionKey ForeignConfigurationFiles = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); + @Option(help = "Resources describing stubs allowing foreign calls.", type = OptionType.User)// + public static final HostedOptionKey ForeignResources = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); + @Option(help = "Files describing predefined classes that can be loaded at runtime.", type = OptionType.User)// @BundleMember(role = BundleMember.Role.Input)// public static final HostedOptionKey PredefinedClassesConfigurationFiles = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/code/AssignedLocation.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/code/AssignedLocation.java new file mode 100644 index 000000000000..bb08caf1e4e1 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/code/AssignedLocation.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2023, 2023, 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.core.graal.code; + +import java.util.Objects; + +import jdk.vm.ci.code.Register; + +/** + * Represent a register or a stack offset. + */ +public final class AssignedLocation { + private static final int NONE = -1; + + private static boolean isValidOffset(int i) { + return i >= 0 || i == NONE; + } + + private void checkClassInvariant() { + if (!isValidOffset(this.stackOffset)) { + throw new IllegalStateException("Stack offset cannot be < 0 (and not NONE)."); + } + if ((this.register == null) == (this.stackOffset == NONE)) { + throw new IllegalStateException("Must assign to either a register or stack (but not both)."); + } + } + + private final Register register; + private final int stackOffset; + + private AssignedLocation(Register register, int stackOffset) { + this.register = register; + this.stackOffset = stackOffset; + checkClassInvariant(); + } + + public static AssignedLocation forRegister(Register register) { + return new AssignedLocation(register, NONE); + } + + public static AssignedLocation forStack(int offset) { + if (offset < 0) { + throw new IllegalArgumentException("Stack offset must be >= 0"); + } + return new AssignedLocation(null, offset); + } + + public boolean assignsToRegister() { + return register != null; + } + + public boolean assignsToStack() { + return stackOffset >= 0; + } + + public Register register() { + if (register == null) { + throw new IllegalStateException("Cannot get register index of a stack location."); + } + return register; + } + + public int stackOffset() { + if (stackOffset == NONE) { + throw new IllegalStateException("Cannot get stack offset of a register location."); + } + return stackOffset; + } + + @Override + public String toString() { + if (assignsToRegister()) { + return "r-" + register; + } else { + assert assignsToStack(); + return "s-" + stackOffset; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + AssignedLocation that = (AssignedLocation) o; + return stackOffset == that.stackOffset && Objects.equals(register, that.register); + } + + @Override + public int hashCode() { + return Objects.hash(register, stackOffset); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/code/SubstrateCallingConventionType.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/code/SubstrateCallingConventionType.java index 98b9178edf03..ab48afd112c6 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/code/SubstrateCallingConventionType.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/code/SubstrateCallingConventionType.java @@ -24,7 +24,9 @@ */ package com.oracle.svm.core.graal.code; +import java.util.Arrays; import java.util.EnumMap; +import java.util.Objects; import jdk.vm.ci.code.CallingConvention; @@ -33,10 +35,14 @@ * of the enum values. Use {@link SubstrateCallingConventionKind#toType} to get an instance. */ public final class SubstrateCallingConventionType implements CallingConvention.Type { + public final SubstrateCallingConventionKind kind; /** Determines if this is a request for the outgoing argument locations at a call site. */ public final boolean outgoing; + public final AssignedLocation[] fixedParameterAssignment; + public final AssignedLocation[] returnSaving; + static final EnumMap outgoingTypes; static final EnumMap incomingTypes; @@ -49,12 +55,60 @@ public final class SubstrateCallingConventionType implements CallingConvention.T } } - private SubstrateCallingConventionType(SubstrateCallingConventionKind kind, boolean outgoing) { + private SubstrateCallingConventionType(SubstrateCallingConventionKind kind, boolean outgoing, AssignedLocation[] fixedRegisters, AssignedLocation[] returnSaving) { this.kind = kind; this.outgoing = outgoing; + this.fixedParameterAssignment = fixedRegisters; + this.returnSaving = returnSaving; + } + + private SubstrateCallingConventionType(SubstrateCallingConventionKind kind, boolean outgoing) { + this(kind, outgoing, null, null); + } + + /** + * Allows to manually assign which location (i.e. which register or stack location) to use for + * each argument. + */ + public SubstrateCallingConventionType withParametersAssigned(AssignedLocation[] fixedRegisters) { + assert nativeABI(); + return new SubstrateCallingConventionType(this.kind, this.outgoing, fixedRegisters, returnSaving); + } + + /** + * Allows to retrieve the return of a function. When said return is more than one word long, we + * have no way of representing it as a value. Thus, this value will instead be stored from the + * registers containing it into a buffer provided (as a pointer) as a prefix argument to the + * function. + *

+ * Note that, even if used in conjunction with + * {@link SubstrateCallingConventionType#withParametersAssigned}, the location of the extra + * argument (i.e. the pointer to the return buffer) should not be assigned to a location, as + * this will be handled by the backend. + */ + public SubstrateCallingConventionType withReturnSaving(AssignedLocation[] newReturnSaving) { + assert nativeABI(); + return new SubstrateCallingConventionType(this.kind, this.outgoing, this.fixedParameterAssignment, newReturnSaving); } public boolean nativeABI() { return kind == SubstrateCallingConventionKind.Native; } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SubstrateCallingConventionType that = (SubstrateCallingConventionType) o; + return outgoing == that.outgoing && kind == that.kind && Arrays.equals(fixedParameterAssignment, that.fixedParameterAssignment) && Arrays.equals(returnSaving, that.returnSaving); + } + + @Override + public int hashCode() { + return Objects.hash(kind, outgoing, Arrays.hashCode(fixedParameterAssignment), Arrays.hashCode(returnSaving)); + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/lir/VerifyCFunctionReferenceMapsLIRPhase.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/lir/VerifyCFunctionReferenceMapsLIRPhase.java index 6d742f980254..48cfc77eddbe 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/lir/VerifyCFunctionReferenceMapsLIRPhase.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/lir/VerifyCFunctionReferenceMapsLIRPhase.java @@ -192,6 +192,8 @@ private int expectedFrameStates() { return 2; case StatusSupport.STATUS_IN_VM: return 1; + case StatusSupport.STATUS_ILLEGAL: + return 1; default: throw VMError.shouldNotReachHere("Unexpected thread status: " + newThreadStatus); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/replacements/SubstrateGraphKit.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/replacements/SubstrateGraphKit.java index 29b98383c613..82df9873f2e1 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/replacements/SubstrateGraphKit.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/replacements/SubstrateGraphKit.java @@ -29,6 +29,7 @@ import org.graalvm.compiler.api.replacements.SnippetReflectionProvider; import org.graalvm.compiler.core.common.CompilationIdentifier; +import org.graalvm.compiler.core.common.spi.ForeignCallDescriptor; import org.graalvm.compiler.core.common.type.Stamp; import org.graalvm.compiler.core.common.type.StampFactory; import org.graalvm.compiler.core.common.type.StampPair; @@ -57,6 +58,7 @@ import org.graalvm.compiler.nodes.calc.FloatingNode; import org.graalvm.compiler.nodes.calc.NarrowNode; import org.graalvm.compiler.nodes.extended.BoxNode; +import org.graalvm.compiler.nodes.extended.FixedValueAnchorNode; import org.graalvm.compiler.nodes.extended.GuardingNode; import org.graalvm.compiler.nodes.extended.StateSplitProxyNode; import org.graalvm.compiler.nodes.extended.UnboxNode; @@ -83,6 +85,7 @@ import com.oracle.svm.core.util.VMError; import jdk.vm.ci.code.BytecodeFrame; +import jdk.vm.ci.code.CallingConvention; import jdk.vm.ci.meta.Constant; import jdk.vm.ci.meta.JavaKind; import jdk.vm.ci.meta.JavaType; @@ -157,7 +160,11 @@ public LoadFieldNode createLoadField(ValueNode object, ResolvedJavaField field) } public ValueNode createLoadIndexed(ValueNode array, int index, JavaKind kind, GuardingNode boundsCheck) { - ValueNode loadIndexed = LoadIndexedNode.create(null, array, ConstantNode.forInt(index, getGraph()), boundsCheck, kind, getMetaAccess(), getConstantReflection()); + return createLoadIndexed(array, ConstantNode.forInt(index, getGraph()), kind, boundsCheck); + } + + public ValueNode createLoadIndexed(ValueNode array, ValueNode index, JavaKind kind, GuardingNode boundsCheck) { + ValueNode loadIndexed = LoadIndexedNode.create(null, array, index, boundsCheck, kind, getMetaAccess(), getConstantReflection()); if (loadIndexed instanceof FixedNode) { return append((FixedNode) loadIndexed); } @@ -189,7 +196,23 @@ public ConstantNode createConstant(Constant value, JavaKind kind) { } public ValueNode createCFunctionCall(ValueNode targetAddress, List arguments, Signature signature, int newThreadStatus, boolean emitDeoptTarget) { - boolean emitTransition = StatusSupport.isValidStatus(newThreadStatus); + return createCFunctionCallWithCapture(targetAddress, arguments, signature, newThreadStatus, emitDeoptTarget, SubstrateCallingConventionKind.Native.toType(true), + null, null, null); + } + + public ValueNode createCFunctionCallWithCapture(ValueNode targetAddress, List arguments, Signature signature, int newThreadStatus, boolean emitDeoptTarget, + CallingConvention.Type convention, ForeignCallDescriptor captureFunction, ValueNode statesToCapture, ValueNode captureBuffer) { + assert CFunctionEpilogueNode.captureArgumentsAreCoherent(captureFunction, statesToCapture, captureBuffer); + + var fixedStatesToCapture = statesToCapture; + if (fixedStatesToCapture != null) { + fixedStatesToCapture = append(new FixedValueAnchorNode(fixedStatesToCapture)); + } + + /* + * State capture require the epilogue (and thus prologue) to be emitted + */ + boolean emitTransition = StatusSupport.isValidStatus(newThreadStatus) || captureBuffer != null; if (emitTransition) { append(new CFunctionPrologueNode(newThreadStatus)); } @@ -205,11 +228,11 @@ public ValueNode createCFunctionCall(ValueNode targetAddress, List ar JavaKind cReturnKind = javaReturnKind.getStackKind(); JavaType returnType = signature.getReturnType(null); Stamp returnStamp = returnStamp(returnType, cReturnKind); - InvokeNode invoke = createIndirectCall(targetAddress, arguments, signature.toParameterTypes(null), returnStamp, cReturnKind, SubstrateCallingConventionKind.Native); + InvokeNode invoke = createIndirectCall(targetAddress, arguments, signature.toParameterTypes(null), returnStamp, cReturnKind, convention); assert !emitDeoptTarget || !emitTransition : "cannot have transition for deoptimization targets"; if (emitTransition) { - CFunctionEpilogueNode epilogue = new CFunctionEpilogueNode(newThreadStatus); + CFunctionEpilogueNode epilogue = new CFunctionEpilogueNode(newThreadStatus, captureFunction, fixedStatesToCapture, captureBuffer); append(epilogue); epilogue.setStateAfter(invoke.stateAfter().duplicateWithVirtualState()); } else if (emitDeoptTarget) { @@ -243,12 +266,17 @@ public InvokeNode createIndirectCall(ValueNode targetAddress, List ar private InvokeNode createIndirectCall(ValueNode targetAddress, List arguments, JavaType[] parameterTypes, Stamp returnStamp, JavaKind returnKind, SubstrateCallingConventionKind callKind) { + return createIndirectCall(targetAddress, arguments, parameterTypes, returnStamp, returnKind, callKind.toType(true)); + } + + private InvokeNode createIndirectCall(ValueNode targetAddress, List arguments, JavaType[] parameterTypes, Stamp returnStamp, JavaKind returnKind, + CallingConvention.Type convention) { frameState.clearStack(); int bci = bci(); CallTargetNode callTarget = getGraph().add( new IndirectCallTargetNode(targetAddress, arguments.toArray(new ValueNode[arguments.size()]), StampPair.createSingle(returnStamp), parameterTypes, null, - callKind.toType(true), InvokeKind.Static)); + convention, InvokeKind.Static)); InvokeNode invoke = append(new InvokeNode(callTarget, bci)); // Insert framestate. diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/CFunctionSnippets.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/CFunctionSnippets.java index a20c3a45d6ae..443fee692d2f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/CFunctionSnippets.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/CFunctionSnippets.java @@ -30,12 +30,16 @@ import org.graalvm.compiler.api.replacements.Snippet; import org.graalvm.compiler.api.replacements.Snippet.ConstantParameter; +import org.graalvm.compiler.core.common.spi.ForeignCallDescriptor; import org.graalvm.compiler.graph.Node; +import org.graalvm.compiler.nodes.ConstantNode; import org.graalvm.compiler.nodes.FixedNode; import org.graalvm.compiler.nodes.FixedWithNextNode; import org.graalvm.compiler.nodes.Invoke; import org.graalvm.compiler.nodes.InvokeNode; import org.graalvm.compiler.nodes.InvokeWithExceptionNode; +import org.graalvm.compiler.nodes.ValueNode; +import org.graalvm.compiler.nodes.extended.ForeignCallNode; import org.graalvm.compiler.nodes.extended.MembarNode; import org.graalvm.compiler.nodes.spi.LoweringTool; import org.graalvm.compiler.options.OptionValues; @@ -47,6 +51,7 @@ import org.graalvm.compiler.replacements.Snippets; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.c.struct.SizeOf; +import org.graalvm.nativeimage.c.type.CIntPointer; import org.graalvm.nativeimage.impl.InternalPlatform; import org.graalvm.word.LocationIdentity; @@ -103,6 +108,11 @@ public final class CFunctionSnippets extends SubstrateTemplates implements Snipp @Snippet private static CPrologueData prologueSnippet(@ConstantParameter int newThreadStatus) { + if (newThreadStatus == StatusSupport.STATUS_ILLEGAL) { + /* No transition wanted, so no anchor needed */ + return null; + } + /* Push a JavaFrameAnchor to the thread-local linked list. */ JavaFrameAnchor anchor = (JavaFrameAnchor) LoweredStackValueNode.loweredStackValue(SizeOf.get(JavaFrameAnchor.class), FrameAccess.wordSize(), frameAnchorIdentity); JavaFrameAnchors.pushFrameAnchor(anchor); @@ -117,8 +127,21 @@ private static CPrologueData prologueSnippet(@ConstantParameter int newThreadSta return CFunctionPrologueDataNode.cFunctionPrologueData(anchor, newThreadStatus); } + @Node.NodeIntrinsic(value = ForeignCallNode.class) + public static native void callCaptureFunction(@Node.ConstantNodeParameter ForeignCallDescriptor descriptor, int states, CIntPointer captureBuffer); + @Snippet - private static void epilogueSnippet(@ConstantParameter int oldThreadStatus) { + private static void epilogueSnippet(@ConstantParameter int oldThreadStatus, @ConstantParameter boolean enableCapture, @ConstantParameter ForeignCallDescriptor captureFunction, int statesToCapture, + CIntPointer captureBuffer) { + if (enableCapture) { + callCaptureFunction(captureFunction, statesToCapture, captureBuffer); + } + + if (oldThreadStatus == StatusSupport.STATUS_ILLEGAL) { + /* No transition was done before the call, so no transition must be done now */ + return; + } + if (SubstrateOptions.MultiThreaded.getValue()) { if (oldThreadStatus == StatusSupport.STATUS_IN_NATIVE) { Safepoint.transitionNativeToJava(true); @@ -167,7 +190,6 @@ public void lower(CFunctionPrologueNode node, LoweringTool tool) { node.graph().addBeforeFixed(node, node.graph().add(new VerificationMarkerNode(node.getMarker()))); int newThreadStatus = node.getNewThreadStatus(); - assert StatusSupport.isValidStatus(newThreadStatus); Arguments args = new Arguments(prologue, node.graph().getGuardsStage(), tool.getLoweringStage()); args.addConst("newThreadStatus", newThreadStatus); @@ -178,6 +200,7 @@ public void lower(CFunctionPrologueNode node, LoweringTool tool) { } class CFunctionEpilogueLowering implements NodeLoweringProvider { + private static final ForeignCallDescriptor PLACEHOLDER_DESCRIPTOR = new ForeignCallDescriptor("unreachable", void.class, new Class[0], true, new LocationIdentity[0], false, false); @Override public void lower(CFunctionEpilogueNode node, LoweringTool tool) { @@ -187,10 +210,24 @@ public void lower(CFunctionEpilogueNode node, LoweringTool tool) { node.graph().addAfterFixed(node, node.graph().add(new VerificationMarkerNode(node.getMarker()))); int oldThreadStatus = node.getOldThreadStatus(); - assert StatusSupport.isValidStatus(oldThreadStatus); - + ValueNode statesToCapture = node.getStatesToCapture(); + if (statesToCapture == null) { + statesToCapture = ConstantNode.forLong(0, node.graph()); + } + ForeignCallDescriptor captureFunction = node.getCaptureFunction(); + ValueNode buffer = node.getCaptureBuffer(); + if (buffer == null) { + // Set it to the null pointer + buffer = ConstantNode.forLong(0, node.graph()); + } Arguments args = new Arguments(epilogue, node.graph().getGuardsStage(), tool.getLoweringStage()); args.addConst("oldThreadStatus", oldThreadStatus); + args.addConst("enableCapture", captureFunction != null); + /* We cannot pass null as const argument, so we pass a dummy instead */ + args.addConst("captureFunction", captureFunction == null ? PLACEHOLDER_DESCRIPTOR : captureFunction); + args.add("statesToCapture", statesToCapture); + args.add("captureBuffer", buffer); + SnippetTemplate template = template(tool, node, args); template.setMayRemoveLocation(true); template.instantiate(tool.getMetaAccess(), node, SnippetTemplate.DEFAULT_REPLACER, args); @@ -218,18 +255,21 @@ private static void matchCallStructure(CFunctionPrologueNode prologueNode) { } InvokeNode invoke = (InvokeNode) cur; - /* - * We are re-using the classInit field of the InvokeNode to store the - * CFunctionPrologueNode. During lowering, we create a PrologueDataNode that holds - * all the prologue-related data that the invoke needs in the backend. - * - * The classInit field is in every InvokeNode, and it is otherwise unused by - * Substrate VM (it is used only by the Java HotSpot VM). If we ever need the - * classInit field for other purposes, we need to create a new subclass of - * InvokeNode, and replace the invoke here with an instance of that new subclass. - */ - VMError.guarantee(invoke.classInit() == null, "Re-using the classInit field to store the JavaFrameAnchor"); - invoke.setClassInit(prologueNode); + if (prologueNode.getNewThreadStatus() != StatusSupport.STATUS_ILLEGAL) { + /* + * We are re-using the classInit field of the InvokeNode to store the + * CFunctionPrologueNode. During lowering, we create a PrologueDataNode that + * holds all the prologue-related data that the invoke needs in the backend. + * + * The classInit field is in every InvokeNode, and it is otherwise unused by + * Substrate VM (it is used only by the Java HotSpot VM). If we ever need the + * classInit field for other purposes, we need to create a new subclass of + * InvokeNode, and replace the invoke here with an instance of that new + * subclass. + */ + VMError.guarantee(invoke.classInit() == null, "Re-using the classInit field to store the JavaFrameAnchor"); + invoke.setClassInit(prologueNode); + } singleInvoke = cur; } @@ -251,7 +291,6 @@ private static void matchCallStructure(CFunctionPrologueNode prologueNode) { @AutomaticallyRegisteredFeature @Platforms(InternalPlatform.NATIVE_ONLY.class) class CFunctionSnippetsFeature implements InternalFeature { - @Override @SuppressWarnings("unused") public void registerLowerings(RuntimeConfiguration runtimeConfig, OptionValues options, Providers providers, diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/headers/WindowsAPIs.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/headers/WindowsAPIs.java new file mode 100644 index 000000000000..cdf33af96f46 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/headers/WindowsAPIs.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2023, 2023, 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.core.headers; + +import org.graalvm.compiler.api.replacements.Fold; +import org.graalvm.nativeimage.ImageSingletons; + +import com.oracle.svm.core.Uninterruptible; + +public class WindowsAPIs { + @Uninterruptible(reason = Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public static int getLastError() { + return win().getLastError(); + } + + @Uninterruptible(reason = Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public static int wsaGetLastError() { + return win().wsaGetLastError(); + } + + @Fold + public static boolean isSupported() { + return ImageSingletons.contains(WindowsAPIsSupport.class); + } + + @Fold + static WindowsAPIsSupport win() { + return ImageSingletons.lookup(WindowsAPIsSupport.class); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/headers/WindowsAPIsSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/headers/WindowsAPIsSupport.java new file mode 100644 index 000000000000..e376a931f855 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/headers/WindowsAPIsSupport.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2023, 2023, 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.core.headers; + +import com.oracle.svm.core.Uninterruptible; + +public interface WindowsAPIsSupport { + @Uninterruptible(reason = Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + int getLastError(); + + @Uninterruptible(reason = Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + int wsaGetLastError(); +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_ClassLoader.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_ClassLoader.java index 5c11916e9d89..5288c2219686 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_ClassLoader.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_ClassLoader.java @@ -251,8 +251,13 @@ private void clearAssertionStatus() { @Delete private static native void registerNatives(); - @Delete - private static native long findNative(ClassLoader loader, String entryName); + /** + * Ignores {@code loader}, as {@link Target_java_lang_ClassLoader#loadLibrary}. + */ + @Substitute + private static long findNative(@SuppressWarnings("unused") ClassLoader loader, String entryName) { + return NativeLibrarySupport.singleton().findSymbol(entryName).rawValue(); + } @Substitute @SuppressWarnings({"unused", "static-method"}) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java index 143954b7ed18..361d6ae7f91e 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UninterruptibleUtils.java @@ -124,6 +124,11 @@ public int decrementAndGet() { return UNSAFE.getAndAddInt(this, VALUE_OFFSET, -1) - 1; } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public int getAndIncrement() { + return UNSAFE.getAndAddInt(this, VALUE_OFFSET, 1); + } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public boolean compareAndSet(int expected, int update) { return UNSAFE.compareAndSetInt(this, VALUE_OFFSET, expected, update); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/MethodHandleIntrinsicImpl.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/MethodHandleIntrinsicImpl.java index b47ca4a555cd..7c39e297220d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/MethodHandleIntrinsicImpl.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/MethodHandleIntrinsicImpl.java @@ -59,6 +59,8 @@ enum Variant { /* MethodHandle.invokeBasic(Object...) */ InvokeBasic(Modifier.FINAL | Modifier.NATIVE), + /* MethodHandle.linkToNative(Object...) */ + LinkToNative(Modifier.STATIC | Modifier.NATIVE), /* MethodHandle.linkTo*(Object...) */ Link(Modifier.STATIC | Modifier.NATIVE), @@ -177,6 +179,10 @@ public Object execute(Object... args) throws Throwable { return mh.invokeBasic(invokeArgs); } + case LinkToNative: { + return Target_java_lang_invoke_MethodHandle.linkToNative(args); + } + /* * The linkTo methods are used to create optimized invokers for a direct method handle. * We handle direct method handles explicitly in MethodHandle.invokeBasic, so we don't @@ -425,6 +431,8 @@ static MethodHandleIntrinsicImpl resolve(Target_java_lang_invoke_MemberName memb switch (name) { case "invokeBasic": return intrinsic(Variant.InvokeBasic); + case "linkToNative": + return intrinsic(Variant.LinkToNative); case "linkToVirtual": case "linkToStatic": case "linkToSpecial": diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandle.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandle.java index ad376ea9d251..d92a71435c07 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandle.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandle.java @@ -24,6 +24,8 @@ */ package com.oracle.svm.core.methodhandles; +import static com.oracle.svm.core.util.VMError.unsupportedFeature; + import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodType; import java.lang.reflect.Constructor; @@ -33,13 +35,16 @@ import java.lang.reflect.Modifier; import java.util.Arrays; +import com.oracle.svm.core.LinkToNativeSupport; import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.annotate.Alias; import com.oracle.svm.core.annotate.RecomputeFieldValue; import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; +import com.oracle.svm.core.annotate.TargetElement; import com.oracle.svm.core.invoke.MethodHandleUtils; import com.oracle.svm.core.invoke.Target_java_lang_invoke_MemberName; +import com.oracle.svm.core.jdk.JDK21OrLater; import com.oracle.svm.core.reflect.SubstrateMethodAccessor; import com.oracle.svm.core.reflect.target.Target_java_lang_reflect_AccessibleObject; import com.oracle.svm.core.reflect.target.Target_java_lang_reflect_Method; @@ -114,6 +119,16 @@ static Object linkToInterface(Object... args) throws Throwable { static Object linkToSpecial(Object... args) throws Throwable { return Util_java_lang_invoke_MethodHandle.linkTo(true, args); } + + @Substitute(polymorphicSignature = true) + @TargetElement(onlyWith = JDK21OrLater.class) + static Object linkToNative(Object... args) throws Throwable { + if (LinkToNativeSupport.isAvailable()) { + return LinkToNativeSupport.singleton().linkToNative(args); + } else { + throw unsupportedFeature("Foreign downcalls feature is not enabled. Make sure you are using a JDK >= 21 and that preview features are enabled."); + } + } } final class Util_java_lang_invoke_MethodHandle { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/nodes/CFunctionEpilogueNode.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/nodes/CFunctionEpilogueNode.java index 8c9ac2358e72..5e194ceede86 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/nodes/CFunctionEpilogueNode.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/nodes/CFunctionEpilogueNode.java @@ -27,15 +27,21 @@ import static org.graalvm.compiler.nodeinfo.InputType.Memory; import static org.graalvm.compiler.nodeinfo.InputType.State; import static org.graalvm.compiler.nodeinfo.NodeCycles.CYCLES_8; +import static org.graalvm.compiler.nodeinfo.NodeCycles.CYCLES_UNKNOWN; import static org.graalvm.compiler.nodeinfo.NodeSize.SIZE_8; +import static org.graalvm.compiler.nodeinfo.NodeSize.SIZE_UNKNOWN; +import org.graalvm.compiler.core.common.spi.ForeignCallDescriptor; import org.graalvm.compiler.core.common.type.StampFactory; import org.graalvm.compiler.graph.Node; import org.graalvm.compiler.graph.NodeClass; +import org.graalvm.compiler.nodeinfo.NodeCycles; import org.graalvm.compiler.nodeinfo.NodeInfo; +import org.graalvm.compiler.nodeinfo.NodeSize; import org.graalvm.compiler.nodes.AbstractStateSplit; import org.graalvm.compiler.nodes.DeoptimizingNode.DeoptBefore; import org.graalvm.compiler.nodes.FrameState; +import org.graalvm.compiler.nodes.ValueNode; import org.graalvm.compiler.nodes.debug.ControlFlowAnchored; import org.graalvm.compiler.nodes.memory.SingleMemoryKill; import org.graalvm.compiler.nodes.spi.Lowerable; @@ -44,17 +50,42 @@ /** * See comments in {@link CFunctionPrologueNode} for details. */ -@NodeInfo(cycles = CYCLES_8, size = SIZE_8, allowedUsageTypes = {Memory}) +@NodeInfo(cycles = CYCLES_UNKNOWN, cyclesRationale = "Capturing the call state requires calls whose cost is unknown.", size = SIZE_UNKNOWN, sizeRationale = "Capturing the call state requires calls whose cost is unknown.", allowedUsageTypes = { + Memory}) public final class CFunctionEpilogueNode extends AbstractStateSplit implements Lowerable, SingleMemoryKill, ControlFlowAnchored, DeoptBefore { public static final NodeClass TYPE = NodeClass.create(CFunctionEpilogueNode.class); private final int oldThreadStatus; - /** See comment in {@link CFunctionPrologueNode}. */ + /* + * This method is called with an integer (the capture mask) and a pointer to int (the capture + * buffer). + * + * This method is called from inside the CFunction prologue before transitioning back into Java. + * This means that no transition to/from should happen and the method must be uninterruptible. + * + * You need to register this method for foreign call and may need to declare it as root method. + */ + final ForeignCallDescriptor captureFunction; + @OptionalInput ValueNode statesToCapture; + @OptionalInput ValueNode captureBuffer; + /** + * See comment in {@link CFunctionPrologueNode}. + */ private CFunctionEpilogueMarker marker; - public CFunctionEpilogueNode(int oldThreadStatus) { + public CFunctionEpilogueNode(int oldThreadStatus, ForeignCallDescriptor captureFunction, ValueNode statesToCapture, ValueNode captureBuffer) { super(TYPE, StampFactory.forVoid()); this.oldThreadStatus = oldThreadStatus; + if (!captureArgumentsAreCoherent(captureFunction, statesToCapture, captureBuffer)) { + throw new IllegalArgumentException("Capture arguments are not coherent: " + captureFunction + " " + statesToCapture + " " + captureBuffer); + } + this.captureFunction = captureFunction; + this.statesToCapture = statesToCapture; + this.captureBuffer = captureBuffer; + } + + public CFunctionEpilogueNode(int oldThreadStatus) { + this(oldThreadStatus, null, null, null); } @Override @@ -79,6 +110,14 @@ public int getOldThreadStatus() { return oldThreadStatus; } + public ValueNode getStatesToCapture() { + return statesToCapture; + } + + public ValueNode getCaptureBuffer() { + return captureBuffer; + } + @NodeIntrinsic public static native void cFunctionEpilogue(@ConstantNodeParameter int oldThreadStatus); @@ -105,4 +144,22 @@ public boolean canUseAsStateDuring() { return true; } + @Override + public NodeCycles estimatedNodeCycles() { + return captureBuffer == null ? CYCLES_8 : super.estimatedNodeCycles(); + } + + @Override + protected NodeSize dynamicNodeSizeEstimate() { + return captureBuffer == null ? SIZE_8 : super.dynamicNodeSizeEstimate(); + } + + public ForeignCallDescriptor getCaptureFunction() { + return captureFunction; + } + + public static boolean captureArgumentsAreCoherent(ForeignCallDescriptor captureFunction, ValueNode statesToCapture, ValueNode captureBuffer) { + return ((captureFunction == null) && (statesToCapture == null) && (captureBuffer == null)) || + ((captureFunction != null) && (statesToCapture != null) && (captureBuffer != null)); + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/nodes/CFunctionPrologueNode.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/nodes/CFunctionPrologueNode.java index 4038c759729d..a9ca0fc5d91b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/nodes/CFunctionPrologueNode.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/nodes/CFunctionPrologueNode.java @@ -55,6 +55,10 @@ * Part of the prologue/epilogue are emitted by the lowering of these nodes using snippets, see * class CFunctionSnippets. Other parts are emitted in the backend when the call instruction is * emitted. + * + * It is also possible to capture the call state (i.e. some global variables, e.g. errno) in a + * provided buffer right after the call, as to prevent the VM from changing it before it could be + * queried by another downcall, see {@link CFunctionEpilogueNode#captureFunction}. */ @NodeInfo(cycles = CYCLES_8, size = SIZE_8, allowedUsageTypes = {Memory}) public final class CFunctionPrologueNode extends FixedWithNextNode implements Lowerable, SingleMemoryKill, ControlFlowAnchored { diff --git a/substratevm/src/com.oracle.svm.driver/resources/Help.txt b/substratevm/src/com.oracle.svm.driver/resources/Help.txt index f2f24406a48d..d5c4e7a8cd54 100644 --- a/substratevm/src/com.oracle.svm.driver/resources/Help.txt +++ b/substratevm/src/com.oracle.svm.driver/resources/Help.txt @@ -31,6 +31,9 @@ where options include: -J pass directly to the JVM running the image generator --diagnostics-mode enable diagnostics output: class initialization, substitutions, etc. --enable-preview allow classes to depend on preview features of this release + --enable-native-access [,...] + modules that are permitted to perform restricted native operations. + can also be ALL-UNNAMED. --verbose enable verbose output --version print product version and exit --help print this help message diff --git a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/DefaultOptionHandler.java b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/DefaultOptionHandler.java index 5abbd4ae8145..ae9040148053 100644 --- a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/DefaultOptionHandler.java +++ b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/DefaultOptionHandler.java @@ -52,6 +52,8 @@ class DefaultOptionHandler extends NativeImage.OptionHandler { /* Defunct legacy options that we have to accept to maintain backward compatibility */ private static final String noServerOption = "--no-server"; + private static final String nativeAccessOption = "--enable-native-access"; + DefaultOptionHandler(NativeImage nativeImage) { super(nativeImage); } @@ -139,6 +141,15 @@ public boolean consume(ArgumentQueue args) { case "--enable-preview": args.poll(); nativeImage.addCustomJavaArgs("--enable-preview"); + nativeImage.enablePreview(); + return true; + case nativeAccessOption: + args.poll(); + String modules = args.poll(); + if (modules == null) { + NativeImage.showError(nativeAccessOption + moduleSetModifierOptionErrorMessage); + } + nativeImage.addCustomJavaArgs(nativeAccessOption + "=" + modules); return true; } @@ -214,6 +225,15 @@ public boolean consume(ArgumentQueue args) { nativeImage.addLimitedModules(limitModulesArgs); return true; } + if (headArg.startsWith(nativeAccessOption + "=")) { + args.poll(); + String nativeAccessModules = headArg.substring(nativeAccessOption.length() + 1); + if (nativeAccessModules.isEmpty()) { + NativeImage.showError(headArg + moduleSetModifierOptionErrorMessage); + } + nativeImage.addCustomJavaArgs(headArg); + return true; + } if (headArg.startsWith("@") && !disableAtFiles) { args.poll(); headArg = headArg.substring(1); diff --git a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java index b4909c3fd0c4..e21cbd007a6b 100644 --- a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java +++ b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java @@ -291,7 +291,6 @@ private static String oR(OptionKey option) { BundleSupport bundleSupport; protected static class BuildConfiguration { - /* * Reuse com.oracle.svm.util.ModuleSupport.isModulePathBuild() to ensure same interpretation * of com.oracle.svm.util.ModuleSupport.ENV_VAR_USE_MODULE_SYSTEM environment variable use. @@ -304,6 +303,7 @@ protected static class BuildConfiguration { protected final Path workDir; protected final Path rootDir; protected final Path libJvmciDir; + protected final Path libPreviewDir; protected final List args; BuildConfiguration(BuildConfiguration original) { @@ -312,6 +312,7 @@ protected static class BuildConfiguration { workDir = original.workDir; rootDir = original.rootDir; libJvmciDir = original.libJvmciDir; + libPreviewDir = original.libPreviewDir; args = original.args; } @@ -355,6 +356,8 @@ protected BuildConfiguration(List args) { } Path ljDir = this.rootDir.resolve(Paths.get("lib", "jvmci")); libJvmciDir = Files.exists(ljDir) ? ljDir : null; + Path lpDir = this.rootDir.resolve(Paths.get("lib", "svm-preview", "builder")); + libPreviewDir = Files.exists(lpDir) ? lpDir : null; } /** @@ -544,6 +547,7 @@ public List getBuilderModulePath() { if (libJvmciDir != null) { result.addAll(getJars(libJvmciDir, "graal-sdk", "enterprise-graal")); } + result.addAll(getJars(rootDir.resolve(Paths.get("lib", "truffle")), "truffle-api")); if (modulePathBuild) { result.addAll(getJars(rootDir.resolve(Paths.get("lib", "svm", "builder")))); @@ -1712,6 +1716,43 @@ Path canonicalize(Path path, boolean strict) { } } + public enum PreviewFeatures { + FOREIGN(JavaVersionUtil.JAVA_SPEC >= 21, "svm-foreign"); + + private final boolean requirementsMet; + private final String libName; + + PreviewFeatures(boolean requirementsMet, String libName) { + this.requirementsMet = requirementsMet; + this.libName = libName; + } + + public boolean requirementsMet() { + return requirementsMet; + } + + public String getLibName() { + return libName; + } + } + + public void enablePreview() { + if (config.libPreviewDir == null && PreviewFeatures.values().length > 0) { + throw showError("The directory containing the preview modules was not found."); + } + + for (var preview : PreviewFeatures.values()) { + if (preview.requirementsMet()) { + Path libPath = config.libPreviewDir == null ? null : config.libPreviewDir.resolve(preview.getLibName() + ".jar"); + if (libPath != null && Files.exists(libPath)) { + addImageBuilderModulePath(libPath); + } else { + throw showError("Preview library " + preview.getLibName() + " should be enabled, but was not found."); + } + } + } + } + public void addImageBuilderModulePath(Path modulePathEntry) { imageBuilderModulePath.add(canonicalize(modulePathEntry)); } @@ -2044,22 +2085,20 @@ private static void show(Consumer printFunc, String message) { } protected static List getJars(Path dir, String... jarBaseNames) { - try { - List baseNameList = Arrays.asList(jarBaseNames); - return Files.list(dir) - .filter(p -> { - String jarFileName = p.getFileName().toString(); - String jarSuffix = ".jar"; - if (!jarFileName.toLowerCase().endsWith(jarSuffix)) { - return false; - } - if (baseNameList.isEmpty()) { - return true; - } - String jarBaseName = jarFileName.substring(0, jarFileName.length() - jarSuffix.length()); - return baseNameList.contains(jarBaseName); - }) - .collect(Collectors.toList()); + List baseNameList = Arrays.asList(jarBaseNames); + try (var files = Files.list(dir)) { + return files.filter(p -> { + String jarFileName = p.getFileName().toString(); + String jarSuffix = ".jar"; + if (!jarFileName.toLowerCase().endsWith(jarSuffix)) { + return false; + } + if (baseNameList.isEmpty()) { + return true; + } + String jarBaseName = jarFileName.substring(0, jarFileName.length() - jarSuffix.length()); + return baseNameList.contains(jarBaseName); + }).collect(Collectors.toList()); } catch (IOException e) { throw showError("Unable to use jar-files from directory " + dir, e); } diff --git a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/metainf/MetaInfFileType.java b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/metainf/MetaInfFileType.java index 963d3b7e552a..b698da42b7cd 100644 --- a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/metainf/MetaInfFileType.java +++ b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/metainf/MetaInfFileType.java @@ -32,6 +32,7 @@ public enum MetaInfFileType { Properties(null, NativeImageMetaInfWalker.nativeImagePropertiesFilename), JniConfiguration(ConfigurationFiles.Options.JNIConfigurationResources, ConfigurationFile.JNI.getFileName()), + ForeignConfiguration(ConfigurationFiles.Options.ForeignResources, ConfigurationFile.FOREIGN.getFileName()), ReflectConfiguration(ConfigurationFiles.Options.ReflectionConfigurationResources, ConfigurationFile.REFLECTION.getFileName()), ResourceConfiguration(ConfigurationFiles.Options.ResourceConfigurationResources, ConfigurationFile.RESOURCES.getFileName()), ProxyConfiguration(ConfigurationFiles.Options.DynamicProxyConfigurationResources, ConfigurationFile.DYNAMIC_PROXY.getFileName()), diff --git a/substratevm/src/com.oracle.svm.hosted.foreign/src/com/oracle/svm/hosted/foreign/DowncallStub.java b/substratevm/src/com.oracle.svm.hosted.foreign/src/com/oracle/svm/hosted/foreign/DowncallStub.java new file mode 100644 index 000000000000..3c64951d969a --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted.foreign/src/com/oracle/svm/hosted/foreign/DowncallStub.java @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2023, 2023, 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.hosted.foreign; + +import java.lang.invoke.MethodType; +import java.util.List; + +import org.graalvm.collections.Pair; +import org.graalvm.compiler.core.common.spi.ForeignCallDescriptor; +import org.graalvm.compiler.debug.DebugContext; +import org.graalvm.compiler.java.FrameStateBuilder; +import org.graalvm.compiler.nodes.StructuredGraph; +import org.graalvm.compiler.nodes.ValueNode; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.c.function.CFunction; + +import com.oracle.graal.pointsto.meta.HostedProviders; +import com.oracle.svm.common.meta.MultiMethod; +import com.oracle.svm.core.foreign.AbiUtils; +import com.oracle.svm.core.foreign.DowncallStubsHolder; +import com.oracle.svm.core.foreign.ForeignFunctionsRuntime; +import com.oracle.svm.core.foreign.LinkToNativeSupportImpl; +import com.oracle.svm.core.foreign.NativeEntryPointInfo; +import com.oracle.svm.core.foreign.Target_jdk_internal_foreign_abi_NativeEntryPoint; +import com.oracle.svm.core.graal.code.SubstrateCallingConventionKind; +import com.oracle.svm.core.graal.code.SubstrateCallingConventionType; +import com.oracle.svm.core.graal.snippets.CFunctionSnippets; +import com.oracle.svm.core.thread.VMThreads; +import com.oracle.svm.hosted.code.NonBytecodeMethod; +import com.oracle.svm.hosted.code.SimpleSignature; +import com.oracle.svm.hosted.phases.HostedGraphKit; +import com.oracle.svm.util.ReflectionUtil; + +import jdk.vm.ci.meta.JavaKind; +import jdk.vm.ci.meta.MetaAccessProvider; +import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.Signature; + +/** + * A stub for foreign downcalls. The "work repartition" for downcalls is as follows: + *

    + *
  • Transform "high-level" (e.g. structs) arguments into "low-level" arguments (i.e. int, long, + * float, double, pointer) which fit in a register --- done by HotSpot's implementation using method + * handles (or specialized classes);
  • + *
  • Unbox the arguments (the arguments are in an array of Objects, due to funneling through + * {@link LinkToNativeSupportImpl#linkToNative}) --- done by + * {@link DowncallStub#adaptArguments};
  • + *
  • Further adapt arguments as to satisfy SubstrateVM's backends --- done by + * {@link DowncallStub#adaptArguments}, see {@link AbiUtils.Adaptation}
  • + *
  • Transform HotSpot's memory moves into ones for SubstrateVM --- done by + * {@link AbiUtils#toMemoryAssignment}
  • + *
  • Perform a C-function call:
  • + *
      + *
    • Setup the frame anchor and capture call state --- Implemented in + * {@link CFunctionSnippets#prologueSnippet}, {@link CFunctionSnippets#epilogueSnippet} and + * {@link ForeignFunctionsRuntime#captureCallState};
    • + *
    • Setup registers, stack and return buffer according to the aforementioned assignments --- + * Implemented in the backend, e.g. + * {@link com.oracle.svm.core.graal.amd64.SubstrateAMD64RegisterConfig#getCallingConvention} and + * {@link com.oracle.svm.core.graal.amd64.SubstrateAMD64Backend.SubstrateAMD64NodeLIRBuilder#emitInvoke}
    • + *
    + *
  • If needed, box the return --- done by {@link DowncallStub#adaptReturnValue}.
  • + *
+ * Call state capture is done in the call epilogue to prevent the runtime environment from modifying + * the call state, which could happen if a safepoint was inserted between the downcall and the + * capture. + */ +@Platforms(Platform.HOSTED_ONLY.class) +class DowncallStub extends NonBytecodeMethod { + public static Signature createSignature(MetaAccessProvider metaAccess) { + return SimpleSignature.fromKinds(new JavaKind[]{JavaKind.Object}, JavaKind.Object, metaAccess); + } + + private final NativeEntryPointInfo nep; + + DowncallStub(NativeEntryPointInfo nep, MetaAccessProvider metaAccess) { + super( + DowncallStubsHolder.stubName(nep), + true, + metaAccess.lookupJavaType(DowncallStubsHolder.class), + createSignature(metaAccess), + DowncallStubsHolder.getConstantPool(metaAccess)); + this.nep = nep; + } + + /** + * The arguments follow the structure described in + * {@link LinkToNativeSupportImpl#linkToNative(Object...)}. + */ + @Override + public StructuredGraph buildGraph(DebugContext debug, ResolvedJavaMethod method, HostedProviders providers, Purpose purpose) { + HostedGraphKit kit = new HostedGraphKit(debug, providers, method, purpose); + FrameStateBuilder state = kit.getFrameState(); + boolean deoptimizationTarget = MultiMethod.isDeoptTarget(method); + List arguments = kit.loadArguments(getSignature().toParameterTypes(null)); + AbiUtils.Adaptation[] adaptations = AbiUtils.singleton().adaptArguments(nep); + + assert arguments.size() == 1; + var argumentsAndNep = adaptArguments(kit, nep.linkMethodType(), arguments.get(0), adaptations); + arguments = argumentsAndNep.getLeft(); + ValueNode runtimeNep = argumentsAndNep.getRight(); + + MethodType callType = adaptMethodType(nep, adaptations); + assert callType.parameterCount() == arguments.size(); + + int callAddressIndex = nep.callAddressIndex(); + ValueNode callAddress = arguments.remove(callAddressIndex); + callType = callType.dropParameterTypes(callAddressIndex, callAddressIndex + 1); + + ForeignCallDescriptor captureFunction = null; + ValueNode captureMask = null; + ValueNode captureAddress = null; + if (nep.capturesCallState()) { + captureFunction = ForeignFunctionsRuntime.CAPTURE_CALL_STATE; + captureMask = kit.createLoadField(runtimeNep, kit.getMetaAccess().lookupJavaField(ReflectionUtil.lookupField(Target_jdk_internal_foreign_abi_NativeEntryPoint.class, "captureMask"))); + + assert nep.captureAddressIndex() > nep.callAddressIndex(); + /* + * We already removed 1 element from the list, so we must offset the index by one as + * well + */ + int captureAddressIndex = nep.captureAddressIndex() - 1; + captureAddress = arguments.remove(captureAddressIndex); + callType = callType.dropParameterTypes(captureAddressIndex, captureAddressIndex + 1); + } + assert callType.parameterCount() == arguments.size(); + + state.clearLocals(); + SubstrateCallingConventionType cc = SubstrateCallingConventionKind.Native.toType(true) + .withParametersAssigned(nep.parametersAssignment()) + /* Assignment might be null, in which case this is a no-op */ + .withReturnSaving(nep.returnsAssignment()); + + CFunction.Transition transition = nep.skipsTransition() ? CFunction.Transition.NO_TRANSITION : CFunction.Transition.TO_NATIVE; + + ValueNode returnValue = kit.createCFunctionCallWithCapture( + callAddress, + arguments, + SimpleSignature.fromMethodType(callType, kit.getMetaAccess()), + VMThreads.StatusSupport.getNewThreadStatus(transition), + deoptimizationTarget, + cc, + captureFunction, + captureMask, + captureAddress); + + returnValue = adaptReturnValue(kit, nep.linkMethodType(), returnValue); + + kit.createReturn(returnValue, JavaKind.Object); + + return kit.finalizeGraph(); + } + + private static ValueNode adaptArgument(HostedGraphKit kit, ValueNode argument, Class type, AbiUtils.Adaptation adaptation) { + argument = kit.createUnboxing(argument, JavaKind.fromJavaClass(type)); + if (adaptation != null) { + argument = adaptation.apply(argument); + } + return argument; + } + + private static MethodType adaptMethodType(NativeEntryPointInfo nep, AbiUtils.Adaptation[] adaptations) { + MethodType mt = nep.linkMethodType(); + + Class[] parameters = new Class[mt.parameterCount()]; + for (int i = 0; i < mt.parameterCount(); ++i) { + Class parameterType = mt.parameterType(i); + assert parameterType.isPrimitive() : parameterType; + + AbiUtils.Adaptation adaptation = adaptations[i]; + if (adaptation != null) { + parameterType = adaptation.apply(parameterType); + } + + parameters[i] = parameterType; + } + + return MethodType.methodType(mt.returnType(), parameters); + } + + private static Pair, ValueNode> adaptArguments(HostedGraphKit kit, MethodType signature, ValueNode argumentsArray, AbiUtils.Adaptation[] adaptations) { + var args = kit.loadArrayElements(argumentsArray, JavaKind.Object, signature.parameterCount() + 1); + assert adaptations.length == signature.parameterCount() : adaptations.length + " " + signature.parameterCount(); + assert args.size() == signature.parameterCount() + 1 : args.size() + " " + (signature.parameterCount() + 1); + // We have to drop the NEP, which is the last argument + ValueNode nep = args.remove(args.size() - 1); + for (int i = 0; i < args.size(); ++i) { + args.set(i, adaptArgument(kit, args.get(i), signature.parameterType(i), adaptations[i])); + } + return Pair.create(args, nep); + } + + private static ValueNode adaptReturnValue(HostedGraphKit kit, MethodType signature, ValueNode invokeValue) { + ValueNode returnValue = invokeValue; + JavaKind returnKind = JavaKind.fromJavaClass(signature.returnType()); + if (returnKind.equals(JavaKind.Void)) { + return kit.createObject(null); + } + + var boxed = kit.getMetaAccess().lookupJavaType(returnKind.toBoxedJavaClass()); + returnValue = kit.createBoxing(returnValue, returnKind, boxed); + return returnValue; + } +} diff --git a/substratevm/src/com.oracle.svm.hosted.foreign/src/com/oracle/svm/hosted/foreign/ForeignFunctionsConfigurationParser.java b/substratevm/src/com.oracle.svm.hosted.foreign/src/com/oracle/svm/hosted/foreign/ForeignFunctionsConfigurationParser.java new file mode 100644 index 000000000000..4a8a1d341807 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted.foreign/src/com/oracle/svm/hosted/foreign/ForeignFunctionsConfigurationParser.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2023, 2023, 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.hosted.foreign; + +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.Linker; +import java.net.URI; +import java.util.ArrayList; +import java.util.List; + +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.impl.ConfigurationCondition; +import org.graalvm.nativeimage.impl.RuntimeForeignAccessSupport; + +import com.oracle.svm.core.configure.ConfigurationParser; + +@Platforms(Platform.HOSTED_ONLY.class) +public class ForeignFunctionsConfigurationParser extends ConfigurationParser { + private static final String DOWNCALL_OPTION_CAPTURE_CALL_STATE = "captureCallState"; + private static final String DOWNCALL_OPTION_FIRST_VARIADIC_ARG = "firstVariadicArg"; + private static final String DOWNCALL_OPTION_TRIVIAL = "trivial"; + + private final RuntimeForeignAccessSupport accessSupport; + + public ForeignFunctionsConfigurationParser(RuntimeForeignAccessSupport access) { + super(true); + this.accessSupport = access; + } + + @Override + public void parseAndRegister(Object json, URI origin) { + var topLevel = asMap(json, "first level of document must be a map"); + checkAttributes(topLevel, "foreign methods categories", List.of("downcalls")); + for (Object downcall : asList(topLevel.get("downcalls"), "downcalls must be an array of method signatures")) { + parseDowncall(downcall); + } + } + + private void parseDowncall(Object downcall) { + var map = asMap(downcall, "a downcall must be a map"); + checkAttributes(map, "downcall", List.of("descriptor"), List.of("options")); + var descriptor = parseDowncallSignatures(map.get("descriptor")); + var options = parseOptions(map.get("options", null)); + accessSupport.registerForDowncall(ConfigurationCondition.alwaysTrue(), descriptor, options.toArray()); + } + + private FunctionDescriptor parseDowncallSignatures(Object signature) { + String input = asString(signature, "downcalls's elements must be function descriptors"); + return FunctionDescriptorParser.parse(input); + } + + private List parseOptions(Object options) { + if (options == null) { + return List.of(); + } + + ArrayList res = new ArrayList<>(); + var map = asMap(options, "options must be a map"); + checkAttributes(map, "options", List.of(), List.of(DOWNCALL_OPTION_FIRST_VARIADIC_ARG, DOWNCALL_OPTION_CAPTURE_CALL_STATE, DOWNCALL_OPTION_TRIVIAL)); + + if (map.containsKey(DOWNCALL_OPTION_FIRST_VARIADIC_ARG)) { + int firstVariadic = (int) asLong(map.get(DOWNCALL_OPTION_FIRST_VARIADIC_ARG), ""); + res.add(Linker.Option.firstVariadicArg(firstVariadic)); + } + if (map.containsKey(DOWNCALL_OPTION_CAPTURE_CALL_STATE)) { + if (asBoolean(map.get(DOWNCALL_OPTION_CAPTURE_CALL_STATE, ""), DOWNCALL_OPTION_CAPTURE_CALL_STATE)) { + /* + * Dirty hack: we need the entrypoint to have a captured state, whatever said state + * is, so that the generated stub handles capture. + */ + res.add(Linker.Option.captureCallState("errno")); + } + } + if (map.containsKey(DOWNCALL_OPTION_TRIVIAL)) { + if (asBoolean(map.get(DOWNCALL_OPTION_TRIVIAL, ""), DOWNCALL_OPTION_TRIVIAL)) { + res.add(Linker.Option.isTrivial()); + } + } + + return res; + } +} diff --git a/substratevm/src/com.oracle.svm.hosted.foreign/src/com/oracle/svm/hosted/foreign/ForeignFunctionsFeature.java b/substratevm/src/com.oracle.svm.hosted.foreign/src/com/oracle/svm/hosted/foreign/ForeignFunctionsFeature.java new file mode 100644 index 000000000000..168e04da47a0 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted.foreign/src/com/oracle/svm/hosted/foreign/ForeignFunctionsFeature.java @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2023, 2023, 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.hosted.foreign; + +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.Linker; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.graalvm.collections.Pair; +import org.graalvm.compiler.api.replacements.Fold; +import org.graalvm.compiler.serviceprovider.JavaVersionUtil; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.c.type.CIntPointer; +import org.graalvm.nativeimage.impl.ConfigurationCondition; +import org.graalvm.nativeimage.impl.RuntimeForeignAccessSupport; + +import com.oracle.graal.pointsto.meta.AnalysisMethod; +import com.oracle.svm.core.LinkToNativeSupport; +import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.configure.ConfigurationFile; +import com.oracle.svm.core.configure.ConfigurationFiles; +import com.oracle.svm.core.configure.ConfigurationParser; +import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; +import com.oracle.svm.core.feature.InternalFeature; +import com.oracle.svm.core.foreign.AbiUtils; +import com.oracle.svm.core.foreign.ForeignFunctionsRuntime; +import com.oracle.svm.core.foreign.LinkToNativeSupportImpl; +import com.oracle.svm.core.foreign.NativeEntryPointInfo; +import com.oracle.svm.core.graal.meta.SubstrateForeignCallsProvider; +import com.oracle.svm.core.meta.MethodPointer; +import com.oracle.svm.core.util.UserError; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.hosted.ConditionalConfigurationRegistry; +import com.oracle.svm.hosted.FeatureImpl; +import com.oracle.svm.hosted.ProgressReporter; +import com.oracle.svm.hosted.config.ConfigurationParserUtils; +import com.oracle.svm.util.ModuleSupport; +import com.oracle.svm.util.ReflectionUtil; + +import jdk.vm.ci.meta.ResolvedJavaMethod; + +@AutomaticallyRegisteredFeature +@Platforms(Platform.HOSTED_ONLY.class) +public class ForeignFunctionsFeature implements InternalFeature { + private static boolean isPreviewEnabled() { + try { + return (boolean) ReflectionUtil.lookupMethod( + ReflectionUtil.lookupClass(false, "jdk.internal.misc.PreviewFeatures"), + "isEnabled").invoke(null); + } catch (ReflectiveOperationException e) { + throw VMError.shouldNotReachHere(e); + } + } + + private static final int FIRST_SUPPORTED_PREVIEW = 21; + private static final int FIRST_SUPPORTED_NON_PREVIEW = Integer.MAX_VALUE - 1; // TBD + + private static final Map REQUIRES_CONCEALED = Map.of( + "jdk.internal.vm.ci", new String[]{"jdk.vm.ci.code", "jdk.vm.ci.meta", "jdk.vm.ci.amd64"}, + "java.base", new String[]{ + "jdk.internal.foreign", + "jdk.internal.foreign.abi", + "jdk.internal.foreign.abi.x64", + "jdk.internal.foreign.abi.x64.sysv", + "jdk.internal.foreign.abi.x64.windows"}); + + private boolean sealed = false; + private final RuntimeForeignAccessSupportImpl accessSupport = new RuntimeForeignAccessSupportImpl(); + private final Set> registeredDowncalls = Collections.newSetFromMap(new ConcurrentHashMap<>()); + private final Map>> downcallsMapping = new HashMap<>(); + + @Fold + public static ForeignFunctionsFeature singleton() { + return ImageSingletons.lookup(ForeignFunctionsFeature.class); + } + + private void checkNotSealed() { + UserError.guarantee(!sealed, "Registration of foreign functions was closed."); + } + + private class RuntimeForeignAccessSupportImpl extends ConditionalConfigurationRegistry implements StronglyTypedRuntimeForeignAccessSupport { + @Override + public void registerForDowncall(ConfigurationCondition condition, FunctionDescriptor desc, Linker.Option... options) { + checkNotSealed(); + registerConditionalConfiguration(condition, () -> registeredDowncalls.add(Pair.create(desc, options))); + } + } + + @Override + public boolean isInConfiguration(IsInConfigurationAccess access) { + return SubstrateOptions.ForeignFunctions.getValue(); + } + + ForeignFunctionsFeature() { + /* + * We add these exports systematically in the constructor, as to avoid access errors from + * plugins when the feature is disabled in the config. + */ + for (var modulePackages : REQUIRES_CONCEALED.entrySet()) { + ModuleSupport.accessPackagesToClass(ModuleSupport.Access.EXPORT, ForeignFunctionsFeature.class, false, modulePackages.getKey(), modulePackages.getValue()); + } + } + + @Override + public void duringSetup(DuringSetupAccess a) { + assert (JavaVersionUtil.JAVA_SPEC >= FIRST_SUPPORTED_PREVIEW && isPreviewEnabled()) || + JavaVersionUtil.JAVA_SPEC >= FIRST_SUPPORTED_NON_PREVIEW; + + boolean supportForeignFunctions = !SubstrateOptions.useLLVMBackend(); + + if (supportForeignFunctions) { + ImageSingletons.add(AbiUtils.class, AbiUtils.create()); + ImageSingletons.add(ForeignFunctionsRuntime.class, new ForeignFunctionsRuntime()); + ImageSingletons.add(RuntimeForeignAccessSupport.class, accessSupport); + ImageSingletons.add(LinkToNativeSupport.class, new LinkToNativeSupportImpl()); + + var access = (FeatureImpl.DuringSetupAccessImpl) a; + ConfigurationParser parser = new ForeignFunctionsConfigurationParser(accessSupport); + ConfigurationParserUtils.parseAndRegisterConfigurations(parser, access.getImageClassLoader(), "panama foreign", + ConfigurationFiles.Options.ForeignConfigurationFiles, ConfigurationFiles.Options.ForeignResources, ConfigurationFile.FOREIGN.getFileName()); + } else { + if (SubstrateOptions.useLLVMBackend()) { + throw UserError.abort("Foreign functions interface is in use, but is not supported together with the LLVM backend."); + } else { + throw UserError.abort("Foreign functions interface is in use, but is not supported for an unspecified reason."); + } + } + } + + @Override + public void afterRegistration(AfterRegistrationAccess a) { + } + + private int createDowncallStubs(FeatureImpl.BeforeAnalysisAccessImpl access) { + Map created = new HashMap<>(); + for (Pair fdOptionsPair : registeredDowncalls) { + NativeEntryPointInfo nepi = AbiUtils.singleton().makeEntrypoint(fdOptionsPair.getLeft(), fdOptionsPair.getRight()); + + if (!created.containsKey(nepi)) { + ResolvedJavaMethod stub = new DowncallStub(nepi, access.getMetaAccess().getWrapped()); + AnalysisMethod analysisStub = access.getUniverse().lookup(stub); + access.getBigBang().addRootMethod(analysisStub, false); + created.put(nepi, analysisStub); + ForeignFunctionsRuntime.singleton().addStubPointer( + nepi, + new MethodPointer(analysisStub)); + downcallsMapping.put(analysisStub.getName(), new ArrayList<>()); + } + + String stubName = created.get(nepi).getName(); + downcallsMapping.get(stubName).add(fdOptionsPair); + + assert created.size() == downcallsMapping.size(); + } + registeredDowncalls.clear(); + + return created.size(); + } + + @Override + public void beforeAnalysis(BeforeAnalysisAccess a) { + var access = (FeatureImpl.BeforeAnalysisAccessImpl) a; + sealed = true; + + AbiUtils.singleton().checkLibrarySupport(); + + /* + * Specializing an adapter would define a new class at runtime, which is not allowed in + * SubstrateVM + */ + access.registerFieldValueTransformer( + ReflectionUtil.lookupField( + ReflectionUtil.lookupClass(false, "jdk.internal.foreign.abi.DowncallLinker"), + "USE_SPEC"), + (receiver, originalValue) -> false); + + access.registerAsRoot(ReflectionUtil.lookupMethod(ForeignFunctionsRuntime.class, "captureCallState", int.class, CIntPointer.class), false); + + int downcallStubsCount = createDowncallStubs(access); + ProgressReporter.singleton().setForeignFunctionsInfo(downcallStubsCount); + } + + @Override + public void registerForeignCalls(SubstrateForeignCallsProvider foreignCalls) { + foreignCalls.register(ForeignFunctionsRuntime.CAPTURE_CALL_STATE); + } + + /* Testing interface */ + + public int getCreatedStubsCount() { + assert sealed; + return downcallsMapping.size(); + } + + public Map>> getCreatedStubsMap() { + assert sealed; + return downcallsMapping; + } +} diff --git a/substratevm/src/com.oracle.svm.hosted.foreign/src/com/oracle/svm/hosted/foreign/FunctionDescriptorParser.java b/substratevm/src/com.oracle.svm.hosted.foreign/src/com/oracle/svm/hosted/foreign/FunctionDescriptorParser.java new file mode 100644 index 000000000000..c1d99e7d7947 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted.foreign/src/com/oracle/svm/hosted/foreign/FunctionDescriptorParser.java @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2023, 2023, 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.hosted.foreign; + +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.ValueLayout; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; + +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +/** + * Parses a string into a {@link java.lang.foreign.FunctionDescriptor}. The syntax is highly + * inspired from how function descriptors are printed by hotspot, but with some slight changes to + * make it a bit more readable. + * + *
+ * {@code
+ *     Descriptor ::= '(' Layout* ')'  (Layout | Void)
+ *     Layout ::=  [ Alignment '%' ] SimpleLayout
+ *     SimpleLayout ::= ValueLayout | StructLayout | UnionLayout | SequenceLayout
+ *     StructLayout ::= '{' Layout* '}' |
+ *     UnionLayout ::=  '<' Layout* '>'
+ *     SequenceLayout ::= '[' Size ':' Layout ']'
+ *     ValueLayout ::= 'z' | 'b' | 's' | 'i' | 'j' | 'f' | 'd' | 'a'
+ *     PaddingLayout ::= 'x' Int
+ *     Void ::= 'v'
+ *     Size ::= Int
+ *     Alignment ::= Int
+ *     Int ::= a positive (decimal) integer
+ * }
+ * 
+ * + * The letters correspond to the following java types/layouts: + *
    + *
  • z: Boolean
  • + *
  • b: Byte
  • + *
  • s: Short
  • + *
  • i: Integer
  • + *
  • j: Long
  • + *
  • f: Float
  • + *
  • d: Double
  • + *
  • a: Address
  • + *
+ */ +@Platforms(Platform.HOSTED_ONLY.class) +public final class FunctionDescriptorParser { + private FunctionDescriptorParser() { + } + + private static final class Impl { + private final String layout; + private int at; + + private Impl(String input) { + this.layout = input; + this.at = 0; + } + + private char peek() { + if (this.at == layout.length()) { + return '\0'; + } + return layout.charAt(at); + } + + private char consume() { + if (this.at == layout.length()) { + return '\0'; + } + return layout.charAt(at++); + } + + private void consumeChecked(char expected) { + char v = consume(); + if (v != expected) { + handleError("Expected " + expected + " but got " + v + " in " + layout); + } + } + + private List parseSequence(char start, char end, Supplier parser) { + consumeChecked(start); + ArrayList elements = new ArrayList<>(); + while (peek() != end) { + elements.add(parser.get()); + } + consumeChecked(end); + return elements; + } + + private FunctionDescriptor parseDescriptor() { + MemoryLayout[] arguments = parseSequence('(', ')', this::parseLayout).toArray(new MemoryLayout[0]); + if (peek() == 'v') { + consume(); + return FunctionDescriptor.ofVoid(arguments); + } else { + return FunctionDescriptor.of(parseLayout(), arguments); + } + } + + private long parseInt() { + int start = at; + boolean atLeastOneDigit = Character.isDigit(peek()); + + if (!atLeastOneDigit) { + handleError("Expected a number at position " + at + " of layout " + layout); + } + + while (Character.isDigit(peek())) { + consume(); + } + return Long.parseLong(layout.substring(start, at)); + } + + private MemoryLayout parseLayout() { + long alignment = -1; + if (Character.isDigit(peek())) { + alignment = parseInt(); + consumeChecked('%'); + } + + MemoryLayout layout = switch (peek()) { + case 'z' -> parseValueLayout(ValueLayout.JAVA_BOOLEAN); + case 'b' -> parseValueLayout(ValueLayout.JAVA_BYTE); + case 's' -> parseValueLayout(ValueLayout.JAVA_SHORT); + case 'c' -> parseValueLayout(ValueLayout.JAVA_CHAR); + case 'i' -> parseValueLayout(ValueLayout.JAVA_INT); + case 'j' -> parseValueLayout(ValueLayout.JAVA_LONG); + case 'f' -> parseValueLayout(ValueLayout.JAVA_FLOAT); + case 'd' -> parseValueLayout(ValueLayout.JAVA_DOUBLE); + case 'a' -> parseValueLayout(ValueLayout.ADDRESS); + case '[' -> parseSequenceLayout(); + case '{' -> parseStructLayout(); + case '<' -> parseUnionLayout(); + case 'X' -> parsePaddingLayout(); + default -> handleError("Unknown carrier: " + peek()); + }; + + if (alignment >= 0) { + layout = layout.withByteAlignment(alignment); + } + + if (peek() == '(') { + handleError("Layout parser does not support named layouts: " + layout); + } + + return layout; + } + + private MemoryLayout parseUnionLayout() { + return MemoryLayout.unionLayout(parseSequence('<', '>', this::parseLayout).toArray(new MemoryLayout[0])); + } + + private MemoryLayout parseStructLayout() { + return MemoryLayout.structLayout(parseSequence('{', '}', this::parseLayout).toArray(new MemoryLayout[0])); + } + + private MemoryLayout parsePaddingLayout() { + consumeChecked('x'); + return MemoryLayout.paddingLayout(parseInt()); + } + + private MemoryLayout parseSequenceLayout() { + consumeChecked('['); + long size = parseInt(); + consumeChecked(':'); + MemoryLayout element = parseLayout(); + consumeChecked(']'); + return MemoryLayout.sequenceLayout(size, element); + } + + private MemoryLayout parseValueLayout(ValueLayout baseLayout) { + consume(); + return baseLayout; + } + + private void checkDone() { + if (this.at < layout.length()) { + handleError("Layout parsing ended (at " + this.at + ") before its end: " + layout); + } + } + } + + public static FunctionDescriptor parse(String input) { + Impl parser = new Impl(input); + FunctionDescriptor res = parser.parseDescriptor(); + parser.checkDone(); + return res; + } + + @SuppressWarnings("serial") + public static final class FunctionDescriptorParserException extends RuntimeException { + public FunctionDescriptorParserException(final String msg) { + super(msg); + } + } + + private static void guarantee(boolean b, String msg) { + if (!b) { + handleError(msg); + } + } + + private static T handleError(String msg) { + throw new FunctionDescriptorParserException(msg); + } +} diff --git a/substratevm/src/com.oracle.svm.hosted.foreign/src/com/oracle/svm/hosted/foreign/StronglyTypedRuntimeForeignAccessSupport.java b/substratevm/src/com.oracle.svm.hosted.foreign/src/com/oracle/svm/hosted/foreign/StronglyTypedRuntimeForeignAccessSupport.java new file mode 100644 index 000000000000..f3fcb16f3258 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted.foreign/src/com/oracle/svm/hosted/foreign/StronglyTypedRuntimeForeignAccessSupport.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2023, 2023, 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.hosted.foreign; + +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.Linker; + +import org.graalvm.nativeimage.impl.ConfigurationCondition; +import org.graalvm.nativeimage.impl.RuntimeForeignAccessSupport; + +/** + * Convenience interface until {@link RuntimeForeignAccessSupport} can be strongly typed. + */ +public interface StronglyTypedRuntimeForeignAccessSupport extends RuntimeForeignAccessSupport { + @Override + default void registerForDowncall(ConfigurationCondition condition, Object descO, Object... optionsO) { + if (!(descO instanceof FunctionDescriptor)) { + throw new IllegalArgumentException("Desc must be an instance of " + FunctionDescriptor.class); + } + FunctionDescriptor desc = (FunctionDescriptor) descO; + Linker.Option[] options = new Linker.Option[optionsO.length]; + + for (int i = 0; i < optionsO.length; ++i) { + if (!(optionsO[i] instanceof Linker.Option)) { + throw new IllegalArgumentException(i + "th option must be an instance of " + Linker.Option.class); + } + options[i] = (Linker.Option) optionsO[i]; + } + + registerForDowncall(condition, desc, options); + } + + void registerForDowncall(ConfigurationCondition condition, FunctionDescriptor desc, Linker.Option... options); +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ModuleLayerFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ModuleLayerFeature.java index 0b50d217dbb5..a85de883ffa6 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ModuleLayerFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ModuleLayerFeature.java @@ -24,6 +24,8 @@ */ package com.oracle.svm.hosted; +import static com.oracle.svm.core.util.VMError.shouldNotReachHereAtRuntime; + import java.lang.module.Configuration; import java.lang.module.FindException; import java.lang.module.ModuleDescriptor; @@ -54,24 +56,26 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import com.oracle.svm.core.jdk.Resources; -import jdk.internal.module.DefaultRoots; -import jdk.internal.module.ModuleBootstrap; -import jdk.internal.module.SystemModuleFinders; +import org.graalvm.compiler.serviceprovider.JavaVersionUtil; import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.SubstrateUtil; +import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.feature.InternalFeature; +import com.oracle.svm.core.jdk.Resources; import com.oracle.svm.core.jdk.RuntimeModuleSupport; -import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.util.VMError; import com.oracle.svm.util.LogUtils; import com.oracle.svm.util.ModuleSupport; import com.oracle.svm.util.ReflectionUtil; -import org.graalvm.nativeimage.Platform; -import org.graalvm.nativeimage.Platforms; + +import jdk.internal.module.DefaultRoots; +import jdk.internal.module.ModuleBootstrap; +import jdk.internal.module.SystemModuleFinders; /** * This feature: @@ -225,6 +229,7 @@ public void afterAnalysis(AfterAnalysisAccess access) { * the originals. */ replicateVisibilityModifications(runtimeBootLayer, accessImpl.imageClassLoader, runtimeImageNamedModules); + replicateNativeAccess(runtimeImageNamedModules); } /** @@ -484,6 +489,28 @@ private void replicateVisibilityModifications(ModuleLayer runtimeBootLayer, Imag } } + private void replicateNativeAccess(Set analysisReachableNamedModules) { + if (JavaVersionUtil.JAVA_SPEC < 19) { + return; + } + + Map modulePairs = analysisReachableNamedModules + .stream() + .collect(Collectors.toMap(m -> m, m -> moduleLayerFeatureUtils.getRuntimeModuleForHostedModule(m, false))); + + Module builderModule = ModuleLayerFeatureUtils.getBuilderModule(); + assert builderModule != null; + + for (Map.Entry modulesPair : modulePairs.entrySet()) { + Module hosted = modulesPair.getKey(); + Module runtime = modulesPair.getValue(); + if (moduleLayerFeatureUtils.allowsNativeAccess(hosted)) { + moduleLayerFeatureUtils.setNativeAccess(runtime, true); + } + } + + } + private static List findApplicationModules(ModuleLayer runtimeBootLayer, List applicationModulePath) { List applicationModules = new ArrayList<>(); List applicationModuleNames; @@ -555,6 +582,7 @@ private static final class ModuleLayerFeatureUtils { private final Field moduleReadsField; private final Field moduleOpenPackagesField; private final Field moduleExportedPackagesField; + private final Field moduleEnableNativeAccessField; private final Method moduleFindModuleMethod; private final Method systemModuleFindersAllSystemModulesMethod; private final Method systemModuleFindersOfMethod; @@ -587,12 +615,17 @@ private static final class ModuleLayerFeatureUtils { moduleReadsField = findFieldByName(moduleClassFields, "reads"); moduleOpenPackagesField = findFieldByName(moduleClassFields, "openPackages"); moduleExportedPackagesField = findFieldByName(moduleClassFields, "exportedPackages"); + // Only present on JDK 19+ + moduleEnableNativeAccessField = findFieldByName(moduleClassFields, "enableNativeAccess", true); moduleDescriptorField.setAccessible(true); moduleLayerField.setAccessible(true); moduleLoaderField.setAccessible(true); moduleReadsField.setAccessible(true); moduleOpenPackagesField.setAccessible(true); moduleExportedPackagesField.setAccessible(true); + if (moduleEnableNativeAccessField != null) { + moduleEnableNativeAccessField.setAccessible(true); + } allUnnamedModuleSet = new HashSet<>(1); allUnnamedModuleSet.add(allUnnamedModule); @@ -624,8 +657,19 @@ private static final class ModuleLayerFeatureUtils { * versions. This method should be removed once {@link ReflectionUtil} becomes immune to * reflection filters. */ + private static Field findFieldByName(Field[] fields, String name, boolean optional) { + var res = Arrays.stream(fields).filter(f -> f.getName().equals(name)).findAny(); + if (res.isPresent()) { + return res.get(); + } else if (optional) { + return null; + } else { + throw shouldNotReachHereAtRuntime(); + } + } + private static Field findFieldByName(Field[] fields, String name) { - return Arrays.stream(fields).filter(f -> f.getName().equals(name)).findAny().orElseThrow(VMError::shouldNotReachHereAtRuntime); + return findFieldByName(fields, name, false); } private static boolean isModuleSynthetic(Module m) { @@ -1021,5 +1065,28 @@ Set invokeDefaultRootsComputeMethod(ModuleFinder finder1, ModuleFinder f throw VMError.shouldNotReachHere("Failed to reflectively invoke DefaultRoots.compute().", e); } } + + /** + * In the future, this can be replaced by calling Module#isNativeAccessEnabled(). We + * currently do it this way to still be compatible with older JDKs. + */ + boolean allowsNativeAccess(Module module) { + assert moduleEnableNativeAccessField != null : "Only available on JDK19+"; + try { + return (boolean) moduleEnableNativeAccessField.get(module); + } catch (IllegalAccessException e) { + throw VMError.shouldNotReachHere("Failed to reflectively access Module.enableNativeAccess.", e); + } + + } + + void setNativeAccess(Module module, boolean value) { + assert moduleEnableNativeAccessField != null : "Only available on JDK19+"; + try { + moduleEnableNativeAccessField.set(module, value); + } catch (IllegalAccessException e) { + throw VMError.shouldNotReachHere("Failed to reflectively set Module.enableNativeAccess.", e); + } + } } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java index 5db59c1f2ca1..34c926e15ec8 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java @@ -133,6 +133,7 @@ public class ProgressReporter { private int numJNIClasses = -1; private int numJNIFields = -1; private int numJNIMethods = -1; + private int numForeignFunctionsDowncallStubs = -1; private Timer debugInfoTimer; private boolean creationStageEndCompleted = false; private boolean reportStringBytes = true; @@ -206,6 +207,10 @@ public void setJNIInfo(int numClasses, int numFields, int numMethods) { numJNIMethods = numMethods; } + public void setForeignFunctionsInfo(int numDowncallStubs) { + this.numForeignFunctionsDowncallStubs = numDowncallStubs; + } + public void disableStringBytesReporting() { reportStringBytes = false; } @@ -402,6 +407,12 @@ private void printAnalysisStatistics(AnalysisUniverse universe, Collection= 0 ? numForeignFunctionsDowncallStubs : UNAVAILABLE_METRIC)); + if (numForeignFunctionsDowncallStubs >= 0) { + l().a(stubsFormat, numForeignFunctionsDowncallStubs) + .a(" registered for foreign downcalls").println(); + } int numLibraries = libraries.size(); if (numLibraries > 0) { TreeSet sortedLibraries = new TreeSet<>(libraries); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporterJsonHelper.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporterJsonHelper.java index 58f6683df3e6..141dd93be9c9 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporterJsonHelper.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporterJsonHelper.java @@ -162,6 +162,7 @@ public enum AnalysisResults implements JsonMetric { FIELD_REACHABLE("fields", "reachable"), FIELD_JNI("fields", "jni"), FIELD_REFLECT("fields", "reflection"), + FOREIGN_FUNCTIONS_DOWNCALL_STUBS("methods", "foreign-functions"), // TODO GR-42148: remove deprecated entries in a future release DEPRECATED_CLASS_TOTAL("classes", "total"), diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/SimpleSignature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/SimpleSignature.java index 6e1b5254b892..17aacf9f159c 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/SimpleSignature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/SimpleSignature.java @@ -24,9 +24,11 @@ */ package com.oracle.svm.hosted.code; +import java.lang.invoke.MethodType; import java.util.Arrays; import java.util.List; import java.util.Objects; +import java.util.stream.Collectors; import com.oracle.svm.core.SubstrateUtil; @@ -49,6 +51,12 @@ public static SimpleSignature fromKinds(JavaKind[] paramKinds, JavaKind returnKi return new SimpleSignature(paramTypes, returnType); } + public static SimpleSignature fromMethodType(MethodType mt, MetaAccessProvider metaAccess) { + return new SimpleSignature( + Arrays.stream(mt.parameterArray()).map(metaAccess::lookupJavaType).collect(Collectors.toList()), + metaAccess.lookupJavaType(mt.returnType())); + } + private static ResolvedJavaType resolveType(JavaKind kind, MetaAccessProvider metaAccess) { return metaAccess.lookupJavaType(kind.isObject() ? Object.class : kind.toJavaClass()); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/HostedGraphKit.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/HostedGraphKit.java index 932c1efdac44..ea4a7ff89ef5 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/HostedGraphKit.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/HostedGraphKit.java @@ -24,6 +24,10 @@ */ package com.oracle.svm.hosted.phases; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + import org.graalvm.compiler.core.common.type.StampFactory; import org.graalvm.compiler.core.common.type.StampPair; import org.graalvm.compiler.debug.DebugContext; @@ -133,4 +137,29 @@ public ValueNode maybeCreateExplicitNullCheck(ValueNode object) { createCheckThrowingBytecodeException(IsNullNode.create(object), true, BytecodeExceptionNode.BytecodeExceptionKind.NULL_POINTER); return append(PiNode.create(object, StampFactory.objectNonNull())); } + + /** + * Lift a node representing an array into a list of nodes representing the values in that array. + * + * @param array The array to lift + * @param elementKinds The kinds of the elements in the array + * @param length The length of the array + */ + public List loadArrayElements(ValueNode array, JavaKind[] elementKinds, int length) { + assert elementKinds.length == length; + + List result = new ArrayList<>(); + for (int i = 0; i < length; ++i) { + ValueNode load = createLoadIndexed(array, i, elementKinds[i], null); + append(load); + result.add(load); + } + return result; + } + + public List loadArrayElements(ValueNode array, JavaKind elementKind, int length) { + JavaKind[] elementKinds = new JavaKind[length]; + Arrays.fill(elementKinds, elementKind); + return loadArrayElements(array, elementKinds, length); + } }