https://github.com/philnik777 created 
https://github.com/llvm/llvm-project/pull/178429

These attributes allow annotating pointers and references with how they are 
accessed inside a function. This allows the compiler to track the value of the 
pointees better, improving CodeGen.


>From 91d0067728c13e697426c1adfe5334fac7612ccc Mon Sep 17 00:00:00 2001
From: Nikolas Klauser <[email protected]>
Date: Wed, 28 Jan 2026 15:08:35 +0100
Subject: [PATCH] [Clang] Add [[readnone, readonly, writeonly]] parameter
 attributes

---
 clang/include/clang/AST/TypeBase.h            | 40 +++++++++++--
 clang/include/clang/Basic/Attr.td             | 19 ++++++
 .../clang/Basic/DiagnosticSemaKinds.td        |  3 +
 clang/lib/AST/TypePrinter.cpp                 | 13 ++++
 clang/lib/CodeGen/CGCall.cpp                  | 23 +++++++
 clang/lib/Sema/SemaDeclAttr.cpp               | 28 +++++++++
 clang/lib/Sema/SemaType.cpp                   | 46 ++++++++++++++
 clang/test/CodeGenCXX/attr-ptr-access.cpp     | 38 ++++++++++++
 clang/test/CodeGenCXX/attr-ptr-access.ll      | 60 +++++++++++++++++++
 clang/test/SemaCXX/attr-ptr-access.cpp        | 36 +++++++++++
 10 files changed, 302 insertions(+), 4 deletions(-)
 create mode 100644 clang/test/CodeGenCXX/attr-ptr-access.cpp
 create mode 100644 clang/test/CodeGenCXX/attr-ptr-access.ll
 create mode 100644 clang/test/SemaCXX/attr-ptr-access.cpp

diff --git a/clang/include/clang/AST/TypeBase.h 
b/clang/include/clang/AST/TypeBase.h
index 279b75f14d7b8..99309e087dcb0 100644
--- a/clang/include/clang/AST/TypeBase.h
+++ b/clang/include/clang/AST/TypeBase.h
@@ -4490,10 +4490,15 @@ class FunctionType : public Type {
   /// not produce the latter.
   class ExtParameterInfo {
     enum {
-      ABIMask = 0x0F,
-      IsConsumed = 0x10,
-      HasPassObjSize = 0x20,
-      IsNoEscape = 0x40,
+      ABIMask = 0x07,
+      IsConsumed = 0x08,
+      HasPassObjSize = 0x10,
+      IsNoEscape = 0x20,
+      PtrAccessMask = 0xC0,
+
+      IsReadNone = 0x40,
+      IsReadOnly = 0x80,
+      IsWriteOnly = 0xC0,
     };
     unsigned char Data = 0;
 
@@ -4537,6 +4542,33 @@ class FunctionType : public Type {
       return Copy;
     }
 
+    bool isReadNone() const { return (Data & PtrAccessMask) == IsReadNone; }
+    ExtParameterInfo withIsReadNone() const {
+      assert((Data & PtrAccessMask) == 0 &&
+             "Trying to change ptr access that has already been set");
+      ExtParameterInfo Copy = *this;
+      Copy.Data |= IsReadNone;
+      return Copy;
+    }
+
+    bool isReadOnly() const { return (Data & PtrAccessMask) == IsReadOnly; }
+    ExtParameterInfo withIsReadOnly() const {
+      assert((Data & PtrAccessMask) == 0 &&
+             "Trying to change ptr access that has already been set");
+      ExtParameterInfo Copy = *this;
+      Copy.Data |= IsReadOnly;
+      return Copy;
+    }
+
+    bool isWriteOnly() const { return (Data & PtrAccessMask) == IsWriteOnly; }
+    ExtParameterInfo withIsWriteOnly() const {
+      assert((Data & PtrAccessMask) == 0 &&
+             "Trying to change ptr access that has already been set");
+      ExtParameterInfo Copy = *this;
+      Copy.Data |= IsWriteOnly;
+      return Copy;
+    }
+
     unsigned char getOpaqueValue() const { return Data; }
     static ExtParameterInfo getFromOpaqueValue(unsigned char data) {
       ExtParameterInfo result;
diff --git a/clang/include/clang/Basic/Attr.td 
b/clang/include/clang/Basic/Attr.td
index ba44266d22c8c..ad936b02bcd58 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -3085,6 +3085,25 @@ def ArmLocallyStreaming : InheritableAttr, 
TargetSpecificAttr<TargetAArch64> {
   let Documentation = [ArmSmeLocallyStreamingDocs];
 }
 
+def ReadNone : InheritableParamAttr {
+  let Spellings = [Clang<"readnone">];
+  let Subjects = SubjectList<[ParmVar, ImplicitObjectParameter], ErrorDiag>;
+  let Documentation = [Undocumented];
+}
+
+def ReadOnly : InheritableParamAttr {
+  let Spellings = [Clang<"readonly">];
+  let Subjects = SubjectList<[ParmVar, ImplicitObjectParameter], ErrorDiag>;
+  let Documentation = [Undocumented];
+}
+
+def WriteOnly : InheritableParamAttr {
+  let Spellings = [Clang<"writeonly">];
+  let Subjects = SubjectList<[ParmVar, ImplicitObjectParameter], ErrorDiag>;
+  let Documentation = [Undocumented];
+}
+
+def : MutualExclusions<[ReadNone, ReadOnly, WriteOnly]>;
 
 def Pure : InheritableAttr {
   let Spellings = [GCC<"pure">];
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td 
b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index a2be7ab3791b9..316a3d8b365ae 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -3258,6 +3258,9 @@ def warn_function_attribute_ignored_in_stmt : Warning<
   "use '%0' on statements">,
   InGroup<IgnoredAttributes>;
 
+def err_pointer_reference_attribute : Error<
+  "%0 attribute can only be applied to pointers and references">;
+
 def err_musttail_needs_trivial_args : Error<
   "tail call requires that the return value, all parameters, and any "
   "temporaries created by the expression are trivially destructible">;
diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp
index 5a0cbc47e7c1a..c61dd696c9bf6 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -1917,6 +1917,19 @@ void TypePrinter::printAttributedAfter(const 
AttributedType *T,
     return;
   }
 
+  if (T->getAttrKind() == attr::ReadNone) {
+    OS <<" [[clang::readnone]]";
+    return;
+  }
+  if (T->getAttrKind() == attr::ReadOnly) {
+    OS <<" [[clang::readonly]]";
+    return;
+  }
+  if (T->getAttrKind() == attr::WriteOnly) {
+    OS <<" [[clang::writeonly]]";
+    return;
+  }
+
   // The printing of the address_space attribute is handled by the qualifier
   // since it is still stored in the qualifier. Return early to prevent 
printing
   // this twice.
diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index 5e33cabc11938..8a36dfeec5c63 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -2816,6 +2816,20 @@ void CodeGenModule::ConstructAttributeList(StringRef 
Name,
     ArgAttrs[IRArgs.first] = llvm::AttributeSet::get(getLLVMContext(), Attrs);
   }
 
+  if (const auto *MD = dyn_cast_if_present<CXXMethodDecl>(TargetDecl)) {
+    QualType T = MD->getType();
+    llvm::AttrBuilder Attrs(getLLVMContext());
+    if (T->hasAttr(attr::ReadNone))
+      Attrs.addAttribute(llvm::Attribute::ReadNone);
+    if (T->hasAttr(attr::ReadOnly))
+      Attrs.addAttribute(llvm::Attribute::ReadOnly);
+    if (T->hasAttr(attr::WriteOnly))
+      Attrs.addAttribute(llvm::Attribute::WriteOnly);
+    if (Attrs.hasAttributes())
+      ArgAttrs[0] = ArgAttrs[0].addAttributes(
+          getLLVMContext(), llvm::AttributeSet::get(getLLVMContext(), Attrs));
+  }
+
   unsigned ArgNo = 0;
   for (CGFunctionInfo::const_arg_iterator I = FI.arg_begin(), E = FI.arg_end();
        I != E; ++I, ++ArgNo) {
@@ -3014,6 +3028,15 @@ void CodeGenModule::ConstructAttributeList(StringRef 
Name,
     if (FI.getExtParameterInfo(ArgNo).isNoEscape())
       Attrs.addCapturesAttr(llvm::CaptureInfo::none());
 
+    if (FI.getExtParameterInfo(ArgNo).isReadNone())
+      Attrs.addAttribute(llvm::Attribute::ReadNone);
+
+    if (FI.getExtParameterInfo(ArgNo).isReadOnly())
+      Attrs.addAttribute(llvm::Attribute::ReadOnly);
+
+    if (FI.getExtParameterInfo(ArgNo).isWriteOnly())
+      Attrs.addAttribute(llvm::Attribute::WriteOnly);
+
     if (Attrs.hasAttributes()) {
       unsigned FirstIRArg, NumIRArgs;
       std::tie(FirstIRArg, NumIRArgs) = IRFunctionArgs.getIRArgs(ArgNo);
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index dc954c3925596..980d51316c9d3 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -5125,6 +5125,29 @@ static void handleOptimizeNoneAttr(Sema &S, Decl *D, 
const ParsedAttr &AL) {
     D->addAttr(Optnone);
 }
 
+static void handlePtrAccessAttrs(Sema &S, Decl *D, const ParsedAttr &AL) {
+  auto *PVD = cast<ParmVarDecl>(D);
+  if (const QualType T = PVD->getType();
+      !T->isPointerType() && !T->isReferenceType()) {
+    S.Diag(AL.getLoc(), diag::err_pointer_reference_attribute) << AL;
+    return;
+  }
+
+  switch (AL.getKind()) {
+  case ParsedAttr::AT_ReadNone:
+    D->addAttr(ReadNoneAttr::Create(S.Context, AL));
+    break;
+  case ParsedAttr::AT_ReadOnly:
+    D->addAttr(ReadOnlyAttr::Create(S.Context, AL));
+    break;
+  case ParsedAttr::AT_WriteOnly:
+    D->addAttr(WriteOnlyAttr::Create(S.Context, AL));
+    break;
+  default:
+    llvm_unreachable("Unexpected attribute");
+  }
+}
+
 static void handleConstantAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
   const auto *VD = cast<VarDecl>(D);
   if (VD->hasLocalStorage()) {
@@ -7473,6 +7496,11 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, 
const ParsedAttr &AL,
   case ParsedAttr::AT_OptimizeNone:
     handleOptimizeNoneAttr(S, D, AL);
     break;
+  case ParsedAttr::AT_ReadOnly:
+  case ParsedAttr::AT_ReadNone:
+  case ParsedAttr::AT_WriteOnly:
+    handlePtrAccessAttrs(S, D, AL);
+    break;
   case ParsedAttr::AT_EnumExtensibility:
     handleEnumExtensibilityAttr(S, D, AL);
     break;
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 91d36f0502d85..2a079d2c88fef 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -5307,6 +5307,21 @@ static TypeSourceInfo 
*GetFullTypeForDeclarator(TypeProcessingState &state,
             HasAnyInterestingExtParameterInfos = true;
           }
 
+          if (Param->hasAttr<ReadNoneAttr>()) {
+            ExtParameterInfos[i] = ExtParameterInfos[i].withIsReadNone();
+            HasAnyInterestingExtParameterInfos = true;
+          }
+
+          if (Param->hasAttr<ReadOnlyAttr>()) {
+            ExtParameterInfos[i] = ExtParameterInfos[i].withIsReadOnly();
+            HasAnyInterestingExtParameterInfos = true;
+          }
+
+          if (Param->hasAttr<WriteOnlyAttr>()) {
+            ExtParameterInfos[i] = ExtParameterInfos[i].withIsWriteOnly();
+            HasAnyInterestingExtParameterInfos = true;
+          }
+
           ParamTys.push_back(ParamTy);
         }
 
@@ -8818,6 +8833,31 @@ static void 
HandleLifetimeCaptureByAttr(TypeProcessingState &State,
   }
 }
 
+static void HandlePtrAccessAttrs(TypeProcessingState &State, QualType &CurType,
+                                 ParsedAttr &PA) {
+  if (!State.getDeclarator().isDeclarationOfFunction()) {
+    State.getSema().Diag(PA.getLoc(), diag::err_attribute_wrong_decl_type)
+        << PA << PA.isRegularKeywordAttribute()
+        << ExpectedParameterOrImplicitObjectParameter;
+    return;
+  }
+
+  auto *Attr = [&]() -> clang::Attr * {
+    switch (PA.getKind()) {
+    case ParsedAttr::AT_ReadNone:
+      return createSimpleAttr<ReadNoneAttr>(State.getSema().Context, PA);
+    case ParsedAttr::AT_ReadOnly:
+      return createSimpleAttr<ReadOnlyAttr>(State.getSema().Context, PA);
+    case ParsedAttr::AT_WriteOnly:
+      return createSimpleAttr<WriteOnlyAttr>(State.getSema().Context, PA);
+    default:
+      llvm_unreachable("Unexpected attribute");
+    }
+  }();
+
+  CurType = State.getAttributedType(Attr, CurType, CurType);
+}
+
 static void HandleHLSLParamModifierAttr(TypeProcessingState &State,
                                         QualType &CurType,
                                         const ParsedAttr &Attr, Sema &S) {
@@ -8985,6 +9025,12 @@ static void processTypeAttrs(TypeProcessingState &state, 
QualType &type,
       if (TAL == TAL_DeclChunk)
         HandleLifetimeCaptureByAttr(state, type, attr);
       break;
+    case ParsedAttr::AT_ReadNone:
+    case ParsedAttr::AT_ReadOnly:
+    case ParsedAttr::AT_WriteOnly:
+      if (TAL == TAL_DeclChunk)
+        HandlePtrAccessAttrs(state, type, attr);
+      break;
 
     case ParsedAttr::AT_NoDeref: {
       // FIXME: `noderef` currently doesn't work correctly in [[]] syntax.
diff --git a/clang/test/CodeGenCXX/attr-ptr-access.cpp 
b/clang/test/CodeGenCXX/attr-ptr-access.cpp
new file mode 100644
index 0000000000000..8fad73101ecf1
--- /dev/null
+++ b/clang/test/CodeGenCXX/attr-ptr-access.cpp
@@ -0,0 +1,38 @@
+// RUN: %clang_cc1 -std=c++23 -emit-llvm -triple x86_64 %s -o - | FileCheck %s
+
+void func1([[clang::readnone]] int*) {
+  // CHECK: @_Z5func1Pi(ptr noundef readnone %0)
+}
+void func2([[clang::readnone]] int&) {
+  // CHECK: @_Z5func2Ri(ptr noundef nonnull readnone align 4 
dereferenceable(4) %0)
+}
+
+void func3([[clang::readonly]] int*) {
+  // CHECK: @_Z5func3Pi(ptr noundef readonly %0)
+}
+void func4([[clang::readonly]] int&) {
+  // CHECK: @_Z5func4Ri(ptr noundef nonnull readonly align 4 
dereferenceable(4) %0)
+}
+
+void func5([[clang::writeonly]] int*) {
+  // CHECK: @_Z5func5Pi(ptr noundef writeonly %0)
+}
+void func6([[clang::writeonly]] int&) {
+  // CHECK: @_Z5func6Ri(ptr noundef nonnull writeonly align 4 
dereferenceable(4) %0)
+}
+
+struct S {
+  void func1() [[clang::readnone]];
+  void func2() [[clang::readonly]];
+  void func3() [[clang::writeonly]];
+};
+
+void S::func1() [[clang::readnone]] {
+  // CHECK: @_ZN1S5func1Ev(ptr noundef nonnull readnone align 1 
dereferenceable(1) %this)
+}
+void S::func2() [[clang::readonly]] {
+  // CHECK: @_ZN1S5func2Ev(ptr noundef nonnull readonly align 1 
dereferenceable(1) %this)
+}
+void S::func3() [[clang::writeonly]] {
+  // CHECK: @_ZN1S5func3Ev(ptr noundef nonnull writeonly align 1 
dereferenceable(1) %this)
+}
diff --git a/clang/test/CodeGenCXX/attr-ptr-access.ll 
b/clang/test/CodeGenCXX/attr-ptr-access.ll
new file mode 100644
index 0000000000000..a35e3d40fc421
--- /dev/null
+++ b/clang/test/CodeGenCXX/attr-ptr-access.ll
@@ -0,0 +1,60 @@
+; ModuleID = 
'/home/nikolas/source/clang/clang/test/CodeGenCXX/attr-ptr-access.cpp'
+source_filename = 
"/home/nikolas/source/clang/clang/test/CodeGenCXX/attr-ptr-access.cpp"
+target datalayout = 
"e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+; Function Attrs: mustprogress noinline nounwind optnone
+define dso_local void @_Z5func1Pi(ptr noundef readnone %0) #0 {
+entry:
+  %.addr = alloca ptr, align 8
+  store ptr %0, ptr %.addr, align 8
+  ret void
+}
+
+; Function Attrs: mustprogress noinline nounwind optnone
+define dso_local void @_Z5func2Ri(ptr noundef nonnull readnone align 4 
dereferenceable(4) %0) #0 {
+entry:
+  %.addr = alloca ptr, align 8
+  store ptr %0, ptr %.addr, align 8
+  ret void
+}
+
+; Function Attrs: mustprogress noinline nounwind optnone
+define dso_local void @_Z5func3Pi(ptr noundef readonly %0) #0 {
+entry:
+  %.addr = alloca ptr, align 8
+  store ptr %0, ptr %.addr, align 8
+  ret void
+}
+
+; Function Attrs: mustprogress noinline nounwind optnone
+define dso_local void @_Z5func4Ri(ptr noundef nonnull readonly align 4 
dereferenceable(4) %0) #0 {
+entry:
+  %.addr = alloca ptr, align 8
+  store ptr %0, ptr %.addr, align 8
+  ret void
+}
+
+; Function Attrs: mustprogress noinline nounwind optnone
+define dso_local void @_Z5func5Pi(ptr noundef writeonly %0) #0 {
+entry:
+  %.addr = alloca ptr, align 8
+  store ptr %0, ptr %.addr, align 8
+  ret void
+}
+
+; Function Attrs: mustprogress noinline nounwind optnone
+define dso_local void @_Z5func6Ri(ptr noundef nonnull writeonly align 4 
dereferenceable(4) %0) #0 {
+entry:
+  %.addr = alloca ptr, align 8
+  store ptr %0, ptr %.addr, align 8
+  ret void
+}
+
+attributes #0 = { mustprogress noinline nounwind optnone 
"min-legal-vector-width"="0" "no-trapping-math"="true" 
"stack-protector-buffer-size"="8" "target-features"="+cx8,+mmx,+sse,+sse2,+x87" 
}
+
+!llvm.module.flags = !{!0}
+!llvm.ident = !{!1}
+
+!0 = !{i32 1, !"wchar_size", i32 4}
+!1 = !{!"clang version 23.0.0git ([email protected]:philnik777/llvm-project.git 
4d55fb46624afb17e8bb70b308cf0d0e8a48e6cd)"}
diff --git a/clang/test/SemaCXX/attr-ptr-access.cpp 
b/clang/test/SemaCXX/attr-ptr-access.cpp
new file mode 100644
index 0000000000000..303e217e1355f
--- /dev/null
+++ b/clang/test/SemaCXX/attr-ptr-access.cpp
@@ -0,0 +1,36 @@
+// RUN: %clang_cc1 -std=c++23 -fsyntax-only -verify %s
+
+[[clang::readnone]] void func_readnone(); // expected-error 
{{'clang::readnone' attribute only applies to parameters and implicit object 
parameters}}
+void func_readnone(int [[clang::readnone]]); // expected-error 
{{'clang::readnone' attribute cannot be applied to types}}
+void func_readnone([[clang::readnone]] int); // expected-error 
{{'clang::readnone' attribute can only be applied to pointers and references}}
+void func_readnone([[clang::readnone]] int*);
+void func_readnone([[clang::readnone]] int&);
+
+[[clang::readonly]] void func_readonly(); // expected-error 
{{'clang::readonly' attribute only applies to parameters and implicit object 
parameters}}
+void func_readonly(int [[clang::readonly]]); // expected-error 
{{'clang::readonly' attribute cannot be applied to types}}
+void func_readonly([[clang::readonly]] int); // expected-error 
{{'clang::readonly' attribute can only be applied to pointers and references}}
+void func_readonly([[clang::readonly]] int*);
+void func_readonly([[clang::readonly]] int&);
+
+[[clang::writeonly]] void func_writeonly(); // expected-error 
{{'clang::writeonly' attribute only applies to parameters and implicit object 
parameters}}
+void func_writeonly(int [[clang::writeonly]]); // expected-error 
{{'clang::writeonly' attribute cannot be applied to types}}
+void func_writeonly([[clang::writeonly]] int); // expected-error 
{{'clang::writeonly' attribute can only be applied to pointers and references}}
+void func_writeonly([[clang::writeonly]] int*);
+void func_writeonly([[clang::writeonly]] int&);
+
+void func_mutex1([[clang::readnone, clang::readonly]] int*); // expected-error 
{{'clang::readonly' and 'clang::readnone' attributes are not compatible}} 
expected-note {{here}}
+void func_mutex2([[clang::readnone, clang::writeonly]] int*); // 
expected-error {{'clang::writeonly' and 'clang::readnone' attributes are not 
compatible}} expected-note {{here}}
+void func_mutex3([[clang::readonly, clang::writeonly]] int*); // 
expected-error {{'clang::writeonly' and 'clang::readonly' attributes are not 
compatible}} expected-note {{here}}
+
+void func_arg_mismatch([[clang::readnone]] int*);
+void func_arg_mismatch([[clang::readnone]] int*); // expected-note {{here}}
+void func_arg_mismatch([[clang::readonly]] int*); // expected-error 
{{conflicting types for 'func_arg_mismatch'}}
+
+struct S {
+  void func1() [[clang::readnone]];
+  void func2() [[clang::readonly]];
+  void func3() [[clang::writeonly]];
+  void func4() [[clang::readnone, clang::readonly]];
+  void func5() [[clang::readnone, clang::writeonly]];
+  void func6() [[clang::readonly, clang::writeonly]];
+};

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to