diff --git a/yjit.rb b/yjit.rb
index 6f7fdb41b3..442f9ef1b6 100644
--- a/yjit.rb
+++ b/yjit.rb
@@ -55,6 +55,43 @@ module YJIT
def self.comments_for(start_address, end_address)
Primitive.comments_for(start_address, end_address)
end
+
+ def self.graphviz_for(iseq)
+ iseq = RubyVM::InstructionSequence.of(iseq)
+ buff = ''
+ blocks = blocks_for(iseq)
+ buff << "digraph g {"
+ buff << " node [shape=record];"
+ blocks.each do |block|
+ buff << "b#{block.id} [label=\"#{disasm_block(block).gsub(/\n/, "\\l\n")}\"];"
+ block.outgoing_ids.each do |id|
+ buff << "b#{block.id} -> b#{id};"
+ end
+ end
+ buff << "}"
+ buff
+ end
+
+ def self.disasm_block(block)
+ cs = YJIT::Disasm.new
+ comments = comments_for(block.address, block.address + block.code.length)
+ comment_idx = 0
+ str = ''
+ cs.disasm(block.code, block.address).each do |i|
+ while (comment = comments[comment_idx]) && comment.address <= i.address
+ str << " ; #{comment.comment}\n"
+ comment_idx += 1
+ end
+
+ str << sprintf(
+ " %
08x: %s\t%s\n",
+ address: i.address,
+ instruction: i.mnemonic,
+ details: i.op_str
+ )
+ end
+ str
+ end
end
# Return a hash for statistics generated for the --yjit-stats command line option.
diff --git a/yjit_iface.c b/yjit_iface.c
index b255a44221..754a2d6529 100644
--- a/yjit_iface.c
+++ b/yjit_iface.c
@@ -1025,6 +1025,55 @@ rb_yjit_call_threshold(void)
return rb_yjit_opts.call_threshold;
}
+# define PTR2NUM(x) (LONG2NUM((long)(x)))
+
+/**
+ * call-seq: block.id -> unique_id
+ *
+ * Returns a unique integer ID for the block. For example:
+ *
+ * blocks = blocks_for(iseq)
+ * blocks.group_by(&:id)
+ */
+static VALUE
+block_id(VALUE self)
+{
+ block_t * block;
+ TypedData_Get_Struct(self, block_t, &yjit_block_type, block);
+ return PTR2NUM(block);
+}
+
+/**
+ * call-seq: block.outgoing_ids -> list
+ *
+ * Returns a list of outgoing ids for the current block. This list can be used
+ * in conjunction with Block#id to construct a graph of block objects.
+ */
+static VALUE
+outgoing_ids(VALUE self)
+{
+ block_t * block;
+ TypedData_Get_Struct(self, block_t, &yjit_block_type, block);
+
+ VALUE ids = rb_ary_new();
+
+ rb_darray_for(block->outgoing, branch_idx) {
+ branch_t* out_branch = rb_darray_get(block->outgoing, branch_idx);
+
+ for (size_t succ_idx = 0; succ_idx < 2; succ_idx++) {
+ block_t* succ = out_branch->blocks[succ_idx];
+
+ if (succ == NULL)
+ continue;
+
+ rb_ary_push(ids, PTR2NUM(succ));
+ }
+
+ }
+
+ return ids;
+}
+
// Can raise RuntimeError
void
rb_yjit_init(struct rb_yjit_options *options)
@@ -1069,9 +1118,11 @@ rb_yjit_init(struct rb_yjit_options *options)
// YJIT::Block (block version, code block)
cYjitBlock = rb_define_class_under(mYjit, "Block", rb_cObject);
rb_define_method(cYjitBlock, "address", block_address, 0);
+ rb_define_method(cYjitBlock, "id", block_id, 0);
rb_define_method(cYjitBlock, "code", block_code, 0);
rb_define_method(cYjitBlock, "iseq_start_index", iseq_start_index, 0);
rb_define_method(cYjitBlock, "iseq_end_index", iseq_end_index, 0);
+ rb_define_method(cYjitBlock, "outgoing_ids", outgoing_ids, 0);
// YJIT disassembler interface
#if HAVE_LIBCAPSTONE