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.x64.windows;
 26 
 27 import jdk.incubator.foreign.FunctionDescriptor;
 28 import jdk.incubator.foreign.GroupLayout;
 29 import jdk.incubator.foreign.MemoryAddress;
 30 import jdk.incubator.foreign.MemoryLayout;
 31 import jdk.incubator.foreign.MemorySegment;
 32 import jdk.incubator.foreign.NativeSymbol;
 33 import jdk.incubator.foreign.ResourceScope;
 34 import jdk.internal.foreign.Utils;
 35 import jdk.internal.foreign.abi.CallingSequenceBuilder;
 36 import jdk.internal.foreign.abi.ABIDescriptor;
 37 import jdk.internal.foreign.abi.Binding;
 38 import jdk.internal.foreign.abi.CallingSequence;
 39 import jdk.internal.foreign.abi.ProgrammableInvoker;
 40 import jdk.internal.foreign.abi.ProgrammableUpcallHandler;
 41 import jdk.internal.foreign.abi.VMStorage;
 42 import jdk.internal.foreign.abi.x64.X86_64Architecture;
 43 import jdk.internal.foreign.abi.SharedUtils;
 44 
 45 import java.lang.invoke.MethodHandle;
 46 import java.lang.invoke.MethodType;
 47 import java.util.List;
 48 import java.util.Optional;
 49 
 50 import static jdk.internal.foreign.PlatformLayouts.*;
 51 import static jdk.internal.foreign.abi.x64.X86_64Architecture.*;
 52 
 53 /**
 54  * For the Windowx x64 C ABI specifically, this class uses the ProgrammableInvoker API, namely CallingSequenceBuilder2
 55  * to translate a C FunctionDescriptor into a CallingSequence2, which can then be turned into a MethodHandle.
 56  *
 57  * This includes taking care of synthetic arguments like pointers to return buffers for 'in-memory' returns.
 58  */
 59 public class CallArranger {
 60     private static final int STACK_SLOT_SIZE = 8;
 61 
 62     private static final ABIDescriptor CWindows = X86_64Architecture.abiFor(
 63         new VMStorage[] { rcx, rdx, r8, r9 },
 64         new VMStorage[] { xmm0, xmm1, xmm2, xmm3 },
 65         new VMStorage[] { rax },
 66         new VMStorage[] { xmm0 },
 67         0,
 68         new VMStorage[] { rax, r10, r11 },
 69         new VMStorage[] { xmm4, xmm5 },
 70         16,
 71         32
 72     );
 73 
 74     // record
 75     public static class Bindings {
 76         public final CallingSequence callingSequence;
 77         public final boolean isInMemoryReturn;
 78 
 79         Bindings(CallingSequence callingSequence, boolean isInMemoryReturn) {
 80             this.callingSequence = callingSequence;
 81             this.isInMemoryReturn = isInMemoryReturn;
 82         }
 83     }
 84 
 85     public static Bindings getBindings(MethodType mt, FunctionDescriptor cDesc, boolean forUpcall) {
 86         class CallingSequenceBuilderHelper {
 87             final CallingSequenceBuilder csb = new CallingSequenceBuilder(forUpcall);
 88             final BindingCalculator argCalc =
 89                 forUpcall ? new BoxBindingCalculator(true) : new UnboxBindingCalculator(true);
 90             final BindingCalculator retCalc =
 91                 forUpcall ? new UnboxBindingCalculator(false) : new BoxBindingCalculator(false);
 92 
 93             void addArgumentBindings(Class<?> carrier, MemoryLayout layout, boolean isVararg) {
 94                 csb.addArgumentBindings(carrier, layout, argCalc.getBindings(carrier, layout, isVararg));
 95             }
 96 
 97             void setReturnBindings(Class<?> carrier, MemoryLayout layout) {
 98                 csb.setReturnBindings(carrier, layout, retCalc.getBindings(carrier, layout, false));
 99             }
100         }
101         var csb = new CallingSequenceBuilderHelper();
102 
103         boolean returnInMemory = isInMemoryReturn(cDesc.returnLayout());
104         if (returnInMemory) {
105             Class<?> carrier = MemoryAddress.class;
106             MemoryLayout layout = Win64.C_POINTER;
107             csb.addArgumentBindings(carrier, layout, false);
108             if (forUpcall) {
109                 csb.setReturnBindings(carrier, layout);
110             }
111         } else if (cDesc.returnLayout().isPresent()) {
112             csb.setReturnBindings(mt.returnType(), cDesc.returnLayout().get());
113         }
114 
115         for (int i = 0; i < mt.parameterCount(); i++) {
116             csb.addArgumentBindings(mt.parameterType(i), cDesc.argumentLayouts().get(i), SharedUtils.isVarargsIndex(cDesc, i));
117         }
118 
119         csb.csb.setTrivial(SharedUtils.isTrivial(cDesc));
120 
121         return new Bindings(csb.csb.build(), returnInMemory);
122     }
123 
124     public static MethodHandle arrangeDowncall(MethodType mt, FunctionDescriptor cDesc) {
125         Bindings bindings = getBindings(mt, cDesc, false);
126 
127         MethodHandle handle = new ProgrammableInvoker(CWindows, bindings.callingSequence).getBoundMethodHandle();
128 
129         if (bindings.isInMemoryReturn) {
130             handle = SharedUtils.adaptDowncallForIMR(handle, cDesc);
131         }
132 
133         return handle;
134     }
135 
136     public static NativeSymbol arrangeUpcall(MethodHandle target, MethodType mt, FunctionDescriptor cDesc, ResourceScope scope) {
137         Bindings bindings = getBindings(mt, cDesc, true);
138 
139         if (bindings.isInMemoryReturn) {
140             target = SharedUtils.adaptUpcallForIMR(target, false /* need the return value as well */);
141         }
142 
143         return ProgrammableUpcallHandler.make(CWindows, target, bindings.callingSequence, scope);
144     }
145 
146     private static boolean isInMemoryReturn(Optional<MemoryLayout> returnLayout) {
147         return returnLayout
148                 .filter(GroupLayout.class::isInstance)
149                 .filter(g -> !TypeClass.isRegisterAggregate(g))
150                 .isPresent();
151     }
152 
153     static class StorageCalculator {
154         private final boolean forArguments;
155 
156         private int nRegs = 0;
157         private long stackOffset = 0;
158 
159         public StorageCalculator(boolean forArguments) {
160             this.forArguments = forArguments;
161         }
162 
163         VMStorage nextStorage(int type, MemoryLayout layout) {
164             if (nRegs >= Windowsx64Linker.MAX_REGISTER_ARGUMENTS) {
165                 assert forArguments : "no stack returns";
166                 // stack
167                 long alignment = Math.max(SharedUtils.alignment(layout, true), STACK_SLOT_SIZE);
168                 stackOffset = Utils.alignUp(stackOffset, alignment);
169 
170                 VMStorage storage = X86_64Architecture.stackStorage((int) (stackOffset / STACK_SLOT_SIZE));
171                 stackOffset += STACK_SLOT_SIZE;
172                 return storage;
173             }
174             return (forArguments
175                     ? CWindows.inputStorage
176                     : CWindows.outputStorage)
177                  [type][nRegs++];
178         }
179 
180         public VMStorage extraVarargsStorage() {
181             assert forArguments;
182             return CWindows.inputStorage[StorageClasses.INTEGER][nRegs - 1];
183         }
184     }
185 
186     private interface BindingCalculator {
187         List<Binding> getBindings(Class<?> carrier, MemoryLayout layout, boolean isVararg);
188     }
189 
190     static class UnboxBindingCalculator implements BindingCalculator {
191         private final StorageCalculator storageCalculator;
192 
193         UnboxBindingCalculator(boolean forArguments) {
194             this.storageCalculator = new StorageCalculator(forArguments);
195         }
196 
197         @Override
198         public List<Binding> getBindings(Class<?> carrier, MemoryLayout layout, boolean isVararg) {
199             TypeClass argumentClass = TypeClass.typeClassFor(layout, isVararg);
200             Binding.Builder bindings = Binding.builder();
201             switch (argumentClass) {
202                 case STRUCT_REGISTER: {
203                     assert carrier == MemorySegment.class;
204                     VMStorage storage = storageCalculator.nextStorage(StorageClasses.INTEGER, layout);
205                     Class<?> type = SharedUtils.primitiveCarrierForSize(layout.byteSize(), false);
206                     bindings.bufferLoad(0, type)
207                             .vmStore(storage, type);
208                     break;
209                 }
210                 case STRUCT_REFERENCE: {
211                     assert carrier == MemorySegment.class;
212                     bindings.copy(layout)
213                             .unboxAddress(MemorySegment.class);
214                     VMStorage storage = storageCalculator.nextStorage(StorageClasses.INTEGER, layout);
215                     bindings.vmStore(storage, long.class);
216                     break;
217                 }
218                 case POINTER: {
219                     bindings.unboxAddress(carrier);
220                     VMStorage storage = storageCalculator.nextStorage(StorageClasses.INTEGER, layout);
221                     bindings.vmStore(storage, long.class);
222                     break;
223                 }
224                 case INTEGER: {
225                     VMStorage storage = storageCalculator.nextStorage(StorageClasses.INTEGER, layout);
226                     bindings.vmStore(storage, carrier);
227                     break;
228                 }
229                 case FLOAT: {
230                     VMStorage storage = storageCalculator.nextStorage(StorageClasses.VECTOR, layout);
231                     bindings.vmStore(storage, carrier);
232                     break;
233                 }
234                 case VARARG_FLOAT: {
235                     VMStorage storage = storageCalculator.nextStorage(StorageClasses.VECTOR, layout);
236                     if (!INSTANCE.isStackType(storage.type())) { // need extra for register arg
237                         VMStorage extraStorage = storageCalculator.extraVarargsStorage();
238                         bindings.dup()
239                                 .vmStore(extraStorage, carrier);
240                     }
241 
242                     bindings.vmStore(storage, carrier);
243                     break;
244                 }
245                 default:
246                     throw new UnsupportedOperationException("Unhandled class " + argumentClass);
247             }
248             return bindings.build();
249         }
250     }
251 
252     static class BoxBindingCalculator implements BindingCalculator {
253         private final StorageCalculator storageCalculator;
254 
255         BoxBindingCalculator(boolean forArguments) {
256             this.storageCalculator = new StorageCalculator(forArguments);
257         }
258 
259         @Override
260         public List<Binding> getBindings(Class<?> carrier, MemoryLayout layout, boolean isVararg) {
261             TypeClass argumentClass = TypeClass.typeClassFor(layout, isVararg);
262             Binding.Builder bindings = Binding.builder();
263             switch (argumentClass) {
264                 case STRUCT_REGISTER: {
265                     assert carrier == MemorySegment.class;
266                     bindings.allocate(layout)
267                             .dup();
268                     VMStorage storage = storageCalculator.nextStorage(StorageClasses.INTEGER, layout);
269                     Class<?> type = SharedUtils.primitiveCarrierForSize(layout.byteSize(), false);
270                     bindings.vmLoad(storage, type)
271                             .bufferStore(0, type);
272                     break;
273                 }
274                 case STRUCT_REFERENCE: {
275                     assert carrier == MemorySegment.class;
276                     VMStorage storage = storageCalculator.nextStorage(StorageClasses.INTEGER, layout);
277                     bindings.vmLoad(storage, long.class)
278                             .boxAddress()
279                             .toSegment(layout);
280                     break;
281                 }
282                 case POINTER: {
283                     VMStorage storage = storageCalculator.nextStorage(StorageClasses.INTEGER, layout);
284                     bindings.vmLoad(storage, long.class)
285                             .boxAddress();
286                     break;
287                 }
288                 case INTEGER: {
289                     VMStorage storage = storageCalculator.nextStorage(StorageClasses.INTEGER, layout);
290                     bindings.vmLoad(storage, carrier);
291                     break;
292                 }
293                 case FLOAT: {
294                     VMStorage storage = storageCalculator.nextStorage(StorageClasses.VECTOR, layout);
295                     bindings.vmLoad(storage, carrier);
296                     break;
297                 }
298                 default:
299                     throw new UnsupportedOperationException("Unhandled class " + argumentClass);
300             }
301             return bindings.build();
302         }
303     }
304 }