Flat token buffer, part 1: parser cursor, lexer emission, flat mbe transcription, view-backed macro args - #100
Open
xmakro wants to merge 2 commits into
Open
Flat token buffer, part 1: parser cursor, lexer emission, flat mbe transcription, view-backed macro args#100xmakro wants to merge 2 commits into
xmakro wants to merge 2 commits into
Conversation
…ion, view-backed macro args Core slice of the flat token buffer architecture (#57 carries the full stack and per-step history). The parser consumes a pre-flattened token buffer: bump is an index increment, lookahead is direct indexing, snapshots and lazy capture replay share the buffer via refcount bumps, and delimited sequences skip in one step through the open/close match table. The lexer produces the buffer directly through FlatSink, so primary parses never build a token tree. mbe transcription writes expansion output through the same sink and macro invocation arguments are lazy views of the buffer, so expansion parses straight from the buffer with no per-expansion flatten and no per-invocation tree rebuild. Captured tt fragments are buffer slices, spliced into expansion output with a depth and match-table rebase. The A/B matrix for this cut (see the PR description) shows the cursor, lexer, views, and flat transcription are one perf unit: leaving out the views costs +2.05% geomean and leaving out flat transcription costs +0.82% with tt-muncher +10%, while this combination measures -0.63% geomean with no primary regressions. Includes the hardening and regression tests from #57 that belong to these layers: length-prefixed stable hashing, Arc pointer-equality fast paths, the u32::MAX entry guard, lookahead fixes at skipped invisible delimiters, sink-only buffer production, and the TokenStream size and iteration fixes. The nonterminal sink replay (#57 commit 12) is the next slice.
Instead of deleting the now-dead TokenCursor, debug builds keep it as a shadow. Parser::new_from_flat rebuilds the token tree from the flat cursor's own buffer and walks the old cursor alongside; every bump asserts the two cursors yield identical (Token, Spacing) pairs, and parse_token_tree keeps the shadow in lockstep across the skip-to-group-end fast path. A failure means either a cursor-stepping divergence or a buffer whose tree rebuild does not round-trip, so every debug-assertions run differentially validates the flat cursor and the lexer's buffer production against the old implementation on every token of every parse. Release builds compile all of it out; the Parser size assertion is release-only now. The old cursor and this shadow go away in the final slice once the stack has baked. With flat transcription in the tree, the shadow also differentially validates expansion output: the tree it walks is rebuilt from the transcribed buffer, so a splice or match-table bug shows up as a divergence on the very next bump.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Core slice of #57, re-scoped after measurement. An earlier version of this PR carried only the cursor, lexer emission, and view-backed arguments; a 5-way A/B (below) showed that cut regresses tt-recursive workloads, so flat mbe transcription is now part of this PR. #57 keeps the full per-step history.
What is in this part
bumpis an index increment, lookahead is direct indexing, parser snapshots and lazy capture replay share the buffer through refcount bumps, and whole delimited sequences skip in one step through the open/close match table. Skipped invisible delimiters stay in the buffer as entries (filtered on consumption), keeping tree-level lookahead and nesting-depth queries byte-exact.FlatSinklike every other producer, so primary parses never construct a token tree. The few cold callers that need a tree rebuild it from the buffer.ttfragments are buffer slices (a refcount bump at capture) and are spliced into expansion output with a depth and match-table rebase; captured non-ttfragments stay on the tree path for now.parse_delim_argscaptures arguments as a view of the buffer,parser_from_cxandDeriveProcMacro::from_ttsparse straight from the view, and the pre-expansionKeywordIdentslint scans flat entries. Attributes and macro definition bodies stay eager so long-lived nodes do not pin token buffers.Arcpointer-equality fast paths, theu32::MAXentry guard, lookahead fixes at skipped invisible delimiters, sink-only buffer production, theTokenStreamsize and iteration fixes, and the six regression tests.Not in this part: the nonterminal sink replay (#57 commit 12, the next slice: -0.51% geomean on its own and it halves the token-stream-stress residual below) and bridge-side flat emission for proc-macro output (later follow-up).
Why exactly this cut: the 5-way A/B
Measured with the #57 protocol (stage2, CI LLVM, lto=thin, jemalloc, instructions:u, Check+Debug, Full, 3 iterations; same-commit rebuild control pair: geomean -0.003%, worst cell 0.04%, zero cells at the 0.25% threshold):
The parser reading flat while any other component produces trees creates a conversion tax at the boundary, paid once per expansion level on tt-recursive code. Cursor, lexer, views, and flat transcription are one perf unit; this PR is that unit. The only wrong-way cells are token-stream-stress +0.72% check / +0.40% debug (proc-macro passthrough still pays one flatten per expansion); the nonterminal replay slice more than halves that residual and bridge-side flat emission is the named fix for the rest. The -0.63% geomean also matches the arithmetic from #57's per-commit numbers (-1.14% for the full stack minus -0.51% for the replay commit), so the two measurement rounds cross-validate.
The benchmarked tree for this cut is byte-identical to #57's eleventh commit, which is the state the #57 test battery already exercised.
The old cursor becomes a debug shadow
Instead of deleting the dead
TokenCursor, debug builds keep it as a shadow.Parser::new_from_flatrebuilds the token tree from the cursor's own buffer and steps the old cursor alongside the flat one; everybumpasserts the two yield identical(Token, Spacing)pairs, andparse_token_treekeeps the shadow in lockstep across the skip-to-group-end fast path. With flat transcription in the tree this now also differentially validates expansion output: the tree the shadow walks is rebuilt from the transcribed buffer, so a splice or match-table bug becomes a divergence on the next bump. Release builds compile all of it out (theParsersize assertion is release-only now). The cursor and shadow get deleted in the final slice once the stack has baked.Correctness
x check compilergreen at both commits