https://github.com/adams381 created
https://github.com/llvm/llvm-project/pull/191482
Emit the hidden `i64` parameter that `__attribute__((pass_object_size(N)))`
requires. At call sites the size is constant-folded when possible (e.g. `&a` →
4) and falls back to `cir.objsize` / `@llvm.objectsize` otherwise (e.g. VLAs).
On the callee side, `buildFunctionArgList` now creates an `ImplicitParamDecl`
for each annotated parameter so that `emitBuiltinObjectSize` can load the
passed size instead of re-computing it.
This also fixes the `llvm_unreachable("NYI")` in
`RequiredArgs::getFromProtoWithExtraSlots` and the `errorNYI` in
`appendParameterTypes` / `arrangeFreeFunctionLikeCall` that fired whenever
`hasExtParameterInfos()` was true.
New test: `clang/test/CIR/CodeGen/pass-object-size.c` (CIR / LLVM / OGCG).
Made with [Cursor](https://cursor.com)
>From d4edb73fcf347868bf335ca2c4599604b06f8bdc Mon Sep 17 00:00:00 2001
From: Adam Smith <[email protected]>
Date: Fri, 10 Apr 2026 11:10:38 -0700
Subject: [PATCH] [CIR] Add pass_object_size hidden parameter support
Implement hidden parameter insertion for
__attribute__((pass_object_size(N))). The compiler now emits an extra
i64 parameter per annotated param, filled with __builtin_object_size
at the call site (constant-folded when possible, otherwise via
cir.objsize / @llvm.objectsize).
Key changes:
- appendParameterTypes adds canonical size_t for each param with
hasPassObjectSize()
- RequiredArgs::getFromProtoWithExtraSlots counts pass_object_size
slots for variadic required-arg accounting
- maybeEmitImplicitObjectSize calls evaluateOrEmitBuiltinObjectSize
to fill the hidden param at call sites
- buildFunctionArgList creates ImplicitParamDecl entries so callee
bodies can load the passed size via sizeArguments
- emitBuiltinObjectSize checks sizeArguments before falling back to
cir.objsize, matching classic codegen behavior
Made-with: Cursor
---
clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp | 24 +++++++-
clang/lib/CIR/CodeGen/CIRGenCall.cpp | 28 +++++++---
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 ++++++++++++++++++++++
6 files changed, 128 insertions(+), 11 deletions(-)
create mode 100644 clang/test/CIR/CodeGen/pass-object-size.c
diff --git a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
index 523348a952775..0cee4832433cf 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
@@ -2578,11 +2578,33 @@ mlir::Value CIRGenFunction::emitVAArg(VAArgExpr *ve) {
return cir::VAArgOp::create(builder, loc, type, vaList);
}
+/// Checks if using the result of __builtin_object_size(p, @p from) in place of
+/// __builtin_object_size(p, @p to) is correct.
+static bool areBOSTypesCompatible(int from, int to) {
+ return from == to || (from == 0 && to == 1) || (from == 3 && to == 2);
+}
+
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.
+ if (auto *d = dyn_cast<DeclRefExpr>(e->IgnoreParenImpCasts())) {
+ auto *param = dyn_cast<ParmVarDecl>(d->getDecl());
+ auto *ps = d->getDecl()->getAttr<PassObjectSizeAttr>();
+ if (param && ps && areBOSTypesCompatible(ps->getType(), type)) {
+ auto iter = sizeArguments.find(param);
+ assert(iter != sizeArguments.end());
+
+ const ImplicitParamDecl *sizeDecl = iter->second;
+ auto dIter = localDeclMap.find(sizeDecl);
+ assert(dIter != localDeclMap.end());
+
+ return emitLoadOfScalar(dIter->second, /*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 876fef687b477..5cf334b959f97 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCall.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
@@ -708,20 +708,26 @@ 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");
+ prefix.reserve(prefix.size() + fpt->getNumParams());
+ auto 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().getCanonicalType(
+ cgt.getASTContext().getSizeType()));
+ }
}
const CIRGenFunctionInfo &
@@ -859,8 +865,7 @@ arrangeFreeFunctionLikeCall(CIRGenTypes &cgt, CIRGenModule
&cgm,
if (const auto *proto = dyn_cast<FunctionProtoType>(fnType)) {
if (proto->isVariadic())
required = RequiredArgs::getFromProtoWithExtraSlots(proto, 0);
- if (proto->hasExtParameterInfos())
- cgm.errorNYI("call to functions with extra parameter info");
+ assert(!cir::MissingFeatures::opCallExtParameterInfo());
} else if (cgm.getTargetCIRGenInfo().isNoProtoCallVariadic(
cast<FunctionNoProtoType>(fnType)))
cgm.errorNYI("call to function without a prototype");
@@ -1396,8 +1401,15 @@ void CIRGenFunction::emitCallArgs(
if (!ps)
return;
- assert(!cir::MissingFeatures::opCallImplicitObjectSizeArgs());
- cgm.errorNYI("emit implicit object size for call arg");
+ const ASTContext &astContext = getContext();
+ QualType sizeTy = astContext.getSizeType();
+ cir::IntType t = builder.getUIntNTy(astContext.getTypeSize(sizeTy));
+ assert(emittedArg.getValue() && "We emitted nothing for the arg?");
+ mlir::Value v = evaluateOrEmitBuiltinObjectSize(
+ arg, ps->getType(), t, emittedArg.getValue(), ps->isDynamic());
+ args.add(RValue::get(v), sizeTy);
+ 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 f7f9331060956..917a27d978030 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
@@ -995,8 +995,17 @@ clang::QualType
CIRGenFunction::buildFunctionArgList(clang::GlobalDecl gd,
cgm.errorNYI(fd->getSourceRange(),
"buildFunctionArgList: inherited constructor");
- for (auto *param : fd->parameters())
+ for (auto *param : fd->parameters()) {
args.push_back(param);
+ 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);
+ }
if (md && (isa<CXXConstructorDecl>(md) || isa<CXXDestructorDecl>(md)))
cgm.getCXXABI().addImplicitStructorParams(*this, retTy, args);
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h
b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index 88c7996eab569..faf6c0e787329 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -133,6 +133,11 @@ class CIRGenFunction : public CIRGenTypeCache {
ImplicitParamDecl *cxxStructorImplicitParamDecl{};
mlir::Value cxxStructorImplicitParamValue{};
+ /// 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 *, 2>
+ sizeArguments;
+
/// The value of 'this' to sue when evaluating CXXDefaultInitExprs within
this
/// expression.
Address cxxDefaultInitExprThis = Address::invalid();
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunctionInfo.h
b/clang/lib/CIR/CodeGen/CIRGenFunctionInfo.h
index 00b107296d9fc..67a33d1e75401 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 &extInfo) {
+ return extInfo.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]])
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits