[clang] 06eee73 - [clang] Allow 'nomerge' attribute for function pointers

Eduard Zingerman via cfe-commits cfe-commits at lists.llvm.org
Mon Jun 26 15:23:05 PDT 2023


Author: Eduard Zingerman
Date: 2023-06-27T01:15:45+03:00
New Revision: 06eee734c1ea9de754c1e7e8c79c0897b9e01350

URL: https://github.com/llvm/llvm-project/commit/06eee734c1ea9de754c1e7e8c79c0897b9e01350
DIFF: https://github.com/llvm/llvm-project/commit/06eee734c1ea9de754c1e7e8c79c0897b9e01350.diff

LOG: [clang] Allow 'nomerge' attribute for function pointers

Allow specifying 'nomerge' attribute for function pointers,
e.g. like in the following C code:

    extern void (*foo)(void) __attribute__((nomerge));
    void bar(long i) {
      if (i)
        foo();
      else
        foo();
    }

With the goal to attach 'nomerge' to both calls done through 'foo':

    @foo = external local_unnamed_addr global ptr, align 8
    define dso_local void @bar(i64 noundef %i) local_unnamed_addr #0 {
      ; ...
      %0 = load ptr, ptr @foo, align 8, !tbaa !5
      ; ...
    if.then:
      tail call void %0() #1
      br label %if.end
    if.else:
      tail call void %0() #1
      br label %if.end
    if.end:
      ret void
    }
    ; ...
    attributes #1 = { nomerge ... }

Report a warning in case if 'nomerge' is specified for a variable that
is not a function pointer, e.g.:

    t.c:2:22: warning: 'nomerge' attribute is ignored because 'j' is not a function pointer [-Wignored-attributes]
        2 | int j __attribute__((nomerge));
          |                      ^

The intended use-case is for BPF backend.

BPF provides a sort of "standard library" functions that are called
helpers. BPF also verifies usage of these helpers before program
execution. Because of limitations of verification / runtime model it
is important to keep calls to some of such helpers from merging.

An example could be found by the link [1], there input C code:

     if (data_end - data > 1024) {
         bpf_for_each_map_elem(&map1, cb, &cb_data, 0);
     } else {
         bpf_for_each_map_elem(&map2, cb, &cb_data, 0);
     }

Is converted to bytecode equivalent to:

     if (data_end - data > 1024)
       tmp = &map1;
     else
       tmp = &map2;
     bpf_for_each_map_elem(tmp, cb, &cb_data, 0);

However, BPF verification/runtime requires to use the same map address
for each particular `bpf_for_each_map_elem()` call.

The 'nomerge' attribute is a perfect match for this situation, but
unfortunately BPF helpers are declared as pointers to functions:

    static long (*bpf_for_each_map_elem)(void *map, ...) = (void *) 164;

Hence, this commit, allowing to use 'nomerge' for function pointers.

[1] https://lore.kernel.org/bpf/03bdf90f-f374-1e67-69d6-76dd9c8318a4@meta.com/

Differential Revision: https://reviews.llvm.org/D152986

Added: 
    

Modified: 
    clang/include/clang/Basic/Attr.td
    clang/include/clang/Basic/AttrDocs.td
    clang/include/clang/Basic/DiagnosticSemaKinds.td
    clang/lib/CodeGen/CGCall.cpp
    clang/lib/Sema/SemaDeclAttr.cpp
    clang/test/CodeGen/attr-nomerge.cpp
    clang/test/Misc/pragma-attribute-supported-attributes-list.test
    clang/test/Parser/stmt-attributes.c
    clang/test/Parser/stmt-attributes.cpp
    clang/test/Parser/stmt-attributes.m
    clang/test/Sema/attr-nomerge-ast.cpp
    clang/test/Sema/attr-nomerge.cpp

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index b9e701641fdf1..d5204b2869667 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -1482,9 +1482,8 @@ def : MutualExclusions<[Likely, Unlikely]>;
 def NoMerge : DeclOrStmtAttr {
   let Spellings = [Clang<"nomerge">];
   let Documentation = [NoMergeDocs];
-  let Subjects = SubjectList<[Function, Stmt], ErrorDiag,
-                             "functions and statements">;
-  let SimpleHandler = 1;
+  let Subjects = SubjectList<[Function, Stmt, Var], ErrorDiag,
+                             "functions, statements and variables">;
 }
 
 def MustTail : StmtAttr {

diff  --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 5178ca43b9710..2c950231255d7 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -549,7 +549,31 @@ over the tradeoff between code size and debug information precision.
 
 ``nomerge`` attribute can also be used as function attribute to prevent all
 calls to the specified function from merging. It has no effect on indirect
-calls.
+calls to such functions. For example:
+
+.. code-block:: c++
+
+  [[clang::nomerge]] void foo(int) {}
+
+  void bar(int x) {
+    auto *ptr = foo;
+    if (x) foo(1); else foo(2); // will not be merged
+    if (x) ptr(1); else ptr(2); // indirect call, can be merged
+  }
+
+``nomerge`` attribute can also be used for pointers to functions to
+prevent calls through such pointer from merging. In such case the
+effect applies only to a specific function pointer. For example:
+
+.. code-block:: c++
+
+  [[clang::nomerge]] void (*foo)(int);
+
+  void bar(int x) {
+    auto *ptr = foo;
+    if (x) foo(1); else foo(2); // will not be merged
+    if (x) ptr(1); else ptr(2); // 'ptr' has no 'nomerge' attribute, can be merged
+  }
   }];
 }
 

diff  --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 3f0b7a54d8890..507e9f11f4664 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -2981,6 +2981,10 @@ def warn_attribute_ignored_no_calls_in_stmt: Warning<
   "statement">,
   InGroup<IgnoredAttributes>;
 
+def warn_attribute_ignored_non_function_pointer: Warning<
+  "%0 attribute is ignored because %1 is not a function pointer">,
+  InGroup<IgnoredAttributes>;
+
 def warn_function_attribute_ignored_in_stmt : Warning<
   "attribute is ignored on this statement as it only applies to functions; "
   "use '%0' on statements">,

diff  --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index 61029c9226d7b..6a9be661998fc 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -2341,6 +2341,9 @@ void CodeGenModule::ConstructAttributeList(StringRef Name,
           FuncAttrs.addAttribute(llvm::Attribute::NoReturn);
         NBA = Fn->getAttr<NoBuiltinAttr>();
       }
+    }
+
+    if (isa<FunctionDecl>(TargetDecl) || isa<VarDecl>(TargetDecl)) {
       // Only place nomerge attribute on call sites, never functions. This
       // allows it to work on indirect virtual function calls.
       if (AttrOnCallSite && TargetDecl->hasAttr<NoMergeAttr>())

diff  --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index f4fb096bb2ffc..ed69e802c95dd 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -8375,6 +8375,16 @@ static void handleAvailableOnlyInDefaultEvalMethod(Sema &S, Decl *D,
   handleSimpleAttribute<AvailableOnlyInDefaultEvalMethodAttr>(S, D, AL);
 }
 
+static void handleNoMergeAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
+  auto *VDecl = dyn_cast<VarDecl>(D);
+  if (VDecl && !VDecl->isFunctionPointerType()) {
+    S.Diag(AL.getLoc(), diag::warn_attribute_ignored_non_function_pointer)
+        << AL << VDecl;
+    return;
+  }
+  D->addAttr(NoMergeAttr::Create(S.Context, AL));
+}
+
 static void handleSYCLKernelAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
   // The 'sycl_kernel' attribute applies only to function templates.
   const auto *FD = cast<FunctionDecl>(D);
@@ -9255,6 +9265,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
   case ParsedAttr::AT_FunctionReturnThunks:
     handleFunctionReturnThunksAttr(S, D, AL);
     break;
+  case ParsedAttr::AT_NoMerge:
+    handleNoMergeAttr(S, D, AL);
+    break;
 
   case ParsedAttr::AT_AvailableOnlyInDefaultEvalMethod:
     handleAvailableOnlyInDefaultEvalMethod(S, D, AL);

diff  --git a/clang/test/CodeGen/attr-nomerge.cpp b/clang/test/CodeGen/attr-nomerge.cpp
index 1be84d76aa45a..2d015f6164550 100644
--- a/clang/test/CodeGen/attr-nomerge.cpp
+++ b/clang/test/CodeGen/attr-nomerge.cpp
@@ -16,12 +16,14 @@ class B : public A {
 
 bool bar();
 [[clang::nomerge]] void f(bool, bool);
+[[clang::nomerge]] void (*fptr)(void);
 
 void foo(int i, A *ap, B *bp) {
   [[clang::nomerge]] bar();
   [[clang::nomerge]] (i = 4, bar());
   [[clang::nomerge]] (void)(bar());
   f(bar(), bar());
+  fptr();
   [[clang::nomerge]] [] { bar(); bar(); }(); // nomerge only applies to the anonymous function call
   [[clang::nomerge]] for (bar(); bar(); bar()) {}
   [[clang::nomerge]] { asm("nop"); }
@@ -66,6 +68,8 @@ void something_else_again() {
 // CHECK: call noundef zeroext i1 @_Z3barv(){{$}}
 // CHECK: call noundef zeroext i1 @_Z3barv(){{$}}
 // CHECK: call void @_Z1fbb({{.*}}) #[[ATTR0]]
+// CHECK: %[[FPTR:.*]] = load ptr, ptr @fptr
+// CHECK-NEXT: call void %[[FPTR]]() #[[ATTR0]]
 // CHECK: call void @"_ZZ3fooiP1AP1BENK3$_0clEv"{{.*}} #[[ATTR0]]
 // CHECK: call noundef zeroext i1 @_Z3barv() #[[ATTR0]]
 // CHECK-LABEL: for.cond:

diff  --git a/clang/test/Misc/pragma-attribute-supported-attributes-list.test b/clang/test/Misc/pragma-attribute-supported-attributes-list.test
index 3691e827dfb1d..eaf6d34421bbe 100644
--- a/clang/test/Misc/pragma-attribute-supported-attributes-list.test
+++ b/clang/test/Misc/pragma-attribute-supported-attributes-list.test
@@ -105,7 +105,7 @@
 // CHECK-NEXT: NoEscape (SubjectMatchRule_variable_is_parameter)
 // CHECK-NEXT: NoInline (SubjectMatchRule_function)
 // CHECK-NEXT: NoInstrumentFunction (SubjectMatchRule_function, SubjectMatchRule_objc_method)
-// CHECK-NEXT: NoMerge (SubjectMatchRule_function)
+// CHECK-NEXT: NoMerge (SubjectMatchRule_function, SubjectMatchRule_variable)
 // CHECK-NEXT: NoMicroMips (SubjectMatchRule_function)
 // CHECK-NEXT: NoMips16 (SubjectMatchRule_function)
 // CHECK-NEXT: NoProfileFunction (SubjectMatchRule_function)

diff  --git a/clang/test/Parser/stmt-attributes.c b/clang/test/Parser/stmt-attributes.c
index ccd206e70a225..782e055f6263e 100644
--- a/clang/test/Parser/stmt-attributes.c
+++ b/clang/test/Parser/stmt-attributes.c
@@ -82,9 +82,11 @@ void foobar(void) {
   int x;
   __attribute__((nomerge)) x = 10; // expected-warning {{'nomerge' attribute is ignored because there exists no call expression inside the statement}}
 
-  __attribute__((nomerge)) label : bar(); // expected-error {{'nomerge' attribute only applies to functions and statements}}
+  __attribute__((nomerge)) label : bar(); // expected-error {{'nomerge' attribute only applies to functions, statements and variables}}
 }
 
 int f(void);
 
-__attribute__((nomerge)) static int i; // expected-error {{'nomerge' attribute only applies to functions and statements}}
+__attribute__((nomerge)) static int (*fptr)(void);
+__attribute__((nomerge)) static int i; // expected-warning {{'nomerge' attribute is ignored because 'i' is not a function pointer}}
+struct buz {} __attribute__((nomerge)); // expected-error {{'nomerge' attribute only applies to functions, statements and variables}}

diff  --git a/clang/test/Parser/stmt-attributes.cpp b/clang/test/Parser/stmt-attributes.cpp
index 74192ea68141d..839c44e908af0 100644
--- a/clang/test/Parser/stmt-attributes.cpp
+++ b/clang/test/Parser/stmt-attributes.cpp
@@ -6,7 +6,7 @@
 
 template <typename T = void>
 class __attribute__((nomerge)) A {
-  // expected-error at -1 {{'nomerge' attribute only applies to functions and statements}}
+  // expected-error at -1 {{'nomerge' attribute only applies to functions, statements and variables}}
 };
 
 class B : public A<> {

diff  --git a/clang/test/Parser/stmt-attributes.m b/clang/test/Parser/stmt-attributes.m
index 227ea9dc2facc..7b15bc8e0b815 100644
--- a/clang/test/Parser/stmt-attributes.m
+++ b/clang/test/Parser/stmt-attributes.m
@@ -19,7 +19,7 @@ - (void)bar;
 
 @implementation Test
 - (void)foo __attribute__((nomerge)) {
-  // expected-error at -1 {{'nomerge' attribute only applies to functions and statements}}
+  // expected-error at -1 {{'nomerge' attribute only applies to functions, statements and variables}}
 }
 
 - (void)bar {

diff  --git a/clang/test/Sema/attr-nomerge-ast.cpp b/clang/test/Sema/attr-nomerge-ast.cpp
index c440cf760b16b..410fda5a4e096 100644
--- a/clang/test/Sema/attr-nomerge-ast.cpp
+++ b/clang/test/Sema/attr-nomerge-ast.cpp
@@ -4,6 +4,7 @@
 [[clang::nomerge]] void func();
 void func();
 [[clang::nomerge]] void func() {}
+[[clang::nomerge]] void (*var)(void);
 
 // CHECK: FunctionDecl {{.*}} func 'void ()'
 // CHECK-NEXT: NoMergeAttr
@@ -14,3 +15,6 @@ void func();
 // CHECK-NEXT: FunctionDecl {{.*}} func 'void ()'
 // CHECK-NEXT: CompoundStmt
 // CHECK-NEXT: NoMergeAttr
+
+// CHECK-NEXT: VarDecl {{.*}} var 'void (*)()'
+// CHECK-NEXT: NoMergeAttr

diff  --git a/clang/test/Sema/attr-nomerge.cpp b/clang/test/Sema/attr-nomerge.cpp
index 294abfafcd2aa..69a535bde76e2 100644
--- a/clang/test/Sema/attr-nomerge.cpp
+++ b/clang/test/Sema/attr-nomerge.cpp
@@ -8,10 +8,14 @@ void foo() {
   int x;
   [[clang::nomerge]] x = 10; // expected-warning {{'nomerge' attribute is ignored because there exists no call expression inside the statement}}
 
-  [[clang::nomerge]] label: bar(); // expected-error {{'nomerge' attribute only applies to functions and statements}}
+  [[clang::nomerge]] label: bar(); // expected-error {{'nomerge' attribute only applies to functions, statements and variables}}
 
 }
 
 [[clang::nomerge]] int f();
 
-[[clang::nomerge]] static int i = f(); // expected-error {{'nomerge' attribute only applies to functions and statements}}
+[[clang::nomerge]] static int i = f(); // expected-warning {{'nomerge' attribute is ignored because 'i' is not a function pointer}}
+
+[[clang::nomerge]] void (*j)(void);
+
+struct [[clang::nomerge]] buz {}; // expected-error {{'nomerge' attribute only applies to functions, statements and variables}}


        


More information about the cfe-commits mailing list