1 /*
  2  *  Copyright (c) 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.  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  */
 26 package jdk.internal.foreign.abi.x64.sysv;
 27 
 28 import jdk.incubator.foreign.CSupport;
 29 import jdk.incubator.foreign.GroupLayout;
 30 import jdk.incubator.foreign.MemoryAddress;
 31 import jdk.incubator.foreign.MemoryHandles;
 32 import jdk.incubator.foreign.MemoryLayout;
 33 import jdk.incubator.foreign.MemorySegment;
 34 import jdk.internal.foreign.NativeMemorySegmentImpl;
 35 import jdk.internal.foreign.Utils;
 36 import jdk.internal.foreign.abi.SharedUtils;
 37 import jdk.internal.misc.Unsafe;
 38 
 39 import java.lang.invoke.VarHandle;
 40 import java.lang.ref.Cleaner;
 41 import java.nio.ByteOrder;
 42 import java.util.ArrayList;
 43 import java.util.List;
 44 
 45 import static jdk.incubator.foreign.CSupport.SysV;
 46 import static jdk.incubator.foreign.CSupport.VaList;
 47 import static jdk.incubator.foreign.MemoryLayout.PathElement.groupElement;
 48 import static jdk.internal.foreign.abi.SharedUtils.SimpleVaArg;
 49 import static jdk.internal.foreign.abi.SharedUtils.checkCompatibleType;
 50 import static jdk.internal.foreign.abi.SharedUtils.vhPrimitiveOrAddress;
 51 
 52 // See https://software.intel.com/sites/default/files/article/402129/mpx-linux64-abi.pdf "3.5.7 Variable Argument Lists"
 53 public class SysVVaList implements VaList {
 54     private static final Unsafe U = Unsafe.getUnsafe();
 55 
 56     static final Class<?> CARRIER = MemoryAddress.class;
 57 
 58 //    struct typedef __va_list_tag __va_list_tag {
 59 //        unsigned int               gp_offset;            /*     0     4 */
 60 //        unsigned int               fp_offset;            /*     4     4 */
 61 //        void *                     overflow_arg_area;    /*     8     8 */
 62 //        void *                     reg_save_area;        /*    16     8 */
 63 //
 64 //        /* size: 24, cachelines: 1, members: 4 */
 65 //        /* last cacheline: 24 bytes */
 66 //    };
 67     static final GroupLayout LAYOUT = MemoryLayout.ofStruct(
 68         SysV.C_INT.withName("gp_offset"),
 69         SysV.C_INT.withName("fp_offset"),
 70         SysV.C_POINTER.withName("overflow_arg_area"),
 71         SysV.C_POINTER.withName("reg_save_area")
 72     ).withName("__va_list_tag");
 73 
 74     private static final MemoryLayout GP_REG = MemoryLayout.ofValueBits(64, ByteOrder.nativeOrder());
 75     private static final MemoryLayout FP_REG = MemoryLayout.ofValueBits(128, ByteOrder.nativeOrder());
 76 
 77     private static final GroupLayout LAYOUT_REG_SAVE_AREA = MemoryLayout.ofStruct(
 78         GP_REG.withName("%rdi"),
 79         GP_REG.withName("%rsi"),
 80         GP_REG.withName("%rdx"),
 81         GP_REG.withName("%rcx"),
 82         GP_REG.withName("%r8"),
 83         GP_REG.withName("%r9"),
 84         FP_REG.withName("%xmm0"),
 85         FP_REG.withName("%xmm1"),
 86         FP_REG.withName("%xmm2"),
 87         FP_REG.withName("%xmm3"),
 88         FP_REG.withName("%xmm4"),
 89         FP_REG.withName("%xmm5"),
 90         FP_REG.withName("%xmm6"),
 91         FP_REG.withName("%xmm7")
 92 // specification and implementation differ as to whether the following are part of a reg save area
 93 // Let's go with the implementation, since then it actually works :)
 94 //        FP_REG.withName("%xmm8"),
 95 //        FP_REG.withName("%xmm9"),
 96 //        FP_REG.withName("%xmm10"),
 97 //        FP_REG.withName("%xmm11"),
 98 //        FP_REG.withName("%xmm12"),
 99 //        FP_REG.withName("%xmm13"),
100 //        FP_REG.withName("%xmm14"),
101 //        FP_REG.withName("%xmm15")
102     );
103 
104     private static final long FP_OFFSET = LAYOUT_REG_SAVE_AREA.byteOffset(groupElement("%xmm0"));
105 
106     private static final int GP_SLOT_SIZE = (int) GP_REG.byteSize();
107     private static final int FP_SLOT_SIZE = (int) FP_REG.byteSize();
108 
109     private static final int MAX_GP_OFFSET = (int) FP_OFFSET; // 6 regs used
110     private static final int MAX_FP_OFFSET = (int) LAYOUT_REG_SAVE_AREA.byteSize(); // 8 16 byte regs
111 
112     private static final VarHandle VH_fp_offset = LAYOUT.varHandle(int.class, groupElement("fp_offset"));
113     private static final VarHandle VH_gp_offset = LAYOUT.varHandle(int.class, groupElement("gp_offset"));
114     private static final VarHandle VH_overflow_arg_area
115         = MemoryHandles.asAddressVarHandle(LAYOUT.varHandle(long.class, groupElement("overflow_arg_area")));
116     private static final VarHandle VH_reg_save_area
117         = MemoryHandles.asAddressVarHandle(LAYOUT.varHandle(long.class, groupElement("reg_save_area")));
118 
119     private static final Cleaner cleaner = Cleaner.create();
120     private static final CSupport.VaList EMPTY = new SharedUtils.EmptyVaList(emptyListAddress());
121 
122     private final MemorySegment segment;
123     private final List<MemorySegment> slices = new ArrayList<>();
124     private final MemorySegment regSaveArea;
125 
126     SysVVaList(MemorySegment segment) {
127         this.segment = segment;
128         regSaveArea = regSaveArea();
129         slices.add(regSaveArea);
130     }
131 
132     private static MemoryAddress emptyListAddress() {
133         long ptr = U.allocateMemory(LAYOUT.byteSize());
134         MemorySegment ms = NativeMemorySegmentImpl.makeNativeSegmentUnchecked(
135                 MemoryAddress.ofLong(ptr), LAYOUT.byteSize(), null, () -> U.freeMemory(ptr), null);
136         cleaner.register(SysVVaList.class, ms::close);
137         MemoryAddress base = ms.baseAddress();
138         VH_gp_offset.set(base, MAX_GP_OFFSET);
139         VH_fp_offset.set(base, MAX_FP_OFFSET);
140         VH_overflow_arg_area.set(base, MemoryAddress.NULL);
141         VH_reg_save_area.set(base, MemoryAddress.NULL);
142         MemorySegment unconfined = NativeMemorySegmentImpl.makeNativeSegmentUnchecked(
143                 base, ms.byteSize(), null, null, null).withAccessModes(0);
144         return unconfined.baseAddress();
145     }
146 
147     public static CSupport.VaList empty() {
148         return EMPTY;
149     }
150 
151     private int currentGPOffset() {
152         return (int) VH_gp_offset.get(segment.baseAddress());
153     }
154 
155     private void currentGPOffset(int i) {
156         VH_gp_offset.set(segment.baseAddress(), i);
157     }
158 
159     private int currentFPOffset() {
160         return (int) VH_fp_offset.get(segment.baseAddress());
161     }
162 
163     private void currentFPOffset(int i) {
164         VH_fp_offset.set(segment.baseAddress(), i);
165     }
166 
167     private MemoryAddress stackPtr() {
168         return (MemoryAddress) VH_overflow_arg_area.get(segment.baseAddress());
169     }
170 
171     private void stackPtr(MemoryAddress ptr) {
172         VH_overflow_arg_area.set(segment.baseAddress(), ptr);
173     }
174 
175     private MemorySegment regSaveArea() {
176         return MemorySegment.ofNativeRestricted((MemoryAddress) VH_reg_save_area.get(segment.baseAddress()),
177             LAYOUT_REG_SAVE_AREA.byteSize(), segment.ownerThread(), null, null);
178     }
179 
180     private void preAlignStack(MemoryLayout layout) {
181         if (layout.byteAlignment() > 8) {
182             stackPtr(Utils.alignUp(stackPtr(), 16));
183         }
184     }
185 
186     private void postAlignStack(MemoryLayout layout) {
187         stackPtr(Utils.alignUp(stackPtr().addOffset(layout.byteSize()), 8));
188     }
189 
190     @Override
191     public int vargAsInt(MemoryLayout layout) {
192         return (int) read(int.class, layout);
193     }
194 
195     @Override
196     public long vargAsLong(MemoryLayout layout) {
197         return (long) read(long.class, layout);
198     }
199 
200     @Override
201     public double vargAsDouble(MemoryLayout layout) {
202         return (double) read(double.class, layout);
203     }
204 
205     @Override
206     public MemoryAddress vargAsAddress(MemoryLayout layout) {
207         return (MemoryAddress) read(MemoryAddress.class, layout);
208     }
209 
210     @Override
211     public MemorySegment vargAsSegment(MemoryLayout layout) {
212         return (MemorySegment) read(MemorySegment.class, layout);
213     }
214 
215     private Object read(Class<?> carrier, MemoryLayout layout) {
216         checkCompatibleType(carrier, layout, SysVx64Linker.ADDRESS_SIZE);
217         TypeClass typeClass = TypeClass.classifyLayout(layout);
218         if (isRegOverflow(currentGPOffset(), currentFPOffset(), typeClass)) {
219             preAlignStack(layout);
220             return switch (typeClass.kind()) {
221                 case STRUCT -> {
222                     try (MemorySegment slice = MemorySegment.ofNativeRestricted(stackPtr(), layout.byteSize(),
223                                                                                 segment.ownerThread(), null, null)) {
224                         MemorySegment seg = MemorySegment.allocateNative(layout);
225                         seg.copyFrom(slice);
226                         postAlignStack(layout);
227                         yield seg;
228                     }
229                 }
230                 case POINTER, INTEGER, FLOAT -> {
231                     VarHandle reader = vhPrimitiveOrAddress(carrier, layout);
232                     try (MemorySegment slice = MemorySegment.ofNativeRestricted(stackPtr(), layout.byteSize(),
233                                                                                 segment.ownerThread(), null, null)) {
234                         Object res = reader.get(slice.baseAddress());
235                         postAlignStack(layout);
236                         yield res;
237                     }
238                 }
239             };
240         } else {
241             return switch (typeClass.kind()) {
242                 case STRUCT -> {
243                     MemorySegment value = MemorySegment.allocateNative(layout);
244                     int classIdx = 0;
245                     long offset = 0;
246                     while (offset < layout.byteSize()) {
247                         final long copy = Math.min(layout.byteSize() - offset, 8);
248                         boolean isSSE = typeClass.classes.get(classIdx++) == ArgumentClassImpl.SSE;
249                         MemorySegment slice = value.asSlice(offset, copy);
250                         if (isSSE) {
251                             slice.copyFrom(regSaveArea.asSlice(currentFPOffset(), copy));
252                             currentFPOffset(currentFPOffset() + FP_SLOT_SIZE);
253                         } else {
254                             slice.copyFrom(regSaveArea.asSlice(currentGPOffset(), copy));
255                             currentGPOffset(currentGPOffset() + GP_SLOT_SIZE);
256                         }
257                         offset += copy;
258                     }
259                     yield value;
260                 }
261                 case POINTER, INTEGER -> {
262                     VarHandle reader = SharedUtils.vhPrimitiveOrAddress(carrier, layout);
263                     Object res = reader.get(regSaveArea.baseAddress().addOffset(currentGPOffset()));
264                     currentGPOffset(currentGPOffset() + GP_SLOT_SIZE);
265                     yield res;
266                 }
267                 case FLOAT -> {
268                     VarHandle reader = layout.varHandle(carrier);
269                     Object res = reader.get(regSaveArea.baseAddress().addOffset(currentFPOffset()));
270                     currentFPOffset(currentFPOffset() + FP_SLOT_SIZE);
271                     yield res;
272                 }
273             };
274         }
275     }
276 
277     @Override
278     public void skip(MemoryLayout... layouts) {
279         for (MemoryLayout layout : layouts) {
280             TypeClass typeClass = TypeClass.classifyLayout(layout);
281             if (isRegOverflow(currentGPOffset(), currentFPOffset(), typeClass)) {
282                 preAlignStack(layout);
283                 postAlignStack(layout);
284             } else {
285                 currentGPOffset(currentGPOffset() + (((int) typeClass.nIntegerRegs()) * GP_SLOT_SIZE));
286                 currentFPOffset(currentFPOffset() + (((int) typeClass.nVectorRegs()) * FP_SLOT_SIZE));
287             }
288         }
289     }
290 
291     static SysVVaList.Builder builder() {
292         return new SysVVaList.Builder();
293     }
294 
295     public static VaList ofAddress(MemoryAddress ma) {
296         return new SysVVaList(MemorySegment.ofNativeRestricted(ma, LAYOUT.byteSize(), Thread.currentThread(), null, null));
297     }
298 
299     @Override
300     public boolean isAlive() {
301         return segment.isAlive();
302     }
303 
304     @Override
305     public void close() {
306         segment.close();
307         slices.forEach(MemorySegment::close);
308     }
309 
310     @Override
311     public VaList copy() {
312         MemorySegment copy = MemorySegment.allocateNative(LAYOUT.byteSize());
313         copy.copyFrom(segment);
314         return new SysVVaList(copy);
315     }
316 
317     @Override
318     public MemoryAddress address() {
319         return segment.baseAddress();
320     }
321 
322     private static boolean isRegOverflow(long currentGPOffset, long currentFPOffset, TypeClass typeClass) {
323         return currentGPOffset > MAX_GP_OFFSET - typeClass.nIntegerRegs() * GP_SLOT_SIZE
324                 || currentFPOffset > MAX_FP_OFFSET - typeClass.nVectorRegs() * FP_SLOT_SIZE;
325     }
326 
327     @Override
328     public String toString() {
329         return "SysVVaList{"
330                + "gp_offset=" + currentGPOffset()
331                + ", fp_offset=" + currentFPOffset()
332                + ", overflow_arg_area=" + stackPtr()
333                + ", reg_save_area=" + regSaveArea()
334                + '}';
335     }
336 
337     static class Builder implements CSupport.VaList.Builder {
338         private final MemorySegment reg_save_area = MemorySegment.allocateNative(LAYOUT_REG_SAVE_AREA);
339         private long currentGPOffset = 0;
340         private long currentFPOffset = FP_OFFSET;
341         private final List<SimpleVaArg> stackArgs = new ArrayList<>();
342 
343         @Override
344         public Builder vargFromInt(MemoryLayout layout, int value) {
345             return arg(int.class, layout, value);
346         }
347 
348         @Override
349         public Builder vargFromLong(MemoryLayout layout, long value) {
350             return arg(long.class, layout, value);
351         }
352 
353         @Override
354         public Builder vargFromDouble(MemoryLayout layout, double value) {
355             return arg(double.class, layout, value);
356         }
357 
358         @Override
359         public Builder vargFromAddress(MemoryLayout layout, MemoryAddress value) {
360             return arg(MemoryAddress.class, layout, value);
361         }
362 
363         @Override
364         public Builder vargFromSegment(MemoryLayout layout, MemorySegment value) {
365             return arg(MemorySegment.class, layout, value);
366         }
367 
368         private Builder arg(Class<?> carrier, MemoryLayout layout, Object value) {
369             checkCompatibleType(carrier, layout, SysVx64Linker.ADDRESS_SIZE);
370             TypeClass typeClass = TypeClass.classifyLayout(layout);
371             if (isRegOverflow(currentGPOffset, currentFPOffset, typeClass)) {
372                 // stack it!
373                 stackArgs.add(new SimpleVaArg(carrier, layout, value));
374             } else {
375                 switch (typeClass.kind()) {
376                     case STRUCT -> {
377                         MemorySegment valueSegment = (MemorySegment) value;
378                         int classIdx = 0;
379                         long offset = 0;
380                         while (offset < layout.byteSize()) {
381                             final long copy = Math.min(layout.byteSize() - offset, 8);
382                             boolean isSSE = typeClass.classes.get(classIdx++) == ArgumentClassImpl.SSE;
383                             MemorySegment slice = valueSegment.asSlice(offset, copy);
384                             if (isSSE) {
385                                 reg_save_area.asSlice(currentFPOffset, copy).copyFrom(slice);
386                                 currentFPOffset += FP_SLOT_SIZE;
387                             } else {
388                                 reg_save_area.asSlice(currentGPOffset, copy).copyFrom(slice);
389                                 currentGPOffset += GP_SLOT_SIZE;
390                             }
391                             offset += copy;
392                         }
393                     }
394                     case POINTER, INTEGER -> {
395                         VarHandle writer = SharedUtils.vhPrimitiveOrAddress(carrier, layout);
396                         writer.set(reg_save_area.baseAddress().addOffset(currentGPOffset), value);
397                         currentGPOffset += GP_SLOT_SIZE;
398                     }
399                     case FLOAT -> {
400                         VarHandle writer = layout.varHandle(carrier);
401                         writer.set(reg_save_area.baseAddress().addOffset(currentFPOffset), value);
402                         currentFPOffset += FP_SLOT_SIZE;
403                     }
404                 }
405             }
406             return this;
407         }
408 
409         private boolean isEmpty() {
410             return currentGPOffset == 0 && currentFPOffset == FP_OFFSET && stackArgs.isEmpty();
411         }
412 
413         public VaList build() {
414             if (isEmpty()) {
415                 return EMPTY;
416             }
417 
418             MemorySegment vaListSegment = MemorySegment.allocateNative(LAYOUT.byteSize());
419             SysVVaList res = new SysVVaList(vaListSegment);
420             MemoryAddress stackArgsPtr = MemoryAddress.NULL;
421             if (!stackArgs.isEmpty()) {
422                 long stackArgsSize = stackArgs.stream().reduce(0L, (acc, e) -> acc + e.layout.byteSize(), Long::sum);
423                 MemorySegment stackArgsSegment = MemorySegment.allocateNative(stackArgsSize, 16);
424                 MemoryAddress maOverflowArgArea = stackArgsSegment.baseAddress();
425                 for (SimpleVaArg arg : stackArgs) {
426                     if (arg.layout.byteSize() > 8) {
427                         maOverflowArgArea = Utils.alignUp(maOverflowArgArea, Math.min(16, arg.layout.byteSize()));
428                     }
429                     VarHandle writer = arg.varHandle();
430                     writer.set(maOverflowArgArea, arg.value);
431                     maOverflowArgArea = maOverflowArgArea.addOffset(arg.layout.byteSize());
432                 }
433                 stackArgsPtr = stackArgsSegment.baseAddress();
434                 res.slices.add(stackArgsSegment);
435             }
436 
437             MemoryAddress vaListAddr = vaListSegment.baseAddress();
438             VH_fp_offset.set(vaListAddr, (int) FP_OFFSET);
439             VH_overflow_arg_area.set(vaListAddr, stackArgsPtr);
440             VH_reg_save_area.set(vaListAddr, reg_save_area.baseAddress());
441             res.slices.add(reg_save_area);
442             assert reg_save_area.ownerThread() == vaListSegment.ownerThread();
443             return res;
444         }
445     }
446 }