1 /*
  2  * Copyright (c) 2020, 2021, 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.MemoryHandles;
 28 import jdk.incubator.foreign.MemoryLayout;
 29 import jdk.incubator.foreign.MemorySegment;
 30 import jdk.incubator.foreign.NativeSymbol;

 31 import jdk.incubator.foreign.SegmentAllocator;
 32 import jdk.incubator.foreign.ValueLayout;
 33 import jdk.internal.access.JavaLangInvokeAccess;
 34 import jdk.internal.access.SharedSecrets;
 35 import jdk.internal.invoke.NativeEntryPoint;
 36 import jdk.internal.invoke.VMStorageProxy;
 37 import sun.security.action.GetPropertyAction;
 38 
 39 import java.lang.invoke.MethodHandle;
 40 import java.lang.invoke.MethodHandles;
 41 import java.lang.invoke.MethodType;
 42 import java.lang.invoke.VarHandle;
 43 import java.nio.ByteOrder;
 44 import java.util.Arrays;
 45 import java.util.List;
 46 import java.util.Map;

 47 import java.util.stream.Stream;
 48 
 49 import static java.lang.invoke.MethodHandles.collectArguments;
 50 import static java.lang.invoke.MethodHandles.dropArguments;
 51 import static java.lang.invoke.MethodHandles.foldArguments;
 52 import static java.lang.invoke.MethodHandles.identity;
 53 import static java.lang.invoke.MethodHandles.insertArguments;
 54 import static java.lang.invoke.MethodType.methodType;

 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 USE_SPEC = Boolean.parseBoolean(
 63         GetPropertyAction.privilegedGetProperty("jdk.internal.foreign.ProgrammableInvoker.USE_SPEC", "true"));


 64 
 65     private static final JavaLangInvokeAccess JLIA = SharedSecrets.getJavaLangInvokeAccess();
 66 



 67     private static final MethodHandle MH_INVOKE_INTERP_BINDINGS;

 68     private static final MethodHandle MH_WRAP_ALLOCATOR;
 69     private static final MethodHandle MH_ALLOCATE_RETURN_BUFFER;
 70     private static final MethodHandle MH_CHECK_SYMBOL;
 71 
 72     private static final MethodHandle EMPTY_OBJECT_ARRAY_HANDLE = MethodHandles.constant(Object[].class, new Object[0]);
 73 
 74     static {
 75         try {
 76             MethodHandles.Lookup lookup = MethodHandles.lookup();


 77             MH_INVOKE_INTERP_BINDINGS = lookup.findVirtual(ProgrammableInvoker.class, "invokeInterpBindings",
 78                     methodType(Object.class, SegmentAllocator.class, Object[].class, InvocationData.class));
 79             MH_WRAP_ALLOCATOR = lookup.findStatic(Binding.Context.class, "ofAllocator",
 80                     methodType(Binding.Context.class, SegmentAllocator.class));
 81             MH_ALLOCATE_RETURN_BUFFER = lookup.findStatic(ProgrammableInvoker.class, "allocateReturnBuffer",
 82                     methodType(MemorySegment.class, Binding.Context.class, long.class));
 83             MH_CHECK_SYMBOL = lookup.findStatic(SharedUtils.class, "checkSymbol",
 84                     methodType(void.class, NativeSymbol.class));
 85         } catch (ReflectiveOperationException e) {
 86             throw new RuntimeException(e);
 87         }
 88     }
 89 
 90     private final ABIDescriptor abi;



 91     private final CallingSequence callingSequence;
 92 




 93     public ProgrammableInvoker(ABIDescriptor abi, CallingSequence callingSequence) {
 94         this.abi = abi;



 95         this.callingSequence = callingSequence;








 96     }
 97 
 98     public MethodHandle getBoundMethodHandle() {
 99         Binding.VMStore[] argMoves = argMoveBindingsStream(callingSequence).toArray(Binding.VMStore[]::new);
100         Class<?>[] argMoveTypes = Arrays.stream(argMoves).map(Binding.VMStore::type).toArray(Class<?>[]::new);
101 
102         Binding.VMLoad[] retMoves = retMoveBindings(callingSequence);
103         Class<?> returnType = retMoves.length == 1 ? retMoves[0].type() : void.class;




104 
105         MethodType leafType = methodType(returnType, argMoveTypes);






















106 
107         NativeEntryPoint nep = NativeEntryPoint.make(
108             "native_invoker_" + leafType.descriptorString(),
109             abi,
110             toStorageArray(argMoves),
111             toStorageArray(retMoves),
112             !callingSequence.isTrivial(),
113             leafType,
114             callingSequence.needsReturnBuffer()
115         );
116         MethodHandle handle = JLIA.nativeMethodHandle(nep);
117 
118         if (USE_SPEC) {
119             handle = specialize(handle);
120          } else {
121             Map<VMStorage, Integer> argIndexMap = SharedUtils.indexMap(argMoves);
122             Map<VMStorage, Integer> retIndexMap = SharedUtils.indexMap(retMoves);
123 
124             InvocationData invData = new InvocationData(handle, argIndexMap, retIndexMap);
125             handle = insertArguments(MH_INVOKE_INTERP_BINDINGS.bindTo(this), 2, invData);
126             MethodType interpType = callingSequence.methodType();
127             if (callingSequence.needsReturnBuffer()) {
128                 // Return buffer is supplied by invokeInterpBindings
129                 assert interpType.parameterType(0) == MemorySegment.class;
130                 interpType.dropParameterTypes(0, 1);
131             }
132             MethodHandle collectorInterp = makeCollectorHandle(interpType);
133             handle = collectArguments(handle, 1, collectorInterp);
134             handle = handle.asType(handle.type().changeReturnType(interpType.returnType()));
135          }
136 
137         assert handle.type().parameterType(0) == SegmentAllocator.class;
138         assert handle.type().parameterType(1) == NativeSymbol.class;
139         handle = foldArguments(handle, 1, MH_CHECK_SYMBOL);
140 
141         handle = SharedUtils.swapArguments(handle, 0, 1); // normalize parameter order
142 
143         return handle;
144     }
145 
146     private static MemorySegment allocateReturnBuffer(Binding.Context context, long size) {
147         return context.allocator().allocate(size);

148     }
149 
150     // Funnel from type to Object[]
151     private static MethodHandle makeCollectorHandle(MethodType type) {
152         return type.parameterCount() == 0
153             ? EMPTY_OBJECT_ARRAY_HANDLE
154             : identity(Object[].class)
155                 .asCollector(Object[].class, type.parameterCount())
156                 .asType(type.changeReturnType(Object[].class));
157     }
158 
159     private Stream<Binding.VMStore> argMoveBindingsStream(CallingSequence callingSequence) {
160         return callingSequence.argumentBindings()
161                 .filter(Binding.VMStore.class::isInstance)
162                 .map(Binding.VMStore.class::cast);
163     }
164 
165     private Binding.VMLoad[] retMoveBindings(CallingSequence callingSequence) {
166         return retMoveBindingsStream(callingSequence).toArray(Binding.VMLoad[]::new);
167     }
168 
169     private Stream<Binding.VMLoad> retMoveBindingsStream(CallingSequence callingSequence) {
170         return callingSequence.returnBindings().stream()
171                 .filter(Binding.VMLoad.class::isInstance)
172                 .map(Binding.VMLoad.class::cast);

173     }
174 

175     private VMStorageProxy[] toStorageArray(Binding.Move[] moves) {
176         return Arrays.stream(moves).map(Binding.Move::storage).toArray(VMStorage[]::new);
177     }
178 
179     private MethodHandle specialize(MethodHandle leafHandle) {
180         MethodType highLevelType = callingSequence.methodType();
181 
182         int argInsertPos = 0;
183         int argContextPos = 0;
184 
185         MethodHandle specializedHandle = dropArguments(leafHandle, argContextPos, Binding.Context.class);

186         for (int i = 0; i < highLevelType.parameterCount(); i++) {
187             List<Binding> bindings = callingSequence.argumentBindings(i);
188             argInsertPos += bindings.stream().filter(Binding.VMStore.class::isInstance).count() + 1;
189             // We interpret the bindings in reverse since we have to construct a MethodHandle from the bottom up
190             for (int j = bindings.size() - 1; j >= 0; j--) {
191                 Binding binding = bindings.get(j);
192                 if (binding.tag() == Binding.Tag.VM_STORE) {
193                     argInsertPos--;
194                 } else {
195                     specializedHandle = binding.specialize(specializedHandle, argInsertPos, argContextPos);
196                 }
197             }
198         }
199 
200         if (highLevelType.returnType() != void.class) {
201             MethodHandle returnFilter = identity(highLevelType.returnType());
202             int retBufPos = -1;
203             long retBufReadOffset = -1;
204             int retContextPos = 0;
205             int retInsertPos = 1;
206             if (callingSequence.needsReturnBuffer()) {
207                 retBufPos = 0;
208                 retBufReadOffset = callingSequence.returnBufferSize();
209                 retContextPos++;
210                 retInsertPos++;
211                 returnFilter = dropArguments(returnFilter, retBufPos, MemorySegment.class);
212             }
213             returnFilter = dropArguments(returnFilter, retContextPos, Binding.Context.class);
214             List<Binding> bindings = callingSequence.returnBindings();
215             for (int j = bindings.size() - 1; j >= 0; j--) {
216                 Binding binding = bindings.get(j);
217                 if (callingSequence.needsReturnBuffer() && binding.tag() == Binding.Tag.VM_LOAD) {
218                     // spacial case this, since we need to update retBufReadOffset as well
219                     Binding.VMLoad load = (Binding.VMLoad) binding;
220                     ValueLayout layout = MemoryLayout.valueLayout(load.type(), ByteOrder.nativeOrder()).withBitAlignment(8);
221                     // since we iterate the bindings in reverse, we have to compute the offset in reverse as well
222                     retBufReadOffset -= abi.arch.typeSize(load.storage().type());
223                     MethodHandle loadHandle = MemoryHandles.insertCoordinates(MemoryHandles.varHandle(layout), 1, retBufReadOffset)
224                             .toMethodHandle(VarHandle.AccessMode.GET);
225 
226                     returnFilter = MethodHandles.collectArguments(returnFilter, retInsertPos, loadHandle);
227                     assert returnFilter.type().parameterType(retInsertPos - 1) == MemorySegment.class;
228                     assert returnFilter.type().parameterType(retInsertPos - 2) == MemorySegment.class;
229                     returnFilter = SharedUtils.mergeArguments(returnFilter, retBufPos, retInsertPos);
230                     // to (... MemorySegment, MemorySegment, <primitive>, ...)
231                     // from (... MemorySegment, MemorySegment, ...)
232                     retInsertPos -= 2; // set insert pos back to the first MS (later DUP binding will merge the 2 MS)
233                 } else {
234                     returnFilter = binding.specialize(returnFilter, retInsertPos, retContextPos);
235                     if (callingSequence.needsReturnBuffer() && binding.tag() == Binding.Tag.BUFFER_STORE) {
236                         // from (... MemorySegment, ...)
237                         // to (... MemorySegment, MemorySegment, <primitive>, ...)
238                         retInsertPos += 2; // set insert pos to <primitive>
239                         assert returnFilter.type().parameterType(retInsertPos - 1) == MemorySegment.class;
240                         assert returnFilter.type().parameterType(retInsertPos - 2) == MemorySegment.class;
241                     }
242                 }
243             }
244             // (R, Context (ret)) -> (MemorySegment?, Context (ret), MemorySegment?, Context (arg), ...)

245             specializedHandle = MethodHandles.collectArguments(returnFilter, retInsertPos, specializedHandle);
246             if (callingSequence.needsReturnBuffer()) {
247                 // (MemorySegment, Context (ret), Context (arg), MemorySegment,  ...) -> (MemorySegment, Context (ret), Context (arg), ...)
248                 specializedHandle = SharedUtils.mergeArguments(specializedHandle, retBufPos, retBufPos + 3);
249 
250                 // allocate the return buffer from the binding context, and then merge the 2 allocator args
251                 MethodHandle retBufAllocHandle = MethodHandles.insertArguments(MH_ALLOCATE_RETURN_BUFFER, 1, callingSequence.returnBufferSize());
252                 // (MemorySegment, Context (ret), Context (arg), ...) -> (Context (arg), Context (ret), Context (arg), ...)
253                 specializedHandle = MethodHandles.filterArguments(specializedHandle, retBufPos, retBufAllocHandle);
254                 // (Context (arg), Context (ret), Context (arg), ...) -> (Context (ret), Context (arg), ...)
255                 specializedHandle = SharedUtils.mergeArguments(specializedHandle, argContextPos + 1, retBufPos); // +1 to skip return context
256             }
257             // (Context (ret), Context (arg), ...) -> (SegmentAllocator, Context (arg), ...)
258             specializedHandle = MethodHandles.filterArguments(specializedHandle, 0, MH_WRAP_ALLOCATOR);
259         } else {
260             specializedHandle = MethodHandles.dropArguments(specializedHandle, 0, SegmentAllocator.class);
261         }
262 
263         // now bind the internal context parameter
264 
265         argContextPos++; // skip over the return SegmentAllocator (inserted by the above code)
266         specializedHandle = SharedUtils.wrapWithAllocator(specializedHandle, argContextPos, callingSequence.allocationSize(), false);
267         return specializedHandle;
268     }
269 
270     private record InvocationData(MethodHandle leaf, Map<VMStorage, Integer> argIndexMap, Map<VMStorage, Integer> retIndexMap) {}















271 
272     Object invokeInterpBindings(SegmentAllocator allocator, Object[] args, InvocationData invData) throws Throwable {
273         Binding.Context unboxContext = callingSequence.allocationSize() != 0
274                 ? Binding.Context.ofBoundedAllocator(callingSequence.allocationSize())













































275                 : Binding.Context.DUMMY;
276         try (unboxContext) {
277             MemorySegment returnBuffer = null;
278 
279             // do argument processing, get Object[] as result
280             Object[] leafArgs = new Object[invData.leaf.type().parameterCount()];
281             if (callingSequence.needsReturnBuffer()) {
282                 // we supply the return buffer (argument array does not contain it)
283                 Object[] prefixedArgs = new Object[args.length + 1];
284                 returnBuffer = unboxContext.allocator().allocate(callingSequence.returnBufferSize());
285                 prefixedArgs[0] = returnBuffer;
286                 System.arraycopy(args, 0, prefixedArgs, 1, args.length);
287                 args = prefixedArgs;
288             }
289             for (int i = 0; i < args.length; i++) {
290                 Object arg = args[i];
291                 BindingInterpreter.unbox(arg, callingSequence.argumentBindings(i),
292                         (storage, type, value) -> {
293                             leafArgs[invData.argIndexMap.get(storage)] = value;
294                         }, unboxContext);
295             }
296 
297             // call leaf
298             Object o = invData.leaf.invokeWithArguments(leafArgs);
299 
300             // return value processing
301             if (o == null) {
302                 if (!callingSequence.needsReturnBuffer()) {
303                     return null;
304                 }
305                 MemorySegment finalReturnBuffer = returnBuffer;
306                 return BindingInterpreter.box(callingSequence.returnBindings(),
307                         new BindingInterpreter.LoadFunc() {
308                             int retBufReadOffset = 0;
309                             @Override
310                             public Object load(VMStorage storage, Class<?> type) {
311                                 Object result1 = SharedUtils.read(finalReturnBuffer.asSlice(retBufReadOffset), type);
312                                 retBufReadOffset += abi.arch.typeSize(storage.type());
313                                 return result1;
314                             }
315                         }, Binding.Context.ofAllocator(allocator));
316             } else {
317                 return BindingInterpreter.box(callingSequence.returnBindings(), (storage, type) -> o,
318                         Binding.Context.ofAllocator(allocator));
319             }
320         }
321     }










322 }
323 
--- EOF ---