[clang] [CIR] Add pass_object_size hidden parameter support (PR #191482)

via cfe-commits cfe-commits at lists.llvm.org
Wed Apr 29 13:58:36 PDT 2026


https://github.com/adams381 updated https://github.com/llvm/llvm-project/pull/191482

>From 22664019404ab772c333e352c0e5d4976d10af1d 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    | 38 ++++++++++++-
 clang/lib/CIR/CodeGen/CIRGenCall.cpp       | 36 +++++++++---
 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, 155 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..b0ac68cc08d39 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
@@ -2585,7 +2585,43 @@ 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 *d = dyn_cast<DeclRefExpr>(e->IgnoreParenImpCasts())) {
+    auto *param = dyn_cast<ParmVarDecl>(d->getDecl());
+    auto *ps = d->getDecl()->getAttr<PassObjectSizeAttr>();
+    if (param && ps) {
+      auto from = ps->getType();
+      bool compatible = from == static_cast<int>(type) ||
+                        (from == MaxWholeObject && type == MaxSubobject) ||
+                        (from == MinSubobject && type == MinWholeObject);
+      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..d139d7d75cc64 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCall.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
@@ -727,20 +727,30 @@ 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 [i, extInfo] : llvm::enumerate(extInfos)) {
+    prefix.push_back(fpt->getParamType(i));
+    if (extInfo.hasPassObjectSize())
+      prefix.push_back(cgt.getASTContext().getCanonicalSizeType());
+  }
 }
 
 const CIRGenFunctionInfo &
@@ -875,10 +885,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 +1450,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]])



More information about the cfe-commits mailing list