Skip to content

perf(indexer): compute interface fingerprints in Rust on the tree bridge - #3495

Merged
vitali87 merged 2 commits into
mainfrom
claude/cgr-rust-porting-mrwohh
Oct 28, 2026
Merged

vitali87 merged 2 commits into
mainfrom
claude/cgr-rust-porting-mrwohh

Conversation

@vitali87

@vitali87 vitali87 commented Oct 28, 2026 •

Copy link
Copy Markdown
Owner

Summary

Demo

cgr start --update-graph on rich, then a hash of every module's interface fingerprint: main, then this branch

The GIF, recorded with VHS, indexes rich from scratch: 213 Python files, no embeddings. The first run uses main's source (4f497f2) with main's own build of the extension, as main's users get it. The second uses this branch, in the same environment. After each run it exports the graph and hashes every module's fingerprint, as "module fingerprint" lines (fingerprints.sh, printed at the start):

  • main: sync in 19.80 s; 213 interface fingerprints, sha256 03f7f80e33988fbb.
  • This branch: sync in 17.39 s; the same 213 fingerprints and the same hash.

That is one run of each. The median of five alternating runs is below: 19.8 s against 17.0 s. The GIF is stored on the claude/cgr-rust-porting-mrwohh-evidence branch and is not part of this PR's diff.

How it works

The walk. rust/cgr_graph_rs/src/trees/interface.rs ports interface_fingerprint.py function by function:

  • the stream of tokens it hashes;
  • the per-language checks of whether anything outside reads a function's body: a call of a parameter, a returned name the body binds, a name handed on to another call;
  • the short projection that replaces a body nothing reads.

It hashes the same length-prefixed tokens with the same SHA-256, so it returns the same string.

The rules. bridge_rules() hands the bridge _RULES and every node type, field and token the walk reads, all from constants/, as stages 1 and 2 do. Their shapes are InterfaceRules and friends in types_defs.py. The node-type sets that parsers/utils.py kept privately for the callable scan moved to constants/ast_python.py and constants/ast_js.py, so both sides read one definition. A length prefix or byte order the Rust side does not write, or an unknown flavor, fails the bridge's construction, and the bridge stays off.

Scope facts. Where the Python checks ask which names a function binds, they call #3492's scope facts. The Rust walk calls #3492's Rust scope walks directly (names_bound_within and add_locals in scopes.rs) and keeps the facts per def for the file, without calling back into Python.

The probe. The startup probe now also fingerprints its sample both ways. A relay function was added to the sample, with the kinds of calls, keyword arguments and returns the fingerprint looks at. Without a guard, the Python twin's scope lookups would go through the very bridge being probed. native_trees._checking keeps them in Python while the probe runs, so it compares two separate computations, not Rust with itself.

Walking faster. The walk never asks a node for its parent, because tree-sitter answers that by walking down from the root; it keeps the enclosing definitions on its own stack instead. It and #3492's scope walks now share one cursor-based preorder iterator (Preorder in trees/mod.rs). The node-type sets hash with FNV (NodeTypes), about a third cheaper per lookup than Rust's default hasher. That is safe here because their keys are the grammar's type names, never text from the file; sets of names read from the file keep the default hasher.

Fallbacks, as in #3492:

  • A node it cannot read exactly (a test double, a tree without its source) raises NotBorrowed, and that call runs the Python code.
  • A build of the extension without interface_fingerprint leaves every file to Python (native_trees.interface_bridge()).
  • A language without rules still returns None.

Results

Per call, each file fingerprinted both ways:

Files Language Python Rust Differences
code-graph-rag codebase_rag/, 1,743 Python 45.24 s 5.35 s 0
rich, 213 Python 2.60 s 0.26 s 0
click, 91 Python 2.28 s 0.27 s 0
express, 142 JavaScript 1.80 s 0.25 s 0
ky, 98 TypeScript 2.76 s 0.31 s 0
ky, the same 98 TSX 2.02 s 0.26 s 0
gson, 264 Java 1.81 s 0.12 s 0
code-graph-rag, 7 Java 0.02 s 0.004 s 0
all 2,656 58.5 s 6.8 s 0 (8.6x)

Most of what is left is tree-sitter's own cursor, 93 ns to 125 ns a step. The walk also visits a function's body more than once: first to check what reads it, then to project it.

End to end, sync time without embeddings, alternating main (with its own extension build) and this branch in the same environment:

Repository main This branch Gain
rich (213 files), median of 5 19.8 s 17.0 s 1.16x
click (91 files), median of 3 12.1 s 11.3 s 1.07x
code-graph-rag (1,846 files), 2 runs 358.2 s, 335.0 s 300.4 s, 312.3 s 1.07x to 1.19x, 1.13x on the means

This container ran about 1.6 times slower than when #3492 was measured: the same code on the same tree took 214 s to 224 s then. Compare within this table only. The 1.13x is what #3492's profile predicted (12.9% at 8.6x). Under py-spy, interface_fingerprint fell from 12.9% of the code-graph-rag run to 1.9%. The scope walks fell from 6.6% to 3.2%, since the fingerprint no longer calls them from Python.

Correctness:

  • Fingerprints: the 2,656 files above, no difference.
  • Graphs:
    • code-graph-rag: each of the two runs on this branch gave the same graph as the main run beside it, byte for byte, fingerprints included (215,163 and 215,164 nodes and edges). The two pairs differ from each other by one CALLS edge, on both sides.
    • click: all six runs gave the same graph.
    • rich: varies a little from run to run on main itself (noted in perf(indexer): walk each Python scope once, in Rust when the tree bridge can #3492). Its five runs on each side wrote the same set of lines.

Type of Change

  • Bug fix
  • New feature
  • Performance improvement
  • Refactoring (no functional changes)
  • Documentation
  • CI/CD or tooling
  • Dependencies

Related Issues

Closes #3473: #3492 was its scope-walk half, and this is interface_fingerprint, the rest. Related to #2531, which introduced the fingerprint.

Test Plan

  • Unit tests pass (uv run pytest -n auto -m "not integration", with the extras CI installs): 24,130 passed and 5 failed, none of them because of this change.
    • 4 need toolchains this environment lacks, the same 4 as in perf(indexer): walk each Python scope once, in Rust when the tree bridge can #3492: the .NET SDK (two C# oracle guards) and the Go go/types helper (two Go oracle tests).
    • The fifth, test_project_selection.py::TestOutsideAnyIndexedRepo::test_several_projects_are_refused_with_the_indexed_names, fails on main too, about 1 run in 740 (see below). It passed on rerun.
  • New tests added:
    • codebase_rag/tests/test_native_interface.py (29 tests) compares Rust and Python on a sample in each language, on every definition in those samples, on each sample's elided and kept bodies, and on every file of the codebase_rag package. It also covers:
      • every fallback: a test double, a tree without its source, the bridge switched off, an older build, rules without the interface section;
      • the rules a bridge refuses: another length prefix or byte order, an unknown flavor;
      • the probe turning the bridge off when the fingerprints disagree, and the probe comparing with Python all the way down.
    • test_interface_fingerprint.py now runs on both paths through the both_interface_walks fixture: 158 runs, all passing.
    • Rust unit tests in src/trees/tests.rs pin the Python twin's value on a sample, and that the fingerprint ignores what callers cannot see and changes with what they can.
  • Integration tests pass (make test-integration, requires Docker). Not run: no Docker here.
  • Manual testing:
    • the per-call comparison and the main-against-branch graph comparisons above;
    • cargo test --release (61 passed);
    • cargo clippy --features python: nothing new; the two existing warnings in lib.rs remain.

Checklist

  • PR title follows Conventional Commits format
  • A breaking change has ! in the title and its release notes in changes/breaking/<PR number>.md: not a breaking change.
  • All pre-commit checks pass (make pre-commit)
  • No hardcoded strings in non-config/non-constants files
  • No # type: ignore, cast(), Any, or object type hints
  • New comments and docstrings explain why, not what

For the maintainer

  • Crate version: cgr-graph-rs goes from 0.1.2 to 0.1.3, as the "Crate version bumped with its code" check requires, and the requirement's floor moves to 0.1.3. In uv.lock, only the crate's version line changed, rewritten in place as in perf(indexer): walk each Python scope once, in Rust when the tree bridge can #3492. uv lock --check passes.
  • A flaky test on main: test_several_projects_are_refused_with_the_indexed_names derives a project name from pytest's temporary folder, and that name carries a hash of the path. When the hash happens to look like cobra__125fa53b, close_project_names suggests it, and the test's expectation (no close match, so every indexed project is listed) fails. The failing run's name was scratch__25fabe29, at 0.625 against the 0.6 cutoff. About 1 random hash in 740 does that. Fixing the folder name, or the hash, in the test would remove it. That fix is unrelated to this change and is left out of it.
  • Java registry scan: the new profile puts java/type_resolver.py:_find_registry_entries_under at 9.5% of the code-graph-rag run, for its 7 Java files. When the registry's trie finds no names under a prefix, it scans every registered name. The plan records it as step 7, a Python fix; it is not part of this PR.

Summary by CodeRabbit

  • New Features
    • The indexer now generates interface fingerprints for supported Python, JavaScript, TypeScript, TSX, and Java code.
  • Improvements
    • Fingerprints account for definitions and relevant function-body details, while changes to unread implementation details can leave them unchanged.
    • The system can fall back to its Python implementation when native fingerprinting is unavailable.

@greptile-apps

greptile-apps Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerTREX TREX

Tested 1 flow, found no issues.

What we tested

  • Pass · Sync interfaces through the indexing CLI · View ↗

Took 2 attempts. TREX adjusted its test setup between them.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-28T08:53:49.872027Z 0e58b57 PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@coderabbitai

coderabbitai Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between 4f497f2 and 0e58b57.

Files ignored due to path filters: 2
  • rust/cgr_graph_rs/Cargo.lock is excluded by !**/*.lock
  • uv.lock is excluded by !**/*.lock
Files selected for processing: 23
  • codebase_rag/constants/ast_js.py
  • codebase_rag/constants/ast_python.py
  • codebase_rag/constants/embedded_graph.py
  • codebase_rag/constants/interface.py
  • codebase_rag/parsers/interface_fingerprint.py
  • codebase_rag/parsers/native_trees.py
  • codebase_rag/parsers/utils.py
  • codebase_rag/tests/conftest.py
  • codebase_rag/tests/test_interface_fingerprint.py
  • codebase_rag/tests/test_native_interface.py
  • codebase_rag/tests/test_rust_extension_packaging.py
  • codebase_rag/types_defs.py
  • docs/architecture/embedded-graph.md
  • docs/reports/INDEXER_RUST_PLAN.md
  • pyproject.toml
  • rust/cgr_graph_rs/Cargo.toml
  • rust/cgr_graph_rs/README.md
  • rust/cgr_graph_rs/pyproject.toml
  • rust/cgr_graph_rs/src/trees/interface.rs
  • rust/cgr_graph_rs/src/trees/mod.rs
  • rust/cgr_graph_rs/src/trees/python.rs
  • rust/cgr_graph_rs/src/trees/scopes.rs
  • rust/cgr_graph_rs/src/trees/tests.rs

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 3 remain after this review.

Walkthrough

The Rust tree bridge now computes interface fingerprints using rules supplied by Python. The Python implementation remains available as a reference and fallback. The change adds Rust scope traversal support, bridge parity checks, cross-language tests, and updates the extension version to 0.1.3.

Changes: Interface fingerprinting

Layer / File(s) Summary
Fingerprint rules and language configuration
codebase_rag/constants/ast_js.py, codebase_rag/constants/ast_python.py, codebase_rag/constants/interface.py, codebase_rag/parsers/interface_fingerprint.py, codebase_rag/parsers/utils.py, codebase_rag/types_defs.py
Python defines the language, node-type, and callable-scan rules used for fingerprinting. The parser packages these rules for the bridge and uses shared node-type sets in callable-parameter scans.
Rust traversal and scope support
rust/cgr_graph_rs/src/trees/mod.rs, rust/cgr_graph_rs/src/trees/scopes.rs, rust/cgr_graph_rs/src/trees/tests.rs
Rust adds preorder tree traversal and scope-binding helpers. Scope walks use the traversal, and their node-type collections use the shared aliases.
Rust fingerprinting and bridge integration
rust/cgr_graph_rs/src/trees/interface.rs, rust/cgr_graph_rs/src/trees/python.rs, codebase_rag/parsers/interface_fingerprint.py, codebase_rag/parsers/native_trees.py, codebase_rag/constants/embedded_graph.py
The Rust implementation fingerprints syntax trees using configured rules. The tree bridge loads those rules and exposes the fingerprint method. The Python parser selects the bridge when available and falls back to its Python implementation when the bridge reports NotBorrowed. Bridge validation compares the Rust and Python fingerprints.
Parity tests and release updates
codebase_rag/tests/conftest.py, codebase_rag/tests/test_interface_fingerprint.py, codebase_rag/tests/test_native_interface.py, rust/cgr_graph_rs/src/trees/tests.rs, codebase_rag/tests/test_rust_extension_packaging.py, docs/architecture/embedded-graph.md, docs/reports/INDEXER_RUST_PLAN.md, pyproject.toml, rust/cgr_graph_rs/Cargo.toml, rust/cgr_graph_rs/README.md, rust/cgr_graph_rs/pyproject.toml
Tests compare native and Python fingerprints across language samples and Python package files, and check fallback and bridge-validation cases. Documentation and performance reports describe the landed implementation. Package version references change to 0.1.3.

Priority: Normal

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Refactor

Sequence Diagram(s)

sequenceDiagram
  participant Fingerprint as interface_fingerprint
  participant Bridge as native_trees.interface_bridge
  participant TreeBridge
  participant Rust as Rust interface_fingerprint
  participant Python as python_interface_fingerprint
  Fingerprint->>Bridge: select available interface bridge
  Bridge-->>Fingerprint: return TreeBridge or None
  Fingerprint->>TreeBridge: request fingerprint for supported language
  TreeBridge->>Rust: fingerprint tree using source and configured rules
  Rust-->>Fingerprint: return fingerprint
  Fingerprint->>Python: use Python implementation when bridge reports NotBorrowed
  Python-->>Fingerprint: return reference fingerprint
Loading

Possibly related PRs

Merge Risk: Low · up to 0e58b

Normal file indexing is unaffected by the identified discrepancy. Nested-definition fingerprints can differ between Rust and Python, warranting bounded follow-up rather than blocking this release.

Pre-merge checks: Passed 4
Check name Status Explanation
Title check Passed The title clearly and concisely describes the primary change: moving interface fingerprint computation to Rust on the tree bridge. It uses Conventional Commits format.
Description check Passed The description includes all required sections, explains the implementation, documents performance and correctness results, identifies test limitations, and records related issues and checklist status…
Linked Issues check Passed The changes implement the active coding requirements in #3473. Rust now computes interface fingerprints on existing py-tree-sitter trees for Python, Java, JavaScript, TypeScript, and TSX. Python remai…
Out of Scope Changes check Passed The changed constants, rule types, bridge validation, traversal helper, Rust scope integration, tests, version updates, and documentation support the #3473 implementation. The Java registry scan appea…
Full details: Description check

Explanation

The description includes all required sections, explains the implementation, documents performance and correctness results, identifies test limitations, and records related issues and checklist status.

Full details: Linked Issues check

Explanation

The changes implement the active coding requirements in #3473. Rust now computes interface fingerprints on existing py-tree-sitter trees for Python, Java, JavaScript, TypeScript, and TSX. Python remains the reference and fallback. The startup probe compares both implementations. The shared scope facts and Rust scope walks support the fingerprint path. The reported tests compare both paths across language samples and package files, and the reported measurements reduce the combined profile share from 21.9% to 5.1%. The reported graph comparisons preserve nodes, edges, and fingerprints for code-graph-rag, rich, and click.

Full details: Out of Scope Changes check

Explanation

The changed constants, rule types, bridge validation, traversal helper, Rust scope integration, tests, version updates, and documentation support the #3473 implementation. The Java registry scan appears only as documented future plan work; no unrelated registry implementation was added. The dependency and crate version updates support the new bridge method.

Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@vitali87 vitali87 added the performance Performance improvements label Oct 28, 2026 — with Claude
@greptile-apps

greptile-apps Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The PR appears safe to merge; no blocking defects were found.

Summary

The PR moves file interface fingerprints to Rust while keeping Python as the reference and fallback.

  • The indexer computes file interface fingerprints in Rust.

No blocking issues were found. Tests were inspected but not run.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[File interface fingerprint] --> B{Language has rules?}
  B -->|No| C[Return None]
  B -->|Yes| D{Bridge supports fingerprint?}
  D -->|No| E[Python reference]
  D -->|Yes| F[Rust walk]
  F -->|NotBorrowed| E
  F -->|Success| G[Return digest]
  E --> G
Loading

Reviews (1) · Last reviewed commit: "docs(indexer): record stage 3 of the Rus..." · Reviewed by Greptile

@sonarqubecloud

Copy link
Copy Markdown

@vitali87
vitali87 merged commit a656089 into main Oct 28, 2026
64 of 67 checks passed
@vitali87
vitali87 deleted the claude/cgr-rust-porting-mrwohh branch October 28, 2026 15:27
vitali87 added a commit that referenced this pull request Oct 28, 2026
Main reached 0.1.3 with #3495, so the pagerank fix needs a version above
it for publish.yml to ship the changed library.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ChfULvr1gXZn6MQXr4xNFS
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

performance Performance improvements

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[Performance]: Python scope analysis is about 22% of indexing: cache it per scope in Python, then port it to Rust

1 participant