Skip to content

Clang-cl has inconsistent behavior compared to MSVC for __declspec(dllexport) template specialization #54717

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

Closed
EAirPeter opened this issue Apr 2, 2022 · 6 comments
Labels
clang-cl `clang-cl` driver. Don't use for other compiler parts

Comments

@EAirPeter
Copy link

A short reproduction of this issue is given below.

// foo.cpp

template<class>
struct Foo
{
    __declspec(dllexport) void Bar();
};

template<>
void Foo<int>::Bar() {}

#include <windows.h>

BOOL WINAPI DllMain(HINSTANCE, DWORD, LPVOID) { return TRUE; }

When compiled with clang-cl foo.cpp /link /dll /out:foo.dll, I get no exported symbols from foo.dll (by running dumpbin /exports foo.dll), which would result in further link errors in our code base.
While there is a ?Bar@?$Foo@H@@QEAAXXZ entry in exports with cl foo.cpp /link /dll /out:foo.dll.

My clang version is 13.0.1.

Hopefully LLVM can get this resolved.

@EAirPeter
Copy link
Author

A known workaround is to place __declspec(dllexport) at the specialization like

template<>
__declspec(dllexport) void Foo<int>::Bar() {}

But IMO, it would be better to have clang-cl be in sync with MSVC's behavior when dealing with MS specific features.

@zmodem
Copy link
Collaborator

zmodem commented Sep 7, 2022

Thanks for the bug!

Slightly trimmed reproducer:

template <typename> struct S {                            
    __declspec(dllexport) void f();
};      
template<> void S<int>::f() {} // MSVC dllexports this; clang-cl does not.

The attribute gets dropped in SemaTemplate.cpp's StripImplicitInstantiation:

/// \brief Strips various properties off an implicit instantiation                                                  
/// that has just been explicitly specialized.                                                                      
static void StripImplicitInstantiation(NamedDecl *D) {                                                              
  D->dropAttr<DLLImportAttr>();                                                                                     
  D->dropAttr<DLLExportAttr>();                                                                                     
                                                                                                                    
  if (FunctionDecl *FD = dyn_cast<FunctionDecl>(D))                                                                 
    FD->setInlineSpecified(false);                                                                                  
}

and we have tests which expect that behaviour in clang/test/CodeGenCXX/dllexport-members.cpp, template<> void MemFunTmpl::exportedNormal<ExplicitSpec_NotExported>() {}.

The tests go back a long time though, to 755a36f. Maybe MSVC has changed since then, or maybe the tests didn't get it quite right.

@zmodem
Copy link
Collaborator

zmodem commented Sep 7, 2022

The tests go back a long time though, to 755a36f. Maybe MSVC has changed since then, or maybe the tests didn't get it quite right.

Testing with MSVC 18.00.21005.1 which is from that era, the function still got exported.

I think the tests just happen to reflect how Clang behaved at the time, i.e. it dropped all attributes for explicit specializations, and then e497438 kept that behavior for dllexport because of the tests.

@zmodem
Copy link
Collaborator

zmodem commented Sep 7, 2022

We should also consider the dllimport case.

clang-cl accepts this:

template <typename> struct S {
    __declspec(dllimport) void f();
};
template<> void S<int>::f() {}

void g(S<int> *s) {
  s->f();
}

But MSVC does not:

a.cc(4) : error C2491: 'S<int>::f' : definition of dllimport function not allowed

It works if we make it a declaration though:

template <typename> struct S {
    __declspec(dllimport) void f();
};
template<> void S<int>::f();

void g(S<int> *s) {
  s->f();
}

MSVC accepts that and treats the function as dllimport; clang-cl accepts it but does not treat it as dllimport.

@zmodem
Copy link
Collaborator

zmodem commented Sep 27, 2022

I'm still working on this. The dllimport side is a bit subtle. Also we need to differentiate between

template <typename> struct S {                            
    __declspec(dllexport) void f();
};      
template<> void S<int>::f() {} // MSVC dllexports this.

struct T {
  template <typename> __declspec(dllexport) void g();
};
template<> void T::g<int>() {} // MSVC does not export this.

Furthermore, I ran into some old bugs while digging around here, #21780 and #20510. Maybe we can fix those at the same time, or we should at least not make them worse.

@zmodem
Copy link
Collaborator

zmodem commented Oct 4, 2022

The test case above is more illustrative than I thought. The latter case is what clang's tests covered. The former case was not covered by tests, and was handled incorrectly.

Patch: https://reviews.llvm.org/D135154

zmodem added a commit that referenced this issue Oct 7, 2022
…class template member functions

Previously we were stripping these normally inherited attributes during
explicit specialization. However for class template member functions
(but not function templates), MSVC keeps the attribute.

This makes Clang match that behavior, and fixes GitHub issue #54717

Differential revision: https://reviews.llvm.org/D135154
@zmodem zmodem closed this as completed Oct 7, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang-cl `clang-cl` driver. Don't use for other compiler parts
Projects
None yet
Development

No branches or pull requests

3 participants