-
Notifications
You must be signed in to change notification settings - Fork 15.2k
[CIR] Upstream __builtin_va_start and __builtin_va_end #153819
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
Conversation
|
@llvm/pr-subscribers-clangir Author: Morris Hafner (mmha) ChangesFull diff: https://github.com/llvm/llvm-project/pull/153819.diff 8 Files Affected:
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
|
|
@llvm/pr-subscribers-clang Author: Morris Hafner (mmha) ChangesFull diff: https://github.com/llvm/llvm-project/pull/153819.diff 8 Files Affected:
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
|
| cir.va.start %p : !cir.ptr<!rec___va_list_tag> | ||
| ``` | ||
| }]; | ||
| let arguments = (ins CIR_PointerType:$arg_list); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bcardosolopes I assumecir.va.start drops the length info from the builtin because the LLVM intrinsic does, but I think it would be useful to retain it in CIR for static code analysis purposes. What's your opinion?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm down for retaining it!
andykaylor
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good. I have just a few nits.
| `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`, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typically? Are there cases where it doesn't?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I remember some issues with va_arg (where OG might not lower to the LLVM intrinsic - llvm/clangir#862), I'm not sure about va.start though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My mind was at "What if we don't lower to LLVM?". I removed the "typically".
The LLVM intrinsic has a few problems because it's not a complete solution and sometimes generates worse code which is why classic codegen only uses it as a default in its ABI lowering. See here or here.
In #153834 I added lowering to llvm.va_arg as a default in LowerToLLVM. Once we get proper ABI lowering in LoweringPrepare (or at a similar stage) upstream a target can replace cir.va_arg with something else. But for now this is a good stopgap to enable things like scalar varargs on most platforms.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sounds good to me
clang/lib/CIR/CodeGen/CIRGenExpr.cpp
Outdated
| return Address::invalid(); | ||
| } | ||
| case CK_ArrayToPointerDecay: | ||
| return emitArrayToPointerDecay(ce->getSubExpr()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add the baseinfo argument and insert a missing feature asserts here for tbaa?
|
|
||
| void varargs(int count, ...) { | ||
| __builtin_va_list args; | ||
| __builtin_va_start(args, 12345); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add test cases that use Builtin::BI__builtin_stdarg_start and Builtin::BI__va_start? (Assuming this one corresponds to Builtin::BI__builtin_va_start)
bcardosolopes
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, nothing to add on top of Andy's comments
| cir.va.start %p : !cir.ptr<!rec___va_list_tag> | ||
| ``` | ||
| }]; | ||
| let arguments = (ins CIR_PointerType:$arg_list); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm down for retaining it!
Retain count parameter add baseInfo arg and add tbaa missing feature Add test
|
I changed the op definition to keep the count in CIR. So I'm asking for a small re-review :) It leads to somewhat worse LLVM IR because of a dead load but I don't think there's much we can do here. |
bcardosolopes
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changes LGTM, thanks
This patch fixes a regression introduced by llvm#153819. The evaluation order of the arguments to `emitVAStart` is unspecified, but the test requires the arguments to be evaluated in left-to-right order.
This patch fixes a regression introduced by #153819. The evaluation order of the arguments to `emitVAStart` is unspecified, but the test requires the arguments to be evaluated in left-to-right order. It's a bit strange that the pre-merge checks did not catch this. The tests failed on my local machine, which runs Fedora 42 with gcc 15.2.1 .
Part of llvm#153286. Depends on llvm#153819. This patch adds support for __builtin_va_arg by adding the cir.va.arg operator. Unlike the incubator it doesn't depend on any target specific lowering (yet) but maps to llvm.va_arg.
Part of llvm/llvm-project#153286. Depends on llvm/llvm-project#153819. This patch adds support for __builtin_va_arg by adding the cir.va.arg operator. Unlike the incubator it doesn't depend on any target specific lowering (yet) but maps to llvm.va_arg.
Part of #153286