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