[clang] [CIR] Upstream __builtin_va_start and __builtin_va_end (PR #153819)
via cfe-commits
cfe-commits at lists.llvm.org
Fri Aug 15 08:19:52 PDT 2025
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang
Author: Morris Hafner (mmha)
<details>
<summary>Changes</summary>
---
Full diff: https://github.com/llvm/llvm-project/pull/153819.diff
8 Files Affected:
- (modified) clang/include/clang/CIR/Dialect/IR/CIROps.td (+77)
- (modified) clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp (+21)
- (modified) clang/lib/CIR/CodeGen/CIRGenExpr.cpp (+2-5)
- (modified) clang/lib/CIR/CodeGen/CIRGenFunction.cpp (+6)
- (modified) clang/lib/CIR/CodeGen/CIRGenFunction.h (+13)
- (modified) clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp (+22)
- (modified) clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h (+20)
- (added) clang/test/CIR/CodeGen/var_arg.c (+48)
``````````diff
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index a77e9199cdc96..86c8b59bf36f0 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -3415,4 +3415,81 @@ def CIR_FAbsOp : CIR_UnaryFPToFPBuiltinOp<"fabs", "FAbsOp"> {
}];
}
+//===----------------------------------------------------------------------===//
+// Variadic Operations
+//===----------------------------------------------------------------------===//
+
+def CIR_VAStartOp : CIR_Op<"va.start"> {
+ let summary = "Starts a variable argument list";
+ let description = [{
+ The cir.va.start operation models the C/C++ va_start macro by
+ initializing a variable argument list at the given va_list storage
+ location.
+
+ The operand must be a pointer to the target's `va_list` representation.
+ This operation has no results and produces its effect by mutating the
+ storage referenced by the pointer operand.
+
+ Each `cir.va.start` must be paired with a corresponding `cir.va.end`
+ on the same logical `va_list` object along all control-flow paths. After
+ `cir.va.end`, the `va_list` must not be accessed unless reinitialized
+ with another `cir.va.start`.
+
+ Lowering typically maps this to the LLVM intrinsic `llvm.va_start`,
+ passing the appropriately decayed pointer to the underlying `va_list`
+ storage.
+
+ Example:
+
+ ```mlir
+ // %args : !cir.ptr<!cir.array<!rec___va_list_tag x 1>>
+ %p = cir.cast(array_to_ptrdecay, %args
+ : !cir.ptr<!cir.array<!rec___va_list_tag x 1>>),
+ !cir.ptr<!rec___va_list_tag>
+ cir.va.start %p : !cir.ptr<!rec___va_list_tag>
+ ```
+ }];
+ let arguments = (ins CIR_PointerType:$arg_list);
+
+ let assemblyFormat = [{
+ $arg_list attr-dict `:` type(operands)
+ }];
+}
+
+def CIR_VAEndOp : CIR_Op<"va.end"> {
+ let summary = "Ends a variable argument list";
+ let description = [{
+ The `cir.va.end` operation models the C/C++ va_end macro by finalizing
+ and cleaning up a variable argument list previously initialized with
+ `cir.va.start`.
+
+ The operand must be a pointer to the target's `va_list` representation.
+ This operation has no results and produces its effect by mutating the
+ storage referenced by the pointer operand.
+
+ `cir.va.end` must only be called after a matching `cir.va.start` on the
+ same `va_list` along all control-flow paths. After `cir.va.end`, the
+ `va_list` is invalid and must not be accessed unless reinitialized.
+
+ Lowering typically maps this to the LLVM intrinsic `llvm.va_end`,
+ passing the appropriately decayed pointer to the underlying `va_list`
+ storage.
+
+ Example:
+ ```mlir
+ // %args : !cir.ptr<!cir.array<!rec___va_list_tag x 1>>
+ %p = cir.cast(array_to_ptrdecay, %args
+ : !cir.ptr<!cir.array<!rec___va_list_tag x 1>>),
+ !cir.ptr<!rec___va_list_tag>
+ cir.va.end %p : !cir.ptr<!rec___va_list_tag>
+ ```
+ }];
+
+ let arguments = (ins CIR_PointerType:$arg_list);
+
+ let assemblyFormat = [{
+ $arg_list attr-dict `:` type(operands)
+ }];
+}
+
#endif // CLANG_CIR_DIALECT_IR_CIROPS_TD
diff --git a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
index 36aea4c1d39ce..25ca10e801194 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
@@ -125,6 +125,18 @@ RValue CIRGenFunction::emitBuiltinExpr(const GlobalDecl &gd, unsigned builtinID,
default:
break;
+ // C stdarg builtins.
+ case Builtin::BI__builtin_stdarg_start:
+ case Builtin::BI__builtin_va_start:
+ case Builtin::BI__va_start:
+ case Builtin::BI__builtin_va_end: {
+ emitVAStartEnd(builtinID == Builtin::BI__va_start
+ ? emitScalarExpr(e->getArg(0))
+ : emitVAListRef(e->getArg(0)).getPointer(),
+ builtinID != Builtin::BI__builtin_va_end);
+ return {};
+ }
+
case Builtin::BIfabs:
case Builtin::BIfabsf:
case Builtin::BIfabsl:
@@ -361,3 +373,12 @@ mlir::Value CIRGenFunction::emitCheckedArgForAssume(const Expr *e) {
"emitCheckedArgForAssume: sanitizers are NYI");
return {};
}
+
+void CIRGenFunction::emitVAStartEnd(mlir::Value argValue, bool isStart) {
+ // LLVM codegen casts to *i8, no real gain on doing this for CIRGen this
+ // early, defer to LLVM lowering.
+ if (isStart)
+ cir::VAStartOp::create(builder, argValue.getLoc(), argValue);
+ else
+ cir::VAEndOp::create(builder, argValue.getLoc(), argValue);
+}
diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
index 8bcca6f5d1803..00f2a64281b8c 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
@@ -90,11 +90,8 @@ Address CIRGenFunction::emitPointerWithAlignment(const Expr *expr,
} break;
// Array-to-pointer decay. TODO(cir): BaseInfo and TBAAInfo.
- case CK_ArrayToPointerDecay: {
- cgm.errorNYI(expr->getSourceRange(),
- "emitPointerWithAlignment: array-to-pointer decay");
- return Address::invalid();
- }
+ case CK_ArrayToPointerDecay:
+ return emitArrayToPointerDecay(ce->getSubExpr());
case CK_UncheckedDerivedToBase:
case CK_DerivedToBase: {
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
index d6a0792292604..917afa8e78021 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
@@ -1080,4 +1080,10 @@ void CIRGenFunction::emitVariablyModifiedType(QualType type) {
} while (type->isVariablyModifiedType());
}
+Address CIRGenFunction::emitVAListRef(const Expr *e) {
+ if (getContext().getBuiltinVaListType()->isArrayType())
+ return emitPointerWithAlignment(e);
+ return emitLValue(e).getAddress();
+}
+
} // namespace clang::CIRGen
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index 9a887ec047f86..1c89c2c9d57d3 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -1411,6 +1411,19 @@ class CIRGenFunction : public CIRGenTypeCache {
const clang::Stmt *thenS,
const clang::Stmt *elseS);
+ /// Build a "reference" to a va_list; this is either the address or the value
+ /// of the expression, depending on how va_list is defined.
+ Address emitVAListRef(const Expr *e);
+
+ /// Emits a CIR variable-argument operation, either
+ /// \c cir.va.start or \c cir.va.end.
+ ///
+ /// \param argValue A reference to the \c va_list as emitted by either
+ /// \c emitVAListRef or \c emitMSVAListRef.
+ ///
+ /// \param isStart If \c true, emits \c cir.va.start, otherwise \c cir.va.end.
+ void emitVAStartEnd(mlir::Value argValue, bool isStart);
+
/// ----------------------
/// CIR build helpers
/// -----------------
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index 1ea296a6887ef..e51fbc9eadaf0 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -2336,6 +2336,8 @@ void ConvertCIRToLLVMPass::runOnOperation() {
CIRToLLVMTrapOpLowering,
CIRToLLVMUnaryOpLowering,
CIRToLLVMUnreachableOpLowering,
+ CIRToLLVMVAEndOpLowering,
+ CIRToLLVMVAStartOpLowering,
CIRToLLVMVecCmpOpLowering,
CIRToLLVMVecCreateOpLowering,
CIRToLLVMVecExtractOpLowering,
@@ -3035,6 +3037,26 @@ mlir::LogicalResult CIRToLLVMInlineAsmOpLowering::matchAndRewrite(
return mlir::success();
}
+mlir::LogicalResult CIRToLLVMVAStartOpLowering::matchAndRewrite(
+ cir::VAStartOp op, OpAdaptor adaptor,
+ mlir::ConversionPatternRewriter &rewriter) const {
+ auto opaquePtr = mlir::LLVM::LLVMPointerType::get(getContext());
+ auto vaList = mlir::LLVM::BitcastOp::create(rewriter, op.getLoc(), opaquePtr,
+ adaptor.getArgList());
+ rewriter.replaceOpWithNewOp<mlir::LLVM::VaStartOp>(op, vaList);
+ return mlir::success();
+}
+
+mlir::LogicalResult CIRToLLVMVAEndOpLowering::matchAndRewrite(
+ cir::VAEndOp op, OpAdaptor adaptor,
+ mlir::ConversionPatternRewriter &rewriter) const {
+ auto opaquePtr = mlir::LLVM::LLVMPointerType::get(getContext());
+ auto vaList = mlir::LLVM::BitcastOp::create(rewriter, op.getLoc(), opaquePtr,
+ adaptor.getArgList());
+ rewriter.replaceOpWithNewOp<mlir::LLVM::VaEndOp>(op, vaList);
+ return mlir::success();
+}
+
std::unique_ptr<mlir::Pass> createConvertCIRToLLVMPass() {
return std::make_unique<ConvertCIRToLLVMPass>();
}
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h
index e32bf2d1bae0c..72315ac127e1b 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h
@@ -684,6 +684,26 @@ class CIRToLLVMInlineAsmOpLowering
mlir::ConversionPatternRewriter &) const override;
};
+class CIRToLLVMVAStartOpLowering
+ : public mlir::OpConversionPattern<cir::VAStartOp> {
+public:
+ using mlir::OpConversionPattern<cir::VAStartOp>::OpConversionPattern;
+
+ mlir::LogicalResult
+ matchAndRewrite(cir::VAStartOp op, OpAdaptor,
+ mlir::ConversionPatternRewriter &) const override;
+};
+
+class CIRToLLVMVAEndOpLowering
+ : public mlir::OpConversionPattern<cir::VAEndOp> {
+public:
+ using mlir::OpConversionPattern<cir::VAEndOp>::OpConversionPattern;
+
+ mlir::LogicalResult
+ matchAndRewrite(cir::VAEndOp op, OpAdaptor,
+ mlir::ConversionPatternRewriter &) const override;
+};
+
} // namespace direct
} // namespace cir
diff --git a/clang/test/CIR/CodeGen/var_arg.c b/clang/test/CIR/CodeGen/var_arg.c
new file mode 100644
index 0000000000000..7cd10e1ca42d2
--- /dev/null
+++ b/clang/test/CIR/CodeGen/var_arg.c
@@ -0,0 +1,48 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-unused-value -fclangir -emit-cir %s -o %t.cir
+// RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-unused-value -fclangir -emit-llvm %s -o %t-cir.ll
+// RUN: FileCheck --input-file=%t-cir.ll %s -check-prefix=LLVM
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-unused-value -emit-llvm %s -o %t.ll
+// RUN: FileCheck --input-file=%t.ll %s -check-prefix=OGCG
+
+void varargs(int count, ...) {
+ __builtin_va_list args;
+ __builtin_va_start(args, 12345);
+ __builtin_va_end(args);
+}
+
+// CIR: !rec___va_list_tag = !cir.record<struct "__va_list_tag" {!u32i, !u32i, !cir.ptr<!void>, !cir.ptr<!void>}
+
+// CIR: cir.func dso_local @varargs(%[[COUNT_ARG:.+]]: !s32i {{.*}}, ...) {{.*}}
+// CIR: %[[COUNT:.+]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["count", init]
+// CIR: %[[ARGS:.+]] = cir.alloca !cir.array<!rec___va_list_tag x 1>, !cir.ptr<!cir.array<!rec___va_list_tag x 1>>, ["args"]
+// CIR: cir.store %[[COUNT_ARG]], %[[COUNT]] : !s32i, !cir.ptr<!s32i>
+// CIR: %[[ARGS_DECAY1:.+]] = cir.cast(array_to_ptrdecay, %[[ARGS]] : !cir.ptr<!cir.array<!rec___va_list_tag x 1>>), !cir.ptr<!rec___va_list_tag>
+// CIR: cir.va.start %[[ARGS_DECAY1]] : !cir.ptr<!rec___va_list_tag>
+// CIR: %[[ARGS_DECAY2:.+]] = cir.cast(array_to_ptrdecay, %[[ARGS]] : !cir.ptr<!cir.array<!rec___va_list_tag x 1>>), !cir.ptr<!rec___va_list_tag>
+// CIR: cir.va.end %[[ARGS_DECAY2]] : !cir.ptr<!rec___va_list_tag>
+// CIR: cir.return
+
+// LLVM: %struct.__va_list_tag = type { i32, i32, ptr, ptr }
+
+// LLVM: define dso_local void @varargs(i32 %[[ARG0:.+]], ...)
+// LLVM: %[[COUNT_ADDR:.+]] = alloca i32, i64 1
+// LLVM: %[[ARGS:.+]] = alloca [1 x %struct.__va_list_tag], i64 1
+// LLVM: store i32 %[[ARG0]], ptr %[[COUNT_ADDR]]
+// LLVM: %[[GEP1:.+]] = getelementptr %struct.__va_list_tag, ptr %[[ARGS]], i32 0
+// LLVM: call void @llvm.va_start.p0(ptr %[[GEP1]])
+// LLVM: %[[GEP2:.+]] = getelementptr %struct.__va_list_tag, ptr %[[ARGS]], i32 0
+// LLVM: call void @llvm.va_end.p0(ptr %[[GEP2]])
+// LLVM: ret void
+
+// OGCG: %struct.__va_list_tag = type { i32, i32, ptr, ptr }
+
+// OGCG: define dso_local void @varargs(i32 noundef %[[COUNT:.+]], ...)
+// OGCG: %[[COUNT_ADDR:.+]] = alloca i32
+// OGCG: %[[ARGS:.+]] = alloca [1 x %struct.__va_list_tag]
+// OGCG: store i32 %[[COUNT]], ptr %[[COUNT_ADDR]]
+// OGCG: %[[ARRDECAY1:.+]] = getelementptr inbounds [1 x %struct.__va_list_tag], ptr %[[ARGS]], i64 0, i64 0
+// OGCG: call void @llvm.va_start.p0(ptr %[[ARRDECAY1]])
+// OGCG: %[[ARRDECAY2:.+]] = getelementptr inbounds [1 x %struct.__va_list_tag], ptr %[[ARGS]], i64 0, i64 0
+// OGCG: call void @llvm.va_end.p0(ptr %[[ARRDECAY2]])
+// OGCG: ret void
``````````
</details>
https://github.com/llvm/llvm-project/pull/153819
More information about the cfe-commits
mailing list