Skip to content

Commit 56700bd

Browse files
authored
Break-glass merge PR #2535
2 parents 910333b + 4a2ad91 commit 56700bd

6 files changed

Lines changed: 1107 additions & 39 deletions

File tree

‎internal/cbm/lsp/py_lsp.c‎

Lines changed: 125 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,18 @@
4343
#ifdef CBM_ENABLE_TEST_SEAMS
4444
_Atomic uint64_t cbm_py_submodule_probes = 0;
4545
_Atomic uint64_t cbm_py_submodule_probe_visits = 0;
46+
_Atomic uint64_t cbm_py_module_class_binds = 0;
47+
_Atomic uint64_t cbm_py_module_class_bind_visits = 0;
48+
_Atomic uint64_t cbm_py_wildcard_invalidations = 0;
49+
_Atomic uint64_t cbm_py_wildcard_invalidation_visits = 0;
50+
static _Thread_local int py_module_scratch_allocations_left = -1;
51+
static _Thread_local int py_module_prefix_allocations_left = -1;
52+
void cbm_py_test_module_scratch_fail_after(int successful_allocations) {
53+
py_module_scratch_allocations_left = successful_allocations;
54+
}
55+
void cbm_py_test_module_prefix_fail_after(int successful_allocations) {
56+
py_module_prefix_allocations_left = successful_allocations;
57+
}
4658
#endif
4759

4860
#ifdef CBM_ENABLE_TEST_SEAMS
@@ -4643,6 +4655,38 @@ static CBMScope *py_root_class_names(PyLSPContext *ctx, const PyKids *rk) {
46434655
return names;
46444656
}
46454657

4658+
/* The temporary query owner never supplies a retained binding/name/type.
4659+
* A disabled/failed optimization leaves the original module-aware scan usable. */
4660+
static bool py_module_scratch_open(CBMArena *scratch) {
4661+
#ifdef CBM_ENABLE_TEST_SEAMS
4662+
if (py_module_scratch_allocations_left == 0)
4663+
return false;
4664+
if (py_module_scratch_allocations_left > 0)
4665+
py_module_scratch_allocations_left--;
4666+
#endif
4667+
cbm_arena_init_lazy(scratch, 4096);
4668+
return true;
4669+
}
4670+
4671+
static char *py_module_prefix(CBMArena *scratch, const char *module_qn, size_t length) {
4672+
/* The arena rounds up by seven bytes after this addition. */
4673+
if (length > SIZE_MAX - 9)
4674+
return NULL;
4675+
#ifdef CBM_ENABLE_TEST_SEAMS
4676+
if (py_module_prefix_allocations_left == 0)
4677+
return NULL;
4678+
if (py_module_prefix_allocations_left > 0)
4679+
py_module_prefix_allocations_left--;
4680+
#endif
4681+
char *prefix = cbm_arena_alloc(scratch, length + 2);
4682+
if (!prefix)
4683+
return NULL;
4684+
memcpy(prefix, module_qn, length);
4685+
prefix[length] = '.';
4686+
prefix[length + 1] = '\0';
4687+
return prefix;
4688+
}
4689+
46464690
/* A project registry can contain another file's class in the same logical
46474691
* Python module. Keep those cross-file globals available, but do not prebind
46484692
* classes declared by this source: their binding epoch belongs in the ordered
@@ -4659,20 +4703,49 @@ static void py_bind_external_module_classes(PyLSPContext *ctx, const PyKids *rk)
46594703
}
46604704
size_t prefix_len = strlen(ctx->module_qn);
46614705
bool package_scope = ctx->file_module_qn && strcmp(ctx->file_module_qn, ctx->module_qn) != 0;
4662-
CBMTypeShortIter all_types;
4663-
cbm_registry_all_types_chain(ctx->registry, &all_types);
4664-
for (int i = -1; (i = cbm_type_short_iter_next(&all_types)) >= 0;) {
4665-
const CBMRegisteredType *type = &all_types.reg->types[i];
4666-
const char *qn = type->qualified_name;
4667-
const char *name = type->short_name;
4668-
if (!qn || !name || strncmp(qn, ctx->module_qn, prefix_len) != 0 || qn[prefix_len] != '.' ||
4669-
cbm_scope_lookup_local(defined, name)) {
4670-
continue;
4706+
CBMArena scratch = {0};
4707+
char *prefix = py_module_scratch_open(&scratch)
4708+
? py_module_prefix(&scratch, ctx->module_qn, prefix_len)
4709+
: NULL;
4710+
uint64_t visits = 0;
4711+
for (const CBMTypeRegistry *link = ctx->registry; link; link = link->fallback) {
4712+
CBMQNPrefixIter it;
4713+
if (prefix)
4714+
cbm_registry_types_with_qn_prefix(link, prefix, package_scope, &scratch, &it);
4715+
int linear = 0;
4716+
for (;;) {
4717+
int i;
4718+
if (prefix) {
4719+
i = cbm_qn_prefix_iter_next(&it);
4720+
} else {
4721+
i = linear < link->type_count ? linear++ : -1;
4722+
if (i >= 0)
4723+
visits++;
4724+
}
4725+
if (i < 0)
4726+
break;
4727+
if (cbm_registry_type_shadowed(ctx->registry, link, i))
4728+
continue;
4729+
const CBMRegisteredType *type = &link->types[i];
4730+
const char *qn = type->qualified_name;
4731+
const char *name = type->short_name;
4732+
if (!qn || !name || strncmp(qn, ctx->module_qn, prefix_len) != 0 ||
4733+
qn[prefix_len] != '.' || cbm_scope_lookup_local(defined, name))
4734+
continue;
4735+
if (package_scope && strchr(qn + prefix_len + 1, '.'))
4736+
continue; /* submodule/nested classes are not package globals */
4737+
py_scope_bind(ctx, name, cbm_type_named(ctx->arena, qn));
46714738
}
4672-
if (package_scope && strchr(qn + prefix_len + 1, '.'))
4673-
continue; /* submodule/nested classes are not package globals */
4674-
py_scope_bind(ctx, name, cbm_type_named(ctx->arena, qn));
4739+
if (prefix)
4740+
visits += it.visits;
46754741
}
4742+
cbm_arena_destroy(&scratch);
4743+
#ifdef CBM_ENABLE_TEST_SEAMS
4744+
atomic_fetch_add_explicit(&cbm_py_module_class_binds, 1, memory_order_relaxed);
4745+
atomic_fetch_add_explicit(&cbm_py_module_class_bind_visits, visits, memory_order_relaxed);
4746+
#else
4747+
(void)visits;
4748+
#endif
46764749
}
46774750

46784751
#ifdef CBM_ENABLE_TEST_SEAMS
@@ -4746,19 +4819,47 @@ static void py_invalidate_registry_module_functions(PyLSPContext *ctx,
47464819
if (!ctx || !registry || !ctx->module_qn)
47474820
return;
47484821
size_t prefix_len = strlen(ctx->module_qn);
4749-
for (int i = 0; i < registry->func_count; i++) {
4750-
const CBMRegisteredFunc *func = &registry->funcs[i];
4751-
const char *qn = func->qualified_name;
4752-
if (!qn || func->receiver_type || strncmp(qn, ctx->module_qn, prefix_len) != 0 ||
4753-
qn[prefix_len] != '.') {
4754-
continue;
4822+
CBMArena scratch = {0};
4823+
char *prefix = py_module_scratch_open(&scratch)
4824+
? py_module_prefix(&scratch, ctx->module_qn, prefix_len)
4825+
: NULL;
4826+
uint64_t visits = 0;
4827+
for (const CBMTypeRegistry *link = registry; link; link = link->fallback) {
4828+
CBMQNPrefixIter it;
4829+
if (prefix)
4830+
cbm_registry_funcs_with_qn_prefix(link, prefix, true, &scratch, &it);
4831+
int linear = 0;
4832+
for (;;) {
4833+
int i;
4834+
if (prefix) {
4835+
i = cbm_qn_prefix_iter_next(&it);
4836+
} else {
4837+
i = linear < link->func_count ? linear++ : -1;
4838+
if (i >= 0)
4839+
visits++;
4840+
}
4841+
if (i < 0)
4842+
break;
4843+
const CBMRegisteredFunc *func = &link->funcs[i];
4844+
const char *qn = func->qualified_name;
4845+
if (!qn || func->receiver_type || strncmp(qn, ctx->module_qn, prefix_len) != 0 ||
4846+
qn[prefix_len] != '.')
4847+
continue;
4848+
const char *suffix = qn + prefix_len + 1;
4849+
if (!suffix[0] || strchr(suffix, '.'))
4850+
continue;
4851+
py_scope_bind(ctx, func->short_name ? func->short_name : suffix, cbm_type_unknown());
47554852
}
4756-
const char *suffix = qn + prefix_len + 1;
4757-
if (!suffix[0] || strchr(suffix, '.'))
4758-
continue;
4759-
py_scope_bind(ctx, func->short_name ? func->short_name : suffix, cbm_type_unknown());
4853+
if (prefix)
4854+
visits += it.visits;
47604855
}
4761-
py_invalidate_registry_module_functions(ctx, registry->fallback);
4856+
cbm_arena_destroy(&scratch);
4857+
#ifdef CBM_ENABLE_TEST_SEAMS
4858+
atomic_fetch_add_explicit(&cbm_py_wildcard_invalidations, 1, memory_order_relaxed);
4859+
atomic_fetch_add_explicit(&cbm_py_wildcard_invalidation_visits, visits, memory_order_relaxed);
4860+
#else
4861+
(void)visits;
4862+
#endif
47624863
}
47634864

47644865
/* `from module import *` may overwrite any module global through `__all__`.
@@ -5749,6 +5850,7 @@ CBMTypeRegistry *cbm_py_build_cross_registry(CBMArena *arena, CBMLSPDef *defs, i
57495850
}
57505851

57515852
py_mark_ambiguous_callable_bindings(reg, arena);
5853+
cbm_registry_build_type_qn_order(reg, arena);
57525854
cbm_registry_finalize(reg);
57535855
reg->read_only = true; /* seal: shared Tier-2 registry is read-only during resolve */
57545856
return reg;

‎internal/cbm/lsp/py_lsp.h‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,15 @@ bool cbm_batch_py_lsp_cross(CBMArena *arena, CBMBatchPyLSPFile *files, int file_
250250
* Only missing exact symbol/type attributes reach this submodule predicate. */
251251
extern _Atomic uint64_t cbm_py_submodule_probes;
252252
extern _Atomic uint64_t cbm_py_submodule_probe_visits;
253+
/* Complete module operations, including optimization fallbacks. Visits count
254+
* inspected QNs/search comparisons, not sorting or string-length work. */
255+
extern _Atomic uint64_t cbm_py_module_class_binds;
256+
extern _Atomic uint64_t cbm_py_module_class_bind_visits;
257+
extern _Atomic uint64_t cbm_py_wildcard_invalidations;
258+
extern _Atomic uint64_t cbm_py_wildcard_invalidation_visits;
259+
/* -1 disables; zero fails until reset; positive counts down attempts. */
260+
void cbm_py_test_module_scratch_fail_after(int successful_allocations);
261+
void cbm_py_test_module_prefix_fail_after(int successful_allocations);
253262
#endif
254263

255264
#endif // CBM_LSP_PY_LSP_H

0 commit comments

Comments
 (0)