Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

How can I see JIT/x86 ASM results from ruby / irb console for a method #2048

Closed
ammonc opened this Issue · 2 comments

3 participants

@ammonc

My goal is to look at how different constructs get converted into bytecode, IR, and then optimized. I am interested in contributing to rubinius but haven't found enough details to understand it.

I would post this to the mailing list but it appears to not have had traffic since 2008 and the below is too verbose for IRC. Please excuse it being filed as a github issue.

What I would like to know is how to get simple IR / optimized IR / x86 asm for a single method for example (not the whole program execution) (and to not have to force everything in the program to get JIT compiled by bypassing the profile-based JIT.

Can I do this from ruby/irb?

Here is how far I have got on my own:

I read an old slide that said to do
rbx -Xjit.enabled -Xjit.dump_code=4 some_script.rb
but -Xjit.enabled is no longer used (now you have the profile-based call count instead due to long startup time)

I ran -Xconfig.print to find -Xjit.call_til_compile.

vagrant@precise32:~/src/rubinius$ cat /tmp/a
a = 1
puts a

vagrant@precise32:~/src/rubinius$ bin/rbx -Xjit.dump_code=7 -Xjit.call_til_compile=0 /tmp/a > /tmp/log &

I specified 7 in hopes that I would get all 3 - simple IR, optimized IR, and assembly, and I did. But I got code that seems much more than those two lines.

I am this far from the aloha rubyconf12 slides:
class DeepThought
def ultimate_answer?(value)
value.to_s == "42"
end
end

cm = DeepThought.method_table.lookup(:ultimate_answer?).method
puts cm.decode

This gets me the bytecode.

I also dug/grepped far enough to find vm/llvm/jit_compiler.cpp being where the simple and optimized IR code gets dumped when selected.

@hosiawak
Collaborator

You can see the AST with:

./bin/rbx compile -A script.rb

and see the bytecode with:

./bin/rbx compile -B script.rb or ./bin/rbx compile -B -e "some code"

There's no way atm to see the IR/asm just for a specific method afaik.
You can play with it in irb in such a way though:

./bin/rbx -Xjit.dump_code=1

def test
  i = 0
  while i < 40_000
    some_code && method_calls
    i += 1
  end
end

test

This will make it inline method calls into test and dump the IR which you should be able to locate.
The way the JIT operates is it inlines method calls and the IR it produces (the optimised version) depends on your whole program. So the asm it produces for a single method might not necessarily be the same asm if your method is run in the context of your whole program. A useful flag to enable to learn what JIT does when inlining is -Xjit.inline.debug

If you're just starting with Rubinius I suggest reading the docs http://rubini.us/doc/en (the bytecode compiler part and the memory system),
there are no JIT specific or the primitive system docs yet though), understanding the bytecode instructions and then looking at how the interpreter and the JIT compiler work together. You have to look at the code in the vm directory, eg.

vm/instructions.def
vm/oop.hpp
vm/ontology.cpp
vm/builtin (primitives)
vm/llvm/jit_visit.hpp

are good starting points IMO. Join #rubinius on IRC if you have further questions, there are usually people able to help answer them.

HTH

@dbussink
Owner

Just a few additional points. IRC is actually fine for these kinds of questions, topics like this are discussed pretty often there, so don't be afraid of that. We love to help people get to know the code base and how things work.

Like @hosiawak already said, understanding how we JIT code in Rubinius is important for answering this question. Basically the JIT depends heavily on type information for jitting your code. So we use the way how your code runs to decide what to compiled and also how to compile it. Should we inline a block or method or not, do we need to add type guards or not etc.

Therefore running the code is necessary before it can be jitted. The reason you see a lot more code with your example, is because a lot of Rubinius is implemented in Ruby. For example the compiler that transforms the AST into bytecode is written in Ruby. That means that this also runs when you start your program. Other things are parts such as the loader for your code that sets up everything before you can run a script.

I suggest using the example of @hosiawak by running a certain piece of code in a loop to exercise it and see how it compiles in the context of that specific loop.

Because this issue doesn't really have any actionable for us, I'm closing it since we don't use the issues as a discussion place. Feel free to hop into IRC and don't be afraid to ask questions about this stuff, we'd love to answer them!

@dbussink dbussink closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.