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 }