Skip to content

Commit 8eb26eb

Browse files
authored
ZJIT: Add a graphviz dumper for HIR (ruby#14117)
This is moderately useful just in stdout (copy and paste into a renderer) but potentially more useful alongside a tool that parses stdout looking for `digraph G { ... }` and renders those automatically.
1 parent 057d7c1 commit 8eb26eb

File tree

2 files changed

+180
-0
lines changed

2 files changed

+180
-0
lines changed

zjit/src/hir.rs

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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("&lt;")?,
2325+
'>' => self.formatter.write_str("&gt;")?,
2326+
'&' => self.formatter.write_str("&amp;")?,
2327+
'"' => self.formatter.write_str("&quot;")?,
2328+
'\'' => self.formatter.write_str("&#39;")?,
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, ")&nbsp;</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, "&nbsp;</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)]
22972398
pub 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@&lt;compiled&gt;: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)&nbsp;</TD></TR>
5278+
<TR><TD ALIGN="left" PORT="v7">PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, 29)&nbsp;</TD></TR>
5279+
<TR><TD ALIGN="left" PORT="v8">v8:Fixnum = GuardType v1, Fixnum&nbsp;</TD></TR>
5280+
<TR><TD ALIGN="left" PORT="v9">v9:Fixnum = GuardType v2, Fixnum&nbsp;</TD></TR>
5281+
<TR><TD ALIGN="left" PORT="v10">v10:Fixnum = FixnumOr v8, v9&nbsp;</TD></TR>
5282+
<TR><TD ALIGN="left" PORT="v6">Return v10&nbsp;</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@&lt;compiled&gt;: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)&nbsp;</TD></TR>
5308+
<TR><TD ALIGN="left" PORT="v3">v3:CBool = Test v1&nbsp;</TD></TR>
5309+
<TR><TD ALIGN="left" PORT="v4">IfFalse v3, bb1(v0, v1)&nbsp;</TD></TR>
5310+
<TR><TD ALIGN="left" PORT="v5">v5:Fixnum[3] = Const Value(3)&nbsp;</TD></TR>
5311+
<TR><TD ALIGN="left" PORT="v6">Return v5&nbsp;</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)&nbsp;</TD></TR>
5316+
<TR><TD ALIGN="left" PORT="v10">v10:Fixnum[4] = Const Value(4)&nbsp;</TD></TR>
5317+
<TR><TD ALIGN="left" PORT="v11">Return v10&nbsp;</TD></TR>
5318+
</TABLE>>];
5319+
}
5320+
"#]]);
5321+
}
5322+
}
5323+
51485324
#[cfg(test)]
51495325
mod opt_tests {
51505326
use super::*;

zjit/src/options.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ pub struct Options {
3737
/// Dump High-level IR after optimization, right before codegen.
3838
pub dump_hir_opt: Option<DumpHIR>,
3939

40+
pub dump_hir_graphviz: bool,
41+
4042
/// Dump low-level IR
4143
pub dump_lir: bool,
4244

@@ -61,6 +63,7 @@ impl Default for Options {
6163
debug: false,
6264
dump_hir_init: None,
6365
dump_hir_opt: None,
66+
dump_hir_graphviz: false,
6467
dump_lir: false,
6568
dump_disasm: false,
6669
perf: false,
@@ -186,6 +189,7 @@ fn parse_option(str_ptr: *const std::os::raw::c_char) -> Option<()> {
186189
("dump-hir" | "dump-hir-opt", "") => options.dump_hir_opt = Some(DumpHIR::WithoutSnapshot),
187190
("dump-hir" | "dump-hir-opt", "all") => options.dump_hir_opt = Some(DumpHIR::All),
188191
("dump-hir" | "dump-hir-opt", "debug") => options.dump_hir_opt = Some(DumpHIR::Debug),
192+
("dump-hir-graphviz", "") => options.dump_hir_graphviz = true,
189193

190194
("dump-hir-init", "") => options.dump_hir_init = Some(DumpHIR::WithoutSnapshot),
191195
("dump-hir-init", "all") => options.dump_hir_init = Some(DumpHIR::All),

0 commit comments

Comments
 (0)