Skip to content

Commit f339aa6

Browse files
ahundtDeusData
authored andcommitted
perf(lsp): collapse repeated Python scope-chain lookups
Add cbm_scope_lookup_binding so callers can read lexical presence, type, and callable identity in one traversal. Route Python expression, call, and callable-reference resolution through that complete binding and use the existing checked bind APIs for allocation-failure detection. The neutral 11-pair production matrix kept exact output parity. Python scale latency was 4.33% below merge parent 07255cf and 61.01% below upstream parent 0c64de9; incremental instructions were 3.09% and 54.07% lower. Focused ASan/UBSan scope and Python suites passed 33 and 125 tests. Signed-off-by: Andrew Hundt <ATHundt@gmail.com> (cherry picked from commit 4d3a103) Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
1 parent 9d65f4c commit f339aa6

4 files changed

Lines changed: 62 additions & 64 deletions

File tree

‎internal/cbm/lsp/py_lsp.c‎

Lines changed: 22 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -191,20 +191,15 @@ static void py_disable_callable_value_proof(PyLSPContext *ctx) {
191191

192192
static void py_scope_bind(PyLSPContext *ctx, const char *name, const CBMType *type) {
193193
ctx->type_cache_gen++;
194-
cbm_scope_bind(ctx->current_scope, name, type);
195-
if (name && !cbm_scope_contains(ctx->current_scope, name))
194+
if (!cbm_scope_bind_checked(ctx->current_scope, name, type))
196195
py_disable_callable_value_proof(ctx);
197196
}
198197

199198
static void py_scope_bind_callable(PyLSPContext *ctx, const char *name, const CBMType *type,
200199
const char *callable_qn) {
201200
ctx->type_cache_gen++;
202-
cbm_scope_bind_callable(ctx->current_scope, name, type, callable_qn);
203-
const char *bound = name ? cbm_scope_lookup_callable(ctx->current_scope, name) : NULL;
204-
if (name && (!cbm_scope_contains(ctx->current_scope, name) ||
205-
(callable_qn && (!bound || strcmp(bound, callable_qn) != 0)))) {
201+
if (!cbm_scope_bind_callable_checked(ctx->current_scope, name, type, callable_qn))
206202
py_disable_callable_value_proof(ctx);
207-
}
208203
}
209204

210205
static CBMScope *py_scope_push_checked(PyLSPContext *ctx) {
@@ -938,8 +933,9 @@ static const char *py_exact_callable_target_ex(PyLSPContext *ctx, TSNode node,
938933
char *name = py_node_text(ctx, node);
939934
if (!name)
940935
return NULL;
941-
if (cbm_scope_contains(ctx->current_scope, name)) {
942-
const char *bound = cbm_scope_lookup_callable(ctx->current_scope, name);
936+
const CBMVarBinding *binding = cbm_scope_lookup_binding(ctx->current_scope, name);
937+
if (binding) {
938+
const char *bound = binding->callable_qn;
943939
if (bound && lexical_alias_out) {
944940
/* A lexical binding that does NOT simply name the module symbol
945941
* of the same spelling is an alias introduced in this body. Only
@@ -1018,11 +1014,12 @@ static void py_resolve_value_references_at(PyLSPContext *ctx, TSNode call) {
10181014
const char *candidate = strcmp(kind, "identifier") == 0
10191015
? py_exact_imported_reference_candidate(ctx, source_name)
10201016
: NULL;
1021-
if (candidate && cbm_scope_contains(ctx->current_scope, source_name)) {
1022-
const CBMType *binding =
1023-
cbm_type_resolve_alias(cbm_scope_lookup(ctx->current_scope, source_name));
1024-
if (binding && binding->kind == CBM_TYPE_NAMED && binding->data.named.qualified_name &&
1025-
strcmp(binding->data.named.qualified_name, candidate) == 0) {
1017+
const CBMVarBinding *scope_binding =
1018+
candidate ? cbm_scope_lookup_binding(ctx->current_scope, source_name) : NULL;
1019+
if (scope_binding) {
1020+
const CBMType *type = cbm_type_resolve_alias(scope_binding->type);
1021+
if (type && type->kind == CBM_TYPE_NAMED && type->data.named.qualified_name &&
1022+
strcmp(type->data.named.qualified_name, candidate) == 0) {
10261023
py_emit_unresolved_reference(ctx, candidate, arg);
10271024
}
10281025
}
@@ -1498,9 +1495,9 @@ static const CBMType *py_eval_expr_type_uncached(PyLSPContext *ctx, TSNode node)
14981495
char *name = py_node_text(ctx, node);
14991496
if (!name)
15001497
return cbm_type_unknown();
1501-
const CBMType *t = cbm_scope_lookup(ctx->current_scope, name);
1502-
if (cbm_scope_contains(ctx->current_scope, name))
1503-
return t ? t : cbm_type_unknown();
1498+
const CBMVarBinding *binding = cbm_scope_lookup_binding(ctx->current_scope, name);
1499+
if (binding)
1500+
return binding->type ? binding->type : cbm_type_unknown();
15041501
// Builtin globals: True / False / None at top level.
15051502
if (strcmp(name, "True") == 0 || strcmp(name, "False") == 0)
15061503
return cbm_type_builtin(ctx->arena, "bool");
@@ -1767,8 +1764,9 @@ static const CBMType *py_eval_expr_type_uncached(PyLSPContext *ctx, TSNode node)
17671764
}
17681765
}
17691766
// Constructor call: ClassName() returns NAMED(ClassName).
1770-
const CBMType *in_scope = cbm_scope_lookup(ctx->current_scope, fname);
1771-
const char *callable_qn = cbm_scope_lookup_callable(ctx->current_scope, fname);
1767+
const CBMVarBinding *binding = cbm_scope_lookup_binding(ctx->current_scope, fname);
1768+
const CBMType *in_scope = binding && binding->type ? binding->type : cbm_type_unknown();
1769+
const char *callable_qn = binding ? binding->callable_qn : NULL;
17721770
if (callable_qn) {
17731771
return py_func_return_type(ctx, callable_qn);
17741772
}
@@ -1796,7 +1794,7 @@ static const CBMType *py_eval_expr_type_uncached(PyLSPContext *ctx, TSNode node)
17961794
}
17971795
/* Even an UNKNOWN local/parameter is a real lexical shadow. Do
17981796
* not borrow the return type of a same-named module function. */
1799-
if (cbm_scope_contains(ctx->current_scope, fname))
1797+
if (binding)
18001798
return cbm_type_unknown();
18011799
// Module-local function call.
18021800
const CBMRegisteredFunc *f =
@@ -2566,14 +2564,15 @@ static void py_emit_call_for(PyLSPContext *ctx, TSNode call_node) {
25662564
* An ordinary local binding is also a hard shadow: if it is not a
25672565
* proven callable alias, do not fall through and fabricate a direct
25682566
* call to a module-level function with the same spelling. */
2569-
if (cbm_scope_contains(ctx->current_scope, fname)) {
2570-
const char *alias_target = cbm_scope_lookup_callable(ctx->current_scope, fname);
2567+
const CBMVarBinding *binding = cbm_scope_lookup_binding(ctx->current_scope, fname);
2568+
if (binding) {
2569+
const char *alias_target = binding->callable_qn;
25712570
if (alias_target) {
25722571
py_emit_resolved_call_reason(ctx, alias_target, "lsp_callable_alias", 0.97f, fname,
25732572
call_node);
25742573
return;
25752574
}
2576-
const CBMType *in_scope = cbm_scope_lookup(ctx->current_scope, fname);
2575+
const CBMType *in_scope = binding->type ? binding->type : cbm_type_unknown();
25772576
if (!cbm_type_is_unknown(in_scope) && in_scope->kind == CBM_TYPE_NAMED) {
25782577
const char *qn = in_scope->data.named.qualified_name;
25792578
const char *tail = qn ? strrchr(qn, '.') : NULL;
@@ -2604,13 +2603,6 @@ static void py_emit_call_for(PyLSPContext *ctx, TSNode call_node) {
26042603
}
26052604
return;
26062605
}
2607-
// Constructor call (ClassName())
2608-
const CBMType *in_scope = cbm_scope_lookup(ctx->current_scope, fname);
2609-
if (!cbm_type_is_unknown(in_scope) && in_scope->kind == CBM_TYPE_NAMED) {
2610-
const char *qn = in_scope->data.named.qualified_name;
2611-
py_emit_resolved_call(ctx, qn, "lsp_constructor", 0.85f, call_node);
2612-
return;
2613-
}
26142606
// Module-local function
26152607
const CBMRegisteredFunc *f =
26162608
cbm_registry_lookup_symbol(ctx->registry, ctx->module_qn, fname);

‎internal/cbm/lsp/scope.c‎

Lines changed: 16 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -88,52 +88,34 @@ bool cbm_scope_bind_callable_checked(CBMScope *scope, const char *name, const CB
8888
return cbm_scope_bind_value(scope, name, type, callable_qn);
8989
}
9090

91-
const CBMType* cbm_scope_lookup(const CBMScope* scope, const char* name) {
92-
if (!name) {
93-
return cbm_type_unknown();
94-
}
91+
const CBMVarBinding *cbm_scope_lookup_binding(const CBMScope *scope, const char *name) {
92+
if (!name)
93+
return NULL;
9594
for (const CBMScope* s = scope; s != NULL; s = s->parent) {
96-
for (CBMScopeChunk* c = s->chunks; c != NULL; c = c->next) {
95+
for (const CBMScopeChunk* c = s->chunks; c != NULL; c = c->next) {
9796
for (int i = 0; i < c->used; i++) {
98-
if (c->bindings[i].name && strcmp(c->bindings[i].name, name) == 0) {
99-
return c->bindings[i].type;
100-
}
97+
if (c->bindings[i].name && strcmp(c->bindings[i].name, name) == 0)
98+
return &c->bindings[i];
10199
}
102100
}
103101
}
102+
return NULL;
103+
}
104+
105+
const CBMType* cbm_scope_lookup(const CBMScope* scope, const char* name) {
106+
const CBMVarBinding *binding = cbm_scope_lookup_binding(scope, name);
107+
if (binding)
108+
return binding->type;
104109
return cbm_type_unknown();
105110
}
106111

107112
bool cbm_scope_contains(const CBMScope *scope, const char *name) {
108-
if (!name) {
109-
return false;
110-
}
111-
for (const CBMScope *s = scope; s != NULL; s = s->parent) {
112-
for (const CBMScopeChunk *c = s->chunks; c != NULL; c = c->next) {
113-
for (int i = 0; i < c->used; i++) {
114-
if (c->bindings[i].name && strcmp(c->bindings[i].name, name) == 0) {
115-
return true;
116-
}
117-
}
118-
}
119-
}
120-
return false;
113+
return cbm_scope_lookup_binding(scope, name) != NULL;
121114
}
122115

123116
const char *cbm_scope_lookup_callable(const CBMScope *scope, const char *name) {
124-
if (!name) {
125-
return NULL;
126-
}
127-
for (const CBMScope *s = scope; s != NULL; s = s->parent) {
128-
for (const CBMScopeChunk *c = s->chunks; c != NULL; c = c->next) {
129-
for (int i = 0; i < c->used; i++) {
130-
if (c->bindings[i].name && strcmp(c->bindings[i].name, name) == 0) {
131-
return c->bindings[i].callable_qn;
132-
}
133-
}
134-
}
135-
}
136-
return NULL;
117+
const CBMVarBinding *binding = cbm_scope_lookup_binding(scope, name);
118+
return binding ? binding->callable_qn : NULL;
137119
}
138120

139121
bool cbm_scope_update_callable(CBMScope *scope, const char *name, const char *callable_qn) {

‎internal/cbm/lsp/scope.h‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@ bool cbm_scope_bind_callable_checked(CBMScope *scope, const char *name, const CB
8282
void cbm_scope_bind_callable(CBMScope *scope, const char *name, const CBMType *type,
8383
const char *callable_qn);
8484
const CBMType* cbm_scope_lookup(const CBMScope* scope, const char* name);
85+
/* Return the complete nearest binding in one scope-chain walk, or NULL when
86+
* unbound. Callers that need presence, type, and callable identity together
87+
* should use this instead of repeating three linear scans. */
88+
const CBMVarBinding *cbm_scope_lookup_binding(const CBMScope *scope, const char *name);
8589
/* True when any lexical frame contains name, even when its type is UNKNOWN. */
8690
bool cbm_scope_contains(const CBMScope *scope, const char *name);
8791
/* Return the exact callable QN from the nearest binding. A nearer ordinary

‎tests/test_scope.c‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,25 @@ TEST(scope_callable_identity_follows_nearest_binding) {
125125
PASS();
126126
}
127127

128+
TEST(scope_lookup_binding_returns_nearest_complete_record) {
129+
CBMArena a;
130+
cbm_arena_init(&a);
131+
CBMScope *root = cbm_scope_push(&a, NULL);
132+
CBMScope *child = cbm_scope_push(&a, root);
133+
cbm_scope_bind_callable(root, "callback", named_t(&a, "Callback"), "pkg.actual");
134+
cbm_scope_bind(child, "callback", named_t(&a, "Shadow"));
135+
136+
const CBMVarBinding *binding = cbm_scope_lookup_binding(child, "callback");
137+
ASSERT_NOT_NULL(binding);
138+
ASSERT_STR_EQ(binding->name, "callback");
139+
ASSERT_STR_EQ(binding->type->data.named.qualified_name, "Shadow");
140+
ASSERT_NULL(binding->callable_qn);
141+
ASSERT_NULL(cbm_scope_lookup_binding(child, "missing"));
142+
143+
cbm_arena_destroy(&a);
144+
PASS();
145+
}
146+
128147
TEST(scope_assignment_updates_or_clears_nearest_callable_only) {
129148
CBMArena a;
130149
cbm_arena_init(&a);
@@ -260,6 +279,7 @@ SUITE(scope) {
260279
RUN_TEST(scope_lookup_walks_parent_chain);
261280
RUN_TEST(scope_child_shadows_parent);
262281
RUN_TEST(scope_callable_identity_follows_nearest_binding);
282+
RUN_TEST(scope_lookup_binding_returns_nearest_complete_record);
263283
RUN_TEST(scope_assignment_updates_or_clears_nearest_callable_only);
264284
RUN_TEST(scope_checked_bind_reports_child_oom_despite_parent_name);
265285
RUN_TEST(scope_dynamic_growth_300_bindings);

0 commit comments

Comments
 (0)