From 1f0bf31ce51e1c9e4524e59427e3089b90fc6988 Mon Sep 17 00:00:00 2001 From: Florian Angerer Date: Tue, 24 Jun 2025 13:58:56 +0200 Subject: [PATCH] svm: add basic FFM API test --- substratevm/mx.substratevm/mx_substratevm.py | 6 +- substratevm/mx.substratevm/suite.py | 2 +- ...veImageResourceFileSystemProviderTest.java | 4 +- .../oracle/svm/test/foreign/ForeignTests.java | 139 ++++++++++++++++++ .../oracle/svm/test/jfr/JfrStreamingTest.java | 4 +- .../svm/test/jfr/TestJfrStreamingCount.java | 6 +- .../svm/test/jfr/TestJfrStreamingStress.java | 6 +- .../test/nmt/NativeMemoryTrackingTests.java | 25 ++-- 8 files changed, 168 insertions(+), 24 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/foreign/ForeignTests.java diff --git a/substratevm/mx.substratevm/mx_substratevm.py b/substratevm/mx.substratevm/mx_substratevm.py index 83e5ece1ef97..76b3ca3f2096 100644 --- a/substratevm/mx.substratevm/mx_substratevm.py +++ b/substratevm/mx.substratevm/mx_substratevm.py @@ -341,7 +341,11 @@ def native_image_func(args, **kwargs): yield native_image_func native_image_context.hosted_assertions = ['-J-ea', '-J-esa'] -_native_unittest_features = '--features=com.oracle.svm.test.ImageInfoTest$TestFeature,com.oracle.svm.test.services.ServiceLoaderTest$TestFeature,com.oracle.svm.test.services.SecurityServiceTest$TestFeature,com.oracle.svm.test.ReflectionRegistrationTest$TestFeature' +_native_unittest_features = '--features=' + ','.join(('com.oracle.svm.test.ImageInfoTest$TestFeature', + 'com.oracle.svm.test.services.ServiceLoaderTest$TestFeature', + 'com.oracle.svm.test.services.SecurityServiceTest$TestFeature', + 'com.oracle.svm.test.ReflectionRegistrationTest$TestFeature', + 'com.oracle.svm.test.foreign.ForeignTests$TestFeature')) IMAGE_ASSERTION_FLAGS = svm_experimental_options(['-H:+VerifyGraalGraphs', '-H:+VerifyPhases']) diff --git a/substratevm/mx.substratevm/suite.py b/substratevm/mx.substratevm/suite.py index 31c547785293..ce92b5af6a9e 100644 --- a/substratevm/mx.substratevm/suite.py +++ b/substratevm/mx.substratevm/suite.py @@ -1160,7 +1160,7 @@ "compiler:GRAAL_PROCESSOR", "SVM_PROCESSOR", ], - "javaCompliance" : "21+", + "javaCompliance" : "22+", "spotbugs": "false", "jacoco" : "exclude", }, diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/NativeImageResourceFileSystemProviderTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/NativeImageResourceFileSystemProviderTest.java index 022782f493fa..0f0400465430 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/NativeImageResourceFileSystemProviderTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/NativeImageResourceFileSystemProviderTest.java @@ -148,7 +148,7 @@ public void githubIssue5020() { // 2. Iterating through all resources in file system starting for the root. Path rootPath = fileSystem.getPath(ROOT_DIRECTORY); try (Stream files = Files.walk(rootPath)) { - files.forEach(path -> { + files.forEach(_ -> { }); } catch (IOException e) { Assert.fail("IOException occurred during file system walk, starting from the root!"); @@ -163,7 +163,7 @@ public void githubIssue5020() { public void githubIssue5080() { Path path = fileSystem.getPath(RESOURCE_DIR + "/"); try (Stream files = Files.walk(path)) { - files.forEach(p -> { + files.forEach(_ -> { }); } catch (IOException e) { Assert.fail("IOException occurred during file system walk, starting from the path: " + path + "!"); diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/foreign/ForeignTests.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/foreign/ForeignTests.java new file mode 100644 index 000000000000..90dfd3d954c5 --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/foreign/ForeignTests.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.test.foreign; + +import static org.junit.Assert.assertEquals; + +import java.lang.foreign.Arena; +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.Linker; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.SymbolLookup; +import java.lang.foreign.ValueLayout; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.util.Arrays; + +import org.graalvm.nativeimage.hosted.Feature; +import org.graalvm.nativeimage.hosted.RuntimeForeignAccess; +import org.junit.Assert; +import org.junit.Assume; +import org.junit.Test; + +import com.oracle.svm.core.SubstrateOptions; + +@SuppressWarnings("restricted") +public class ForeignTests { + public static final FunctionDescriptor QSORT_COMPARE_DESC = FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS.withTargetLayout(ValueLayout.JAVA_INT), + ValueLayout.ADDRESS.withTargetLayout(ValueLayout.JAVA_INT)); + private static final String STRING = "Hello, World!"; + private static final FunctionDescriptor STRLEN_SIG = FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS); + private static final FunctionDescriptor QSORT_SIG = FunctionDescriptor.ofVoid(ValueLayout.ADDRESS, ValueLayout.JAVA_LONG, ValueLayout.JAVA_LONG, ValueLayout.ADDRESS); + private static final MethodHandle COMPARE_HANDLE = initCompareHandle(); + + @Test + public void testInvokeStrlen() throws Throwable { + Assume.assumeFalse("Linker.nativeLinker().defaultLookup() is not supported in static executables", + SubstrateOptions.StaticExecutable.getValue()); + + try (Arena arena = Arena.ofConfined()) { + MemorySegment nativeString = arena.allocateFrom(STRING); + assertEquals(STRING.length(), (long) strlenMH().invokeExact(nativeString)); + } + } + + @Test + public void testUpcall() throws Throwable { + Assume.assumeFalse("Linker.nativeLinker().defaultLookup() is not supported in static executables", + SubstrateOptions.StaticExecutable.getValue()); + + /* + * Due to native memory tracking (NMT) tests, we use 'Arena.global()' to ensure that the + * upcall stub won't be deallocated during NMT tests which would lead to unexpected memory + * usage values. + */ + MemorySegment compareFunc = Linker.nativeLinker().upcallStub(COMPARE_HANDLE, QSORT_COMPARE_DESC, Arena.global()); + + int[] unsortedArray = new int[]{0, 9, 3, 4, 6, 5, 1, 8, 2, 7}; + + int[] sorted; + + try (Arena arena = Arena.ofConfined()) { + MemorySegment array = arena.allocateFrom(ValueLayout.JAVA_INT, unsortedArray); + + qsortMH().invoke(array, + (long) unsortedArray.length, + ValueLayout.JAVA_INT.byteSize(), + compareFunc); + + sorted = array.toArray(ValueLayout.JAVA_INT); + } + + int[] jSortedArray = Arrays.copyOf(unsortedArray, unsortedArray.length); + Arrays.sort(jSortedArray); + + Assert.assertArrayEquals(jSortedArray, sorted); + } + + private static MethodHandle strlenMH() { + SymbolLookup stdLib = Linker.nativeLinker().defaultLookup(); + MemorySegment strlenAddr = stdLib.find("strlen").orElseThrow(); + return Linker.nativeLinker().downcallHandle(strlenAddr, STRLEN_SIG); + } + + private static MethodHandle qsortMH() { + MemorySegment qsortAddr = Linker.nativeLinker().defaultLookup().find("qsort").orElseThrow(); + return Linker.nativeLinker().downcallHandle(qsortAddr, QSORT_SIG); + } + + private static MethodHandle initCompareHandle() { + try { + return MethodHandles.lookup().findStatic(Qsort.class, "qsortCompare", + MethodType.methodType(int.class, MemorySegment.class, MemorySegment.class)); + } catch (NoSuchMethodException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + static class Qsort { + static int qsortCompare(MemorySegment elem1, MemorySegment elem2) { + return Integer.compare(elem1.get(ValueLayout.JAVA_INT, 0), elem2.get(ValueLayout.JAVA_INT, 0)); + } + } + + public static class TestFeature implements Feature { + + @Override + public void duringSetup(DuringSetupAccess access) { + RuntimeForeignAccess.registerForDowncall(STRLEN_SIG); + RuntimeForeignAccess.registerForDowncall(QSORT_SIG); + RuntimeForeignAccess.registerForDirectUpcall(COMPARE_HANDLE, QSORT_COMPARE_DESC); + } + } +} diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrStreamingTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrStreamingTest.java index e7e8ead4172c..81fc466cb8df 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrStreamingTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrStreamingTest.java @@ -62,10 +62,10 @@ protected RecordingStream startStream(String[] events) throws Throwable { stream.setMaxSize(JFR_MAX_SIZE); stream.enable("com.jfr.StartStream"); - stream.onEvent("com.jfr.StartStream", e -> streamStates.get(stream).started = true); + stream.onEvent("com.jfr.StartStream", _ -> streamStates.get(stream).started = true); stream.enable("com.jfr.EndStream"); - stream.onEvent("com.jfr.EndStream", e -> { + stream.onEvent("com.jfr.EndStream", _ -> { stream.close(); streamStates.get(stream).endedSuccessfully = true; }); diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrStreamingCount.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrStreamingCount.java index 5ce7eef80495..3f7696f323ed 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrStreamingCount.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrStreamingCount.java @@ -60,9 +60,9 @@ public void test() throws Throwable { String[] events = new String[]{"com.jfr.String", "com.jfr.Integer", "com.jfr.Class"}; RecordingStream stream = startStream(events); - stream.onEvent("com.jfr.Class", event -> classEvents.incrementAndGet()); - stream.onEvent("com.jfr.Integer", event -> integerEvents.incrementAndGet()); - stream.onEvent("com.jfr.String", event -> stringEvents.incrementAndGet()); + stream.onEvent("com.jfr.Class", _ -> classEvents.incrementAndGet()); + stream.onEvent("com.jfr.Integer", _ -> integerEvents.incrementAndGet()); + stream.onEvent("com.jfr.String", _ -> stringEvents.incrementAndGet()); Runnable eventEmitter = () -> { for (int i = 0; i < COUNT; i++) { diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrStreamingStress.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrStreamingStress.java index 4ea919762e57..54de73be7536 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrStreamingStress.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrStreamingStress.java @@ -67,9 +67,9 @@ public void test() throws Throwable { String[] events = new String[]{"com.jfr.String", "com.jfr.Integer", "com.jfr.Class", JfrEvent.JavaMonitorWait.getName()}; RecordingStream stream = startStream(events); - stream.onEvent("com.jfr.Class", event -> classEvents.incrementAndGet()); - stream.onEvent("com.jfr.Integer", event -> integerEvents.incrementAndGet()); - stream.onEvent("com.jfr.String", event -> stringEvents.incrementAndGet()); + stream.onEvent("com.jfr.Class", _ -> classEvents.incrementAndGet()); + stream.onEvent("com.jfr.Integer", _ -> integerEvents.incrementAndGet()); + stream.onEvent("com.jfr.String", _ -> stringEvents.incrementAndGet()); stream.onEvent(JfrEvent.JavaMonitorWait.getName(), event -> { if (event. getValue("monitorClass").getName().equals(MonitorWaitHelper.class.getName())) { waitEvents.incrementAndGet(); diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/nmt/NativeMemoryTrackingTests.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/nmt/NativeMemoryTrackingTests.java index 61747876e0a8..f0214ff560eb 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/nmt/NativeMemoryTrackingTests.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/nmt/NativeMemoryTrackingTests.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -29,7 +29,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import jdk.graal.compiler.word.Word; import org.graalvm.word.Pointer; import org.junit.Test; @@ -37,6 +36,8 @@ import com.oracle.svm.core.nmt.NativeMemoryTracking; import com.oracle.svm.core.nmt.NmtCategory; +import jdk.graal.compiler.word.Word; + public class NativeMemoryTrackingTests { private static final int K = 1024; private static final int M = 1024 * 1024; @@ -72,7 +73,7 @@ public void testRealloc() { assertEquals(0, getUsedMemory()); Pointer ptr = NativeMemory.malloc(16 * K, NmtCategory.Code); - assertEquals(getUsedMemory(), 16 * K); + assertEquals(16 * K, getUsedMemory()); assertTrue(getUsedMemory() > 0); Pointer reallocPtr = NativeMemory.realloc(ptr, Word.unsigned(8 * K), NmtCategory.Code); @@ -133,22 +134,22 @@ public void testVirtualMemoryTracking() { assertTrue(NativeMemoryTracking.singleton().getPeakReservedVirtualMemory(NmtCategory.JavaHeap) > 0); assertTrue(NativeMemoryTracking.singleton().getPeakReservedVirtualMemory(NmtCategory.ImageHeap) > 0); - // Ensure we have a zero baseline - assertTrue(NativeMemoryTracking.singleton().getReservedVirtualMemory(NmtCategory.Code) == 0); - assertTrue(NativeMemoryTracking.singleton().getCommittedVirtualMemory(NmtCategory.Code) == 0); - assertTrue(NativeMemoryTracking.singleton().getPeakReservedVirtualMemory(NmtCategory.Code) == 0); - assertTrue(NativeMemoryTracking.singleton().getPeakCommittedVirtualMemory(NmtCategory.Code) == 0); + // determine baseline + long codeReservedVirtualMemory = NativeMemoryTracking.singleton().getReservedVirtualMemory(NmtCategory.Code); + long codeCommittedVirtualMemory = NativeMemoryTracking.singleton().getCommittedVirtualMemory(NmtCategory.Code); + long codePeakReservedVirtualMemory = NativeMemoryTracking.singleton().getPeakReservedVirtualMemory(NmtCategory.Code); + long codePeakCommittedVirtualMemory = NativeMemoryTracking.singleton().getPeakCommittedVirtualMemory(NmtCategory.Code); // Use some memory NativeMemoryTracking.singleton().trackReserve(1024, NmtCategory.Code); NativeMemoryTracking.singleton().trackCommit(512, NmtCategory.Code); - assertTrue(NativeMemoryTracking.singleton().getReservedVirtualMemory(NmtCategory.Code) == 1024); - assertTrue(NativeMemoryTracking.singleton().getCommittedVirtualMemory(NmtCategory.Code) == 512); + assertEquals(codeReservedVirtualMemory + 1024, NativeMemoryTracking.singleton().getReservedVirtualMemory(NmtCategory.Code)); + assertEquals(codeCommittedVirtualMemory + 512, NativeMemoryTracking.singleton().getCommittedVirtualMemory(NmtCategory.Code)); // Uncommit and check peaks NativeMemoryTracking.singleton().trackUncommit(512, NmtCategory.Code); NativeMemoryTracking.singleton().trackFree(1024, NmtCategory.Code); - assertTrue(NativeMemoryTracking.singleton().getPeakReservedVirtualMemory(NmtCategory.Code) == 1024); - assertTrue(NativeMemoryTracking.singleton().getPeakCommittedVirtualMemory(NmtCategory.Code) == 512); + assertEquals(codePeakReservedVirtualMemory + 1024, NativeMemoryTracking.singleton().getPeakReservedVirtualMemory(NmtCategory.Code)); + assertEquals(codePeakCommittedVirtualMemory + 512, NativeMemoryTracking.singleton().getPeakCommittedVirtualMemory(NmtCategory.Code)); } }