1 /*
  2  * Copyright (c) 2020, 2023, 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.internal.access.JavaLangAccess;
 28 import jdk.internal.access.JavaLangInvokeAccess;
 29 import jdk.internal.access.SharedSecrets;
 30 import jdk.internal.foreign.CABI;
 31 import jdk.internal.foreign.abi.AbstractLinker.UpcallStubFactory;
 32 import jdk.internal.foreign.abi.aarch64.linux.LinuxAArch64Linker;
 33 import jdk.internal.foreign.abi.aarch64.macos.MacOsAArch64Linker;
 34 import jdk.internal.foreign.abi.aarch64.windows.WindowsAArch64Linker;
 35 import jdk.internal.foreign.abi.fallback.FallbackLinker;
 36 import jdk.internal.foreign.abi.ppc64.linux.LinuxPPC64Linker;
 37 import jdk.internal.foreign.abi.ppc64.linux.LinuxPPC64leLinker;
 38 import jdk.internal.foreign.abi.riscv64.linux.LinuxRISCV64Linker;
 39 import jdk.internal.foreign.abi.s390.linux.LinuxS390Linker;
 40 import jdk.internal.foreign.abi.x64.sysv.SysVx64Linker;
 41 import jdk.internal.foreign.abi.x64.windows.Windowsx64Linker;
 42 import jdk.internal.vm.annotation.ForceInline;
 43 
 44 import java.lang.foreign.AddressLayout;
 45 import java.lang.foreign.Arena;
 46 import java.lang.foreign.Linker;
 47 import java.lang.foreign.FunctionDescriptor;
 48 import java.lang.foreign.GroupLayout;
 49 import java.lang.foreign.MemoryLayout;
 50 import java.lang.foreign.MemorySegment;
 51 import java.lang.foreign.MemorySegment.Scope;
 52 import java.lang.foreign.SegmentAllocator;
 53 import java.lang.foreign.ValueLayout;
 54 import java.lang.invoke.MethodHandle;
 55 import java.lang.invoke.MethodHandles;
 56 import java.lang.invoke.MethodType;
 57 import java.lang.invoke.VarHandle;
 58 import java.lang.ref.Reference;
 59 import java.nio.ByteOrder;
 60 import java.nio.charset.StandardCharsets;
 61 import java.util.Arrays;
 62 import java.util.Map;
 63 import java.util.Objects;
 64 import java.util.stream.Collectors;
 65 import java.util.stream.IntStream;
 66 
 67 import static java.lang.foreign.ValueLayout.*;
 68 import static java.lang.invoke.MethodHandles.*;
 69 import static java.lang.invoke.MethodType.methodType;
 70 
 71 public final class SharedUtils {
 72 
 73     private SharedUtils() {
 74     }
 75 
 76     private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
 77     private static final JavaLangInvokeAccess JLIA = SharedSecrets.getJavaLangInvokeAccess();
 78 
 79     private static final MethodHandle MH_ALLOC_BUFFER;
 80     private static final MethodHandle MH_BUFFER_COPY;
 81     private static final MethodHandle MH_REACHABILITY_FENCE;
 82     public static final MethodHandle MH_CHECK_SYMBOL;
 83     private static final MethodHandle MH_CHECK_CAPTURE_SEGMENT;
 84 
 85     public static final AddressLayout C_POINTER = ADDRESS
 86             .withTargetLayout(MemoryLayout.sequenceLayout(JAVA_BYTE));
 87 
 88     public static final Arena DUMMY_ARENA = new Arena() {
 89         @Override
 90         public Scope scope() {
 91             throw new UnsupportedOperationException();
 92         }
 93 
 94         @Override
 95         public MemorySegment allocate(long byteSize) {
 96             throw new UnsupportedOperationException();
 97         }
 98 
 99         @Override
100         public void close() {
101             // do nothing
102         }
103     };
104 
105     static {
106         try {
107             MethodHandles.Lookup lookup = MethodHandles.lookup();
108             MH_ALLOC_BUFFER = lookup.findVirtual(SegmentAllocator.class, "allocate",
109                     methodType(MemorySegment.class, MemoryLayout.class));
110             MH_BUFFER_COPY = lookup.findStatic(SharedUtils.class, "bufferCopy",
111                     methodType(MemorySegment.class, MemorySegment.class, MemorySegment.class));
112             MH_REACHABILITY_FENCE = lookup.findStatic(Reference.class, "reachabilityFence",
113                     methodType(void.class, Object.class));
114             MH_CHECK_SYMBOL = lookup.findStatic(SharedUtils.class, "checkSymbol",
115                     methodType(void.class, MemorySegment.class));
116             MH_CHECK_CAPTURE_SEGMENT = lookup.findStatic(SharedUtils.class, "checkCaptureSegment",
117                     methodType(MemorySegment.class, MemorySegment.class));
118         } catch (ReflectiveOperationException e) {
119             throw new BootstrapMethodError(e);
120         }
121     }
122 
123     // this allocator should be used when no allocation is expected
124     public static final SegmentAllocator THROWING_ALLOCATOR = (size, align) -> {
125         throw new IllegalStateException("Cannot get here");
126     };
127 
128     public static long alignUp(long addr, long alignment) {
129         return ((addr - 1) | (alignment - 1)) + 1;
130     }
131 
132     /**
133      * Takes a MethodHandle that takes an input buffer as a first argument (a MemorySegment), and returns nothing,
134      * and adapts it to return a MemorySegment, by allocating a MemorySegment for the input
135      * buffer, calling the target MethodHandle, and then returning the allocated MemorySegment.
136      *
137      * This allows viewing a MethodHandle that makes use of in memory return (IMR) as a MethodHandle that just returns
138      * a MemorySegment without requiring a pre-allocated buffer as an explicit input.
139      *
140      * @param handle the target handle to adapt
141      * @param cDesc the function descriptor of the native function (with actual return layout)
142      * @return the adapted handle
143      */
144     public static MethodHandle adaptDowncallForIMR(MethodHandle handle, FunctionDescriptor cDesc, CallingSequence sequence) {
145         if (handle.type().returnType() != void.class)
146             throw new IllegalArgumentException("return expected to be void for in memory returns: " + handle.type());
147         int imrAddrIdx = sequence.numLeadingParams();
148         if (handle.type().parameterType(imrAddrIdx) != MemorySegment.class)
149             throw new IllegalArgumentException("MemorySegment expected as third param: " + handle.type());
150         if (cDesc.returnLayout().isEmpty())
151             throw new IllegalArgumentException("Return layout needed: " + cDesc);
152 
153         MethodHandle ret = identity(MemorySegment.class); // (MemorySegment) MemorySegment
154         handle = collectArguments(ret, 1, handle); // (MemorySegment, MemorySegment, SegmentAllocator, MemorySegment, ...) MemorySegment
155         handle = mergeArguments(handle, 0, 1 + imrAddrIdx);  // (MemorySegment, MemorySegment, SegmentAllocator, ...) MemorySegment
156         handle = collectArguments(handle, 0, insertArguments(MH_ALLOC_BUFFER, 1, cDesc.returnLayout().get())); // (SegmentAllocator, MemorySegment, SegmentAllocator, ...) MemorySegment
157         handle = mergeArguments(handle, 0, 2);  // (SegmentAllocator, MemorySegment, ...) MemorySegment
158         handle = swapArguments(handle, 0, 1); // (MemorySegment, SegmentAllocator, ...) MemorySegment
159         return handle;
160     }
161 
162     /**
163      * Takes a MethodHandle that returns a MemorySegment, and adapts it to take an input buffer as a first argument
164      * (a MemorySegment), and upon invocation, copies the contents of the returned MemorySegment into the input buffer
165      * passed as the first argument.
166      *
167      * @param target the target handle to adapt
168      * @return the adapted handle
169      */
170     private static MethodHandle adaptUpcallForIMR(MethodHandle target, boolean dropReturn) {
171         if (target.type().returnType() != MemorySegment.class)
172             throw new IllegalArgumentException("Must return MemorySegment for IMR");
173 
174         target = collectArguments(MH_BUFFER_COPY, 1, target); // (MemorySegment, ...) MemorySegment
175 
176         if (dropReturn) { // no handling for return value, need to drop it
177             target = dropReturn(target);
178         } else {
179             // adjust return type so it matches the inferred type of the effective
180             // function descriptor
181             target = target.asType(target.type().changeReturnType(MemorySegment.class));
182         }
183 
184         return target;
185     }
186 
187     public static UpcallStubFactory arrangeUpcallHelper(MethodType targetType, boolean isInMemoryReturn, boolean dropReturn,
188                                                         ABIDescriptor abi, CallingSequence callingSequence) {
189         if (isInMemoryReturn) {
190             // simulate the adaptation to get the type
191             MethodHandle fakeTarget = MethodHandles.empty(targetType);
192             targetType = adaptUpcallForIMR(fakeTarget, dropReturn).type();
193         }
194 
195         UpcallStubFactory factory = UpcallLinker.makeFactory(targetType, abi, callingSequence);
196 
197         if (isInMemoryReturn) {
198             final UpcallStubFactory finalFactory = factory;
199             factory = (target, scope) -> {
200                 target = adaptUpcallForIMR(target, dropReturn);
201                 return finalFactory.makeStub(target, scope);
202             };
203         }
204 
205         return factory;
206     }
207 
208     private static MemorySegment bufferCopy(MemorySegment dest, MemorySegment buffer) {
209         return dest.copyFrom(buffer);
210     }
211 
212     public static Class<?> primitiveCarrierForSize(long size, boolean useFloat) {
213         return primitiveLayoutForSize(size, useFloat).carrier();
214     }
215 
216     public static ValueLayout primitiveLayoutForSize(long size, boolean useFloat) {
217         if (useFloat) {
218             if (size == 4) {
219                 return JAVA_FLOAT;
220             } else if (size == 8) {
221                 return JAVA_DOUBLE;
222             }
223         } else {
224             if (size == 1) {
225                 return JAVA_BYTE;
226             } else if (size == 2) {
227                 return JAVA_SHORT;
228             } else if (size <= 4) {
229                 return JAVA_INT;
230             } else if (size <= 8) {
231                 return JAVA_LONG;
232             }
233         }
234 
235         throw new IllegalArgumentException("No layout for size: " + size + " isFloat=" + useFloat);
236     }
237 
238     public static Linker getSystemLinker() {
239         return switch (CABI.current()) {
240             case WIN_64 -> Windowsx64Linker.getInstance();
241             case SYS_V -> SysVx64Linker.getInstance();
242             case LINUX_AARCH_64 -> LinuxAArch64Linker.getInstance();
243             case MAC_OS_AARCH_64 -> MacOsAArch64Linker.getInstance();
244             case WIN_AARCH_64 -> WindowsAArch64Linker.getInstance();
245             case LINUX_PPC_64 -> LinuxPPC64Linker.getInstance();
246             case LINUX_PPC_64_LE -> LinuxPPC64leLinker.getInstance();
247             case LINUX_RISCV_64 -> LinuxRISCV64Linker.getInstance();
248             case LINUX_S390 -> LinuxS390Linker.getInstance();
249             case FALLBACK -> FallbackLinker.getInstance();
250             case UNSUPPORTED -> throw new UnsupportedOperationException("Platform does not support native linker");
251         };
252     }
253 
254     public static String toJavaStringInternal(MemorySegment segment, long start) {
255         int len = strlen(segment, start);
256         byte[] bytes = new byte[len];
257         MemorySegment.copy(segment, JAVA_BYTE, start, bytes, 0, len);
258         return new String(bytes, StandardCharsets.UTF_8);
259     }
260 
261     private static int strlen(MemorySegment segment, long start) {
262         // iterate until overflow (String can only hold a byte[], whose length can be expressed as an int)
263         for (int offset = 0; offset >= 0; offset++) {
264             byte curr = segment.get(JAVA_BYTE, start + offset);
265             if (curr == 0) {
266                 return offset;
267             }
268         }
269         throw new IllegalArgumentException("String too large");
270     }
271 
272     static Map<VMStorage, Integer> indexMap(Binding.Move[] moves) {
273         return IntStream.range(0, moves.length)
274                         .boxed()
275                         .collect(Collectors.toMap(i -> moves[i].storage(), i -> i));
276     }
277 
278     static MethodHandle mergeArguments(MethodHandle mh, int sourceIndex, int destIndex) {
279         MethodType oldType = mh.type();
280         Class<?> sourceType = oldType.parameterType(sourceIndex);
281         Class<?> destType = oldType.parameterType(destIndex);
282         if (sourceType != destType) {
283             // TODO meet?
284             throw new IllegalArgumentException("Parameter types differ: " + sourceType + " != " + destType);
285         }
286         MethodType newType = oldType.dropParameterTypes(destIndex, destIndex + 1);
287         int[] reorder = new int[oldType.parameterCount()];
288         if (destIndex < sourceIndex) {
289             sourceIndex--;
290         }
291         for (int i = 0, index = 0; i < reorder.length; i++) {
292             if (i != destIndex) {
293                 reorder[i] = index++;
294             } else {
295                 reorder[i] = sourceIndex;
296             }
297         }
298         return permuteArguments(mh, newType, reorder);
299     }
300 
301 
302     public static MethodHandle swapArguments(MethodHandle mh, int firstArg, int secondArg) {
303         MethodType mtype = mh.type();
304         int[] perms = new int[mtype.parameterCount()];
305         MethodType swappedType = MethodType.methodType(mtype.returnType());
306         for (int i = 0 ; i < perms.length ; i++) {
307             int dst = i;
308             if (i == firstArg) dst = secondArg;
309             if (i == secondArg) dst = firstArg;
310             perms[i] = dst;
311             swappedType = swappedType.appendParameterTypes(mtype.parameterType(dst));
312         }
313         return permuteArguments(mh, swappedType, perms);
314     }
315 
316     private static MethodHandle reachabilityFenceHandle(Class<?> type) {
317         return MH_REACHABILITY_FENCE.asType(MethodType.methodType(void.class, type));
318     }
319 
320     public static void handleUncaughtException(Throwable t) {
321         if (t != null) {
322             try {
323                 t.printStackTrace();
324                 System.err.println("Unrecoverable uncaught exception encountered. The VM will now exit");
325             } finally {
326                 JLA.exit(1);
327             }
328         }
329     }
330 
331     public static long unboxSegment(MemorySegment segment) {
332         if (!segment.isNative()) {
333             throw new IllegalArgumentException("Heap segment not allowed: " + segment);
334         }
335         return segment.address();
336     }
337 
338     public static void checkExceptions(MethodHandle target) {
339         Class<?>[] exceptions = JLIA.exceptionTypes(target);
340         if (exceptions != null && exceptions.length != 0) {
341             throw new IllegalArgumentException("Target handle may throw exceptions: " + Arrays.toString(exceptions));
342         }
343     }
344 
345     public static MethodHandle maybeInsertAllocator(FunctionDescriptor descriptor, MethodHandle handle) {
346         if (descriptor.returnLayout().isEmpty() || !(descriptor.returnLayout().get() instanceof GroupLayout)) {
347             // not returning segment, just insert a throwing allocator
348             handle = insertArguments(handle, 1, THROWING_ALLOCATOR);
349         }
350         return handle;
351     }
352 
353     public static MethodHandle maybeCheckCaptureSegment(MethodHandle handle, LinkerOptions options) {
354         if (options.hasCapturedCallState()) {
355             // (<target address>, SegmentAllocator, <capture segment>, ...) -> ...
356             handle = MethodHandles.filterArguments(handle, 2, MH_CHECK_CAPTURE_SEGMENT);
357         }
358         return handle;
359     }
360 
361     @ForceInline
362     public static MemorySegment checkCaptureSegment(MemorySegment captureSegment) {
363         Objects.requireNonNull(captureSegment);
364         if (captureSegment.equals(MemorySegment.NULL)) {
365             throw new IllegalArgumentException("Capture segment is NULL: " + captureSegment);
366         }
367         return captureSegment.asSlice(0, CapturableState.LAYOUT);
368     }
369 
370     @ForceInline
371     public static void checkSymbol(MemorySegment symbol) {
372         Objects.requireNonNull(symbol);
373         if (symbol.equals(MemorySegment.NULL))
374             throw new IllegalArgumentException("Symbol is NULL: " + symbol);
375     }
376 
377     static void checkType(Class<?> actualType, Class<?> expectedType) {
378         if (expectedType != actualType) {
379             throw new IllegalArgumentException(
380                     String.format("Invalid operand type: %s. %s expected", actualType, expectedType));
381         }
382     }
383 
384     public static boolean isPowerOfTwo(int width) {
385         return Integer.bitCount(width) == 1;
386     }
387 
388     static long pickChunkOffset(long chunkOffset, long byteWidth, int chunkWidth) {
389         return ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN
390                 ? byteWidth - chunkWidth - chunkOffset
391                 : chunkOffset;
392     }
393 
394     public static Arena newBoundedArena(long size) {
395         return new Arena() {
396             final Arena arena = Arena.ofConfined();
397             final SegmentAllocator slicingAllocator = SegmentAllocator.slicingAllocator(arena.allocate(size));
398 
399             @Override
400             public Scope scope() {
401                 return arena.scope();
402             }
403 
404             @Override
405             public void close() {
406                 arena.close();
407             }
408 
409             @Override
410             public MemorySegment allocate(long byteSize, long byteAlignment) {
411                 return slicingAllocator.allocate(byteSize, byteAlignment);
412             }
413         };
414     }
415 
416     public static Arena newEmptyArena() {
417         return new Arena() {
418             final Arena arena = Arena.ofConfined();
419 
420             @Override
421             public Scope scope() {
422                 return arena.scope();
423             }
424 
425             @Override
426             public void close() {
427                 arena.close();
428             }
429 
430             @Override
431             public MemorySegment allocate(long byteSize, long byteAlignment) {
432                 throw new UnsupportedOperationException();
433             }
434         };
435     }
436 
437     public static final class SimpleVaArg {
438         public final MemoryLayout layout;
439         public final Object value;
440 
441         public SimpleVaArg(MemoryLayout layout, Object value) {
442             this.layout = layout;
443             this.value = value;
444         }
445 
446         public VarHandle varHandle() {
447             return layout.varHandle();
448         }
449     }
450 
451     static void writeOverSized(MemorySegment ptr, Class<?> type, Object o) {
452         // use VH_LONG for integers to zero out the whole register in the process
453         if (type == long.class) {
454             ptr.set(JAVA_LONG_UNALIGNED, 0, (long) o);
455         } else if (type == int.class) {
456             ptr.set(JAVA_LONG_UNALIGNED, 0, (int) o);
457         } else if (type == short.class) {
458             ptr.set(JAVA_LONG_UNALIGNED, 0, (short) o);
459         } else if (type == char.class) {
460             ptr.set(JAVA_LONG_UNALIGNED, 0, (char) o);
461         } else if (type == byte.class) {
462             ptr.set(JAVA_LONG_UNALIGNED, 0, (byte) o);
463         } else if (type == float.class) {
464             ptr.set(JAVA_FLOAT_UNALIGNED, 0, (float) o);
465         } else if (type == double.class) {
466             ptr.set(JAVA_DOUBLE_UNALIGNED, 0, (double) o);
467         } else if (type == boolean.class) {
468             boolean b = (boolean)o;
469             ptr.set(JAVA_LONG_UNALIGNED, 0, b ? (long)1 : (long)0);
470         } else {
471             throw new IllegalArgumentException("Unsupported carrier: " + type);
472         }
473     }
474 
475     static void write(MemorySegment ptr, long offset, Class<?> type, Object o) {
476         if (type == long.class) {
477             ptr.set(JAVA_LONG_UNALIGNED, offset, (long) o);
478         } else if (type == int.class) {
479             ptr.set(JAVA_INT_UNALIGNED, offset, (int) o);
480         } else if (type == short.class) {
481             ptr.set(JAVA_SHORT_UNALIGNED, offset, (short) o);
482         } else if (type == char.class) {
483             ptr.set(JAVA_CHAR_UNALIGNED, offset, (char) o);
484         } else if (type == byte.class) {
485             ptr.set(JAVA_BYTE, offset, (byte) o);
486         } else if (type == float.class) {
487             ptr.set(JAVA_FLOAT_UNALIGNED, offset, (float) o);
488         } else if (type == double.class) {
489             ptr.set(JAVA_DOUBLE_UNALIGNED, offset, (double) o);
490         } else if (type == boolean.class) {
491             ptr.set(JAVA_BOOLEAN, offset, (boolean) o);
492         } else {
493             throw new IllegalArgumentException("Unsupported carrier: " + type);
494         }
495     }
496 
497     static Object read(MemorySegment ptr, long offset, Class<?> type) {
498         if (type == long.class) {
499             return ptr.get(JAVA_LONG_UNALIGNED, offset);
500         } else if (type == int.class) {
501             return ptr.get(JAVA_INT_UNALIGNED, offset);
502         } else if (type == short.class) {
503             return ptr.get(JAVA_SHORT_UNALIGNED, offset);
504         } else if (type == char.class) {
505             return ptr.get(JAVA_CHAR_UNALIGNED, offset);
506         } else if (type == byte.class) {
507             return ptr.get(JAVA_BYTE, offset);
508         } else if (type == float.class) {
509             return ptr.get(JAVA_FLOAT_UNALIGNED, offset);
510         } else if (type == double.class) {
511             return ptr.get(JAVA_DOUBLE_UNALIGNED, offset);
512         } else if (type == boolean.class) {
513             return ptr.get(JAVA_BOOLEAN, offset);
514         } else {
515             throw new IllegalArgumentException("Unsupported carrier: " + type);
516         }
517     }
518 }