The inliner is yielding benefits for ZJIT


Originally published on Rails At Scale.
We recently enabled a really cool feature in ZJIT that makes it feel like a Real Compiler™: the inliner! We’ll write more about it soon. In this post, we’ll talk about one excellent concrete benefit we are already seeing and how it optimizes blocks in pretty much every Ruby program.
I’ll start off with a refresher on how blocks work in the Ruby interpreter, then show you how ZJIT understands and optimizes that bytecode, and then show you the impact of the inliner.
Ruby: a refresher
In the beginning, there were loops.
arr = [1, 2, 3]
i = 0
sum = 0
while i < arr.length
sum += arr[i]
i += 1
end
People used them to navigate and manipulate variable-length structures, like arrays and strings. This was fine.
Then, in the 1970s, a small group of computer scientists at Palo Alto Research Center invented a programming language called Smalltalk. One of the core features of Smalltalk was that everything was an object and computation was done by sending messages to objects.
This meant that iteration wouldn’t do at all. Instead, we would have to send the do: message to the array object and pass it a block object.
#(1 2 3) do: [:a | sum := sum + a].
Then, in the 1990s, Matz, inspired by Smalltalk and Perl, created Ruby. We still have “normal” loops but we also have a very Smalltalk-y way of doing it, too:
arr = [1, 2, 3]
sum = 0
arr.each do |a|
sum += a
end
When this program gets compiled to Ruby bytecode, it ends up looking like a mostly normal method call to each except that we pass a special kind of argument to it: a block argument.
To see how this works inside CRuby, we’re going to look at a listing of YARV bytecode—CRuby bytecode. For more on YARV, I recommend Kevin Newton’s excellent Advent of YARV.
Ignore most of the bytecode dump below except for the instruction at 0009, the send instruction. We are send-ing (see? a message!) each with the block argument block in (passed a different way than “normal” arguments,…