This is an archive of the discontinued LLVM Phabricator instance.

[Sema] Clone VisibilityAttr for functions in template instantiations
Needs ReviewPublic

Authored by MaskRay on Jun 26 2023, 10:43 PM.

Details

Reviewers
aaron.ballman
efriedma
rjmccall
smeenai
Group Reviewers
Restricted Project
Summary

Fix https://github.com/llvm/llvm-project/issues/16561
Fix https://github.com/llvm/llvm-project/issues/31462

Modify the behavior from 7f90b7d4c29d560b57f6026f886adbf4d7ab4382 (2012) to
clone VisibilityAttr for functions. I think that commit fixed a behavior
but made us more difficult to align with GCC.

The new behavior aligns better with GCC. Specifically, an instantiated
FunctionTemplateDecl/CXXMethodDecl now inherits the original visibility
attribute (test51 and test71 in visibility.cpp).

The cloned VisibilityAttr is set to implicit. This is important for
template void HIDDEN zed<&y>(); to override the original VisibilityAttr (test51),
not getting an err_mismatched_visibility error.

For now, it's important not to clone VisibilityAttr for non-FunctionDecl as that
would break test38 (and change test35 in another way to be incompatible
with GCC).

Diff Detail

Event Timeline

MaskRay created this revision.Jun 26 2023, 10:43 PM
Herald added a project: Restricted Project. · View Herald TranscriptJun 26 2023, 10:43 PM
MaskRay requested review of this revision.Jun 26 2023, 10:43 PM
Herald added a project: Restricted Project. · View Herald TranscriptJun 26 2023, 10:43 PM
Herald added a subscriber: cfe-commits. · View Herald Transcript
MaskRay updated this revision to Diff 534842.Jun 26 2023, 10:48 PM
MaskRay edited the summary of this revision. (Show Details)

remove a misleading comment

Can you write the complete rule we're trying to follow here? Like, if you have a free function template, prioritize attributes in the following order ..., if you have a member function, use the following order ..., etc. I know that's a significant amount of writing, but I don't really have any intuition here about how it should work. So I'm not sure how to review this without a reference. (The closest thing I have to intuition is comparing the rules to the ones for C++ anonymous namespaces, but visibility is a lot more complicated.)

Given how complicated the rules are here, it might be easier to understand if you teach the relevant code in clang/lib/AST/Decl.cpp (getExplicitVisibilityAux, I think?) to explicitly check for attributes on the original template, instead of cloning the attribute.

MaskRay added a comment.EditedJul 6 2023, 7:13 PM

Can you write the complete rule we're trying to follow here? Like, if you have a free function template, prioritize attributes in the following order ..., if you have a member function, use the following order ..., etc. I know that's a significant amount of writing, but I don't really have any intuition here about how it should work. So I'm not sure how to review this without a reference. (The closest thing I have to intuition is comparing the rules to the ones for C++ anonymous namespaces, but visibility is a lot more complicated.)

The rules are quite complex and sorry that I am unable to describe them...

Given how complicated the rules are here, it might be easier to understand if you teach the relevant code in clang/lib/AST/Decl.cpp (getExplicitVisibilityAux, I think?) to explicitly check for attributes on the original template, instead of cloning the attribute.

If we don't clone VisibilityAttr, the best I can come up with is the following patch

--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -375,6 +375,9 @@ static bool shouldConsiderTemplateVisibility(const FunctionDecl *fn,
   if (!specInfo->isExplicitInstantiationOrSpecialization())
     return true;

+  if (specInfo->getTemplate()->getTemplatedDecl()->hasAttr<VisibilityAttr>())
+    return false;
+
   return !fn->hasAttr<VisibilityAttr>();
 }

test71 still fails (while this patch fixes test71). Here is my observation:

As confirmed by -fsyntax-only -Xclang -ast-dump, the explicit instantiation declaration (extern template struct DEFAULT foo<int>;) clones the FunctionTemplateDecl tree (including CXXMethodDecl) without cloning VisibilityAttr. This causes all hasAttr<VisibilityAttr>() code to behave incorrectly like the status quo.
By debugging TemplateDeclInstantiator, I believe the cloned FunctionTemplateDecl has no pointer referencing back to the original template.

I think this VisibilityAttr cloning patch matches GCC's spirit better and will enable simplification of the complex template instantiation/specialization rules.

rjmccall requested changes to this revision.Jul 7 2023, 11:20 AM

We made a decision ten years or so ago to use a slightly different interpretation of the visibility attributes, so differences from GCC are not by themselves unexpected or problematic. In this case, I'm not sure I agree that it's a bug that an attribute on an explicit class template instantiation overrides attributes on member functions in the template pattern. Even if we decide to adopt a new rule here, though, I agree with Eli that this seems like a very heavy-handed way of implementing it; basically every line in this patch seems to be impactful in ways that are hard to anticipate.

This revision now requires changes to proceed.Jul 7 2023, 11:20 AM
MaskRay added a subscriber: erichkeane.EditedJul 7 2023, 12:44 PM

We made a decision ten years or so ago to use a slightly different interpretation of the visibility attributes, so differences from GCC are not by themselves unexpected or problematic.

Sure, I think we should assess every corner case and decide whether it makes sense to match GCC compatibility.
I started the patch by noticing https://github.com/llvm/llvm-project/issues/31462 (D29157) to address a specific libc++ problem, not that I started to match a random GCC behavior.
I have noticed that libcxx/include/__config:592 says

// The inline should be removed once PR32114 is resolved
#      define _LIBCPP_METHOD_TEMPLATE_IMPLICIT_INSTANTIATION_VIS inline _LIBCPP_HIDDEN

(The doc is old stale. This is something I want to improve next.)

In this case, I'm not sure I agree that it's a bug that an attribute on an explicit class template instantiation overrides attributes on member functions in the template pattern.

OK, I think this is where our opinions differ. I think that:

  • (test51)(consensus bugfix?) the visibility attribute in template<foo *z> void DEFAULT zed() should affect template void zed<&x>();
  • (test71)(differed opinion) the visibility attribute in template <class U> HIDDEN U bar(); should override the visibility attribute in extern template struct DEFAULT foo<int>;, like GCC does, as the method attribute is more specific.

Perhaps #libc folks and @erichkeane (who introduced MeaningfulToClassTemplateDefinition) can weigh in.

Even if we decide to adopt a new rule here, though, I agree with Eli that this seems like a very heavy-handed way of implementing it; basically every line in this patch seems to be impactful in ways that are hard to anticipate.

I actually think that the patch makes VisibilityAttr behave in a more normal way as the majority of attributes are cloned by instantiateTemplateAttribute in the tablegen-generated tools/clang/include/clang/Sema/AttrTemplateInstantiate.inc.
However, MeaningfulToClassTemplateDefinition attributes are rare and even few need to think about precedence for class attributes and method attributes.
This does make VisibilityAttr unusual, but I think the complexity resides mainly in the existing code, not the code I added in this patch.
I think that CodeGenCXX/visibility.cpp and a few related visibility tests are good enough to catch almost all cases we'd run into in practice.

I agree that the change to test51 is the right result, yes. The explicit visibility attribute on the template declaration should take precedence over the visibility of the types in the template parameter list, and then the explicit visibility attribute on the variables should take precedence over the visibility of the variable types, and then the instantiation should be a specialization of a default-visibility template with default-visibility arguments. But I think that logic ought to be fixed in getLVFor..., not by changing the basic propagation of attributes.

I actually think that the patch makes VisibilityAttr behave in a more normal way as the majority of attributes are cloned by instantiateTemplateAttribute in the tablegen-generated tools/clang/include/clang/Sema/AttrTemplateInstantiate.inc.

You're not really just cloning it, because making the cloned attribute implicit (i.e. giving it the effect of a visibility pragma instead of an attribute) is a huge difference.

I agree that the change to test51 is the right result, yes. The explicit visibility attribute on the template declaration should take precedence over the visibility of the types in the template parameter list, and then the explicit visibility attribute on the variables should take precedence over the visibility of the variable types, and then the instantiation should be a specialization of a default-visibility template with default-visibility arguments. But I think that logic ought to be fixed in getLVFor..., not by changing the basic propagation of attributes.

I actually think that the patch makes VisibilityAttr behave in a more normal way as the majority of attributes are cloned by instantiateTemplateAttribute in the tablegen-generated tools/clang/include/clang/Sema/AttrTemplateInstantiate.inc.

You're not really just cloning it, because making the cloned attribute implicit (i.e. giving it the effect of a visibility pragma instead of an attribute) is a huge difference.

OK, created D154774 as an alternative. I added more cases to test51 and test71. D154774 will change the tests in the same way that this VisibilityAttr cloning patch changes the tests.

I did not know that implicit VisibilityAttr is for #pragma GCC visibility push(...). I agree that reusing IsImplicit for an instantiated FunctionTemplateDecl/CXXMethodDecl has unclear interaction.

However, I am not fully convinced that avoiding cloning VisibilityAttr is the most clean way forward. That said, since D154774 will have the same effect as far as CodeGenCXX/visibility.cpp is concerned, I am fine not pursing this patch forward.
I wonder whether using another bit that is not IsImplicit will be better.

MaskRay updated this revision to Diff 550963.Aug 16 2023, 7:27 PM

rebase
we may not go with this approach, but update to show the test difference

MaskRay edited the summary of this revision. (Show Details)Nov 12 2023, 11:22 AM