Skip to content

Commit b44e47a

Browse files
authored
[CIR] Upstream __builtin_va_start and __builtin_va_end (#153819)
Part of #153286
1 parent eb7a1d9 commit b44e47a

File tree

8 files changed

+256
-7
lines changed

8 files changed

+256
-7
lines changed

clang/include/clang/CIR/Dialect/IR/CIROps.td

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3614,4 +3614,86 @@ def CIR_FAbsOp : CIR_UnaryFPToFPBuiltinOp<"fabs", "FAbsOp"> {
36143614
}];
36153615
}
36163616

3617+
//===----------------------------------------------------------------------===//
3618+
// Variadic Operations
3619+
//===----------------------------------------------------------------------===//
3620+
3621+
def CIR_VAStartOp : CIR_Op<"va_start"> {
3622+
let summary = "Starts a variable argument list";
3623+
let description = [{
3624+
The cir.va_start operation models the C/C++ va_start macro by
3625+
initializing a variable argument list at the given va_list storage
3626+
location.
3627+
3628+
The first operand must be a pointer to the target's `va_list`
3629+
representation. This operation has no results and produces its effect by
3630+
mutating the storage referenced by the pointer operand. The second operand
3631+
must be an integer value that contains the expected number of arguments in
3632+
that list.
3633+
3634+
Each `cir.va_start` must be paired with a corresponding `cir.va_end`
3635+
on the same logical `va_list` object along all control-flow paths. After
3636+
`cir.va_end`, the `va_list` must not be accessed unless reinitialized
3637+
with another `cir.va_start`.
3638+
3639+
Lowering maps this to the LLVM intrinsic `llvm.va_start`, passing the
3640+
appropriately decayed pointer to the underlying `va_list` storage.
3641+
3642+
Example:
3643+
3644+
```mlir
3645+
// %args : !cir.ptr<!cir.array<!rec___va_list_tag x 1>>
3646+
%p = cir.cast(array_to_ptrdecay, %args
3647+
: !cir.ptr<!cir.array<!rec___va_list_tag x 1>>),
3648+
!cir.ptr<!rec___va_list_tag>
3649+
%count = cir.load %0 : !cir.ptr<!s32i>, !s32i
3650+
cir.va_start %p %count : !cir.ptr<!rec___va_list_tag>, !s32i
3651+
```
3652+
}];
3653+
let arguments = (ins
3654+
CIR_PointerType:$arg_list,
3655+
CIR_AnyFundamentalIntType:$count
3656+
);
3657+
3658+
let assemblyFormat = [{
3659+
$arg_list $count attr-dict `:` type(operands)
3660+
}];
3661+
}
3662+
3663+
def CIR_VAEndOp : CIR_Op<"va_end"> {
3664+
let summary = "Ends a variable argument list";
3665+
let description = [{
3666+
The `cir.va_end` operation models the C/C++ va_end macro by finalizing
3667+
and cleaning up a variable argument list previously initialized with
3668+
`cir.va_start`.
3669+
3670+
The operand must be a pointer to the target's `va_list` representation.
3671+
This operation has no results and produces its effect by mutating the
3672+
storage referenced by the pointer operand.
3673+
3674+
`cir.va_end` must only be called after a matching `cir.va_start` on the
3675+
same `va_list` along all control-flow paths. After `cir.va_end`, the
3676+
`va_list` is invalid and must not be accessed unless reinitialized.
3677+
3678+
Lowering typically maps this to the LLVM intrinsic `llvm.va_end`,
3679+
passing the appropriately decayed pointer to the underlying `va_list`
3680+
storage.
3681+
3682+
Example:
3683+
```mlir
3684+
// %args : !cir.ptr<!cir.array<!rec___va_list_tag x 1>>
3685+
%p = cir.cast(array_to_ptrdecay, %args
3686+
: !cir.ptr<!cir.array<!rec___va_list_tag x 1>>),
3687+
!cir.ptr<!rec___va_list_tag>
3688+
cir.va_end %p : !cir.ptr<!rec___va_list_tag>
3689+
```
3690+
}];
3691+
3692+
let arguments = (ins CIR_PointerType:$arg_list);
3693+
3694+
let assemblyFormat = [{
3695+
$arg_list attr-dict `:` type(operands)
3696+
}];
3697+
}
3698+
36173699
#endif // CLANG_CIR_DIALECT_IR_CIROPS_TD

clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,21 @@ RValue CIRGenFunction::emitBuiltinExpr(const GlobalDecl &gd, unsigned builtinID,
125125
default:
126126
break;
127127

128+
// C stdarg builtins.
129+
case Builtin::BI__builtin_stdarg_start:
130+
case Builtin::BI__builtin_va_start:
131+
case Builtin::BI__va_start: {
132+
emitVAStart(builtinID == Builtin::BI__va_start
133+
? emitScalarExpr(e->getArg(0))
134+
: emitVAListRef(e->getArg(0)).getPointer(),
135+
emitScalarExpr(e->getArg(1)));
136+
return {};
137+
}
138+
139+
case Builtin::BI__builtin_va_end:
140+
emitVAEnd(emitVAListRef(e->getArg(0)).getPointer());
141+
return {};
142+
128143
case Builtin::BIfabs:
129144
case Builtin::BIfabsf:
130145
case Builtin::BIfabsl:
@@ -375,3 +390,13 @@ mlir::Value CIRGenFunction::emitCheckedArgForAssume(const Expr *e) {
375390
"emitCheckedArgForAssume: sanitizers are NYI");
376391
return {};
377392
}
393+
394+
void CIRGenFunction::emitVAStart(mlir::Value vaList, mlir::Value count) {
395+
// LLVM codegen casts to *i8, no real gain on doing this for CIRGen this
396+
// early, defer to LLVM lowering.
397+
cir::VAStartOp::create(builder, vaList.getLoc(), vaList, count);
398+
}
399+
400+
void CIRGenFunction::emitVAEnd(mlir::Value vaList) {
401+
cir::VAEndOp::create(builder, vaList.getLoc(), vaList);
402+
}

clang/lib/CIR/CodeGen/CIRGenExpr.cpp

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,8 @@ Address CIRGenFunction::emitPointerWithAlignment(const Expr *expr,
9090
} break;
9191

9292
// Array-to-pointer decay. TODO(cir): BaseInfo and TBAAInfo.
93-
case CK_ArrayToPointerDecay: {
94-
cgm.errorNYI(expr->getSourceRange(),
95-
"emitPointerWithAlignment: array-to-pointer decay");
96-
return Address::invalid();
97-
}
93+
case CK_ArrayToPointerDecay:
94+
return emitArrayToPointerDecay(ce->getSubExpr(), baseInfo);
9895

9996
case CK_UncheckedDerivedToBase:
10097
case CK_DerivedToBase: {
@@ -1626,7 +1623,9 @@ void CIRGenFunction::emitIgnoredExpr(const Expr *e) {
16261623
emitLValue(e);
16271624
}
16281625

1629-
Address CIRGenFunction::emitArrayToPointerDecay(const Expr *e) {
1626+
Address CIRGenFunction::emitArrayToPointerDecay(const Expr *e,
1627+
LValueBaseInfo *baseInfo) {
1628+
assert(!cir::MissingFeatures::opTBAA());
16301629
assert(e->getType()->isArrayType() &&
16311630
"Array to pointer decay must have array source type!");
16321631

clang/lib/CIR/CodeGen/CIRGenFunction.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1080,4 +1080,10 @@ void CIRGenFunction::emitVariablyModifiedType(QualType type) {
10801080
} while (type->isVariablyModifiedType());
10811081
}
10821082

1083+
Address CIRGenFunction::emitVAListRef(const Expr *e) {
1084+
if (getContext().getBuiltinVaListType()->isArrayType())
1085+
return emitPointerWithAlignment(e);
1086+
return emitLValue(e).getAddress();
1087+
}
1088+
10831089
} // namespace clang::CIRGen

clang/lib/CIR/CodeGen/CIRGenFunction.h

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -952,7 +952,8 @@ class CIRGenFunction : public CIRGenTypeCache {
952952
QualType &baseType, Address &addr);
953953
LValue emitArraySubscriptExpr(const clang::ArraySubscriptExpr *e);
954954

955-
Address emitArrayToPointerDecay(const Expr *array);
955+
Address emitArrayToPointerDecay(const Expr *e,
956+
LValueBaseInfo *baseInfo = nullptr);
956957

957958
mlir::LogicalResult emitAsmStmt(const clang::AsmStmt &s);
958959

@@ -1420,6 +1421,24 @@ class CIRGenFunction : public CIRGenTypeCache {
14201421
const clang::Stmt *thenS,
14211422
const clang::Stmt *elseS);
14221423

1424+
/// Build a "reference" to a va_list; this is either the address or the value
1425+
/// of the expression, depending on how va_list is defined.
1426+
Address emitVAListRef(const Expr *e);
1427+
1428+
/// Emits the start of a CIR variable-argument operation (`cir.va_start`)
1429+
///
1430+
/// \param vaList A reference to the \c va_list as emitted by either
1431+
/// \c emitVAListRef or \c emitMSVAListRef.
1432+
///
1433+
/// \param count The number of arguments in \c vaList
1434+
void emitVAStart(mlir::Value vaList, mlir::Value count);
1435+
1436+
/// Emits the end of a CIR variable-argument operation (`cir.va_start`)
1437+
///
1438+
/// \param vaList A reference to the \c va_list as emitted by either
1439+
/// \c emitVAListRef or \c emitMSVAListRef.
1440+
void emitVAEnd(mlir::Value vaList);
1441+
14231442
/// ----------------------
14241443
/// CIR build helpers
14251444
/// -----------------

clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2378,6 +2378,8 @@ void ConvertCIRToLLVMPass::runOnOperation() {
23782378
CIRToLLVMTrapOpLowering,
23792379
CIRToLLVMUnaryOpLowering,
23802380
CIRToLLVMUnreachableOpLowering,
2381+
CIRToLLVMVAEndOpLowering,
2382+
CIRToLLVMVAStartOpLowering,
23812383
CIRToLLVMVecCmpOpLowering,
23822384
CIRToLLVMVecCreateOpLowering,
23832385
CIRToLLVMVecExtractOpLowering,
@@ -3104,6 +3106,26 @@ mlir::LogicalResult CIRToLLVMInlineAsmOpLowering::matchAndRewrite(
31043106
return mlir::success();
31053107
}
31063108

3109+
mlir::LogicalResult CIRToLLVMVAStartOpLowering::matchAndRewrite(
3110+
cir::VAStartOp op, OpAdaptor adaptor,
3111+
mlir::ConversionPatternRewriter &rewriter) const {
3112+
auto opaquePtr = mlir::LLVM::LLVMPointerType::get(getContext());
3113+
auto vaList = mlir::LLVM::BitcastOp::create(rewriter, op.getLoc(), opaquePtr,
3114+
adaptor.getArgList());
3115+
rewriter.replaceOpWithNewOp<mlir::LLVM::VaStartOp>(op, vaList);
3116+
return mlir::success();
3117+
}
3118+
3119+
mlir::LogicalResult CIRToLLVMVAEndOpLowering::matchAndRewrite(
3120+
cir::VAEndOp op, OpAdaptor adaptor,
3121+
mlir::ConversionPatternRewriter &rewriter) const {
3122+
auto opaquePtr = mlir::LLVM::LLVMPointerType::get(getContext());
3123+
auto vaList = mlir::LLVM::BitcastOp::create(rewriter, op.getLoc(), opaquePtr,
3124+
adaptor.getArgList());
3125+
rewriter.replaceOpWithNewOp<mlir::LLVM::VaEndOp>(op, vaList);
3126+
return mlir::success();
3127+
}
3128+
31073129
std::unique_ptr<mlir::Pass> createConvertCIRToLLVMPass() {
31083130
return std::make_unique<ConvertCIRToLLVMPass>();
31093131
}

clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -725,6 +725,26 @@ class CIRToLLVMInlineAsmOpLowering
725725
mlir::ConversionPatternRewriter &) const override;
726726
};
727727

728+
class CIRToLLVMVAStartOpLowering
729+
: public mlir::OpConversionPattern<cir::VAStartOp> {
730+
public:
731+
using mlir::OpConversionPattern<cir::VAStartOp>::OpConversionPattern;
732+
733+
mlir::LogicalResult
734+
matchAndRewrite(cir::VAStartOp op, OpAdaptor,
735+
mlir::ConversionPatternRewriter &) const override;
736+
};
737+
738+
class CIRToLLVMVAEndOpLowering
739+
: public mlir::OpConversionPattern<cir::VAEndOp> {
740+
public:
741+
using mlir::OpConversionPattern<cir::VAEndOp>::OpConversionPattern;
742+
743+
mlir::LogicalResult
744+
matchAndRewrite(cir::VAEndOp op, OpAdaptor,
745+
mlir::ConversionPatternRewriter &) const override;
746+
};
747+
728748
} // namespace direct
729749
} // namespace cir
730750

clang/test/CIR/CodeGen/var_arg.c

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-unused-value -fclangir -emit-cir %s -o %t.cir
2+
// RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR
3+
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-unused-value -fclangir -emit-llvm %s -o %t-cir.ll
4+
// RUN: FileCheck --input-file=%t-cir.ll %s -check-prefix=LLVM
5+
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-unused-value -emit-llvm %s -o %t.ll
6+
// RUN: FileCheck --input-file=%t.ll %s -check-prefix=OGCG
7+
8+
// CIR: !rec___va_list_tag = !cir.record<struct "__va_list_tag" {!u32i, !u32i, !cir.ptr<!void>, !cir.ptr<!void>}
9+
// LLVM: %struct.__va_list_tag = type { i32, i32, ptr, ptr }
10+
// OGCG: %struct.__va_list_tag = type { i32, i32, ptr, ptr }
11+
12+
void varargs(int count, ...) {
13+
__builtin_va_list args;
14+
__builtin_va_start(args, 12345);
15+
__builtin_va_end(args);
16+
}
17+
18+
// CIR: cir.func dso_local @varargs(%[[COUNT:.+]]: !s32i{{.*}}, ...)
19+
// CIR: %[[COUNT_ADDR:.+]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["count", init]
20+
// CIR: %[[ARGS:.+]] = cir.alloca !cir.array<!rec___va_list_tag x 1>, !cir.ptr<!cir.array<!rec___va_list_tag x 1>>, ["args"]
21+
// CIR: cir.store %[[COUNT]], %[[COUNT_ADDR]] : !s32i, !cir.ptr<!s32i>
22+
// CIR: %[[APTR:.+]] = cir.cast(array_to_ptrdecay, %[[ARGS]] : !cir.ptr<!cir.array<!rec___va_list_tag x 1>>), !cir.ptr<!rec___va_list_tag>
23+
// CIR: %[[C12345:.+]] = cir.const #cir.int<12345> : !s32i
24+
// CIR: cir.va_start %[[APTR]] %[[C12345]] : !cir.ptr<!rec___va_list_tag>, !s32i
25+
// CIR: %[[APTR2:.+]] = cir.cast(array_to_ptrdecay, %[[ARGS]] : !cir.ptr<!cir.array<!rec___va_list_tag x 1>>), !cir.ptr<!rec___va_list_tag>
26+
// CIR: cir.va_end %[[APTR2]] : !cir.ptr<!rec___va_list_tag>
27+
// CIR: cir.return
28+
29+
// LLVM: define dso_local void @varargs(
30+
// LLVM: %[[ARGS:.+]] = alloca [1 x %struct.__va_list_tag], i64 1, align 16
31+
// LLVM: %[[ARGS_PTR:.+]] = getelementptr %struct.__va_list_tag, ptr %[[ARGS]], i32 0
32+
// LLVM: call void @llvm.va_start.p0(ptr %[[ARGS_PTR]])
33+
// LLVM: %[[ARGS_PTR2:.+]] = getelementptr %struct.__va_list_tag, ptr %[[ARGS]], i32 0
34+
// LLVM: call void @llvm.va_end.p0(ptr %[[ARGS_PTR2]])
35+
// LLVM: ret void
36+
37+
// OGCG: define dso_local void @varargs(
38+
// OGCG: %[[ARGS:.+]] = alloca [1 x %struct.__va_list_tag], align 16
39+
// OGCG: %[[ARGS_PTR:.+]] = getelementptr inbounds [1 x %struct.__va_list_tag], ptr %[[ARGS]], i64 0, i64 0
40+
// OGCG: call void @llvm.va_start.p0(ptr %[[ARGS_PTR]])
41+
// OGCG: %[[ARGS_PTR2:.+]] = getelementptr inbounds [1 x %struct.__va_list_tag], ptr %[[ARGS]], i64 0, i64 0
42+
// OGCG: call void @llvm.va_end.p0(ptr %[[ARGS_PTR2]])
43+
// OGCG: ret void
44+
45+
void stdarg_start(int count, ...) {
46+
__builtin_va_list args;
47+
__builtin_stdarg_start(args, 12345);
48+
__builtin_va_end(args);
49+
}
50+
51+
// CIR: cir.func dso_local @stdarg_start(%[[COUNT2:.+]]: !s32i{{.*}}, ...)
52+
// CIR: %[[COUNT2_ADDR:.+]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["count", init]
53+
// CIR: %[[ARGS2:.+]] = cir.alloca !cir.array<!rec___va_list_tag x 1>, !cir.ptr<!cir.array<!rec___va_list_tag x 1>>, ["args"]
54+
// CIR: cir.store %[[COUNT2]], %[[COUNT2_ADDR]] : !s32i, !cir.ptr<!s32i>
55+
// CIR: %[[APTR3:.+]] = cir.cast(array_to_ptrdecay, %[[ARGS2]] : !cir.ptr<!cir.array<!rec___va_list_tag x 1>>), !cir.ptr<!rec___va_list_tag>
56+
// CIR: %[[C12345_2:.+]] = cir.const #cir.int<12345> : !s32i
57+
// CIR: cir.va_start %[[APTR3]] %[[C12345_2]] : !cir.ptr<!rec___va_list_tag>, !s32i
58+
// CIR: %[[APTR4:.+]] = cir.cast(array_to_ptrdecay, %[[ARGS2]] : !cir.ptr<!cir.array<!rec___va_list_tag x 1>>), !cir.ptr<!rec___va_list_tag>
59+
// CIR: cir.va_end %[[APTR4]] : !cir.ptr<!rec___va_list_tag>
60+
// CIR: cir.return
61+
62+
// LLVM: define dso_local void @stdarg_start(
63+
// LLVM: %[[ARGS:.+]] = alloca [1 x %struct.__va_list_tag], i64 1, align 16
64+
// LLVM: %[[ARGS_PTR:.+]] = getelementptr %struct.__va_list_tag, ptr %[[ARGS]], i32 0
65+
// LLVM: call void @llvm.va_start.p0(ptr %[[ARGS_PTR]])
66+
// LLVM: %[[ARGS_PTR2:.+]] = getelementptr %struct.__va_list_tag, ptr %[[ARGS]], i32 0
67+
// LLVM: call void @llvm.va_end.p0(ptr %[[ARGS_PTR2]])
68+
// LLVM: ret void
69+
70+
// OGCG: define dso_local void @stdarg_start(
71+
// OGCG: %[[ARGS:.+]] = alloca [1 x %struct.__va_list_tag], align 16
72+
// OGCG: %[[ARGS_PTR:.+]] = getelementptr inbounds [1 x %struct.__va_list_tag], ptr %[[ARGS]], i64 0, i64 0
73+
// OGCG: call void @llvm.va_start.p0(ptr %[[ARGS_PTR]])
74+
// OGCG: %[[ARGS_PTR2:.+]] = getelementptr inbounds [1 x %struct.__va_list_tag], ptr %[[ARGS]], i64 0, i64 0
75+
// OGCG: call void @llvm.va_end.p0(ptr %[[ARGS_PTR2]])
76+
// OGCG: ret void

0 commit comments

Comments
 (0)