Skip to content

Commit 4316a7a

Browse files
committed
Requestify ExportedSourceFile parsing
Avoid parsing the syntax tree up-front, and instead only parse it when required, which happens when either: 1. ASTGen parsing is enabled (currently disabled by default) 2. Round trip checking is enabled for a primary file (enabled by default in a debug build, except when dep scanning or doing an IDE operation) 3. We need to evaluate a macro in that file This change therefore means that we now no longer need to parse the syntax tree for secondary files by default unless we specifically need to evaluate a macro in them (e.g if we need to lookup a member on a decl with an attached macro). And the same for primaries in release builds. rdar://109283847
1 parent 5c589a5 commit 4316a7a

File tree

11 files changed

+262
-105
lines changed

11 files changed

+262
-105
lines changed

include/swift/AST/ParseRequests.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,24 @@ class ParseSourceFileRequest
116116
readDependencySource(const evaluator::DependencyRecorder &) const;
117117
};
118118

119+
/// Parse the ExportedSourceFile for a given SourceFile.
120+
class ExportedSourceFileRequest
121+
: public SimpleRequest<ExportedSourceFileRequest,
122+
void *(const SourceFile *),
123+
RequestFlags::Cached> {
124+
public:
125+
using SimpleRequest::SimpleRequest;
126+
127+
private:
128+
friend SimpleRequest;
129+
130+
void *evaluate(Evaluator &evaluator, const SourceFile *SF) const;
131+
132+
public:
133+
// Cached.
134+
bool isCached() const { return true; }
135+
};
136+
119137
/// Parse the top-level items of a SourceFile.
120138
class ParseTopLevelDeclsRequest
121139
: public SimpleRequest<

include/swift/AST/ParseTypeIDZone.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,6 @@ SWIFT_REQUEST(Parse, ParseSourceFileRequest,
2828
SWIFT_REQUEST(Parse, ParseTopLevelDeclsRequest,
2929
ArrayRef<Decl *>(SourceFile *), Cached,
3030
NoLocationInfo)
31+
SWIFT_REQUEST(Parse, ExportedSourceFileRequest,
32+
void *(const SourceFile *), Cached,
33+
NoLocationInfo)

include/swift/AST/SourceFile.h

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ class SourceFile final : public FileUnit {
5858

5959
public:
6060
/// Flags that direct how the source file is parsed.
61-
enum class ParsingFlags : uint8_t {
61+
enum class ParsingFlags : uint16_t {
6262
/// Whether to disable delayed parsing for nominal type, extension, and
6363
/// function bodies.
6464
///
@@ -91,9 +91,17 @@ class SourceFile final : public FileUnit {
9191
/// files, as they get parsed multiple times.
9292
SuppressWarnings = 1 << 4,
9393

94-
/// Whether to disable the Swift Parser ASTGen
95-
/// e.g. in dependency scanning, where an AST is not needed.
96-
DisableSwiftParserASTGen = 1 << 5,
94+
/// Ensure that the SwiftSyntax tree round trips correctly.
95+
RoundTrip = 1 << 5,
96+
97+
/// Validate the new SwiftSyntax parser diagnostics.
98+
ValidateNewParserDiagnostics = 1 << 6,
99+
100+
/// Enable ASTGen for producing the AST nodes.
101+
EnableASTGen = 1 << 7,
102+
103+
/// Enable ASTGen for producing parser diagnostics.
104+
EnableASTGenDiagnostics = 1 << 8,
97105
};
98106
using ParsingOptions = OptionSet<ParsingFlags>;
99107

@@ -262,6 +270,10 @@ class SourceFile final : public FileUnit {
262270
/// code for it. Note this method returns \c false in WMO.
263271
bool isPrimary() const { return IsPrimary; }
264272

273+
/// Retrieve the \c ExportedSourceFile instance produced by ASTGen, which
274+
/// includes the SourceFileSyntax node corresponding to this source file.
275+
void *getExportedSourceFile() const;
276+
265277
/// The list of local type declarations in the source file.
266278
llvm::SetVector<TypeDecl *> LocalTypeDecls;
267279

@@ -334,10 +346,6 @@ class SourceFile final : public FileUnit {
334346
/// this source file.
335347
llvm::SmallVector<Located<StringRef>, 0> VirtualFilePaths;
336348

337-
/// The \c ExportedSourceFile instance produced by ASTGen, which includes
338-
/// the SourceFileSyntax node corresponding to this source file.
339-
void *exportedSourceFile = nullptr;
340-
341349
/// Returns information about the file paths used for diagnostics and magic
342350
/// identifiers in this source file, including virtual filenames introduced by
343351
/// \c #sourceLocation(file:) declarations.

lib/AST/Module.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3687,6 +3687,14 @@ SourceFile::getDefaultParsingOptions(const LangOptions &langOpts) {
36873687
opts |= ParsingFlags::DisablePoundIfEvaluation;
36883688
if (langOpts.CollectParsedToken)
36893689
opts |= ParsingFlags::CollectParsedTokens;
3690+
if (langOpts.hasFeature(Feature::ParserRoundTrip))
3691+
opts |= ParsingFlags::RoundTrip;
3692+
if (langOpts.hasFeature(Feature::ParserValidation))
3693+
opts |= ParsingFlags::ValidateNewParserDiagnostics;
3694+
if (langOpts.hasFeature(Feature::ParserASTGen))
3695+
opts |= ParsingFlags::EnableASTGen;
3696+
if (langOpts.hasFeature(Feature::ParserDiagnostics))
3697+
opts |= ParsingFlags::EnableASTGenDiagnostics;
36903698
return opts;
36913699
}
36923700

@@ -3762,6 +3770,11 @@ ArrayRef<Decl *> SourceFile::getHoistedDecls() const {
37623770
return Hoisted;
37633771
}
37643772

3773+
void *SourceFile::getExportedSourceFile() const {
3774+
auto &eval = getASTContext().evaluator;
3775+
return evaluateOrDefault(eval, ExportedSourceFileRequest{this}, nullptr);
3776+
}
3777+
37653778
void SourceFile::addDeclWithRuntimeDiscoverableAttrs(ValueDecl *decl) {
37663779
assert(!decl->getRuntimeDiscoverableAttrs().empty());
37673780
DeclsWithRuntimeDiscoverableAttrs.insert(decl);

lib/Frontend/Frontend.cpp

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1513,6 +1513,9 @@ void CompilerInstance::finishTypeChecking() {
15131513

15141514
SourceFile::ParsingOptions
15151515
CompilerInstance::getSourceFileParsingOptions(bool forPrimary) const {
1516+
using ActionType = FrontendOptions::ActionType;
1517+
using ParsingFlags = SourceFile::ParsingFlags;
1518+
15161519
const auto &frontendOpts = Invocation.getFrontendOptions();
15171520
const auto action = frontendOpts.RequestedAction;
15181521

@@ -1521,42 +1524,45 @@ CompilerInstance::getSourceFileParsingOptions(bool forPrimary) const {
15211524
// Generally in a parse-only invocation, we want to disable #if evaluation.
15221525
// However, there are a couple of modes where we need to know which clauses
15231526
// are active.
1524-
if (action != FrontendOptions::ActionType::EmitImportedModules &&
1525-
action != FrontendOptions::ActionType::ScanDependencies) {
1526-
opts |= SourceFile::ParsingFlags::DisablePoundIfEvaluation;
1527+
if (action != ActionType::EmitImportedModules &&
1528+
action != ActionType::ScanDependencies) {
1529+
opts |= ParsingFlags::DisablePoundIfEvaluation;
15271530
}
15281531

15291532
// If we need to dump the parse tree, disable delayed bodies as we want to
15301533
// show everything.
1531-
if (action == FrontendOptions::ActionType::DumpParse)
1532-
opts |= SourceFile::ParsingFlags::DisableDelayedBodies;
1534+
if (action == ActionType::DumpParse)
1535+
opts |= ParsingFlags::DisableDelayedBodies;
15331536
}
15341537

1535-
auto typeOpts = getASTContext().TypeCheckerOpts;
1536-
if (forPrimary || isWholeModuleCompilation()) {
1538+
const auto &typeOpts = getASTContext().TypeCheckerOpts;
1539+
const auto isEffectivelyPrimary = forPrimary || isWholeModuleCompilation();
1540+
if (isEffectivelyPrimary) {
15371541
// Disable delayed body parsing for primaries and in WMO, unless
15381542
// forcefully skipping function bodies
15391543
if (typeOpts.SkipFunctionBodies == FunctionBodySkipping::None)
1540-
opts |= SourceFile::ParsingFlags::DisableDelayedBodies;
1544+
opts |= ParsingFlags::DisableDelayedBodies;
15411545
} else {
15421546
// Suppress parse warnings for non-primaries, as they'll get parsed multiple
15431547
// times.
1544-
opts |= SourceFile::ParsingFlags::SuppressWarnings;
1548+
opts |= ParsingFlags::SuppressWarnings;
15451549
}
15461550

1547-
// Dependency scanning does not require an AST, so disable Swift Parser
1548-
// ASTGen parsing completely.
1549-
if (frontendOpts.RequestedAction ==
1550-
FrontendOptions::ActionType::ScanDependencies)
1551-
opts |= SourceFile::ParsingFlags::DisableSwiftParserASTGen;
1551+
// Turn off round-trip checking for secondary files, and for dependency
1552+
// scanning and IDE inspection.
1553+
if (!isEffectivelyPrimary || SourceMgr.hasIDEInspectionTargetBuffer() ||
1554+
frontendOpts.RequestedAction == ActionType::ScanDependencies) {
1555+
opts -= ParsingFlags::RoundTrip;
1556+
opts -= ParsingFlags::ValidateNewParserDiagnostics;
1557+
}
15521558

15531559
// Enable interface hash computation for primaries or emit-module-separately,
15541560
// but not in WMO, as it's only currently needed for incremental mode.
15551561
if (forPrimary ||
15561562
typeOpts.SkipFunctionBodies ==
15571563
FunctionBodySkipping::NonInlinableWithoutTypes ||
15581564
frontendOpts.ReuseFrontendForMultipleCompilations) {
1559-
opts |= SourceFile::ParsingFlags::EnableInterfaceHash;
1565+
opts |= ParsingFlags::EnableInterfaceHash;
15601566
}
15611567
return opts;
15621568
}

lib/Parse/ParseDecl.cpp

Lines changed: 111 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -209,9 +209,7 @@ extern "C" void swift_ASTGen_buildTopLevelASTNodes(void *sourceFile,
209209
void Parser::parseTopLevelItems(SmallVectorImpl<ASTNode> &items) {
210210
#if SWIFT_SWIFT_PARSER
211211
Optional<DiagnosticTransaction> existingParsingTransaction;
212-
if (!SF.getParsingOptions()
213-
.contains(SourceFile::ParsingFlags::DisableSwiftParserASTGen))
214-
parseSourceFileViaASTGen(items, existingParsingTransaction);
212+
parseSourceFileViaASTGen(items, existingParsingTransaction);
215213
#endif
216214

217215
// Prime the lexer.
@@ -261,92 +259,135 @@ void Parser::parseTopLevelItems(SmallVectorImpl<ASTNode> &items) {
261259
}
262260

263261
#if SWIFT_SWIFT_PARSER
264-
if (!SF.getParsingOptions().contains(
265-
SourceFile::ParsingFlags::DisableSwiftParserASTGen)) {
266-
if (existingParsingTransaction)
267-
existingParsingTransaction->abort();
268-
269-
// Perform round-trip and/or validation checking.
270-
if ((Context.LangOpts.hasFeature(Feature::ParserRoundTrip) ||
271-
Context.LangOpts.hasFeature(Feature::ParserValidation)) &&
272-
SF.exportedSourceFile &&
273-
!SourceMgr.hasIDEInspectionTargetBuffer()) {
274-
if (Context.LangOpts.hasFeature(Feature::ParserRoundTrip) &&
275-
swift_ASTGen_roundTripCheck(SF.exportedSourceFile)) {
276-
SourceLoc loc;
277-
if (auto bufferID = SF.getBufferID()) {
278-
loc = Context.SourceMgr.getLocForBufferStart(*bufferID);
279-
}
280-
diagnose(loc, diag::parser_round_trip_error);
281-
} else if (Context.LangOpts.hasFeature(Feature::ParserValidation) &&
282-
!Context.Diags.hadAnyError() &&
283-
swift_ASTGen_emitParserDiagnostics(
284-
&Context.Diags, SF.exportedSourceFile,
285-
/*emitOnlyErrors=*/true,
286-
/*downgradePlaceholderErrorsToWarnings=*/
287-
Context.LangOpts.Playground ||
288-
Context.LangOpts.WarnOnEditorPlaceholder)) {
289-
// We might have emitted warnings in the C++ parser but no errors, in
290-
// which case we still have `hadAnyError() == false`. To avoid emitting
291-
// the same warnings from SwiftParser, only emit errors from SwiftParser
292-
SourceLoc loc;
293-
if (auto bufferID = SF.getBufferID()) {
262+
if (existingParsingTransaction)
263+
existingParsingTransaction->abort();
264+
265+
using ParsingFlags = SourceFile::ParsingFlags;
266+
const auto parsingOpts = SF.getParsingOptions();
267+
268+
// If we don't need to validate anything, we're done.
269+
if (!parsingOpts.contains(ParsingFlags::RoundTrip) &&
270+
!parsingOpts.contains(ParsingFlags::ValidateNewParserDiagnostics)) {
271+
return;
272+
}
273+
274+
auto *exportedSourceFile = SF.getExportedSourceFile();
275+
if (!exportedSourceFile)
276+
return;
277+
278+
// Perform round-trip and/or validation checking.
279+
if (parsingOpts.contains(ParsingFlags::RoundTrip) &&
280+
swift_ASTGen_roundTripCheck(exportedSourceFile)) {
281+
SourceLoc loc;
282+
if (auto bufferID = SF.getBufferID()) {
283+
loc = Context.SourceMgr.getLocForBufferStart(*bufferID);
284+
}
285+
diagnose(loc, diag::parser_round_trip_error);
286+
return;
287+
}
288+
if (parsingOpts.contains(ParsingFlags::ValidateNewParserDiagnostics) &&
289+
!Context.Diags.hadAnyError()) {
290+
auto hadSyntaxError = swift_ASTGen_emitParserDiagnostics(
291+
&Context.Diags, exportedSourceFile,
292+
/*emitOnlyErrors=*/true,
293+
/*downgradePlaceholderErrorsToWarnings=*/
294+
Context.LangOpts.Playground ||
295+
Context.LangOpts.WarnOnEditorPlaceholder);
296+
if (hadSyntaxError) {
297+
// We might have emitted warnings in the C++ parser but no errors, in
298+
// which case we still have `hadAnyError() == false`. To avoid
299+
// emitting the same warnings from SwiftParser, only emit errors from
300+
// SwiftParser
301+
SourceLoc loc;
302+
if (auto bufferID = SF.getBufferID()) {
294303
loc = Context.SourceMgr.getLocForBufferStart(*bufferID);
295-
}
296-
diagnose(loc, diag::parser_new_parser_errors);
297304
}
305+
diagnose(loc, diag::parser_new_parser_errors);
298306
}
299307
}
300308
#endif
301309
}
302310

311+
void *ExportedSourceFileRequest::evaluate(Evaluator &evaluator,
312+
const SourceFile *SF) const {
313+
// The SwiftSyntax parser doesn't (yet?) handle SIL.
314+
if (SF->Kind == SourceFileKind::SIL)
315+
return nullptr;
316+
317+
#if SWIFT_SWIFT_PARSER
318+
auto &ctx = SF->getASTContext();
319+
auto &SM = ctx.SourceMgr;
320+
321+
auto bufferID = SF->getBufferID();
322+
if (!bufferID)
323+
return nullptr;
324+
325+
StringRef contents = SM.extractText(SM.getRangeForBuffer(*bufferID));
326+
327+
// Parse the source file.
328+
auto exportedSourceFile = swift_ASTGen_parseSourceFile(
329+
contents.begin(), contents.size(),
330+
SF->getParentModule()->getName().str().str().c_str(),
331+
SF->getFilename().str().c_str());
332+
333+
ctx.addCleanup([exportedSourceFile] {
334+
swift_ASTGen_destroySourceFile(exportedSourceFile);
335+
});
336+
return exportedSourceFile;
337+
#else
338+
return nullptr;
339+
#endif
340+
}
341+
303342
void
304343
Parser::parseSourceFileViaASTGen(SmallVectorImpl<ASTNode> &items,
305344
Optional<DiagnosticTransaction> &transaction,
306345
bool suppressDiagnostics) {
307346
#if SWIFT_SWIFT_PARSER
308-
Optional<DiagnosticTransaction> existingParsingTransaction;
309-
if (SF.Kind != SourceFileKind::SIL) {
310-
StringRef contents =
311-
SourceMgr.extractText(SourceMgr.getRangeForBuffer(L->getBufferID()));
312-
313-
// Parse the source file.
314-
auto exportedSourceFile = swift_ASTGen_parseSourceFile(
315-
contents.begin(), contents.size(),
316-
SF.getParentModule()->getName().str().str().c_str(),
317-
SF.getFilename().str().c_str());
318-
SF.exportedSourceFile = exportedSourceFile;
319-
Context.addCleanup([exportedSourceFile] {
320-
swift_ASTGen_destroySourceFile(exportedSourceFile);
321-
});
347+
using ParsingFlags = SourceFile::ParsingFlags;
348+
const auto parsingOpts = SF.getParsingOptions();
322349

323-
// If we're supposed to emit diagnostics from the parser, do so now.
324-
if ((Context.LangOpts.hasFeature(Feature::ParserDiagnostics) ||
325-
Context.LangOpts.hasFeature(Feature::ParserASTGen)) &&
326-
!suppressDiagnostics &&
327-
swift_ASTGen_emitParserDiagnostics(
328-
&Context.Diags, SF.exportedSourceFile, /*emitOnlyErrors=*/false,
329-
/*downgradePlaceholderErrorsToWarnings=*/
330-
Context.LangOpts.Playground ||
331-
Context.LangOpts.WarnOnEditorPlaceholder) &&
332-
Context.Diags.hadAnyError() &&
333-
!Context.LangOpts.hasFeature(Feature::ParserASTGen)) {
350+
// We only need to do parsing if we either have ASTGen enabled, or want the
351+
// new parser diagnostics.
352+
auto needToParse = [&]() {
353+
if (parsingOpts.contains(ParsingFlags::EnableASTGen))
354+
return true;
355+
if (!suppressDiagnostics &&
356+
parsingOpts.contains(ParsingFlags::EnableASTGenDiagnostics)) {
357+
return true;
358+
}
359+
return false;
360+
}();
361+
if (!needToParse)
362+
return;
363+
364+
auto *exportedSourceFile = SF.getExportedSourceFile();
365+
if (!exportedSourceFile)
366+
return;
367+
368+
// If we're supposed to emit diagnostics from the parser, do so now.
369+
if (!suppressDiagnostics) {
370+
const auto &langOpts = Context.LangOpts;
371+
auto hadSyntaxError = swift_ASTGen_emitParserDiagnostics(
372+
&Context.Diags, exportedSourceFile, /*emitOnlyErrors=*/false,
373+
/*downgradePlaceholderErrorsToWarnings=*/langOpts.Playground ||
374+
langOpts.WarnOnEditorPlaceholder);
375+
if (hadSyntaxError && Context.Diags.hadAnyError() &&
376+
!parsingOpts.contains(ParsingFlags::EnableASTGen)) {
334377
// Errors were emitted, and we're still using the C++ parser, so
335378
// disable diagnostics from the C++ parser.
336379
transaction.emplace(Context.Diags);
337380
}
381+
}
338382

339-
// If we want to do ASTGen, do so now.
340-
if (Context.LangOpts.hasFeature(Feature::ParserASTGen)) {
341-
swift_ASTGen_buildTopLevelASTNodes(
342-
exportedSourceFile, CurDeclContext, &Context, &items, appendToVector);
343-
344-
// Spin the C++ parser to the end; we won't be using it.
345-
while (!Tok.is(tok::eof)) {
346-
consumeToken();
347-
}
383+
// If we want to do ASTGen, do so now.
384+
if (parsingOpts.contains(ParsingFlags::EnableASTGen)) {
385+
swift_ASTGen_buildTopLevelASTNodes(exportedSourceFile, CurDeclContext,
386+
&Context, &items, appendToVector);
348387

349-
return;
388+
// Spin the C++ parser to the end; we won't be using it.
389+
while (!Tok.is(tok::eof)) {
390+
consumeToken();
350391
}
351392
}
352393
#endif

0 commit comments

Comments
 (0)