[clang] [CIR] Add pass_object_size hidden parameter support (PR #191482)
via cfe-commits
cfe-commits at lists.llvm.org
Mon May 4 09:53:59 PDT 2026
https://github.com/adams381 updated https://github.com/llvm/llvm-project/pull/191482
>From 55be23ecfa656d593c6d8f913b1b0195dadd191c 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 1/2] [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 | 37 +++++++++++-
clang/lib/CIR/CodeGen/CIRGenCall.cpp | 38 ++++++++++---
clang/lib/CIR/CodeGen/CIRGenFunction.cpp | 10 +++-
clang/lib/CIR/CodeGen/CIRGenFunction.h | 5 ++
clang/lib/CIR/CodeGen/CIRGenFunctionInfo.h | 15 ++++-
clang/test/CIR/CodeGen/pass-object-size.c | 65 ++++++++++++++++++++++
7 files changed, 156 insertions(+), 15 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..94f7f806bf185 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
@@ -2585,7 +2585,42 @@ 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: a pass_object_size annotation with one type can
+ // satisfy a __builtin_object_size query with a different type when the
+ // annotated type is a safe approximation. Type 0 (max, whole object) is
+ // an overestimate for type 1 (max, closest surrounding subobject), and
+ // type 3 (min, closest surrounding subobject) is an underestimate for
+ // type 2 (min, whole object).
+ enum BOSType {
+ MaxWholeObject = 0,
+ MaxSubobject = 1,
+ MinWholeObject = 2,
+ MinSubobject = 3,
+ };
+ if (auto *dre = dyn_cast<DeclRefExpr>(e->IgnoreParenImpCasts())) {
+ auto *param = dyn_cast<ParmVarDecl>(dre->getDecl());
+ auto *objSizeAttr = dre->getDecl()->getAttr<PassObjectSizeAttr>();
+ if (param && objSizeAttr) {
+ auto from = objSizeAttr->getType();
+ bool compatible = from == static_cast<int>(type) ||
+ (from == MaxWholeObject && type == MaxSubobject) ||
+ (from == MinSubobject && type == MinWholeObject);
+ if (compatible) {
+ const ImplicitParamDecl *sizeDecl = sizeArguments.lookup(param);
+ assert(sizeDecl && "expected pass_object_size implicit param");
+
+ DeclMapTy::iterator declIter = localDeclMap.find(sizeDecl);
+ assert(declIter != localDeclMap.end());
+ Address addr = declIter->second;
+
+ 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..b2b6d289c5db2 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCall.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
@@ -727,20 +727,32 @@ 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();
+ assert(extInfos.size() == fpt->getNumParams());
+ for (auto [paramType, extInfo] : llvm::zip_equal(
+ llvm::make_range(fpt->param_type_begin(), fpt->param_type_end()),
+ extInfos)) {
+ prefix.push_back(paramType);
+ if (extInfo.hasPassObjectSize())
+ prefix.push_back(cgt.getASTContext().getCanonicalSizeType());
+ }
}
const CIRGenFunctionInfo &
@@ -875,10 +887,9 @@ arrangeFreeFunctionLikeCall(CIRGenTypes &cgt, CIRGenModule &cgm,
RequiredArgs required = RequiredArgs::All;
if (const auto *proto = dyn_cast<FunctionProtoType>(fnType)) {
+ unsigned numExtraSlots = getNumPassObjectSizeParams(proto);
if (proto->isVariadic())
- required = RequiredArgs::getFromProtoWithExtraSlots(proto, 0);
- if (proto->hasExtParameterInfos())
- cgm.errorNYI("call to functions with extra parameter info");
+ required = RequiredArgs::getFromProtoWithExtraSlots(proto, numExtraSlots);
} else if (cgm.getTargetCIRGenInfo().isNoProtoCallVariadic(
cast<FunctionNoProtoType>(fnType)))
cgm.errorNYI("call to function without a prototype");
@@ -1441,8 +1452,17 @@ void CIRGenFunction::emitCallArgs(
if (!ps)
return;
- assert(!cir::MissingFeatures::opCallImplicitObjectSizeArgs());
- cgm.errorNYI("emit implicit object size for call arg");
+ QualType sizeTy = getContext().getSizeType();
+ assert(emittedArg.getValue() && "We emitted nothing for the arg?");
+ mlir::Value v = evaluateOrEmitBuiltinObjectSize(
+ arg, ps->getType(), cast<cir::IntType>(cgm.sizeTy),
+ 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::iter_swap(args.rbegin(), std::next(args.rbegin()));
};
// 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..e750c2cadb215 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
@@ -1004,8 +1004,14 @@ 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..d37a6149bcafa 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunctionInfo.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunctionInfo.h
@@ -23,6 +23,18 @@
namespace clang::CIRGen {
+/// Return the number of parameters with the pass_object_size attribute.
+inline unsigned
+getNumPassObjectSizeParams(const clang::FunctionProtoType *proto) {
+ if (!proto->hasExtParameterInfos())
+ return 0;
+ return llvm::count_if(
+ proto->getExtParameterInfos(),
+ [](const clang::FunctionProtoType::ExtParameterInfo &info) {
+ return info.hasPassObjectSize();
+ });
+}
+
/// A class for recording the number of arguments that a function signature
/// requires.
class RequiredArgs {
@@ -50,8 +62,7 @@ class RequiredArgs {
if (!prototype->isVariadic())
return All;
- if (prototype->hasExtParameterInfos())
- llvm_unreachable("NYI");
+ additional += getNumPassObjectSizeParams(prototype);
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]])
>From b644b9b24ffca9951b11a93d6a26c5e620652b28 Mon Sep 17 00:00:00 2001
From: Adam Smith <adams at nvidia.com>
Date: Mon, 4 May 2026 09:53:39 -0700
Subject: [PATCH 2/2] [CIR] Reorder pass-object-size.c checks per function
Address andykaylor's review feedback on #191482: move the OGCG
test_constant checks (and the OGCG @b declaration) up so that
each function's CIR / LLVM / OGCG checks are grouped together
rather than collecting OGCG at the bottom of the file.
Pure test-only reordering; no functional change.
Co-authored-by: Cursor <cursoragent at cursor.com>
---
clang/test/CIR/CodeGen/pass-object-size.c | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/clang/test/CIR/CodeGen/pass-object-size.c b/clang/test/CIR/CodeGen/pass-object-size.c
index 55337baa7e0c8..eaaeb77241bca 100644
--- a/clang/test/CIR/CodeGen/pass-object-size.c
+++ b/clang/test/CIR/CodeGen/pass-object-size.c
@@ -29,6 +29,12 @@ void test_constant() {
// LLVM: %[[ALLOCA:.*]] = alloca i32
// LLVM: call void @b(ptr noundef %[[ALLOCA]], i64 noundef 4)
+// 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)
+
void test_vla(int n) {
int d[n];
b(d);
@@ -51,12 +57,6 @@ void test_vla(int n) {
// 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)
More information about the cfe-commits
mailing list