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.SysVVaList;
 41 import jdk.internal.foreign.abi.x64.sysv.SysVx64Linker;
 42 import jdk.internal.foreign.abi.x64.windows.Windowsx64Linker;
 43 
 44 import java.lang.invoke.MethodHandle;
 45 import java.lang.invoke.MethodHandles;
 46 import java.lang.invoke.MethodType;
 47 import java.lang.invoke.VarHandle;
 48 import java.util.List;
 49 import java.util.function.Consumer;
 50 import java.util.stream.IntStream;
 51 
 52 import static java.lang.invoke.MethodHandles.collectArguments;
 53 import static java.lang.invoke.MethodHandles.identity;
 54 import static java.lang.invoke.MethodHandles.insertArguments;
 55 import static java.lang.invoke.MethodHandles.permuteArguments;
 56 import static java.lang.invoke.MethodType.methodType;
 57 import static jdk.incubator.foreign.CSupport.*;
 58 
 59 public class SharedUtils {
 60 
 61     private static final MethodHandle MH_ALLOC_BUFFER;
 62     private static final MethodHandle MH_BASEADDRESS;
 63     private static final MethodHandle MH_BUFFER_COPY;
 64 
 65     static {
 66         try {
 67             var lookup = MethodHandles.lookup();
 68             MH_ALLOC_BUFFER = lookup.findStatic(SharedUtils.class, "allocateNative",
 69                     methodType(MemorySegment.class, MemoryLayout.class));
 70             MH_BASEADDRESS = lookup.findVirtual(MemorySegment.class, "baseAddress",
 71                     methodType(MemoryAddress.class));
 72             MH_BUFFER_COPY = lookup.findStatic(SharedUtils.class, "bufferCopy",
 73                     methodType(MemoryAddress.class, MemoryAddress.class, MemorySegment.class));
 74         } catch (ReflectiveOperationException e) {
 75             throw new BootstrapMethodError(e);
 76         }
 77     }
 78 
 79     // workaround for https://bugs.openjdk.java.net/browse/JDK-8239083
 80     private static MemorySegment allocateNative(MemoryLayout layout) {
 81         return MemorySegment.allocateNative(layout);
 82     }
 83 
 84     /**
 85      * Align the specified type from a given address
 86      * @return The address the data should be at based on alignment requirement
 87      */
 88     public static long align(MemoryLayout t, boolean isVar, long addr) {
 89         return alignUp(addr, alignment(t, isVar));
 90     }
 91 
 92     public static long alignUp(long addr, long alignment) {
 93         return ((addr - 1) | (alignment - 1)) + 1;
 94     }
 95 
 96     /**
 97      * The alignment requirement for a given type
 98      * @param isVar indicate if the type is a standalone variable. This change how
 99      * array is aligned. for example.
100      */
101     public static long alignment(MemoryLayout t, boolean isVar) {
102         if (t instanceof ValueLayout) {
103             return alignmentOfScalar((ValueLayout) t);
104         } else if (t instanceof SequenceLayout) {
105             // when array is used alone
106             return alignmentOfArray((SequenceLayout) t, isVar);
107         } else if (t instanceof GroupLayout) {
108             return alignmentOfContainer((GroupLayout) t);
109         } else if (t.isPadding()) {
110             return 1;
111         } else {
112             throw new IllegalArgumentException("Invalid type: " + t);
113         }
114     }
115 
116     private static long alignmentOfScalar(ValueLayout st) {
117         return st.byteSize();
118     }
119 
120     private static long alignmentOfArray(SequenceLayout ar, boolean isVar) {
121         if (ar.elementCount().orElseThrow() == 0) {
122             // VLA or incomplete
123             return 16;
124         } else if ((ar.byteSize()) >= 16 && isVar) {
125             return 16;
126         } else {
127             // align as element type
128             MemoryLayout elementType = ar.elementLayout();
129             return alignment(elementType, false);
130         }
131     }
132 
133     private static long alignmentOfContainer(GroupLayout ct) {
134         // Most strict member
135         return ct.memberLayouts().stream().mapToLong(t -> alignment(t, false)).max().orElse(1);
136     }
137 
138     /**
139      * Takes a MethodHandle that takes an input buffer as a first argument (a MemoryAddress), and returns nothing,
140      * and adapts it to return a MemorySegment, by allocating a MemorySegment for the input
141      * buffer, calling the target MethodHandle, and then returning the allocated MemorySegment.
142      *
143      * This allows viewing a MethodHandle that makes use of in memory return (IMR) as a MethodHandle that just returns
144      * a MemorySegment without requiring a pre-allocated buffer as an explicit input.
145      *
146      * @param handle the target handle to adapt
147      * @param cDesc the function descriptor of the native function (with actual return layout)
148      * @return the adapted handle
149      */
150     public static MethodHandle adaptDowncallForIMR(MethodHandle handle, FunctionDescriptor cDesc) {
151         if (handle.type().returnType() != void.class)
152             throw new IllegalArgumentException("return expected to be void for in memory returns");
153         if (handle.type().parameterType(0) != MemoryAddress.class)
154             throw new IllegalArgumentException("MemoryAddress expected as first param");
155         if (cDesc.returnLayout().isEmpty())
156             throw new IllegalArgumentException("Return layout needed: " + cDesc);
157 
158         MethodHandle ret = identity(MemorySegment.class); // (MemorySegment) MemorySegment
159         handle = collectArguments(ret, 1, handle); // (MemorySegment, MemoryAddress ...) MemorySegment
160         handle = collectArguments(handle, 1, MH_BASEADDRESS); // (MemorySegment, MemorySegment ...) MemorySegment
161         MethodType oldType = handle.type(); // (MemorySegment, MemorySegment, ...) MemorySegment
162         MethodType newType = oldType.dropParameterTypes(0, 1); // (MemorySegment, ...) MemorySegment
163         int[] reorder = IntStream.range(-1, newType.parameterCount()).toArray();
164         reorder[0] = 0; // [0, 0, 1, 2, 3, ...]
165         handle = permuteArguments(handle, newType, reorder); // (MemorySegment, ...) MemoryAddress
166         handle = collectArguments(handle, 0, insertArguments(MH_ALLOC_BUFFER, 0, cDesc.returnLayout().get())); // (...) MemoryAddress
167 
168         return handle;
169     }
170 
171     /**
172      * Takes a MethodHandle that returns a MemorySegment, and adapts it to take an input buffer as a first argument
173      * (a MemoryAddress), and upon invocation, copies the contents of the returned MemorySegment into the input buffer
174      * passed as the first argument.
175      *
176      * @param target the target handle to adapt
177      * @return the adapted handle
178      */
179     public static MethodHandle adaptUpcallForIMR(MethodHandle target) {
180         if (target.type().returnType() != MemorySegment.class)
181             throw new IllegalArgumentException("Must return MemorySegment for IMR");
182 
183         target = collectArguments(MH_BUFFER_COPY, 1, target); // (MemoryAddress, ...) MemoryAddress
184 
185         return target;
186     }
187 
188     private static MemoryAddress bufferCopy(MemoryAddress dest, MemorySegment buffer) {
189         MemoryAddressImpl.ofLongUnchecked(dest.toRawLongValue(), buffer.byteSize())
190                 .segment().copyFrom(buffer);
191         return dest;
192     }
193 
194     public static void checkCompatibleType(Class<?> carrier, MemoryLayout layout, long addressSize) {
195         if (carrier.isPrimitive()) {
196             Utils.checkPrimitiveCarrierCompat(carrier, layout);
197         } else if (carrier == MemoryAddress.class) {
198             Utils.checkLayoutType(layout, ValueLayout.class);
199             if (layout.bitSize() != addressSize)
200                 throw new IllegalArgumentException("Address size mismatch: " + addressSize + " != " + layout.bitSize());
201         } else if (carrier == MemorySegment.class) {
202             Utils.checkLayoutType(layout, GroupLayout.class);
203         } else {
204             throw new IllegalArgumentException("Unsupported carrier: " + carrier);
205         }
206     }
207 
208     public static void checkFunctionTypes(MethodType mt, FunctionDescriptor cDesc, long addressSize) {
209         if (mt.returnType() == void.class != cDesc.returnLayout().isEmpty())
210             throw new IllegalArgumentException("Return type mismatch: " + mt + " != " + cDesc);
211         List<MemoryLayout> argLayouts = cDesc.argumentLayouts();
212         if (mt.parameterCount() != argLayouts.size())
213             throw new IllegalArgumentException("Arity mismatch: " + mt + " != " + cDesc);
214 
215         int paramCount = mt.parameterCount();
216         for (int i = 0; i < paramCount; i++) {
217             checkCompatibleType(mt.parameterType(i), argLayouts.get(i), addressSize);
218         }
219         cDesc.returnLayout().ifPresent(rl -> checkCompatibleType(mt.returnType(), rl, addressSize));
220     }
221 
222     public static Class<?> primitiveCarrierForSize(long size) {
223         if (size == 1) {
224             return byte.class;
225         } else if(size == 2) {
226             return short.class;
227         } else if (size <= 4) {
228             return int.class;
229         } else if (size <= 8) {
230             return long.class;
231         }
232 
233         throw new IllegalArgumentException("Size too large: " + size);
234     }
235 
236     public static ForeignLinker getSystemLinker() {
237         String arch = System.getProperty("os.arch");
238         String os = System.getProperty("os.name");
239         if (arch.equals("amd64") || arch.equals("x86_64")) {
240             if (os.startsWith("Windows")) {
241                 return Windowsx64Linker.getInstance();
242             } else {
243                 return SysVx64Linker.getInstance();
244             }
245         } else if (arch.equals("aarch64")) {
246             return AArch64Linker.getInstance();
247         }
248         throw new UnsupportedOperationException("Unsupported os or arch: " + os + ", " + arch);
249     }
250 
251     public static VaList newVaList(Consumer<VaList.Builder> actions) {
252         String name = CSupport.getSystemLinker().name();
253         return switch(name) {
254             case Win64.NAME -> Windowsx64Linker.newVaList(actions);
255             case SysV.NAME -> SysVx64Linker.newVaList(actions);
256             case AArch64.NAME -> throw new UnsupportedOperationException("Not yet implemented for this platform");
257             default -> throw new IllegalStateException("Unknown linker name: " + name);
258         };
259     }
260 
261     public static VarHandle vhPrimitiveOrAddress(Class<?> carrier, MemoryLayout layout) {
262         return carrier == MemoryAddress.class
263             ? MemoryHandles.asAddressVarHandle(layout.varHandle(primitiveCarrierForSize(layout.byteSize())))
264             : layout.varHandle(carrier);
265     }
266 
267     public static VaList newVaListOfAddress(MemoryAddress ma) {
268         String name = CSupport.getSystemLinker().name();
269         return switch(name) {
270             case Win64.NAME -> Windowsx64Linker.newVaListOfAddress(ma);
271             case SysV.NAME -> SysVx64Linker.newVaListOfAddress(ma);
272             case AArch64.NAME -> throw new UnsupportedOperationException("Not yet implemented for this platform");
273             default -> throw new IllegalStateException("Unknown linker name: " + name);
274         };
275     }
276 
277     public static VaList emptyVaList() {
278         String name = CSupport.getSystemLinker().name();
279         return switch(name) {
280             case Win64.NAME -> Windowsx64Linker.emptyVaList();
281             case SysV.NAME -> SysVx64Linker.emptyVaList();
282             case AArch64.NAME -> throw new UnsupportedOperationException("Not yet implemented for this platform");
283             default -> throw new IllegalStateException("Unknown linker name: " + name);
284         };
285     }
286 
287     public static MethodType convertVaListCarriers(MethodType mt, Class<?> carrier) {
288         Class<?>[] params = new Class<?>[mt.parameterCount()];
289         for (int i = 0; i < params.length; i++) {
290             Class<?> pType = mt.parameterType(i);
291             params[i] = ((pType == VaList.class) ? carrier : pType);
292         }
293         return methodType(mt.returnType(), params);
294     }
295 
296     public static MethodHandle unboxVaLists(MethodType type, MethodHandle handle, MethodHandle unboxer) {
297         for (int i = 0; i < type.parameterCount(); i++) {
298             if (type.parameterType(i) == VaList.class) {
299                handle = MethodHandles.filterArguments(handle, i, unboxer);
300             }
301         }
302         return handle;
303     }
304 
305     public static MethodHandle boxVaLists(MethodHandle handle, MethodHandle boxer) {
306         MethodType type = handle.type();
307         for (int i = 0; i < type.parameterCount(); i++) {
308             if (type.parameterType(i) == VaList.class) {
309                handle = MethodHandles.filterArguments(handle, i, boxer);
310             }
311         }
312         return handle;
313     }
314 
315     public static class SimpleVaArg {
316         public final Class<?> carrier;
317         public final MemoryLayout layout;
318         public final Object value;
319 
320         public SimpleVaArg(Class<?> carrier, MemoryLayout layout, Object value) {
321             this.carrier = carrier;
322             this.layout = layout;
323             this.value = value;
324         }
325 
326         public VarHandle varHandle() {
327             return carrier == MemoryAddress.class
328                 ? MemoryHandles.asAddressVarHandle(layout.varHandle(primitiveCarrierForSize(layout.byteSize())))
329                 : layout.varHandle(carrier);
330         }
331     }
332 
333     public static class EmptyVaList implements CSupport.VaList {
334 
335         private final MemoryAddress address;
336 
337         public EmptyVaList(MemoryAddress address) {
338             this.address = address;
339         }
340 
341         private static UnsupportedOperationException uoe() {
342             return new UnsupportedOperationException("Empty VaList");
343         }
344 
345         @Override
346         public int vargAsInt(MemoryLayout layout) {
347             throw uoe();
348         }
349 
350         @Override
351         public long vargAsLong(MemoryLayout layout) {
352             throw uoe();
353         }
354 
355         @Override
356         public double vargAsDouble(MemoryLayout layout) {
357             throw uoe();
358         }
359 
360         @Override
361         public MemoryAddress vargAsAddress(MemoryLayout layout) {
362             throw uoe();
363         }
364 
365         @Override
366         public MemorySegment vargAsSegment(MemoryLayout layout) {
367             throw uoe();
368         }
369 
370         @Override
371         public void skip(MemoryLayout... layouts) {
372             throw uoe();
373         }
374 
375         @Override
376         public boolean isAlive() {
377             return true;
378         }
379 
380         @Override
381         public void close() {
382             throw uoe();
383         }
384 
385         @Override
386         public VaList copy() {
387             return this;
388         }
389 
390         @Override
391         public MemoryAddress address() {
392             return address;
393         }
394     }
395 }