1 /*
  2  * Copyright (c) 2019, 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.  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 package jdk.internal.foreign.abi;
 26 
 27 import jdk.incubator.foreign.CSupport;
 28 import jdk.incubator.foreign.ForeignLinker;
 29 import jdk.incubator.foreign.FunctionDescriptor;
 30 import jdk.incubator.foreign.GroupLayout;
 31 import jdk.incubator.foreign.MemoryAddress;
 32 import jdk.incubator.foreign.MemoryHandles;
 33 import jdk.incubator.foreign.MemoryLayout;
 34 import jdk.incubator.foreign.MemorySegment;
 35 import jdk.incubator.foreign.SequenceLayout;
 36 import jdk.incubator.foreign.ValueLayout;
 37 import jdk.internal.foreign.MemoryAddressImpl;
 38 import jdk.internal.foreign.Utils;
 39 import jdk.internal.foreign.abi.aarch64.AArch64Linker;
 40 import jdk.internal.foreign.abi.x64.sysv.SysVx64Linker;
 41 import jdk.internal.foreign.abi.x64.windows.Windowsx64Linker;
 42 
 43 import java.lang.invoke.MethodHandle;
 44 import java.lang.invoke.MethodHandles;
 45 import java.lang.invoke.MethodType;
 46 import java.lang.invoke.VarHandle;
 47 import java.util.List;
 48 import java.util.function.Consumer;
 49 import java.util.stream.IntStream;
 50 
 51 import static java.lang.invoke.MethodHandles.collectArguments;
 52 import static java.lang.invoke.MethodHandles.identity;
 53 import static java.lang.invoke.MethodHandles.insertArguments;
 54 import static java.lang.invoke.MethodHandles.permuteArguments;
 55 import static java.lang.invoke.MethodType.methodType;
 56 import static jdk.incubator.foreign.CSupport.*;
 57 
 58 public class SharedUtils {
 59 
 60     private static final MethodHandle MH_ALLOC_BUFFER;
 61     private static final MethodHandle MH_BASEADDRESS;
 62     private static final MethodHandle MH_BUFFER_COPY;
 63 
 64     static {
 65         try {
 66             var lookup = MethodHandles.lookup();
 67             MH_ALLOC_BUFFER = lookup.findStatic(SharedUtils.class, "allocateNative",
 68                     methodType(MemorySegment.class, MemoryLayout.class));
 69             MH_BASEADDRESS = lookup.findVirtual(MemorySegment.class, "baseAddress",
 70                     methodType(MemoryAddress.class));
 71             MH_BUFFER_COPY = lookup.findStatic(SharedUtils.class, "bufferCopy",
 72                     methodType(MemoryAddress.class, MemoryAddress.class, MemorySegment.class));
 73         } catch (ReflectiveOperationException e) {
 74             throw new BootstrapMethodError(e);
 75         }
 76     }
 77 
 78     // workaround for https://bugs.openjdk.java.net/browse/JDK-8239083
 79     private static MemorySegment allocateNative(MemoryLayout layout) {
 80         return MemorySegment.allocateNative(layout);
 81     }
 82 
 83     /**
 84      * Align the specified type from a given address
 85      * @return The address the data should be at based on alignment requirement
 86      */
 87     public static long align(MemoryLayout t, boolean isVar, long addr) {
 88         return alignUp(addr, alignment(t, isVar));
 89     }
 90 
 91     public static long alignUp(long addr, long alignment) {
 92         return ((addr - 1) | (alignment - 1)) + 1;
 93     }
 94 
 95     /**
 96      * The alignment requirement for a given type
 97      * @param isVar indicate if the type is a standalone variable. This change how
 98      * array is aligned. for example.
 99      */
100     public static long alignment(MemoryLayout t, boolean isVar) {
101         if (t instanceof ValueLayout) {
102             return alignmentOfScalar((ValueLayout) t);
103         } else if (t instanceof SequenceLayout) {
104             // when array is used alone
105             return alignmentOfArray((SequenceLayout) t, isVar);
106         } else if (t instanceof GroupLayout) {
107             return alignmentOfContainer((GroupLayout) t);
108         } else if (t.isPadding()) {
109             return 1;
110         } else {
111             throw new IllegalArgumentException("Invalid type: " + t);
112         }
113     }
114 
115     private static long alignmentOfScalar(ValueLayout st) {
116         return st.byteSize();
117     }
118 
119     private static long alignmentOfArray(SequenceLayout ar, boolean isVar) {
120         if (ar.elementCount().orElseThrow() == 0) {
121             // VLA or incomplete
122             return 16;
123         } else if ((ar.byteSize()) >= 16 && isVar) {
124             return 16;
125         } else {
126             // align as element type
127             MemoryLayout elementType = ar.elementLayout();
128             return alignment(elementType, false);
129         }
130     }
131 
132     private static long alignmentOfContainer(GroupLayout ct) {
133         // Most strict member
134         return ct.memberLayouts().stream().mapToLong(t -> alignment(t, false)).max().orElse(1);
135     }
136 
137     /**
138      * Takes a MethodHandle that takes an input buffer as a first argument (a MemoryAddress), and returns nothing,
139      * and adapts it to return a MemorySegment, by allocating a MemorySegment for the input
140      * buffer, calling the target MethodHandle, and then returning the allocated MemorySegment.
141      *
142      * This allows viewing a MethodHandle that makes use of in memory return (IMR) as a MethodHandle that just returns
143      * a MemorySegment without requiring a pre-allocated buffer as an explicit input.
144      *
145      * @param handle the target handle to adapt
146      * @param cDesc the function descriptor of the native function (with actual return layout)
147      * @return the adapted handle
148      */
149     public static MethodHandle adaptDowncallForIMR(MethodHandle handle, FunctionDescriptor cDesc) {
150         if (handle.type().returnType() != void.class)
151             throw new IllegalArgumentException("return expected to be void for in memory returns");
152         if (handle.type().parameterType(0) != MemoryAddress.class)
153             throw new IllegalArgumentException("MemoryAddress expected as first param");
154         if (cDesc.returnLayout().isEmpty())
155             throw new IllegalArgumentException("Return layout needed: " + cDesc);
156 
157         MethodHandle ret = identity(MemorySegment.class); // (MemorySegment) MemorySegment
158         handle = collectArguments(ret, 1, handle); // (MemorySegment, MemoryAddress ...) MemorySegment
159         handle = collectArguments(handle, 1, MH_BASEADDRESS); // (MemorySegment, MemorySegment ...) MemorySegment
160         MethodType oldType = handle.type(); // (MemorySegment, MemorySegment, ...) MemorySegment
161         MethodType newType = oldType.dropParameterTypes(0, 1); // (MemorySegment, ...) MemorySegment
162         int[] reorder = IntStream.range(-1, newType.parameterCount()).toArray();
163         reorder[0] = 0; // [0, 0, 1, 2, 3, ...]
164         handle = permuteArguments(handle, newType, reorder); // (MemorySegment, ...) MemoryAddress
165         handle = collectArguments(handle, 0, insertArguments(MH_ALLOC_BUFFER, 0, cDesc.returnLayout().get())); // (...) MemoryAddress
166 
167         return handle;
168     }
169 
170     /**
171      * Takes a MethodHandle that returns a MemorySegment, and adapts it to take an input buffer as a first argument
172      * (a MemoryAddress), and upon invocation, copies the contents of the returned MemorySegment into the input buffer
173      * passed as the first argument.
174      *
175      * @param target the target handle to adapt
176      * @return the adapted handle
177      */
178     public static MethodHandle adaptUpcallForIMR(MethodHandle target) {
179         if (target.type().returnType() != MemorySegment.class)
180             throw new IllegalArgumentException("Must return MemorySegment for IMR");
181 
182         target = collectArguments(MH_BUFFER_COPY, 1, target); // (MemoryAddress, ...) MemoryAddress
183 
184         return target;
185     }
186 
187     private static MemoryAddress bufferCopy(MemoryAddress dest, MemorySegment buffer) {
188         MemoryAddressImpl.ofLongUnchecked(dest.toRawLongValue(), buffer.byteSize())
189                 .segment().copyFrom(buffer);
190         return dest;
191     }
192 
193     public static void checkCompatibleType(Class<?> carrier, MemoryLayout layout, long addressSize) {
194         if (carrier.isPrimitive()) {
195             Utils.checkPrimitiveCarrierCompat(carrier, layout);
196         } else if (carrier == MemoryAddress.class) {
197             Utils.checkLayoutType(layout, ValueLayout.class);
198             if (layout.bitSize() != addressSize)
199                 throw new IllegalArgumentException("Address size mismatch: " + addressSize + " != " + layout.bitSize());
200         } else if (carrier == MemorySegment.class) {
201             Utils.checkLayoutType(layout, GroupLayout.class);
202         } else {
203             throw new IllegalArgumentException("Unsupported carrier: " + carrier);
204         }
205     }
206 
207     public static void checkFunctionTypes(MethodType mt, FunctionDescriptor cDesc, long addressSize) {
208         if (mt.returnType() == void.class != cDesc.returnLayout().isEmpty())
209             throw new IllegalArgumentException("Return type mismatch: " + mt + " != " + cDesc);
210         List<MemoryLayout> argLayouts = cDesc.argumentLayouts();
211         if (mt.parameterCount() != argLayouts.size())
212             throw new IllegalArgumentException("Arity mismatch: " + mt + " != " + cDesc);
213 
214         int paramCount = mt.parameterCount();
215         for (int i = 0; i < paramCount; i++) {
216             checkCompatibleType(mt.parameterType(i), argLayouts.get(i), addressSize);
217         }
218         cDesc.returnLayout().ifPresent(rl -> checkCompatibleType(mt.returnType(), rl, addressSize));
219     }
220 
221     public static Class<?> primitiveCarrierForSize(long size) {
222         if (size == 1) {
223             return byte.class;
224         } else if(size == 2) {
225             return short.class;
226         } else if (size <= 4) {
227             return int.class;
228         } else if (size <= 8) {
229             return long.class;
230         }
231 
232         throw new IllegalArgumentException("Size too large: " + size);
233     }
234 
235     public static ForeignLinker getSystemLinker() {
236         String arch = System.getProperty("os.arch");
237         String os = System.getProperty("os.name");
238         if (arch.equals("amd64") || arch.equals("x86_64")) {
239             if (os.startsWith("Windows")) {
240                 return Windowsx64Linker.getInstance();
241             } else {
242                 return SysVx64Linker.getInstance();
243             }
244         } else if (arch.equals("aarch64")) {
245             return AArch64Linker.getInstance();
246         }
247         throw new UnsupportedOperationException("Unsupported os or arch: " + os + ", " + arch);
248     }
249 
250     public static VaList newVaList(Consumer<VaList.Builder> actions) {
251         String name = CSupport.getSystemLinker().name();
252         return switch(name) {
253             case Win64.NAME -> Windowsx64Linker.newVaList(actions);
254             case SysV.NAME -> SysVx64Linker.newVaList(actions);
255             case AArch64.NAME -> throw new UnsupportedOperationException("Not yet implemented for this platform");
256             default -> throw new IllegalStateException("Unknown linker name: " + name);
257         };
258     }
259 
260     public static VarHandle vhPrimitiveOrAddress(Class<?> carrier, MemoryLayout layout) {
261         return carrier == MemoryAddress.class
262             ? MemoryHandles.asAddressVarHandle(layout.varHandle(primitiveCarrierForSize(layout.byteSize())))
263             : layout.varHandle(carrier);
264     }
265 
266     public static VaList newVaListOfAddress(MemoryAddress ma) {
267         String name = CSupport.getSystemLinker().name();
268         return switch(name) {
269             case Win64.NAME -> Windowsx64Linker.newVaListOfAddress(ma);
270             case SysV.NAME -> SysVx64Linker.newVaListOfAddress(ma);
271             case AArch64.NAME -> throw new UnsupportedOperationException("Not yet implemented for this platform");
272             default -> throw new IllegalStateException("Unknown linker name: " + name);
273         };
274     }
275 
276     public static class SimpleVaArg {
277         public final Class<?> carrier;
278         public final MemoryLayout layout;
279         public final Object value;
280 
281         public SimpleVaArg(Class<?> carrier, MemoryLayout layout, Object value) {
282             this.carrier = carrier;
283             this.layout = layout;
284             this.value = value;
285         }
286 
287         public VarHandle varHandle() {
288             return carrier == MemoryAddress.class
289                 ? MemoryHandles.asAddressVarHandle(layout.varHandle(primitiveCarrierForSize(layout.byteSize())))
290                 : layout.varHandle(carrier);
291         }
292     }
293 }