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