Skip to content

Commit b9e21af

Browse files
[transforms] Inline simple variadic functions
1 parent 3ab5dbb commit b9e21af

16 files changed

+3550
-0
lines changed
Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py
2+
3+
// REQUIRES: x86-registered-target
4+
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -target-cpu x86-64-v4 -std=c23 -O1 -ffreestanding -emit-llvm -o - %s | FileCheck %s
5+
6+
// This test sanity checks calling a variadic function with the expansion transform disabled.
7+
// The IR test cases {arch}/expand-variadic-call-*.ll correspond to IR generated from this test case.
8+
9+
typedef __builtin_va_list va_list;
10+
#define va_copy(dest, src) __builtin_va_copy(dest, src)
11+
#define va_start(ap, ...) __builtin_va_start(ap, 0)
12+
#define va_end(ap) __builtin_va_end(ap)
13+
#define va_arg(ap, type) __builtin_va_arg(ap, type)
14+
15+
// 32 bit x86 alignment uses getTypeStackAlign for special cases
16+
// Whitebox testing.
17+
// Needs a type >= 16 which is either a simd or a struct containing a simd
18+
// darwinvectorabi should force 4 bytes
19+
// linux vectors with align 16/32/64 return that alignment
20+
21+
22+
void wrapped(va_list);
23+
24+
// CHECK-LABEL: @codegen_for_copy(
25+
// CHECK-NEXT: entry:
26+
// CHECK-NEXT: [[CP:%.*]] = alloca [1 x %struct.__va_list_tag], align 16
27+
// CHECK-NEXT: call void @llvm.lifetime.start.p0(i64 24, ptr nonnull [[CP]]) #[[ATTR7:[0-9]+]]
28+
// CHECK-NEXT: call void @llvm.va_copy(ptr nonnull [[CP]], ptr [[X:%.*]])
29+
// CHECK-NEXT: call void @wrapped(ptr noundef nonnull [[CP]]) #[[ATTR8:[0-9]+]]
30+
// CHECK-NEXT: call void @llvm.va_end(ptr [[CP]])
31+
// CHECK-NEXT: call void @llvm.lifetime.end.p0(i64 24, ptr nonnull [[CP]]) #[[ATTR7]]
32+
// CHECK-NEXT: ret void
33+
//
34+
void codegen_for_copy(va_list x)
35+
{
36+
va_list cp;
37+
va_copy(cp, x);
38+
wrapped(cp);
39+
va_end(cp);
40+
}
41+
42+
43+
// CHECK-LABEL: @vararg(
44+
// CHECK-NEXT: entry:
45+
// CHECK-NEXT: [[VA:%.*]] = alloca [1 x %struct.__va_list_tag], align 16
46+
// CHECK-NEXT: call void @llvm.lifetime.start.p0(i64 24, ptr nonnull [[VA]]) #[[ATTR7]]
47+
// CHECK-NEXT: call void @llvm.va_start(ptr nonnull [[VA]])
48+
// CHECK-NEXT: call void @wrapped(ptr noundef nonnull [[VA]]) #[[ATTR8]]
49+
// CHECK-NEXT: call void @llvm.va_end(ptr [[VA]])
50+
// CHECK-NEXT: call void @llvm.lifetime.end.p0(i64 24, ptr nonnull [[VA]]) #[[ATTR7]]
51+
// CHECK-NEXT: ret void
52+
//
53+
void vararg(...)
54+
{
55+
va_list va;
56+
__builtin_va_start(va, 0);
57+
wrapped(va);
58+
va_end(va);
59+
}
60+
61+
// vectors with alignment 16/32/64 are natively aligned on linux x86
62+
// v32f32 would be a m1024 type, larger than x64 defines at time of writing
63+
typedef int i32;
64+
typedef float v4f32 __attribute__((__vector_size__(16), __aligned__(16)));
65+
typedef float v8f32 __attribute__((__vector_size__(32), __aligned__(32)));
66+
typedef float v16f32 __attribute__((__vector_size__(64), __aligned__(64)));
67+
typedef float v32f32 __attribute__((__vector_size__(128), __aligned__(128)));
68+
69+
70+
// Pass a single value to wrapped() via vararg(...)
71+
// CHECK-LABEL: @single_i32(
72+
// CHECK-NEXT: entry:
73+
// CHECK-NEXT: tail call void (...) @vararg(i32 noundef [[X:%.*]]) #[[ATTR9:[0-9]+]]
74+
// CHECK-NEXT: ret void
75+
//
76+
void single_i32(i32 x)
77+
{
78+
vararg(x);
79+
}
80+
81+
// CHECK-LABEL: @single_double(
82+
// CHECK-NEXT: entry:
83+
// CHECK-NEXT: tail call void (...) @vararg(double noundef [[X:%.*]]) #[[ATTR9]]
84+
// CHECK-NEXT: ret void
85+
//
86+
void single_double(double x)
87+
{
88+
vararg(x);
89+
}
90+
91+
// CHECK-LABEL: @single_v4f32(
92+
// CHECK-NEXT: entry:
93+
// CHECK-NEXT: tail call void (...) @vararg(<4 x float> noundef [[X:%.*]]) #[[ATTR9]]
94+
// CHECK-NEXT: ret void
95+
//
96+
void single_v4f32(v4f32 x)
97+
{
98+
vararg(x);
99+
}
100+
101+
// CHECK-LABEL: @single_v8f32(
102+
// CHECK-NEXT: entry:
103+
// CHECK-NEXT: tail call void (...) @vararg(<8 x float> noundef [[X:%.*]]) #[[ATTR9]]
104+
// CHECK-NEXT: ret void
105+
//
106+
void single_v8f32(v8f32 x)
107+
{
108+
vararg(x);
109+
}
110+
111+
// CHECK-LABEL: @single_v16f32(
112+
// CHECK-NEXT: entry:
113+
// CHECK-NEXT: tail call void (...) @vararg(<16 x float> noundef [[X:%.*]]) #[[ATTR9]]
114+
// CHECK-NEXT: ret void
115+
//
116+
void single_v16f32(v16f32 x)
117+
{
118+
vararg(x);
119+
}
120+
121+
// CHECK-LABEL: @single_v32f32(
122+
// CHECK-NEXT: entry:
123+
// CHECK-NEXT: [[INDIRECT_ARG_TEMP:%.*]] = alloca <32 x float>, align 128
124+
// CHECK-NEXT: [[X:%.*]] = load <32 x float>, ptr [[TMP0:%.*]], align 128, !tbaa [[TBAA2:![0-9]+]]
125+
// CHECK-NEXT: store <32 x float> [[X]], ptr [[INDIRECT_ARG_TEMP]], align 128, !tbaa [[TBAA2]]
126+
// CHECK-NEXT: tail call void (...) @vararg(ptr noundef nonnull byval(<32 x float>) align 128 [[INDIRECT_ARG_TEMP]]) #[[ATTR9]]
127+
// CHECK-NEXT: ret void
128+
//
129+
void single_v32f32(v32f32 x)
130+
{
131+
vararg(x);
132+
}
133+
134+
135+
136+
// CHECK-LABEL: @i32_double(
137+
// CHECK-NEXT: entry:
138+
// CHECK-NEXT: tail call void (...) @vararg(i32 noundef [[X:%.*]], double noundef [[Y:%.*]]) #[[ATTR9]]
139+
// CHECK-NEXT: ret void
140+
//
141+
void i32_double(i32 x, double y)
142+
{
143+
vararg(x, y);
144+
}
145+
146+
// CHECK-LABEL: @double_i32(
147+
// CHECK-NEXT: entry:
148+
// CHECK-NEXT: tail call void (...) @vararg(double noundef [[X:%.*]], i32 noundef [[Y:%.*]]) #[[ATTR9]]
149+
// CHECK-NEXT: ret void
150+
//
151+
void double_i32(double x, i32 y)
152+
{
153+
vararg(x, y);
154+
}
155+
156+
157+
// A struct used by libc variadic tests
158+
159+
typedef struct {
160+
char c;
161+
short s;
162+
int i;
163+
long l;
164+
float f;
165+
double d;
166+
} libcS;
167+
168+
// CHECK-LABEL: @i32_libcS(
169+
// CHECK-NEXT: entry:
170+
// CHECK-NEXT: tail call void (...) @vararg(i32 noundef [[X:%.*]], ptr noundef nonnull byval([[STRUCT_LIBCS:%.*]]) align 8 [[Y:%.*]]) #[[ATTR9]]
171+
// CHECK-NEXT: ret void
172+
//
173+
void i32_libcS(i32 x, libcS y)
174+
{
175+
vararg(x, y);
176+
}
177+
178+
// CHECK-LABEL: @libcS_i32(
179+
// CHECK-NEXT: entry:
180+
// CHECK-NEXT: tail call void (...) @vararg(ptr noundef nonnull byval([[STRUCT_LIBCS:%.*]]) align 8 [[X:%.*]], i32 noundef [[Y:%.*]]) #[[ATTR9]]
181+
// CHECK-NEXT: ret void
182+
//
183+
void libcS_i32(libcS x, i32 y)
184+
{
185+
vararg(x, y);
186+
}
187+
188+
189+
// CHECK-LABEL: @i32_v4f32(
190+
// CHECK-NEXT: entry:
191+
// CHECK-NEXT: tail call void (...) @vararg(i32 noundef [[X:%.*]], <4 x float> noundef [[Y:%.*]]) #[[ATTR9]]
192+
// CHECK-NEXT: ret void
193+
//
194+
void i32_v4f32(i32 x, v4f32 y)
195+
{
196+
vararg(x, y);
197+
}
198+
199+
// CHECK-LABEL: @v4f32_i32(
200+
// CHECK-NEXT: entry:
201+
// CHECK-NEXT: tail call void (...) @vararg(<4 x float> noundef [[X:%.*]], i32 noundef [[Y:%.*]]) #[[ATTR9]]
202+
// CHECK-NEXT: ret void
203+
//
204+
void v4f32_i32(v4f32 x, i32 y)
205+
{
206+
vararg(x, y);
207+
}
208+
209+
// CHECK-LABEL: @i32_v8f32(
210+
// CHECK-NEXT: entry:
211+
// CHECK-NEXT: tail call void (...) @vararg(i32 noundef [[X:%.*]], <8 x float> noundef [[Y:%.*]]) #[[ATTR9]]
212+
// CHECK-NEXT: ret void
213+
//
214+
void i32_v8f32(i32 x, v8f32 y)
215+
{
216+
vararg(x, y);
217+
}
218+
219+
// CHECK-LABEL: @v8f32_i32(
220+
// CHECK-NEXT: entry:
221+
// CHECK-NEXT: tail call void (...) @vararg(<8 x float> noundef [[X:%.*]], i32 noundef [[Y:%.*]]) #[[ATTR9]]
222+
// CHECK-NEXT: ret void
223+
//
224+
void v8f32_i32(v8f32 x, i32 y)
225+
{
226+
vararg(x, y);
227+
}
228+
229+
// CHECK-LABEL: @i32_v16f32(
230+
// CHECK-NEXT: entry:
231+
// CHECK-NEXT: tail call void (...) @vararg(i32 noundef [[X:%.*]], <16 x float> noundef [[Y:%.*]]) #[[ATTR9]]
232+
// CHECK-NEXT: ret void
233+
//
234+
void i32_v16f32(i32 x, v16f32 y)
235+
{
236+
vararg(x, y);
237+
}
238+
239+
// CHECK-LABEL: @v16f32_i32(
240+
// CHECK-NEXT: entry:
241+
// CHECK-NEXT: tail call void (...) @vararg(<16 x float> noundef [[X:%.*]], i32 noundef [[Y:%.*]]) #[[ATTR9]]
242+
// CHECK-NEXT: ret void
243+
//
244+
void v16f32_i32(v16f32 x, i32 y)
245+
{
246+
vararg(x, y);
247+
}
248+
249+
// CHECK-LABEL: @i32_v32f32(
250+
// CHECK-NEXT: entry:
251+
// CHECK-NEXT: [[INDIRECT_ARG_TEMP:%.*]] = alloca <32 x float>, align 128
252+
// CHECK-NEXT: [[Y:%.*]] = load <32 x float>, ptr [[TMP0:%.*]], align 128, !tbaa [[TBAA2]]
253+
// CHECK-NEXT: store <32 x float> [[Y]], ptr [[INDIRECT_ARG_TEMP]], align 128, !tbaa [[TBAA2]]
254+
// CHECK-NEXT: tail call void (...) @vararg(i32 noundef [[X:%.*]], ptr noundef nonnull byval(<32 x float>) align 128 [[INDIRECT_ARG_TEMP]]) #[[ATTR9]]
255+
// CHECK-NEXT: ret void
256+
//
257+
void i32_v32f32(i32 x, v32f32 y)
258+
{
259+
vararg(x, y);
260+
}
261+
262+
// CHECK-LABEL: @v32f32_i32(
263+
// CHECK-NEXT: entry:
264+
// CHECK-NEXT: [[INDIRECT_ARG_TEMP:%.*]] = alloca <32 x float>, align 128
265+
// CHECK-NEXT: [[X:%.*]] = load <32 x float>, ptr [[TMP0:%.*]], align 128, !tbaa [[TBAA2]]
266+
// CHECK-NEXT: store <32 x float> [[X]], ptr [[INDIRECT_ARG_TEMP]], align 128, !tbaa [[TBAA2]]
267+
// CHECK-NEXT: tail call void (...) @vararg(ptr noundef nonnull byval(<32 x float>) align 128 [[INDIRECT_ARG_TEMP]], i32 noundef [[Y:%.*]]) #[[ATTR9]]
268+
// CHECK-NEXT: ret void
269+
//
270+
void v32f32_i32(v32f32 x, i32 y)
271+
{
272+
vararg(x, y);
273+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// REQUIRES: x86-registered-target
2+
// RUN: %clang_cc1 -triple i386-unknown-linux-gnu -O1 -emit-llvm -o - %s | opt --passes=expand-variadics -S | FileCheck %s
3+
// RUN: %clang_cc1 -triple=x86_64-linux-gnu -O1 -emit-llvm -o - %s | opt --passes=expand-variadics -S | FileCheck %s
4+
5+
// neither arm arch is implemented yet, leaving it here as a reminder
6+
// armv6 is a ptr as far as the struct is concerned, but possibly also a [1 x i32] passed by value
7+
// that seems insistent, maybe leave 32 bit arm alone for now
8+
// aarch64 is a struct of five things passed using byval memcpy
9+
10+
// R-N: %clang_cc1 -triple=armv6-none--eabi -O1 -emit-llvm -o - %s | opt --passes=expand-variadics -S | FileCheck %s
11+
12+
// R-N: %clang_cc1 -triple=aarch64-none-linux-gnu -O1 -emit-llvm -o - %s | opt --passes=expand-variadics -S | FileCheck %s
13+
14+
15+
16+
// expand-variadics rewrites calls to variadic functions into calls to
17+
// equivalent functions that take a va_list argument. A property of the
18+
// implementation is that said "equivalent function" may be a pre-existing one.
19+
// This is equivalent to inlining a sufficiently simple variadic wrapper.
20+
21+
#include <stdarg.h>
22+
23+
typedef int FILE; // close enough for this test
24+
25+
// fprintf is sometimes implemented as a call to vfprintf. That fits the
26+
// pattern the transform pass recognises - given an implementation of fprintf
27+
// in the IR module, calls to it can be rewritten into calls into vfprintf.
28+
29+
// CHECK-LABEL: define{{.*}} i32 @fprintf(
30+
// CHECK-LABEL: define{{.*}} i32 @call_fprintf(
31+
// CHECK-NOT: @fprintf
32+
// CHECK: @vfprintf
33+
int vfprintf(FILE *restrict f, const char *restrict fmt, va_list ap);
34+
int fprintf(FILE *restrict f, const char *restrict fmt, ...)
35+
{
36+
int ret;
37+
va_list ap;
38+
va_start(ap, fmt);
39+
ret = vfprintf(f, fmt, ap);
40+
va_end(ap);
41+
return ret;
42+
}
43+
int call_fprintf(FILE *f)
44+
{
45+
int x = 42;
46+
double y = 3.14;
47+
return fprintf(f, "int %d dbl %g\n", x, y);
48+
}
49+
50+
// Void return type is also OK
51+
52+
// CHECK-LABEL: define{{.*}} void @no_result(
53+
// CHECK-LABEL: define{{.*}} void @call_no_result(
54+
// CHECK-NOT: @no_result
55+
// CHECK: @vno_result
56+
void vno_result(const char * fmt, va_list);
57+
void no_result(const char * fmt, ...)
58+
{
59+
va_list ap;
60+
va_start(ap, fmt);
61+
vno_result(fmt, ap);
62+
va_end(ap);
63+
}
64+
void call_no_result(FILE *f)
65+
{
66+
int x = 101;
67+
no_result("", x);
68+
}
69+
70+
// The vaend in the forwarding implementation is optional where it's a no-op
71+
72+
// CHECK-LABEL: define{{.*}} i32 @no_vaend(
73+
// CHECK-LABEL: define{{.*}} i32 @call_no_vaend(
74+
// CHECK-NOT: @no_vaend
75+
// CHECK: @vno_vaend
76+
int vno_vaend(int x, va_list);
77+
int no_vaend(int x, ...)
78+
{
79+
va_list ap;
80+
va_start(ap, x);
81+
return vno_vaend(x, ap);
82+
}
83+
int call_no_vaend(int x)
84+
{
85+
return no_vaend(x, 10, 2.5f);
86+
}

0 commit comments

Comments
 (0)