File: TestLinker.java

package info (click to toggle)
openjdk-24 24.0.2%2B12-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 831,900 kB
  • sloc: java: 5,677,020; cpp: 1,323,154; xml: 1,320,524; ansic: 486,889; asm: 405,131; objc: 21,025; sh: 15,221; javascript: 11,049; python: 8,222; makefile: 2,504; perl: 357; awk: 351; sed: 172; pascal: 103; exp: 54; jsp: 24; csh: 3
file content (284 lines) | stat: -rw-r--r-- 13,240 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
/*
 * Copyright (c) 2022, 2024, 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.
 *
 * 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.
 */

/*
 * @test
 * @modules java.base/jdk.internal.foreign java.base/jdk.internal.foreign.abi.fallback
 * @run testng TestLinker
 * @run testng/othervm TestLinker
 */

import jdk.internal.foreign.CABI;
import jdk.internal.foreign.abi.fallback.FallbackLinker;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.Linker;
import java.lang.foreign.MemoryLayout;
import java.lang.foreign.PaddingLayout;
import java.lang.foreign.SequenceLayout;
import java.lang.foreign.StructLayout;
import java.lang.foreign.ValueLayout;
import java.lang.invoke.MethodHandle;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import static java.lang.foreign.MemoryLayout.*;
import static java.lang.foreign.ValueLayout.*;
import static org.testng.Assert.*;

public class TestLinker extends NativeTestHelper {

    static final boolean IS_FALLBACK_LINKER = CABI.current() == CABI.FALLBACK;

    record LinkRequest(FunctionDescriptor descriptor, Linker.Option... options) {}

    @Test(dataProvider = "notSameCases")
    public void testLinkerOptionsCache(LinkRequest l1, LinkRequest l2) {
        Linker linker = Linker.nativeLinker();
        MethodHandle mh1 = linker.downcallHandle(l1.descriptor(), l1.options());
        MethodHandle mh2 = linker.downcallHandle(l2.descriptor(), l2.options());
        // assert that these are 2 distinct link request. No caching allowed
        assertNotSame(mh1, mh2);
    }

    @DataProvider
    public static Object[][] notSameCases() {
        FunctionDescriptor fd_II_V = FunctionDescriptor.ofVoid(C_INT, C_INT);
        return new Object[][]{
            {new LinkRequest(fd_II_V), new LinkRequest(fd_II_V, Linker.Option.firstVariadicArg(1))},
            {new LinkRequest(FunctionDescriptor.ofVoid(JAVA_SHORT)), new LinkRequest(FunctionDescriptor.ofVoid(JAVA_CHAR))},
            {new LinkRequest(FunctionDescriptor.ofVoid(JAVA_SHORT)), new LinkRequest(FunctionDescriptor.ofVoid(JAVA_CHAR))},
        };
    }

    @Test(dataProvider = "namedDescriptors")
    public void testNamedLinkerCache(FunctionDescriptor f1, FunctionDescriptor f2) {
        Linker linker = Linker.nativeLinker();
        MethodHandle mh1 = linker.downcallHandle(f1);
        MethodHandle mh2 = linker.downcallHandle(f2);
        // assert that these are the same link request, even though layout names differ
        assertSame(mh1, mh2);
    }

    @DataProvider
    public static Object[][] namedDescriptors() {
        List<Object[]> cases = new ArrayList<>(Arrays.asList(new Object[][]{
            { FunctionDescriptor.ofVoid(C_INT),
                    FunctionDescriptor.ofVoid(C_INT.withName("x")) },
            { FunctionDescriptor.ofVoid(structLayout(C_INT)),
                    FunctionDescriptor.ofVoid(structLayout(C_INT).withName("x")) },
            { FunctionDescriptor.ofVoid(structLayout(C_INT)),
                    FunctionDescriptor.ofVoid(structLayout(C_INT.withName("x"))) },
            { FunctionDescriptor.ofVoid(structLayout(sequenceLayout(1, C_INT))),
                    FunctionDescriptor.ofVoid(structLayout(sequenceLayout(1, C_INT).withName("x"))) },
            { FunctionDescriptor.ofVoid(structLayout(sequenceLayout(1, C_INT))),
                    FunctionDescriptor.ofVoid(structLayout(sequenceLayout(1, C_INT.withName("x")))) },
            { FunctionDescriptor.ofVoid(C_POINTER),
                    FunctionDescriptor.ofVoid(C_POINTER.withName("x")) },
            { FunctionDescriptor.ofVoid(C_POINTER.withTargetLayout(C_INT)),
                    FunctionDescriptor.ofVoid(C_POINTER.withTargetLayout(C_INT.withName("x"))) },
            { FunctionDescriptor.ofVoid(C_POINTER.withTargetLayout(C_INT)),
                    FunctionDescriptor.ofVoid(C_POINTER.withName("x").withTargetLayout(C_INT.withName("x"))) },
        }));

        if (!IS_FALLBACK_LINKER) {
            cases.add(new Object[]{ FunctionDescriptor.ofVoid(unionLayout(C_INT)),
                    FunctionDescriptor.ofVoid(unionLayout(C_INT).withName("x")) });
            cases.add(new Object[]{ FunctionDescriptor.ofVoid(unionLayout(C_INT)),
                    FunctionDescriptor.ofVoid(unionLayout(C_INT.withName("x"))) });
        }
        if (C_LONG_LONG.byteAlignment() == 8) {
            cases.add(new Object[]{ FunctionDescriptor.ofVoid(structLayout(C_INT, paddingLayout(4), C_LONG_LONG)),
                    FunctionDescriptor.ofVoid(structLayout(C_INT, paddingLayout(4), C_LONG_LONG.withName("x"))) });
            cases.add(new Object[]{ FunctionDescriptor.ofVoid(structLayout(C_INT, paddingLayout(4), C_LONG_LONG)),
                    FunctionDescriptor.ofVoid(structLayout(C_INT, paddingLayout(4).withName("x"), C_LONG_LONG)) });
        }

        return cases.toArray(Object[][]::new);
    }

    @DataProvider
    public static Object[][] invalidIndexCases() {
        return new Object[][]{
                { -1, },
                { 42, },
        };
    }

    @Test(dataProvider = "invalidIndexCases",
          expectedExceptions = IllegalArgumentException.class,
          expectedExceptionsMessageRegExp = ".*not in bounds for descriptor.*")
    public void testInvalidOption(int invalidIndex) {
        Linker.Option option = Linker.Option.firstVariadicArg(invalidIndex);
        FunctionDescriptor desc = FunctionDescriptor.ofVoid();
        Linker.nativeLinker().downcallHandle(desc, option); // throws
    }

    @Test(expectedExceptions = IllegalArgumentException.class,
          expectedExceptionsMessageRegExp = ".*Unknown name.*")
    public void testInvalidPreservedValueName() {
        Linker.Option.captureCallState("foo"); // throws
    }

    @Test(dataProvider = "canonicalTypeNames")
    public void testCanonicalLayouts(String typeName) {
        MemoryLayout layout = LINKER.canonicalLayouts().get(typeName);
        assertNotNull(layout);
        assertTrue(layout instanceof ValueLayout);
    }

    @Test
    public void embeddedPaddingLayout() {
        PaddingLayout padding = MemoryLayout.paddingLayout(64).withByteAlignment(64);
        SequenceLayout sequence = MemoryLayout.sequenceLayout(2, padding);
        StructLayout struct = MemoryLayout.structLayout(sequence);
        FunctionDescriptor fd = FunctionDescriptor.of(struct, struct);
        Linker linker = Linker.nativeLinker();
        var x = expectThrows(IllegalArgumentException.class, () -> linker.downcallHandle(fd));
        assertTrue(x.getMessage().contains("not supported because a sequence of a padding layout is not allowed"));
    }

    @Test
    public void groupLayoutWithOnlyPadding() {
        PaddingLayout padding = MemoryLayout.paddingLayout(1);
        StructLayout struct = MemoryLayout.structLayout(padding);
        FunctionDescriptor fd = FunctionDescriptor.of(struct, struct);
        Linker linker = Linker.nativeLinker();
        var x = expectThrows(IllegalArgumentException.class, () -> linker.downcallHandle(fd));
        assertTrue(x.getMessage().contains("is non-empty and only has padding layouts"));
    }

    @Test
    public void interwovenPadding() {
        Linker linker = Linker.nativeLinker();
        var padding1 = MemoryLayout.paddingLayout(1);
        var padding2 = MemoryLayout.paddingLayout(2).withByteAlignment(2);

        var struct = MemoryLayout.structLayout(JAVA_BYTE, padding1, padding2, JAVA_INT);

        var fd = FunctionDescriptor.of(struct, struct, struct);
        var e = expectThrows(IllegalArgumentException.class, () -> linker.downcallHandle(fd));
        assertEquals(e.getMessage(),
                "The padding layout x2 was preceded by another padding layout x1 in " + struct);
    }

    @Test
    public void stackedPadding() {
        Linker linker = Linker.nativeLinker();
        var struct32 = MemoryLayout.structLayout(MemoryLayout.sequenceLayout(4, JAVA_LONG));
        var padding1 = MemoryLayout.paddingLayout(1);
        var padding2 = MemoryLayout.paddingLayout(2).withByteAlignment(2);
        var padding4 = MemoryLayout.paddingLayout(4).withByteAlignment(4);
        var padding8 = MemoryLayout.paddingLayout(8).withByteAlignment(8);
        var padding16 = MemoryLayout.paddingLayout(16).withByteAlignment(16);
        var padding32 = MemoryLayout.paddingLayout(32).withByteAlignment(32);
        var union = MemoryLayout.unionLayout(struct32, padding32);
        var struct = MemoryLayout.structLayout(JAVA_BYTE, padding1, padding2, padding4, padding8, padding16, union);
        var fd = FunctionDescriptor.of(struct, struct, struct);
        var e = expectThrows(IllegalArgumentException.class, () -> linker.downcallHandle(fd));
        assertEquals(e.getMessage(),
                "The padding layout x2 was preceded by another padding layout x1 in " + struct);
    }

    @Test
    public void paddingUnionByteSize3() {
        Linker linker = Linker.nativeLinker();
        var union = MemoryLayout.unionLayout(MemoryLayout.paddingLayout(3), ValueLayout.JAVA_INT);
        var fd = FunctionDescriptor.of(union, union, union);
        var e = expectThrows(IllegalArgumentException.class, () -> linker.downcallHandle(fd));
        assertEquals(e.getMessage(), "Superfluous padding x3 in " + union);
    }

    @Test
    public void paddingUnionByteSize4() {
        Linker linker = Linker.nativeLinker();
        var union = MemoryLayout.unionLayout(MemoryLayout.paddingLayout(4), ValueLayout.JAVA_INT);
        var fd = FunctionDescriptor.of(union, union, union);
        var e = expectThrows(IllegalArgumentException.class, () -> linker.downcallHandle(fd));
        assertEquals(e.getMessage(), "Superfluous padding x4 in " + union);
    }

    @Test
    public void paddingUnionByteSize5() {
        Linker linker = Linker.nativeLinker();
        var union = MemoryLayout.unionLayout(MemoryLayout.paddingLayout(5), ValueLayout.JAVA_INT);
        var fd = FunctionDescriptor.of(union, union, union);
        var e = expectThrows(IllegalArgumentException.class, () -> linker.downcallHandle(fd));
        assertEquals(e.getMessage(), "Layout '" + union + "' has unexpected size: 5 != 4");
    }

    @Test
    public void paddingUnionSeveral() {
        Linker linker = Linker.nativeLinker();
        var union = MemoryLayout.unionLayout(
                MemoryLayout.sequenceLayout(3, ValueLayout.JAVA_INT),
                ValueLayout.JAVA_LONG,
                MemoryLayout.paddingLayout(16),
                MemoryLayout.paddingLayout(16));
        var fd = FunctionDescriptor.of(union, union, union);
        var e = expectThrows(IllegalArgumentException.class, () -> linker.downcallHandle(fd));
        assertEquals(e.getMessage(), "More than one padding in " + union);
    }

    @Test
    public void sequenceOfZeroElements() {
        Linker linker = Linker.nativeLinker();
        var sequence0a8 = MemoryLayout.sequenceLayout(0, JAVA_LONG);
        var sequence3a1 = MemoryLayout.sequenceLayout(3, JAVA_BYTE);
        var padding5a1 = MemoryLayout.paddingLayout(5);
        var struct8a8 = MemoryLayout.structLayout(sequence0a8, sequence3a1, padding5a1);
        var fd = FunctionDescriptor.of(struct8a8, struct8a8, struct8a8);
        if (linker.getClass().equals(FallbackLinker.class)) {
            // The fallback linker does not support empty layouts (FFI_BAD_TYPEDEF)
            var iae = expectThrows(IllegalArgumentException.class, () -> linker.downcallHandle(fd));
            assertTrue(iae.getMessage().contains("is empty"));
        } else {
            linker.downcallHandle(fd);
        }
    }

    @DataProvider
    public static Object[][] canonicalTypeNames() {
        return new Object[][]{
                { "bool" },
                { "char" },
                { "short" },
                { "int" },
                { "long" },
                { "long long" },
                { "float" },
                { "double" },
                { "void*" },
                { "size_t" },
                { "wchar_t" },
        };
    }

    @Test(expectedExceptions=UnsupportedOperationException.class)
    public void testCanonicalLayoutsUnmodifiable() {
        LINKER.canonicalLayouts().put("asdf", C_INT);
    }
}