[clang] [CIR] Add pass_object_size hidden parameter support (PR #191482)
via cfe-commits
cfe-commits at lists.llvm.org
Thu Apr 16 13:57:00 PDT 2026
https://github.com/adams381 updated https://github.com/llvm/llvm-project/pull/191482
>From 0b71c655d42596da25b71c9aed604c870e406524 Mon Sep 17 00:00:00 2001
From: Adam Smith <adams at nvidia.com>
Date: Thu, 16 Apr 2026 13:54:08 -0700
Subject: [PATCH] [CIR] Add pass_object_size hidden parameter support
Implement pass_object_size attribute handling: append hidden
i64 parameters for each pass_object_size-annotated param in
function type computation, emit __builtin_object_size or
constant-fold at call sites, and load the implicit arg inside
emitBuiltinObjectSize when the BOS types are compatible.
Use count_if for extra slot counting, structured bindings for
map lookups, and getASTContext().getCanonicalSizeType() per
reviewer feedback.
Made-with: Cursor
---
clang/include/clang/CIR/MissingFeatures.h | 1 -
clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp | 28 +++++++++-
clang/lib/CIR/CodeGen/CIRGenCall.cpp | 42 +++++++++++---
clang/lib/CIR/CodeGen/CIRGenFunction.cpp | 11 +++-
clang/lib/CIR/CodeGen/CIRGenFunction.h | 5 ++
clang/lib/CIR/CodeGen/CIRGenFunctionInfo.h | 6 +-
clang/test/CIR/CodeGen/pass-object-size.c | 65 ++++++++++++++++++++++
7 files changed, 144 insertions(+), 14 deletions(-)
create mode 100644 clang/test/CIR/CodeGen/pass-object-size.c
diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h
index 9fd9397e85e63..521e921b7805c 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -98,7 +98,6 @@ struct MissingFeatures {
static bool opCallABIIndirectArg() { return false; }
static bool opCallWidenArg() { return false; }
static bool opCallBitcastArg() { return false; }
- static bool opCallImplicitObjectSizeArgs() { return false; }
static bool opCallReturn() { return false; }
static bool opCallArgEvaluationOrder() { return false; }
static bool opCallCallConv() { return false; }
diff --git a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
index 1a6aeef73fb79..15765f2c9cc17 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
@@ -2585,7 +2585,33 @@ mlir::Value CIRGenFunction::emitBuiltinObjectSize(const Expr *e, unsigned type,
cir::IntType resType,
mlir::Value emittedE,
bool isDynamic) {
- assert(!cir::MissingFeatures::opCallImplicitObjectSizeArgs());
+ // If this is a pass_object_size parameter, load the implicit size arg.
+ // BOS type compatibility: type 0 (max total) is a safe overestimate for
+ // type 1 (max remaining), and type 3 (min remaining) is a safe
+ // underestimate for type 2 (min total).
+ if (auto *d = dyn_cast<DeclRefExpr>(e->IgnoreParenImpCasts())) {
+ auto *param = dyn_cast<ParmVarDecl>(d->getDecl());
+ auto *ps = d->getDecl()->getAttr<PassObjectSizeAttr>();
+ if (param && ps) {
+ int from = ps->getType();
+ bool compatible =
+ from == static_cast<int>(type) ||
+ (from == 0 && type == 1) || (from == 3 && type == 2);
+ if (compatible) {
+ auto sizeIter = sizeArguments.find(param);
+ assert(sizeIter != sizeArguments.end());
+ auto [sizeParam, sizeDecl] = *sizeIter;
+
+ auto declIter = localDeclMap.find(sizeDecl);
+ assert(declIter != localDeclMap.end());
+ auto [decl, addr] = *declIter;
+
+ return emitLoadOfScalar(addr, /*volatile=*/false,
+ getContext().getSizeType(), e->getBeginLoc(),
+ LValueBaseInfo(AlignmentSource::Decl));
+ }
+ }
+ }
// LLVM can't handle type=3 appropriately, and __builtin_object_size shouldn't
// evaluate e for side-effects. In either case, just like original LLVM
diff --git a/clang/lib/CIR/CodeGen/CIRGenCall.cpp b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
index 19bb30aa1b656..0fa5ce9c37aa3 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCall.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
@@ -727,20 +727,29 @@ static CanQual<FunctionProtoType> getFormalType(const CXXMethodDecl *md) {
.getAs<FunctionProtoType>();
}
-/// Adds the formal parameters in FPT to the given prefix. If any parameter in
-/// FPT has pass_object_size_attrs, then we'll add parameters for those, too.
+/// Adds the formal parameters in FPT to the given prefix. If any parameter in
+/// FPT has pass_object_size attrs, then we'll add parameters for those, too.
/// TODO(cir): this should be shared with LLVM codegen
static void appendParameterTypes(const CIRGenTypes &cgt,
SmallVectorImpl<CanQualType> &prefix,
CanQual<FunctionProtoType> fpt) {
- assert(!cir::MissingFeatures::opCallExtParameterInfo());
// Fast path: don't touch param info if we don't need to.
if (!fpt->hasExtParameterInfos()) {
prefix.append(fpt->param_type_begin(), fpt->param_type_end());
return;
}
- cgt.getCGModule().errorNYI("appendParameterTypes: hasExtParameterInfos");
+ // In the vast majority of cases, we'll have precisely fpt->getNumParams()
+ // parameters; the only thing that can change this is the presence of
+ // pass_object_size. So, we preallocate for the common case.
+ prefix.reserve(prefix.size() + fpt->getNumParams());
+ ArrayRef<FunctionProtoType::ExtParameterInfo> extInfos =
+ fpt->getExtParameterInfos();
+ for (unsigned i = 0, e = fpt->getNumParams(); i != e; ++i) {
+ prefix.push_back(fpt->getParamType(i));
+ if (extInfos[i].hasPassObjectSize())
+ prefix.push_back(cgt.getASTContext().getCanonicalSizeType());
+ }
}
const CIRGenFunctionInfo &
@@ -875,10 +884,16 @@ arrangeFreeFunctionLikeCall(CIRGenTypes &cgt, CIRGenModule &cgm,
RequiredArgs required = RequiredArgs::All;
if (const auto *proto = dyn_cast<FunctionProtoType>(fnType)) {
- if (proto->isVariadic())
- required = RequiredArgs::getFromProtoWithExtraSlots(proto, 0);
+ unsigned numExtraSlots = 0;
if (proto->hasExtParameterInfos())
- cgm.errorNYI("call to functions with extra parameter info");
+ numExtraSlots += llvm::count_if(
+ proto->getExtParameterInfos(),
+ [](const FunctionProtoType::ExtParameterInfo &info) {
+ return info.hasPassObjectSize();
+ });
+ if (proto->isVariadic())
+ required =
+ RequiredArgs::getFromProtoWithExtraSlots(proto, numExtraSlots);
} else if (cgm.getTargetCIRGenInfo().isNoProtoCallVariadic(
cast<FunctionNoProtoType>(fnType)))
cgm.errorNYI("call to function without a prototype");
@@ -1441,8 +1456,17 @@ void CIRGenFunction::emitCallArgs(
if (!ps)
return;
- assert(!cir::MissingFeatures::opCallImplicitObjectSizeArgs());
- cgm.errorNYI("emit implicit object size for call arg");
+ QualType sizeTy = getContext().getSizeType();
+ cir::IntType cirSizeTy = cast<cir::IntType>(cgm.sizeTy);
+ assert(emittedArg.getValue() && "We emitted nothing for the arg?");
+ mlir::Value v = evaluateOrEmitBuiltinObjectSize(
+ arg, ps->getType(), cirSizeTy, emittedArg.getValue(), ps->isDynamic());
+ args.add(RValue::get(v), sizeTy);
+ // When emitting right-to-left, the size arg was appended after the
+ // pointer arg; swap them so the size follows the pointer in the final
+ // argument list after the outer reverse.
+ if (!leftToRight)
+ std::swap(args.back(), *(&args.back() - 1));
};
// Evaluate each argument in the appropriate order.
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
index 329c8b05a5f77..7d125ddfc4d72 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
@@ -1004,8 +1004,15 @@ clang::QualType CIRGenFunction::buildFunctionArgList(clang::GlobalDecl gd,
if (passedParams) {
for (auto *param : fd->parameters()) {
args.push_back(param);
- if (param->hasAttr<PassObjectSizeAttr>())
- cgm.errorNYI(param->getSourceRange(), "pass-object-size attribute");
+ if (!param->hasAttr<PassObjectSizeAttr>())
+ continue;
+
+ auto *implicit = ImplicitParamDecl::Create(
+ getContext(), param->getDeclContext(), param->getLocation(),
+ /*Id=*/nullptr, getContext().getSizeType(),
+ ImplicitParamKind::Other);
+ sizeArguments[param] = implicit;
+ args.push_back(implicit);
}
}
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index 7f91cba115f04..438e06c3646a3 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -156,6 +156,11 @@ class CIRGenFunction : public CIRGenTypeCache {
GlobalDecl curSEHParent;
+ /// If a ParmVarDecl had the pass_object_size attribute, this will contain a
+ /// mapping from said ParmVarDecl to its implicit "object_size" parameter.
+ llvm::SmallDenseMap<const ParmVarDecl *, const ImplicitParamDecl *>
+ sizeArguments;
+
/// A mapping from NRVO variables to the flags used to indicate
/// when the NRVO has been applied to this variable.
llvm::DenseMap<const VarDecl *, mlir::Value> nrvoFlags;
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunctionInfo.h b/clang/lib/CIR/CodeGen/CIRGenFunctionInfo.h
index 00b107296d9fc..f2f25215441b5 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunctionInfo.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunctionInfo.h
@@ -51,7 +51,11 @@ class RequiredArgs {
return All;
if (prototype->hasExtParameterInfos())
- llvm_unreachable("NYI");
+ additional += llvm::count_if(
+ prototype->getExtParameterInfos(),
+ [](const clang::FunctionProtoType::ExtParameterInfo &info) {
+ return info.hasPassObjectSize();
+ });
return RequiredArgs(prototype->getNumParams() + additional);
}
diff --git a/clang/test/CIR/CodeGen/pass-object-size.c b/clang/test/CIR/CodeGen/pass-object-size.c
new file mode 100644
index 0000000000000..55337baa7e0c8
--- /dev/null
+++ b/clang/test/CIR/CodeGen/pass-object-size.c
@@ -0,0 +1,65 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
+// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t-cir.ll
+// RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll
+// RUN: FileCheck --check-prefix=OGCG --input-file=%t.ll %s
+
+void b(void *__attribute__((pass_object_size(0))));
+void e(void *__attribute__((pass_object_size(2))));
+
+// CIR: cir.func private @b(!cir.ptr<!void> {llvm.noundef}, !u64i {llvm.noundef})
+
+void test_constant() {
+ int a;
+ b(&a);
+}
+
+// CIR: cir.func {{.*}} @test_constant()
+// CIR: %[[ALLOCA:.*]] = cir.alloca !s32i, !cir.ptr<!s32i>
+// CIR: %[[CAST:.*]] = cir.cast bitcast %[[ALLOCA]] : !cir.ptr<!s32i> -> !cir.ptr<!void>
+// CIR: %[[SIZE:.*]] = cir.const #cir.int<4> : !u64i
+// CIR: cir.call @b(%[[CAST]], %[[SIZE]]) : (!cir.ptr<!void> {{.*}}, !u64i {{.*}}) -> ()
+
+// CIR: cir.func private @e(!cir.ptr<!void> {llvm.noundef}, !u64i {llvm.noundef})
+
+// LLVM: declare void @b(ptr noundef, i64 noundef)
+
+// LLVM: define dso_local void @test_constant()
+// LLVM: %[[ALLOCA:.*]] = alloca i32
+// LLVM: call void @b(ptr noundef %[[ALLOCA]], i64 noundef 4)
+
+void test_vla(int n) {
+ int d[n];
+ b(d);
+ e(d);
+}
+
+// CIR: cir.func {{.*}} @test_vla
+// CIR: %[[VLA:.*]] = cir.alloca !s32i, !cir.ptr<!s32i>, %{{.*}} : !u64i, ["d"] {alignment = 16 : i64}
+// CIR: %[[CAST1:.*]] = cir.cast bitcast %[[VLA]] : !cir.ptr<!s32i> -> !cir.ptr<!void>
+// CIR: %[[SIZE1:.*]] = cir.objsize max nullunknown %[[CAST1]] : !cir.ptr<!void> -> !u64i
+// CIR: cir.call @b(%[[CAST1]], %[[SIZE1]]) : (!cir.ptr<!void> {{.*}}, !u64i {{.*}}) -> ()
+// CIR: %[[CAST2:.*]] = cir.cast bitcast %[[VLA]] : !cir.ptr<!s32i> -> !cir.ptr<!void>
+// CIR: %[[SIZE2:.*]] = cir.objsize min nullunknown %[[CAST2]] : !cir.ptr<!void> -> !u64i
+// CIR: cir.call @e(%[[CAST2]], %[[SIZE2]]) : (!cir.ptr<!void> {{.*}}, !u64i {{.*}}) -> ()
+
+// LLVM: define dso_local void @test_vla(i32 noundef %{{.*}})
+// LLVM: %[[VLA:.*]] = alloca i32, i64 %{{.*}}, align 16
+// LLVM: %[[SIZE1:.*]] = call i64 @llvm.objectsize.i64.p0(ptr %[[VLA]], i1 false, i1 true, i1 false)
+// LLVM: call void @b(ptr noundef %[[VLA]], i64 noundef %[[SIZE1]])
+// LLVM: %[[SIZE2:.*]] = call i64 @llvm.objectsize.i64.p0(ptr %[[VLA]], i1 true, i1 true, i1 false)
+// LLVM: call void @e(ptr noundef %[[VLA]], i64 noundef %[[SIZE2]])
+
+// OGCG: define dso_local void @test_constant()
+// OGCG: %[[A:.*]] = alloca i32
+// OGCG: call void @b(ptr noundef %[[A]], i64 noundef 4)
+
+// OGCG: declare void @b(ptr noundef, i64 noundef)
+
+// OGCG: define dso_local void @test_vla(i32 noundef %{{.*}})
+// OGCG: %[[VLA:.*]] = alloca i32, i64 %{{.*}}, align 16
+// OGCG: %[[SIZE1:.*]] = call i64 @llvm.objectsize.i64.p0(ptr %[[VLA]], i1 false, i1 true, i1 false)
+// OGCG: call void @b(ptr noundef %[[VLA]], i64 noundef %[[SIZE1]])
+// OGCG: %[[SIZE2:.*]] = call i64 @llvm.objectsize.i64.p0(ptr %[[VLA]], i1 true, i1 true, i1 false)
+// OGCG: call void @e(ptr noundef %[[VLA]], i64 noundef %[[SIZE2]])
More information about the cfe-commits
mailing list