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 ;
0 commit comments