Skip to content
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

[backport] [C++20] [Modules] Backport the ability to skip ODR checks in GMF #80249

Merged
merged 3 commits into from
Feb 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,12 @@ C++20 Feature Support
This feature is still experimental. Accordingly, ``__cpp_nontype_template_args`` was not updated.
However, its support can be tested with ``__has_extension(cxx_generalized_nttp)``.

- Clang won't perform ODR checks for decls in the global module fragment any
more to ease the implementation and improve the user's using experience.
This follows the MSVC's behavior. Users interested in testing the more strict
behavior can use the flag '-Xclang -fno-skip-odr-check-in-gmf'.
(`#79240 <https://github.com/llvm/llvm-project/issues/79240>`_).

C++23 Feature Support
^^^^^^^^^^^^^^^^^^^^^
- Implemented `P0847R7: Deducing this <https://wg21.link/P0847R7>`_. Some related core issues were also
Expand Down Expand Up @@ -1036,9 +1042,6 @@ Bug Fixes to C++ Support
in different visibility.
Fixes (`#67893 <https://github.com/llvm/llvm-project/issues/67893>`_)

- Fix a false-positive ODR violation for different definitions for `std::align_val_t`.
Fixes (`#76638 <https://github.com/llvm/llvm-project/issues/76638>`_)

- Remove recorded `#pragma once` state for headers included in named modules.
Fixes (`#77995 <https://github.com/llvm/llvm-project/issues/77995>`_)

Expand Down
23 changes: 23 additions & 0 deletions clang/docs/StandardCPlusPlusModules.rst
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,29 @@ Note that **currently** the compiler doesn't consider inconsistent macro definit
Currently Clang would accept the above example. But it may produce surprising results if the
debugging code depends on consistent use of ``NDEBUG`` also in other translation units.

Definitions consistency
^^^^^^^^^^^^^^^^^^^^^^^

The C++ language defines that same declarations in different translation units should have
the same definition, as known as ODR (One Definition Rule). Prior to modules, the translation
units don't dependent on each other and the compiler itself can't perform a strong
ODR violation check. With the introduction of modules, now the compiler have
the chance to perform ODR violations with language semantics across translation units.

However, in the practice, we found the existing ODR checking mechanism is not stable
enough. Many people suffers from the false positive ODR violation diagnostics, AKA,
the compiler are complaining two identical declarations have different definitions
incorrectly. Also the true positive ODR violations are rarely reported.
Also we learned that MSVC don't perform ODR check for declarations in the global module
fragment.

So in order to get better user experience, save the time checking ODR and keep consistent
behavior with MSVC, we disabled the ODR check for the declarations in the global module
fragment by default. Users who want more strict check can still use the
``-Xclang -fno-skip-odr-check-in-gmf`` flag to get the ODR check enabled. It is also
encouraged to report issues if users find false positive ODR violations or false negative ODR
violations with the flag enabled.

ABI Impacts
-----------

Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/Basic/LangOptions.def
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ LANGOPT(MathErrno , 1, 1, "errno in math functions")
BENIGN_LANGOPT(HeinousExtensions , 1, 0, "extensions that we really don't like and may be ripped out at any time")
LANGOPT(Modules , 1, 0, "modules semantics")
COMPATIBLE_LANGOPT(CPlusPlusModules, 1, 0, "C++ modules syntax")
LANGOPT(SkipODRCheckInGMF, 1, 0, "Skip ODR checks for decls in the global module fragment")
LANGOPT(BuiltinHeadersInSystemModules, 1, 0, "builtin headers belong to system modules, and _Builtin_ modules are ignored for cstdlib headers")
BENIGN_ENUM_LANGOPT(CompilingModule, CompilingModuleKind, 3, CMK_None,
"compiling a module interface")
Expand Down
8 changes: 8 additions & 0 deletions clang/include/clang/Driver/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -2985,6 +2985,14 @@ def fmodule_output : Flag<["-"], "fmodule-output">, Flags<[NoXarchOption]>,
Visibility<[ClangOption, CC1Option]>,
HelpText<"Save intermediate module file results when compiling a standard C++ module unit.">;

defm skip_odr_check_in_gmf : BoolOption<"f", "skip-odr-check-in-gmf",
LangOpts<"SkipODRCheckInGMF">, DefaultFalse,
PosFlag<SetTrue, [], [CC1Option],
"Skip ODR checks for decls in the global module fragment.">,
NegFlag<SetFalse, [], [CC1Option],
"Perform ODR checks for decls in the global module fragment.">>,
Group<f_Group>;

def fmodules_prune_interval : Joined<["-"], "fmodules-prune-interval=">, Group<i_Group>,
Visibility<[ClangOption, CC1Option]>, MetaVarName<"<seconds>">,
HelpText<"Specify the interval (in seconds) between attempts to prune the module cache">,
Expand Down
6 changes: 6 additions & 0 deletions clang/include/clang/Serialization/ASTReader.h
Original file line number Diff line number Diff line change
Expand Up @@ -2452,6 +2452,12 @@ class BitsUnpacker {
uint32_t CurrentBitsIndex = ~0;
};

inline bool shouldSkipCheckingODR(const Decl *D) {
return D->getOwningModule() &&
D->getASTContext().getLangOpts().SkipODRCheckInGMF &&
D->getOwningModule()->isExplicitGlobalModule();
}

} // namespace clang

#endif // LLVM_CLANG_SERIALIZATION_ASTREADER_H
49 changes: 1 addition & 48 deletions clang/lib/AST/ODRHash.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -745,55 +745,8 @@ void ODRHash::AddEnumDecl(const EnumDecl *Enum) {
if (Enum->isScoped())
AddBoolean(Enum->isScopedUsingClassTag());

if (Enum->getIntegerTypeSourceInfo()) {
// FIMXE: This allows two enums with different spellings to have the same
// hash.
//
// // mod1.cppm
// module;
// extern "C" {
// typedef unsigned __int64 size_t;
// }
// namespace std {
// using :: size_t;
// }
//
// extern "C++" {
// namespace std {
// enum class align_val_t : std::size_t {};
// }
// }
//
// export module mod1;
// export using std::align_val_t;
//
// // mod2.cppm
// module;
// extern "C" {
// typedef unsigned __int64 size_t;
// }
//
// extern "C++" {
// namespace std {
// enum class align_val_t : size_t {};
// }
// }
//
// export module mod2;
// import mod1;
// export using std::align_val_t;
//
// The above example should be disallowed since it violates
// [basic.def.odr]p14:
//
// Each such definition shall consist of the same sequence of tokens
//
// The definitions of `std::align_val_t` in two module units have different
// spellings but we failed to give an error here.
//
// See https://github.com/llvm/llvm-project/issues/76638 for details.
if (Enum->getIntegerTypeSourceInfo())
AddQualType(Enum->getIntegerType().getCanonicalType());
}

// Filter out sub-Decls which will not be processed in order to get an
// accurate count of Decl's.
Expand Down
4 changes: 4 additions & 0 deletions clang/lib/Driver/ToolChains/Clang.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3942,6 +3942,10 @@ static bool RenderModulesOptions(Compilation &C, const Driver &D,
Args.ClaimAllArgs(options::OPT_fmodules_disable_diagnostic_validation);
}

// FIXME: We provisionally don't check ODR violations for decls in the global
// module fragment.
CmdArgs.push_back("-fskip-odr-check-in-gmf");

// Claim `-fmodule-output` and `-fmodule-output=` to avoid unused warnings.
Args.ClaimAllArgs(options::OPT_fmodule_output);
Args.ClaimAllArgs(options::OPT_fmodule_output_EQ);
Expand Down
3 changes: 3 additions & 0 deletions clang/lib/Serialization/ASTReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9743,6 +9743,9 @@ void ASTReader::finishPendingActions() {

if (!FD->isLateTemplateParsed() &&
!NonConstDefn->isLateTemplateParsed() &&
// We only perform ODR checks for decls not in the explicit
// global module fragment.
!shouldSkipCheckingODR(FD) &&
FD->getODRHash() != NonConstDefn->getODRHash()) {
if (!isa<CXXMethodDecl>(FD)) {
PendingFunctionOdrMergeFailures[FD].push_back(NonConstDefn);
Expand Down
38 changes: 29 additions & 9 deletions clang/lib/Serialization/ASTReaderDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -804,8 +804,10 @@ void ASTDeclReader::VisitEnumDecl(EnumDecl *ED) {
ED->setScopedUsingClassTag(EnumDeclBits.getNextBit());
ED->setFixed(EnumDeclBits.getNextBit());

ED->setHasODRHash(true);
ED->ODRHash = Record.readInt();
if (!shouldSkipCheckingODR(ED)) {
ED->setHasODRHash(true);
ED->ODRHash = Record.readInt();
}

// If this is a definition subject to the ODR, and we already have a
// definition, merge this one into it.
Expand All @@ -827,7 +829,10 @@ void ASTDeclReader::VisitEnumDecl(EnumDecl *ED) {
Reader.MergedDeclContexts.insert(std::make_pair(ED, OldDef));
ED->demoteThisDefinitionToDeclaration();
Reader.mergeDefinitionVisibility(OldDef, ED);
if (OldDef->getODRHash() != ED->getODRHash())
// We don't want to check the ODR hash value for declarations from global
// module fragment.
if (!shouldSkipCheckingODR(ED) &&
OldDef->getODRHash() != ED->getODRHash())
Reader.PendingEnumOdrMergeFailures[OldDef].push_back(ED);
} else {
OldDef = ED;
Expand Down Expand Up @@ -866,6 +871,9 @@ ASTDeclReader::VisitRecordDeclImpl(RecordDecl *RD) {

void ASTDeclReader::VisitRecordDecl(RecordDecl *RD) {
VisitRecordDeclImpl(RD);
// We should only reach here if we're in C/Objective-C. There is no
// global module fragment.
assert(!shouldSkipCheckingODR(RD));
RD->setODRHash(Record.readInt());

// Maintain the invariant of a redeclaration chain containing only
Expand Down Expand Up @@ -1094,8 +1102,10 @@ void ASTDeclReader::VisitFunctionDecl(FunctionDecl *FD) {
if (FD->isExplicitlyDefaulted())
FD->setDefaultLoc(readSourceLocation());

FD->ODRHash = Record.readInt();
FD->setHasODRHash(true);
if (!shouldSkipCheckingODR(FD)) {
FD->ODRHash = Record.readInt();
FD->setHasODRHash(true);
}

if (FD->isDefaulted()) {
if (unsigned NumLookups = Record.readInt()) {
Expand Down Expand Up @@ -1971,9 +1981,12 @@ void ASTDeclReader::ReadCXXDefinitionData(
#include "clang/AST/CXXRecordDeclDefinitionBits.def"
#undef FIELD

// Note: the caller has deserialized the IsLambda bit already.
Data.ODRHash = Record.readInt();
Data.HasODRHash = true;
// We only perform ODR checks for decls not in GMF.
if (!shouldSkipCheckingODR(D)) {
// Note: the caller has deserialized the IsLambda bit already.
Data.ODRHash = Record.readInt();
Data.HasODRHash = true;
}

if (Record.readInt()) {
Reader.DefinitionSource[D] =
Expand Down Expand Up @@ -2134,6 +2147,10 @@ void ASTDeclReader::MergeDefinitionData(
}
}

// We don't want to check ODR for decls in the global module fragment.
if (shouldSkipCheckingODR(MergeDD.Definition))
return;

if (D->getODRHash() != MergeDD.ODRHash) {
DetectedOdrViolation = true;
}
Expand Down Expand Up @@ -3498,11 +3515,14 @@ ASTDeclReader::FindExistingResult ASTDeclReader::findExisting(NamedDecl *D) {
// If this declaration is from a merged context, make a note that we need to
// check that the canonical definition of that context contains the decl.
//
// Note that we don't perform ODR checks for decls from the global module
// fragment.
//
// FIXME: We should do something similar if we merge two definitions of the
// same template specialization into the same CXXRecordDecl.
auto MergedDCIt = Reader.MergedDeclContexts.find(D->getLexicalDeclContext());
if (MergedDCIt != Reader.MergedDeclContexts.end() &&
MergedDCIt->second == D->getDeclContext())
!shouldSkipCheckingODR(D) && MergedDCIt->second == D->getDeclContext())
Reader.PendingOdrMergeChecks.push_back(D);

return FindExistingResult(Reader, D, /*Existing=*/nullptr,
Expand Down
8 changes: 6 additions & 2 deletions clang/lib/Serialization/ASTWriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6022,8 +6022,12 @@ void ASTRecordWriter::AddCXXDefinitionData(const CXXRecordDecl *D) {

Record->push_back(DefinitionBits);

// getODRHash will compute the ODRHash if it has not been previously computed.
Record->push_back(D->getODRHash());
// We only perform ODR checks for decls not in GMF.
if (!shouldSkipCheckingODR(D)) {
// getODRHash will compute the ODRHash if it has not been previously
// computed.
Record->push_back(D->getODRHash());
}

bool ModulesDebugInfo =
Writer->Context->getLangOpts().ModulesDebugInfo && !D->isDependentType();
Expand Down
13 changes: 9 additions & 4 deletions clang/lib/Serialization/ASTWriterDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,9 @@ void ASTDeclWriter::VisitEnumDecl(EnumDecl *D) {
EnumDeclBits.addBit(D->isFixed());
Record.push_back(EnumDeclBits);

Record.push_back(D->getODRHash());
// We only perform ODR checks for decls not in GMF.
if (!shouldSkipCheckingODR(D))
Record.push_back(D->getODRHash());

if (MemberSpecializationInfo *MemberInfo = D->getMemberSpecializationInfo()) {
Record.AddDeclRef(MemberInfo->getInstantiatedFrom());
Expand All @@ -510,7 +512,7 @@ void ASTDeclWriter::VisitEnumDecl(EnumDecl *D) {
!D->isTopLevelDeclInObjCContainer() &&
!CXXRecordDecl::classofKind(D->getKind()) &&
!D->getIntegerTypeSourceInfo() && !D->getMemberSpecializationInfo() &&
!needsAnonymousDeclarationNumber(D) &&
!needsAnonymousDeclarationNumber(D) && !shouldSkipCheckingODR(D) &&
D->getDeclName().getNameKind() == DeclarationName::Identifier)
AbbrevToUse = Writer.getDeclEnumAbbrev();

Expand Down Expand Up @@ -701,7 +703,9 @@ void ASTDeclWriter::VisitFunctionDecl(FunctionDecl *D) {
if (D->isExplicitlyDefaulted())
Record.AddSourceLocation(D->getDefaultLoc());

Record.push_back(D->getODRHash());
// We only perform ODR checks for decls not in GMF.
if (!shouldSkipCheckingODR(D))
Record.push_back(D->getODRHash());

if (D->isDefaulted()) {
if (auto *FDI = D->getDefaultedFunctionInfo()) {
Expand Down Expand Up @@ -1506,7 +1510,8 @@ void ASTDeclWriter::VisitCXXMethodDecl(CXXMethodDecl *D) {
D->getFirstDecl() == D->getMostRecentDecl() && !D->isInvalidDecl() &&
!D->hasAttrs() && !D->isTopLevelDeclInObjCContainer() &&
D->getDeclName().getNameKind() == DeclarationName::Identifier &&
!D->hasExtInfo() && !D->isExplicitlyDefaulted()) {
!shouldSkipCheckingODR(D) && !D->hasExtInfo() &&
!D->isExplicitlyDefaulted()) {
if (D->getTemplatedKind() == FunctionDecl::TK_NonTemplate ||
D->getTemplatedKind() == FunctionDecl::TK_FunctionTemplate ||
D->getTemplatedKind() == FunctionDecl::TK_MemberSpecialization ||
Expand Down
10 changes: 10 additions & 0 deletions clang/test/Driver/modules-skip-odr-check-in-gmf.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// RUN: %clang -std=c++20 -### -c %s 2>&1 | FileCheck %s
// RUN: %clang -std=c++20 -fno-skip-odr-check-in-gmf -### -c %s 2>&1 \
// RUN: | FileCheck %s --check-prefix=UNUSED
// RUN: %clang -std=c++20 -Xclang -fno-skip-odr-check-in-gmf -### -c %s 2>&1 \
// RUN: | FileCheck %s --check-prefix=NO-SKIP

// CHECK: -fskip-odr-check-in-gmf
// UNUSED: warning: argument unused during compilation: '-fno-skip-odr-check-in-gmf'
// UNUSED-NOT: -fno-skip-odr-check-in-gmf
// NO-SKIP: -fskip-odr-check-in-gmf{{.*}}-fno-skip-odr-check-in-gmf
11 changes: 10 additions & 1 deletion clang/test/Modules/concept.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@
// RUN: %clang_cc1 -std=c++20 %t/A.cppm -emit-module-interface -o %t/A.pcm
// RUN: %clang_cc1 -std=c++20 -fprebuilt-module-path=%t -I%t -DDIFFERENT %t/B.cppm -verify
// RUN: %clang_cc1 -std=c++20 -fprebuilt-module-path=%t -I%t %t/B.cppm -verify
//
// Testing the behavior of `-fskip-odr-check-in-gmf`
// RUN: %clang_cc1 -std=c++20 -fskip-odr-check-in-gmf %t/A.cppm -emit-module-interface -o %t/A.pcm
// RUN: %clang_cc1 -std=c++20 -fskip-odr-check-in-gmf -fprebuilt-module-path=%t -I%t \
// RUN: -DDIFFERENT -DSKIP_ODR_CHECK_IN_GMF %t/B.cppm -verify


//--- foo.h
#ifndef FOO_H
Expand Down Expand Up @@ -70,7 +76,10 @@ module;
export module B;
import A;

#ifdef DIFFERENT
#ifdef SKIP_ODR_CHECK_IN_GMF
// [email protected]:* {{call to object of type '__fn' is ambiguous}}
// expected-note@* 1+{{candidate function}}
#elif defined(DIFFERENT)
// [email protected]:41 {{'__fn::operator()' from module 'A.<global>' is not present in definition of '__fn' provided earlier}}
// expected-note@* 1+{{declaration of 'operator()' does not match}}
#else
Expand Down
Loading
Loading