1 /**
2  * Break down a D type into basic (register) types for the AArch64 ABI.
3  *
4  * Copyright:   Copyright (C) 1999-2021 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_aarch64.d, _argtypes_aarch64.d)
8  * Documentation:  https://dlang.org/phobos/dmd_argtypes_aarch64.html
9  * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/src/dmd/argtypes_aarch64.d
10  */
11 
12 module dmd.argtypes_aarch64;
13 
14 import dmd.mtype;
15 
16 /****************************************************
17  * This breaks a type down into 'simpler' types that can be passed to a function
18  * in registers, and returned in registers.
19  * This is the implementation for the AAPCS64 ABI, based on
20  * https://github.com/ARM-software/abi-aa/blob/master/aapcs64/aapcs64.rst.
21  * Params:
22  *      t = type to break down
23  * Returns:
24  *      tuple of 1 type if `t` can be passed in registers; e.g., a static array
25  *      for Homogeneous Floating-point/Vector Aggregates (HFVA).
26  *      A tuple of zero length means the type cannot be passed/returned in registers.
27  *      null indicates a `void`.
28  */
29 extern (C++) TypeTuple toArgTypes_aarch64(Type t)
30 {
31     if (t == Type.terror)
32         return new TypeTuple(t);
33 
34     const size = cast(size_t) t.size();
35     if (size == 0)
36         return null;
37 
38     Type tb = t.toBasetype();
39     const isAggregate = tb.ty == Tstruct || tb.ty == Tsarray || tb.ty == Tarray || tb.ty == Tdelegate || tb.iscomplex();
40     if (!isAggregate)
41         return new TypeTuple(t);
42 
43     Type hfvaType;
44     enum maxNumHFVAElements = 4;
45     const isHFVA = size > maxNumHFVAElements * 16 ? false : isHFVA(tb, maxNumHFVAElements, &hfvaType);
46 
47     // non-PODs and larger non-HFVA PODs are passed indirectly by value (pointer to caller-allocated copy)
48     if ((size > 16 && !isHFVA) || !isPOD(tb))
49         return TypeTuple.empty;
50 
51     if (isHFVA)
52     {
53         // pass in SIMD registers
54         return new TypeTuple(hfvaType);
55     }
56 
57     // pass remaining aggregates in 1 or 2 GP registers
58     static Type getGPType(size_t size)
59     {
60         switch (size)
61         {
62         case 1:  return Type.tint8;
63         case 2:  return Type.tint16;
64         case 4:  return Type.tint32;
65         case 8:  return Type.tint64;
66         default: return Type.tint64.sarrayOf((size + 7) / 8);
67         }
68     }
69     return new TypeTuple(getGPType(size));
70 }
71 
72 /**
73  * A Homogeneous Floating-point/Vector Aggregate (HFA/HVA) is an ARM/AArch64
74  * concept that consists of up to 4 elements of the same floating point/vector
75  * type. It is the aggregate final data layout that matters so structs, unions,
76  * static arrays and complex numbers can result in an HFVA.
77  *
78  * simple HFAs: struct F1 {float f;}  struct D4 {double a,b,c,d;}
79  * interesting HFA: struct {F1[2] vals; float weight;}
80  *
81  * If the type is an HFVA and `rewriteType` is specified, it is set to a
82  * corresponding static array type.
83  */
84 extern (C++) bool isHFVA(Type t, int maxNumElements = 4, Type* rewriteType = null)
85 {
86     t = t.toBasetype();
87     if ((t.ty != Tstruct && t.ty != Tsarray && !t.iscomplex()) || !isPOD(t))
88         return false;
89 
90     Type fundamentalType;
91     const N = getNestedHFVA(t, fundamentalType);
92     if (N < 1 || N > maxNumElements)
93         return false;
94 
95     if (rewriteType)
96         *rewriteType = fundamentalType.sarrayOf(N);
97 
98     return true;
99 }
100 
101 private:
102 
103 bool isPOD(Type t)
104 {
105     auto baseType = t.baseElemOf();
106     if (auto ts = baseType.isTypeStruct())
107         return ts.sym.isPOD();
108     return true;
109 }
110 
111 /**
112  * Recursive helper.
113  * Returns size_t.max if the type isn't suited as HFVA (element) or incompatible
114  * to the specified fundamental type, otherwise the number of consumed elements
115  * of that fundamental type.
116  * If `fundamentalType` is null, it is set on the first occasion and then left
117  * untouched.
118  */
119 size_t getNestedHFVA(Type t, ref Type fundamentalType)
120 {
121     t = t.toBasetype();
122 
123     if (auto tarray = t.isTypeSArray())
124     {
125         const N = getNestedHFVA(tarray.nextOf(), fundamentalType);
126         return N == size_t.max ? N : N * cast(size_t) tarray.dim.toUInteger(); // => T[0] may return 0
127     }
128 
129     if (auto tstruct = t.isTypeStruct())
130     {
131         // check each field recursively and set fundamentalType
132         bool isEmpty = true;
133         foreach (field; tstruct.sym.fields)
134         {
135             const field_N = getNestedHFVA(field.type, fundamentalType);
136             if (field_N == size_t.max)
137                 return field_N;
138             if (field_N > 0) // might be 0 for empty static array
139                 isEmpty = false;
140         }
141 
142         // an empty struct (no fields or only empty static arrays) is an undefined
143         // byte, i.e., no HFVA
144         if (isEmpty)
145             return size_t.max;
146 
147         // due to possibly overlapping fields (for unions and nested anonymous
148         // unions), use the overall struct size to determine N
149         const structSize = t.size();
150         const fundamentalSize = fundamentalType.size();
151         assert(structSize % fundamentalSize == 0);
152         return cast(size_t) (structSize / fundamentalSize);
153     }
154 
155     Type thisFundamentalType;
156     size_t N;
157 
158     if (t.isTypeVector())
159     {
160         thisFundamentalType = t;
161         N = 1;
162     }
163     else if (t.isfloating()) // incl. imaginary and complex
164     {
165         auto ftSize = t.size();
166         N = 1;
167 
168         if (t.iscomplex())
169         {
170             ftSize /= 2;
171             N = 2;
172         }
173 
174         switch (ftSize)
175         {
176             case  4: thisFundamentalType = Type.tfloat32; break;
177             case  8: thisFundamentalType = Type.tfloat64; break;
178             case 16: thisFundamentalType = Type.tfloat80; break; // IEEE quadruple
179             default: assert(0, "unexpected floating-point type size");
180         }
181     }
182     else
183     {
184         return size_t.max; // reject all other types
185     }
186 
187     if (!fundamentalType)
188         fundamentalType = thisFundamentalType; // initialize
189     else if (fundamentalType != thisFundamentalType)
190         return size_t.max; // incompatible fundamental types, reject
191 
192     return N;
193 }