Skip to content

Commit faf879a

Browse files
authored
[clang][lex] Introduce new single-module-parse mode (llvm#135813)
This PR introduces new single-module preprocessing mode. It is very similar to single-file-parse mode, but has the following differences: * Single-file mode skips over all inclusion directives, while the single-module mode skips only over import directives and does resolve textual inclusion directives. * Single-file mode enters all branches of a conditional directive with an undefined identifier, while the single-module enters none of them. This will be used from the dependency scanner to quickly discover a subset of modular dependencies of a TU/module. The dependencies aren't being imported in this mode, but the file-inclusion preprocessor callback does get invoked.
1 parent 1fbf33c commit faf879a

File tree

5 files changed

+134
-9
lines changed

5 files changed

+134
-9
lines changed

clang/include/clang/Lex/PreprocessorOptions.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,13 @@ class PreprocessorOptions {
152152
/// that the client can get the maximum amount of information from the parser.
153153
bool SingleFileParseMode = false;
154154

155+
/// When enabled, preprocessor is in a mode for parsing a single module only.
156+
///
157+
/// Disables imports of other modules and if there are any unresolved
158+
/// identifiers in preprocessor directive conditions it causes all blocks to
159+
/// be skipped so that the client can get a strict subset of the contents.
160+
bool SingleModuleParseMode = false;
161+
155162
/// When enabled, the preprocessor will construct editor placeholder tokens.
156163
bool LexEditorPlaceholders = true;
157164

clang/include/clang/Options/Options.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3482,6 +3482,10 @@ def fno_modules_prune_non_affecting_module_map_files :
34823482
Group<f_Group>, Flags<[]>, Visibility<[CC1Option]>,
34833483
MarshallingInfoNegativeFlag<HeaderSearchOpts<"ModulesPruneNonAffectingModuleMaps">>,
34843484
HelpText<"Do not prune non-affecting module map files when writing module files">;
3485+
def fmodules_single_module_parse_mode :
3486+
Flag<["-"], "fmodules-single-module-parse-mode">,
3487+
Group<f_Group>, Flags<[]>, Visibility<[CC1Option]>,
3488+
MarshallingInfoFlag<PreprocessorOpts<"SingleModuleParseMode">>;
34853489

34863490
def fincremental_extensions :
34873491
Flag<["-"], "fincremental-extensions">,

clang/lib/Lex/PPDirectives.cpp

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2094,10 +2094,11 @@ void Preprocessor::HandleIncludeDirective(SourceLocation HashLoc,
20942094
return;
20952095

20962096
if (FilenameTok.isNot(tok::header_name)) {
2097-
if (FilenameTok.is(tok::identifier) && PPOpts.SingleFileParseMode) {
2097+
if (FilenameTok.is(tok::identifier) &&
2098+
(PPOpts.SingleFileParseMode || PPOpts.SingleModuleParseMode)) {
20982099
// If we saw #include IDENTIFIER and lexing didn't turn in into a header
2099-
// name, it was undefined. In 'single-file-parse' mode, just skip the
2100-
// directive without emitting diagnostics - the identifier might be
2100+
// name, it was undefined. In 'single-{file,module}-parse' mode, just skip
2101+
// the directive without emitting diagnostics - the identifier might be
21012102
// normally defined in previously-skipped include directive.
21022103
DiscardUntilEndOfDirective();
21032104
return;
@@ -2405,10 +2406,15 @@ Preprocessor::ImportAction Preprocessor::HandleHeaderIncludeOrImport(
24052406
(getLangOpts().CPlusPlusModules || getLangOpts().Modules) &&
24062407
ModuleToImport && !ModuleToImport->isHeaderUnit();
24072408

2409+
if (MaybeTranslateInclude && (UsableHeaderUnit || UsableClangHeaderModule) &&
2410+
PPOpts.SingleModuleParseMode) {
2411+
Action = IncludeLimitReached;
2412+
}
24082413
// Determine whether we should try to import the module for this #include, if
24092414
// there is one. Don't do so if precompiled module support is disabled or we
24102415
// are processing this module textually (because we're building the module).
2411-
if (MaybeTranslateInclude && (UsableHeaderUnit || UsableClangHeaderModule)) {
2416+
else if (MaybeTranslateInclude &&
2417+
(UsableHeaderUnit || UsableClangHeaderModule)) {
24122418
// If this include corresponds to a module but that module is
24132419
// unavailable, diagnose the situation and bail out.
24142420
// FIXME: Remove this; loadModule does the same check (but produces
@@ -3443,6 +3449,13 @@ void Preprocessor::HandleIfdefDirective(Token &Result,
34433449
CurPPLexer->pushConditionalLevel(DirectiveTok.getLocation(),
34443450
/*wasskip*/false, /*foundnonskip*/false,
34453451
/*foundelse*/false);
3452+
} else if (PPOpts.SingleModuleParseMode && !MI) {
3453+
// In 'single-module-parse mode' undefined identifiers trigger skipping of
3454+
// all the directive blocks. We lie here and set FoundNonSkipPortion so that
3455+
// even any \#else blocks get skipped.
3456+
SkipExcludedConditionalBlock(
3457+
HashToken.getLocation(), DirectiveTok.getLocation(),
3458+
/*FoundNonSkipPortion=*/true, /*FoundElse=*/false);
34463459
} else if (!MI == isIfndef || RetainExcludedCB) {
34473460
// Yes, remember that we are inside a conditional, then lex the next token.
34483461
CurPPLexer->pushConditionalLevel(DirectiveTok.getLocation(),
@@ -3497,6 +3510,13 @@ void Preprocessor::HandleIfDirective(Token &IfToken,
34973510
// the directive blocks.
34983511
CurPPLexer->pushConditionalLevel(IfToken.getLocation(), /*wasskip*/false,
34993512
/*foundnonskip*/false, /*foundelse*/false);
3513+
} else if (PPOpts.SingleModuleParseMode && DER.IncludedUndefinedIds) {
3514+
// In 'single-module-parse mode' undefined identifiers trigger skipping of
3515+
// all the directive blocks. We lie here and set FoundNonSkipPortion so that
3516+
// even any \#else blocks get skipped.
3517+
SkipExcludedConditionalBlock(HashToken.getLocation(), IfToken.getLocation(),
3518+
/*FoundNonSkipPortion=*/true,
3519+
/*FoundElse=*/false);
35003520
} else if (ConditionalTrue || RetainExcludedCB) {
35013521
// Yes, remember that we are inside a conditional, then lex the next token.
35023522
CurPPLexer->pushConditionalLevel(IfToken.getLocation(), /*wasskip*/false,

clang/lib/Lex/PPExpressions.cpp

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -593,11 +593,13 @@ static bool EvaluateDirectiveSubExpr(PPValue &LHS, unsigned MinPrec,
593593
Token &PeekTok, bool ValueLive,
594594
bool &IncludedUndefinedIds,
595595
Preprocessor &PP) {
596-
if (PP.getPreprocessorOpts().SingleFileParseMode && IncludedUndefinedIds) {
597-
// The single-file parse mode behavior kicks in as soon as single identifier
598-
// is undefined. If we've already seen one, there's no point in continuing
599-
// with the rest of the expression. Besides saving work, this also prevents
600-
// calling undefined function-like macros.
596+
if ((PP.getPreprocessorOpts().SingleFileParseMode ||
597+
PP.getPreprocessorOpts().SingleModuleParseMode) &&
598+
IncludedUndefinedIds) {
599+
// The single-{file,module}-parse mode behavior kicks in as soon as single
600+
// identifier is undefined. If we've already seen one, there's no point in
601+
// continuing with the rest of the expression. Besides saving work, this
602+
// also prevents calling undefined function-like macros.
601603
PP.DiscardUntilEndOfDirective(PeekTok);
602604
return true;
603605
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// RUN: rm -rf %t
2+
// RUN: split-file %s %t
3+
4+
// RUN: %clang_cc1 -fmodules -fmodules-cache-path=%t/cache \
5+
// RUN: -emit-module %t/module.modulemap -fmodule-name=B -o %t/cache/B.pcm \
6+
// RUN: -fmodules-single-module-parse-mode 2>&1 | FileCheck %s
7+
8+
// Modules are not imported.
9+
// CHECK-NOT: A.h:1:2: error: unreachable
10+
11+
// Headers belonging to this module are included.
12+
// CHECK: B2.h:2:2: warning: success
13+
14+
// Non-modular headers are included.
15+
// CHECK: T.h:2:2: warning: success
16+
17+
// No branches are entered for #if UNDEFINED.
18+
// CHECK-NOT: B1.h:6:2: error: unreachable
19+
// CHECK-NOT: B1.h:8:2: error: unreachable
20+
// CHECK-NOT: B1.h:10:2: error: unreachable
21+
22+
// No branches are entered for #ifdef UNDEFINED.
23+
// CHECK-NOT: B1.h:14:2: error: unreachable
24+
// CHECK-NOT: B1.h:16:2: error: unreachable
25+
26+
// No branches are entered for #ifndef UNDEFINED.
27+
// CHECK-NOT: B1.h:20:2: error: unreachable
28+
// CHECK-NOT: B1.h:22:2: error: unreachable
29+
30+
// No error messages are emitted for UNDEFINED_FUNCTION_LIKE().
31+
// CHECK-NOT: B1.h:25:2: error: unreachable
32+
33+
// The correct branch is entered for #if DEFINED.
34+
// CHECK: B1.h:32:3: warning: success
35+
// CHECK-NOT: B1.h:34:3: error: unreachable
36+
// CHECK-NOT: B1.h:36:3: error: unreachable
37+
38+
// Headers belonging to this module are included.
39+
// CHECK: B2.h:2:2: warning: success
40+
41+
//--- module.modulemap
42+
module A { header "A.h" }
43+
module B {
44+
header "B1.h"
45+
header "B2.h"
46+
}
47+
//--- A.h
48+
#error unreachable
49+
//--- B1.h
50+
#include "A.h"
51+
#include "B2.h"
52+
#include "T.h"
53+
54+
#if UNDEFINED
55+
# error unreachable
56+
#elif UNDEFINED2
57+
# error unreachable
58+
#else
59+
# error unreachable
60+
#endif
61+
62+
#ifdef UNDEFINED
63+
# error unreachable
64+
#else
65+
# error unreachable
66+
#endif
67+
68+
#ifndef UNDEFINED
69+
# error unreachable
70+
#else
71+
# error unreachable
72+
#endif
73+
74+
#if UNDEFINED_FUNCTION_LIKE()
75+
#endif
76+
77+
#define DEFINED_1 1
78+
#define DEFINED_2 1
79+
80+
#if DEFINED_1
81+
# warning success
82+
#elif DEFINED_2
83+
# error unreachable
84+
#else
85+
# error unreachable
86+
#endif
87+
//--- B2.h
88+
// Headers belonging to this module are included.
89+
#warning success
90+
//--- T.h
91+
// Non-modular headers are included.
92+
#warning success

0 commit comments

Comments
 (0)