This is an archive of the discontinued LLVM Phabricator instance.

[clang] Fix interaction between dllimport and exclude_from_explicit_instantiation
AbandonedPublic

Authored by ldionne on Jul 19 2023, 8:07 AM.

Details

Summary

When an entity is marked with both dllimport and exclude_from_explicit_instantiation,
the compiler should not assume that the entity will be provided in another TU.

Fixes https://github.com/llvm/llvm-project/issues/40363

Diff Detail

Event Timeline

ldionne created this revision.Jul 19 2023, 8:07 AM
Herald added a project: Restricted Project. · View Herald TranscriptJul 19 2023, 8:07 AM
ldionne requested review of this revision.Jul 19 2023, 8:07 AM
Herald added a project: Restricted Project. · View Herald TranscriptJul 19 2023, 8:07 AM
ldionne added inline comments.Jul 19 2023, 8:10 AM
clang/lib/CodeGen/CodeGenModule.cpp
1515

To be perfectly honest, I searched for DLLImportAttr until I found places that made some amount of sense to exclude when the entity has ExcludeFromExplicitInstantiationAttr, but there might be much deeper consequences to those changes that I don't understand. I'm very naive when it comes to Clang, especially when it comes to codegen. So I recommend really looking over these changes or we might be making things worse.

rnk added a comment.Jul 19 2023, 9:49 AM

Thanks for the patch!

The other similar functionality that exists is /Zc:dllexportInlines-, which we could track down the implementation of to try to share logic, but I'm not sure about that.

I should defer to @hans for a more thorough code review.

clang/lib/AST/ASTContext.cpp
11699

I think this may not be quite right. If it is possible for the EFEI attr to appear on non-template entities with vague linkage, we would still need to import/export them.

11702

I wonder if we have to do something on the other side to prevent dllexport from upgrading linkonce_odr linkage to weak_odr linkage if we later go on to suppress the dllexport storage class.

hans added a comment.Aug 16 2023, 5:50 AM

Apologies for the delay, I'm still catching up with my post-vacation backlog.

(For my notes, this came up in https://reviews.llvm.org/D153709)

I think the root of this bug is how class-level dll attributes get propagated to class members. Here is an example:

template <typename T>
struct __declspec(dllimport) S {
  void __attribute__((exclude_from_explicit_instantiation)) f() {}
};

extern template struct S<int>;

The problem is that when S<int> is explicitly instantiated, the dllimport attribute gets propagated to all its members, assuming all members are also being instantiated -- which may not be true now that exclude_from_explicit_instantiation exists.

I think the right location for the fix is where that inheritance happens:

diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index a7ab0948d01b..12cb2542641a 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -6592,6 +6592,13 @@ void Sema::checkClassLevelDLLAttribute(CXXRecordDecl *Class) {
     if (!VD && !MD)
       continue;
 
+    if ((TSK == TSK_ExplicitInstantiationDeclaration ||
+         TSK == TSK_ExplicitInstantiationDefinition) &&
+        Member->hasAttr<ExcludeFromExplicitInstantiationAttr>()) {
+      // Skip members excluded from instantiation.
+      continue;
+    }
+
     if (MD) {
       // Don't process deleted methods.
       if (MD->isDeleted())

That's also the approach suggested in https://bugs.llvm.org/show_bug.cgi?id=41018#c0 (and it also handles the dllexport case.)

That doesn't handle the second of your test cases though, where dllimport is put on the member directly:

template <typename T>
struct  S {
  void __attribute__((exclude_from_explicit_instantiation)) __declspec(dllimport) f() {}
};

extern template struct S<int>;

void use(S<int> &s) {
  s.f();
}

Now, S<int>::f *is* technically being excluded from explicit instantiation as the attribute says, but when it gets implicitly instantiated it will be dllimport, because it was declared that way.

It's not clear to me how we'd want to handle that. I don't think it comes up in libc++, and I can't think of any code that would want to do that either, really.

https://bugs.llvm.org/show_bug.cgi?id=41018#c0 suggest emitting an error about incompatible attributes in that case. I suppose it could also be a warning, or we could do nothing for now.

rnk added a comment.Aug 16 2023, 10:36 AM

That doesn't handle the second of your test cases though, where dllimport is put on the member directly:
...
It's not clear to me how we'd want to handle that. I don't think it comes up in libc++, and I can't think of any code that would want to do that either, really.

I think this does actually matter for libc++, I think I have seen this pattern:

template <typename T>
struct Foo {
  _LIBCPP_FUNC_VIS _LIBCPP_EXCLUDE_FROM_ABI void method() { }
};

I can't find an instance right now, though. I think it comes up when you want to have a default visibility function, and also keep it out of the libc++ DSO interface.

This is a somewhat weird and contradictory case, though, the attributes directly conflict. I think it would be reasonable to teach SemaDeclAttr to ignore the dllimport attribute if the other attribute is already present.

hans added a comment.Aug 16 2023, 11:38 AM

That doesn't handle the second of your test cases though, where dllimport is put on the member directly:
...
It's not clear to me how we'd want to handle that. I don't think it comes up in libc++, and I can't think of any code that would want to do that either, really.

I think this does actually matter for libc++, I think I have seen this pattern:

template <typename T>
struct Foo {
  _LIBCPP_FUNC_VIS _LIBCPP_EXCLUDE_FROM_ABI void method() { }
};

I can't find an instance right now, though. I think it comes up when you want to have a default visibility function, and also keep it out of the libc++ DSO interface.

I guess the libc++ folks can answer if that's something that could occur.

This is a somewhat weird and contradictory case, though, the attributes directly conflict. I think it would be reasonable to teach SemaDeclAttr to ignore the dllimport attribute if the other attribute is already present.

I think what worries me is at that point, the attribute is no longer about just suppressing explicit instantiation, but suppressing the dll attribute in general, which means perhaps it shouldn't just apply to templates, but also

struct __declspec(dllimport)  S {
  void __attribute__((exclude_from_explicit_instantiation)) f() {}
};

(This is what you mentioned in https://bugs.llvm.org/show_bug.cgi?id=41018#c1)

In one way I suppose it could make sense, but it's also pretty different from what the attribute is today, and I worry about growing its scope and the complexity that often comes with tweaking the behavior of the dll attributes.

ldionne abandoned this revision.Sep 5 2023, 11:50 AM

I think this does actually matter for libc++, I think I have seen this pattern:

template <typename T>
struct Foo {
  _LIBCPP_FUNC_VIS _LIBCPP_EXCLUDE_FROM_ABI void method() { }
};

I can't find an instance right now, though. I think it comes up when you want to have a default visibility function, and also keep it out of the libc++ DSO interface.

I guess the libc++ folks can answer if that's something that could occur.

I can't imagine why we'd want to hide something from the DSO but give it default visibility. It's not impossible that we have some of those, but I can't think of any right now and I'd definitely learn something new if I came across one of those. I believe it might make sense to diagnose when that happens and not try to handle this case.

I came back to this as part of the Github PR transition cleanup. Realistically, I don't believe I will have time to work on this in the near future and I also feel like I am not really the best person to fix this issue considering I am fluent neither with Clang nor with the semantics of DLLs. It'd be really great if someone could pick this up, but until then I'll need to abandon the patch to clear up my review queue, which has gotten completely unrealistic.

hans added a comment.Sep 11 2023, 6:19 AM

Sent my diff along with tests based on Louis's patch as a pull request here: https://github.com/llvm/llvm-project/pull/65961