[clang] [clang] Add [[clang::returns_argument]] (PR #169815)
Nikolas Klauser via cfe-commits
cfe-commits at lists.llvm.org
Fri Nov 28 02:35:19 PST 2025
https://github.com/philnik777 updated https://github.com/llvm/llvm-project/pull/169815
>From f9d65aba4f7ed0f4401e7252f74c1ee465d0630f Mon Sep 17 00:00:00 2001
From: Nikolas Klauser <nikolasklauser at berlin.de>
Date: Wed, 9 Jul 2025 15:08:44 +0200
Subject: [PATCH] [clang] Add [[clang::returns]]
---
clang/include/clang/Basic/Attr.td | 7 +++
clang/include/clang/Basic/AttrDocs.td | 16 ++++++
.../clang/Basic/DiagnosticSemaKinds.td | 5 ++
clang/lib/CodeGen/CGCall.cpp | 9 ++++
clang/lib/Sema/SemaDeclAttr.cpp | 51 +++++++++++++++++++
.../test/CodeGenCXX/attr-returns-argument.cpp | 41 +++++++++++++++
...a-attribute-supported-attributes-list.test | 1 +
clang/test/SemaCXX/attr-returns-argument.cpp | 18 +++++++
8 files changed, 148 insertions(+)
create mode 100644 clang/test/CodeGenCXX/attr-returns-argument.cpp
create mode 100644 clang/test/SemaCXX/attr-returns-argument.cpp
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 8e5f7ef0bb82d..c90f29fb2011c 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -2682,6 +2682,13 @@ def NoEscape : Attr {
let Documentation = [NoEscapeDocs];
}
+def ReturnsArgument : InheritableAttr {
+ let Spellings = [Clang<"returns_argument">];
+ let Subjects = SubjectList<[Function]>;
+ let Args = [ParamIdxArgument<"ReturnedVal">];
+ let Documentation = [ReturnsArgumentDocs];
+}
+
def MaybeUndef : InheritableAttr {
let Spellings = [Clang<"maybe_undef">];
let Subjects = SubjectList<[ParmVar]>;
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index c1b1510f363d4..130cfcb19cab5 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -287,6 +287,22 @@ applies to copies of the block. For example:
}];
}
+def ReturnsArgumentDocs : Documentation {
+ let Category = DocCatFunction;
+ let Content = [{
+The ``returns_argument`` attribute can be placed on a function to indicate when
+an argument is returned identically. This allows the compiler to use the
+returned valud instead of the argument, potentially decreasing register
+pressure. This attribute can currently only be applied to arguments that are
+pointers or references to the same type as the returned pointer or reference.
+
+Sample usage:
+.. code-block:: c
+
+ [[clang::returns_argument(1)]] void* memcpy(void*, const void*, size_t);
+ }];
+}
+
def MaybeUndefDocs : Documentation {
let Category = DocCatVariable;
let Content = [{
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 4a145fd71eedd..d203380acccbd 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -3439,6 +3439,9 @@ def err_attribute_only_once_per_parameter : Error<
"%0 attribute can only be applied once per parameter">;
def err_mismatched_uuid : Error<"uuid does not match previous declaration">;
def note_previous_uuid : Note<"previous uuid specified here">;
+def err_return_type_mismatch : Error<
+ "returned parameter has to reference the same type as the return type">;
+def note_return_type : Note<"return type is %0">;
def warn_attribute_pointers_only : Warning<
"%0 attribute only applies to%select{| constant}1 pointer arguments">,
InGroup<IgnoredAttributes>;
@@ -3473,6 +3476,8 @@ def warn_attribute_return_pointers_refs_only : Warning<
def warn_attribute_pointer_or_reference_only : Warning<
"%0 attribute only applies to a pointer or reference (%1 is invalid)">,
InGroup<IgnoredAttributes>;
+def err_attribute_pointer_or_reference_only : Error<
+ warn_attribute_pointer_or_reference_only.Summary>;
def err_attribute_no_member_pointers : Error<
"%0 attribute cannot be used with pointers to members">;
def err_attribute_invalid_implicit_this_argument : Error<
diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index efacb3cc04c01..02471fbf718cb 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -3009,6 +3009,15 @@ void CodeGenModule::ConstructAttributeList(StringRef Name,
getLLVMContext(), llvm::AttributeSet::get(getLLVMContext(), Attrs));
}
}
+
+ if (TargetDecl) {
+ if (const auto *Returns = TargetDecl->getAttr<ReturnsArgumentAttr>()) {
+ llvm::AttributeSet &Attrs =
+ ArgAttrs[Returns->getReturnedVal().getLLVMIndex()];
+ Attrs = Attrs.addAttribute(getLLVMContext(), llvm::Attribute::Returned);
+ }
+ }
+
assert(ArgNo == FI.arg_size());
ArgNo = 0;
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index e3af5023c74d0..d264c9481cc38 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -1375,6 +1375,54 @@ static void handleNoEscapeAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
D->addAttr(::new (S.Context) NoEscapeAttr(S.Context, AL));
}
+static void handleReturnsArgumentAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
+ auto *FD = cast<FunctionDecl>(D);
+
+ ParamIdx Index;
+ if (!S.checkFunctionOrMethodParameterIndex(
+ D, AL, 1, AL.getArgAsExpr(0), Index, /*CanIndexImplicitThis=*/true))
+ return;
+
+ QualType ArgType;
+
+ if (const auto *MD = dyn_cast<CXXMethodDecl>(FD);
+ MD && MD->isInstance() && Index.getLLVMIndex() == 0) {
+ ArgType = MD->getThisType();
+ } else {
+ ArgType = MD->getParamDecl(Index.getASTIndex())->getType();
+ }
+
+ if (!ArgType->isPointerOrReferenceType()) {
+ S.Diag(D->getBeginLoc(), diag::err_attribute_pointer_or_reference_only)
+ << AL << ArgType;
+ return;
+ }
+
+ ArgType = ArgType->isPointerType() ? ArgType->getPointeeType()
+ : ArgType.getNonReferenceType();
+
+ QualType ReturnType = FD->getReturnType();
+
+ if (!ReturnType->isPointerOrReferenceType()) {
+ S.Diag(D->getBeginLoc(), diag::err_return_type_mismatch);
+ S.Diag(FD->getReturnTypeSourceRange().getBegin(), diag::note_return_type)
+ << FD->getReturnType();
+ return;
+ }
+
+ ReturnType = ReturnType->isPointerType() ? ReturnType->getPointeeType()
+ : ReturnType.getNonReferenceType();
+
+ if (!S.Context.hasSameType(ReturnType, ArgType)) {
+ S.Diag(D->getBeginLoc(), diag::err_return_type_mismatch);
+ S.Diag(FD->getReturnTypeSourceRange().getBegin(), diag::note_return_type)
+ << FD->getReturnType();
+ return;
+ }
+
+ D->addAttr(ReturnsArgumentAttr::Create(S.Context, Index, AL));
+}
+
static void handleAssumeAlignedAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
Expr *E = AL.getArgAsExpr(0),
*OE = AL.getNumArgs() > 1 ? AL.getArgAsExpr(1) : nullptr;
@@ -7361,6 +7409,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
case ParsedAttr::AT_NoEscape:
handleNoEscapeAttr(S, D, AL);
break;
+ case ParsedAttr::AT_ReturnsArgument:
+ handleReturnsArgumentAttr(S, D, AL);
+ break;
case ParsedAttr::AT_MaybeUndef:
handleSimpleAttribute<MaybeUndefAttr>(S, D, AL);
break;
diff --git a/clang/test/CodeGenCXX/attr-returns-argument.cpp b/clang/test/CodeGenCXX/attr-returns-argument.cpp
new file mode 100644
index 0000000000000..7d1f61c24f0d1
--- /dev/null
+++ b/clang/test/CodeGenCXX/attr-returns-argument.cpp
@@ -0,0 +1,41 @@
+// RUN: %clang_cc1 -triple=x86_64-apple-darwin -std=c++11 -emit-llvm -o - %s | FileCheck %s
+
+[[clang::returns_argument(1)]] int* test3(int* i) {
+ // CHECK: @_Z5test3Pi(ptr noundef returned
+ return i;
+}
+
+[[clang::returns_argument(1)]] int& test4(int& i) {
+ // CHECK: @_Z5test4Ri(ptr noundef nonnull returned
+ return i;
+}
+
+[[clang::returns_argument(1)]] int* test5(int* const i) {
+ // CHECK: _Z5test5Pi(ptr noundef returned
+ return i;
+}
+
+[[clang::returns_argument(2)]] int* test6(int*, int* i) {
+ // CHECK: @_Z5test6PiS_(ptr noundef %{{.*}}, ptr noundef returned
+ return i;
+}
+
+[[clang::returns_argument(1)]] int& test7(int* i) {
+ //CHECK: @_Z5test7Pi(ptr noundef returned
+ return *i;
+}
+
+struct S {
+ [[clang::returns_argument(1)]] S& func1();
+ [[clang::returns_argument(1)]] static S& func4(S*);
+};
+
+S& S::func1() {
+ // CHECK: @_ZN1S5func1Ev(ptr noundef nonnull returned
+ return *this;
+}
+
+S& S::func4(S* i) {
+ // CHECK: @_ZN1S5func4EPS_(ptr noundef returned
+ return *i;
+}
diff --git a/clang/test/Misc/pragma-attribute-supported-attributes-list.test b/clang/test/Misc/pragma-attribute-supported-attributes-list.test
index 747eb17446c87..59b2501f103a7 100644
--- a/clang/test/Misc/pragma-attribute-supported-attributes-list.test
+++ b/clang/test/Misc/pragma-attribute-supported-attributes-list.test
@@ -185,6 +185,7 @@
// CHECK-NEXT: ReqdWorkGroupSize (SubjectMatchRule_function)
// CHECK-NEXT: Restrict (SubjectMatchRule_function)
// CHECK-NEXT: ReturnTypestate (SubjectMatchRule_function, SubjectMatchRule_variable_is_parameter)
+// CHECK-NEXT: ReturnsArgument (SubjectMatchRule_function)
// CHECK-NEXT: ReturnsNonNull (SubjectMatchRule_objc_method, SubjectMatchRule_function)
// CHECK-NEXT: ReturnsTwice (SubjectMatchRule_function)
// CHECK-NEXT: SYCLExternal (SubjectMatchRule_function)
diff --git a/clang/test/SemaCXX/attr-returns-argument.cpp b/clang/test/SemaCXX/attr-returns-argument.cpp
new file mode 100644
index 0000000000000..ed0960bddf9e7
--- /dev/null
+++ b/clang/test/SemaCXX/attr-returns-argument.cpp
@@ -0,0 +1,18 @@
+// RUN: %clang_cc1 -fsyntax-only -verify %s
+
+[[clang::returns_argument(1)]] int test1(int); // expected-error {{'clang::returns_argument' attribute only applies to a pointer or reference ('int' is invalid)}}
+[[clang::returns_argument(1)]] int* test2(long*); // expected-error {{returned parameter has to reference the same type as the return type}} \
+ expected-note {{return type is 'int *'}}
+[[clang::returns_argument(1)]] int* test3(int*);
+[[clang::returns_argument(1)]] int& test4(int&);
+[[clang::returns_argument(1)]] int* test5(int* const);
+[[clang::returns_argument(1)]] int& test6(int*);
+[[clang::returns_argument(2)]] int& test7(int*); // expected-error {{'clang::returns_argument' attribute parameter 1 is out of bounds}}
+
+struct S {
+ [[clang::returns_argument(1)]] S& func1();
+ [[clang::returns_argument(2)]] S& func2(); // expected-error {{'clang::returns_argument' attribute parameter 1 is out of bounds}}
+ [[clang::returns_argument(1)]] int* func3(int*); // expected-error {{returned parameter has to reference the same type as the return type}} \
+ expected-note {{return type is 'int *'}}
+ [[clang::returns_argument(1)]] static S& func4(S*);
+};
More information about the cfe-commits
mailing list