1 /*
  2  *  Copyright (c) 2020, 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  * @requires ((os.arch == "amd64" | os.arch == "x86_64") & sun.arch.data.model == "64") | os.arch == "aarch64"
 28  * @modules jdk.incubator.foreign/jdk.internal.foreign
 29  * @build NativeTestHelper CallGeneratorHelper TestDowncall
 30  *
 31  * @run testng/othervm -XX:+IgnoreUnrecognizedVMOptions -XX:-VerifyDependencies
 32  *   --enable-native-access=ALL-UNNAMED -Dgenerator.sample.factor=17
 33  *   TestDowncall
 34  */
 35 
 36 import jdk.incubator.foreign.CLinker;
 37 import jdk.incubator.foreign.FunctionDescriptor;
 38 import jdk.incubator.foreign.GroupLayout;
 39 import jdk.incubator.foreign.NativeSymbol;
 40 import jdk.incubator.foreign.ResourceScope;
 41 import jdk.incubator.foreign.SymbolLookup;
 42 import jdk.incubator.foreign.MemoryLayout;
 43 
 44 import java.lang.invoke.MethodHandle;
 45 import java.util.ArrayList;
 46 import java.util.List;
 47 import java.util.function.Consumer;
 48 import java.util.stream.Stream;
 49 
 50 import jdk.incubator.foreign.MemorySegment;
 51 import jdk.incubator.foreign.SegmentAllocator;
 52 import org.testng.annotations.*;
 53 import static org.testng.Assert.*;
 54 
 55 public class TestDowncall extends CallGeneratorHelper {
 56 
 57     static CLinker abi = CLinker.systemCLinker();
 58     static {
 59         System.loadLibrary("TestDowncall");
 60         System.loadLibrary("TestDowncallStack");
 61     }
 62 
 63     static final SymbolLookup LOOKUP = SymbolLookup.loaderLookup();
 64 
 65     @Test(dataProvider="functions", dataProviderClass=CallGeneratorHelper.class)
 66     public void testDowncall(int count, String fName, Ret ret, List<ParamType> paramTypes, List<StructFieldType> fields) throws Throwable {
 67         List<Consumer<Object>> checks = new ArrayList<>();
 68         NativeSymbol addr = LOOKUP.lookup(fName).get();
 69         FunctionDescriptor descriptor = function(ret, paramTypes, fields);
 70         Object[] args = makeArgs(paramTypes, fields, checks);
 71         try (ResourceScope scope = ResourceScope.newSharedScope()) {
 72             boolean needsScope = descriptor.returnLayout().map(GroupLayout.class::isInstance).orElse(false);
 73             SegmentAllocator allocator = needsScope ?
 74                     SegmentAllocator.newNativeArena(scope) :
 75                     THROWING_ALLOCATOR;
 76             Object res = doCall(addr, allocator, descriptor, args);
 77             if (ret == Ret.NON_VOID) {
 78                 checks.forEach(c -> c.accept(res));
 79                 if (needsScope) {
 80                     // check that return struct has indeed been allocated in the native scope
 81                     assertEquals(((MemorySegment) res).scope(), scope);
 82                 }
 83             }
 84         }
 85     }
 86 
 87     @Test(dataProvider="functions", dataProviderClass=CallGeneratorHelper.class)
 88     public void testDowncallStack(int count, String fName, Ret ret, List<ParamType> paramTypes, List<StructFieldType> fields) throws Throwable {
 89         List<Consumer<Object>> checks = new ArrayList<>();
 90         NativeSymbol addr = LOOKUP.lookup("s" + fName).get();
 91         FunctionDescriptor descriptor = functionStack(ret, paramTypes, fields);
 92         Object[] args = makeArgsStack(paramTypes, fields, checks);
 93         try (ResourceScope scope = ResourceScope.newSharedScope()) {
 94             boolean needsScope = descriptor.returnLayout().map(GroupLayout.class::isInstance).orElse(false);
 95             SegmentAllocator allocator = needsScope ?
 96                     SegmentAllocator.newNativeArena(scope) :
 97                     THROWING_ALLOCATOR;
 98             Object res = doCall(addr, allocator, descriptor, args);
 99             if (ret == Ret.NON_VOID) {
100                 checks.forEach(c -> c.accept(res));
101                 if (needsScope) {
102                     // check that return struct has indeed been allocated in the native scope
103                     assertEquals(((MemorySegment) res).scope(), scope);
104                 }
105             }
106         }
107     }
108 
109     Object doCall(NativeSymbol symbol, SegmentAllocator allocator, FunctionDescriptor descriptor, Object[] args) throws Throwable {
110         MethodHandle mh = downcallHandle(abi, symbol, allocator, descriptor);
111         Object res = mh.invokeWithArguments(args);
112         return res;
113     }
114 
115     static FunctionDescriptor functionStack(Ret ret, List<ParamType> params, List<StructFieldType> fields) {
116         return function(ret, params, fields, STACK_PREFIX_LAYOUTS);
117     }
118 
119     static FunctionDescriptor function(Ret ret, List<ParamType> params, List<StructFieldType> fields) {
120         return function(ret, params, fields, List.of());
121     }
122 
123     static FunctionDescriptor function(Ret ret, List<ParamType> params, List<StructFieldType> fields, List<MemoryLayout> prefix) {
124         List<MemoryLayout> pLayouts = params.stream().map(p -> p.layout(fields)).toList();
125         MemoryLayout[] paramLayouts = Stream.concat(prefix.stream(), pLayouts.stream()).toArray(MemoryLayout[]::new);
126         return ret == Ret.VOID ?
127                 FunctionDescriptor.ofVoid(paramLayouts) :
128                 FunctionDescriptor.of(paramLayouts[prefix.size()], paramLayouts);
129     }
130 
131     static Object[] makeArgsStack(List<ParamType> params, List<StructFieldType> fields, List<Consumer<Object>> checks) throws ReflectiveOperationException {
132         return makeArgs(params, fields, checks, STACK_PREFIX_LAYOUTS);
133     }
134 
135     static Object[] makeArgs(List<ParamType> params, List<StructFieldType> fields, List<Consumer<Object>> checks) throws ReflectiveOperationException {
136         return makeArgs(params, fields, checks, List.of());
137     }
138 
139     static Object[] makeArgs(List<ParamType> params, List<StructFieldType> fields, List<Consumer<Object>> checks, List<MemoryLayout> prefix) throws ReflectiveOperationException {
140         Object[] args = new Object[prefix.size() + params.size()];
141         int argNum = 0;
142         for (MemoryLayout layout : prefix) {
143             args[argNum++] = makeArg(layout, null, false);
144         }
145         for (int i = 0 ; i < params.size() ; i++) {
146             args[argNum++] = makeArg(params.get(i).layout(fields), checks, i == 0);
147         }
148         return args;
149     }
150 }