Skip to content

[Clang] Allow the use of [[gnu::visibility]] with #pragma clang attribute #145653

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 26, 2025

Conversation

philnik777
Copy link
Contributor

I don't see any reason this shouldn't be allowed. AFAICT this is only disabled due to the heuristics used to determine whether it makes sense to allow the use of an attribute with #pragma clang attribute.

This allows libc++ to drop _LIBCPP_HIDE_FROM_ABI in a lot of places, making the library significantly easier to read.

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Jun 25, 2025
@llvmbot
Copy link
Member

llvmbot commented Jun 25, 2025

@llvm/pr-subscribers-clang

Author: Nikolas Klauser (philnik777)

Changes

I don't see any reason this shouldn't be allowed. AFAICT this is only disabled due to the heuristics used to determine whether it makes sense to allow the use of an attribute with #pragma clang attribute.

This allows libc++ to drop _LIBCPP_HIDE_FROM_ABI in a lot of places, making the library significantly easier to read.


Full diff: https://github.com/llvm/llvm-project/pull/145653.diff

3 Files Affected:

  • (modified) clang/include/clang/Basic/Attr.td (+1)
  • (modified) clang/test/CodeGen/visibility.c (+10)
  • (modified) clang/test/Sema/attr-visibility.c (+6)
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index f113cd2ba2fbf..7272715558bf2 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -3735,6 +3735,7 @@ def Visibility : InheritableAttr {
                            ["default", "hidden", "internal", "protected"],
                            ["Default", "Hidden", "Hidden", "Protected"]>];
   let MeaningfulToClassTemplateDefinition = 1;
+  let PragmaAttributeSupport = 1;
   let Documentation = [Undocumented];
 }
 
diff --git a/clang/test/CodeGen/visibility.c b/clang/test/CodeGen/visibility.c
index ee760ec77879e..3abd70870bf92 100644
--- a/clang/test/CodeGen/visibility.c
+++ b/clang/test/CodeGen/visibility.c
@@ -72,3 +72,13 @@ __private_extern__ int test4 = 10;
 // CHECK-HIDDEN-LABEL: define hidden void @test5()
 __attribute__((availability(macosx,introduced=10.5,deprecated=10.6)))
 __private_extern__ void test5(void) {}
+
+
+#pragma clang attribute push([[gnu::visibility("hidden")]], apply_to=function)
+
+// CHECK-DEFAULT-LABEL: define hidden void @func()
+// CHECK-PROTECTED-LABEL: define hidden void @func()
+// CHECK-HIDDEN-LABEL: define hidden void @func()
+void func(void) {}
+
+#pragma clang attribute pop
diff --git a/clang/test/Sema/attr-visibility.c b/clang/test/Sema/attr-visibility.c
index 4acca7a7f69a3..0497e9760c44f 100644
--- a/clang/test/Sema/attr-visibility.c
+++ b/clang/test/Sema/attr-visibility.c
@@ -25,3 +25,9 @@ typedef int __attribute__((visibility("default"))) bar; // expected-warning {{'v
 int x __attribute__((type_visibility("default"))); // expected-error {{'type_visibility' attribute only applies to types and namespaces}}
 
 int PR17105 __attribute__((visibility(hidden))); // expected-error {{'visibility' attribute requires a string}}
+
+#pragma clang attribute push([[gnu::visibility("default")]], apply_to=function)
+
+void func(void) {}
+
+#pragma clang attribute pop

@AaronBallman
Copy link
Collaborator

Precommit CI found relevant failures:

FAIL: Clang :: Misc/pragma-attribute-supported-attributes-list.test (13566 of 21745)
******************** TEST 'Clang :: Misc/pragma-attribute-supported-attributes-list.test' FAILED ********************
Exit Code: 1

Command Output (stderr):
--
/home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/clang-tblgen -gen-clang-test-pragma-attribute-supported-attributes -I/home/gha/actions-runner/_work/llvm-project/llvm-project/clang/include /home/gha/actions-runner/_work/llvm-project/llvm-project/clang/include/clang/Basic/Attr.td -o - | /home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/FileCheck /home/gha/actions-runner/_work/llvm-project/llvm-project/clang/test/Misc/pragma-attribute-supported-attributes-list.test # RUN: at line 1
+ /home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/clang-tblgen -gen-clang-test-pragma-attribute-supported-attributes -I/home/gha/actions-runner/_work/llvm-project/llvm-project/clang/include /home/gha/actions-runner/_work/llvm-project/llvm-project/clang/include/clang/Basic/Attr.td -o -
+ /home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/FileCheck /home/gha/actions-runner/_work/llvm-project/llvm-project/clang/test/Misc/pragma-attribute-supported-attributes-list.test
/home/gha/actions-runner/_work/llvm-project/llvm-project/clang/test/Misc/pragma-attribute-supported-attributes-list.test:215:16: error: CHECK-NEXT: is not on the line after the previous match
// CHECK-NEXT: WarnUnused (SubjectMatchRule_record)
               ^
<stdin>:212:1: note: 'next' match was here
WarnUnused (SubjectMatchRule_record)
^
<stdin>:210:40: note: previous match ended here
VecTypeHint (SubjectMatchRule_function)
                                       ^
<stdin>:211:1: note: non-matching line after previous match is here
Visibility ()
^

Input file: <stdin>
Check file: /home/gha/actions-runner/_work/llvm-project/llvm-project/clang/test/Misc/pragma-attribute-supported-attributes-list.test

-dump-input=help explains the following input dump.

Input was:
<<<<<<
          .
          .
          .
        207: UseHandle (SubjectMatchRule_variable_is_parameter) 
        208: VTablePointerAuthentication (SubjectMatchRule_record) 
        209: VecReturn (SubjectMatchRule_record) 
        210: VecTypeHint (SubjectMatchRule_function) 
        211: Visibility () 
        212: WarnUnused (SubjectMatchRule_record) 
next:215     !~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  error: match on wrong line
        213: WarnUnusedResult (SubjectMatchRule_objc_method, SubjectMatchRule_enum, SubjectMatchRule_record, SubjectMatchRule_hasType_functionType, SubjectMatchRule_type_alias) 
        214: Weak (SubjectMatchRule_variable, SubjectMatchRule_function, SubjectMatchRule_record) 
        215: WeakRef (SubjectMatchRule_variable, SubjectMatchRule_function) 
        216: WebAssemblyExportName (SubjectMatchRule_function) 
        217: WebAssemblyImportModule (SubjectMatchRule_function) 
          .
          .
          .
>>>>>>

--
********************

@philnik777 philnik777 force-pushed the visibility_push_pop branch from 16bde54 to 5b1e820 Compare June 25, 2025 13:42
Copy link
Collaborator

@erichkeane erichkeane left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see a reason to not do this, and if it makes the library simpler, even better. I think you fixed the pre-commit CI now, so LGTM.

@AaronBallman
Copy link
Collaborator

This is missing test coverage for the interesting cases.

I'm a bit worried about how easy it will be to apply this attribute in unexpected places. e.g., it will apply to member functions as well as free functions, will anyone expect that though? It also applies to special functions like main, though that's more a question of the attribute and less about the pragma.

What's the behavior going to be in C when you get an implicit function declaration in C89 mode? Does that declaration then pick up this attribute as well? How about other special member functions we define on behalf of the user?

@philnik777
Copy link
Contributor Author

@AaronBallman Are any of these concerns specific to [[gnu::visibility]]? The exact same concerns seem to apply to any other attribute that applies to functions.

@erichkeane
Copy link
Collaborator

@AaronBallman Are any of these concerns specific to [[gnu::visibility]]? The exact same concerns seem to apply to any other attribute that applies to functions.

We just talked about it between the two of us, and I think I agree, we want to make sure the behavior is 'sane' for these cases and make sure we know they aren't surprising. Visibility has seemingly a custom appertainment which is concerning. So please make sure these are covered in tests, and we can make sure we are OK with them.

@AaronBallman
Copy link
Collaborator

@AaronBallman Are any of these concerns specific to [[gnu::visibility]]? The exact same concerns seem to apply to any other attribute that applies to functions.

Yes and no. It's a general issue with other attributes, but when we added this pragma, we decided to be conservative with what attributes are allowed. We'd go on a case-by-case basis for whether the attribute will be something users can make sense of using. (For example, we don't support [[deprecated]] or [[maybe_unused]] etc).

I think this may be reasonable, but it would help if there were a significant number of test cases around the edges so we can reason about how usable the functionality is.

@philnik777
Copy link
Contributor Author

This is missing test coverage for the interesting cases.

I'm a bit worried about how easy it will be to apply this attribute in unexpected places. e.g., it will apply to member functions as well as free functions, will anyone expect that though?

This is explicitly documented, so I'd say yes. This is how the pragma works for better or worse.

It also applies to special functions like main, though that's more a question of the attribute and less about the pragma.

I'm not sure whether this is a request for a test?

What's the behavior going to be in C when you get an implicit function declaration in C89 mode? Does that declaration then pick up this attribute as well? How about other special member functions we define on behalf of the user?

I'm not 100% sure what you mean by "define on behalf of users". I've added a test for an explicitly defaulted special member. Note though that using #pragma clang attribute is basically like explicitly defaulting the special members and adding the attribute there.

@AaronBallman
Copy link
Collaborator

This is missing test coverage for the interesting cases.
I'm a bit worried about how easy it will be to apply this attribute in unexpected places. e.g., it will apply to member functions as well as free functions, will anyone expect that though?

This is explicitly documented, so I'd say yes. This is how the pragma works for better or worse.

I think you're right, but is that actually helpful in this case? (I'm not super experienced with the visibility attributes, coming from a Windows background -- does it make sense to have "hidden" member functions on a class? If so, then I think the attribute makes sense to support. If not, maybe we want to consider different diagnostics to help users figure out when they've made a potential mistake?)

CC @compnerd who maybe can help me with my education. :-D

It also applies to special functions like main, though that's more a question of the attribute and less about the pragma.

I'm not sure whether this is a request for a test?

Yes, along with "maybe we want to consider different diagnostics" if the idea of adding visibility to main makes no sense.

What's the behavior going to be in C when you get an implicit function declaration in C89 mode? Does that declaration then pick up this attribute as well? How about other special member functions we define on behalf of the user?

I'm not 100% sure what you mean by "define on behalf of users". I've added a test for an explicitly defaulted special member.

That's what I was after.

Note though that using #pragma clang attribute is basically like explicitly defaulting the special members and adding the attribute there.

Good to know, I kind of expected that. Does the same happen for an implicit function declaration in C? I can see us defining it with or without the attribute, so it's more a matter of knowing what happens and capturing it in a test so we can be aware if we change that behavior.

@philnik777
Copy link
Contributor Author

This is missing test coverage for the interesting cases.
I'm a bit worried about how easy it will be to apply this attribute in unexpected places. e.g., it will apply to member functions as well as free functions, will anyone expect that though?

This is explicitly documented, so I'd say yes. This is how the pragma works for better or worse.

I think you're right, but is that actually helpful in this case? (I'm not super experienced with the visibility attributes, coming from a Windows background -- does it make sense to have "hidden" member functions on a class? If so, then I think the attribute makes sense to support. If not, maybe we want to consider different diagnostics to help users figure out when they've made a potential mistake?)

At least that is exactly what libc++ needs. So yeah, depending on what you want to achieve it can definitely make sense. If someone needs something different we can still add function(unless(is_member)) or something like that to the matchers.

CC @compnerd who maybe can help me with my education. :-D

It also applies to special functions like main, though that's more a question of the attribute and less about the pragma.

I'm not sure whether this is a request for a test?

Yes, along with "maybe we want to consider different diagnostics" if the idea of adding visibility to main makes no sense.

What's the behavior going to be in C when you get an implicit function declaration in C89 mode? Does that declaration then pick up this attribute as well? How about other special member functions we define on behalf of the user?

I'm not 100% sure what you mean by "define on behalf of users". I've added a test for an explicitly defaulted special member.

That's what I was after.

Note though that using #pragma clang attribute is basically like explicitly defaulting the special members and adding the attribute there.

Good to know, I kind of expected that. Does the same happen for an implicit function declaration in C? I can see us defining it with or without the attribute, so it's more a matter of knowing what happens and capturing it in a test so we can be aware if we change that behavior.

I've added a test for implicit declarations in C and that's affected by #pragma clang attribute, but it seems to not be affected by -fvisibility. Not sure whether this makes any sense. To me implicit declarations don't make any sense in general.

@AaronBallman
Copy link
Collaborator

This is missing test coverage for the interesting cases.
I'm a bit worried about how easy it will be to apply this attribute in unexpected places. e.g., it will apply to member functions as well as free functions, will anyone expect that though?

This is explicitly documented, so I'd say yes. This is how the pragma works for better or worse.

I think you're right, but is that actually helpful in this case? (I'm not super experienced with the visibility attributes, coming from a Windows background -- does it make sense to have "hidden" member functions on a class? If so, then I think the attribute makes sense to support. If not, maybe we want to consider different diagnostics to help users figure out when they've made a potential mistake?)

At least that is exactly what libc++ needs. So yeah, depending on what you want to achieve it can definitely make sense. If someone needs something different we can still add function(unless(is_member)) or something like that to the matchers.

I think I come down on the same side, yeah.

CC @compnerd who maybe can help me with my education. :-D

It also applies to special functions like main, though that's more a question of the attribute and less about the pragma.

I'm not sure whether this is a request for a test?

Yes, along with "maybe we want to consider different diagnostics" if the idea of adding visibility to main makes no sense.

What's the behavior going to be in C when you get an implicit function declaration in C89 mode? Does that declaration then pick up this attribute as well? How about other special member functions we define on behalf of the user?

I'm not 100% sure what you mean by "define on behalf of users". I've added a test for an explicitly defaulted special member.

That's what I was after.

Note though that using #pragma clang attribute is basically like explicitly defaulting the special members and adding the attribute there.

Good to know, I kind of expected that. Does the same happen for an implicit function declaration in C? I can see us defining it with or without the attribute, so it's more a matter of knowing what happens and capturing it in a test so we can be aware if we change that behavior.

I've added a test for implicit declarations in C and that's affected by #pragma clang attribute, but it seems to not be affected by -fvisibility. Not sure whether this makes any sense. To me implicit declarations don't make any sense in general.

Huh, that is a bit surprising of behavior. But I think it's orthogonal here and not blocking anything.

Copy link
Collaborator

@AaronBallman AaronBallman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@philnik777 philnik777 merged commit afc6c2b into llvm:main Jun 26, 2025
7 checks passed
@philnik777 philnik777 deleted the visibility_push_pop branch June 26, 2025 12:54
@compnerd
Copy link
Member

compnerd commented Jun 26, 2025

This is missing test coverage for the interesting cases.
I'm a bit worried about how easy it will be to apply this attribute in unexpected places. e.g., it will apply to member functions as well as free functions, will anyone expect that though?

This is explicitly documented, so I'd say yes. This is how the pragma works for better or worse.

I think you're right, but is that actually helpful in this case? (I'm not super experienced with the visibility attributes, coming from a Windows background -- does it make sense to have "hidden" member functions on a class? If so, then I think the attribute makes sense to support. If not, maybe we want to consider different diagnostics to help users figure out when they've made a potential mistake?)

CC @compnerd who maybe can help me with my education. :-D

It is possible to apply the hidden visibility to member functions on classes. However, it does tend to complicate your ability to reason about the code IMO (although the same argument holds for __declspec(dllexport) on member functions). The attribute simply would prevent the member function from participating in dynamic linking - so any other module would need to define their own copy.

@erichkeane
Copy link
Collaborator

This is missing test coverage for the interesting cases.
I'm a bit worried about how easy it will be to apply this attribute in unexpected places. e.g., it will apply to member functions as well as free functions, will anyone expect that though?

This is explicitly documented, so I'd say yes. This is how the pragma works for better or worse.

I think you're right, but is that actually helpful in this case? (I'm not super experienced with the visibility attributes, coming from a Windows background -- does it make sense to have "hidden" member functions on a class? If so, then I think the attribute makes sense to support. If not, maybe we want to consider different diagnostics to help users figure out when they've made a potential mistake?)
CC @compnerd who maybe can help me with my education. :-D

It is possible to apply the hidden visibility to member functions on classes. However, it does tend to complicate your ability to reason about the code IMO (although the same argument holds for __declspec(dllexport) on member functions. The attribute simply would prevent the member function from participating in dynamic linking - so any other module would need to define their own copy.

That DOES sound error prone. I wonder if there is use to have this diagnose via a warning that explains this, particularly now that we are allowing this to be more easily added to members.

@compnerd
Copy link
Member

It is possible to apply the hidden visibility to member functions on classes. However, it does tend to complicate your ability to reason about the code IMO (although the same argument holds for __declspec(dllexport) on member functions. The attribute simply would prevent the member function from participating in dynamic linking - so any other module would need to define their own copy.

That DOES sound error prone. I wonder if there is use to have this diagnose via a warning that explains this, particularly now that we are allowing this to be more easily added to members.

The interesting thing is the inverse case - which is what we are working towards with the efforts to build LLVM as a DLL. We annotate the members explicitly for their participation in the ABI via LLVM_ABI. The intention is that all members will have __attribute__((__visibility__("hidden"))) by default via -fvisibility-default=hidden and then explicit members participate in dynamic linkage by means of the LLVM_ABI attribute which expands to __attribute__((__visibility__("default"))) on ELFish (and MachO) targets.

On Linux, there is the possibility of further optimization by means of having LLVM_ABI expand to __attribute__((__visibility__("protected"))) which permits the symbol to participate in global dynamic linkage, but prevents interpositioning, allowing more efficient code generation (and avoids the need for the use of the GOT within the defining module).

anthonyhatran pushed a commit to anthonyhatran/llvm-project that referenced this pull request Jun 26, 2025
…bute (llvm#145653)

I don't see any reason this shouldn't be allowed. AFAICT this is only
disabled due to the heuristics used to determine whether it makes sense
to allow the use of an attribute with `#pragma clang attribute`.

This allows libc++ to drop `_LIBCPP_HIDE_FROM_ABI` in a lot of places,
making the library significantly easier to read.
rlavaee pushed a commit to rlavaee/llvm-project that referenced this pull request Jul 1, 2025
…bute (llvm#145653)

I don't see any reason this shouldn't be allowed. AFAICT this is only
disabled due to the heuristics used to determine whether it makes sense
to allow the use of an attribute with `#pragma clang attribute`.

This allows libc++ to drop `_LIBCPP_HIDE_FROM_ABI` in a lot of places,
making the library significantly easier to read.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants