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