From 150c88e1c85a3f0a5ee4f97cde02729d896d7fc5 Mon Sep 17 00:00:00 2001 From: Andrew Hundt Date: Thu, 13 Aug 2026 02:58:52 -0400 Subject: [PATCH] internal/cbm/lsp/scope.*: inline complete binding lookup Previous behavior: cbm_scope_lookup(), cbm_scope_contains(), and cbm_scope_lookup_callable() called an out-of-line complete-binding scan after the Python lookup consolidation. What changed: - internal/cbm/lsp/scope.h defines cbm_scope_lookup_binding() as the one static-inline scope-chain scan. - internal/cbm/lsp/scope.c keeps the type, presence, and callable wrappers on that shared implementation without another function boundary. Why: Python still reads type and callable identity in one traversal, while existing language resolvers retain the direct hot path used before the consolidation. Verification: - CBM_ONLY_SUITE=scope build/c/test-runner: 33 passed - CBM_ONLY_SUITE=cs_lsp build/c/test-runner: 62 passed - CBM_ONLY_SUITE=py_lsp build/c/test-runner: 125 passed - neutral 11-pair extraction matrix: output parity; C# latency -2.97% vs first parent and -1.64% vs upstream; retired instructions +0.29% vs first parent and -1.01% vs upstream; Python scale latency -4.25% and instructions -2.81% vs first parent Signed-off-by: Andrew Hundt --- internal/cbm/lsp/scope.c | 14 -------------- internal/cbm/lsp/scope.h | 24 ++++++++++++++++++++---- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/internal/cbm/lsp/scope.c b/internal/cbm/lsp/scope.c index c44a0b544..2d9740db0 100644 --- a/internal/cbm/lsp/scope.c +++ b/internal/cbm/lsp/scope.c @@ -88,20 +88,6 @@ bool cbm_scope_bind_callable_checked(CBMScope *scope, const char *name, const CB return cbm_scope_bind_value(scope, name, type, callable_qn); } -const CBMVarBinding *cbm_scope_lookup_binding(const CBMScope *scope, const char *name) { - if (!name) - return NULL; - for (const CBMScope* s = scope; s != NULL; s = s->parent) { - for (const CBMScopeChunk* c = s->chunks; c != NULL; c = c->next) { - for (int i = 0; i < c->used; i++) { - if (c->bindings[i].name && strcmp(c->bindings[i].name, name) == 0) - return &c->bindings[i]; - } - } - } - return NULL; -} - const CBMType* cbm_scope_lookup(const CBMScope* scope, const char* name) { const CBMVarBinding *binding = cbm_scope_lookup_binding(scope, name); if (binding) diff --git a/internal/cbm/lsp/scope.h b/internal/cbm/lsp/scope.h index 3ec438fe9..5ba7265a3 100644 --- a/internal/cbm/lsp/scope.h +++ b/internal/cbm/lsp/scope.h @@ -5,6 +5,7 @@ #include "../arena.h" #include /* relaxed cache for cbm_lsp_max_walk_depth */ #include /* getenv, atoi (cbm_lsp_max_walk_depth) */ +#include /* strcmp (cbm_scope_lookup_binding) */ typedef struct { const char* name; @@ -30,6 +31,25 @@ typedef struct CBMScope { CBMArena* arena; // owning arena, propagated to children at push time } CBMScope; +/* Return the complete nearest binding in one scope-chain walk, or NULL when + * unbound. Keep this internal hot-path primitive inline: all language + * resolvers use cbm_scope_lookup(), while Python also consumes the complete + * record to avoid repeating the same linear scan. */ +static inline const CBMVarBinding *cbm_scope_lookup_binding(const CBMScope *scope, + const char *name) { + if (!name) + return NULL; + for (const CBMScope *s = scope; s != NULL; s = s->parent) { + for (const CBMScopeChunk *c = s->chunks; c != NULL; c = c->next) { + for (int i = 0; i < c->used; i++) { + if (c->bindings[i].name && strcmp(c->bindings[i].name, name) == 0) + return &c->bindings[i]; + } + } + } + return NULL; +} + // Bail-to-UNKNOWN depth for type-lookup chains: alias resolution, MRO walks, // embedded-field/struct-traversal. Exceeding this collapses to cbm_type_unknown // rather than recursing — guards against pathological hierarchies. @@ -82,10 +102,6 @@ bool cbm_scope_bind_callable_checked(CBMScope *scope, const char *name, const CB void cbm_scope_bind_callable(CBMScope *scope, const char *name, const CBMType *type, const char *callable_qn); const CBMType* cbm_scope_lookup(const CBMScope* scope, const char* name); -/* Return the complete nearest binding in one scope-chain walk, or NULL when - * unbound. Callers that need presence, type, and callable identity together - * should use this instead of repeating three linear scans. */ -const CBMVarBinding *cbm_scope_lookup_binding(const CBMScope *scope, const char *name); /* True when any lexical frame contains name, even when its type is UNKNOWN. */ bool cbm_scope_contains(const CBMScope *scope, const char *name); /* Return the exact callable QN from the nearest binding. A nearer ordinary