Tech News
← Back to articles

Compiling Ruby to Machine Language

read original related products more articles

I've started working on a new edition of Ruby Under a Microscope that covers Ruby 3.x. I'm working on this in my spare time, so it will take a while. Leave a comment or drop me a line and I'll email you when it's finished.

Here’s an excerpt from the completely new content for Chapter 4, about YJIT and ZJIT. I’m still finishing this up… so this content is fresh off the page! It’s been a lot of fun for me to learn about how JIT compilers work and to brush up on my Rust skills as well. And it’s very exciting to see all the impressive work the Ruby team at Shopify and other contributors have done to improve Ruby’s runtime performance.

Chapter 4: Compiling Ruby To Machine Language

Interpreting vs. Compiling Ruby Code 4 Yet Another JIT (YJIT) 6 Virtual Machines and Actual Machines 6 Counting Method and Block Calls 8 YJIT Blocks 8 YJIT Branch Stubs 10 Executing YJIT Blocks and Branches 11 Deferred Compilation 12 Regenerating a YJIT Branch 12 YJIT Guards 14 Adding Two Integers Using Machine Language 15 Experiment 4-1: Which Code Does YJIT Optimize? 18 How YJIT Recompiles Code 22 Finding a Block Version 22 Saving Multiple Block Versions 24 ZJIT, Ruby’s Next Generation JIT 26 Counting Method and Block Calls 27 ZJIT Blocks 29 Method Based JIT 31 Rust Inside of Ruby 33 Experiment 4-2: Reading ZJIT HIR and LIR 35 Summary 37

Counting Method and Block Calls

To find hot spots, YJIT counts how many times your program calls each function or block. When this count reaches a certain threshold, YJIT stops your program and converts that section of code into machine language. Later Ruby will execute the machine language version instead of the original YARV instructions.

To keep track of these counts, YJIT saves an internal counter nearby the YARV instruction sequence for each function or block.

Figure 4-5: YJIT saves information adjacent to each set of YARV instructions

Figure 4-5 shows the YARV instruction sequence the main Ruby compiler created for the sum += i block at (3) in Listing 4-1. At the top, above the YARV instructions, Figure 4-5 shows two YJIT related values: jit_entry and jit_entry_calls. As we’ll see in a moment, jit_entry starts as a null value but will later hold a pointer to the machine language instructions YJIT produces for this Ruby block. Below jit_entry, Figure 4-5 also shows jit_entry_calls, YJIT’s internal counter.

... continue reading