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. 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 package jdk.internal.foreign.abi; 24 25 import jdk.incubator.foreign.MemoryAddress; 26 import jdk.incubator.foreign.MemoryHandles; 27 import jdk.incubator.foreign.MemorySegment; 28 import jdk.incubator.foreign.NativeScope; 29 import jdk.internal.foreign.MemoryAddressImpl; 30 import jdk.internal.foreign.Utils; 31 32 import java.lang.invoke.MethodHandle; 33 import java.lang.invoke.MethodHandles; 34 import java.lang.invoke.MethodType; 35 import java.lang.invoke.VarHandle; 36 import java.nio.ByteOrder; 37 import java.util.ArrayList; 38 import java.util.Arrays; 39 import java.util.List; 40 import java.util.Map; 41 import java.util.concurrent.ConcurrentHashMap; 42 import java.util.stream.Collectors; 43 import java.util.stream.IntStream; 44 45 import static java.lang.invoke.MethodHandles.collectArguments; 46 import static java.lang.invoke.MethodHandles.dropArguments; 47 import static java.lang.invoke.MethodHandles.empty; 48 import static java.lang.invoke.MethodHandles.filterArguments; 49 import static java.lang.invoke.MethodHandles.identity; 50 import static java.lang.invoke.MethodHandles.insertArguments; 51 import static java.lang.invoke.MethodHandles.permuteArguments; 52 import static java.lang.invoke.MethodHandles.tryFinally; 53 import static java.lang.invoke.MethodType.methodType; 54 import static sun.security.action.GetBooleanAction.privilegedGetProperty; 55 56 /** 57 * This class implements native call invocation through a so called 'universal adapter'. A universal adapter takes 58 * an array of longs together with a call 'recipe', which is used to move the arguments in the right places as 59 * expected by the system ABI. 60 */ 61 public class ProgrammableInvoker { 62 private static final boolean DEBUG = 63 privilegedGetProperty("jdk.internal.foreign.ProgrammableInvoker.DEBUG"); 64 private static final boolean NO_SPEC = 65 privilegedGetProperty("jdk.internal.foreign.ProgrammableInvoker.NO_SPEC"); 66 67 private static final VarHandle VH_LONG = MemoryHandles.varHandle(long.class, ByteOrder.nativeOrder()); 68 69 private static final MethodHandle MH_INVOKE_MOVES; 70 private static final MethodHandle MH_INVOKE_INTERP_BINDINGS; 71 72 private static final MethodHandle MH_MAKE_ALLOCATOR; 73 private static final MethodHandle MH_CLOSE_ALLOCATOR; 74 75 private static final Map<ABIDescriptor, Long> adapterStubs = new ConcurrentHashMap<>(); 76 77 static { 78 try { 79 MethodHandles.Lookup lookup = MethodHandles.lookup(); 80 MH_INVOKE_MOVES = lookup.findVirtual(ProgrammableInvoker.class, "invokeMoves", 81 methodType(Object.class, Object[].class, Binding.Move[].class, Binding.Move[].class)); 82 MH_INVOKE_INTERP_BINDINGS = lookup.findVirtual(ProgrammableInvoker.class, "invokeInterpBindings", 83 methodType(Object.class, Object[].class, MethodHandle.class, Map.class, Map.class)); 84 MH_MAKE_ALLOCATOR = lookup.findStatic(NativeScope.class, "boundedScope", 85 methodType(NativeScope.class, long.class)); 86 MH_CLOSE_ALLOCATOR = lookup.findVirtual(NativeScope.class, "close", 87 methodType(void.class)); 88 } catch (ReflectiveOperationException e) { 89 throw new RuntimeException(e); 90 } 91 } 92 93 private final ABIDescriptor abi; 94 private final BufferLayout layout; 95 private final long stackArgsBytes; 96 97 private final CallingSequence callingSequence; 98 99 private final MemoryAddress addr; 100 private final long stubAddress; 101 102 private final long bufferCopySize; 103 104 public ProgrammableInvoker(ABIDescriptor abi, MemoryAddress addr, CallingSequence callingSequence) { 105 this.abi = abi; 106 this.layout = BufferLayout.of(abi); 107 this.stubAddress = adapterStubs.computeIfAbsent(abi, key -> generateAdapter(key, layout)); 108 109 this.addr = addr; 110 this.callingSequence = callingSequence; 111 112 this.stackArgsBytes = callingSequence.argMoveBindings() 113 .map(Binding.Move::storage) 114 .filter(s -> abi.arch.isStackType(s.type())) 115 .count() 116 * abi.arch.typeSize(abi.arch.stackType()); 117 118 this.bufferCopySize = bufferCopySize(callingSequence); 119 } 120 121 private static long bufferCopySize(CallingSequence callingSequence) { 122 // FIXME: > 16 bytes alignment might need extra space since the 123 // starting address of the allocator might be un-aligned. 124 long size = 0; 125 for (int i = 0; i < callingSequence.argumentCount(); i++) { 126 List<Binding> bindings = callingSequence.argumentBindings(i); 127 for (Binding b : bindings) { 128 if (b instanceof Binding.Copy) { 129 Binding.Copy c = (Binding.Copy) b; 130 size = Utils.alignUp(size, c.alignment()); 131 size += c.size(); 132 } 133 } 134 } 135 return size; 136 } 137 138 public MethodHandle getBoundMethodHandle() { 139 Binding.Move[] argMoves = callingSequence.argMoveBindings().toArray(Binding.Move[]::new); 140 Class<?>[] argMoveTypes = Arrays.stream(argMoves).map(Binding.Move::type).toArray(Class<?>[]::new); 141 142 Binding.Move[] retMoves = callingSequence.retMoveBindings().toArray(Binding.Move[]::new); 143 Class<?> returnType = retMoves.length == 0 144 ? void.class 145 : retMoves.length == 1 146 ? retMoves[0].type() 147 : Object[].class; 148 149 MethodType leafType = methodType(returnType, argMoveTypes); 150 151 MethodHandle handle = insertArguments(MH_INVOKE_MOVES.bindTo(this), 1, argMoves, retMoves) 152 .asCollector(Object[].class, leafType.parameterCount()) 153 .asType(leafType); 154 155 if (NO_SPEC || retMoves.length > 1) { 156 Map<VMStorage, Integer> argIndexMap = indexMap(argMoves); 157 Map<VMStorage, Integer> retIndexMap = indexMap(retMoves); 158 159 handle = insertArguments(MH_INVOKE_INTERP_BINDINGS.bindTo(this), 1, handle, argIndexMap, retIndexMap); 160 handle = handle.asCollector(Object[].class, callingSequence.methodType().parameterCount()) 161 .asType(callingSequence.methodType()); 162 } else { 163 handle = specialize(handle); 164 } 165 166 return handle; 167 } 168 169 private MethodHandle specialize(MethodHandle leafHandle) { 170 MethodType highLevelType = callingSequence.methodType(); 171 MethodType leafType = leafHandle.type(); 172 173 MethodHandle specializedHandle = leafHandle; // initial 174 175 int insertPos = -1; 176 if (bufferCopySize > 0) { 177 specializedHandle = dropArguments(specializedHandle, 0, NativeScope.class); 178 insertPos++; 179 } 180 for (int i = 0; i < highLevelType.parameterCount(); i++) { 181 List<Binding> bindings = callingSequence.argumentBindings(i); 182 insertPos += bindings.stream().filter(Binding.Move.class::isInstance).count() + 1; 183 // We interpret the bindings in reverse since we have to construct a MethodHandle from the bottom up 184 for (int j = bindings.size() - 1; j >= 0; j--) { 185 Binding binding = bindings.get(j); 186 if (binding.tag() == Binding.Tag.MOVE) { 187 insertPos--; 188 } else { 189 specializedHandle = binding.specializeUnbox(specializedHandle, insertPos); 190 } 191 } 192 } 193 194 if (highLevelType.returnType() != void.class) { 195 MethodHandle returnFilter = identity(highLevelType.returnType()); 196 List<Binding> bindings = callingSequence.returnBindings(); 197 for (int j = bindings.size() - 1; j >= 0; j--) { 198 Binding binding = bindings.get(j); 199 returnFilter = binding.specializeBox(returnFilter); 200 } 201 specializedHandle = MethodHandles.filterReturnValue(specializedHandle, returnFilter); 202 } 203 204 if (bufferCopySize > 0) { 205 // insert try-finally to close the NativeScope used for Binding.Copy 206 MethodHandle closer = leafType.returnType() == void.class 207 // (Throwable, NativeScope) -> void 208 ? collectArguments(empty(methodType(void.class, Throwable.class)), 1, MH_CLOSE_ALLOCATOR) 209 // (Throwable, V, NativeScope) -> V 210 : collectArguments(dropArguments(identity(specializedHandle.type().returnType()), 0, Throwable.class), 211 2, MH_CLOSE_ALLOCATOR); 212 specializedHandle = tryFinally(specializedHandle, closer); 213 specializedHandle = collectArguments(specializedHandle, 0, insertArguments(MH_MAKE_ALLOCATOR, 0, bufferCopySize)); 214 } 215 return specializedHandle; 216 } 217 218 private Map<VMStorage, Integer> indexMap(Binding.Move[] moves) { 219 return IntStream.range(0, moves.length) 220 .boxed() 221 .collect(Collectors.toMap(i -> moves[i].storage(), i -> i)); 222 } 223 224 /** 225 * Does a native invocation by moving primitive values from the arg array into an intermediate buffer 226 * and calling the assembly stub that forwards arguments from the buffer to the target function 227 * 228 * @param args an array of primitive values to be copied in to the buffer 229 * @param argBindings Binding.Move values describing how arguments should be copied 230 * @param returnBindings Binding.Move values describing how return values should be copied 231 * @return null, a single primitive value, or an Object[] of primitive values 232 */ 233 Object invokeMoves(Object[] args, Binding.Move[] argBindings, Binding.Move[] returnBindings) { 234 MemorySegment stackArgsSeg = null; 235 try (MemorySegment argBuffer = MemorySegment.allocateNative(layout.size, 64)) { 236 MemoryAddress argsPtr = argBuffer.baseAddress(); 237 MemoryAddress stackArgs; 238 if (stackArgsBytes > 0) { 239 stackArgsSeg = MemorySegment.allocateNative(stackArgsBytes, 8); 240 stackArgs = stackArgsSeg.baseAddress(); 241 } else { 242 stackArgs = MemoryAddressImpl.NULL; 243 } 244 245 VH_LONG.set(argsPtr.addOffset(layout.arguments_next_pc), addr.toRawLongValue()); 246 VH_LONG.set(argsPtr.addOffset(layout.stack_args_bytes), stackArgsBytes); 247 VH_LONG.set(argsPtr.addOffset(layout.stack_args), stackArgs.toRawLongValue()); 248 249 for (int i = 0; i < argBindings.length; i++) { 250 Binding.Move binding = argBindings[i]; 251 VMStorage storage = binding.storage(); 252 MemoryAddress ptr = abi.arch.isStackType(storage.type()) 253 ? stackArgs.addOffset(storage.index() * abi.arch.typeSize(abi.arch.stackType())) 254 : argsPtr.addOffset(layout.argOffset(storage)); 255 SharedUtils.writeOverSized(ptr, binding.type(), args[i]); 256 } 257 258 if (DEBUG) { 259 System.err.println("Buffer state before:"); 260 layout.dump(abi.arch, argsPtr, System.err); 261 } 262 263 invokeNative(stubAddress, argsPtr.toRawLongValue()); 264 265 if (DEBUG) { 266 System.err.println("Buffer state after:"); 267 layout.dump(abi.arch, argsPtr, System.err); 268 } 269 270 if (returnBindings.length == 0) { 271 return null; 272 } else if (returnBindings.length == 1) { 273 Binding.Move move = returnBindings[0]; 274 VMStorage storage = move.storage(); 275 return SharedUtils.read(argsPtr.addOffset(layout.retOffset(storage)), move.type()); 276 } else { // length > 1 277 Object[] returns = new Object[returnBindings.length]; 278 for (int i = 0; i < returnBindings.length; i++) { 279 Binding.Move move = returnBindings[i]; 280 VMStorage storage = move.storage(); 281 returns[i] = SharedUtils.read(argsPtr.addOffset(layout.retOffset(storage)), move.type()); 282 } 283 return returns; 284 } 285 } finally { 286 if (stackArgsSeg != null) { 287 stackArgsSeg.close(); 288 } 289 } 290 } 291 292 Object invokeInterpBindings(Object[] args, MethodHandle leaf, 293 Map<VMStorage, Integer> argIndexMap, 294 Map<VMStorage, Integer> retIndexMap) throws Throwable { 295 NativeScope scope = bufferCopySize != 0 ? NativeScope.boundedScope(bufferCopySize) : null; 296 try { 297 // do argument processing, get Object[] as result 298 Object[] moves = new Object[leaf.type().parameterCount()]; 299 for (int i = 0; i < args.length; i++) { 300 Object arg = args[i]; 301 BindingInterpreter.unbox(arg, callingSequence.argumentBindings(i), 302 (storage, type, value) -> { 303 moves[argIndexMap.get(storage)] = value; 304 }, scope); 305 } 306 307 // call leaf 308 Object o = leaf.invokeWithArguments(moves); 309 310 // return value processing 311 if (o == null) { 312 return null; 313 } else if (o instanceof Object[]) { 314 Object[] oArr = (Object[]) o; 315 return BindingInterpreter.box(callingSequence.returnBindings(), 316 (storage, type) -> oArr[retIndexMap.get(storage)]); 317 } else { 318 return BindingInterpreter.box(callingSequence.returnBindings(), (storage, type) -> o); 319 } 320 } finally { 321 if (scope != null) { 322 scope.close(); 323 } 324 } 325 } 326 327 //natives 328 329 static native void invokeNative(long adapterStub, long buff); 330 static native long generateAdapter(ABIDescriptor abi, BufferLayout layout); 331 332 private static native void registerNatives(); 333 static { 334 registerNatives(); 335 } 336 } 337