This is an archive of the discontinued LLVM Phabricator instance.

[MSVC compatibility][DLLEXPORT/DLLIMPORT] Allow dllexport/dllimport for local classes
AbandonedPublic

Authored by wolfgangp on Mar 3 2023, 1:17 PM.

Details

Summary

This is intended to address issue 56068.

MSVC seems to allow dllexport/dllimport on local classes. It also allows local classes that derive from dllexported/dllimported template classes. The resulting classes get external linkage. The utility of this is questionable, but clang should do the same in Microsoft mode.

Diff Detail

Event Timeline

wolfgangp created this revision.Mar 3 2023, 1:17 PM
Herald added a project: Restricted Project. · View Herald TranscriptMar 3 2023, 1:17 PM
wolfgangp requested review of this revision.Mar 3 2023, 1:17 PM
wolfgangp added a reviewer: probinson.
probinson added a project: Restricted Project.Mar 3 2023, 1:43 PM
probinson added a subscriber: cfe-commits.

I've looked at this but I'd like someone more in tune with MSVC behavior to review as well.

hans added a comment.Mar 6 2023, 5:39 AM

Interesting! Do you have an example where this (local dllexport/import classes) comes up in practice?

MSVC will also allow the following:

namespace {
struct __declspec(dllexport) T {
    void foo();
};
}

whereas Clang will not, and I think Clang is making the right call there.

clang/lib/AST/Decl.cpp
492

We usually check ASTContext's getTargetInfo().getCXXABI().isMicrosoft()

Interesting! Do you have an example where this (local dllexport/import classes) comes up in practice?

A customer complained about the following code (I'm obscuring the class names) compiling with MSVC but
rejected by clang:

template <class T>
class __declspec(dllimport) A
{
};

void func()
{
  // local class with internal linkage
  class B: public A<B>
  {
  };
}

The code derives from various instantiations of A in several contexts and it's causing extra work for the customer to do something different in a local context.
While I do agree that clang has it right I think this is more a case of bug-compatibility with MSVC.

MSVC will also allow the following:

namespace {
struct __declspec(dllexport) T {
    void foo();
};
}

whereas Clang will not, and I think Clang is making the right call there.

Again, I do think clang makes the right call in all of these cases, but it's about MSVC compatibility, for better or worse.
I did also look at the anonymous namespace case above, but that's a bit more complicated, so I didn't address it this time.

clang/lib/AST/Decl.cpp
492

That would not work for Playstation, since it's not following the Microsoft ABI. The call to shouldDLLImport...(), checks the triple (it includes Windows Itanium, Windows MSVC and PS).
Perhaps we need a less obscure predicate to indicate source compatibility with MSVC as opposed to MS ABI compatibility.

+mstorsjo for thoughts about Windows code, even if this might not apply to mingw.

A customer complained about the following code (I'm obscuring the class names) compiling with MSVC but
rejected by clang:

template <class T>
class __declspec(dllimport) A
{
};

void func()
{
  // local class with internal linkage
  class B: public A<B>
  {
  };
}

Oh I see, it's not intentionally trying to dllimport/export a local class, the problem is really dllimport/exporting a class template instantiated with a local class as template argument.

The problem can be hit without inheritance too:

template <typename> struct __declspec(dllimport) Base { };

void f() {
  struct Local { };
  Base<Local> x;
}

error: 'Base<Local>' must have external linkage when declared 'dllimport'

It still seems like the export/import there is an accident since Base<f::Local> can't really be referenced from outside the file anyway.

Perhaps rather than giving Base<f::Local> external linkage to allow it to be imported/exported, the better fix would be to drop its dllimport/export attribute when instantiated with a local type?

It still seems like the export/import there is an accident since Base<f::Local> can't really be referenced from outside the file anyway.

Perhaps rather than giving Base<f::Local> external linkage to allow it to be imported/exported, the better fix would be to drop its dllimport/export attribute when instantiated with a local type?

I like it. However, we would then not match MSVC's behavior in generating external definitions for the class (useless as they are). I suppose that's OK.

But on another note, would we then abandon matching MSVC on the following?

void func() {
  class __declspec(dllexport) A {
  };
}

This is currently accepted by MSVC, though arguably much less defensible than the above example.

hans added a comment.Mar 8 2023, 12:15 PM

It still seems like the export/import there is an accident since Base<f::Local> can't really be referenced from outside the file anyway.

Perhaps rather than giving Base<f::Local> external linkage to allow it to be imported/exported, the better fix would be to drop its dllimport/export attribute when instantiated with a local type?

I like it. However, we would then not match MSVC's behavior in generating external definitions for the class (useless as they are). I suppose that's OK.

But on another note, would we then abandon matching MSVC on the following?

void func() {
  class __declspec(dllexport) A {
  };
}

This is currently accepted by MSVC, though arguably much less defensible than the above example.

Right. Unless someone shows a compelling example why it's needed, I think we should continue to reject that.

wolfgangp abandoned this revision.Apr 3 2023, 8:11 AM