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 }