[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