Skip to content

Commit 670a86f

Browse files
committed
[GR-64608] Add basic FFM API tests.
PullRequest: graal/21225
2 parents 9a903d9 + 1f0bf31 commit 670a86f

File tree

8 files changed

+168
-24
lines changed

8 files changed

+168
-24
lines changed

substratevm/mx.substratevm/mx_substratevm.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,11 @@ def native_image_func(args, **kwargs):
341341
yield native_image_func
342342

343343
native_image_context.hosted_assertions = ['-J-ea', '-J-esa']
344-
_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'
344+
_native_unittest_features = '--features=' + ','.join(('com.oracle.svm.test.ImageInfoTest$TestFeature',
345+
'com.oracle.svm.test.services.ServiceLoaderTest$TestFeature',
346+
'com.oracle.svm.test.services.SecurityServiceTest$TestFeature',
347+
'com.oracle.svm.test.ReflectionRegistrationTest$TestFeature',
348+
'com.oracle.svm.test.foreign.ForeignTests$TestFeature'))
345349

346350
IMAGE_ASSERTION_FLAGS = svm_experimental_options(['-H:+VerifyGraalGraphs', '-H:+VerifyPhases'])
347351

substratevm/mx.substratevm/suite.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1160,7 +1160,7 @@
11601160
"compiler:GRAAL_PROCESSOR",
11611161
"SVM_PROCESSOR",
11621162
],
1163-
"javaCompliance" : "21+",
1163+
"javaCompliance" : "22+",
11641164
"spotbugs": "false",
11651165
"jacoco" : "exclude",
11661166
},

substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/NativeImageResourceFileSystemProviderTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ public void githubIssue5020() {
148148
// 2. Iterating through all resources in file system starting for the root.
149149
Path rootPath = fileSystem.getPath(ROOT_DIRECTORY);
150150
try (Stream<Path> files = Files.walk(rootPath)) {
151-
files.forEach(path -> {
151+
files.forEach(_ -> {
152152
});
153153
} catch (IOException e) {
154154
Assert.fail("IOException occurred during file system walk, starting from the root!");
@@ -163,7 +163,7 @@ public void githubIssue5020() {
163163
public void githubIssue5080() {
164164
Path path = fileSystem.getPath(RESOURCE_DIR + "/");
165165
try (Stream<Path> files = Files.walk(path)) {
166-
files.forEach(p -> {
166+
files.forEach(_ -> {
167167
});
168168
} catch (IOException e) {
169169
Assert.fail("IOException occurred during file system walk, starting from the path: " + path + "!");
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/*
2+
* Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
26+
package com.oracle.svm.test.foreign;
27+
28+
import static org.junit.Assert.assertEquals;
29+
30+
import java.lang.foreign.Arena;
31+
import java.lang.foreign.FunctionDescriptor;
32+
import java.lang.foreign.Linker;
33+
import java.lang.foreign.MemorySegment;
34+
import java.lang.foreign.SymbolLookup;
35+
import java.lang.foreign.ValueLayout;
36+
import java.lang.invoke.MethodHandle;
37+
import java.lang.invoke.MethodHandles;
38+
import java.lang.invoke.MethodType;
39+
import java.util.Arrays;
40+
41+
import org.graalvm.nativeimage.hosted.Feature;
42+
import org.graalvm.nativeimage.hosted.RuntimeForeignAccess;
43+
import org.junit.Assert;
44+
import org.junit.Assume;
45+
import org.junit.Test;
46+
47+
import com.oracle.svm.core.SubstrateOptions;
48+
49+
@SuppressWarnings("restricted")
50+
public class ForeignTests {
51+
public static final FunctionDescriptor QSORT_COMPARE_DESC = FunctionDescriptor.of(
52+
ValueLayout.JAVA_INT,
53+
ValueLayout.ADDRESS.withTargetLayout(ValueLayout.JAVA_INT),
54+
ValueLayout.ADDRESS.withTargetLayout(ValueLayout.JAVA_INT));
55+
private static final String STRING = "Hello, World!";
56+
private static final FunctionDescriptor STRLEN_SIG = FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS);
57+
private static final FunctionDescriptor QSORT_SIG = FunctionDescriptor.ofVoid(ValueLayout.ADDRESS, ValueLayout.JAVA_LONG, ValueLayout.JAVA_LONG, ValueLayout.ADDRESS);
58+
private static final MethodHandle COMPARE_HANDLE = initCompareHandle();
59+
60+
@Test
61+
public void testInvokeStrlen() throws Throwable {
62+
Assume.assumeFalse("Linker.nativeLinker().defaultLookup() is not supported in static executables",
63+
SubstrateOptions.StaticExecutable.getValue());
64+
65+
try (Arena arena = Arena.ofConfined()) {
66+
MemorySegment nativeString = arena.allocateFrom(STRING);
67+
assertEquals(STRING.length(), (long) strlenMH().invokeExact(nativeString));
68+
}
69+
}
70+
71+
@Test
72+
public void testUpcall() throws Throwable {
73+
Assume.assumeFalse("Linker.nativeLinker().defaultLookup() is not supported in static executables",
74+
SubstrateOptions.StaticExecutable.getValue());
75+
76+
/*
77+
* Due to native memory tracking (NMT) tests, we use 'Arena.global()' to ensure that the
78+
* upcall stub won't be deallocated during NMT tests which would lead to unexpected memory
79+
* usage values.
80+
*/
81+
MemorySegment compareFunc = Linker.nativeLinker().upcallStub(COMPARE_HANDLE, QSORT_COMPARE_DESC, Arena.global());
82+
83+
int[] unsortedArray = new int[]{0, 9, 3, 4, 6, 5, 1, 8, 2, 7};
84+
85+
int[] sorted;
86+
87+
try (Arena arena = Arena.ofConfined()) {
88+
MemorySegment array = arena.allocateFrom(ValueLayout.JAVA_INT, unsortedArray);
89+
90+
qsortMH().invoke(array,
91+
(long) unsortedArray.length,
92+
ValueLayout.JAVA_INT.byteSize(),
93+
compareFunc);
94+
95+
sorted = array.toArray(ValueLayout.JAVA_INT);
96+
}
97+
98+
int[] jSortedArray = Arrays.copyOf(unsortedArray, unsortedArray.length);
99+
Arrays.sort(jSortedArray);
100+
101+
Assert.assertArrayEquals(jSortedArray, sorted);
102+
}
103+
104+
private static MethodHandle strlenMH() {
105+
SymbolLookup stdLib = Linker.nativeLinker().defaultLookup();
106+
MemorySegment strlenAddr = stdLib.find("strlen").orElseThrow();
107+
return Linker.nativeLinker().downcallHandle(strlenAddr, STRLEN_SIG);
108+
}
109+
110+
private static MethodHandle qsortMH() {
111+
MemorySegment qsortAddr = Linker.nativeLinker().defaultLookup().find("qsort").orElseThrow();
112+
return Linker.nativeLinker().downcallHandle(qsortAddr, QSORT_SIG);
113+
}
114+
115+
private static MethodHandle initCompareHandle() {
116+
try {
117+
return MethodHandles.lookup().findStatic(Qsort.class, "qsortCompare",
118+
MethodType.methodType(int.class, MemorySegment.class, MemorySegment.class));
119+
} catch (NoSuchMethodException | IllegalAccessException e) {
120+
throw new RuntimeException(e);
121+
}
122+
}
123+
124+
static class Qsort {
125+
static int qsortCompare(MemorySegment elem1, MemorySegment elem2) {
126+
return Integer.compare(elem1.get(ValueLayout.JAVA_INT, 0), elem2.get(ValueLayout.JAVA_INT, 0));
127+
}
128+
}
129+
130+
public static class TestFeature implements Feature {
131+
132+
@Override
133+
public void duringSetup(DuringSetupAccess access) {
134+
RuntimeForeignAccess.registerForDowncall(STRLEN_SIG);
135+
RuntimeForeignAccess.registerForDowncall(QSORT_SIG);
136+
RuntimeForeignAccess.registerForDirectUpcall(COMPARE_HANDLE, QSORT_COMPARE_DESC);
137+
}
138+
}
139+
}

substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/JfrStreamingTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,10 @@ protected RecordingStream startStream(String[] events) throws Throwable {
6262
stream.setMaxSize(JFR_MAX_SIZE);
6363

6464
stream.enable("com.jfr.StartStream");
65-
stream.onEvent("com.jfr.StartStream", e -> streamStates.get(stream).started = true);
65+
stream.onEvent("com.jfr.StartStream", _ -> streamStates.get(stream).started = true);
6666

6767
stream.enable("com.jfr.EndStream");
68-
stream.onEvent("com.jfr.EndStream", e -> {
68+
stream.onEvent("com.jfr.EndStream", _ -> {
6969
stream.close();
7070
streamStates.get(stream).endedSuccessfully = true;
7171
});

substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrStreamingCount.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,9 @@ public void test() throws Throwable {
6060
String[] events = new String[]{"com.jfr.String", "com.jfr.Integer", "com.jfr.Class"};
6161
RecordingStream stream = startStream(events);
6262

63-
stream.onEvent("com.jfr.Class", event -> classEvents.incrementAndGet());
64-
stream.onEvent("com.jfr.Integer", event -> integerEvents.incrementAndGet());
65-
stream.onEvent("com.jfr.String", event -> stringEvents.incrementAndGet());
63+
stream.onEvent("com.jfr.Class", _ -> classEvents.incrementAndGet());
64+
stream.onEvent("com.jfr.Integer", _ -> integerEvents.incrementAndGet());
65+
stream.onEvent("com.jfr.String", _ -> stringEvents.incrementAndGet());
6666

6767
Runnable eventEmitter = () -> {
6868
for (int i = 0; i < COUNT; i++) {

substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJfrStreamingStress.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,9 @@ public void test() throws Throwable {
6767
String[] events = new String[]{"com.jfr.String", "com.jfr.Integer", "com.jfr.Class", JfrEvent.JavaMonitorWait.getName()};
6868
RecordingStream stream = startStream(events);
6969

70-
stream.onEvent("com.jfr.Class", event -> classEvents.incrementAndGet());
71-
stream.onEvent("com.jfr.Integer", event -> integerEvents.incrementAndGet());
72-
stream.onEvent("com.jfr.String", event -> stringEvents.incrementAndGet());
70+
stream.onEvent("com.jfr.Class", _ -> classEvents.incrementAndGet());
71+
stream.onEvent("com.jfr.Integer", _ -> integerEvents.incrementAndGet());
72+
stream.onEvent("com.jfr.String", _ -> stringEvents.incrementAndGet());
7373
stream.onEvent(JfrEvent.JavaMonitorWait.getName(), event -> {
7474
if (event.<RecordedClass> getValue("monitorClass").getName().equals(MonitorWaitHelper.class.getName())) {
7575
waitEvents.incrementAndGet();

substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/nmt/NativeMemoryTrackingTests.java

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved.
33
* Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved.
44
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
55
*
@@ -29,14 +29,15 @@
2929
import static org.junit.Assert.assertEquals;
3030
import static org.junit.Assert.assertTrue;
3131

32-
import jdk.graal.compiler.word.Word;
3332
import org.graalvm.word.Pointer;
3433
import org.junit.Test;
3534

3635
import com.oracle.svm.core.memory.NativeMemory;
3736
import com.oracle.svm.core.nmt.NativeMemoryTracking;
3837
import com.oracle.svm.core.nmt.NmtCategory;
3938

39+
import jdk.graal.compiler.word.Word;
40+
4041
public class NativeMemoryTrackingTests {
4142
private static final int K = 1024;
4243
private static final int M = 1024 * 1024;
@@ -72,7 +73,7 @@ public void testRealloc() {
7273
assertEquals(0, getUsedMemory());
7374
Pointer ptr = NativeMemory.malloc(16 * K, NmtCategory.Code);
7475

75-
assertEquals(getUsedMemory(), 16 * K);
76+
assertEquals(16 * K, getUsedMemory());
7677
assertTrue(getUsedMemory() > 0);
7778

7879
Pointer reallocPtr = NativeMemory.realloc(ptr, Word.unsigned(8 * K), NmtCategory.Code);
@@ -133,22 +134,22 @@ public void testVirtualMemoryTracking() {
133134
assertTrue(NativeMemoryTracking.singleton().getPeakReservedVirtualMemory(NmtCategory.JavaHeap) > 0);
134135
assertTrue(NativeMemoryTracking.singleton().getPeakReservedVirtualMemory(NmtCategory.ImageHeap) > 0);
135136

136-
// Ensure we have a zero baseline
137-
assertTrue(NativeMemoryTracking.singleton().getReservedVirtualMemory(NmtCategory.Code) == 0);
138-
assertTrue(NativeMemoryTracking.singleton().getCommittedVirtualMemory(NmtCategory.Code) == 0);
139-
assertTrue(NativeMemoryTracking.singleton().getPeakReservedVirtualMemory(NmtCategory.Code) == 0);
140-
assertTrue(NativeMemoryTracking.singleton().getPeakCommittedVirtualMemory(NmtCategory.Code) == 0);
137+
// determine baseline
138+
long codeReservedVirtualMemory = NativeMemoryTracking.singleton().getReservedVirtualMemory(NmtCategory.Code);
139+
long codeCommittedVirtualMemory = NativeMemoryTracking.singleton().getCommittedVirtualMemory(NmtCategory.Code);
140+
long codePeakReservedVirtualMemory = NativeMemoryTracking.singleton().getPeakReservedVirtualMemory(NmtCategory.Code);
141+
long codePeakCommittedVirtualMemory = NativeMemoryTracking.singleton().getPeakCommittedVirtualMemory(NmtCategory.Code);
141142

142143
// Use some memory
143144
NativeMemoryTracking.singleton().trackReserve(1024, NmtCategory.Code);
144145
NativeMemoryTracking.singleton().trackCommit(512, NmtCategory.Code);
145-
assertTrue(NativeMemoryTracking.singleton().getReservedVirtualMemory(NmtCategory.Code) == 1024);
146-
assertTrue(NativeMemoryTracking.singleton().getCommittedVirtualMemory(NmtCategory.Code) == 512);
146+
assertEquals(codeReservedVirtualMemory + 1024, NativeMemoryTracking.singleton().getReservedVirtualMemory(NmtCategory.Code));
147+
assertEquals(codeCommittedVirtualMemory + 512, NativeMemoryTracking.singleton().getCommittedVirtualMemory(NmtCategory.Code));
147148

148149
// Uncommit and check peaks
149150
NativeMemoryTracking.singleton().trackUncommit(512, NmtCategory.Code);
150151
NativeMemoryTracking.singleton().trackFree(1024, NmtCategory.Code);
151-
assertTrue(NativeMemoryTracking.singleton().getPeakReservedVirtualMemory(NmtCategory.Code) == 1024);
152-
assertTrue(NativeMemoryTracking.singleton().getPeakCommittedVirtualMemory(NmtCategory.Code) == 512);
152+
assertEquals(codePeakReservedVirtualMemory + 1024, NativeMemoryTracking.singleton().getPeakReservedVirtualMemory(NmtCategory.Code));
153+
assertEquals(codePeakCommittedVirtualMemory + 512, NativeMemoryTracking.singleton().getPeakCommittedVirtualMemory(NmtCategory.Code));
153154
}
154155
}

0 commit comments

Comments
 (0)