Add graphviz output

This adds a method to blocks to get outgoing ids, then uses the outgoing
ids to generate a graphviz graph.  Two methods were added to the Block
object.  One method returns an id for the block, which is just the
address of the underlying block.  The other method returns a list of
outgoing block ids.  We can use Block#id in conjunction with
Block#outgoing_ids to construct a graph of blocks
This commit is contained in:
Aaron Patterson 2021-06-10 13:16:58 -07:00 committed by Alan Wu
parent f54e6e1310
commit 46d5e10279
2 changed files with 88 additions and 0 deletions

37
yjit.rb
View File

@ -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(
" %<address>08x: %<instruction>s\t%<details>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.

View File

@ -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