Skip to content

Commit 794457f

Browse files
[amdgpu] Pass variadic arguments without splitting (#94083)
Pass variadic arguments without changing their type, unlike the fixed ones. Fixed arguments are modified to better fit into registers. This patch leaves those unchanged. Splitting struct types into individual fields and packing small structs into integers works well for passing via registers. Variadic arguments are currently unimplemented in the backend. They're likely to be implemented as a pointer to stack memory in which case register-themed optimisations are inapplicable. Splitting the struct into fields makes it difficult to implement va_arg robustly. The rules around padding and alignment to inverse the struct splitting could be constructed, but at high complexity and no particular advantage. Passing types as-is means there is a 1:1 correspondence with the type information va_arg has to work with and the parameter type at the call site. This is an ABI change, but as the only functions affected are variadic ones which are presently a compilation error, not a functional break. Factored out of the larger #93362 and can land independently.
1 parent e57308b commit 794457f

File tree

2 files changed

+309
-3
lines changed

2 files changed

+309
-3
lines changed

clang/lib/CodeGen/Targets/AMDGPU.cpp

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ class AMDGPUABIInfo final : public DefaultABIInfo {
4545

4646
ABIArgInfo classifyReturnType(QualType RetTy) const;
4747
ABIArgInfo classifyKernelArgumentType(QualType Ty) const;
48-
ABIArgInfo classifyArgumentType(QualType Ty, unsigned &NumRegsLeft) const;
48+
ABIArgInfo classifyArgumentType(QualType Ty, bool Variadic,
49+
unsigned &NumRegsLeft) const;
4950

5051
void computeInfo(CGFunctionInfo &FI) const override;
5152
Address EmitVAArg(CodeGenFunction &CGF, Address VAListAddr,
@@ -103,12 +104,16 @@ void AMDGPUABIInfo::computeInfo(CGFunctionInfo &FI) const {
103104
if (!getCXXABI().classifyReturnType(FI))
104105
FI.getReturnInfo() = classifyReturnType(FI.getReturnType());
105106

107+
unsigned ArgumentIndex = 0;
108+
const unsigned numFixedArguments = FI.getNumRequiredArgs();
109+
106110
unsigned NumRegsLeft = MaxNumRegsForArgsRet;
107111
for (auto &Arg : FI.arguments()) {
108112
if (CC == llvm::CallingConv::AMDGPU_KERNEL) {
109113
Arg.info = classifyKernelArgumentType(Arg.type);
110114
} else {
111-
Arg.info = classifyArgumentType(Arg.type, NumRegsLeft);
115+
bool FixedArgument = ArgumentIndex++ < numFixedArguments;
116+
Arg.info = classifyArgumentType(Arg.type, !FixedArgument, NumRegsLeft);
112117
}
113118
}
114119
}
@@ -197,12 +202,20 @@ ABIArgInfo AMDGPUABIInfo::classifyKernelArgumentType(QualType Ty) const {
197202
return ABIArgInfo::getDirect(LTy, 0, nullptr, false);
198203
}
199204

200-
ABIArgInfo AMDGPUABIInfo::classifyArgumentType(QualType Ty,
205+
ABIArgInfo AMDGPUABIInfo::classifyArgumentType(QualType Ty, bool Variadic,
201206
unsigned &NumRegsLeft) const {
202207
assert(NumRegsLeft <= MaxNumRegsForArgsRet && "register estimate underflow");
203208

204209
Ty = useFirstFieldIfTransparentUnion(Ty);
205210

211+
if (Variadic) {
212+
return ABIArgInfo::getDirect(/*T=*/nullptr,
213+
/*Offset=*/0,
214+
/*Padding=*/nullptr,
215+
/*CanBeFlattened=*/false,
216+
/*Align=*/0);
217+
}
218+
206219
if (isAggregateTypeForABI(Ty)) {
207220
// Records with non-trivial destructors/copy-constructors should not be
208221
// passed by value.
Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
// REQUIRES: amdgpu-registered-target
2+
// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --function-signature
3+
// RUN: %clang_cc1 -cc1 -std=c23 -triple amdgcn-amd-amdhsa -emit-llvm -O1 %s -o - | FileCheck %s
4+
5+
void sink_0(...);
6+
void sink_1(int, ...);
7+
void sink_2(double, int, ...);
8+
9+
// Simple scalar values
10+
11+
// CHECK-LABEL: define {{[^@]+}}@zero_varargs
12+
// CHECK-SAME: (i32 noundef [[F0:%.*]], double noundef [[F1:%.*]]) local_unnamed_addr #[[ATTR0:[0-9]+]] {
13+
// CHECK-NEXT: entry:
14+
// CHECK-NEXT: tail call void (...) @sink_0() #[[ATTR2:[0-9]+]]
15+
// CHECK-NEXT: tail call void (i32, ...) @sink_1(i32 noundef [[F0]]) #[[ATTR2]]
16+
// CHECK-NEXT: tail call void (double, i32, ...) @sink_2(double noundef [[F1]], i32 noundef [[F0]]) #[[ATTR2]]
17+
// CHECK-NEXT: ret void
18+
//
19+
void zero_varargs(int f0, double f1)
20+
{
21+
sink_0();
22+
sink_1(f0);
23+
sink_2(f1, f0);
24+
}
25+
26+
// CHECK-LABEL: define {{[^@]+}}@one_i32
27+
// CHECK-SAME: (i32 noundef [[F0:%.*]], double noundef [[F1:%.*]], i32 noundef [[V0:%.*]]) local_unnamed_addr #[[ATTR0]] {
28+
// CHECK-NEXT: entry:
29+
// CHECK-NEXT: tail call void (...) @sink_0(i32 noundef [[V0]]) #[[ATTR2]]
30+
// CHECK-NEXT: tail call void (i32, ...) @sink_1(i32 noundef [[F0]], i32 noundef [[V0]]) #[[ATTR2]]
31+
// CHECK-NEXT: tail call void (double, i32, ...) @sink_2(double noundef [[F1]], i32 noundef [[F0]], i32 noundef [[V0]]) #[[ATTR2]]
32+
// CHECK-NEXT: ret void
33+
//
34+
void one_i32(int f0, double f1, int v0)
35+
{
36+
sink_0(v0);
37+
sink_1(f0, v0);
38+
sink_2(f1, f0, v0);
39+
}
40+
41+
// CHECK-LABEL: define {{[^@]+}}@one_ptr
42+
// CHECK-SAME: (i32 noundef [[F0:%.*]], double noundef [[F1:%.*]], ptr noundef [[V0:%.*]]) local_unnamed_addr #[[ATTR0]] {
43+
// CHECK-NEXT: entry:
44+
// CHECK-NEXT: tail call void (...) @sink_0(ptr noundef [[V0]]) #[[ATTR2]]
45+
// CHECK-NEXT: tail call void (i32, ...) @sink_1(i32 noundef [[F0]], ptr noundef [[V0]]) #[[ATTR2]]
46+
// CHECK-NEXT: tail call void (double, i32, ...) @sink_2(double noundef [[F1]], i32 noundef [[F0]], ptr noundef [[V0]]) #[[ATTR2]]
47+
// CHECK-NEXT: ret void
48+
//
49+
void one_ptr(int f0, double f1, void* v0)
50+
{
51+
sink_0(v0);
52+
sink_1(f0, v0);
53+
sink_2(f1, f0, v0);
54+
}
55+
56+
// CHECK-LABEL: define {{[^@]+}}@one_f64
57+
// CHECK-SAME: (i32 noundef [[F0:%.*]], double noundef [[F1:%.*]], double noundef [[V0:%.*]]) local_unnamed_addr #[[ATTR0]] {
58+
// CHECK-NEXT: entry:
59+
// CHECK-NEXT: tail call void (...) @sink_0(double noundef [[V0]]) #[[ATTR2]]
60+
// CHECK-NEXT: tail call void (i32, ...) @sink_1(i32 noundef [[F0]], double noundef [[V0]]) #[[ATTR2]]
61+
// CHECK-NEXT: tail call void (double, i32, ...) @sink_2(double noundef [[F1]], i32 noundef [[F0]], double noundef [[V0]]) #[[ATTR2]]
62+
// CHECK-NEXT: ret void
63+
//
64+
void one_f64(int f0, double f1, double v0)
65+
{
66+
sink_0(v0);
67+
sink_1(f0, v0);
68+
sink_2(f1, f0, v0);
69+
}
70+
71+
72+
// C has various type promotion rules for variadics
73+
74+
// CHECK-LABEL: define {{[^@]+}}@one_i8
75+
// CHECK-SAME: (i32 noundef [[F0:%.*]], double noundef [[F1:%.*]], i8 noundef signext [[V0:%.*]]) local_unnamed_addr #[[ATTR0]] {
76+
// CHECK-NEXT: entry:
77+
// CHECK-NEXT: [[CONV:%.*]] = sext i8 [[V0]] to i32
78+
// CHECK-NEXT: tail call void (...) @sink_0(i32 noundef [[CONV]]) #[[ATTR2]]
79+
// CHECK-NEXT: tail call void (i32, ...) @sink_1(i32 noundef [[F0]], i32 noundef [[CONV]]) #[[ATTR2]]
80+
// CHECK-NEXT: tail call void (double, i32, ...) @sink_2(double noundef [[F1]], i32 noundef [[F0]], i32 noundef [[CONV]]) #[[ATTR2]]
81+
// CHECK-NEXT: ret void
82+
//
83+
void one_i8(int f0, double f1, char v0)
84+
{
85+
sink_0(v0);
86+
sink_1(f0, v0);
87+
sink_2(f1, f0, v0);
88+
}
89+
90+
// CHECK-LABEL: define {{[^@]+}}@one_i16
91+
// CHECK-SAME: (i32 noundef [[F0:%.*]], double noundef [[F1:%.*]], i16 noundef signext [[V0:%.*]]) local_unnamed_addr #[[ATTR0]] {
92+
// CHECK-NEXT: entry:
93+
// CHECK-NEXT: [[CONV:%.*]] = sext i16 [[V0]] to i32
94+
// CHECK-NEXT: tail call void (...) @sink_0(i32 noundef [[CONV]]) #[[ATTR2]]
95+
// CHECK-NEXT: tail call void (i32, ...) @sink_1(i32 noundef [[F0]], i32 noundef [[CONV]]) #[[ATTR2]]
96+
// CHECK-NEXT: tail call void (double, i32, ...) @sink_2(double noundef [[F1]], i32 noundef [[F0]], i32 noundef [[CONV]]) #[[ATTR2]]
97+
// CHECK-NEXT: ret void
98+
//
99+
void one_i16(int f0, double f1, short v0)
100+
{
101+
sink_0(v0);
102+
sink_1(f0, v0);
103+
sink_2(f1, f0, v0);
104+
}
105+
106+
// CHECK-LABEL: define {{[^@]+}}@one_f32
107+
// CHECK-SAME: (i32 noundef [[F0:%.*]], double noundef [[F1:%.*]], float noundef [[V0:%.*]]) local_unnamed_addr #[[ATTR0]] {
108+
// CHECK-NEXT: entry:
109+
// CHECK-NEXT: [[CONV:%.*]] = fpext float [[V0]] to double
110+
// CHECK-NEXT: tail call void (...) @sink_0(double noundef [[CONV]]) #[[ATTR2]]
111+
// CHECK-NEXT: tail call void (i32, ...) @sink_1(i32 noundef [[F0]], double noundef [[CONV]]) #[[ATTR2]]
112+
// CHECK-NEXT: tail call void (double, i32, ...) @sink_2(double noundef [[F1]], i32 noundef [[F0]], double noundef [[CONV]]) #[[ATTR2]]
113+
// CHECK-NEXT: ret void
114+
//
115+
void one_f32(int f0, double f1, float v0)
116+
{
117+
sink_0(v0);
118+
sink_1(f0, v0);
119+
sink_2(f1, f0, v0);
120+
}
121+
122+
123+
// Various half types. _Float16 is passed as half and __fp16 as double
124+
125+
// CHECK-LABEL: define {{[^@]+}}@one_f16a
126+
// CHECK-SAME: (i32 noundef [[F0:%.*]], double noundef [[F1:%.*]], half noundef [[V0:%.*]]) local_unnamed_addr #[[ATTR0]] {
127+
// CHECK-NEXT: entry:
128+
// CHECK-NEXT: tail call void (...) @sink_0(half noundef [[V0]]) #[[ATTR2]]
129+
// CHECK-NEXT: tail call void (i32, ...) @sink_1(i32 noundef [[F0]], half noundef [[V0]]) #[[ATTR2]]
130+
// CHECK-NEXT: tail call void (double, i32, ...) @sink_2(double noundef [[F1]], i32 noundef [[F0]], half noundef [[V0]]) #[[ATTR2]]
131+
// CHECK-NEXT: ret void
132+
//
133+
void one_f16a(int f0, double f1, _Float16 v0)
134+
{
135+
sink_0(v0);
136+
sink_1(f0, v0);
137+
sink_2(f1, f0, v0);
138+
}
139+
140+
// CHECK-LABEL: define {{[^@]+}}@one_f16b
141+
// CHECK-SAME: (i32 noundef [[F0:%.*]], double noundef [[F1:%.*]], half noundef [[V0:%.*]]) local_unnamed_addr #[[ATTR0]] {
142+
// CHECK-NEXT: entry:
143+
// CHECK-NEXT: [[CONV:%.*]] = fpext half [[V0]] to double
144+
// CHECK-NEXT: tail call void (...) @sink_0(double noundef [[CONV]]) #[[ATTR2]]
145+
// CHECK-NEXT: tail call void (i32, ...) @sink_1(i32 noundef [[F0]], double noundef [[CONV]]) #[[ATTR2]]
146+
// CHECK-NEXT: tail call void (double, i32, ...) @sink_2(double noundef [[F1]], i32 noundef [[F0]], double noundef [[CONV]]) #[[ATTR2]]
147+
// CHECK-NEXT: ret void
148+
//
149+
void one_f16b(int f0, double f1, __fp16 v0)
150+
{
151+
sink_0(v0);
152+
sink_1(f0, v0);
153+
sink_2(f1, f0, v0);
154+
}
155+
156+
// CHECK-LABEL: define {{[^@]+}}@one_f16c
157+
// CHECK-SAME: (i32 noundef [[F0:%.*]], double noundef [[F1:%.*]], bfloat noundef [[V0:%.*]]) local_unnamed_addr #[[ATTR0]] {
158+
// CHECK-NEXT: entry:
159+
// CHECK-NEXT: tail call void (...) @sink_0(bfloat noundef [[V0]]) #[[ATTR2]]
160+
// CHECK-NEXT: tail call void (i32, ...) @sink_1(i32 noundef [[F0]], bfloat noundef [[V0]]) #[[ATTR2]]
161+
// CHECK-NEXT: tail call void (double, i32, ...) @sink_2(double noundef [[F1]], i32 noundef [[F0]], bfloat noundef [[V0]]) #[[ATTR2]]
162+
// CHECK-NEXT: ret void
163+
//
164+
void one_f16c(int f0, double f1, __bf16 v0)
165+
{
166+
sink_0(v0);
167+
sink_1(f0, v0);
168+
sink_2(f1, f0, v0);
169+
}
170+
171+
// Simple composites
172+
173+
typedef struct
174+
{
175+
double x0;
176+
double x1;
177+
} pair_f64;
178+
179+
// CHECK-LABEL: define {{[^@]+}}@one_pair_f64
180+
// CHECK-SAME: (i32 noundef [[F0:%.*]], double noundef [[F1:%.*]], double [[V0_COERCE0:%.*]], double [[V0_COERCE1:%.*]]) local_unnamed_addr #[[ATTR0]] {
181+
// CHECK-NEXT: entry:
182+
// CHECK-NEXT: [[DOTFCA_0_INSERT:%.*]] = insertvalue [[STRUCT_PAIR_F64:%.*]] poison, double [[V0_COERCE0]], 0
183+
// CHECK-NEXT: [[DOTFCA_1_INSERT:%.*]] = insertvalue [[STRUCT_PAIR_F64]] [[DOTFCA_0_INSERT]], double [[V0_COERCE1]], 1
184+
// CHECK-NEXT: tail call void (...) @sink_0([[STRUCT_PAIR_F64]] [[DOTFCA_1_INSERT]]) #[[ATTR2]]
185+
// CHECK-NEXT: tail call void (i32, ...) @sink_1(i32 noundef [[F0]], [[STRUCT_PAIR_F64]] [[DOTFCA_1_INSERT]]) #[[ATTR2]]
186+
// CHECK-NEXT: tail call void (double, i32, ...) @sink_2(double noundef [[F1]], i32 noundef [[F0]], [[STRUCT_PAIR_F64]] [[DOTFCA_1_INSERT]]) #[[ATTR2]]
187+
// CHECK-NEXT: ret void
188+
//
189+
void one_pair_f64(int f0, double f1, pair_f64 v0)
190+
{
191+
sink_0(v0);
192+
sink_1(f0, v0);
193+
sink_2(f1, f0, v0);
194+
}
195+
196+
typedef double v2f64 __attribute__((ext_vector_type(2)));
197+
198+
// CHECK-LABEL: define {{[^@]+}}@one_pair_v2f64
199+
// CHECK-SAME: (i32 noundef [[F0:%.*]], double noundef [[F1:%.*]], <2 x double> noundef [[V0:%.*]]) local_unnamed_addr #[[ATTR0]] {
200+
// CHECK-NEXT: entry:
201+
// CHECK-NEXT: tail call void (...) @sink_0(<2 x double> noundef [[V0]]) #[[ATTR2]]
202+
// CHECK-NEXT: tail call void (i32, ...) @sink_1(i32 noundef [[F0]], <2 x double> noundef [[V0]]) #[[ATTR2]]
203+
// CHECK-NEXT: tail call void (double, i32, ...) @sink_2(double noundef [[F1]], i32 noundef [[F0]], <2 x double> noundef [[V0]]) #[[ATTR2]]
204+
// CHECK-NEXT: ret void
205+
//
206+
void one_pair_v2f64(int f0, double f1, v2f64 v0)
207+
{
208+
sink_0(v0);
209+
sink_1(f0, v0);
210+
sink_2(f1, f0, v0);
211+
}
212+
213+
typedef union
214+
{
215+
float x0;
216+
int x1;
217+
} union_f32_i32;
218+
219+
// CHECK-LABEL: define {{[^@]+}}@one_pair_union_f32_i32
220+
// CHECK-SAME: (i32 noundef [[F0:%.*]], double noundef [[F1:%.*]], i32 [[V0_COERCE:%.*]]) local_unnamed_addr #[[ATTR0]] {
221+
// CHECK-NEXT: entry:
222+
// CHECK-NEXT: [[TMP0:%.*]] = bitcast i32 [[V0_COERCE]] to float
223+
// CHECK-NEXT: [[DOTFCA_0_INSERT:%.*]] = insertvalue [[UNION_UNION_F32_I32:%.*]] poison, float [[TMP0]], 0
224+
// CHECK-NEXT: tail call void (...) @sink_0([[UNION_UNION_F32_I32]] [[DOTFCA_0_INSERT]]) #[[ATTR2]]
225+
// CHECK-NEXT: tail call void (i32, ...) @sink_1(i32 noundef [[F0]], [[UNION_UNION_F32_I32]] [[DOTFCA_0_INSERT]]) #[[ATTR2]]
226+
// CHECK-NEXT: tail call void (double, i32, ...) @sink_2(double noundef [[F1]], i32 noundef [[F0]], [[UNION_UNION_F32_I32]] [[DOTFCA_0_INSERT]]) #[[ATTR2]]
227+
// CHECK-NEXT: ret void
228+
//
229+
void one_pair_union_f32_i32(int f0, double f1, union_f32_i32 v0)
230+
{
231+
sink_0(v0);
232+
sink_1(f0, v0);
233+
sink_2(f1, f0, v0);
234+
}
235+
236+
typedef union
237+
{
238+
int x0;
239+
float x1;
240+
} transparent_union_f32_i32 __attribute__((transparent_union));
241+
242+
// CHECK-LABEL: define {{[^@]+}}@one_pair_transparent_union_f32_i32
243+
// CHECK-SAME: (i32 noundef [[F0:%.*]], double noundef [[F1:%.*]], i32 [[V0_COERCE:%.*]]) local_unnamed_addr #[[ATTR0]] {
244+
// CHECK-NEXT: entry:
245+
// CHECK-NEXT: [[DOTFCA_0_INSERT:%.*]] = insertvalue [[UNION_TRANSPARENT_UNION_F32_I32:%.*]] poison, i32 [[V0_COERCE]], 0
246+
// CHECK-NEXT: tail call void (...) @sink_0([[UNION_TRANSPARENT_UNION_F32_I32]] [[DOTFCA_0_INSERT]]) #[[ATTR2]]
247+
// CHECK-NEXT: tail call void (i32, ...) @sink_1(i32 noundef [[F0]], [[UNION_TRANSPARENT_UNION_F32_I32]] [[DOTFCA_0_INSERT]]) #[[ATTR2]]
248+
// CHECK-NEXT: tail call void (double, i32, ...) @sink_2(double noundef [[F1]], i32 noundef [[F0]], [[UNION_TRANSPARENT_UNION_F32_I32]] [[DOTFCA_0_INSERT]]) #[[ATTR2]]
249+
// CHECK-NEXT: ret void
250+
//
251+
void one_pair_transparent_union_f32_i32(int f0, double f1, transparent_union_f32_i32 v0)
252+
{
253+
sink_0(v0);
254+
sink_1(f0, v0);
255+
sink_2(f1, f0, v0);
256+
}
257+
258+
// Passing multiple values in the variadic pack
259+
260+
// CHECK-LABEL: define {{[^@]+}}@multiple_one
261+
// CHECK-SAME: (i32 noundef [[F0:%.*]], double noundef [[F1:%.*]], i32 noundef [[V0:%.*]], double noundef [[V1:%.*]]) local_unnamed_addr #[[ATTR0]] {
262+
// CHECK-NEXT: entry:
263+
// CHECK-NEXT: tail call void (...) @sink_0(i32 noundef [[V0]], double noundef [[V1]]) #[[ATTR2]]
264+
// CHECK-NEXT: tail call void (i32, ...) @sink_1(i32 noundef [[F0]], i32 noundef [[V0]], double noundef [[V1]]) #[[ATTR2]]
265+
// CHECK-NEXT: tail call void (double, i32, ...) @sink_2(double noundef [[F1]], i32 noundef [[F0]], i32 noundef [[V0]], double noundef [[V1]]) #[[ATTR2]]
266+
// CHECK-NEXT: ret void
267+
//
268+
void multiple_one(int f0, double f1, int v0, double v1)
269+
{
270+
sink_0(v0, v1);
271+
sink_1(f0, v0, v1);
272+
sink_2(f1, f0, v0, v1);
273+
}
274+
275+
// CHECK-LABEL: define {{[^@]+}}@multiple_two
276+
// CHECK-SAME: (i32 noundef [[F0:%.*]], double noundef [[F1:%.*]], double [[V0_COERCE0:%.*]], double [[V0_COERCE1:%.*]], float noundef [[V1:%.*]], i32 [[V2_COERCE:%.*]], i32 noundef [[V3:%.*]]) local_unnamed_addr #[[ATTR0]] {
277+
// CHECK-NEXT: entry:
278+
// CHECK-NEXT: [[TMP0:%.*]] = bitcast i32 [[V2_COERCE]] to float
279+
// CHECK-NEXT: [[CONV:%.*]] = fpext float [[V1]] to double
280+
// CHECK-NEXT: [[DOTFCA_0_INSERT16:%.*]] = insertvalue [[STRUCT_PAIR_F64:%.*]] poison, double [[V0_COERCE0]], 0
281+
// CHECK-NEXT: [[DOTFCA_1_INSERT:%.*]] = insertvalue [[STRUCT_PAIR_F64]] [[DOTFCA_0_INSERT16]], double [[V0_COERCE1]], 1
282+
// CHECK-NEXT: [[DOTFCA_0_INSERT:%.*]] = insertvalue [[UNION_UNION_F32_I32:%.*]] poison, float [[TMP0]], 0
283+
// CHECK-NEXT: tail call void (...) @sink_0([[STRUCT_PAIR_F64]] [[DOTFCA_1_INSERT]], double noundef [[CONV]], [[UNION_UNION_F32_I32]] [[DOTFCA_0_INSERT]], i32 noundef [[V3]]) #[[ATTR2]]
284+
// CHECK-NEXT: tail call void (i32, ...) @sink_1(i32 noundef [[F0]], [[STRUCT_PAIR_F64]] [[DOTFCA_1_INSERT]], double noundef [[CONV]], [[UNION_UNION_F32_I32]] [[DOTFCA_0_INSERT]], i32 noundef [[V3]]) #[[ATTR2]]
285+
// CHECK-NEXT: tail call void (double, i32, ...) @sink_2(double noundef [[F1]], i32 noundef [[F0]], [[STRUCT_PAIR_F64]] [[DOTFCA_1_INSERT]], double noundef [[CONV]], [[UNION_UNION_F32_I32]] [[DOTFCA_0_INSERT]], i32 noundef [[V3]]) #[[ATTR2]]
286+
// CHECK-NEXT: ret void
287+
//
288+
void multiple_two(int f0, double f1, pair_f64 v0, float v1, union_f32_i32 v2, int v3)
289+
{
290+
sink_0(v0, v1, v2, v3);
291+
sink_1(f0, v0, v1, v2, v3);
292+
sink_2(f1, f0, v0, v1, v2, v3);
293+
}

0 commit comments

Comments
 (0)