Skip to content

[WIP] Expand variadic functions in IR #89007

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions clang/lib/CodeGen/ABIInfoImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ llvm::Value *CodeGen::emitRoundPointerUpToAlignment(CodeGenFunction &CGF,
llvm::Value *RoundUp = CGF.Builder.CreateConstInBoundsGEP1_32(
CGF.Builder.getInt8Ty(), Ptr, Align.getQuantity() - 1);
return CGF.Builder.CreateIntrinsic(
llvm::Intrinsic::ptrmask, {Ptr->getType(), CGF.IntPtrTy},
llvm::Intrinsic::ptrmask, {Ptr->getType(), CGF.IntPtrTy},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spurious whitespace change?

{RoundUp, llvm::ConstantInt::get(CGF.IntPtrTy, -Align.getQuantity())},
nullptr, Ptr->getName() + ".aligned");
}
Expand Down Expand Up @@ -247,7 +247,7 @@ Address CodeGen::emitMergePHI(CodeGenFunction &CGF, Address Addr1,

bool CodeGen::isEmptyField(ASTContext &Context, const FieldDecl *FD,
bool AllowArrays, bool AsIfNoUniqueAddr) {
if (FD->isUnnamedBitField())
if (FD->isUnnamedBitfield())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated change pulled in?

return true;

QualType FT = FD->getType();
Expand Down
10 changes: 9 additions & 1 deletion clang/lib/CodeGen/Targets/AMDGPU.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,15 @@ void AMDGPUABIInfo::computeInfo(CGFunctionInfo &FI) const {

Address AMDGPUABIInfo::EmitVAArg(CodeGenFunction &CGF, Address VAListAddr,
QualType Ty) const {
llvm_unreachable("AMDGPU does not support varargs");
const bool IsIndirect = false;
const bool AllowHigherAlign = true;
// Would rather not naturally align values, e.g. passing double on 4 bytes
// Splitting {char, short} into two separate arguments makes that difficult
// TODO: See if forcing exactly four byte alignment on everything, struct or
// scalar, lowers correctly regardless of structs being split across arguments
return emitVoidPtrVAArg(CGF, VAListAddr, Ty, IsIndirect,
getContext().getTypeInfoInChars(Ty),
CharUnits::fromQuantity(1), AllowHigherAlign);
}

ABIArgInfo AMDGPUABIInfo::classifyReturnType(QualType RetTy) const {
Expand Down
8 changes: 7 additions & 1 deletion clang/lib/CodeGen/Targets/NVPTX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,13 @@ void NVPTXABIInfo::computeInfo(CGFunctionInfo &FI) const {

Address NVPTXABIInfo::EmitVAArg(CodeGenFunction &CGF, Address VAListAddr,
QualType Ty) const {
llvm_unreachable("NVPTX does not support varargs");
// TODO: Work out to what extent this correlates with ptx
// doubles get passed with 8 byte alignment and C promotes smaller integer
// types to int. Printf doesn't really do structs so hard to guess what
// the right thing is for that.
return emitVoidPtrVAArg(CGF, VAListAddr, Ty, false,
getContext().getTypeInfoInChars(Ty),
CharUnits::fromQuantity(4), true);
}

void NVPTXTargetCodeGenInfo::setTargetAttributes(
Expand Down
314 changes: 314 additions & 0 deletions clang/test/CodeGen/expand-variadic-call.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,314 @@
// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py

// REQUIRES: x86-registered-target
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -target-cpu x86-64-v4 -std=c23 -O1 -ffreestanding -mllvm --expand-variadics-override=disable -emit-llvm -o - %s | FileCheck %s

// This test sanity checks calling a variadic function with the expansion transform disabled.
// The IR test cases {arch}/expand-variadic-call-*.ll correspond to IR generated from this test case.

typedef __builtin_va_list va_list;
#define va_copy(dest, src) __builtin_va_copy(dest, src)
#define va_start(ap, ...) __builtin_va_start(ap, 0)
#define va_end(ap) __builtin_va_end(ap)
#define va_arg(ap, type) __builtin_va_arg(ap, type)

// 32 bit x86 alignment uses getTypeStackAlign for special cases
// Whitebox testing.
// Needs a type >= 16 which is either a simd or a struct containing a simd
// darwinvectorabi should force 4 bytes
// linux vectors with align 16/32/64 return that alignment


// Might want various copy/end style constructs in a separate test

void vararg(...);
void valist(va_list);

// CHECK-LABEL: @copy(
// CHECK-NEXT: entry:
// CHECK-NEXT: [[CP:%.*]] = alloca [1 x %struct.__va_list_tag], align 16
// CHECK-NEXT: call void @llvm.lifetime.start.p0(i64 24, ptr nonnull [[CP]]) #[[ATTR7:[0-9]+]]
// CHECK-NEXT: call void @llvm.va_copy.p0(ptr nonnull [[CP]], ptr [[VA:%.*]])
// CHECK-NEXT: call void @valist(ptr noundef nonnull [[CP]]) #[[ATTR8:[0-9]+]]
// CHECK-NEXT: call void @llvm.lifetime.end.p0(i64 24, ptr nonnull [[CP]]) #[[ATTR7]]
// CHECK-NEXT: ret void
//
void copy(va_list va)
{
va_list cp;
va_copy(cp, va);
valist(cp);
}

// CHECK-LABEL: @start_once(
// CHECK-NEXT: entry:
// CHECK-NEXT: [[S:%.*]] = alloca [1 x %struct.__va_list_tag], align 16
// CHECK-NEXT: call void @llvm.lifetime.start.p0(i64 24, ptr nonnull [[S]]) #[[ATTR7]]
// CHECK-NEXT: call void @llvm.va_start.p0(ptr nonnull [[S]])
// CHECK-NEXT: call void @valist(ptr noundef nonnull [[S]]) #[[ATTR8]]
// CHECK-NEXT: call void @llvm.va_end.p0(ptr [[S]])
// CHECK-NEXT: call void @llvm.lifetime.end.p0(i64 24, ptr nonnull [[S]]) #[[ATTR7]]
// CHECK-NEXT: ret void
//
void start_once(...)
{
va_list s;
va_start(s);
valist(s);
va_end(s);
}

// CHECK-LABEL: @start_twice(
// CHECK-NEXT: entry:
// CHECK-NEXT: [[S0:%.*]] = alloca [1 x %struct.__va_list_tag], align 16
// CHECK-NEXT: [[S1:%.*]] = alloca [1 x %struct.__va_list_tag], align 16
// CHECK-NEXT: call void @llvm.lifetime.start.p0(i64 24, ptr nonnull [[S0]]) #[[ATTR7]]
// CHECK-NEXT: call void @llvm.lifetime.start.p0(i64 24, ptr nonnull [[S1]]) #[[ATTR7]]
// CHECK-NEXT: call void @llvm.va_start.p0(ptr nonnull [[S0]])
// CHECK-NEXT: call void @valist(ptr noundef nonnull [[S0]]) #[[ATTR8]]
// CHECK-NEXT: call void @llvm.va_end.p0(ptr [[S0]])
// CHECK-NEXT: call void @llvm.va_start.p0(ptr nonnull [[S1]])
// CHECK-NEXT: call void @valist(ptr noundef nonnull [[S1]]) #[[ATTR8]]
// CHECK-NEXT: call void @llvm.va_end.p0(ptr [[S1]])
// CHECK-NEXT: call void @llvm.lifetime.end.p0(i64 24, ptr nonnull [[S1]]) #[[ATTR7]]
// CHECK-NEXT: call void @llvm.lifetime.end.p0(i64 24, ptr nonnull [[S0]]) #[[ATTR7]]
// CHECK-NEXT: ret void
//
void start_twice(...)
{
va_list s0,s1;
va_start(s0);
valist(s0);
va_end(s0);
va_start(s1);
valist(s1);
va_end(s1);
}

// vectors with alignment 16/32/64 are natively aligned on linux x86
// v32f32 would be a m1024 type, larger than x64 defines at time of writing
typedef int i32;
typedef float v4f32 __attribute__((__vector_size__(16), __aligned__(16)));
typedef float v8f32 __attribute__((__vector_size__(32), __aligned__(32)));
typedef float v16f32 __attribute__((__vector_size__(64), __aligned__(64)));
typedef float v32f32 __attribute__((__vector_size__(128), __aligned__(128)));


// Pass a single value to wrapped() via vararg(...)
// CHECK-LABEL: @single_i32(
// CHECK-NEXT: entry:
// CHECK-NEXT: tail call void (...) @vararg(i32 noundef [[X:%.*]]) #[[ATTR8]]
// CHECK-NEXT: ret void
//
void single_i32(i32 x)
{
vararg(x);
}


// CHECK-LABEL: @single_double(
// CHECK-NEXT: entry:
// CHECK-NEXT: tail call void (...) @vararg(double noundef [[X:%.*]]) #[[ATTR8]]
// CHECK-NEXT: ret void
//
void single_double(double x)
{
vararg(x);
}

// CHECK-LABEL: @single_v4f32(
// CHECK-NEXT: entry:
// CHECK-NEXT: tail call void (...) @vararg(<4 x float> noundef [[X:%.*]]) #[[ATTR8]]
// CHECK-NEXT: ret void
//
void single_v4f32(v4f32 x)
{
vararg(x);
}

// CHECK-LABEL: @single_v8f32(
// CHECK-NEXT: entry:
// CHECK-NEXT: tail call void (...) @vararg(<8 x float> noundef [[X:%.*]]) #[[ATTR8]]
// CHECK-NEXT: ret void
//
void single_v8f32(v8f32 x)
{
vararg(x);
}

// CHECK-LABEL: @single_v16f32(
// CHECK-NEXT: entry:
// CHECK-NEXT: tail call void (...) @vararg(<16 x float> noundef [[X:%.*]]) #[[ATTR8]]
// CHECK-NEXT: ret void
//
void single_v16f32(v16f32 x)
{
vararg(x);
}

// CHECK-LABEL: @single_v32f32(
// CHECK-NEXT: entry:
// CHECK-NEXT: [[INDIRECT_ARG_TEMP:%.*]] = alloca <32 x float>, align 128
// CHECK-NEXT: [[X:%.*]] = load <32 x float>, ptr [[TMP0:%.*]], align 128, !tbaa [[TBAA2:![0-9]+]]
// CHECK-NEXT: store <32 x float> [[X]], ptr [[INDIRECT_ARG_TEMP]], align 128, !tbaa [[TBAA2]]
// CHECK-NEXT: tail call void (...) @vararg(ptr noundef nonnull byval(<32 x float>) align 128 [[INDIRECT_ARG_TEMP]]) #[[ATTR8]]
// CHECK-NEXT: ret void
//
void single_v32f32(v32f32 x)
{
vararg(x);
}



// CHECK-LABEL: @i32_double(
// CHECK-NEXT: entry:
// CHECK-NEXT: tail call void (...) @vararg(i32 noundef [[X:%.*]], double noundef [[Y:%.*]]) #[[ATTR8]]
// CHECK-NEXT: ret void
//
void i32_double(i32 x, double y)
{
vararg(x, y);
}

// CHECK-LABEL: @double_i32(
// CHECK-NEXT: entry:
// CHECK-NEXT: tail call void (...) @vararg(double noundef [[X:%.*]], i32 noundef [[Y:%.*]]) #[[ATTR8]]
// CHECK-NEXT: ret void
//
void double_i32(double x, i32 y)
{
vararg(x, y);
}


// A struct used by libc variadic tests

typedef struct {
char c;
short s;
int i;
long l;
float f;
double d;
} libcS;

// CHECK-LABEL: @i32_libcS(
// CHECK-NEXT: entry:
// CHECK-NEXT: tail call void (...) @vararg(i32 noundef [[X:%.*]], ptr noundef nonnull byval([[STRUCT_LIBCS:%.*]]) align 8 [[Y:%.*]]) #[[ATTR8]]
// CHECK-NEXT: ret void
//
void i32_libcS(i32 x, libcS y)
{
vararg(x, y);
}

// CHECK-LABEL: @libcS_i32(
// CHECK-NEXT: entry:
// CHECK-NEXT: tail call void (...) @vararg(ptr noundef nonnull byval([[STRUCT_LIBCS:%.*]]) align 8 [[X:%.*]], i32 noundef [[Y:%.*]]) #[[ATTR8]]
// CHECK-NEXT: ret void
//
void libcS_i32(libcS x, i32 y)
{
vararg(x, y);
}


// CHECK-LABEL: @i32_v4f32(
// CHECK-NEXT: entry:
// CHECK-NEXT: tail call void (...) @vararg(i32 noundef [[X:%.*]], <4 x float> noundef [[Y:%.*]]) #[[ATTR8]]
// CHECK-NEXT: ret void
//
void i32_v4f32(i32 x, v4f32 y)
{
vararg(x, y);
}

// CHECK-LABEL: @v4f32_i32(
// CHECK-NEXT: entry:
// CHECK-NEXT: tail call void (...) @vararg(<4 x float> noundef [[X:%.*]], i32 noundef [[Y:%.*]]) #[[ATTR8]]
// CHECK-NEXT: ret void
//
void v4f32_i32(v4f32 x, i32 y)
{
vararg(x, y);
}

// CHECK-LABEL: @i32_v8f32(
// CHECK-NEXT: entry:
// CHECK-NEXT: tail call void (...) @vararg(i32 noundef [[X:%.*]], <8 x float> noundef [[Y:%.*]]) #[[ATTR8]]
// CHECK-NEXT: ret void
//
void i32_v8f32(i32 x, v8f32 y)
{
vararg(x, y);
}

// CHECK-LABEL: @v8f32_i32(
// CHECK-NEXT: entry:
// CHECK-NEXT: tail call void (...) @vararg(<8 x float> noundef [[X:%.*]], i32 noundef [[Y:%.*]]) #[[ATTR8]]
// CHECK-NEXT: ret void
//
void v8f32_i32(v8f32 x, i32 y)
{
vararg(x, y);
}

// CHECK-LABEL: @i32_v16f32(
// CHECK-NEXT: entry:
// CHECK-NEXT: tail call void (...) @vararg(i32 noundef [[X:%.*]], <16 x float> noundef [[Y:%.*]]) #[[ATTR8]]
// CHECK-NEXT: ret void
//
void i32_v16f32(i32 x, v16f32 y)
{
vararg(x, y);
}

// CHECK-LABEL: @v16f32_i32(
// CHECK-NEXT: entry:
// CHECK-NEXT: tail call void (...) @vararg(<16 x float> noundef [[X:%.*]], i32 noundef [[Y:%.*]]) #[[ATTR8]]
// CHECK-NEXT: ret void
//
void v16f32_i32(v16f32 x, i32 y)
{
vararg(x, y);
}

// CHECK-LABEL: @i32_v32f32(
// CHECK-NEXT: entry:
// CHECK-NEXT: [[INDIRECT_ARG_TEMP:%.*]] = alloca <32 x float>, align 128
// CHECK-NEXT: [[Y:%.*]] = load <32 x float>, ptr [[TMP0:%.*]], align 128, !tbaa [[TBAA2]]
// CHECK-NEXT: store <32 x float> [[Y]], ptr [[INDIRECT_ARG_TEMP]], align 128, !tbaa [[TBAA2]]
// CHECK-NEXT: tail call void (...) @vararg(i32 noundef [[X:%.*]], ptr noundef nonnull byval(<32 x float>) align 128 [[INDIRECT_ARG_TEMP]]) #[[ATTR8]]
// CHECK-NEXT: ret void
//
void i32_v32f32(i32 x, v32f32 y)
{
vararg(x, y);
}

// CHECK-LABEL: @v32f32_i32(
// CHECK-NEXT: entry:
// CHECK-NEXT: [[INDIRECT_ARG_TEMP:%.*]] = alloca <32 x float>, align 128
// CHECK-NEXT: [[X:%.*]] = load <32 x float>, ptr [[TMP0:%.*]], align 128, !tbaa [[TBAA2]]
// CHECK-NEXT: store <32 x float> [[X]], ptr [[INDIRECT_ARG_TEMP]], align 128, !tbaa [[TBAA2]]
// CHECK-NEXT: tail call void (...) @vararg(ptr noundef nonnull byval(<32 x float>) align 128 [[INDIRECT_ARG_TEMP]], i32 noundef [[Y:%.*]]) #[[ATTR8]]
// CHECK-NEXT: ret void
//
void v32f32_i32(v32f32 x, i32 y)
{
vararg(x, y);
}

#if __AMDGPU__ || __NVPTX__


void (*volatile vararg_ptr)(...) = &vararg;

void indirect_single_i32(i32 x)
{
vararg_ptr(x);
}


#endif
Loading