Skip to content

Commit

Permalink
[clang] Allow 'nomerge' attribute for function pointers
Browse files Browse the repository at this point in the history
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
  • Loading branch information
eddyz87 committed Jun 26, 2023
1 parent d45b1b6 commit 06eee73
Show file tree
Hide file tree
Showing 12 changed files with 68 additions and 11 deletions.
5 changes: 2 additions & 3 deletions clang/include/clang/Basic/Attr.td
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
26 changes: 25 additions & 1 deletion clang/include/clang/Basic/AttrDocs.td
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}];
}

Expand Down
4 changes: 4 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -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">,
Expand Down
3 changes: 3 additions & 0 deletions clang/lib/CodeGen/CGCall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>())
Expand Down
13 changes: 13 additions & 0 deletions clang/lib/Sema/SemaDeclAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
4 changes: 4 additions & 0 deletions clang/test/CodeGen/attr-nomerge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"); }
Expand Down Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 4 additions & 2 deletions clang/test/Parser/stmt-attributes.c
Original file line number Diff line number Diff line change
Expand Up @@ -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}}
2 changes: 1 addition & 1 deletion clang/test/Parser/stmt-attributes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

template <typename T = void>
class __attribute__((nomerge)) A {
// expected-error@-1 {{'nomerge' attribute only applies to functions and statements}}
// expected-error@-1 {{'nomerge' attribute only applies to functions, statements and variables}}
};

class B : public A<> {
Expand Down
2 changes: 1 addition & 1 deletion clang/test/Parser/stmt-attributes.m
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ - (void)bar;

@implementation Test
- (void)foo __attribute__((nomerge)) {
// expected-error@-1 {{'nomerge' attribute only applies to functions and statements}}
// expected-error@-1 {{'nomerge' attribute only applies to functions, statements and variables}}
}

- (void)bar {
Expand Down
4 changes: 4 additions & 0 deletions clang/test/Sema/attr-nomerge-ast.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
8 changes: 6 additions & 2 deletions clang/test/Sema/attr-nomerge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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}}

0 comments on commit 06eee73

Please sign in to comment.