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
