@@ -99,61 +99,166 @@ static void append_complexity_props(cbm_gbuf_node_t *node, int tld, bool recursi
9999 node -> properties_json = neu ;
100100}
101101
102+ /* Content-only node order: qualified_name, then file path and start line.
103+ * Never the temp id: extract workers draw ids from one shared counter, so id
104+ * order is worker-scheduling order and differs run to run. The cycle guard
105+ * flags whichever member the DFS ENTERS first, so both the seed order and the
106+ * callee order must be a function of the inputs alone, or `recursive` flips
107+ * between otherwise identical multi-worker runs. A dangling target (no node
108+ * for the id) has no edges of its own, so where it sorts cannot move a flag;
109+ * it keys as empty strings. */
110+ enum { TLD_CMP_LESS = -1 , TLD_CMP_GREATER = 1 };
111+
112+ static const char * str_or_empty (const char * s ) {
113+ return s ? s : "" ;
114+ }
115+
116+ static int cmp_node_canonical (const cbm_gbuf_node_t * a , const cbm_gbuf_node_t * b ) {
117+ int r = strcmp (str_or_empty (a ? a -> qualified_name : NULL ),
118+ str_or_empty (b ? b -> qualified_name : NULL ));
119+ if (r != 0 ) {
120+ return r ;
121+ }
122+ r = strcmp (str_or_empty (a ? a -> file_path : NULL ), str_or_empty (b ? b -> file_path : NULL ));
123+ if (r != 0 ) {
124+ return r ;
125+ }
126+ int la = a ? a -> start_line : 0 ;
127+ int lb = b ? b -> start_line : 0 ;
128+ if (la != lb ) {
129+ return la < lb ? TLD_CMP_LESS : TLD_CMP_GREATER ;
130+ }
131+ return 0 ;
132+ }
133+
134+ static int cmp_seed_canonical (const void * pa , const void * pb ) {
135+ return cmp_node_canonical (* (const cbm_gbuf_node_t * const * )pa ,
136+ * (const cbm_gbuf_node_t * const * )pb );
137+ }
138+
139+ typedef struct {
140+ const cbm_gbuf_node_t * node ; /* NULL for a dangling target id */
141+ int64_t id ;
142+ } tld_callee_t ;
143+
144+ static int cmp_callee_canonical (const void * pa , const void * pb ) {
145+ return cmp_node_canonical (((const tld_callee_t * )pa )-> node , ((const tld_callee_t * )pb )-> node );
146+ }
147+
148+ /* Traversal state. `callees` is one bump stack shared by every DFS frame: a
149+ * frame takes its out-degree worth of slots, sorts them, recurses, then
150+ * releases them. Nodes on the recursion path are distinct (state 1 blocks
151+ * re-entry), so the live slots never exceed the CALLS edge count the stack is
152+ * sized for. */
153+ typedef struct {
154+ const cbm_gbuf_t * gb ;
155+ int64_t maxid ;
156+ int * loop_depth ;
157+ int * tld ;
158+ char * state ;
159+ bool * recursive ;
160+ cbm_gbuf_node_t * * seeds ; /* Function/Method nodes, canonical order */
161+ int seed_count ;
162+ int seed_cap ;
163+ tld_callee_t * callees ;
164+ int callee_top ;
165+ int callee_cap ;
166+ } tld_ctx_t ;
167+
168+ static void tld_ctx_free (tld_ctx_t * cx ) {
169+ free (cx -> loop_depth );
170+ free (cx -> tld );
171+ free (cx -> state );
172+ free (cx -> recursive );
173+ free (cx -> seeds );
174+ free (cx -> callees );
175+ }
176+
102177/* Memoized DFS: tld(id) = loop_depth(id) + max over CALLS-callees of tld(callee).
103- * state: 0=unvisited, 1=in-progress (back-edge → cycle), 2=done. */
104- static int tld_dfs (const cbm_gbuf_t * gb , int64_t id , const int * loop_depth , int * tld , char * state ,
105- bool * recursive , int64_t maxid , int depth ) {
106- if (id < 1 || id > maxid ) {
178+ * state: 0=unvisited, 1=in-progress (back-edge -> cycle), 2=done. */
179+ static int tld_dfs (tld_ctx_t * cx , int64_t id , int depth ) {
180+ if (id < 1 || id > cx -> maxid ) {
107181 return 0 ;
108182 }
109- if (state [id ] == 2 ) {
110- return tld [id ];
183+ if (cx -> state [id ] == 2 ) {
184+ return cx -> tld [id ];
111185 }
112- if (state [id ] == 1 ) {
113- recursive [id ] = true; /* back edge → call-graph cycle */
186+ if (cx -> state [id ] == 1 ) {
187+ cx -> recursive [id ] = true; /* back edge -> call-graph cycle */
114188 return 0 ;
115189 }
116190 if (depth > CBM_TLD_MAX_DEPTH ) {
117- return loop_depth [id ];
191+ return cx -> loop_depth [id ];
118192 }
119- state [id ] = 1 ;
120- int best = 0 ;
121193 const cbm_gbuf_edge_t * * edges = NULL ;
122194 int ne = 0 ;
123- cbm_gbuf_find_edges_by_source_type (gb , id , "CALLS" , & edges , & ne );
195+ cbm_gbuf_find_edges_by_source_type (cx -> gb , id , "CALLS" , & edges , & ne );
196+ if (ne > cx -> callee_cap - cx -> callee_top ) {
197+ return cx -> loop_depth [id ]; /* unreachable by construction; same as the depth cap */
198+ }
199+ cx -> state [id ] = 1 ;
200+ tld_callee_t * callees = cx -> callees + cx -> callee_top ;
201+ int nc = 0 ;
124202 for (int i = 0 ; i < ne ; i ++ ) {
125203 int64_t c = edges [i ]-> target_id ;
126204 if (c == id ) {
127- recursive [id ] = true; /* direct self-recursion */
205+ cx -> recursive [id ] = true; /* direct self-recursion */
128206 continue ;
129207 }
130- int ct = tld_dfs (gb , c , loop_depth , tld , state , recursive , maxid , depth + 1 );
208+ callees [nc ].id = c ;
209+ callees [nc ].node = cbm_gbuf_find_by_id (cx -> gb , c );
210+ nc ++ ;
211+ }
212+ cx -> callee_top += nc ;
213+ qsort (callees , (size_t )nc , sizeof (* callees ), cmp_callee_canonical );
214+ int best = 0 ;
215+ for (int i = 0 ; i < nc ; i ++ ) {
216+ int ct = tld_dfs (cx , callees [i ].id , depth + 1 );
131217 if (ct > best ) {
132218 best = ct ;
133219 }
134220 }
135- tld [id ] = loop_depth [id ] + best ;
136- state [id ] = 2 ;
137- return tld [id ];
221+ cx -> callee_top -= nc ;
222+ cx -> tld [id ] = cx -> loop_depth [id ] + best ;
223+ cx -> state [id ] = 2 ;
224+ return cx -> tld [id ];
138225}
139226
140- /* Seed each Function/Method node's loop_depth and self_recursive flag, and
141- * remember the node pointer for write-back. The self_recursive seed (set at
142- * extraction) feeds the final recursive flag; tld_dfs additionally ORs in
143- * mutual recursion discovered as a call-graph cycle. */
144- static void seed_loop_depths (const cbm_gbuf_t * gb , const char * label , int * loop_depth ,
145- bool * recursive , cbm_gbuf_node_t * * nptr , int64_t maxid ) {
227+ static int label_count (const cbm_gbuf_t * gb , const char * label ) {
146228 const cbm_gbuf_node_t * * nodes = NULL ;
147229 int count = 0 ;
148230 if (cbm_gbuf_find_by_label (gb , label , & nodes , & count ) != 0 ) {
231+ return 0 ;
232+ }
233+ return count ;
234+ }
235+
236+ static int calls_edge_count (const cbm_gbuf_t * gb ) {
237+ const cbm_gbuf_edge_t * * edges = NULL ;
238+ int count = 0 ;
239+ if (cbm_gbuf_find_edges_by_type (gb , "CALLS" , & edges , & count ) != 0 ) {
240+ return 0 ;
241+ }
242+ return count ;
243+ }
244+
245+ /* Seed each Function/Method node's loop_depth and self_recursive flag, and
246+ * collect the node as a traversal seed (also the write-back target). The
247+ * self_recursive seed (set at extraction) feeds the final recursive flag;
248+ * tld_dfs additionally ORs in mutual recursion discovered as a call-graph
249+ * cycle. */
250+ static void seed_loop_depths (tld_ctx_t * cx , const char * label ) {
251+ const cbm_gbuf_node_t * * nodes = NULL ;
252+ int count = 0 ;
253+ if (cbm_gbuf_find_by_label (cx -> gb , label , & nodes , & count ) != 0 ) {
149254 return ;
150255 }
151- for (int i = 0 ; i < count ; i ++ ) {
256+ for (int i = 0 ; i < count && cx -> seed_count < cx -> seed_cap ; i ++ ) {
152257 const cbm_gbuf_node_t * n = nodes [i ];
153- if (n -> id >= 1 && n -> id <= maxid ) {
154- loop_depth [n -> id ] = json_get_int (n -> properties_json , "loop_depth" , 0 );
155- recursive [n -> id ] = json_get_bool (n -> properties_json , "self_recursive" );
156- nptr [ n -> id ] = (cbm_gbuf_node_t * )n ;
258+ if (n -> id >= 1 && n -> id <= cx -> maxid ) {
259+ cx -> loop_depth [n -> id ] = json_get_int (n -> properties_json , "loop_depth" , 0 );
260+ cx -> recursive [n -> id ] = json_get_bool (n -> properties_json , "self_recursive" );
261+ cx -> seeds [ cx -> seed_count ++ ] = (cbm_gbuf_node_t * )n ;
157262 }
158263 }
159264}
@@ -168,40 +273,35 @@ void cbm_pipeline_pass_complexity(cbm_pipeline_ctx_t *ctx) {
168273 return ;
169274 }
170275 size_t sz = (size_t )maxid + 1 ;
171- int * loop_depth = calloc (sz , sizeof (int ));
172- int * tld = calloc (sz , sizeof (int ));
173- char * state = calloc (sz , sizeof (char ));
174- bool * recursive = calloc (sz , sizeof (bool ));
175- cbm_gbuf_node_t * * nptr = calloc (sz , sizeof (cbm_gbuf_node_t * ));
176- if (!loop_depth || !tld || !state || !recursive || !nptr ) {
177- free (loop_depth );
178- free (tld );
179- free (state );
180- free (recursive );
181- free (nptr );
276+ tld_ctx_t cx = {0 };
277+ cx .gb = gb ;
278+ cx .maxid = maxid ;
279+ cx .seed_cap = label_count (gb , "Function" ) + label_count (gb , "Method" );
280+ cx .callee_cap = calls_edge_count (gb );
281+ cx .loop_depth = calloc (sz , sizeof (int ));
282+ cx .tld = calloc (sz , sizeof (int ));
283+ cx .state = calloc (sz , sizeof (char ));
284+ cx .recursive = calloc (sz , sizeof (bool ));
285+ cx .seeds = calloc ((size_t )cx .seed_cap + 1 , sizeof (cbm_gbuf_node_t * ));
286+ cx .callees = calloc ((size_t )cx .callee_cap + 1 , sizeof (tld_callee_t ));
287+ if (!cx .loop_depth || !cx .tld || !cx .state || !cx .recursive || !cx .seeds || !cx .callees ) {
288+ tld_ctx_free (& cx );
182289 return ;
183290 }
184291
185- seed_loop_depths (gb , "Function" , loop_depth , recursive , nptr , maxid );
186- seed_loop_depths (gb , "Method" , loop_depth , recursive , nptr , maxid );
292+ seed_loop_depths (& cx , "Function" );
293+ seed_loop_depths (& cx , "Method" );
294+ qsort (cx .seeds , (size_t )cx .seed_count , sizeof (* cx .seeds ), cmp_seed_canonical );
187295
188- int updated = 0 ;
189- for (int64_t id = 1 ; id <= maxid ; id ++ ) {
190- if (!nptr [id ]) {
191- continue ; /* only Function/Method nodes */
192- }
193- if (state [id ] != 2 ) {
194- tld_dfs (gb , id , loop_depth , tld , state , recursive , maxid , 0 );
296+ for (int i = 0 ; i < cx .seed_count ; i ++ ) {
297+ cbm_gbuf_node_t * n = cx .seeds [i ];
298+ if (cx .state [n -> id ] != 2 ) {
299+ tld_dfs (& cx , n -> id , 0 );
195300 }
196- append_complexity_props (nptr [id ], tld [id ], recursive [id ]);
197- updated ++ ;
301+ append_complexity_props (n , cx .tld [n -> id ], cx .recursive [n -> id ]);
198302 }
199303
200- cbm_log_info ("pass.complexity" , "functions" , itoa_cx (updated ));
304+ cbm_log_info ("pass.complexity" , "functions" , itoa_cx (cx . seed_count ));
201305
202- free (loop_depth );
203- free (tld );
204- free (state );
205- free (recursive );
206- free (nptr );
306+ tld_ctx_free (& cx );
207307}
0 commit comments