Upgrade to Pro — share decks privately, control downloads, hide ads and more …

RubyKaigi2026: Invariants in my own Ruby

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.

RubyKaigi2026: Invariants in my own Ruby

RubyKaigi2026 presentation slides.

Avatar for monochrome

monochrome

June 13, 2026

More Decks by monochrome

Other Decks in Programming

Transcript

  1. About me - monochrome (@s_isshiki1969) or 一色聡一郎 - surgeon -

    loves Ruby, Rust and assembly - developing monoruby
  2. monoruby - https://github.com/sisshiki1969/monoruby - Yet another Ruby implementation with JIT

    compiler - Written in Rust from (almost) scratch - No runtime dependency on LLVM, JVM, .. - Includes parser, garbage collector, interpreter - Only x86-64 / Linux is supported - new! Supports RubyGems and Bundler - new! Trying to run ruby/spec
  3. compile at one time Specialization “specialized” Array#each 1000.times do |i|

    puts i end do |i| puts i end class Array def each .. yield .. end end
  4. Compatibility: ruby/spec Metric Value Examples 22,309 Passed 12,813 Failed 3,976

    Errors 5,568 Overall pass rate 57.4% Categories at 100% 10
  5. 0xa0 %dst %lhs %rhs ClassId(lhs) ClassId(rhs) 8 bytes 8 bytes

    ADD instruction opcode operand %7 = %2 + %3 [Integer][Integer] inline cache
  6. Store Overflow Guard Add Fixnum Guard for rhs Fixnum Guard

    for lhs Load lhs & rhs %7 = %2 + %3 [Integer][Integer] mov rdi,QWORD PTR [rbp-0x40] mov rsi,r15 test rdi,0x1 je $deoptimize test rsi,0x1 je $deoptimize sub rdi,0x1 add rdi,rsi jo $deoptimize mov r15,rdi asm code for ADD
  7. Store Overflow Guard Add Fixnum Guard for rhs Fixnum Guard

    for lhs Load lhs & rhs %2 = 1 %3 = 2 %7 = %2 + %3 [Integer][Integer] mov rdi,QWORD PTR [rbp-0x40] mov rsi,r15 test rdi,0x1 je 0xffeaf02 test rsi,0x1 je 0xffeaf3b sub rdi,0x1 add rdi,rsi jo 0xffeaf43 mov r15,rdi asm code for ADD
  8. Store %2 = 1 %3 = 2 %7 = %2

    + %3 [Integer][Integer] mov r15,0x7 constant folding
  9. METHOD_CALL instruction opcode inline cache 0x1e %dst CallsiteId cached FuncId

    0x82 %rcv %args pos cached ClassId cached version opcode operand %3 = %0.f(%3) [#<main>] FuncId(1849)
  10. Push frame Set arguments Class Version Guard Call Check Exception

    Check Stack Overflow Pop frame %2 = %0.f(%2) mov eax,DWORD PTR [rip+$GlobalVersion] cmp eax,DWORD PTR [rip+$ICVersion] jne $deoptimize cmp rsp,QWORD PTR [rbx+0x18] jle 0xfff3461 cmp DWORD PTR [rip+$GCCounter],0x8 jge 0xfff3489 mov rax,QWORD PTR [r14-0x18] mov QWORD PTR [rsp-0x40],rax mov QWORD PTR [rsp-0x48],r15 sub rsp,0x20 xor rax,rax push rax movabs rax,0x10000005000007b2 push rax xor rax,rax push rax add rsp,0x38 lea r14,[rsp-0x28] mov QWORD PTR [rsp-0x20],r14 mov rdi,QWORD PTR [rbx] lea rsi,[rsp-0x18] mov QWORD PTR [rsi],rdi mov QWORD PTR [rbx],rsi call $JIT_f_entry lea r14,[rbp-0x8] mov QWORD PTR [rbx],r14 mov r14,QWORD PTR [rbp-0x10] test rax,rax je 0xfff3266 mov r15,rax cost of method call in Ruby
  11. specializing + constant folding a = 1 b = 2

    c = f(a, b) f(1, 2) = 3 def f(a, b) a + b end
  12. Store Version Guard specializing + constant folding %2 = 1

    %3 = 2 %7 = %2 %8 = %3 %7 = %0.f(%7,%8) [#<main>] FuncId(2835) mov eax,DWORD PTR [rip+$GlobalVersion] cmp eax,DWORD PTR [rip+$ICVersion] jne $deoptimize mov r15,0x7
  13. a = 1 b = 2 class Integer def +(other)

    42 end end p a + b sample0.rb There are no assignments to local variables a and b.
  14. a = 1 b = 2 class Integer def +(other)

    42 end end p a + b sample0.rb There are no assignments to local variable a and b. ❯ monoruby sample0.rb 42
  15. a = 1 b = 2 class Integer def +(other)

    42 end end p a + b sample0.rb Redefinition of basic ops: 1+2=3 isn’t an eternal truth. ❯ monoruby sample0.rb 42
  16. sample1.rb alias evil eval curse = "a = 40" a

    = 1 b = 2 evil(curse) p a + b
  17. sample1.rb alias evil eval curse = "a = 40" a

    = 1 b = 2 evil(curse) p a + b ❯ monoruby sample1.rb 42
  18. sample1.rb alias evil eval curse = "a = 40" a

    = 1 b = 2 evil(curse) p a + b Eval: eval can do evil things.
  19. sample2.rb $b = binding a = 1 b = 2

    evil p a + b def evil  $b.eval("a = 40") end Compiler knows evil is not Kernel.#eval. and there are no method definitions inside it.
  20. sample2.rb $b = binding a = 1 b = 2

    evil p a + b def evil $b.eval("a = 40") end ❯ monoruby sample2.rb 42 Compiler knows evil is not Kernel.#eval. and there are no method definitions inside it.
  21. sample2.rb alias evil eval curse = "a = 40" a

    = 1 b = 2 evil(curse) $b = binding a = 1 b = 2 evil p a + b def evil $b.eval("a = 40") end Binding: Local variables are not local.
  22. sample3.rb alias evil eval curse = "a = 40" a

    = 1 b = 2 evil(curse) p(a + b) tp = TracePoint.new(:line) do |tp| if tp.lineno == 8 tp.binding.eval("a = 40") end end tp.enable a = 1 b = 2 p a + b ❯ monoruby sample3.rb 42
  23. sample3.rb alias evil eval curse = "a = 40" a

    = 1 b = 2 evil(curse) p(a + b) tp = TracePoint.new(:line) do |tp| if tp.lineno == 8 tp.binding.eval("a = 40") end end tp.enable a = 1 b = 2 p a + b TracePoint: You can do everything anywhere.
  24. How do we fight against these devils in Ruby? -

    Redefinition of basic methods - Eval - Binding - TracePoint
  25. How do we fight against these devils in Ruby? Invariants

    - if some assumptions are broken, we must immediately escape from JIT code, and bail out to the interpreter. - version in inline cache - receiver’s class in inline cache - constants - the local frame is not captured
  26. How do we fight against these devils in Ruby? runtime

    checks - Guards - If check fails, go back to interpreter (deoptimization). - Some runtime overhead - version - receiver’s class - constant cache - frame capture
  27. How do we fight against these devils in Ruby? compile

    time assumptions - If method call is assumed to be dangerous, stop compiling and continue interpreter execution. - eval - binding
  28. How do we fight against these devils in Ruby? Patch

    Point (binary patches) - Prepare memory space for patching in advance - No runtime penalty - If disaster occurs, rewrite all patch points. - re-definition of basic operations (i.e. Integer#+) - TracePoint