@@ -843,6 +843,22 @@ impl<'a> FunctionPrinter<'a> {
843843 }
844844}
845845
846+ /// Pretty printer for [`Function`].
847+ pub struct FunctionGraphvizPrinter < ' a > {
848+ fun : & ' a Function ,
849+ ptr_map : PtrPrintMap ,
850+ }
851+
852+ impl < ' a > FunctionGraphvizPrinter < ' a > {
853+ pub fn new ( fun : & ' a Function ) -> Self {
854+ let mut ptr_map = PtrPrintMap :: identity ( ) ;
855+ if cfg ! ( test) {
856+ ptr_map. map_ptrs = true ;
857+ }
858+ Self { fun, ptr_map }
859+ }
860+ }
861+
846862/// Union-Find (Disjoint-Set) is a data structure for managing disjoint sets that has an interface
847863/// of two operations:
848864///
@@ -2115,6 +2131,10 @@ impl Function {
21152131 Some ( DumpHIR :: Debug ) => println ! ( "Optimized HIR:\n {:#?}" , & self ) ,
21162132 None => { } ,
21172133 }
2134+
2135+ if get_option ! ( dump_hir_graphviz) {
2136+ println ! ( "{}" , FunctionGraphvizPrinter :: new( & self ) ) ;
2137+ }
21182138 }
21192139
21202140
@@ -2293,6 +2313,87 @@ impl<'a> std::fmt::Display for FunctionPrinter<'a> {
22932313 }
22942314}
22952315
2316+ struct HtmlEncoder < ' a , ' b > {
2317+ formatter : & ' a mut std:: fmt:: Formatter < ' b > ,
2318+ }
2319+
2320+ impl < ' a , ' b > std:: fmt:: Write for HtmlEncoder < ' a , ' b > {
2321+ fn write_str ( & mut self , s : & str ) -> std:: fmt:: Result {
2322+ for ch in s. chars ( ) {
2323+ match ch {
2324+ '<' => self . formatter . write_str ( "<" ) ?,
2325+ '>' => self . formatter . write_str ( ">" ) ?,
2326+ '&' => self . formatter . write_str ( "&" ) ?,
2327+ '"' => self . formatter . write_str ( """ ) ?,
2328+ '\'' => self . formatter . write_str ( "'" ) ?,
2329+ _ => self . formatter . write_char ( ch) ?,
2330+ }
2331+ }
2332+ Ok ( ( ) )
2333+ }
2334+ }
2335+
2336+ impl < ' a > std:: fmt:: Display for FunctionGraphvizPrinter < ' a > {
2337+ fn fmt ( & self , f : & mut std:: fmt:: Formatter ) -> std:: fmt:: Result {
2338+ macro_rules! write_encoded {
2339+ ( $f: ident, $( $arg: tt) * ) => {
2340+ HtmlEncoder { formatter: $f } . write_fmt( format_args!( $( $arg) * ) )
2341+ } ;
2342+ }
2343+ use std:: fmt:: Write ;
2344+ let fun = & self . fun ;
2345+ let iseq_name = iseq_get_location ( fun. iseq , 0 ) ;
2346+ write ! ( f, "digraph G {{ # " ) ?;
2347+ write_encoded ! ( f, "{iseq_name}" ) ?;
2348+ write ! ( f, "\n " ) ?;
2349+ writeln ! ( f, "node [shape=plaintext];" ) ?;
2350+ writeln ! ( f, "mode=hier; overlap=false; splines=true;" ) ?;
2351+ for block_id in fun. rpo ( ) {
2352+ writeln ! ( f, r#" {block_id} [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">"# ) ?;
2353+ write ! ( f, r#"<TR><TD ALIGN="LEFT" PORT="params" BGCOLOR="gray">{block_id}("# ) ?;
2354+ if !fun. blocks [ block_id. 0 ] . params . is_empty ( ) {
2355+ let mut sep = "" ;
2356+ for param in & fun. blocks [ block_id. 0 ] . params {
2357+ write_encoded ! ( f, "{sep}{param}" ) ?;
2358+ let insn_type = fun. type_of ( * param) ;
2359+ if !insn_type. is_subtype ( types:: Empty ) {
2360+ write_encoded ! ( f, ":{}" , insn_type. print( & self . ptr_map) ) ?;
2361+ }
2362+ sep = ", " ;
2363+ }
2364+ }
2365+ let mut edges = vec ! [ ] ;
2366+ writeln ! ( f, ") </TD></TR>" ) ?;
2367+ for insn_id in & fun. blocks [ block_id. 0 ] . insns {
2368+ let insn_id = fun. union_find . borrow ( ) . find_const ( * insn_id) ;
2369+ let insn = fun. find ( insn_id) ;
2370+ if matches ! ( insn, Insn :: Snapshot { ..} ) {
2371+ continue ;
2372+ }
2373+ write ! ( f, r#"<TR><TD ALIGN="left" PORT="{insn_id}">"# ) ?;
2374+ if insn. has_output ( ) {
2375+ let insn_type = fun. type_of ( insn_id) ;
2376+ if insn_type. is_subtype ( types:: Empty ) {
2377+ write_encoded ! ( f, "{insn_id} = " ) ?;
2378+ } else {
2379+ write_encoded ! ( f, "{insn_id}:{} = " , insn_type. print( & self . ptr_map) ) ?;
2380+ }
2381+ }
2382+ if let Insn :: Jump ( ref target) | Insn :: IfTrue { ref target, .. } | Insn :: IfFalse { ref target, .. } = insn {
2383+ edges. push ( ( insn_id, target. target ) ) ;
2384+ }
2385+ write_encoded ! ( f, "{}" , insn. print( & self . ptr_map) ) ?;
2386+ writeln ! ( f, " </TD></TR>" ) ?;
2387+ }
2388+ writeln ! ( f, "</TABLE>>];" ) ?;
2389+ for ( src, dst) in edges {
2390+ writeln ! ( f, " {block_id}:{src} -> {dst}:params;" ) ?;
2391+ }
2392+ }
2393+ writeln ! ( f, "}}" )
2394+ }
2395+ }
2396+
22962397#[ derive( Debug , Clone , PartialEq ) ]
22972398pub struct FrameState {
22982399 iseq : IseqPtr ,
@@ -5145,6 +5246,81 @@ mod tests {
51455246 }
51465247}
51475248
5249+ #[ cfg( test) ]
5250+ mod graphviz_tests {
5251+ use super :: * ;
5252+ use expect_test:: { expect, Expect } ;
5253+
5254+ #[ track_caller]
5255+ fn assert_optimized_graphviz ( method : & str , expected : Expect ) {
5256+ let iseq = crate :: cruby:: with_rubyvm ( || get_method_iseq ( "self" , method) ) ;
5257+ unsafe { crate :: cruby:: rb_zjit_profile_disable ( iseq) } ;
5258+ let mut function = iseq_to_hir ( iseq) . unwrap ( ) ;
5259+ function. optimize ( ) ;
5260+ function. validate ( ) . unwrap ( ) ;
5261+ let actual = format ! ( "{}" , FunctionGraphvizPrinter :: new( & function) ) ;
5262+ expected. assert_eq ( & actual) ;
5263+ }
5264+
5265+ #[ test]
5266+ fn test_guard_fixnum_or_fixnum ( ) {
5267+ eval ( r#"
5268+ def test(x, y) = x | y
5269+
5270+ test(1, 2)
5271+ "# ) ;
5272+ assert_optimized_graphviz ( "test" , expect ! [ [ r#"
5273+ digraph G { # test@<compiled>:2
5274+ node [shape=plaintext];
5275+ mode=hier; overlap=false; splines=true;
5276+ bb0 [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
5277+ <TR><TD ALIGN="LEFT" PORT="params" BGCOLOR="gray">bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject) </TD></TR>
5278+ <TR><TD ALIGN="left" PORT="v7">PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, 29) </TD></TR>
5279+ <TR><TD ALIGN="left" PORT="v8">v8:Fixnum = GuardType v1, Fixnum </TD></TR>
5280+ <TR><TD ALIGN="left" PORT="v9">v9:Fixnum = GuardType v2, Fixnum </TD></TR>
5281+ <TR><TD ALIGN="left" PORT="v10">v10:Fixnum = FixnumOr v8, v9 </TD></TR>
5282+ <TR><TD ALIGN="left" PORT="v6">Return v10 </TD></TR>
5283+ </TABLE>>];
5284+ }
5285+ "# ] ] ) ;
5286+ }
5287+
5288+ #[ test]
5289+ fn test_multiple_blocks ( ) {
5290+ eval ( r#"
5291+ def test(c)
5292+ if c
5293+ 3
5294+ else
5295+ 4
5296+ end
5297+ end
5298+
5299+ test(1)
5300+ test("x")
5301+ "# ) ;
5302+ assert_optimized_graphviz ( "test" , expect ! [ [ r#"
5303+ digraph G { # test@<compiled>:3
5304+ node [shape=plaintext];
5305+ mode=hier; overlap=false; splines=true;
5306+ bb0 [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
5307+ <TR><TD ALIGN="LEFT" PORT="params" BGCOLOR="gray">bb0(v0:BasicObject, v1:BasicObject) </TD></TR>
5308+ <TR><TD ALIGN="left" PORT="v3">v3:CBool = Test v1 </TD></TR>
5309+ <TR><TD ALIGN="left" PORT="v4">IfFalse v3, bb1(v0, v1) </TD></TR>
5310+ <TR><TD ALIGN="left" PORT="v5">v5:Fixnum[3] = Const Value(3) </TD></TR>
5311+ <TR><TD ALIGN="left" PORT="v6">Return v5 </TD></TR>
5312+ </TABLE>>];
5313+ bb0:v4 -> bb1:params;
5314+ bb1 [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
5315+ <TR><TD ALIGN="LEFT" PORT="params" BGCOLOR="gray">bb1(v7:BasicObject, v8:BasicObject) </TD></TR>
5316+ <TR><TD ALIGN="left" PORT="v10">v10:Fixnum[4] = Const Value(4) </TD></TR>
5317+ <TR><TD ALIGN="left" PORT="v11">Return v10 </TD></TR>
5318+ </TABLE>>];
5319+ }
5320+ "# ] ] ) ;
5321+ }
5322+ }
5323+
51485324#[ cfg( test) ]
51495325mod opt_tests {
51505326 use super :: * ;
0 commit comments