This is an archive of the discontinued LLVM Phabricator instance.

Instantiate static constexpr data members on MS ABI.
Needs ReviewPublic

Authored by zoecarver on Mar 18 2021, 3:49 PM.

Details

Reviewers
rsmith
Summary

Because MSVC will not treat the definition of a static data member as part of the declaration, it will not get instantiated. This means Clang won't diagnose instantiation errors until the data member itself is used.

Note: this only happens for constexpr data members because they are the only ones that are marked as implicitly inline and therefore not part of the declaration.

This patch makes Clang instantiate those data members when the class is instantiated. This matches the behavior for other targets, the behavior of MSVC, and is more consistent with the standard.

Refs: https://llvm.org/PR49618
Refs: https://github.com/apple/swift/pull/35962

Diff Detail

Event Timeline

zoecarver requested review of this revision.Mar 18 2021, 3:49 PM
zoecarver created this revision.
Herald added a project: Restricted Project. · View Herald TranscriptMar 18 2021, 3:49 PM
Herald added a subscriber: cfe-commits. · View Herald Transcript
zoecarver edited the summary of this revision. (Show Details)Mar 18 2021, 3:51 PM

So, as I mentioned in the description of this patch, the reason that these members are treated as definitions is that Clang implicitly marks them as inline when targeting MS. This is sort of interesting a) because it only applies to constexpr vars, and b) because this also happens on all platforms for std=c++17. This means that the following program will compile successfully with std=c++17:

template <class T>
struct GetTypeValue {
  static constexpr const bool value = T::value;
};

using Invalid = GetTypeValue<int>;

However, if the constexpr is removed, it will fail to compile. Conversely, after this patch is applied, that will not happen on Windows (it will fail to compile regardless of the targeted standard or constexprness). I think it would make sense to "fix" this for std=c++17 as well, but I'm not sure if that's in line with the standard.

So, as I mentioned in the description of this patch, the reason that these members are treated as definitions is that Clang implicitly marks them as inline when targeting MS. This is sort of interesting a) because it only applies to constexpr vars, and b) because this also happens on all platforms for std=c++17. This means that the following program will compile successfully with std=c++17:

template <class T>
struct GetTypeValue {
  static constexpr const bool value = T::value;
};

using Invalid = GetTypeValue<int>;

(I think you're missing something here to trigger the instantiation of the class, such as Invalid x;.)

However, if the constexpr is removed, it will fail to compile. Conversely, after this patch is applied, that will not happen on Windows (it will fail to compile regardless of the targeted standard or constexprness). I think it would make sense to "fix" this for std=c++17 as well, but I'm not sure if that's in line with the standard.

The above example is valid in C++17 onwards, because in C++17 onwards, static constexpr data members are implicitly inline, and the delayed instantiation behavior here is correct for inline static data members. We'll need to distinguish here between the case where the variable is implicitly inline solely because of the MS ABI rules and the case where the variable is inline because of the actual language rules (where it's declared either inline or constexpr).

(I think you're missing something here to trigger the instantiation of the class, such as Invalid x;.)

Yes, you're right.

The above example is valid in C++17 onwards, because in C++17 onwards, static constexpr data members are implicitly inline, and the delayed instantiation behavior here is correct for inline static data members. We'll need to distinguish here between the case where the variable is implicitly inline solely because of the MS ABI rules and the case where the variable is inline because of the actual language rules (where it's declared either inline or constexpr).

Hmm. This is interesting. Thanks for explaining. Because this patch isn't going to help solve the Swift interop issue anymore, I don't feel a need to continue with it. However, if you still think it would be a valuable change to make, I'm happy to continue working on it.

I think we'd basically need a condition that says: is-microsoft && less-than-cxx17 && is-constexpr && is-static-data-member.

I think we'd basically need a condition that says: is-microsoft && less-than-cxx17 && is-constexpr && is-static-data-member.

Yes.

Looking back over the history a bit here, https://reviews.llvm.org/D47956 suggests that treating these static constexpr data members as implicitly inline matches the MS behavior. And I don't think I can disprove that: considering https://godbolt.org/z/7z6T3q, it looks like MSVC gets the "don't instantiate the initializer of an inline static data member with the class" rule wrong in general, which (perhaps by chance alone) means that MSVC doesn't have the same accepts-invalid bug that we have, but only because it's being hidden by a rejects-valid bug for the same case!

So. I think the status quo is OK but not great; we accept invalid code, in the name of MSVC compatibility, that MSVC doesn't accept. I don't think following MSVC would be a good thing, as that'd lead to our rejecting valid code (that MSVC also rejects but that we currently accept). If we can split the difference, and eagerly instantiate only in the case where the language rules say the variable is not inline but MSVC says it is inline, that would be an improvement, but it seems awkward as I think we've already lost the relevant information by the point we need to make the decision.

So. I think the status quo is OK but not great; we accept invalid code, in the name of MSVC compatibility, that MSVC doesn't accept. I don't think following MSVC would be a good thing, as that'd lead to our rejecting valid code (that MSVC also rejects but that we currently accept). If we can split the difference, and eagerly instantiate only in the case where the language rules say the variable is not inline but MSVC says it is inline, that would be an improvement, but it seems awkward as I think we've already lost the relevant information by the point we need to make the decision.

I agree. And, I think you're right about the fact that we've unfortunately lost the relevant info at the point. Even though it's a C++17 extension, someone could make value inline explicitly (as is done in your Godbolt) and we'd still error pre-c++17. Based on that, I'm going to close this for now. But if you (or someone else) thinks of another solution, or reason that it would be good to move forward with this, I'd be happy to re-open it and continue to work on it.

Thanks for all the help with this bug and patch.