@@ -11,7 +11,9 @@ use crate::printer::SmirJson;
1111use crate :: MonoItemKind ;
1212
1313use crate :: mk_graph:: context:: GraphContext ;
14- use crate :: mk_graph:: util:: { block_name, is_unqualified, name_lines, short_name, GraphLabelString } ;
14+ use crate :: mk_graph:: util:: {
15+ block_name, escape_dot, is_unqualified, name_lines, short_name, GraphLabelString ,
16+ } ;
1517
1618use crate :: mk_graph:: traverse:: render_graph;
1719use crate :: mk_graph:: traverse:: { GraphBuilder , RenderedFunction } ;
@@ -311,16 +313,27 @@ impl SmirJson {
311313}
312314
313315// =============================================================================
314- // DOT Builder
316+ // DOT Builder (new)
315317// =============================================================================
316318
317319pub struct DOTBuilder {
318320 buf : String ,
321+ /// Cross-cluster call edges deferred until finish():
322+ /// (from_node_id, to_node_id, rendered_args)
323+ deferred_call_edges : Vec < ( String , String , String ) > ,
319324}
320325
321326impl DOTBuilder {
322327 pub fn new ( ) -> Self {
323- Self { buf : String :: new ( ) }
328+ Self {
329+ buf : String :: new ( ) ,
330+ deferred_call_edges : Vec :: new ( ) ,
331+ }
332+ }
333+
334+ fn line ( & mut self , s : & str ) {
335+ self . buf . push_str ( s) ;
336+ self . buf . push ( '\n' ) ;
324337 }
325338}
326339
@@ -333,27 +346,164 @@ impl Default for DOTBuilder {
333346impl GraphBuilder for DOTBuilder {
334347 type Output = String ;
335348
336- fn begin_graph ( & mut self , _name : & str ) { }
349+ fn begin_graph ( & mut self , name : & str ) {
350+ self . line ( "digraph {" ) ;
351+ self . line ( & format ! ( " label=\" {}\" ;" , escape_dot( name) ) ) ;
352+ self . line ( " node [shape=rectangle];" ) ;
353+ }
354+
355+ fn alloc_legend ( & mut self , lines : & [ String ] ) {
356+ if lines. is_empty ( ) {
357+ return ;
358+ }
359+ let mut all_lines = lines. to_vec ( ) ;
360+ all_lines. push ( String :: new ( ) ) ;
361+ let label = all_lines
362+ . iter ( )
363+ . map ( |l| escape_dot ( l) )
364+ . collect :: < Vec < _ > > ( )
365+ . join ( "\\ l" ) ;
366+ self . line ( & format ! (
367+ " node_allocs [label=\" {}\" , style=\" filled\" , color=lightyellow];" ,
368+ label
369+ ) ) ;
370+ }
371+
372+ fn type_legend ( & mut self , lines : & [ String ] ) {
373+ // Only emit when there are entries beyond the header line.
374+ if lines. len ( ) <= 1 {
375+ return ;
376+ }
377+ let mut all_lines = lines. to_vec ( ) ;
378+ all_lines. push ( String :: new ( ) ) ;
379+ let label = all_lines
380+ . iter ( )
381+ . map ( |l| escape_dot ( l) )
382+ . collect :: < Vec < _ > > ( )
383+ . join ( "\\ l" ) ;
384+ self . line ( & format ! (
385+ " node_types [label=\" {}\" , style=\" filled\" , color=lavender];" ,
386+ label
387+ ) ) ;
388+ }
389+
390+ /// Emit a red external node for a callee that has no defined body.
391+ /// `name` is the full resolved symbol name; block_name(name, 0) gives
392+ /// the node ID, matching the target IDs used in deferred call edges.
393+ fn external_function ( & mut self , name : & str ) {
394+ let node_id = block_name ( name, 0 ) ;
395+ let label = escape_dot ( & name_lines ( name) ) ;
396+ self . line ( & format ! ( " {} [label=\" {}\" , color=red];" , node_id, label) ) ;
397+ }
337398
338- fn alloc_legend ( & mut self , _lines : & [ String ] ) { }
399+ fn render_function ( & mut self , func : & RenderedFunction ) {
400+ let cluster_color = if func. is_unqualified {
401+ "palegreen"
402+ } else {
403+ "lightgray"
404+ } ;
405+
406+ self . line ( & format ! (
407+ " subgraph cluster_{} {{" ,
408+ short_name( & func. symbol_name)
409+ ) ) ;
410+ self . line ( & format ! (
411+ " label=\" {}\" ;" ,
412+ escape_dot( & func. display_name)
413+ ) ) ;
414+ self . line ( " style=\" filled\" ;" ) ;
415+ self . line ( & format ! ( " color={};" , cluster_color) ) ;
416+
417+ // LOCALS node
418+ {
419+ let mut lines = vec ! [ "LOCALS" . to_string( ) ] ;
420+ for ( idx, ty) in & func. locals {
421+ lines. push ( format ! ( "{} = {}" , idx, ty) ) ;
422+ }
423+ lines. push ( String :: new ( ) ) ;
424+ let label = lines
425+ . iter ( )
426+ . map ( |l| escape_dot ( l) )
427+ . collect :: < Vec < _ > > ( )
428+ . join ( "\\ l" ) ;
429+ self . line ( & format ! (
430+ " {}_locals [label=\" {}\" , style=\" filled\" , color=palegreen3];" ,
431+ short_name( & func. symbol_name) ,
432+ label
433+ ) ) ;
434+ }
339435
340- fn type_legend ( & mut self , _lines : & [ String ] ) { }
436+ // Block nodes
437+ for block in & func. blocks {
438+ let node_id = block_name ( & func. symbol_name , block. idx ) ;
439+ let mut parts: Vec < String > = block. stmts . clone ( ) ;
440+ parts. push ( block. terminator . clone ( ) ) ;
441+ parts. push ( String :: new ( ) ) ;
442+ let label = parts
443+ . iter ( )
444+ . map ( |l| escape_dot ( l) )
445+ . collect :: < Vec < _ > > ( )
446+ . join ( "\\ l" ) ;
447+ self . line ( & format ! ( " {} [label=\" {}\" ];" , node_id, label) ) ;
448+ }
341449
342- fn external_function ( & mut self , _id : & str , _name : & str ) { }
450+ // Intra-cluster CFG edges
451+ for block in & func. blocks {
452+ let from = block_name ( & func. symbol_name , block. idx ) ;
453+ for ( target, label_opt) in & block. cfg_edges {
454+ let to = block_name ( & func. symbol_name , * target) ;
455+ match label_opt {
456+ Some ( lbl) => self . line ( & format ! (
457+ " {} -> {} [label=\" {}\" ];" ,
458+ from,
459+ to,
460+ escape_dot( lbl)
461+ ) ) ,
462+ None => self . line ( & format ! ( " {} -> {};" , from, to) ) ,
463+ }
464+ }
465+ }
343466
344- fn render_function ( & mut self , _func : & RenderedFunction ) { }
467+ self . line ( " }" ) ;
468+
469+ // Defer cross-cluster call edges - emitted after all clusters in finish().
470+ // callee_name is the full symbol name so block_name(callee_name, 0)
471+ // matches the node ID emitted by external_function or by another
472+ // cluster's block 0.
473+ for edge in & func. call_edges {
474+ let from = block_name ( & func. symbol_name , edge. block_idx ) ;
475+ let to = block_name ( & edge. callee_name , 0 ) ;
476+ self . deferred_call_edges
477+ . push ( ( from, to, edge. rendered_args . clone ( ) ) ) ;
478+ }
479+ }
345480
346- fn static_item ( & mut self , _id : & str , _name : & str ) { }
481+ fn static_item ( & mut self , id : & str , name : & str ) {
482+ self . line ( & format ! ( " {} [label=\" {}\" ];" , id, escape_dot( name) ) ) ;
483+ }
347484
348- fn asm_item ( & mut self , _id : & str , _content : & str ) { }
485+ fn asm_item ( & mut self , id : & str , content : & str ) {
486+ let text = content. lines ( ) . collect :: < String > ( ) ;
487+ self . line ( & format ! ( " {} [label=\" {}\" ];" , id, escape_dot( & text) ) ) ;
488+ }
349489
350- fn finish ( self ) -> Self :: Output {
490+ fn finish ( mut self ) -> String {
491+ // Emit all cross-cluster call edges after all subgraph clusters.
492+ for ( from, to, args) in & self . deferred_call_edges {
493+ self . buf . push_str ( & format ! (
494+ " {} -> {} [label=\" {}\" ];\n " ,
495+ from,
496+ to,
497+ escape_dot( args)
498+ ) ) ;
499+ }
500+ self . buf . push_str ( "}\n " ) ;
351501 self . buf
352502 }
353503}
354504
355505// =============================================================================
356- // Public entry point (new)
506+ // Public entry points (new)
357507// =============================================================================
358508
359509impl SmirJson {
0 commit comments