1 /*
  2  *  Copyright (c) 2020, 2021, 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.
  8  *
  9  *  This code is distributed in the hope that it will be useful, but WITHOUT
 10  *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  *  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  *  version 2 for more details (a copy is included in the LICENSE file that
 13  *  accompanied this code).
 14  *
 15  *  You should have received a copy of the GNU General Public License version
 16  *  2 along with this work; if not, write to the Free Software Foundation,
 17  *  Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  *   Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  *  or visit www.oracle.com if you need additional information or have any
 21  *  questions.
 22  *
 23  */
 24 
 25 /*
 26  * @test
 27  * @library ../
 28  * @requires ((os.arch == "amd64" | os.arch == "x86_64") & sun.arch.data.model == "64") | os.arch == "aarch64"
 29  * @modules jdk.incubator.foreign/jdk.internal.foreign
 30  *          jdk.incubator.foreign/jdk.internal.foreign.abi
 31  *          jdk.incubator.foreign/jdk.internal.foreign.abi.aarch64.linux
 32  *          jdk.incubator.foreign/jdk.internal.foreign.abi.aarch64.macos
 33  *          jdk.incubator.foreign/jdk.internal.foreign.abi.x64.windows
 34  *          jdk.incubator.foreign/jdk.internal.foreign.abi.x64.sysv
 35  * @run testng/othervm --enable-native-access=ALL-UNNAMED VaListTest
 36  */
 37 
 38 import jdk.incubator.foreign.*;
 39 import jdk.incubator.foreign.CLinker.VaList;
 40 import jdk.internal.foreign.abi.aarch64.linux.LinuxAArch64Linker;
 41 import jdk.internal.foreign.abi.aarch64.macos.MacOsAArch64Linker;
 42 import jdk.internal.foreign.abi.x64.sysv.SysVx64Linker;
 43 import jdk.internal.foreign.abi.x64.windows.Windowsx64Linker;
 44 import org.testng.annotations.DataProvider;
 45 import org.testng.annotations.Test;
 46 
 47 import java.lang.invoke.MethodHandle;
 48 import java.lang.invoke.MethodHandleProxies;
 49 import java.lang.invoke.MethodHandles;
 50 import java.lang.invoke.MethodType;
 51 import java.lang.invoke.VarHandle;
 52 import java.util.function.BiFunction;
 53 import java.util.function.Consumer;
 54 import java.util.function.Function;
 55 import java.util.stream.DoubleStream;
 56 import java.util.stream.IntStream;
 57 
 58 import static jdk.incubator.foreign.CLinker.C_DOUBLE;
 59 import static jdk.incubator.foreign.CLinker.C_FLOAT;
 60 import static jdk.incubator.foreign.CLinker.C_INT;
 61 import static jdk.incubator.foreign.CLinker.C_LONG;
 62 import static jdk.incubator.foreign.CLinker.C_LONG_LONG;
 63 import static jdk.incubator.foreign.CLinker.C_POINTER;
 64 import static jdk.incubator.foreign.CLinker.C_VA_LIST;
 65 import static jdk.incubator.foreign.MemoryLayout.PathElement.groupElement;
 66 import static jdk.incubator.foreign.MemoryLayouts.JAVA_INT;
 67 import static jdk.internal.foreign.PlatformLayouts.*;
 68 import static org.testng.Assert.*;
 69 
 70 public class VaListTest extends NativeTestHelper {
 71 
 72     private static final CLinker abi = CLinker.getInstance();
 73     static {
 74         System.loadLibrary("VaList");
 75     }
 76 
 77     static final SymbolLookup LOOKUP = SymbolLookup.loaderLookup();
 78 
 79     private static final MethodHandle MH_sumInts = link("sumInts",
 80             MethodType.methodType(int.class, int.class, VaList.class),
 81             FunctionDescriptor.of(C_INT, C_INT, C_VA_LIST));
 82     private static final MethodHandle MH_sumDoubles = link("sumDoubles",
 83             MethodType.methodType(double.class, int.class, VaList.class),
 84             FunctionDescriptor.of(C_DOUBLE, C_INT, C_VA_LIST));
 85     private static final MethodHandle MH_getInt = link("getInt",
 86             MethodType.methodType(int.class, VaList.class),
 87             FunctionDescriptor.of(C_INT, C_VA_LIST));
 88     private static final MethodHandle MH_sumStruct = link("sumStruct",
 89             MethodType.methodType(int.class, VaList.class),
 90             FunctionDescriptor.of(C_INT, C_VA_LIST));
 91     private static final MethodHandle MH_sumBigStruct = link("sumBigStruct",
 92             MethodType.methodType(long.class, VaList.class),
 93             FunctionDescriptor.of(C_LONG_LONG, C_VA_LIST));
 94     private static final MethodHandle MH_sumHugeStruct = link("sumHugeStruct",
 95             MethodType.methodType(long.class, VaList.class),
 96             FunctionDescriptor.of(C_LONG_LONG, C_VA_LIST));
 97     private static final MethodHandle MH_sumFloatStruct = link("sumFloatStruct",
 98             MethodType.methodType(float.class, VaList.class),
 99             FunctionDescriptor.of(C_FLOAT, C_VA_LIST));
100     private static final MethodHandle MH_sumStack = link("sumStack",
101             MethodType.methodType(void.class, MemoryAddress.class, MemoryAddress.class, VaList.class),
102             FunctionDescriptor.ofVoid(C_POINTER, C_POINTER, C_VA_LIST));
103 
104     private static MethodHandle link(String symbol, MethodType mt, FunctionDescriptor fd) {
105         return abi.downcallHandle(LOOKUP.lookup(symbol).get(), mt, fd);
106     }
107 
108     private static MethodHandle linkVaListCB(String symbol) {
109         return link(symbol,
110                 MethodType.methodType(void.class, MemoryAddress.class),
111                 FunctionDescriptor.ofVoid(C_POINTER));
112 
113     }
114 
115     private static final Function<Consumer<VaList.Builder>, VaList> winVaListFactory
116             = actions -> Windowsx64Linker.newVaList(actions, ResourceScope.newConfinedScope());
117     private static final Function<Consumer<VaList.Builder>, VaList> sysvVaListFactory
118             = actions -> SysVx64Linker.newVaList(actions, ResourceScope.newConfinedScope());
119     private static final Function<Consumer<VaList.Builder>, VaList> linuxAArch64VaListFactory
120             = actions -> LinuxAArch64Linker.newVaList(actions, ResourceScope.newConfinedScope());
121     private static final Function<Consumer<VaList.Builder>, VaList> macAArch64VaListFactory
122             = actions -> MacOsAArch64Linker.newVaList(actions, ResourceScope.newConfinedScope());
123     private static final Function<Consumer<VaList.Builder>, VaList> platformVaListFactory
124             = (builder) -> VaList.make(builder, ResourceScope.newConfinedScope());
125 
126     private static final BiFunction<Consumer<VaList.Builder>, NativeScope, VaList> winVaListScopedFactory
127             = (builder, scope) -> Windowsx64Linker.newVaList(builder, scope.scope());
128     private static final BiFunction<Consumer<VaList.Builder>, NativeScope, VaList> sysvVaListScopedFactory
129             = (builder, scope) -> SysVx64Linker.newVaList(builder, scope.scope());
130     private static final BiFunction<Consumer<VaList.Builder>, NativeScope, VaList> linuxAArch64VaListScopedFactory
131             = (builder, scope) -> LinuxAArch64Linker.newVaList(builder, scope.scope());
132     private static final BiFunction<Consumer<VaList.Builder>, NativeScope, VaList> macAArch64VaListScopedFactory
133             = (builder, scope) -> MacOsAArch64Linker.newVaList(builder, scope.scope());
134     private static final BiFunction<Consumer<VaList.Builder>, NativeScope, VaList> platformVaListScopedFactory
135             = (builder, scope) -> VaList.make(builder, scope.scope());
136 
137     @DataProvider
138     @SuppressWarnings("unchecked")
139     public static Object[][] sumInts() {
140         Function<MemoryLayout, BiFunction<Integer, VaList, Integer>> sumIntsJavaFact = layout ->
141                 (num, list) -> IntStream.generate(() -> list.vargAsInt(layout)).limit(num).sum();
142         BiFunction<Integer, VaList, Integer> sumIntsNative
143                 = MethodHandleProxies.asInterfaceInstance(BiFunction.class, MH_sumInts);
144         return new Object[][]{
145                 { winVaListFactory,          sumIntsJavaFact.apply(Win64.C_INT),   Win64.C_INT   },
146                 { sysvVaListFactory,         sumIntsJavaFact.apply(SysV.C_INT),    SysV.C_INT    },
147                 { linuxAArch64VaListFactory, sumIntsJavaFact.apply(AArch64.C_INT), AArch64.C_INT },
148                 { macAArch64VaListFactory,   sumIntsJavaFact.apply(AArch64.C_INT), AArch64.C_INT },
149                 { platformVaListFactory,     sumIntsNative,                        C_INT         },
150         };
151     }
152 
153     @Test(dataProvider = "sumInts")
154     public void testIntSum(Function<Consumer<VaList.Builder>, VaList> vaListFactory,
155                            BiFunction<Integer, VaList, Integer> sumInts,
156                            ValueLayout intLayout) {
157         VaList vaList = vaListFactory.apply(b ->
158             b.vargFromInt(intLayout, 10)
159                     .vargFromInt(intLayout, 15)
160                     .vargFromInt(intLayout, 20));
161         int x = sumInts.apply(3, vaList);
162         assertEquals(x, 45);
163         vaList.scope().close();
164     }
165 
166     @DataProvider
167     @SuppressWarnings("unchecked")
168     public static Object[][] sumDoubles() {
169         Function<MemoryLayout, BiFunction<Integer, VaList, Double>> sumDoublesJavaFact  = layout ->
170                 (num, list) -> DoubleStream.generate(() -> list.vargAsDouble(layout)).limit(num).sum();
171         BiFunction<Integer, VaList, Double> sumDoublesNative
172                 = MethodHandleProxies.asInterfaceInstance(BiFunction.class, MH_sumDoubles);
173         return new Object[][]{
174                 { winVaListFactory,          sumDoublesJavaFact.apply(Win64.C_DOUBLE),   Win64.C_DOUBLE   },
175                 { sysvVaListFactory,         sumDoublesJavaFact.apply(SysV.C_DOUBLE),    SysV.C_DOUBLE    },
176                 { linuxAArch64VaListFactory, sumDoublesJavaFact.apply(AArch64.C_DOUBLE), AArch64.C_DOUBLE },
177                 { macAArch64VaListFactory,   sumDoublesJavaFact.apply(AArch64.C_DOUBLE), AArch64.C_DOUBLE },
178                 { platformVaListFactory,     sumDoublesNative,                           C_DOUBLE         },
179         };
180     }
181 
182     @Test(dataProvider = "sumDoubles")
183     public void testDoubleSum(Function<Consumer<VaList.Builder>, VaList> vaListFactory,
184                               BiFunction<Integer, VaList, Double> sumDoubles,
185                               ValueLayout doubleLayout) {
186         VaList vaList = vaListFactory.apply(b ->
187             b.vargFromDouble(doubleLayout, 3.0D)
188                     .vargFromDouble(doubleLayout, 4.0D)
189                     .vargFromDouble(doubleLayout, 5.0D));
190         double x = sumDoubles.apply(3, vaList);
191         assertEquals(x, 12.0D);
192         vaList.scope().close();
193     }
194 
195     @DataProvider
196     @SuppressWarnings("unchecked")
197     public static Object[][] pointers() {
198         Function<MemoryLayout, Function<VaList, Integer>> getIntJavaFact = layout ->
199                 list -> {
200                     MemoryAddress ma = list.vargAsAddress(layout);
201                     return MemoryAccess.getIntAtOffset(MemorySegment.globalNativeSegment(), ma.toRawLongValue());
202                 };
203         Function<VaList, Integer> getIntNative = MethodHandleProxies.asInterfaceInstance(Function.class, MH_getInt);
204         return new Object[][]{
205                 { winVaListFactory,          getIntJavaFact.apply(Win64.C_POINTER),   Win64.C_POINTER   },
206                 { sysvVaListFactory,         getIntJavaFact.apply(SysV.C_POINTER),    SysV.C_POINTER    },
207                 { linuxAArch64VaListFactory, getIntJavaFact.apply(AArch64.C_POINTER), AArch64.C_POINTER },
208                 { macAArch64VaListFactory,   getIntJavaFact.apply(AArch64.C_POINTER), AArch64.C_POINTER },
209                 { platformVaListFactory,     getIntNative,                            C_POINTER         },
210         };
211     }
212 
213     @Test(dataProvider = "pointers")
214     public void testVaListMemoryAddress(Function<Consumer<VaList.Builder>, VaList> vaListFactory,
215                                         Function<VaList, Integer> getFromPointer,
216                                         ValueLayout pointerLayout) {
217         try (ResourceScope scope = ResourceScope.newConfinedScope()) {
218             MemorySegment msInt = MemorySegment.allocateNative(JAVA_INT, scope);
219             MemoryAccess.setInt(msInt, 10);
220             VaList vaList = vaListFactory.apply(b -> b.vargFromAddress(pointerLayout, msInt.address()));
221             int x = getFromPointer.apply(vaList);
222             assertEquals(x, 10);
223             vaList.scope().close();
224         }
225     }
226 
227     interface TriFunction<S, T, U, R> {
228         R apply(S s, T t, U u);
229     }
230 
231     @DataProvider
232     @SuppressWarnings("unchecked")
233     public static Object[][] structs() {
234         TriFunction<MemoryLayout, VarHandle, VarHandle, Function<VaList, Integer>> sumStructJavaFact
235                 = (pointLayout, VH_Point_x, VH_Point_y) ->
236                 list -> {
237                     MemorySegment struct = list.vargAsSegment(pointLayout, ResourceScope.newImplicitScope());
238                     int x = (int) VH_Point_x.get(struct);
239                     int y = (int) VH_Point_y.get(struct);
240                     return x + y;
241                 };
242 
243         TriFunction<MemoryLayout, VarHandle, VarHandle, Function<VaList, Integer>> sumStructNativeFact
244                 = (pointLayout, VH_Point_x, VH_Point_y) ->
245                 MethodHandleProxies.asInterfaceInstance(Function.class, MH_sumStruct);
246 
247         TriFunction<Function<Consumer<VaList.Builder>, VaList>, MemoryLayout,
248                 TriFunction<MemoryLayout, VarHandle, VarHandle, Function<VaList, Integer>>, Object[]> argsFact
249                 = (vaListFact, intLayout, sumStructFact) -> {
250             GroupLayout pointLayout =  MemoryLayout.structLayout(
251                     intLayout.withName("x"),
252                     intLayout.withName("y")
253             );
254             VarHandle VH_Point_x = pointLayout.varHandle(int.class, groupElement("x"));
255             VarHandle VH_Point_y = pointLayout.varHandle(int.class, groupElement("y"));
256             return new Object[] { vaListFact, sumStructFact.apply(pointLayout, VH_Point_x, VH_Point_y),
257                     pointLayout, VH_Point_x, VH_Point_y  };
258         };
259         return new Object[][]{
260                 argsFact.apply(winVaListFactory,          Win64.C_INT,   sumStructJavaFact),
261                 argsFact.apply(sysvVaListFactory,         SysV.C_INT,    sumStructJavaFact),
262                 argsFact.apply(linuxAArch64VaListFactory, AArch64.C_INT, sumStructJavaFact),
263                 argsFact.apply(macAArch64VaListFactory,   AArch64.C_INT, sumStructJavaFact),
264                 argsFact.apply(platformVaListFactory,     C_INT,         sumStructNativeFact),
265         };
266     }
267 
268     @Test(dataProvider = "structs")
269     public void testStruct(Function<Consumer<VaList.Builder>, VaList> vaListFactory,
270                            Function<VaList, Integer> sumStruct,
271                            GroupLayout Point_LAYOUT, VarHandle VH_Point_x, VarHandle VH_Point_y) {
272         try (ResourceScope scope = ResourceScope.newConfinedScope()) {
273             MemorySegment struct = MemorySegment.allocateNative(Point_LAYOUT, scope);
274             VH_Point_x.set(struct, 5);
275             VH_Point_y.set(struct, 10);
276 
277             VaList vaList = vaListFactory.apply(b -> b.vargFromSegment(Point_LAYOUT, struct));
278             int sum = sumStruct.apply(vaList);
279             assertEquals(sum, 15);
280             vaList.scope().close();
281         }
282     }
283 
284     @DataProvider
285     @SuppressWarnings("unchecked")
286     public static Object[][] bigStructs() {
287         TriFunction<MemoryLayout, VarHandle, VarHandle, Function<VaList, Long>> sumStructJavaFact
288                 = (BigPoint_LAYOUT, VH_BigPoint_x, VH_BigPoint_y) ->
289                 list -> {
290                     MemorySegment struct = list.vargAsSegment(BigPoint_LAYOUT, ResourceScope.newImplicitScope());
291                     long x = (long) VH_BigPoint_x.get(struct);
292                     long y = (long) VH_BigPoint_y.get(struct);
293                     return x + y;
294                 };
295 
296         TriFunction<MemoryLayout, VarHandle, VarHandle, Function<VaList, Long>> sumStructNativeFact
297                 = (pointLayout, VH_BigPoint_x, VH_BigPoint_y) ->
298                 MethodHandleProxies.asInterfaceInstance(Function.class, MH_sumBigStruct);
299 
300         TriFunction<Function<Consumer<VaList.Builder>, VaList>, MemoryLayout,
301                 TriFunction<MemoryLayout, VarHandle, VarHandle, Function<VaList, Long>>, Object[]> argsFact
302                 = (vaListFact, longLongLayout, sumBigStructFact) -> {
303             GroupLayout BigPoint_LAYOUT =  MemoryLayout.structLayout(
304                     longLongLayout.withName("x"),
305                     longLongLayout.withName("y")
306             );
307             VarHandle VH_BigPoint_x = BigPoint_LAYOUT.varHandle(long.class, groupElement("x"));
308             VarHandle VH_BigPoint_y = BigPoint_LAYOUT.varHandle(long.class, groupElement("y"));
309             return new Object[] { vaListFact, sumBigStructFact.apply(BigPoint_LAYOUT, VH_BigPoint_x, VH_BigPoint_y),
310                     BigPoint_LAYOUT, VH_BigPoint_x, VH_BigPoint_y  };
311         };
312         return new Object[][]{
313                 argsFact.apply(winVaListFactory,          Win64.C_LONG_LONG,   sumStructJavaFact),
314                 argsFact.apply(sysvVaListFactory,         SysV.C_LONG_LONG,    sumStructJavaFact),
315                 argsFact.apply(linuxAArch64VaListFactory, AArch64.C_LONG_LONG, sumStructJavaFact),
316                 argsFact.apply(macAArch64VaListFactory,   AArch64.C_LONG_LONG, sumStructJavaFact),
317                 argsFact.apply(platformVaListFactory,     C_LONG_LONG,         sumStructNativeFact),
318         };
319     }
320 
321     @Test(dataProvider = "bigStructs")
322     public void testBigStruct(Function<Consumer<VaList.Builder>, VaList> vaListFactory,
323                               Function<VaList, Long> sumBigStruct,
324                               GroupLayout BigPoint_LAYOUT, VarHandle VH_BigPoint_x, VarHandle VH_BigPoint_y) {
325         try (ResourceScope scope = ResourceScope.newConfinedScope()) {
326             MemorySegment struct = MemorySegment.allocateNative(BigPoint_LAYOUT, scope);
327             VH_BigPoint_x.set(struct, 5);
328             VH_BigPoint_y.set(struct, 10);
329 
330             VaList vaList = vaListFactory.apply(b -> b.vargFromSegment(BigPoint_LAYOUT, struct));
331             long sum = sumBigStruct.apply(vaList);
332             assertEquals(sum, 15);
333             vaList.scope().close();
334         }
335     }
336 
337     @DataProvider
338     @SuppressWarnings("unchecked")
339     public static Object[][] floatStructs() {
340         TriFunction<MemoryLayout, VarHandle, VarHandle, Function<VaList, Float>> sumStructJavaFact
341                 = (FloatPoint_LAYOUT, VH_FloatPoint_x, VH_FloatPoint_y) ->
342                 list -> {
343                     MemorySegment struct = list.vargAsSegment(FloatPoint_LAYOUT, ResourceScope.newImplicitScope());
344                     float x = (float) VH_FloatPoint_x.get(struct);
345                     float y = (float) VH_FloatPoint_y.get(struct);
346                     return x + y;
347                 };
348 
349         TriFunction<MemoryLayout, VarHandle, VarHandle, Function<VaList, Float>> sumStructNativeFact
350                 = (pointLayout, VH_FloatPoint_x, VH_FloatPoint_y) ->
351                 MethodHandleProxies.asInterfaceInstance(Function.class, MH_sumFloatStruct);
352 
353         TriFunction<Function<Consumer<VaList.Builder>, VaList>, MemoryLayout,
354                 TriFunction<MemoryLayout, VarHandle, VarHandle, Function<VaList, Float>>, Object[]> argsFact
355                 = (vaListFact, floatLayout, sumFloatStructFact) -> {
356             GroupLayout FloatPoint_LAYOUT = MemoryLayout.structLayout(
357                     floatLayout.withName("x"),
358                     floatLayout.withName("y")
359             );
360             VarHandle VH_FloatPoint_x = FloatPoint_LAYOUT.varHandle(float.class, groupElement("x"));
361             VarHandle VH_FloatPoint_y = FloatPoint_LAYOUT.varHandle(float.class, groupElement("y"));
362             return new Object[] { vaListFact, sumFloatStructFact.apply(FloatPoint_LAYOUT, VH_FloatPoint_x, VH_FloatPoint_y),
363                     FloatPoint_LAYOUT, VH_FloatPoint_x, VH_FloatPoint_y  };
364         };
365         return new Object[][]{
366                 argsFact.apply(winVaListFactory,          Win64.C_FLOAT,   sumStructJavaFact),
367                 argsFact.apply(sysvVaListFactory,         SysV.C_FLOAT,    sumStructJavaFact),
368                 argsFact.apply(linuxAArch64VaListFactory, AArch64.C_FLOAT, sumStructJavaFact),
369                 argsFact.apply(macAArch64VaListFactory,   AArch64.C_FLOAT, sumStructJavaFact),
370                 argsFact.apply(platformVaListFactory,     C_FLOAT,         sumStructNativeFact),
371         };
372     }
373 
374     @Test(dataProvider = "floatStructs")
375     public void testFloatStruct(Function<Consumer<VaList.Builder>, VaList> vaListFactory,
376                                 Function<VaList, Float> sumFloatStruct,
377                                 GroupLayout FloatPoint_LAYOUT,
378                                 VarHandle VH_FloatPoint_x, VarHandle VH_FloatPoint_y) {
379         try (ResourceScope scope = ResourceScope.newConfinedScope()) {
380             MemorySegment struct = MemorySegment.allocateNative(FloatPoint_LAYOUT, scope);
381             VH_FloatPoint_x.set(struct, 1.234f);
382             VH_FloatPoint_y.set(struct, 3.142f);
383 
384             VaList vaList = vaListFactory.apply(b -> b.vargFromSegment(FloatPoint_LAYOUT, struct));
385             float sum = sumFloatStruct.apply(vaList);
386             assertEquals(sum, 4.376f, 0.00001f);
387             vaList.scope().close();
388         }
389     }
390 
391     interface QuadFunc<T0, T1, T2, T3, R> {
392         R apply(T0 t0, T1 t1, T2 t2, T3 t3);
393     }
394 
395     @DataProvider
396     @SuppressWarnings("unchecked")
397     public static Object[][] hugeStructs() {
398         QuadFunc<MemoryLayout, VarHandle, VarHandle, VarHandle, Function<VaList, Long>> sumStructJavaFact
399                 = (HugePoint_LAYOUT, VH_HugePoint_x, VH_HugePoint_y, VH_HugePoint_z) ->
400                 list -> {
401                     MemorySegment struct = list.vargAsSegment(HugePoint_LAYOUT, ResourceScope.newImplicitScope());
402                     long x = (long) VH_HugePoint_x.get(struct);
403                     long y = (long) VH_HugePoint_y.get(struct);
404                     long z = (long) VH_HugePoint_z.get(struct);
405                     return x + y + z;
406                 };
407 
408         QuadFunc<MemoryLayout, VarHandle, VarHandle, VarHandle, Function<VaList, Long>> sumStructNativeFact
409                 = (pointLayout, VH_HugePoint_x, VH_HugePoint_y, VH_HugePoint_z) ->
410                 MethodHandleProxies.asInterfaceInstance(Function.class, MH_sumHugeStruct);
411 
412         TriFunction<Function<Consumer<VaList.Builder>, VaList>, MemoryLayout,
413                 QuadFunc<MemoryLayout, VarHandle, VarHandle, VarHandle, Function<VaList, Long>>, Object[]> argsFact
414                 = (vaListFact, longLongLayout, sumBigStructFact) -> {
415             GroupLayout HugePoint_LAYOUT = MemoryLayout.structLayout(
416                     longLongLayout.withName("x"),
417                     longLongLayout.withName("y"),
418                     longLongLayout.withName("z")
419             );
420             VarHandle VH_HugePoint_x = HugePoint_LAYOUT.varHandle(long.class, groupElement("x"));
421             VarHandle VH_HugePoint_y = HugePoint_LAYOUT.varHandle(long.class, groupElement("y"));
422             VarHandle VH_HugePoint_z = HugePoint_LAYOUT.varHandle(long.class, groupElement("z"));
423             return new Object[] { vaListFact,
424                     sumBigStructFact.apply(HugePoint_LAYOUT, VH_HugePoint_x, VH_HugePoint_y, VH_HugePoint_z),
425                     HugePoint_LAYOUT, VH_HugePoint_x, VH_HugePoint_y, VH_HugePoint_z  };
426         };
427         return new Object[][]{
428                 argsFact.apply(winVaListFactory,          Win64.C_LONG_LONG,   sumStructJavaFact),
429                 argsFact.apply(sysvVaListFactory,         SysV.C_LONG_LONG,    sumStructJavaFact),
430                 argsFact.apply(linuxAArch64VaListFactory, AArch64.C_LONG_LONG, sumStructJavaFact),
431                 argsFact.apply(macAArch64VaListFactory,   AArch64.C_LONG_LONG, sumStructJavaFact),
432                 argsFact.apply(platformVaListFactory,     C_LONG_LONG,         sumStructNativeFact),
433         };
434     }
435 
436     @Test(dataProvider = "hugeStructs")
437     public void testHugeStruct(Function<Consumer<VaList.Builder>, VaList> vaListFactory,
438                                Function<VaList, Long> sumHugeStruct,
439                                GroupLayout HugePoint_LAYOUT,
440                                VarHandle VH_HugePoint_x, VarHandle VH_HugePoint_y, VarHandle VH_HugePoint_z) {
441         // On AArch64 a struct needs to be larger than 16 bytes to be
442         // passed by reference.
443         try (ResourceScope scope = ResourceScope.newConfinedScope()) {
444             MemorySegment struct = MemorySegment.allocateNative(HugePoint_LAYOUT, scope);
445             VH_HugePoint_x.set(struct, 1);
446             VH_HugePoint_y.set(struct, 2);
447             VH_HugePoint_z.set(struct, 3);
448 
449             VaList vaList = vaListFactory.apply(b -> b.vargFromSegment(HugePoint_LAYOUT, struct));
450             long sum = sumHugeStruct.apply(vaList);
451             assertEquals(sum, 6);
452             vaList.scope().close();
453         }
454     }
455 
456     public interface SumStackFunc {
457         void invoke(MemorySegment longSum, MemorySegment doubleSum, VaList list);
458     }
459 
460     @DataProvider
461     public static Object[][] sumStack() {
462         BiFunction<MemoryLayout, MemoryLayout, SumStackFunc> sumStackJavaFact = (longLayout, doubleLayout) ->
463                 (longSum, doubleSum, list) -> {
464                     long lSum = 0L;
465                     for (int i = 0; i < 16; i++) {
466                         lSum += list.vargAsLong(longLayout);
467                     }
468                     MemoryAccess.setLong(longSum, lSum);
469                     double dSum = 0D;
470                     for (int i = 0; i < 16; i++) {
471                         dSum += list.vargAsDouble(doubleLayout);
472                     }
473                     MemoryAccess.setDouble(doubleSum, dSum);
474                 };
475         SumStackFunc sumStackNative = (longSum, doubleSum, list) -> {
476             try {
477                 MH_sumStack.invokeExact(longSum.address(), doubleSum.address(), list);
478             } catch (Throwable ex) {
479                 throw new AssertionError(ex);
480             }
481         };
482         return new Object[][]{
483                 { winVaListFactory,           sumStackJavaFact.apply(Win64.C_LONG_LONG, Win64.C_DOUBLE),     Win64.C_LONG_LONG,   Win64.C_DOUBLE   },
484                 { sysvVaListFactory,          sumStackJavaFact.apply(SysV.C_LONG_LONG, SysV.C_DOUBLE),       SysV.C_LONG_LONG,    SysV.C_DOUBLE    },
485                 { linuxAArch64VaListFactory,  sumStackJavaFact.apply(AArch64.C_LONG_LONG, AArch64.C_DOUBLE), AArch64.C_LONG_LONG, AArch64.C_DOUBLE },
486                 { macAArch64VaListFactory,    sumStackJavaFact.apply(AArch64.C_LONG_LONG, AArch64.C_DOUBLE), AArch64.C_LONG_LONG, AArch64.C_DOUBLE },
487                 { platformVaListFactory,      sumStackNative,                                                C_LONG_LONG,         C_DOUBLE         },
488         };
489     }
490 
491     @Test(dataProvider = "sumStack")
492     public void testStack(Function<Consumer<VaList.Builder>, VaList> vaListFactory,
493                           SumStackFunc sumStack,
494                           ValueLayout longLayout,
495                           ValueLayout doubleLayout) {
496         try (ResourceScope scope = ResourceScope.newConfinedScope()) {
497             MemorySegment longSum = MemorySegment.allocateNative(longLayout, scope);
498             MemorySegment doubleSum = MemorySegment.allocateNative(doubleLayout, scope);
499             MemoryAccess.setLong(longSum, 0L);
500             MemoryAccess.setDouble(doubleSum, 0D);
501 
502             VaList list = vaListFactory.apply(b -> {
503                 for (long l = 1; l <= 16L; l++) {
504                     b.vargFromLong(longLayout, l);
505                 }
506                 for (double d = 1; d <= 16D; d++) {
507                     b.vargFromDouble(doubleLayout, d);
508                 }
509             });
510 
511             try {
512                 sumStack.invoke(longSum, doubleSum, list);
513             } finally {
514                 list.scope().close();
515             }
516 
517             long lSum = MemoryAccess.getLong(longSum);
518             double dSum = MemoryAccess.getDouble(doubleSum);
519 
520             assertEquals(lSum, 136L);
521             assertEquals(dSum, 136D);
522         }
523     }
524 
525     @Test(dataProvider = "upcalls")
526     public void testUpcall(MethodHandle target, MethodHandle callback) throws Throwable {
527         FunctionDescriptor desc = FunctionDescriptor.ofVoid(C_VA_LIST);
528         try (ResourceScope scope = ResourceScope.newConfinedScope()) {
529             MemoryAddress stub = abi.upcallStub(callback, desc, scope);
530             target.invokeExact(stub.address());
531         }
532     }
533 
534     @DataProvider
535     public Object[][] emptyVaLists() {
536         return new Object[][] {
537                 { Windowsx64Linker.emptyVaList()           },
538                 { winVaListFactory.apply(b -> {})          },
539                 { SysVx64Linker.emptyVaList()              },
540                 { sysvVaListFactory.apply(b -> {})         },
541                 { LinuxAArch64Linker.emptyVaList()         },
542                 { linuxAArch64VaListFactory.apply(b -> {}) },
543                 { MacOsAArch64Linker.emptyVaList()         },
544                 { macAArch64VaListFactory.apply(b -> {})   },
545         };
546     }
547 
548     @Test(expectedExceptions = UnsupportedOperationException.class,
549             expectedExceptionsMessageRegExp = ".*Scope cannot be closed.*",
550             dataProvider = "emptyVaLists")
551     public void testEmptyNotCloseable(VaList emptyList) {
552         emptyList.scope().close();
553     }
554 
555     @DataProvider
556     @SuppressWarnings("unchecked")
557     public static Object[][] sumIntsScoped() {
558         Function<MemoryLayout, BiFunction<Integer, VaList, Integer>> sumIntsJavaFact = layout ->
559                 (num, list) -> IntStream.generate(() -> list.vargAsInt(layout)).limit(num).sum();
560         BiFunction<Integer, VaList, Integer> sumIntsNative
561                 = MethodHandleProxies.asInterfaceInstance(BiFunction.class, MH_sumInts);
562         return new Object[][]{
563                 { winVaListScopedFactory,          sumIntsJavaFact.apply(Win64.C_INT),   Win64.C_INT   },
564                 { sysvVaListScopedFactory,         sumIntsJavaFact.apply(SysV.C_INT),    SysV.C_INT    },
565                 { linuxAArch64VaListScopedFactory, sumIntsJavaFact.apply(AArch64.C_INT), AArch64.C_INT },
566                 { macAArch64VaListScopedFactory,   sumIntsJavaFact.apply(AArch64.C_INT), AArch64.C_INT },
567                 { platformVaListScopedFactory,     sumIntsNative,                        C_INT         },
568         };
569     }
570 
571     @Test(dataProvider = "sumIntsScoped")
572     public void testScopedVaList(BiFunction<Consumer<VaList.Builder>, NativeScope, VaList> vaListFactory,
573                                  BiFunction<Integer, VaList, Integer> sumInts,
574                                  ValueLayout intLayout) {
575         VaList listLeaked;
576         try (NativeScope scope = new NativeScope()) {
577             VaList list = vaListFactory.apply(b -> b.vargFromInt(intLayout, 4)
578                             .vargFromInt(intLayout, 8),
579                     scope);
580             int x = sumInts.apply(2, list);
581             assertEquals(x, 12);
582             listLeaked = list;
583         }
584         assertFalse(listLeaked.scope().isAlive());
585     }
586 
587     @Test(dataProvider = "structs")
588     public void testScopeMSRead(Function<Consumer<VaList.Builder>, VaList> vaListFactory,
589                                 Function<VaList, Integer> sumStruct, // ignored
590                                 GroupLayout Point_LAYOUT, VarHandle VH_Point_x, VarHandle VH_Point_y) {
591         MemorySegment pointOut;
592         try (NativeScope scope = new NativeScope()) {
593             try (ResourceScope innerScope = ResourceScope.newConfinedScope()) {
594                 MemorySegment pointIn = MemorySegment.allocateNative(Point_LAYOUT, innerScope);
595                 VH_Point_x.set(pointIn, 3);
596                 VH_Point_y.set(pointIn, 6);
597                 VaList list = vaListFactory.apply(b -> b.vargFromSegment(Point_LAYOUT, pointIn));
598                 pointOut = list.vargAsSegment(Point_LAYOUT, scope);
599                 assertEquals((int) VH_Point_x.get(pointOut), 3);
600                 assertEquals((int) VH_Point_y.get(pointOut), 6);
601                 list.scope().close();
602                 assertTrue(pointOut.scope().isAlive()); // after VaList freed
603             }
604             assertTrue(pointOut.scope().isAlive()); // after inner scope freed
605         }
606         assertFalse(pointOut.scope().isAlive()); // after outer scope freed
607     }
608 
609     @DataProvider
610     public Object[][] copy() {
611         return new Object[][] {
612                 { winVaListFactory,          Win64.C_INT   },
613                 { sysvVaListFactory,         SysV.C_INT    },
614                 { linuxAArch64VaListFactory, AArch64.C_INT },
615                 { macAArch64VaListFactory,   AArch64.C_INT },
616         };
617     }
618 
619     @Test(dataProvider = "copy")
620     public void testCopy(Function<Consumer<VaList.Builder>, VaList> vaListFactory, ValueLayout intLayout) {
621         VaList list = vaListFactory.apply(b -> b.vargFromInt(intLayout, 4)
622                 .vargFromInt(intLayout, 8));
623         VaList  copy = list.copy();
624         assertEquals(copy.vargAsInt(intLayout), 4);
625         assertEquals(copy.vargAsInt(intLayout), 8);
626 
627 //        try { // this logic only works on Windows!
628 //            int x = copy.vargAsInt(intLayout);
629 //            fail();
630 //        } catch (IndexOutOfBoundsException ex) {
631 //            // ok - we exhausted the list
632 //        }
633 
634         assertEquals(list.vargAsInt(intLayout), 4);
635         assertEquals(list.vargAsInt(intLayout), 8);
636         list.scope().close();
637     }
638 
639     @Test(dataProvider = "copy",
640             expectedExceptions = IllegalStateException.class)
641     public void testCopyUnusableAfterOriginalClosed(Function<Consumer<VaList.Builder>, VaList> vaListFactory,
642                                                     ValueLayout intLayout) {
643         VaList list = vaListFactory.apply(b -> b.vargFromInt(intLayout, 4)
644                 .vargFromInt(intLayout, 8));
645         VaList copy = list.copy();
646         list.scope().close();
647 
648         copy.vargAsInt(intLayout); // should throw
649     }
650 
651     @DataProvider
652     public static Object[][] upcalls() {
653         GroupLayout BigPoint_LAYOUT = MemoryLayout.structLayout(
654                 C_LONG_LONG.withName("x"),
655                 C_LONG_LONG.withName("y")
656         );
657         VarHandle VH_BigPoint_x = BigPoint_LAYOUT.varHandle(long.class, groupElement("x"));
658         VarHandle VH_BigPoint_y = BigPoint_LAYOUT.varHandle(long.class, groupElement("y"));
659         GroupLayout Point_LAYOUT = MemoryLayout.structLayout(
660                 C_INT.withName("x"),
661                 C_INT.withName("y")
662         );
663         VarHandle VH_Point_x = Point_LAYOUT.varHandle(int.class, groupElement("x"));
664         VarHandle VH_Point_y = Point_LAYOUT.varHandle(int.class, groupElement("y"));
665         GroupLayout FloatPoint_LAYOUT = MemoryLayout.structLayout(
666                 C_FLOAT.withName("x"),
667                 C_FLOAT.withName("y")
668         );
669         VarHandle VH_FloatPoint_x = FloatPoint_LAYOUT.varHandle(float.class, groupElement("x"));
670         VarHandle VH_FloatPoint_y = FloatPoint_LAYOUT.varHandle(float.class, groupElement("y"));
671         GroupLayout HugePoint_LAYOUT = MemoryLayout.structLayout(
672                 C_LONG_LONG.withName("x"),
673                 C_LONG_LONG.withName("y"),
674                 C_LONG_LONG.withName("z")
675         );
676         VarHandle VH_HugePoint_x = HugePoint_LAYOUT.varHandle(long.class, groupElement("x"));
677         VarHandle VH_HugePoint_y = HugePoint_LAYOUT.varHandle(long.class, groupElement("y"));
678         VarHandle VH_HugePoint_z = HugePoint_LAYOUT.varHandle(long.class, groupElement("z"));
679 
680         return new Object[][]{
681                 { linkVaListCB("upcallBigStruct"), VaListConsumer.mh(vaList -> {
682                     MemorySegment struct = vaList.vargAsSegment(BigPoint_LAYOUT, ResourceScope.newImplicitScope());
683                     assertEquals((long) VH_BigPoint_x.get(struct), 8);
684                     assertEquals((long) VH_BigPoint_y.get(struct), 16);
685                 })},
686                 { linkVaListCB("upcallBigStruct"), VaListConsumer.mh(vaList -> {
687                     VaList copy = vaList.copy();
688                     MemorySegment struct = vaList.vargAsSegment(BigPoint_LAYOUT, ResourceScope.newImplicitScope());
689                     assertEquals((long) VH_BigPoint_x.get(struct), 8);
690                     assertEquals((long) VH_BigPoint_y.get(struct), 16);
691 
692                     VH_BigPoint_x.set(struct, 0);
693                     VH_BigPoint_y.set(struct, 0);
694 
695                     // should be independent
696                     struct = copy.vargAsSegment(BigPoint_LAYOUT, ResourceScope.newImplicitScope());
697                     assertEquals((long) VH_BigPoint_x.get(struct), 8);
698                     assertEquals((long) VH_BigPoint_y.get(struct), 16);
699                 })},
700                 { linkVaListCB("upcallBigStructPlusScalar"), VaListConsumer.mh(vaList -> {
701                     MemorySegment struct = vaList.vargAsSegment(BigPoint_LAYOUT, ResourceScope.newImplicitScope());
702                     assertEquals((long) VH_BigPoint_x.get(struct), 8);
703                     assertEquals((long) VH_BigPoint_y.get(struct), 16);
704 
705                     assertEquals(vaList.vargAsLong(C_LONG_LONG), 42);
706                 })},
707                 { linkVaListCB("upcallBigStructPlusScalar"), VaListConsumer.mh(vaList -> {
708                     vaList.skip(BigPoint_LAYOUT);
709                     assertEquals(vaList.vargAsLong(C_LONG_LONG), 42);
710                 })},
711                 { linkVaListCB("upcallStruct"), VaListConsumer.mh(vaList -> {
712                     MemorySegment struct = vaList.vargAsSegment(Point_LAYOUT, ResourceScope.newImplicitScope());
713                     assertEquals((int) VH_Point_x.get(struct), 5);
714                     assertEquals((int) VH_Point_y.get(struct), 10);
715                 })},
716                 { linkVaListCB("upcallHugeStruct"), VaListConsumer.mh(vaList -> {
717                     MemorySegment struct = vaList.vargAsSegment(HugePoint_LAYOUT, ResourceScope.newImplicitScope());
718                     assertEquals((long) VH_HugePoint_x.get(struct), 1);
719                     assertEquals((long) VH_HugePoint_y.get(struct), 2);
720                     assertEquals((long) VH_HugePoint_z.get(struct), 3);
721                 })},
722                 { linkVaListCB("upcallFloatStruct"), VaListConsumer.mh(vaList -> {
723                     MemorySegment struct = vaList.vargAsSegment(FloatPoint_LAYOUT, ResourceScope.newImplicitScope());
724                     assertEquals((float) VH_FloatPoint_x.get(struct), 1.0f);
725                     assertEquals((float) VH_FloatPoint_y.get(struct), 2.0f);
726                 })},
727                 { linkVaListCB("upcallMemoryAddress"), VaListConsumer.mh(vaList -> {
728                     MemoryAddress intPtr = vaList.vargAsAddress(C_POINTER);
729                     MemorySegment ms = intPtr.asSegment(C_INT.byteSize(), ResourceScope.globalScope());
730                     int x = MemoryAccess.getInt(ms);
731                     assertEquals(x, 10);
732                 })},
733                 { linkVaListCB("upcallDoubles"), VaListConsumer.mh(vaList -> {
734                     assertEquals(vaList.vargAsDouble(C_DOUBLE), 3.0);
735                     assertEquals(vaList.vargAsDouble(C_DOUBLE), 4.0);
736                     assertEquals(vaList.vargAsDouble(C_DOUBLE), 5.0);
737                 })},
738                 { linkVaListCB("upcallInts"), VaListConsumer.mh(vaList -> {
739                     assertEquals(vaList.vargAsInt(C_INT), 10);
740                     assertEquals(vaList.vargAsInt(C_INT), 15);
741                     assertEquals(vaList.vargAsInt(C_INT), 20);
742                 })},
743                 { linkVaListCB("upcallStack"), VaListConsumer.mh(vaList -> {
744                     // skip all registers
745                     for (long l = 1; l <= 16; l++) {
746                         assertEquals(vaList.vargAsLong(C_LONG_LONG), l);
747                     }
748                     for (double d = 1; d <= 16; d++) {
749                         assertEquals(vaList.vargAsDouble(C_DOUBLE), d);
750                     }
751 
752                     // test some arbitrary values on the stack
753                     assertEquals((byte) vaList.vargAsInt(C_INT), (byte) 1);
754                     assertEquals((char) vaList.vargAsInt(C_INT), 'a');
755                     assertEquals((short) vaList.vargAsInt(C_INT), (short) 3);
756                     assertEquals(vaList.vargAsInt(C_INT), 4);
757                     assertEquals(vaList.vargAsLong(C_LONG_LONG), 5L);
758                     assertEquals((float) vaList.vargAsDouble(C_DOUBLE), 6.0F);
759                     assertEquals(vaList.vargAsDouble(C_DOUBLE), 7.0D);
760                     assertEquals((byte) vaList.vargAsInt(C_INT), (byte) 8);
761                     assertEquals((char) vaList.vargAsInt(C_INT), 'b');
762                     assertEquals((short) vaList.vargAsInt(C_INT), (short) 10);
763                     assertEquals(vaList.vargAsInt(C_INT), 11);
764                     assertEquals(vaList.vargAsLong(C_LONG_LONG), 12L);
765                     assertEquals((float) vaList.vargAsDouble(C_DOUBLE), 13.0F);
766                     assertEquals(vaList.vargAsDouble(C_DOUBLE), 14.0D);
767 
768                     MemorySegment point = vaList.vargAsSegment(Point_LAYOUT, ResourceScope.newImplicitScope());
769                     assertEquals((int) VH_Point_x.get(point), 5);
770                     assertEquals((int) VH_Point_y.get(point), 10);
771 
772                     VaList copy = vaList.copy();
773                     MemorySegment bigPoint = vaList.vargAsSegment(BigPoint_LAYOUT, ResourceScope.newImplicitScope());
774                     assertEquals((long) VH_BigPoint_x.get(bigPoint), 15);
775                     assertEquals((long) VH_BigPoint_y.get(bigPoint), 20);
776 
777                     VH_BigPoint_x.set(bigPoint, 0);
778                     VH_BigPoint_y.set(bigPoint, 0);
779 
780                     // should be independent
781                     MemorySegment struct = copy.vargAsSegment(BigPoint_LAYOUT, ResourceScope.newImplicitScope());
782                     assertEquals((long) VH_BigPoint_x.get(struct), 15);
783                     assertEquals((long) VH_BigPoint_y.get(struct), 20);
784                 })},
785                 // test skip
786                 { linkVaListCB("upcallStack"), VaListConsumer.mh(vaList -> {
787                     vaList.skip(C_LONG_LONG, C_LONG_LONG, C_LONG_LONG, C_LONG_LONG);
788                     assertEquals(vaList.vargAsLong(C_LONG_LONG), 5L);
789                     vaList.skip(C_LONG_LONG, C_LONG_LONG, C_LONG_LONG, C_LONG_LONG);
790                     assertEquals(vaList.vargAsLong(C_LONG_LONG), 10L);
791                     vaList.skip(C_LONG_LONG, C_LONG_LONG, C_LONG_LONG, C_LONG_LONG, C_LONG_LONG, C_LONG_LONG);
792                     assertEquals(vaList.vargAsDouble(C_DOUBLE), 1.0D);
793                     vaList.skip(C_DOUBLE, C_DOUBLE, C_DOUBLE, C_DOUBLE);
794                     assertEquals(vaList.vargAsDouble(C_DOUBLE), 6.0D);
795                 })},
796         };
797     }
798 
799     interface VaListConsumer {
800         void accept(VaList list);
801 
802         static MethodHandle mh(VaListConsumer instance) {
803             try {
804                 return MethodHandles.lookup().findVirtual(VaListConsumer.class, "accept",
805                         MethodType.methodType(void.class, VaList.class)).bindTo(instance);
806             } catch (ReflectiveOperationException e) {
807                 throw new InternalError(e);
808             }
809         }
810     }
811 }