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 }