Skip to content

Commit c6c8696

Browse files
ChuanqiXu9tstellar
authored andcommitted
[C++20] [Modules] Introduce -fskip-odr-check-in-gmf (#79959)
Close #79240 Cite the comment from @mizvekov in //github.com//issues/79240: > There are two kinds of bugs / issues relevant here: > > Clang bugs that this change hides > Here we can add a Frontend flag that disables the GMF ODR check, just > so > we can keep tracking, testing and fixing these issues. > The Driver would just always pass that flag. > We could add that flag in this current issue. > Bugs in user code: > I don't think it's worth adding a corresponding Driver flag for > controlling the above Frontend flag, since we intend it's behavior to > become default as we fix the problems, and users interested in testing > the more strict behavior can just use the Frontend flag directly. This patch follows the suggestion: - Introduce the CC1 flag `-fskip-odr-check-in-gmf` which is by default off, so that the every existing test will still be tested with checking ODR violations. - Passing `-fskip-odr-check-in-gmf` in the driver to keep the behavior we intended. - Edit the document to tell the users who are still interested in more strict checks can use `-Xclang -fno-skip-odr-check-in-gmf` to get the existing behavior.
1 parent 70195e0 commit c6c8696

15 files changed

+167
-30
lines changed

clang/docs/ReleaseNotes.rst

+2-1
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,8 @@ C++20 Feature Support
190190

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

196197
C++23 Feature Support

clang/docs/StandardCPlusPlusModules.rst

+23
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,29 @@ Note that **currently** the compiler doesn't consider inconsistent macro definit
457457
Currently Clang would accept the above example. But it may produce surprising results if the
458458
debugging code depends on consistent use of ``NDEBUG`` also in other translation units.
459459

460+
Definitions consistency
461+
^^^^^^^^^^^^^^^^^^^^^^^
462+
463+
The C++ language defines that same declarations in different translation units should have
464+
the same definition, as known as ODR (One Definition Rule). Prior to modules, the translation
465+
units don't dependent on each other and the compiler itself can't perform a strong
466+
ODR violation check. With the introduction of modules, now the compiler have
467+
the chance to perform ODR violations with language semantics across translation units.
468+
469+
However, in the practice, we found the existing ODR checking mechanism is not stable
470+
enough. Many people suffers from the false positive ODR violation diagnostics, AKA,
471+
the compiler are complaining two identical declarations have different definitions
472+
incorrectly. Also the true positive ODR violations are rarely reported.
473+
Also we learned that MSVC don't perform ODR check for declarations in the global module
474+
fragment.
475+
476+
So in order to get better user experience, save the time checking ODR and keep consistent
477+
behavior with MSVC, we disabled the ODR check for the declarations in the global module
478+
fragment by default. Users who want more strict check can still use the
479+
``-Xclang -fno-skip-odr-check-in-gmf`` flag to get the ODR check enabled. It is also
480+
encouraged to report issues if users find false positive ODR violations or false negative ODR
481+
violations with the flag enabled.
482+
460483
ABI Impacts
461484
-----------
462485

clang/include/clang/Basic/LangOptions.def

+1
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ LANGOPT(MathErrno , 1, 1, "errno in math functions")
174174
BENIGN_LANGOPT(HeinousExtensions , 1, 0, "extensions that we really don't like and may be ripped out at any time")
175175
LANGOPT(Modules , 1, 0, "modules semantics")
176176
COMPATIBLE_LANGOPT(CPlusPlusModules, 1, 0, "C++ modules syntax")
177+
LANGOPT(SkipODRCheckInGMF, 1, 0, "Skip ODR checks for decls in the global module fragment")
177178
LANGOPT(BuiltinHeadersInSystemModules, 1, 0, "builtin headers belong to system modules, and _Builtin_ modules are ignored for cstdlib headers")
178179
BENIGN_ENUM_LANGOPT(CompilingModule, CompilingModuleKind, 3, CMK_None,
179180
"compiling a module interface")

clang/include/clang/Driver/Options.td

+8
Original file line numberDiff line numberDiff line change
@@ -2985,6 +2985,14 @@ def fmodule_output : Flag<["-"], "fmodule-output">, Flags<[NoXarchOption]>,
29852985
Visibility<[ClangOption, CC1Option]>,
29862986
HelpText<"Save intermediate module file results when compiling a standard C++ module unit.">;
29872987

2988+
defm skip_odr_check_in_gmf : BoolOption<"f", "skip-odr-check-in-gmf",
2989+
LangOpts<"SkipODRCheckInGMF">, DefaultFalse,
2990+
PosFlag<SetTrue, [], [CC1Option],
2991+
"Skip ODR checks for decls in the global module fragment.">,
2992+
NegFlag<SetFalse, [], [CC1Option],
2993+
"Perform ODR checks for decls in the global module fragment.">>,
2994+
Group<f_Group>;
2995+
29882996
def fmodules_prune_interval : Joined<["-"], "fmodules-prune-interval=">, Group<i_Group>,
29892997
Visibility<[ClangOption, CC1Option]>, MetaVarName<"<seconds>">,
29902998
HelpText<"Specify the interval (in seconds) between attempts to prune the module cache">,

clang/include/clang/Serialization/ASTReader.h

+4-2
Original file line numberDiff line numberDiff line change
@@ -2452,8 +2452,10 @@ class BitsUnpacker {
24522452
uint32_t CurrentBitsIndex = ~0;
24532453
};
24542454

2455-
inline bool isFromExplicitGMF(const Decl *D) {
2456-
return D->getOwningModule() && D->getOwningModule()->isExplicitGlobalModule();
2455+
inline bool shouldSkipCheckingODR(const Decl *D) {
2456+
return D->getOwningModule() &&
2457+
D->getASTContext().getLangOpts().SkipODRCheckInGMF &&
2458+
D->getOwningModule()->isExplicitGlobalModule();
24572459
}
24582460

24592461
} // namespace clang

clang/lib/Driver/ToolChains/Clang.cpp

+4
Original file line numberDiff line numberDiff line change
@@ -3942,6 +3942,10 @@ static bool RenderModulesOptions(Compilation &C, const Driver &D,
39423942
Args.ClaimAllArgs(options::OPT_fmodules_disable_diagnostic_validation);
39433943
}
39443944

3945+
// FIXME: We provisionally don't check ODR violations for decls in the global
3946+
// module fragment.
3947+
CmdArgs.push_back("-fskip-odr-check-in-gmf");
3948+
39453949
// Claim `-fmodule-output` and `-fmodule-output=` to avoid unused warnings.
39463950
Args.ClaimAllArgs(options::OPT_fmodule_output);
39473951
Args.ClaimAllArgs(options::OPT_fmodule_output_EQ);

clang/lib/Serialization/ASTReader.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -9745,7 +9745,7 @@ void ASTReader::finishPendingActions() {
97459745
!NonConstDefn->isLateTemplateParsed() &&
97469746
// We only perform ODR checks for decls not in the explicit
97479747
// global module fragment.
9748-
!isFromExplicitGMF(FD) &&
9748+
!shouldSkipCheckingODR(FD) &&
97499749
FD->getODRHash() != NonConstDefn->getODRHash()) {
97509750
if (!isa<CXXMethodDecl>(FD)) {
97519751
PendingFunctionOdrMergeFailures[FD].push_back(NonConstDefn);

clang/lib/Serialization/ASTReaderDecl.cpp

+9-8
Original file line numberDiff line numberDiff line change
@@ -804,7 +804,7 @@ void ASTDeclReader::VisitEnumDecl(EnumDecl *ED) {
804804
ED->setScopedUsingClassTag(EnumDeclBits.getNextBit());
805805
ED->setFixed(EnumDeclBits.getNextBit());
806806

807-
if (!isFromExplicitGMF(ED)) {
807+
if (!shouldSkipCheckingODR(ED)) {
808808
ED->setHasODRHash(true);
809809
ED->ODRHash = Record.readInt();
810810
}
@@ -831,7 +831,8 @@ void ASTDeclReader::VisitEnumDecl(EnumDecl *ED) {
831831
Reader.mergeDefinitionVisibility(OldDef, ED);
832832
// We don't want to check the ODR hash value for declarations from global
833833
// module fragment.
834-
if (!isFromExplicitGMF(ED) && OldDef->getODRHash() != ED->getODRHash())
834+
if (!shouldSkipCheckingODR(ED) &&
835+
OldDef->getODRHash() != ED->getODRHash())
835836
Reader.PendingEnumOdrMergeFailures[OldDef].push_back(ED);
836837
} else {
837838
OldDef = ED;
@@ -872,7 +873,7 @@ void ASTDeclReader::VisitRecordDecl(RecordDecl *RD) {
872873
VisitRecordDeclImpl(RD);
873874
// We should only reach here if we're in C/Objective-C. There is no
874875
// global module fragment.
875-
assert(!isFromExplicitGMF(RD));
876+
assert(!shouldSkipCheckingODR(RD));
876877
RD->setODRHash(Record.readInt());
877878

878879
// Maintain the invariant of a redeclaration chain containing only
@@ -1101,7 +1102,7 @@ void ASTDeclReader::VisitFunctionDecl(FunctionDecl *FD) {
11011102
if (FD->isExplicitlyDefaulted())
11021103
FD->setDefaultLoc(readSourceLocation());
11031104

1104-
if (!isFromExplicitGMF(FD)) {
1105+
if (!shouldSkipCheckingODR(FD)) {
11051106
FD->ODRHash = Record.readInt();
11061107
FD->setHasODRHash(true);
11071108
}
@@ -1981,7 +1982,7 @@ void ASTDeclReader::ReadCXXDefinitionData(
19811982
#undef FIELD
19821983

19831984
// We only perform ODR checks for decls not in GMF.
1984-
if (!isFromExplicitGMF(D)) {
1985+
if (!shouldSkipCheckingODR(D)) {
19851986
// Note: the caller has deserialized the IsLambda bit already.
19861987
Data.ODRHash = Record.readInt();
19871988
Data.HasODRHash = true;
@@ -2147,7 +2148,7 @@ void ASTDeclReader::MergeDefinitionData(
21472148
}
21482149

21492150
// We don't want to check ODR for decls in the global module fragment.
2150-
if (isFromExplicitGMF(MergeDD.Definition))
2151+
if (shouldSkipCheckingODR(MergeDD.Definition))
21512152
return;
21522153

21532154
if (D->getODRHash() != MergeDD.ODRHash) {
@@ -3520,8 +3521,8 @@ ASTDeclReader::FindExistingResult ASTDeclReader::findExisting(NamedDecl *D) {
35203521
// FIXME: We should do something similar if we merge two definitions of the
35213522
// same template specialization into the same CXXRecordDecl.
35223523
auto MergedDCIt = Reader.MergedDeclContexts.find(D->getLexicalDeclContext());
3523-
if (MergedDCIt != Reader.MergedDeclContexts.end() && !isFromExplicitGMF(D) &&
3524-
MergedDCIt->second == D->getDeclContext())
3524+
if (MergedDCIt != Reader.MergedDeclContexts.end() &&
3525+
!shouldSkipCheckingODR(D) && MergedDCIt->second == D->getDeclContext())
35253526
Reader.PendingOdrMergeChecks.push_back(D);
35263527

35273528
return FindExistingResult(Reader, D, /*Existing=*/nullptr,

clang/lib/Serialization/ASTWriter.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -6023,7 +6023,7 @@ void ASTRecordWriter::AddCXXDefinitionData(const CXXRecordDecl *D) {
60236023
Record->push_back(DefinitionBits);
60246024

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

clang/lib/Serialization/ASTWriterDecl.cpp

+4-4
Original file line numberDiff line numberDiff line change
@@ -494,7 +494,7 @@ void ASTDeclWriter::VisitEnumDecl(EnumDecl *D) {
494494
Record.push_back(EnumDeclBits);
495495

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

500500
if (MemberSpecializationInfo *MemberInfo = D->getMemberSpecializationInfo()) {
@@ -512,7 +512,7 @@ void ASTDeclWriter::VisitEnumDecl(EnumDecl *D) {
512512
!D->isTopLevelDeclInObjCContainer() &&
513513
!CXXRecordDecl::classofKind(D->getKind()) &&
514514
!D->getIntegerTypeSourceInfo() && !D->getMemberSpecializationInfo() &&
515-
!needsAnonymousDeclarationNumber(D) && !isFromExplicitGMF(D) &&
515+
!needsAnonymousDeclarationNumber(D) && !shouldSkipCheckingODR(D) &&
516516
D->getDeclName().getNameKind() == DeclarationName::Identifier)
517517
AbbrevToUse = Writer.getDeclEnumAbbrev();
518518

@@ -704,7 +704,7 @@ void ASTDeclWriter::VisitFunctionDecl(FunctionDecl *D) {
704704
Record.AddSourceLocation(D->getDefaultLoc());
705705

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

710710
if (D->isDefaulted()) {
@@ -1510,7 +1510,7 @@ void ASTDeclWriter::VisitCXXMethodDecl(CXXMethodDecl *D) {
15101510
D->getFirstDecl() == D->getMostRecentDecl() && !D->isInvalidDecl() &&
15111511
!D->hasAttrs() && !D->isTopLevelDeclInObjCContainer() &&
15121512
D->getDeclName().getNameKind() == DeclarationName::Identifier &&
1513-
!isFromExplicitGMF(D) && !D->hasExtInfo() &&
1513+
!shouldSkipCheckingODR(D) && !D->hasExtInfo() &&
15141514
!D->isExplicitlyDefaulted()) {
15151515
if (D->getTemplatedKind() == FunctionDecl::TK_NonTemplate ||
15161516
D->getTemplatedKind() == FunctionDecl::TK_FunctionTemplate ||
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// RUN: %clang -std=c++20 -### -c %s 2>&1 | FileCheck %s
2+
// RUN: %clang -std=c++20 -fno-skip-odr-check-in-gmf -### -c %s 2>&1 \
3+
// RUN: | FileCheck %s --check-prefix=UNUSED
4+
// RUN: %clang -std=c++20 -Xclang -fno-skip-odr-check-in-gmf -### -c %s 2>&1 \
5+
// RUN: | FileCheck %s --check-prefix=NO-SKIP
6+
7+
// CHECK: -fskip-odr-check-in-gmf
8+
// UNUSED: warning: argument unused during compilation: '-fno-skip-odr-check-in-gmf'
9+
// UNUSED-NOT: -fno-skip-odr-check-in-gmf
10+
// NO-SKIP: -fskip-odr-check-in-gmf{{.*}}-fno-skip-odr-check-in-gmf

clang/test/Modules/concept.cppm

+16-7
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@
55
// RUN: %clang_cc1 -std=c++20 %t/A.cppm -emit-module-interface -o %t/A.pcm
66
// RUN: %clang_cc1 -std=c++20 -fprebuilt-module-path=%t -I%t -DDIFFERENT %t/B.cppm -verify
77
// RUN: %clang_cc1 -std=c++20 -fprebuilt-module-path=%t -I%t %t/B.cppm -verify
8+
//
9+
// Testing the behavior of `-fskip-odr-check-in-gmf`
10+
// RUN: %clang_cc1 -std=c++20 -fskip-odr-check-in-gmf %t/A.cppm -emit-module-interface -o %t/A.pcm
11+
// RUN: %clang_cc1 -std=c++20 -fskip-odr-check-in-gmf -fprebuilt-module-path=%t -I%t \
12+
// RUN: -DDIFFERENT -DSKIP_ODR_CHECK_IN_GMF %t/B.cppm -verify
13+
814

915
//--- foo.h
1016
#ifndef FOO_H
@@ -70,6 +76,16 @@ module;
7076
export module B;
7177
import A;
7278

79+
#ifdef SKIP_ODR_CHECK_IN_GMF
80+
// [email protected]:* {{call to object of type '__fn' is ambiguous}}
81+
// expected-note@* 1+{{candidate function}}
82+
#elif defined(DIFFERENT)
83+
// [email protected]:41 {{'__fn::operator()' from module 'A.<global>' is not present in definition of '__fn' provided earlier}}
84+
// expected-note@* 1+{{declaration of 'operator()' does not match}}
85+
#else
86+
// expected-no-diagnostics
87+
#endif
88+
7389
template <class T>
7490
struct U {
7591
auto operator+(U) { return 0; }
@@ -87,10 +103,3 @@ void foo() {
87103

88104
__fn{}(U<int>(), U<int>());
89105
}
90-
91-
#ifdef DIFFERENT
92-
// [email protected]:* {{call to object of type '__fn' is ambiguous}}
93-
// expected-note@* 1+{{candidate function}}
94-
#else
95-
// expected-no-diagnostics
96-
#endif

clang/test/Modules/polluted-operator.cppm

+15-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@
44
//
55
// RUN: %clang_cc1 -std=c++20 -emit-module-interface %t/a.cppm -o %t/a.pcm
66
// RUN: %clang_cc1 -std=c++20 %t/b.cppm -fprebuilt-module-path=%t -emit-module-interface -o %t/b.pcm -verify
7+
//
8+
// Testing the behavior of `-fskip-odr-check-in-gmf`
9+
// RUN: %clang_cc1 -std=c++20 -fskip-odr-check-in-gmf -emit-module-interface %t/a.cppm -o \
10+
// RUN: %t/a.pcm
11+
// RUN: %clang_cc1 -std=c++20 -fskip-odr-check-in-gmf %t/b.cppm -fprebuilt-module-path=%t \
12+
// RUN: -emit-module-interface -DSKIP_ODR_CHECK_IN_GMF -o %t/b.pcm -verify
713

814
//--- foo.h
915

@@ -46,10 +52,16 @@ module;
4652
export module a;
4753

4854
//--- b.cppm
49-
// This is actually an ODR violation. But given https://github.com/llvm/llvm-project/issues/79240,
50-
// we don't count it as an ODR violation any more.
51-
// expected-no-diagnostics
5255
module;
5356
#include "bar.h"
5457
export module b;
5558
import a;
59+
60+
#ifdef SKIP_ODR_CHECK_IN_GMF
61+
// expected-no-diagnostics
62+
#else
63+
// expected-error@* {{has different definitions in different modules; first difference is defined here found data member '_S_copy_ctor' with an initializer}}
64+
// expected-note@* {{but in 'a.<global>' found data member '_S_copy_ctor' with a different initializer}}
65+
// expected-error@* {{from module 'a.<global>' is not present in definition of 'variant<_Types...>' provided earlier}}
66+
// expected-note@* {{declaration of 'swap' does not match}}
67+
#endif

clang/test/Modules/pr76638.cppm

+13-3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@
1010
// RUN: %clang_cc1 -std=c++20 %t/mod4.cppm -fmodule-file=mod3=%t/mod3.pcm \
1111
// RUN: -fsyntax-only -verify
1212

13+
// Testing the behavior of `-fskip-odr-check-in-gmf`
14+
// RUN: %clang_cc1 -std=c++20 %t/mod3.cppm -fskip-odr-check-in-gmf \
15+
// RUN: -emit-module-interface -o %t/mod3.pcm
16+
// RUN: %clang_cc1 -std=c++20 %t/mod4.cppm -fmodule-file=mod3=%t/mod3.pcm \
17+
// RUN: -fskip-odr-check-in-gmf -DSKIP_ODR_CHECK_IN_GMF -fsyntax-only -verify
18+
1319
//--- size_t.h
1420

1521
extern "C" {
@@ -57,13 +63,17 @@ export module mod3;
5763
export using std::align_val_t;
5864

5965
//--- mod4.cppm
60-
// This is actually an ODR violation. But given https://github.com/llvm/llvm-project/issues/79240,
61-
// we don't count it as an ODR violation now.
62-
// expected-no-diagnostics
6366
module;
6467
#include "signed_size_t.h"
6568
#include "csize_t"
6669
#include "align.h"
6770
export module mod4;
6871
import mod3;
6972
export using std::align_val_t;
73+
74+
#ifdef SKIP_ODR_CHECK_IN_GMF
75+
// expected-no-diagnostics
76+
#else
77+
// [email protected]:* {{'std::align_val_t' has different definitions in different modules; defined here first difference is enum with specified type 'size_t' (aka 'int')}}
78+
// [email protected]:* {{but in 'mod3.<global>' found enum with specified type 'size_t' (aka 'unsigned int')}}
79+
#endif
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// RUN: rm -rf %t
2+
// RUN: mkdir -p %t
3+
// RUN: split-file %s %t
4+
//
5+
// Baseline testing to make sure we can detect the ODR violation from the CC1 invocation.
6+
// RUNX: %clang_cc1 -std=c++20 %t/a.cppm -emit-module-interface -o %t/a.pcm
7+
// RUNX: %clang_cc1 -std=c++20 %t/b.cppm -emit-module-interface -o %t/b.pcm
8+
// RUNX: %clang_cc1 -std=c++20 %t/test.cc -fprebuilt-module-path=%t -fsyntax-only -verify
9+
//
10+
// Testing that we can ignore the ODR violation from the driver invocation.
11+
// RUN: %clang -std=c++20 %t/a.cppm --precompile -o %t/a.pcm
12+
// RUN: %clang -std=c++20 %t/b.cppm --precompile -o %t/b.pcm
13+
// RUN: %clang -std=c++20 %t/test.cc -fprebuilt-module-path=%t -fsyntax-only -Xclang -verify \
14+
// RUN: -DIGNORE_ODR_VIOLATION
15+
//
16+
// Testing that the driver can require to check the ODR violation.
17+
// RUN: %clang -std=c++20 -Xclang -fno-skip-odr-check-in-gmf %t/a.cppm --precompile -o %t/a.pcm
18+
// RUN: %clang -std=c++20 -Xclang -fno-skip-odr-check-in-gmf %t/b.cppm --precompile -o %t/b.pcm
19+
// RUN: %clang -std=c++20 -Xclang -fno-skip-odr-check-in-gmf %t/test.cc -fprebuilt-module-path=%t \
20+
// RUN: -fsyntax-only -Xclang -verify
21+
22+
//--- func1.h
23+
bool func(int x, int y) {
24+
return true;
25+
}
26+
27+
//--- func2.h
28+
bool func(int x, int y) {
29+
return false;
30+
}
31+
32+
//--- a.cppm
33+
module;
34+
#include "func1.h"
35+
export module a;
36+
export using ::func;
37+
38+
//--- b.cppm
39+
module;
40+
#include "func2.h"
41+
export module b;
42+
export using ::func;
43+
44+
//--- test.cc
45+
import a;
46+
import b;
47+
bool test() {
48+
return func(1, 2);
49+
}
50+
51+
#ifdef IGNORE_ODR_VIOLATION
52+
// expected-no-diagnostics
53+
#else
54+
// [email protected]:1 {{'func' has different definitions in different modules;}}
55+
// [email protected]:1 {{but in 'a.<global>' found a different body}}
56+
#endif

0 commit comments

Comments
 (0)