On MSVC, if an implicit instantiation already exists and an explicit
instantiation definition with a DLL attribute is created, the DLL
attribute still takes effect. Make clang match this behavior for
exporting.
Details
Diff Detail
- Build Status
Buildable 1354 Build 1354: arc lint + arc unit
Event Timeline
lib/Sema/SemaTemplate.cpp | ||
---|---|---|
7666 | The fragment doesn't make sense to me. Perhaps needs a bit more context? |
lib/Sema/SemaTemplate.cpp | ||
---|---|---|
7666 | Do you mean the comment specifically? |
On MSVC, if an implicit instantiation already exists and an explicit
instantiation definition with a DLL attribute is created, the DLL
attribute still takes effect. Make clang match this behavior.
This is scary territory, and behaviour I think might be hard for us to match.
What if we already codegenned the implicit instantiation?
Hmm. Under what specific cases would that happen?
I can see odd behavior differences occurring for dllimport. For example, for the C++ source in P7936, if I compile with clang (even with this patch applied), the t.f() call in g gets assembled to a call to ?f@?$s@H@@QAEHXZ. If I hoist the dllimport explicit instantiation definition above g, t.f() instead assembles to __imp_?f@?$s@H@@QAEHXZ. With cl 19.00.24210, t.f() always assembles to __imp_?f@?$s@H@@QAEHXZ, regardless of the placement of the explicit instantiation definition.
I'm having a harder time imagining things going awry for dllexport. I can limit this patch to only dllexport, if that's going to be safer.
To give some context, libc++ uses extern templates extensively, and I ran into an issue with dllexport explicit locale template instantiations. Some of those instantiations are also implicitly instantiated via sizeof calls. Therefore, all instances which make is called on end up having their dllexport attribute ignored.
We could work around this in libc++ by hoisting the affected instantiations to near the top of the file, but it seemed preferable to make clang match MSVC's behavior instead, at least for dllexport. I don't have any particular interest in making dllimport semantics match MSVC for this specific case.
Actually maybe I was being overly cautius. I think https://reviews.llvm.org/rL225570 might just make this work.
I can see odd behavior differences occurring for dllimport. For example, for the C++ source in P7936, if I compile with clang (even with this patch applied), the t.f() call in g gets assembled to a call to ?f@?$s@H@@QAEHXZ. If I hoist the dllimport explicit instantiation definition above g, t.f() instead assembles to __imp_?f@?$s@H@@QAEHXZ.
Yeah, dllimport is interesting here. If you turn on -O1, it will call the __imp one. Fun times :-)
With cl 19.00.24210, t.f() always assembles to __imp_?f@?$s@H@@QAEHXZ, regardless of the placement of the explicit instantiation definition.
Presumably they parse and analyze the whole translation unit before emitting any code, which makes changing things around later easier for them than for us.
I'm having a harder time imagining things going awry for dllexport. I can limit this patch to only dllexport, if that's going to be safer.
Yeah, I think https://reviews.llvm.org/rL225570 makes this work actually.
To give some context, libc++ uses extern templates extensively, and I ran into an issue with dllexport explicit locale template instantiations. Some of those instantiations are also implicitly instantiated via sizeof calls. Therefore, all instances which make is called on end up having their dllexport attribute ignored.
We could work around this in libc++ by hoisting the affected instantiations to near the top of the file, but it seemed preferable to make clang match MSVC's behavior instead, at least for dllexport. I don't have any particular interest in making dllimport semantics match MSVC for this specific case.
Dont' we need dllimport to work analogously though? When using libc++, don't these template members need to be dllimported? If not, why are they being exported in the first place?
Huh. Weird stuff.
With cl 19.00.24210, t.f() always assembles to __imp_?f@?$s@H@@QAEHXZ, regardless of the placement of the explicit instantiation definition.
Presumably they parse and analyze the whole translation unit before emitting any code, which makes changing things around later easier for them than for us.
I'm having a harder time imagining things going awry for dllexport. I can limit this patch to only dllexport, if that's going to be safer.
Yeah, I think https://reviews.llvm.org/rL225570 makes this work actually.
So is the conclusion that this is probably safe as is, or should I limit it to dllexport specifically?
To give some context, libc++ uses extern templates extensively, and I ran into an issue with dllexport explicit locale template instantiations. Some of those instantiations are also implicitly instantiated via sizeof calls. Therefore, all instances which make is called on end up having their dllexport attribute ignored.
We could work around this in libc++ by hoisting the affected instantiations to near the top of the file, but it seemed preferable to make clang match MSVC's behavior instead, at least for dllexport. I don't have any particular interest in making dllimport semantics match MSVC for this specific case.
Dont' we need dllimport to work analogously though? When using libc++, don't these template members need to be dllimported? If not, why are they being exported in the first place?
Ah. We put dllimport on the explicit template instantiation declarations in the header (all the instances of _LIBCPP_EXTERN_TEMPLATE2 in that file). Those get paired with the dllexport on the corresponding instantiation definitions. If you're compiling the library, the declarations have no DLL attribute, and the definitions have dllexport. If you're referencing the headers from outside the library, the declarations get the dllimport.
I think we should limit it do dllexport if we can't fix the dllimport case.
To give some context, libc++ uses extern templates extensively, and I ran into an issue with dllexport explicit locale template instantiations. Some of those instantiations are also implicitly instantiated via sizeof calls. Therefore, all instances which make is called on end up having their dllexport attribute ignored.
We could work around this in libc++ by hoisting the affected instantiations to near the top of the file, but it seemed preferable to make clang match MSVC's behavior instead, at least for dllexport. I don't have any particular interest in making dllimport semantics match MSVC for this specific case.
Dont' we need dllimport to work analogously though? When using libc++, don't these template members need to be dllimported? If not, why are they being exported in the first place?
Ah. We put dllimport on the explicit template instantiation declarations in the header (all the instances of _LIBCPP_EXTERN_TEMPLATE2 in that file). Those get paired with the dllexport on the corresponding instantiation definitions. If you're compiling the library, the declarations have no DLL attribute, and the definitions have dllexport. If you're referencing the headers from outside the library, the declarations get the dllimport.
lib/Sema/SemaTemplate.cpp | ||
---|---|---|
7667–7670 | Is this the right logic in the case of Old_TSK == TSK_Implicit && TSK == TSK_Explicit? We don't want a behavior change when DLLImport is not involved. A test for that case would be good. |
lib/Sema/SemaTemplate.cpp | ||
---|---|---|
7683–7685 | Please do not do this. The canonical location for comments in LLVM are the comments themselves. We have moved the location of the review system and I can easily believe that it will move yet again. |
lib/Sema/SemaTemplate.cpp | ||
---|---|---|
7667–7670 | I think what Reid is alluding to is regardless of dll attributes, you're adding another OR-clause to this if condition which will cause us to e.g. do Def->setTemplateSpecializationKind below, so something is changing in how we handle implicit instantiation followed by explicit instantiation def in general, not just for dllexport which might not be what we want? |
lib/Sema/SemaTemplate.cpp | ||
---|---|---|
7667–7670 | That's a fair point. I'll modify this to address that. |
General coding style question. Over here, I'm creating a local helper function. However, that helper needs to access member functions of Sema, which is why I made it a private member function right now, which also unfortunately makes the change somewhat non-local (since the header file needs to be modified as well). I can think of two alternatives:
- Define a local helper lambda (which will capture this) instead of a private member function.
- Define a local static helper function and pass the Sema instance as a parameter so that it can call member functions on it.
Would either of those be preferable to what I currently have? I'm pretty new when it comes to llvm/clang changes, so stylistic feedback is greatly appreciated.
Apologies for the delay. I was out last week.
I usually prefer a static helper function and passing the Sema instance if it's possiblem, but that requires the Sema members we need to access to be public. If that's not the case, adding the helper as a member function is fine.
include/clang/Sema/Sema.h | ||
---|---|---|
7494 ↗ | (On Diff #78621) | Nit: I think /// implies \brief, so we don't usually include it. |
7495 ↗ | (On Diff #78621) | I'd suggest making the function name more specific. It obviously doesn't apply to all DLL attributes, but only class templates. Also, the "ActOn" methods in Sema are used as an interface to be called by the parser, so I would avoid that prefix. Naming is hard :-/ Maybe checkDllExportedTemplateSpecialization |
lib/Sema/SemaTemplate.cpp | ||
7439 | This function only applies to dllexport, not dllimport, so it would be good if the name reflected that, and maybe we could also add an assert to check for it. | |
7717 | Why the isWindowsItaniumEnvironment check? I'd expect checking for the MS ABI is sufficient? |
include/clang/Sema/Sema.h | ||
---|---|---|
7495 ↗ | (On Diff #78621) | also it should be in the private: section since it's just a helper. And maybe the "check" part is unnecessary? dllExportClassTemplateSpecialization might work. |
include/clang/Sema/Sema.h | ||
---|---|---|
7494 ↗ | (On Diff #78621) | Cool, will drop. |
7495 ↗ | (On Diff #78621) | Good point about the prefix. I'll do the rename. I intended to make it private; if it's in the wrong part of the header, that's an accident on my part :) |
lib/Sema/SemaTemplate.cpp | ||
7439 | It's called in two places (the refactored original call for explicit instantiation declaration followed by explicit instantiation definition, and my new call for implicit instantiation followed by explicit instantiation definition). The dllexport guarantee only applies to the second one, right? I'll come up with a better name based on your suggestions in the other comment. | |
7717 | windows-itanium in general tries to stick to MSVC semantics for dllexport/import annotations (unlike Cygwin and MinGW which kinda do their own thing). This is consistent with the conditional for the previous case (lines 7691 to 7693 in this diff). |
lib/Sema/SemaTemplate.cpp | ||
---|---|---|
7439 | You're right, my mistake. | |
7717 | Oh I see, this seems to be a new thing, starting with e.g. r284288. Seems fine then, but I'm a little worried that we're adding another variable into the matrix here. IIRC, we key dll attribute behaviour off getCXXABI().isMicrosoft() in lots of places. |
Addressing comments
lib/Sema/SemaTemplate.cpp | ||
---|---|---|
7717 | You're right; the current situation isn't ideal. I can clean that up in a follow-up. |
This function only applies to dllexport, not dllimport, so it would be good if the name reflected that, and maybe we could also add an assert to check for it.