Skip to content

perf(codegen): lower untranslated parser predicates as generatable Unknown templates - #218

Merged
tinovyatkin merged 3 commits into
mainfrom
perf/209-fold-unknown-predicates
Jul 26, 2026
Merged

perf(codegen): lower untranslated parser predicates as generatable Unknown templates#218
tinovyatkin merged 3 commits into
mainfrom
perf/209-fold-unknown-predicates

Conversation

@tinovyatkin

@tinovyatkin tinovyatkin commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Fixes #209.

Root cause

The 5–6× slowdown on predicate-carrying grammars was never per-evaluation predicate cost — it was rule-compilation loss cascading through the call graph:

  1. An untranslated parser predicate body (e.g. the untouched grammars-v4 JavaParser.g4's bare helper calls { this.IsNotIdentifierAssign() }?, { this.DoLastRecordComponent() }?) parses to no PredicateTemplate, so structural_predicate_templates pushed nothing for it.
  2. Its coordinate then lands in predicate_coordinates.all but not .generated, and compile_generated_parser_transition refuses to compile the containing rule.
  3. The grammar also carries one ANTLR-synthesized expression action, so require_generated_callees is true and drop_rules_calling_disabled_rules cascades the kill upward: only 15 of 129 rules kept generated bodies. Everything else routed through the interpreter.

Fix

Lower such coordinates as a new PredicateTemplate::Unknown variant instead of leaving them uncovered, mirroring the existing UnknownWithFailMessage precedent:

  • SemIR-lowered to PExpr::Hook(HookId::new(0)), so evaluation keeps the documented hook → unknown-policy chain: an attached typed/closure hook is still consulted, and a declining hook falls through to the configured --sem-unknown disposition. (A pure epsilon fold was rejected — it would silently stop consulting with_typed_hooks users, which work today via the interpreter.)
  • Manifest output is unchanged: disposition still reports the policy (assume-true by default), template stays nullUnknown is an internal lowering, not a translation.
  • --sem-unknown=error and --require-full-semantics still abort listing the coordinates; a per-coordinate dispose = "error" override still lowers to no SemIR entry (the parsed_body gate) and stays fatal.
  • The legacy ParserPredicate table errors on Unknown exactly like Hook (SemIR is the active path).

Results

3-way bench on tools/parse-bench fixtures (min ms, generated parsers for portable/stripped vs untouched vs hand-edited { true }? grammar):

fixture untouched before untouched after lit-true portable
mojang-data-result.java 14.7 3.09 3.09 2.24
google-closure-property.java 4.1 0.88 0.84 0.59

The untouched grammar now generates all 129 rule dispatch bodies and matches the lit-true build within noise. The residual gap to fully-stripped portable is allow_semantic_context adaptive prediction at the two predicate-bearing decisions; a per-coordinate dispose = "assume-true" override (literal True template) remains available for the last bit.

Verification

  • Conformance: 357/357 passed (re-run after rebase) — descriptors with real predicates keep exact behavior.
  • Unit/integration: 314 runtime + 739 generator + 31 CLI tests green; clippy with CI flags clean.
  • New regression test untranslated_parser_predicate_keeps_generated_rule pins the generated-dispatch survival and the PExpr::Hook lowering.
  • Hook semantics end-to-end: a typed hook returning false steers annotationFieldValue to its second alternative on the generated path (verified against the regenerated untouched Java parser).
  • Kotlin parity: all snippets still match the antlr4-python3-runtime oracle byte-for-byte.
  • Policy matrix spot-checked on the untouched Java grammar: default → 129 generated dispatches, manifest assume-true/template: null; hookhooked + runtime Error policy installed; error / --require-full-semantics → generation aborts naming both coordinates.

CI parse-bench: benchmark variant bumped to v2

The parse-bench job's first run flagged java/bazel-sky-value-retriever.java at 1.82× vs the base report. That is the methodology change this PR makes, not a runtime regression: the Java Rust lane benches the untouched predicate grammar, which previously ran through the ATN interpreter and now runs generated rule bodies. Per-fixture deltas are mixed by design — mojang 6.4→3.2 ms and google-closure 1.9→0.9 ms improve, while the bazel fixture's decision mix happens to favor the warmed interpreter DFA over the generated walker (the same shape exists on main between the portable-generated and interpreted lanes; nothing in the runtime changed).

JAVA_RUST_PREDICATE_VARIANT is bumped to java-upstream-parser-predicates-v2 (304c1ac), the same baseline-reset mechanism the v1 tag used for the legacy-to-predicate transition in #175: the comparator skips mismatched-variant rows on this PR and re-arms the 1.15× Java gate once the base branch report also carries v2. Verified locally by running the real harness Java lane (report rows tagged v2) and feeding it to compare.py against the failed run's v1 numbers — rows skip cleanly and the job passes.

Summary by CodeRabbit

  • Bug Fixes
    • Improved generation and rendering behavior for untranslated parser predicates by treating them as “unknown” templates, ensuring runtime predicate evaluation still follows the configured policy.
    • Preserved the fast-path dispatch for parser rules when predicate bodies can’t be translated.
    • Fixed predicate handling for unsupported lexer/legacy predicate paths and corrected manifest output for unknown templates.
  • Tests
    • Added/updated unit tests to confirm untranslated predicates and native comparison predicates follow the correct fallback behavior.
  • Documentation
    • Updated parse benchmark documentation and switched benchmark routing to the newer predicate variant scheme.

…known templates

An untranslated predicate body (e.g. a bare this.IsNotIdentifierAssign()
helper call) previously produced no PredicateTemplate, leaving its
coordinate out of the generated set. compile_generated_parser_transition
then refused to compile the containing rule, and with
require_generated_callees active the drop cascaded through
drop_rules_calling_disabled_rules to every calling rule — the untouched
grammars-v4 JavaParser.g4 kept only 15 of 129 generated rule bodies and
routed everything else through the interpreter, 5-6x slower than the
predicate-stripped portable grammar (issue #209).

Lower such coordinates as a new PredicateTemplate::Unknown instead,
mirroring UnknownWithFailMessage: SemIR PExpr::Hook(0), so evaluation
keeps the documented hook -> unknown-policy chain. Typed/closure hooks
stay consulted, --sem-unknown dispositions and --require-full-semantics
behave unchanged, the manifest still reports disposition assume-true
with template null, and a dispose="error" coordinate override still
lowers to no SemIR entry.

Untouched JavaParser.g4 now generates all 129 rule dispatch bodies;
parse times match the lit-true ({ true }?) build within noise:
mojang-data-result.java 14.7 -> 3.1 ms, google-closure-property.java
4.1 -> 0.9 ms (portable baseline 2.2 / 0.6 ms; the small residual is
allow_semantic_context adaptive prediction at the two predicate-bearing
decisions).

Fixes #209
@github-actions

Copy link
Copy Markdown

Copy/Paste Detection

No duplications found in 1 changed Rust file(s) (threshold: 100 tokens).

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The generator adds PredicateTemplate::Unknown for untranslated parser predicates, applies unknown-predicate policy metadata, lowers parser predicates through SemIR hooks, rejects unsupported legacy and lexer paths, and updates tests and Java benchmark routing documentation.

Changes

Unknown predicate codegen and benchmark routing

Layer / File(s) Summary
Predicate model and semantic collection
src/bin/antlr4-rust-gen.rs
Untranslated parser predicates are represented as Unknown, remain generatable, and retain policy-based manifest dispositions with a null template value.
Parser and lexer lowering
src/bin/antlr4-rust-gen.rs
SemIR lowers Unknown through PExpr::Hook; legacy parser rendering rejects it, and lexer rendering treats it as unreachable.
Predicate validation and benchmark metadata
src/bin/antlr4-rust-gen.rs, tools/parse-bench/README.md, tools/parse-bench/run.py
Tests verify generated-rule dispatch and Unknown classification for native comparisons; benchmark documentation and the Java predicate variant are updated to describe the new routing comparison.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SemanticCollection
  participant ParserSemIR
  participant GeneratedParserRule
  SemanticCollection->>ParserSemIR: collect PredicateTemplate::Unknown
  ParserSemIR->>GeneratedParserRule: emit PExpr::Hook
  GeneratedParserRule->>ParserSemIR: evaluate unknown predicate policy
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The code lowers untranslated predicate coordinates without changing predicate semantics and keeps generated bodies fast, matching #209's routing fix.
Out of Scope Changes check ✅ Passed The docs and benchmark-variant edits support the codegen change and do not introduce unrelated functionality.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main codegen change: lowering untranslated parser predicates into Unknown templates for generation.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch perf/209-fold-unknown-predicates

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 26, 2026

Copy link
Copy Markdown

📊 Source Code Metrics (this PR vs main)

File Cyclomatic Cognitive Functions LLOC MI
src/bin/antlr4-rust-gen.rs 2261 (main: 2257) 🔴 1419 (main: 1418) 🔴 501 (main: 500) 🔴 3961 (main: 3953) 🔴 0 ⚪

Generated by mehen v1.7.0 — the code quality watcher.

@codecov

codecov Bot commented Jul 26, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@github-actions

github-actions Bot commented Jul 26, 2026

Copy link
Copy Markdown

Claude Code review skipped — usage limit reached.

You've hit your weekly limit · resets Jul 29, 8pm (UTC) Re-run the workflow once the quota resets.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f883ab08c5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/bin/antlr4-rust-gen.rs Outdated
Swap the hand-written assert_eq! in
untranslated_parser_predicate_keeps_generated_rule for a named insta
snapshot per the repository snapshot guidance (pinning a collection's
full contents is a value test, not a property test).

Addresses the Codex review comment on PR #218.
…uting change

The untouched-JavaParser.g4 lane previously ran the two predicate-bearing
rules (and, via the caller cascade, most of the grammar) through the ATN
interpreter. With untranslated predicates now lowering as generatable
templates, that lane routes through generated rule bodies — a methodology
change, not a runtime regression, and the per-fixture deltas are mixed
by design (mojang 6.4->3.2 ms, google-closure 1.9->0.9 ms, but
bazel-sky-value-retriever 10.4->19.0 ms on the CI runner, where the
generated walker loses to the warmed interpreter DFA for that fixture's
decision mix; the same shape exists on main between the portable and
interpreted lanes).

Bump JAVA_RUST_PREDICATE_VARIANT to v2 so the comparator skips the
mismatched-variant rows and re-arms the 1.15x Java regression gate once
both reports carry v2 — the same reset the v1 tag performed for the
legacy-to-predicate transition in #175.
@tinovyatkin
tinovyatkin merged commit 84fda7d into main Jul 26, 2026
12 checks passed
@tinovyatkin
tinovyatkin deleted the perf/209-fold-unknown-predicates branch July 26, 2026 19:35
@ophiarch ophiarch Bot mentioned this pull request Jul 26, 2026
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.

perf(codegen): assume-true predicates force the interpreter path — profile the routing and fold constant-true coordinates

1 participant