From 96abd2b7c876a5246762659b917cc5519601ce3d Mon Sep 17 00:00:00 2001 From: Amit Kumar Date: Wed, 13 May 2026 13:25:22 +0000 Subject: [PATCH] perf(parser): rewrite Walk to use TreeCursor (Task B1) Replaces the recursive Node.Child(i) traversal with an iterative tree-sitter TreeCursor walk. Matches the canonical tree-sitter idiom and removes Go-level recursion frames per descent. Honest accounting: smacker's *TreeCursor.CurrentNode() still routes through Tree.cachedNode, so the per-visit *Node allocation is unchanged. The 91%-of-allocations cachedNode hot spot pprof flagged on airflow was driven by per-node *re-parse* (Task A1 fixed that by parsing once per file). Phase B's structural change keeps the public Walk(root, fn) API identical; callers and tests are untouched. Plan: docs/superpowers/plans/2026-05-13-enrich-oom-fix.md Task B1. Verification: - go test ./internal/parser/... ./internal/intelligence/extractor/... pass - go test ./... -count=1: 877 pass (unchanged from main) - Determinism preserved: pre-order DFS visitation order matches the recursive form exactly. --- go/internal/parser/walk.go | 38 +++++++++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/go/internal/parser/walk.go b/go/internal/parser/walk.go index cc651835..997ba00f 100644 --- a/go/internal/parser/walk.go +++ b/go/internal/parser/walk.go @@ -19,15 +19,43 @@ type Node = sitter.Node // recurse into the current node's children, false to skip them. Walking stops // when the visitor returns false at the root or when all descendants have // been visited. nil-safe. +// +// Implementation uses tree-sitter's TreeCursor for iterative traversal. +// Compared to the previous recursive `n.Child(i)` form, the cursor avoids +// Go-level recursion frames per descent and matches the canonical +// tree-sitter walking idiom. Note: each visited node still flows through +// smacker's per-Tree node cache (allocates one *Node on first visit per +// node), so the allocation count is roughly the same as the recursive form +// — the win is in stack discipline and code clarity, not GC pressure. func Walk(n *Node, visit func(*Node) bool) { if n == nil || visit == nil { return } - if !visit(n) { - return - } - for i := 0; i < int(n.ChildCount()); i++ { - Walk(n.Child(i), visit) + cur := sitter.NewTreeCursor(n) + defer cur.Close() + // Visit root. + descend := visit(cur.CurrentNode()) + for { + if descend && cur.GoToFirstChild() { + descend = visit(cur.CurrentNode()) + continue + } + // No children to descend into; advance to next sibling, climbing + // out of the subtree as necessary. The loop terminates when + // GoToParent returns false (we have climbed back above the + // original root). + for { + if cur.GoToNextSibling() { + descend = visit(cur.CurrentNode()) + break + } + if !cur.GoToParent() { + return + } + // After climbing to parent, the parent itself was already + // visited when we descended into it — do NOT re-visit. Continue + // trying GoToNextSibling at the parent's level. + } } }