GCC Code Coverage Report


src/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
177 / 190
93.2%
Branches:
109 / 128
85.2%
Functions:
21 / 22
95.5%

irgenerator/abi-info.spice
Line Branch Exec Source
1 // Copyright (c) 2021-2026 ChilliBits. All rights reserved.
2
3 // Std imports
4 import "std/data/vector";
5 import "std/bindings/llvm/llvm" as llvm;
6
7 public type ReturnABIKind enum {
8 DIRECT, // Returned as is
9 COERCED, // Returned in registers as another type with the same memory representation
10 INDIRECT // Returned via a hidden sret pointer to caller-allocated memory, passed as first argument
11 }
12
13 public type ReturnABIInfo struct {
14 public ReturnABIKind kind = ReturnABIKind::DIRECT
15 public llvm::Type memoryType // Type of the return value in memory
16 public llvm::Type coercedType // Type, that is returned in registers (only set for COERCED)
17 }
18
19 42092 public f<bool> ReturnABIInfo.isDirect() {
20 42092 return this.kind == ReturnABIKind::DIRECT;
21 }
22
23 157886 public f<bool> ReturnABIInfo.isCoerced() {
24 157886 return this.kind == ReturnABIKind::COERCED;
25 }
26
27 369036 public f<bool> ReturnABIInfo.isIndirect() {
28 369036 return this.kind == ReturnABIKind::INDIRECT;
29 }
30
31 // Register class of an eightbyte in the x86-64 SysV ABI
32 type ArgClass enum {
33 NO_CLASS,
34 INTEGER,
35 SSE,
36 MEMORY
37 }
38
39 // Scalar of an aggregate together with its offset
40 type Scalar struct {
41 llvm::Type llvmType
42 unsigned long offset
43 }
44
45 5674 f<ArgClass> mergeClasses(ArgClass accumulated, ArgClass field) {
46
4/4
✓ Branch 0 taken 267 times.
✓ Branch 1 taken 5407 times.
✓ Branch 2 taken 267 times.
✓ Branch 3 taken 5407 times.
5674 if accumulated == field || field == ArgClass::NO_CLASS {
47 267 return accumulated;
48 }
49
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 5395 times.
5407 if accumulated == ArgClass::NO_CLASS {
50 5395 return field;
51 }
52
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
12 if accumulated == ArgClass::MEMORY || field == ArgClass::MEMORY {
53 12 return ArgClass::MEMORY;
54 }
55 ✗ return ArgClass::INTEGER; // INTEGER and SSE
56 }
57
58 /**
59 * Lowers return values to the C calling convention of the target, like Clang does it. Aggregates, that do not fit into
60 * the return registers, are returned via an sret pointer, small ones are coerced to the types, that are returned in the
61 * return registers. Types, that are non-trivial for the purpose of calls (custom copy ctor or dtor), are always returned
62 * via an sret pointer, like in the Itanium C++ ABI.
63 */
64 3499 public type ABIInfo struct {
65 llvm::Triple targetTriple
66 llvm::Module* module
67 llvm::LLVMContext* context
68 }
69
70 3499 public p ABIInfo.ctor(const llvm::Triple& targetTriple, llvm::Module* module, llvm::LLVMContext* context) {
71 3499 this.targetTriple = targetTriple;
72 3499 this.module = module;
73 3499 this.context = context;
74 3499 }
75
76 /**
77 * Classify how a value of the given type is returned from a function
78 *
79 * @param llvmType Type of the return value
80 * @param isNonTrivial True if the type has a custom copy ctor or dtor (also transitively via its fields)
81 * @return Return ABI info
82 */
83 239595 public f<ReturnABIInfo> ABIInfo.classifyReturnType(llvm::Type llvmType, bool isNonTrivial) {
84 // Only aggregates are subject to lowering
85
2/2
✓ Branch 0 taken 205502 times.
✓ Branch 1 taken 34093 times.
239595 if !llvmType.isAggregateType() {
86 205502 return direct(llvmType);
87 }
88
89 // Types, that can't be copied trivially, are always returned via memory, so that they keep their address
90
2/2
✓ Branch 0 taken 21609 times.
✓ Branch 1 taken 12484 times.
34093 if isNonTrivial {
91 21609 return indirect(llvmType);
92 }
93
94 // Empty aggregates do not occupy any return register
95
1/2
✓ Branch 0 taken 12484 times.
✗ Branch 1 not taken.
12484 if this.module.getTypeAllocSize(llvmType) == 0l {
96 ✗ return direct(llvmType);
97 }
98
99
2/2
✓ Branch 0 taken 12108 times.
✓ Branch 1 taken 376 times.
12484 if this.targetTriple.isX86_64() {
100
2/2
✓ Branch 0 taken 188 times.
✓ Branch 1 taken 11920 times.
12108 return this.targetTriple.isOSWindows() ? this.classifyReturnTypeWin64(llvmType) : this.classifyReturnTypeX86_64SysV(llvmType);
101 }
102
2/2
✓ Branch 0 taken 232 times.
✓ Branch 1 taken 144 times.
376 if this.targetTriple.isAArch64() {
103 232 return this.classifyReturnTypeAArch64(llvmType);
104 }
105
1/2
✓ Branch 0 taken 144 times.
✗ Branch 1 not taken.
144 if this.targetTriple.isWasm() {
106 144 return this.classifyReturnTypeWebAssembly(llvmType);
107 }
108 // Like the default ABI of Clang, return all other aggregates via memory
109 ✗ return indirect(llvmType);
110 }
111
112 11920 f<ReturnABIInfo> ABIInfo.classifyReturnTypeX86_64SysV(llvm::Type llvmType) {
113 // Aggregates larger than two eightbytes are returned via memory
114 11920 const unsigned long size = this.module.getTypeAllocSize(llvmType);
115
2/2
✓ Branch 0 taken 8044 times.
✓ Branch 1 taken 3876 times.
11920 if size > 16l {
116 8044 return indirect(llvmType);
117 }
118
119 // Classify both eightbytes, based on the scalars they contain
120 3876 ArgClass[2] classes = [ArgClass::NO_CLASS, ArgClass::NO_CLASS];
121 3876 Vector<Scalar> scalars;
122 3876 this.collectScalars(llvmType, 0l, scalars);
123
2/2
✓ Branch 0 taken 5662 times.
✓ Branch 1 taken 3876 times.
9538 foreach const Scalar& scalar : scalars {
124 5662 llvm::Type scalarType = scalar.llvmType;
125 5662 ArgClass scalarClass = ArgClass::MEMORY;
126
4/4
✓ Branch 0 taken 2133 times.
✓ Branch 1 taken 3529 times.
✓ Branch 2 taken 5570 times.
✓ Branch 3 taken 92 times.
5662 if scalarType.isIntegerTy() || scalarType.isPointerTy() {
127 5570 scalarClass = ArgClass::INTEGER;
128
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 92 times.
5662 } else if scalarType.isDoubleTy() {
129 92 scalarClass = ArgClass::SSE;
130 92 }
131 // Unaligned fields, e.g. in packed structs, force the aggregate to be returned via memory
132
2/2
✓ Branch 0 taken 5650 times.
✓ Branch 1 taken 12 times.
5662 if scalar.offset % cast<unsigned long>(this.module.getABITypeAlign(scalarType)) != 0l {
133 12 scalarClass = ArgClass::MEMORY;
134 12 }
135 5662 const unsigned long firstEightbyte = scalar.offset / 8l;
136 5662 const unsigned long lastEightbyte = (scalar.offset + this.module.getTypeStoreSize(scalarType) - 1l) / 8l;
137
4/4
✓ Branch 0 taken 5662 times.
✓ Branch 1 taken 5674 times.
✓ Branch 2 taken 5674 times.
✓ Branch 3 taken 5662 times.
11336 for unsigned long i = firstEightbyte; i <= lastEightbyte && i < 2l; i++ {
138 5674 classes[i] = mergeClasses(classes[i], scalarClass);
139 5674 }
140 5662 }
141
4/4
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 3864 times.
✓ Branch 2 taken 12 times.
✓ Branch 3 taken 3864 times.
3876 if classes[0] == ArgClass::MEMORY || classes[1] == ArgClass::MEMORY {
142 24 return indirect(llvmType);
143 }
144
2/4
✓ Branch 0 taken 3864 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 3864 times.
3864 if classes[0] == ArgClass::NO_CLASS && classes[1] == ArgClass::NO_CLASS {
145 ✗ return direct(llvmType);
146 }
147 // Leading padding is passed in a general purpose register
148
1/2
✓ Branch 0 taken 3864 times.
✗ Branch 1 not taken.
3864 if classes[0] == ArgClass::NO_CLASS {
149 ✗ classes[0] = ArgClass::INTEGER;
150 ✗ }
151
152 // Build the type, that is returned in registers. Spice has no single precision floating point type, so an SSE
153 // eightbyte always holds a double
154 3864 llvm::Type loType = this.getEightbyteType(llvmType, 0l, classes[0]);
155
2/2
✓ Branch 0 taken 1507 times.
✓ Branch 1 taken 2357 times.
3864 if classes[1] == ArgClass::NO_CLASS {
156 4714 return coerced(llvmType, loType);
157 }
158 1507 const llvm::Type hiType = this.getEightbyteType(llvmType, 8l, classes[1]);
159 // The high part has to start at offset 8, so widen the low part, if the high part would start earlier otherwise
160 1507 const unsigned long hiAlign = cast<unsigned long>(this.module.getABITypeAlign(hiType));
161 1507 const unsigned long hiStart = (this.module.getTypeAllocSize(loType) + hiAlign - 1l) / hiAlign * hiAlign;
162
1/2
✓ Branch 0 taken 1507 times.
✗ Branch 1 not taken.
1507 if hiStart != 8l {
163 ✗ loType = llvm::getInt64Ty(*this.context);
164 ✗ }
165 1507 Vector<llvm::Type> elementTypes;
166 1507 elementTypes.pushBack(loType);
167 1507 elementTypes.pushBack(hiType);
168 3014 return coerced(llvmType, llvm::getStructType(*this.context, elementTypes));
169 3876 }
170
171 5371 f<llvm::Type> ABIInfo.getEightbyteType(llvm::Type llvmType, unsigned long offset, ArgClass argClass) {
172
2/2
✓ Branch 0 taken 92 times.
✓ Branch 1 taken 5279 times.
5371 if argClass == ArgClass::SSE {
173 92 return llvm::getDoubleTy(*this.context);
174 }
175 5279 return this.getIntegerTypeAtOffset(llvmType, offset);
176 }
177
178 188 f<ReturnABIInfo> ABIInfo.classifyReturnTypeWin64(llvm::Type llvmType) {
179 // Aggregates of size 1, 2, 4 or 8 bytes are returned in RAX, all others via memory
180 188 const unsigned long size = this.module.getTypeAllocSize(llvmType);
181
4/4
✓ Branch 0 taken 140 times.
✓ Branch 1 taken 48 times.
✓ Branch 2 taken 152 times.
✓ Branch 3 taken 36 times.
188 if size > 8l || (size & (size - 1l)) != 0l {
182 152 return indirect(llvmType);
183 }
184 36 return coerced(llvmType, llvm::getIntNTy(*this.context, cast<unsigned int>(size * 8l)));
185 }
186
187 232 f<ReturnABIInfo> ABIInfo.classifyReturnTypeAArch64(llvm::Type llvmType) {
188 // Homogeneous floating point aggregates with up to four members are returned in the floating point registers
189 232 const unsigned long memberCount = this.getHomogeneousFPAggregateMemberCount(llvmType);
190
4/4
✓ Branch 0 taken 208 times.
✓ Branch 1 taken 24 times.
✓ Branch 2 taken 24 times.
✓ Branch 3 taken 208 times.
232 if memberCount > 0l && memberCount <= 4l {
191 24 return direct(llvmType);
192 }
193
194 // Aggregates of up to 16 bytes are returned in general purpose registers, all others via memory
195 208 const unsigned long size = this.module.getTypeAllocSize(llvmType);
196
2/2
✓ Branch 0 taken 88 times.
✓ Branch 1 taken 120 times.
208 if size > 16l {
197 88 return indirect(llvmType);
198 }
199
2/2
✓ Branch 0 taken 36 times.
✓ Branch 1 taken 84 times.
120 if size <= 8l {
200 36 return coerced(llvmType, llvm::getIntNTy(*this.context, cast<unsigned int>(size * 8l)));
201 }
202
1/2
✓ Branch 0 taken 84 times.
✗ Branch 1 not taken.
84 if this.module.getABITypeAlign(llvmType) < 16 {
203 84 return coerced(llvmType, llvm::getArrayType(llvm::getInt64Ty(*this.context), 2l));
204 }
205 ✗ return coerced(llvmType, llvm::getIntNTy(*this.context, 128));
206 }
207
208 144 f<ReturnABIInfo> ABIInfo.classifyReturnTypeWebAssembly(llvm::Type llvmType) {
209 // Structs with a single scalar element are returned as this element, all other aggregates via memory
210 144 const llvm::Type elementType = this.getSingleElementType(llvmType);
211
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 132 times.
144 if !elementType.isNull() {
212 12 return coerced(llvmType, elementType);
213 }
214 132 return indirect(llvmType);
215 }
216
217 /**
218 * Get the integer type for the eightbyte at the given offset in the given type, like Clang's GetINTEGERTypeAtOffset
219 */
220 5279 f<llvm::Type> ABIInfo.getIntegerTypeAtOffset(llvm::Type llvmType, unsigned long offset) {
221 5279 llvm::Type scalarType = this.getScalarTypeAtOffset(llvmType, offset);
222
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5279 times.
5279 if !scalarType.isNull() {
223 // Pointers and 64-bit integers fill the whole eightbyte
224
4/4
✓ Branch 0 taken 3437 times.
✓ Branch 1 taken 1842 times.
✓ Branch 2 taken 4861 times.
✓ Branch 3 taken 418 times.
5279 if scalarType.isPointerTy() || scalarType.isIntegerTy(64) {
225 4861 return scalarType;
226 }
227 // Smaller integers can be used, if the rest of the eightbyte contains only padding. Booleans are bytes in memory
228
6/8
✓ Branch 0 taken 361 times.
✓ Branch 1 taken 57 times.
✓ Branch 2 taken 304 times.
✓ Branch 3 taken 57 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 304 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 418 times.
418 if scalarType.isIntegerTy(1) || scalarType.isIntegerTy(8) || scalarType.isIntegerTy(16) || scalarType.isIntegerTy(32) {
229
2/2
✓ Branch 0 taken 57 times.
✓ Branch 1 taken 361 times.
418 const unsigned int bitWidth = scalarType.isIntegerTy(1) ? 8 : scalarType.getIntegerBitWidth();
230
2/2
✓ Branch 0 taken 163 times.
✓ Branch 1 taken 255 times.
418 if this.bitsContainNoUserData(llvmType, offset * 8l + cast<unsigned long>(bitWidth), offset * 8l + 64l) {
231 163 return llvm::getIntNTy(*this.context, bitWidth);
232 }
233 255 }
234 255 }
235 // Fall back to an integer, that covers the rest of the eightbyte
236 255 const unsigned long size = this.module.getTypeAllocSize(llvmType);
237
2/2
✓ Branch 0 taken 57 times.
✓ Branch 1 taken 198 times.
255 const unsigned long restSize = size - offset < 8l ? size - offset : 8l;
238 255 return llvm::getIntNTy(*this.context, cast<unsigned int>(restSize * 8l));
239 }
240
241 /**
242 * Get the scalar type, that starts at exactly the given offset in the given type
243 *
244 * @return Scalar type or a null type, if there is none
245 */
246 10655 f<llvm::Type> ABIInfo.getScalarTypeAtOffset(llvm::Type llvmType, unsigned long offset) {
247
2/2
✓ Branch 0 taken 5352 times.
✓ Branch 1 taken 5303 times.
10655 if llvmType.isStructTy() {
248
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5352 times.
5352 if offset >= this.module.getTypeAllocSize(llvmType) {
249 ✗ return llvm::Type();
250 }
251 5352 const unsigned int elementIdx = this.module.getStructElementContainingOffset(llvmType, offset);
252 5352 const unsigned long elementOffset = this.module.getStructElementOffset(llvmType, elementIdx);
253 5352 return this.getScalarTypeAtOffset(llvmType.getStructElementType(elementIdx), offset - elementOffset);
254 }
255
2/2
✓ Branch 0 taken 24 times.
✓ Branch 1 taken 5279 times.
5303 if llvmType.isArrayTy() {
256 24 const llvm::Type elementType = llvmType.getArrayElementType();
257 24 const unsigned long elementSize = this.module.getTypeAllocSize(elementType);
258
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 24 times.
24 if elementSize == 0l || offset >= elementSize * llvmType.getArrayNumElements() {
259 ✗ return llvm::Type();
260 }
261 24 return this.getScalarTypeAtOffset(elementType, offset % elementSize);
262 }
263
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5279 times.
5279 return offset == 0l ? llvmType : llvm::Type();
264 }
265
266 /**
267 * Check if none of the scalars in the given type overlaps the given bit range, like Clang's BitsContainNoUserData
268 */
269 418 f<bool> ABIInfo.bitsContainNoUserData(llvm::Type llvmType, unsigned long startBit, unsigned long endBit) {
270 418 Vector<Scalar> scalars;
271 418 this.collectScalars(llvmType, 0l, scalars);
272
2/2
✓ Branch 0 taken 814 times.
✓ Branch 1 taken 163 times.
977 foreach const Scalar& scalar : scalars {
273 814 const unsigned long scalarStartBit = scalar.offset * 8l;
274 814 const unsigned long scalarEndBit = scalarStartBit + this.module.getTypeStoreSize(scalar.llvmType) * 8l;
275
4/4
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 802 times.
✓ Branch 2 taken 255 times.
✓ Branch 3 taken 559 times.
814 if scalarStartBit < endBit && scalarEndBit > startBit {
276 255 return false;
277 }
278 559 }
279 163 return true;
280 418 }
281
282 /**
283 * Collect all scalars in the given type, together with their offsets
284 */
285 12311 p ABIInfo.collectScalars(llvm::Type llvmType, unsigned long offset, Vector<Scalar>& scalars) {
286
2/2
✓ Branch 0 taken 4827 times.
✓ Branch 1 taken 7484 times.
12311 if llvmType.isStructTy() {
287 4827 const unsigned int elementCount = llvmType.getStructNumElements();
288
2/2
✓ Branch 0 taken 7461 times.
✓ Branch 1 taken 4827 times.
12288 for unsigned int i = 0; i < elementCount; i++ {
289 7461 const unsigned long elementOffset = this.module.getStructElementOffset(llvmType, i);
290 7461 this.collectScalars(llvmType.getStructElementType(i), offset + elementOffset, scalars);
291 7461 }
292
2/2
✓ Branch 0 taken 60 times.
✓ Branch 1 taken 7424 times.
12311 } else if llvmType.isArrayTy() {
293 60 const llvm::Type elementType = llvmType.getArrayElementType();
294 60 const unsigned long elementSize = this.module.getTypeAllocSize(elementType);
295 60 const unsigned long elementCount = llvmType.getArrayNumElements();
296
2/2
✓ Branch 0 taken 180 times.
✓ Branch 1 taken 60 times.
240 for unsigned long i = 0l; i < elementCount; i++ {
297 180 this.collectScalars(elementType, offset + i * elementSize, scalars);
298 180 }
299 60 } else {
300 7424 scalars.pushBack(Scalar{llvmType, offset});
301 7484 }
302 12311 }
303
304 /**
305 * Get the number of members of the given homogeneous floating point aggregate. Spice has no single precision floating
306 * point type, so these are aggregates, that consist of doubles only.
307 *
308 * @return Number of members or 0, if the type is no homogeneous floating point aggregate
309 */
310 232 f<unsigned long> ABIInfo.getHomogeneousFPAggregateMemberCount(llvm::Type llvmType) {
311 232 Vector<Scalar> scalars;
312 232 this.collectScalars(llvmType, 0l, scalars);
313
2/2
✓ Branch 0 taken 256 times.
✓ Branch 1 taken 24 times.
280 foreach const Scalar& scalar : scalars {
314 256 llvm::Type scalarType = scalar.llvmType;
315
2/2
✓ Branch 0 taken 208 times.
✓ Branch 1 taken 48 times.
256 if !scalarType.isDoubleTy() {
316 208 return 0l;
317 }
318 48 }
319 // There must not be any padding
320
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 24 times.
24 if scalars.getSize() * 8l != this.module.getTypeAllocSize(llvmType) {
321 ✗ return 0l;
322 }
323 48 return scalars.getSize();
324 232 }
325
326 /**
327 * Get the type of the only scalar of a struct, like Clang's isSingleElementStruct
328 *
329 * @return Element type or a null type, if the type is no single element struct
330 */
331 144 f<llvm::Type> ABIInfo.getSingleElementType(llvm::Type llvmType) {
332 144 Vector<Scalar> scalars;
333 144 this.collectScalars(llvmType, 0l, scalars);
334
2/2
✓ Branch 0 taken 132 times.
✓ Branch 1 taken 12 times.
144 if scalars.getSize() != 1l {
335 264 return llvm::Type();
336 }
337 12 const llvm::Type elementType = scalars.get(0l).llvmType;
338 // The element must fill the whole struct
339
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 if this.module.getTypeAllocSize(elementType) != this.module.getTypeAllocSize(llvmType) {
340 ✗ return llvm::Type();
341 }
342 24 return elementType;
343 144 }
344
345 205526 f<ReturnABIInfo> direct(llvm::Type llvmType) {
346 205526 return ReturnABIInfo{ReturnABIKind::DIRECT, llvmType, llvm::Type()};
347 }
348
349 4032 f<ReturnABIInfo> coerced(llvm::Type llvmType, llvm::Type coercedType) {
350 4032 return ReturnABIInfo{ReturnABIKind::COERCED, llvmType, coercedType};
351 }
352
353 30037 f<ReturnABIInfo> indirect(llvm::Type llvmType) {
354 30037 return ReturnABIInfo{ReturnABIKind::INDIRECT, llvmType, llvm::Type()};
355 }
356