1 /**
2  * Break down a D type into basic (register) types for the x86_64 System V ABI.
3  *
4  * Copyright:   Copyright (C) 1999-2023 by The D Language Foundation, All Rights Reserved
5  * Authors:     Martin Kinkelin
6  * License:     $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
7  * Source:      $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/argtypes_sysv_x64.d, _argtypes_sysv_x64.d)
8  * Documentation:  https://dlang.org/phobos/dmd_argtypes_sysv_x64.html
9  * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/src/dmd/argtypes_sysv_x64.d
10  */
11 
12 module dmd.argtypes_sysv_x64;
13 
14 import dmd.astenums;
15 import dmd.declaration;
16 import dmd.globals;
17 import dmd.mtype;
18 import dmd.target;
19 import dmd.visitor;
20 
21 /****************************************************
22  * This breaks a type down into 'simpler' types that can be passed to a function
23  * in registers, and returned in registers.
24  * This is the implementation for the x86_64 System V ABI (not used for Win64),
25  * based on https://www.uclibc.org/docs/psABI-x86_64.pdf.
26  * Params:
27  *      t = type to break down
28  * Returns:
29  *      tuple of types, each element can be passed in a register.
30  *      A tuple of zero length means the type cannot be passed/returned in registers.
31  *      null indicates a `void`.
32  */
33 extern (C++) TypeTuple toArgTypes_sysv_x64(Type t)
34 {
35     if (t == Type.terror)
36         return new TypeTuple(t);
37 
38     const size = cast(size_t) t.size();
39     if (size == 0)
40         return null;
41     if (size > 32)
42         return TypeTuple.empty;
43 
44     const classification = classify(t, size);
45     const classes = classification.slice();
46     const N = classes.length;
47     const c0 = classes[0];
48 
49     switch (c0)
50     {
51     case Class.memory:
52          return TypeTuple.empty;
53     case Class.x87:
54         return new TypeTuple(Type.tfloat80);
55     case Class.complexX87:
56         return new TypeTuple(Type.tfloat80, Type.tfloat80);
57     default:
58         break;
59     }
60 
61     if (N > 2 || (N == 2 && classes[1] == Class.sseUp))
62     {
63         assert(c0 == Class.sse);
64         foreach (c; classes[1 .. $])
65             assert(c == Class.sseUp);
66 
67         assert(size % 8 == 0);
68         return new TypeTuple(new TypeVector(Type.tfloat64.sarrayOf(N)));
69     }
70 
71     assert(N >= 1 && N <= 2);
72     Type[2] argtypes;
73     foreach (i, c; classes)
74     {
75         // the last eightbyte may be filled partially only
76         auto sizeInEightbyte = (i < N - 1) ? 8 : size % 8;
77         if (sizeInEightbyte == 0)
78             sizeInEightbyte = 8;
79 
80         if (c == Class.integer)
81         {
82             argtypes[i] =
83                 sizeInEightbyte > 4 ? Type.tint64 :
84                 sizeInEightbyte > 2 ? Type.tint32 :
85                 sizeInEightbyte > 1 ? Type.tint16 :
86                                       Type.tint8;
87         }
88         else if (c == Class.sse)
89         {
90             argtypes[i] =
91                 sizeInEightbyte > 4 ? Type.tfloat64 :
92                                       Type.tfloat32;
93         }
94         else
95             assert(0, "Unexpected class");
96     }
97 
98     return N == 1
99         ? new TypeTuple(argtypes[0])
100         : new TypeTuple(argtypes[0], argtypes[1]);
101 }
102 
103 
104 private:
105 
106 // classification per eightbyte (64-bit chunk)
107 enum Class : ubyte
108 {
109     integer,
110     sse,
111     sseUp,
112     x87,
113     x87Up,
114     complexX87,
115     noClass,
116     memory
117 }
118 
119 Class merge(Class a, Class b) @safe
120 {
121     bool any(Class value) { return a == value || b == value; }
122 
123     if (a == b)
124         return a;
125     if (a == Class.noClass)
126         return b;
127     if (b == Class.noClass)
128         return a;
129     if (any(Class.memory))
130         return Class.memory;
131     if (any(Class.integer))
132         return Class.integer;
133     if (any(Class.x87) || any(Class.x87Up) || any(Class.complexX87))
134         return Class.memory;
135     return Class.sse;
136 }
137 
138 struct Classification
139 {
140     Class[4] classes;
141     int numEightbytes;
142 
143     const(Class[]) slice() const return @safe { return classes[0 .. numEightbytes]; }
144 }
145 
146 Classification classify(Type t, size_t size)
147 {
148     scope v = new ToClassesVisitor(size);
149     t.accept(v);
150     return Classification(v.result, v.numEightbytes);
151 }
152 
153 extern (C++) final class ToClassesVisitor : Visitor
154 {
155     const size_t size;
156     int numEightbytes;
157     Class[4] result = Class.noClass;
158 
159     this(size_t size) scope @safe
160     {
161         assert(size > 0);
162         this.size = size;
163         this.numEightbytes = cast(int) ((size + 7) / 8);
164     }
165 
166     void memory()
167     {
168         result[0 .. numEightbytes] = Class.memory;
169     }
170 
171     void one(Class a)
172     {
173         result[0] = a;
174     }
175 
176     void two(Class a, Class b)
177     {
178         result[0] = a;
179         result[1] = b;
180     }
181 
182     alias visit = Visitor.visit;
183 
184     override void visit(Type)
185     {
186         assert(0, "Unexpected type");
187     }
188 
189     override void visit(TypeEnum t)
190     {
191         t.toBasetype().accept(this);
192     }
193 
194     override void visit(TypeNoreturn t)
195     {
196         // Treat as void
197         return visit(Type.tvoid);
198     }
199 
200     override void visit(TypeBasic t)
201     {
202         switch (t.ty)
203         {
204         case Tvoid:
205         case Tbool:
206         case Tint8:
207         case Tuns8:
208         case Tint16:
209         case Tuns16:
210         case Tint32:
211         case Tuns32:
212         case Tint64:
213         case Tuns64:
214         case Tchar:
215         case Twchar:
216         case Tdchar:
217             return one(Class.integer);
218 
219         case Tint128:
220         case Tuns128:
221             return two(Class.integer, Class.integer);
222 
223         case Tfloat80:
224         case Timaginary80:
225             return two(Class.x87, Class.x87Up);
226 
227         case Tfloat32:
228         case Tfloat64:
229         case Timaginary32:
230         case Timaginary64:
231         case Tcomplex32: // struct { float a, b; }
232             return one(Class.sse);
233 
234         case Tcomplex64: // struct { double a, b; }
235             return two(Class.sse, Class.sse);
236 
237         case Tcomplex80: // struct { real a, b; }
238             result[0 .. 4] = Class.complexX87;
239             return;
240 
241         default:
242             assert(0, "Unexpected basic type");
243         }
244     }
245 
246     override void visit(TypeVector t)
247     {
248         result[0] = Class.sse;
249         result[1 .. numEightbytes] = Class.sseUp;
250     }
251 
252     override void visit(TypeAArray)
253     {
254         return one(Class.integer);
255     }
256 
257     override void visit(TypePointer)
258     {
259         return one(Class.integer);
260     }
261 
262     override void visit(TypeNull)
263     {
264         return one(Class.integer);
265     }
266 
267     override void visit(TypeClass)
268     {
269         return one(Class.integer);
270     }
271 
272     override void visit(TypeDArray)
273     {
274         if (!target.isLP64)
275             return one(Class.integer);
276         return two(Class.integer, Class.integer);
277     }
278 
279     override void visit(TypeDelegate)
280     {
281         if (!target.isLP64)
282             return one(Class.integer);
283         return two(Class.integer, Class.integer);
284     }
285 
286     override void visit(TypeSArray t)
287     {
288         // treat as struct with N fields
289 
290         auto baseElemType = t.next.toBasetype().isTypeStruct();
291         if (baseElemType && !baseElemType.sym.isPOD())
292             return memory();
293 
294         classifyStaticArrayElements(0, t);
295         finalizeAggregate();
296     }
297 
298     override void visit(TypeStruct t)
299     {
300         if (!t.sym.isPOD())
301             return memory();
302 
303         classifyStructFields(0, t);
304         finalizeAggregate();
305     }
306 
307     void classifyStructFields(uint baseOffset, TypeStruct t)
308     {
309         extern(D) Type getNthField(size_t n, out uint offset, out uint typeAlignment)
310         {
311             auto field = t.sym.fields[n];
312             offset = field.offset;
313             typeAlignment = field.type.alignsize();
314             return field.type;
315         }
316 
317         classifyFields(baseOffset, t.sym.fields.length, &getNthField);
318     }
319 
320     void classifyStaticArrayElements(uint baseOffset, TypeSArray t)
321     {
322         Type elemType = t.next;
323         const elemSize = elemType.size();
324         const elemTypeAlignment = elemType.alignsize();
325 
326         extern(D) Type getNthElement(size_t n, out uint offset, out uint typeAlignment)
327         {
328             offset = cast(uint)(n * elemSize);
329             typeAlignment = elemTypeAlignment;
330             return elemType;
331         }
332 
333         classifyFields(baseOffset, cast(size_t) t.dim.toInteger(), &getNthElement);
334     }
335 
336     extern(D) void classifyFields(uint baseOffset, size_t nfields, Type delegate(size_t, out uint, out uint) getFieldInfo)
337     {
338         // classify each field (recursively for aggregates) and merge all classes per eightbyte
339         foreach (n; 0 .. nfields)
340         {
341             uint foffset_relative;
342             uint ftypeAlignment;
343             Type ftype = getFieldInfo(n, foffset_relative, ftypeAlignment);
344             const fsize = cast(size_t) ftype.size();
345 
346             const foffset = baseOffset + foffset_relative;
347             if (foffset & (ftypeAlignment - 1)) // not aligned
348                 return memory();
349 
350             if (auto ts = ftype.isTypeStruct())
351                 classifyStructFields(foffset, ts);
352             else if (auto tsa = ftype.isTypeSArray())
353                 classifyStaticArrayElements(foffset, tsa);
354             else if (ftype.toBasetype().isTypeNoreturn())
355             {
356                 // Ignore noreturn members with sizeof = 0
357                 // Potential custom alignment changes are factored in above
358                 nfields--;
359                 continue;
360             }
361             else
362             {
363                 const fEightbyteStart = foffset / 8;
364                 const fEightbyteEnd = (foffset + fsize + 7) / 8;
365                 if (ftype.ty == Tcomplex32) // may lie in 2 eightbytes
366                 {
367                     assert(foffset % 4 == 0);
368                     foreach (ref existingClass; result[fEightbyteStart .. fEightbyteEnd])
369                         existingClass = merge(existingClass, Class.sse);
370                 }
371                 else
372                 {
373                     assert(foffset % 8 == 0 ||
374                         fEightbyteEnd - fEightbyteStart <= 1 ||
375                         !target.isLP64,
376                         "Field not aligned at eightbyte boundary but contributing to multiple eightbytes?"
377                     );
378                     foreach (i, fclass; classify(ftype, fsize).slice())
379                     {
380                         Class* existingClass = &result[fEightbyteStart + i];
381                         *existingClass = merge(*existingClass, fclass);
382                     }
383                 }
384             }
385         }
386 
387         if (nfields == 0)
388             return memory();
389     }
390 
391     void finalizeAggregate()
392     {
393         foreach (i, ref c; result)
394         {
395             if (c == Class.memory ||
396                 (c == Class.x87Up && !(i > 0 && result[i - 1] == Class.x87)))
397                 return memory();
398 
399             if (c == Class.sseUp && !(i > 0 &&
400                 (result[i - 1] == Class.sse || result[i - 1] == Class.sseUp)))
401                 c = Class.sse;
402         }
403 
404         if (numEightbytes > 2)
405         {
406             if (result[0] != Class.sse)
407                 return memory();
408 
409             foreach (c; result[1 .. numEightbytes])
410                 if (c != Class.sseUp)
411                     return memory();
412         }
413 
414         // Undocumented special case for aggregates with the 2nd eightbyte
415         // consisting of padding only (`struct S { align(16) int a; }`).
416         // clang only passes the first eightbyte in that case, so let's do the
417         // same.
418         if (numEightbytes == 2 && result[1] == Class.noClass)
419             numEightbytes = 1;
420     }
421 }