1 /**
2  * Varargs implementation for the x86_64 System V ABI (not used for Win64).
3  * Used by core.stdc.stdarg and core.vararg.
4  *
5  * Reference: https://www.uclibc.org/docs/psABI-x86_64.pdf
6  *
7  * Copyright: Copyright Digital Mars 2009 - 2020.
8  * License:   $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
9  * Authors:   Walter Bright, Hauke Duden
10  * Source: $(DRUNTIMESRC core/internal/vararg/sysv_x64.d)
11  */
12 
13 module core.internal.vararg.sysv_x64;
14 
15 version (X86_64)
16 {
17     version (Windows) { /* different ABI */ }
18     else version = SysV_x64;
19 }
20 
21 version (SysV_x64):
22 
23 import core.stdc.stdarg : alignUp;
24 
25 //@nogc:    // Not yet, need to make TypeInfo's member functions @nogc first
26 nothrow:
27 
28 // Layout of this struct must match __gnuc_va_list for C ABI compatibility
29 struct __va_list_tag
30 {
31     uint offset_regs = 6 * 8;            // no regs
32     uint offset_fpregs = 6 * 8 + 8 * 16; // no fp regs
33     void* stack_args;
34     void* reg_args;
35 }
36 alias __va_list = __va_list_tag;
37 
38 /**
39  * Making it an array of 1 causes va_list to be passed as a pointer in
40  * function argument lists
41  */
42 alias va_list = __va_list*;
43 
44 ///
45 T va_arg(T)(va_list ap)
46 {
47     static if (is(T U == __argTypes))
48     {
49         static if (U.length == 0 || T.sizeof > 16 || (U[0].sizeof > 8 && !is(U[0] == __vector)))
50         {   // Always passed in memory
51             // The arg may have more strict alignment than the stack
52             void* p = ap.stack_args.alignUp!(T.alignof);
53             ap.stack_args = p + T.sizeof.alignUp;
54             return *cast(T*) p;
55         }
56         else static if (U.length == 1)
57         {   // Arg is passed in one register
58             alias U[0] T1;
59             static if (is(T1 == double) || is(T1 == float) || is(T1 == __vector))
60             {   // Passed in XMM register
61                 if (ap.offset_fpregs < (6 * 8 + 16 * 8))
62                 {
63                     auto p = cast(T*) (ap.reg_args + ap.offset_fpregs);
64                     ap.offset_fpregs += 16;
65                     return *p;
66                 }
67                 else
68                 {
69                     auto p = cast(T*) ap.stack_args;
70                     ap.stack_args += T1.sizeof.alignUp;
71                     return *p;
72                 }
73             }
74             else
75             {   // Passed in regular register
76                 if (ap.offset_regs < 6 * 8 && T.sizeof <= 8)
77                 {
78                     auto p = cast(T*) (ap.reg_args + ap.offset_regs);
79                     ap.offset_regs += 8;
80                     return *p;
81                 }
82                 else
83                 {
84                     void* p = ap.stack_args.alignUp!(T.alignof);
85                     ap.stack_args = p + T.sizeof.alignUp;
86                     return *cast(T*) p;
87                 }
88             }
89         }
90         else static if (U.length == 2)
91         {   // Arg is passed in two registers
92             alias U[0] T1;
93             alias U[1] T2;
94 
95             T result = void;
96             auto p1 = cast(T1*) &result;
97             auto p2 = cast(T2*) ((cast(void*) &result) + 8);
98 
99             // Both must be in registers, or both on stack, hence 4 cases
100 
101             static if ((is(T1 == double) || is(T1 == float)) &&
102                        (is(T2 == double) || is(T2 == float)))
103             {
104                 if (ap.offset_fpregs < (6 * 8 + 16 * 8) - 16)
105                 {
106                     *p1 = *cast(T1*) (ap.reg_args + ap.offset_fpregs);
107                     *p2 = *cast(T2*) (ap.reg_args + ap.offset_fpregs + 16);
108                     ap.offset_fpregs += 32;
109                 }
110                 else
111                 {
112                     *p1 = *cast(T1*) ap.stack_args;
113                     ap.stack_args += T1.sizeof.alignUp;
114                     *p2 = *cast(T2*) ap.stack_args;
115                     ap.stack_args += T2.sizeof.alignUp;
116                 }
117             }
118             else static if (is(T1 == double) || is(T1 == float))
119             {
120                 void* a = void;
121                 if (ap.offset_fpregs < (6 * 8 + 16 * 8) &&
122                     ap.offset_regs < 6 * 8 && T2.sizeof <= 8)
123                 {
124                     *p1 = *cast(T1*) (ap.reg_args + ap.offset_fpregs);
125                     ap.offset_fpregs += 16;
126                     a = ap.reg_args + ap.offset_regs;
127                     ap.offset_regs += 8;
128                 }
129                 else
130                 {
131                     *p1 = *cast(T1*) ap.stack_args;
132                     ap.stack_args += T1.sizeof.alignUp;
133                     a = ap.stack_args;
134                     ap.stack_args += 8;
135                 }
136                 // Be careful not to go past the size of the actual argument
137                 const sz2 = T.sizeof - 8;
138                 (cast(void*) p2)[0..sz2] = a[0..sz2];
139             }
140             else static if (is(T2 == double) || is(T2 == float))
141             {
142                 if (ap.offset_regs < 6 * 8 && T1.sizeof <= 8 &&
143                     ap.offset_fpregs < (6 * 8 + 16 * 8))
144                 {
145                     *p1 = *cast(T1*) (ap.reg_args + ap.offset_regs);
146                     ap.offset_regs += 8;
147                     *p2 = *cast(T2*) (ap.reg_args + ap.offset_fpregs);
148                     ap.offset_fpregs += 16;
149                 }
150                 else
151                 {
152                     *p1 = *cast(T1*) ap.stack_args;
153                     ap.stack_args += 8;
154                     *p2 = *cast(T2*) ap.stack_args;
155                     ap.stack_args += T2.sizeof.alignUp;
156                 }
157             }
158             else // both in regular registers
159             {
160                 void* a = void;
161                 if (ap.offset_regs < 5 * 8 && T1.sizeof <= 8 && T2.sizeof <= 8)
162                 {
163                     *p1 = *cast(T1*) (ap.reg_args + ap.offset_regs);
164                     ap.offset_regs += 8;
165                     a = ap.reg_args + ap.offset_regs;
166                     ap.offset_regs += 8;
167                 }
168                 else
169                 {
170                     *p1 = *cast(T1*) ap.stack_args;
171                     ap.stack_args += 8;
172                     a = ap.stack_args;
173                     ap.stack_args += 8;
174                 }
175                 // Be careful not to go past the size of the actual argument
176                 const sz2 = T.sizeof - 8;
177                 (cast(void*) p2)[0..sz2] = a[0..sz2];
178             }
179 
180             return result;
181         }
182         else
183         {
184             static assert(false);
185         }
186     }
187     else
188     {
189         static assert(false, "not a valid argument type for va_arg");
190     }
191 }
192 
193 ///
194 void va_arg()(va_list ap, TypeInfo ti, void* parmn)
195 {
196     TypeInfo arg1, arg2;
197     if (!ti.argTypes(arg1, arg2))
198     {
199         bool inXMMregister(TypeInfo arg) pure nothrow @safe
200         {
201             return (arg.flags & 2) != 0;
202         }
203 
204         TypeInfo_Vector v1 = arg1 ? cast(TypeInfo_Vector) arg1 : null;
205         if (arg1 && (arg1.tsize <= 8 || v1))
206         {   // Arg is passed in one register
207             auto tsize = arg1.tsize;
208             void* p;
209             bool stack = false;
210             auto offset_fpregs_save = ap.offset_fpregs;
211             auto offset_regs_save = ap.offset_regs;
212         L1:
213             if (inXMMregister(arg1) || v1)
214             {   // Passed in XMM register
215                 if (ap.offset_fpregs < (6 * 8 + 16 * 8) && !stack)
216                 {
217                     p = ap.reg_args + ap.offset_fpregs;
218                     ap.offset_fpregs += 16;
219                 }
220                 else
221                 {
222                     p = ap.stack_args;
223                     ap.stack_args += tsize.alignUp;
224                     stack = true;
225                 }
226             }
227             else
228             {   // Passed in regular register
229                 if (ap.offset_regs < 6 * 8 && !stack)
230                 {
231                     p = ap.reg_args + ap.offset_regs;
232                     ap.offset_regs += 8;
233                 }
234                 else
235                 {
236                     p = ap.stack_args;
237                     ap.stack_args += 8;
238                     stack = true;
239                 }
240             }
241             parmn[0..tsize] = p[0..tsize];
242 
243             if (arg2)
244             {
245                 if (inXMMregister(arg2))
246                 {   // Passed in XMM register
247                     if (ap.offset_fpregs < (6 * 8 + 16 * 8) && !stack)
248                     {
249                         p = ap.reg_args + ap.offset_fpregs;
250                         ap.offset_fpregs += 16;
251                     }
252                     else
253                     {
254                         if (!stack)
255                         {   // arg1 is really on the stack, so rewind and redo
256                             ap.offset_fpregs = offset_fpregs_save;
257                             ap.offset_regs = offset_regs_save;
258                             stack = true;
259                             goto L1;
260                         }
261                         p = ap.stack_args;
262                         ap.stack_args += arg2.tsize.alignUp;
263                     }
264                 }
265                 else
266                 {   // Passed in regular register
267                     if (ap.offset_regs < 6 * 8 && !stack)
268                     {
269                         p = ap.reg_args + ap.offset_regs;
270                         ap.offset_regs += 8;
271                     }
272                     else
273                     {
274                         if (!stack)
275                         {   // arg1 is really on the stack, so rewind and redo
276                             ap.offset_fpregs = offset_fpregs_save;
277                             ap.offset_regs = offset_regs_save;
278                             stack = true;
279                             goto L1;
280                         }
281                         p = ap.stack_args;
282                         ap.stack_args += 8;
283                     }
284                 }
285                 auto sz = ti.tsize - 8;
286                 (parmn + 8)[0..sz] = p[0..sz];
287             }
288         }
289         else
290         {   // Always passed in memory
291             // The arg may have more strict alignment than the stack
292             auto talign = ti.talign;
293             auto tsize = ti.tsize;
294             auto p = cast(void*) ((cast(size_t) ap.stack_args + talign - 1) & ~(talign - 1));
295             ap.stack_args = p + tsize.alignUp;
296             parmn[0..tsize] = p[0..tsize];
297         }
298     }
299     else
300     {
301         assert(false, "not a valid argument type for va_arg");
302     }
303 }