Skip to content
Merged
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
82 changes: 82 additions & 0 deletions clang/include/clang/CIR/Dialect/IR/CIROps.td
Original file line number Diff line number Diff line change
Expand Up @@ -3415,4 +3415,86 @@ 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 first 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. The second operand
must be an integer value that contains the expected number of arguments in
that list.

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 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>
%count = cir.load %0 : !cir.ptr<!s32i>, !s32i
cir.va_start %p %count : !cir.ptr<!rec___va_list_tag>, !s32i
```
}];
let arguments = (ins
CIR_PointerType:$arg_list,
CIR_AnyFundamentalIntType:$count
);

let assemblyFormat = [{
$arg_list $count 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
25 changes: 25 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,21 @@ 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: {
emitVAStart(builtinID == Builtin::BI__va_start
? emitScalarExpr(e->getArg(0))
: emitVAListRef(e->getArg(0)).getPointer(),
emitScalarExpr(e->getArg(1)));
return {};
}

case Builtin::BI__builtin_va_end:
emitVAEnd(emitVAListRef(e->getArg(0)).getPointer());
return {};

case Builtin::BIfabs:
case Builtin::BIfabsf:
case Builtin::BIfabsl:
Expand Down Expand Up @@ -361,3 +376,13 @@ mlir::Value CIRGenFunction::emitCheckedArgForAssume(const Expr *e) {
"emitCheckedArgForAssume: sanitizers are NYI");
return {};
}

void CIRGenFunction::emitVAStart(mlir::Value vaList, mlir::Value count) {
// LLVM codegen casts to *i8, no real gain on doing this for CIRGen this
// early, defer to LLVM lowering.
cir::VAStartOp::create(builder, vaList.getLoc(), vaList, count);
}

void CIRGenFunction::emitVAEnd(mlir::Value vaList) {
cir::VAEndOp::create(builder, vaList.getLoc(), vaList);
}
11 changes: 5 additions & 6 deletions clang/lib/CIR/CodeGen/CIRGenExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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(), baseInfo);

case CK_UncheckedDerivedToBase:
case CK_DerivedToBase: {
Expand Down Expand Up @@ -1626,7 +1623,9 @@ void CIRGenFunction::emitIgnoredExpr(const Expr *e) {
emitLValue(e);
}

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

Expand Down
6 changes: 6 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
21 changes: 20 additions & 1 deletion clang/lib/CIR/CodeGen/CIRGenFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -947,7 +947,8 @@ class CIRGenFunction : public CIRGenTypeCache {
QualType &baseType, Address &addr);
LValue emitArraySubscriptExpr(const clang::ArraySubscriptExpr *e);

Address emitArrayToPointerDecay(const Expr *array);
Address emitArrayToPointerDecay(const Expr *e,
LValueBaseInfo *baseInfo = nullptr);

RValue emitAtomicExpr(AtomicExpr *e);
void emitAtomicInit(Expr *init, LValue dest);
Expand Down Expand Up @@ -1411,6 +1412,24 @@ 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 the start of a CIR variable-argument operation (`cir.va_start`)
///
/// \param vaList A reference to the \c va_list as emitted by either
/// \c emitVAListRef or \c emitMSVAListRef.
///
/// \param count The number of arguments in \c vaList
void emitVAStart(mlir::Value vaList, mlir::Value count);

/// Emits the end of a CIR variable-argument operation (`cir.va_start`)
///
/// \param vaList A reference to the \c va_list as emitted by either
/// \c emitVAListRef or \c emitMSVAListRef.
void emitVAEnd(mlir::Value vaList);

/// ----------------------
/// CIR build helpers
/// -----------------
Expand Down
22 changes: 22 additions & 0 deletions clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2336,6 +2336,8 @@ void ConvertCIRToLLVMPass::runOnOperation() {
CIRToLLVMTrapOpLowering,
CIRToLLVMUnaryOpLowering,
CIRToLLVMUnreachableOpLowering,
CIRToLLVMVAEndOpLowering,
CIRToLLVMVAStartOpLowering,
CIRToLLVMVecCmpOpLowering,
CIRToLLVMVecCreateOpLowering,
CIRToLLVMVecExtractOpLowering,
Expand Down Expand Up @@ -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>();
}
Expand Down
20 changes: 20 additions & 0 deletions clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
76 changes: 76 additions & 0 deletions clang/test/CIR/CodeGen/var_arg.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// 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

// CIR: !rec___va_list_tag = !cir.record<struct "__va_list_tag" {!u32i, !u32i, !cir.ptr<!void>, !cir.ptr<!void>}
// LLVM: %struct.__va_list_tag = type { i32, i32, ptr, ptr }
// OGCG: %struct.__va_list_tag = type { i32, i32, ptr, ptr }

void varargs(int count, ...) {
__builtin_va_list args;
__builtin_va_start(args, 12345);
Copy link
Contributor

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)

__builtin_va_end(args);
}

// CIR: cir.func dso_local @varargs(%[[COUNT:.+]]: !s32i{{.*}}, ...)
// CIR: %[[COUNT_ADDR:.+]] = 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]], %[[COUNT_ADDR]] : !s32i, !cir.ptr<!s32i>
// CIR: %[[APTR:.+]] = cir.cast(array_to_ptrdecay, %[[ARGS]] : !cir.ptr<!cir.array<!rec___va_list_tag x 1>>), !cir.ptr<!rec___va_list_tag>
// CIR: %[[C12345:.+]] = cir.const #cir.int<12345> : !s32i
// CIR: cir.va_start %[[APTR]] %[[C12345]] : !cir.ptr<!rec___va_list_tag>, !s32i
// CIR: %[[APTR2:.+]] = 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 %[[APTR2]] : !cir.ptr<!rec___va_list_tag>
// CIR: cir.return

// LLVM: define dso_local void @varargs(
// LLVM: %[[ARGS:.+]] = alloca [1 x %struct.__va_list_tag], i64 1, align 16
// LLVM: %[[ARGS_PTR:.+]] = getelementptr %struct.__va_list_tag, ptr %[[ARGS]], i32 0
// LLVM: call void @llvm.va_start.p0(ptr %[[ARGS_PTR]])
// LLVM: %[[ARGS_PTR2:.+]] = getelementptr %struct.__va_list_tag, ptr %[[ARGS]], i32 0
// LLVM: call void @llvm.va_end.p0(ptr %[[ARGS_PTR2]])
// LLVM: ret void

// OGCG: define dso_local void @varargs(
// OGCG: %[[ARGS:.+]] = alloca [1 x %struct.__va_list_tag], align 16
// OGCG: %[[ARGS_PTR:.+]] = getelementptr inbounds [1 x %struct.__va_list_tag], ptr %[[ARGS]], i64 0, i64 0
// OGCG: call void @llvm.va_start.p0(ptr %[[ARGS_PTR]])
// OGCG: %[[ARGS_PTR2:.+]] = getelementptr inbounds [1 x %struct.__va_list_tag], ptr %[[ARGS]], i64 0, i64 0
// OGCG: call void @llvm.va_end.p0(ptr %[[ARGS_PTR2]])
// OGCG: ret void

void stdarg_start(int count, ...) {
__builtin_va_list args;
__builtin_stdarg_start(args, 12345);
__builtin_va_end(args);
}

// CIR: cir.func dso_local @stdarg_start(%[[COUNT2:.+]]: !s32i{{.*}}, ...)
// CIR: %[[COUNT2_ADDR:.+]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["count", init]
// CIR: %[[ARGS2:.+]] = cir.alloca !cir.array<!rec___va_list_tag x 1>, !cir.ptr<!cir.array<!rec___va_list_tag x 1>>, ["args"]
// CIR: cir.store %[[COUNT2]], %[[COUNT2_ADDR]] : !s32i, !cir.ptr<!s32i>
// CIR: %[[APTR3:.+]] = cir.cast(array_to_ptrdecay, %[[ARGS2]] : !cir.ptr<!cir.array<!rec___va_list_tag x 1>>), !cir.ptr<!rec___va_list_tag>
// CIR: %[[C12345_2:.+]] = cir.const #cir.int<12345> : !s32i
// CIR: cir.va_start %[[APTR3]] %[[C12345_2]] : !cir.ptr<!rec___va_list_tag>, !s32i
// CIR: %[[APTR4:.+]] = cir.cast(array_to_ptrdecay, %[[ARGS2]] : !cir.ptr<!cir.array<!rec___va_list_tag x 1>>), !cir.ptr<!rec___va_list_tag>
// CIR: cir.va_end %[[APTR4]] : !cir.ptr<!rec___va_list_tag>
// CIR: cir.return

// LLVM: define dso_local void @stdarg_start(
// LLVM: %[[ARGS:.+]] = alloca [1 x %struct.__va_list_tag], i64 1, align 16
// LLVM: %[[ARGS_PTR:.+]] = getelementptr %struct.__va_list_tag, ptr %[[ARGS]], i32 0
// LLVM: call void @llvm.va_start.p0(ptr %[[ARGS_PTR]])
// LLVM: %[[ARGS_PTR2:.+]] = getelementptr %struct.__va_list_tag, ptr %[[ARGS]], i32 0
// LLVM: call void @llvm.va_end.p0(ptr %[[ARGS_PTR2]])
// LLVM: ret void

// OGCG: define dso_local void @stdarg_start(
// OGCG: %[[ARGS:.+]] = alloca [1 x %struct.__va_list_tag], align 16
// OGCG: %[[ARGS_PTR:.+]] = getelementptr inbounds [1 x %struct.__va_list_tag], ptr %[[ARGS]], i64 0, i64 0
// OGCG: call void @llvm.va_start.p0(ptr %[[ARGS_PTR]])
// OGCG: %[[ARGS_PTR2:.+]] = getelementptr inbounds [1 x %struct.__va_list_tag], ptr %[[ARGS]], i64 0, i64 0
// OGCG: call void @llvm.va_end.p0(ptr %[[ARGS_PTR2]])
// OGCG: ret void