Skip to content

Commit 06eee73

Browse files
committed
[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/[email protected]/ Differential Revision: https://reviews.llvm.org/D152986
1 parent d45b1b6 commit 06eee73

12 files changed

+68
-11
lines changed

clang/include/clang/Basic/Attr.td

+2-3
Original file line numberDiff line numberDiff line change
@@ -1482,9 +1482,8 @@ def : MutualExclusions<[Likely, Unlikely]>;
14821482
def NoMerge : DeclOrStmtAttr {
14831483
let Spellings = [Clang<"nomerge">];
14841484
let Documentation = [NoMergeDocs];
1485-
let Subjects = SubjectList<[Function, Stmt], ErrorDiag,
1486-
"functions and statements">;
1487-
let SimpleHandler = 1;
1485+
let Subjects = SubjectList<[Function, Stmt, Var], ErrorDiag,
1486+
"functions, statements and variables">;
14881487
}
14891488

14901489
def MustTail : StmtAttr {

clang/include/clang/Basic/AttrDocs.td

+25-1
Original file line numberDiff line numberDiff line change
@@ -549,7 +549,31 @@ over the tradeoff between code size and debug information precision.
549549

550550
``nomerge`` attribute can also be used as function attribute to prevent all
551551
calls to the specified function from merging. It has no effect on indirect
552-
calls.
552+
calls to such functions. For example:
553+
554+
.. code-block:: c++
555+
556+
[[clang::nomerge]] void foo(int) {}
557+
558+
void bar(int x) {
559+
auto *ptr = foo;
560+
if (x) foo(1); else foo(2); // will not be merged
561+
if (x) ptr(1); else ptr(2); // indirect call, can be merged
562+
}
563+
564+
``nomerge`` attribute can also be used for pointers to functions to
565+
prevent calls through such pointer from merging. In such case the
566+
effect applies only to a specific function pointer. For example:
567+
568+
.. code-block:: c++
569+
570+
[[clang::nomerge]] void (*foo)(int);
571+
572+
void bar(int x) {
573+
auto *ptr = foo;
574+
if (x) foo(1); else foo(2); // will not be merged
575+
if (x) ptr(1); else ptr(2); // 'ptr' has no 'nomerge' attribute, can be merged
576+
}
553577
}];
554578
}
555579

clang/include/clang/Basic/DiagnosticSemaKinds.td

+4
Original file line numberDiff line numberDiff line change
@@ -2981,6 +2981,10 @@ def warn_attribute_ignored_no_calls_in_stmt: Warning<
29812981
"statement">,
29822982
InGroup<IgnoredAttributes>;
29832983

2984+
def warn_attribute_ignored_non_function_pointer: Warning<
2985+
"%0 attribute is ignored because %1 is not a function pointer">,
2986+
InGroup<IgnoredAttributes>;
2987+
29842988
def warn_function_attribute_ignored_in_stmt : Warning<
29852989
"attribute is ignored on this statement as it only applies to functions; "
29862990
"use '%0' on statements">,

clang/lib/CodeGen/CGCall.cpp

+3
Original file line numberDiff line numberDiff line change
@@ -2341,6 +2341,9 @@ void CodeGenModule::ConstructAttributeList(StringRef Name,
23412341
FuncAttrs.addAttribute(llvm::Attribute::NoReturn);
23422342
NBA = Fn->getAttr<NoBuiltinAttr>();
23432343
}
2344+
}
2345+
2346+
if (isa<FunctionDecl>(TargetDecl) || isa<VarDecl>(TargetDecl)) {
23442347
// Only place nomerge attribute on call sites, never functions. This
23452348
// allows it to work on indirect virtual function calls.
23462349
if (AttrOnCallSite && TargetDecl->hasAttr<NoMergeAttr>())

clang/lib/Sema/SemaDeclAttr.cpp

+13
Original file line numberDiff line numberDiff line change
@@ -8375,6 +8375,16 @@ static void handleAvailableOnlyInDefaultEvalMethod(Sema &S, Decl *D,
83758375
handleSimpleAttribute<AvailableOnlyInDefaultEvalMethodAttr>(S, D, AL);
83768376
}
83778377

8378+
static void handleNoMergeAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
8379+
auto *VDecl = dyn_cast<VarDecl>(D);
8380+
if (VDecl && !VDecl->isFunctionPointerType()) {
8381+
S.Diag(AL.getLoc(), diag::warn_attribute_ignored_non_function_pointer)
8382+
<< AL << VDecl;
8383+
return;
8384+
}
8385+
D->addAttr(NoMergeAttr::Create(S.Context, AL));
8386+
}
8387+
83788388
static void handleSYCLKernelAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
83798389
// The 'sycl_kernel' attribute applies only to function templates.
83808390
const auto *FD = cast<FunctionDecl>(D);
@@ -9255,6 +9265,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
92559265
case ParsedAttr::AT_FunctionReturnThunks:
92569266
handleFunctionReturnThunksAttr(S, D, AL);
92579267
break;
9268+
case ParsedAttr::AT_NoMerge:
9269+
handleNoMergeAttr(S, D, AL);
9270+
break;
92589271

92599272
case ParsedAttr::AT_AvailableOnlyInDefaultEvalMethod:
92609273
handleAvailableOnlyInDefaultEvalMethod(S, D, AL);

clang/test/CodeGen/attr-nomerge.cpp

+4
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@ class B : public A {
1616

1717
bool bar();
1818
[[clang::nomerge]] void f(bool, bool);
19+
[[clang::nomerge]] void (*fptr)(void);
1920

2021
void foo(int i, A *ap, B *bp) {
2122
[[clang::nomerge]] bar();
2223
[[clang::nomerge]] (i = 4, bar());
2324
[[clang::nomerge]] (void)(bar());
2425
f(bar(), bar());
26+
fptr();
2527
[[clang::nomerge]] [] { bar(); bar(); }(); // nomerge only applies to the anonymous function call
2628
[[clang::nomerge]] for (bar(); bar(); bar()) {}
2729
[[clang::nomerge]] { asm("nop"); }
@@ -66,6 +68,8 @@ void something_else_again() {
6668
// CHECK: call noundef zeroext i1 @_Z3barv(){{$}}
6769
// CHECK: call noundef zeroext i1 @_Z3barv(){{$}}
6870
// CHECK: call void @_Z1fbb({{.*}}) #[[ATTR0]]
71+
// CHECK: %[[FPTR:.*]] = load ptr, ptr @fptr
72+
// CHECK-NEXT: call void %[[FPTR]]() #[[ATTR0]]
6973
// CHECK: call void @"_ZZ3fooiP1AP1BENK3$_0clEv"{{.*}} #[[ATTR0]]
7074
// CHECK: call noundef zeroext i1 @_Z3barv() #[[ATTR0]]
7175
// CHECK-LABEL: for.cond:

clang/test/Misc/pragma-attribute-supported-attributes-list.test

+1-1
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@
105105
// CHECK-NEXT: NoEscape (SubjectMatchRule_variable_is_parameter)
106106
// CHECK-NEXT: NoInline (SubjectMatchRule_function)
107107
// CHECK-NEXT: NoInstrumentFunction (SubjectMatchRule_function, SubjectMatchRule_objc_method)
108-
// CHECK-NEXT: NoMerge (SubjectMatchRule_function)
108+
// CHECK-NEXT: NoMerge (SubjectMatchRule_function, SubjectMatchRule_variable)
109109
// CHECK-NEXT: NoMicroMips (SubjectMatchRule_function)
110110
// CHECK-NEXT: NoMips16 (SubjectMatchRule_function)
111111
// CHECK-NEXT: NoProfileFunction (SubjectMatchRule_function)

clang/test/Parser/stmt-attributes.c

+4-2
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,11 @@ void foobar(void) {
8282
int x;
8383
__attribute__((nomerge)) x = 10; // expected-warning {{'nomerge' attribute is ignored because there exists no call expression inside the statement}}
8484

85-
__attribute__((nomerge)) label : bar(); // expected-error {{'nomerge' attribute only applies to functions and statements}}
85+
__attribute__((nomerge)) label : bar(); // expected-error {{'nomerge' attribute only applies to functions, statements and variables}}
8686
}
8787

8888
int f(void);
8989

90-
__attribute__((nomerge)) static int i; // expected-error {{'nomerge' attribute only applies to functions and statements}}
90+
__attribute__((nomerge)) static int (*fptr)(void);
91+
__attribute__((nomerge)) static int i; // expected-warning {{'nomerge' attribute is ignored because 'i' is not a function pointer}}
92+
struct buz {} __attribute__((nomerge)); // expected-error {{'nomerge' attribute only applies to functions, statements and variables}}

clang/test/Parser/stmt-attributes.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

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

1212
class B : public A<> {

clang/test/Parser/stmt-attributes.m

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ - (void)bar;
1919

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

2525
- (void)bar {

clang/test/Sema/attr-nomerge-ast.cpp

+4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
[[clang::nomerge]] void func();
55
void func();
66
[[clang::nomerge]] void func() {}
7+
[[clang::nomerge]] void (*var)(void);
78

89
// CHECK: FunctionDecl {{.*}} func 'void ()'
910
// CHECK-NEXT: NoMergeAttr
@@ -14,3 +15,6 @@ void func();
1415
// CHECK-NEXT: FunctionDecl {{.*}} func 'void ()'
1516
// CHECK-NEXT: CompoundStmt
1617
// CHECK-NEXT: NoMergeAttr
18+
19+
// CHECK-NEXT: VarDecl {{.*}} var 'void (*)()'
20+
// CHECK-NEXT: NoMergeAttr

clang/test/Sema/attr-nomerge.cpp

+6-2
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,14 @@ void foo() {
88
int x;
99
[[clang::nomerge]] x = 10; // expected-warning {{'nomerge' attribute is ignored because there exists no call expression inside the statement}}
1010

11-
[[clang::nomerge]] label: bar(); // expected-error {{'nomerge' attribute only applies to functions and statements}}
11+
[[clang::nomerge]] label: bar(); // expected-error {{'nomerge' attribute only applies to functions, statements and variables}}
1212

1313
}
1414

1515
[[clang::nomerge]] int f();
1616

17-
[[clang::nomerge]] static int i = f(); // expected-error {{'nomerge' attribute only applies to functions and statements}}
17+
[[clang::nomerge]] static int i = f(); // expected-warning {{'nomerge' attribute is ignored because 'i' is not a function pointer}}
18+
19+
[[clang::nomerge]] void (*j)(void);
20+
21+
struct [[clang::nomerge]] buz {}; // expected-error {{'nomerge' attribute only applies to functions, statements and variables}}

0 commit comments

Comments
 (0)