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.SymbolLookup;
 39 import jdk.incubator.foreign.MemoryAddress;
 40 import jdk.incubator.foreign.MemoryLayout;
 41 
 42 import java.lang.invoke.MethodHandle;
 43 import java.lang.invoke.MethodType;
 44 import java.util.ArrayList;
 45 import java.util.List;
 46 import java.util.function.Consumer;
 47 
 48 import jdk.incubator.foreign.MemorySegment;
 49 import jdk.incubator.foreign.SegmentAllocator;
 50 import org.testng.annotations.*;
 51 import static org.testng.Assert.*;
 52 
 53 public class TestDowncall extends CallGeneratorHelper {
 54 
 55     static CLinker abi = CLinker.getInstance();
 56     static {
 57         System.loadLibrary("TestDowncall");
 58     }
 59 
 60     static final SymbolLookup LOOKUP = SymbolLookup.loaderLookup();
 61 
 62     @Test(dataProvider="functions", dataProviderClass=CallGeneratorHelper.class)
 63     public void testDowncall(int count, String fName, Ret ret, List<ParamType> paramTypes, List<StructFieldType> fields) throws Throwable {
 64         List<Consumer<Object>> checks = new ArrayList<>();
 65         MemoryAddress addr = LOOKUP.lookup(fName).get();
 66         MethodType mt = methodType(ret, paramTypes, fields);
 67         FunctionDescriptor descriptor = function(ret, paramTypes, fields);
 68         Object[] args = makeArgs(paramTypes, fields, checks);
 69         try (NativeScope scope = new NativeScope()) {
 70             boolean needsScope = mt.returnType().equals(MemorySegment.class);
 71             Object res = doCall(addr, scope, mt, descriptor, args);
 72             if (ret == Ret.NON_VOID) {
 73                 checks.forEach(c -> c.accept(res));
 74                 if (needsScope) {
 75                     // check that return struct has indeed been allocated in the native scope
 76                     assertEquals(((MemorySegment) res).scope(), scope.scope());
 77                     assertEquals(scope.allocatedBytes(), descriptor.returnLayout().get().byteSize());
 78                 } else {
 79                     // if here, there should be no allocation through the scope!
 80                     assertEquals(scope.allocatedBytes(), 0L);
 81                 }
 82             } else {
 83                 // if here, there should be no allocation through the scope!
 84                 assertEquals(scope.allocatedBytes(), 0L);
 85             }
 86         }
 87     }
 88 
 89     @Test(dataProvider="functions", dataProviderClass=CallGeneratorHelper.class)
 90     public void testDowncallNoScope(int count, String fName, Ret ret, List<ParamType> paramTypes, List<StructFieldType> fields) throws Throwable {
 91         List<Consumer<Object>> checks = new ArrayList<>();
 92         MemoryAddress addr = LOOKUP.lookup(fName).get();
 93         MethodType mt = methodType(ret, paramTypes, fields);
 94         FunctionDescriptor descriptor = function(ret, paramTypes, fields);
 95         Object[] args = makeArgs(paramTypes, fields, checks);
 96         boolean needsScope = mt.returnType().equals(MemorySegment.class);
 97         Object res = doCall(addr, IMPLICIT_ALLOCATOR, mt, descriptor, args);
 98         if (ret == Ret.NON_VOID) {
 99             checks.forEach(c -> c.accept(res));
100             if (needsScope) {
101                 // check that return struct has indeed been allocated in the default scope
102                 try {
103                     ((MemorySegment)res).scope().close(); // should throw
104                     fail("Expected exception!");
105                 } catch (UnsupportedOperationException ex) {
106                     // ok
107                 }
108             }
109         }
110     }
111 
112     Object doCall(MemoryAddress addr, SegmentAllocator allocator, MethodType type, FunctionDescriptor descriptor, Object[] args) throws Throwable {
113         MethodHandle mh = abi.downcallHandle(addr, allocator, type, descriptor);
114         Object res = mh.invokeWithArguments(args);
115         return res;
116     }
117 
118     static MethodType methodType(Ret ret, List<ParamType> params, List<StructFieldType> fields) {
119         MethodType mt = ret == Ret.VOID ?
120                 MethodType.methodType(void.class) : MethodType.methodType(paramCarrier(params.get(0).layout(fields)));
121         for (ParamType p : params) {
122             mt = mt.appendParameterTypes(paramCarrier(p.layout(fields)));
123         }
124         return mt;
125     }
126 
127     static FunctionDescriptor function(Ret ret, List<ParamType> params, List<StructFieldType> fields) {
128         MemoryLayout[] paramLayouts = params.stream().map(p -> p.layout(fields)).toArray(MemoryLayout[]::new);
129         return ret == Ret.VOID ?
130                 FunctionDescriptor.ofVoid(paramLayouts) :
131                 FunctionDescriptor.of(paramLayouts[0], paramLayouts);
132     }
133 
134     static Object[] makeArgs(List<ParamType> params, List<StructFieldType> fields, List<Consumer<Object>> checks) throws ReflectiveOperationException {
135         Object[] args = new Object[params.size()];
136         for (int i = 0 ; i < params.size() ; i++) {
137             args[i] = makeArg(params.get(i).layout(fields), checks, i == 0);
138         }
139         return args;
140     }
141 }