Skip to content

[Perf][SwiftParser] Record lookahead ranges only for a parse that hands them on - #3447

Draft
rintaro wants to merge 1 commit into
swiftlang:mainfrom
rintaro:perf-parser-16-lookahead-ranges
Draft

rintaro wants to merge 1 commit into
swiftlang:mainfrom
rintaro:perf-parser-16-lookahead-ranges

Conversation

@rintaro

@rintaro rintaro commented Sep 18, 2026

Copy link
Copy Markdown
Member

Every parse used to register each node that might later be reused in a dictionary keyed by node identity, which is a hash insertion per node. In Parser.parse(source:), nothing reads the dictionary and it was wasted.

Parser now takes collectsLookaheadRanges. The parseIncrementally entry points, which return the ranges, pass true; the entry points that return only a tree leave it false. A parse given a parseTransition records regardless of what the caller asked, because what it hands on is what the next reparse decides reuse from, and IncrementalParseLookup will not reuse a node it has no range for — so a caller driving Parser directly would otherwise lose reuse for everything that parse produced, with a correct tree and no diagnostic to show for it.

A parse that does collect reserves the table against the source's byte count first, since a node is registered roughly every 90 bytes and the table would otherwise grow and rehash through the parse; an incremental parse skips the reserve, because the table it inherits replaces it.

Measured against main(1f995c7), 2.5-2.9% instruction reduction, but 0.1% for deliberately corrupted sample source, which spends its parse recovering rather than registering nodes.

One behavior change: Parser.lookaheadRanges is public internal(set), and a caller driving Parser directly for a full parse now finds it empty unless it passes collectsLookaheadRanges: true. An incremental parse is not affected.

@rintaro

rintaro commented Sep 18, 2026

Copy link
Copy Markdown
Member Author

@swift-ci Please test

@rintaro
rintaro marked this pull request as draft September 22, 2026 05:42
@rintaro
rintaro force-pushed the perf-parser-16-lookahead-ranges branch from f7d88c5 to 5cd2106 Compare September 23, 2026 03:13
@rintaro

rintaro commented Sep 23, 2026

Copy link
Copy Markdown
Member Author

@swift-ci Please test

@rintaro

rintaro commented Sep 23, 2026

Copy link
Copy Markdown
Member Author

@swift-ci Please test Windows

@rintaro
rintaro force-pushed the perf-parser-16-lookahead-ranges branch from 5cd2106 to 832894e Compare September 23, 2026 03:22
@rintaro

rintaro commented Sep 23, 2026

Copy link
Copy Markdown
Member Author

@swift-ci Please test

@rintaro

rintaro commented Sep 23, 2026

Copy link
Copy Markdown
Member Author

@swift-ci Please test Windows

1 similar comment
@rintaro

rintaro commented Oct 1, 2026

Copy link
Copy Markdown
Member Author

@swift-ci Please test Windows

Every parse records how far it looked ahead for each node it might
later reuse, in a dictionary in the parsing arena keyed by node
identity, which is a hash insertion per node. Only an incremental
reparse reads those lengths, so a parse that is not part of one does
the work for nothing.

`Parser` takes `collectsLookaheadRanges`, which defaults to true so that
a caller holding a `Parser` of its own keeps recording. What opts out is
`Parser.parse(source:)` and its buffer sibling, which are not entry
points for incremental parsing. A tree they return can still be handed
to `IncrementalParseTransition(previousTree:edits:lookaheadRanges:)`,
but none of its nodes will be reused. A parse handed a `parseTransition`
records whatever it was asked, since the reparse after it decides reuse
from what it records.

The default matters more than it looks. `IncrementalParseLookup` never
reuses a node it has no recorded length for, so a parse that records
none yields a correct tree, no diagnostic, and no reuse at all
downstream. `SyntaxTreeManager` in sourcekit-lsp drives a chain of
`Parser`s directly, since `parseIncrementally` takes no language
features, and with the flag defaulted the other way nothing its first
parse produced would ever be reusable.
`testLookaheadRangesAreRecordedForAParserDrivenDirectly` is that chain
in miniature.

A full parse that records reserves the arena's table against the byte
count first, since a node is registered roughly every 90 bytes of
source and the table would otherwise grow and rehash through the parse.
@rintaro
rintaro force-pushed the perf-parser-16-lookahead-ranges branch from 832894e to 3d58210 Compare October 8, 2026 23:28
@rintaro

rintaro commented Oct 8, 2026

Copy link
Copy Markdown
Member Author

@swift-ci Please test

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant