320

# Some Assembly Required

My RubyConf 2021 presentation ## Aaron Patterson

November 13, 2021

## Transcript

1. None
2. ### Aaron Patterson Some Assembly Required ASSEMBLY! GET IT???? LIKE, YOU

HAVE TO PUT IT TOGETHER BUT ALSO IT USES ASSEMBLY LANGUAGE???

4. None
5. None

7. None

13. None

15. ### https://www.bonappetit.com/recipes/article/groat-ricks-chili-colorado It’s a traditional Mexican dish of beef or pork

stewed in a red chili sauce—chili “colored red,” not chili from the state of Colorado.

30. ### Fibonacci Benchmark YARV vs TenderJIT require "benchmark" def fib(n) if

n < 2 n else fib(n - 2) + fib(n - 1) end end N = 40 puts BASE: Benchmark.measure { fib(N) }.total if ARGV == "TJ" require "tenderjit" jit = TenderJIT.new jit.compile(method(:fib)) jit.enable! puts TJ1: Benchmark.measure { fib(N) }.total # Warm puts TJ2: Benchmark.measure { fib(N) }.total jit.disable! end Doesn’t Automatically Compile Stuff (yet) \$ be ruby -I lib:test fib.rb TJ {:BASE=>9.943876999999999} {:TJ1=>3.9653509999999996} {:TJ2=>3.8689980000000004}
31. ### Fibonacci Benchmark YJIT require "benchmark" def fib(n) if n <

2 n else fib(n - 2) + fib(n - 1) end end N = 40 puts BASE: Benchmark.measure { fib(N) }.total if ARGV == "TJ" require "tenderjit" jit = TenderJIT.new jit.compile(method(:fib)) jit.enable! puts TJ1: Benchmark.measure { fib(N) }.total # Warm puts TJ2: Benchmark.measure { fib(N) }.total jit.disable! end \$ be ruby --yjit -I lib:test fib.rb {:BASE=>2.2541659999999997}

40. ### Generate Machine Code print "H\xC7\xC0*\x00\x00\x00\xC3" \$ ruby machine2.rb | ndisasm

-b 64 - 00000000 48C7C02A000000 mov rax,0x2a 00000007 C3 ret Ruby Program Disassembled Output

42. ### ERB Template <h1>Machine Code is Cool!</h1> <ul> <li> Move <%=

value_1 %> to register R9 </li> <li> Move <%= value_2 %> to register RAX </li> <li> Add RAX and R9 </li> <ul>

52. ### Stack Machine 5 + 3 push 5 push 3 plus

Source Code Machine Code 5 3 8 Machine Stack
53. ### Stack Machine 5 + 3 push 5 push 3 plus

Source Code Machine Code 5 3 Machine Stack PC (Program Counter) SP (Stack Pointer)
54. ### YARV Instructions \$ cat thing.rb 5 + 3 \$ ruby

--dump=insns thing.rb == disasm: #<ISeq:<main>@thing.rb:1 (1,0)-(1,5)> (catch: FALSE) 0000 putobject 5 ( 1)[Li] 0002 putobject 3 0004 opt_plus <calldata!mid:+, argc:1, ARGS_SIMPLE>[CcCr] 0006 leave
55. ### Infinite Stack Depth! (not really in fi nite, but ykwim)

\$ cat thing.rb foo(:foo, :bar, :baz, :zot, :hoge, :hoge2) \$ ruby --dump=insns thing.rb == disasm: #<ISeq:<main>@thing.rb:1 (1,0)-(1,42)> (catch: FALSE) 0000 putself ( 1)[Li] 0001 putobject :foo 0003 putobject :bar 0005 putobject :baz 0007 putobject :zot 0009 putobject :hoge 0011 putobject :hoge2 0013 opt_send_without_block <calldata!mid:foo, argc:6, FCALL|ARGS_SIMPLE> Stack is now 7 deep!!

57. ### Register Machine 5 + 3 mov r1, 5 mov r2,

3 add r1, r2 Source Code Machine Code Machine Registers Register Name Value r1 r2 r3 … 5 3 8
58. ### x86 Instructions int main(int argc, char *argv[]) { int x

= 5; int y = 3; return x + y; } 100003fa2: mov dword ptr [rbp - 20], 5 100003fa9: mov dword ptr [rbp - 24], 3 100003fb0: mov eax, dword ptr [rbp - 20] 100003fb3: add eax, dword ptr [rbp - 24] Source Code Machine Code
59. ### x86 Instructions int main(int argc, char *argv[]) { int x

= 5; int y = 3; return x + y; } 100003fa2: mov dword ptr [rbp - 20], 5 100003fa9: mov dword ptr [rbp - 24], 3 100003fb0: mov eax, dword ptr [rbp - 20] 100003fb3: add eax, dword ptr [rbp - 24] Source Code Machine Code
60. ### x86 Instructions int main(int argc, char *argv[]) { int x

= 5; int y = 3; return x + y; } 100003fa2: mov dword ptr [rbp - 20], 5 100003fa9: mov dword ptr [rbp - 24], 3 100003fb0: mov eax, dword ptr [rbp - 20] 100003fb3: add eax, dword ptr [rbp - 24] Source Code Machine Code
61. ### x86 Instructions int main(int argc, char *argv[]) { int x

= 5; int y = 3; return x + y; } 100003fa2: mov dword ptr [rbp - 20], 5 100003fa9: mov dword ptr [rbp - 24], 3 100003fb0: mov eax, dword ptr [rbp - 20] 100003fb3: add eax, dword ptr [rbp - 24] Source Code Machine Code Register “EAX”
62. ### x86 Instructions int main(int argc, char *argv[]) { int x

= 5; int y = 3; return x + y; } 100003fa2: mov dword ptr [rbp - 20], 5 100003fa9: mov dword ptr [rbp - 24], 3 100003fb0: mov eax, dword ptr [rbp - 20] 100003fb3: add eax, dword ptr [rbp - 24] Source Code Machine Code

65. ### Fisk Example Add 5 and 3 require "fisk" fisk =

Fisk.new # Write 5 to R9 fisk.mov(fisk.r9, fisk.imm(5)) # Write 3 to RAX fisk.mov(fisk.rax, fisk.imm(3)) # Add R9 and RAX fisk.add(fisk.rax, fisk.r9) # Return fisk.ret fisk.write_to(\$stdout) \$ ruby machine.rb | ndisasm -b 64 - 00000000 49C7C105000000 mov r9,0x5 00000007 48C7C003000000 mov rax,0x3 0000000E 4C01C8 add rax,r9 00000011 C3 ret
66. ### Fisk Example Eval Based API require "fisk" fisk = Fisk.new

fisk.asm do # Write 5 to R9 mov(r9, imm(5)) # Write 3 to RAX mov(rax, imm(3)) # Add R9 and RAX add(rax, r9) # Return ret end fisk.write_to(\$stdout) \$ ruby machine.rb | ndisasm -b 64 - 00000000 49C7C105000000 mov r9,0x5 00000007 48C7C003000000 mov rax,0x3 0000000E 4C01C8 add rax,r9 00000011 C3 ret

68. ### Run Some Bytes 🏃🏃🏃 fisk = Fisk.new fisk.asm do #

Write "5" in to the R9 register mov(r9, imm(0x5)) # Write "3" in to the RAX register mov(rax, imm(0x3)) # Add RAX and R9. Result will end up in RAX add(rax, r9) # Return from this function ret end # Allocate some executable memory jit_buffer = Fisk::Helpers.jitbuffer 4096 # Assemble the code and write to the JIT buffer fisk.write_to jit_buffer p jit_buffer.to_function([], Fiddle::TYPE_INT).call \$ ruby add_two_numbers.rb 8 Code Output Write our code Allocate executable memory Write the bytes to executable memory Point the CPU at our bytes!
69. ### Just Define A Method! # [snip] # Allocate some executable

memory jit_buffer = Fisk::Helpers.jitbuffer 4096 # Assemble the code and write to the JIT buffer fisk.write_to jit_buffer func = jit_buffer.to_function([], Fiddle::TYPE_INT) define_method :add, &func p add De fi ne a method!

73. ### 😅😅😅😅😅😅 fisk = Fisk.new fisk.asm do # Copy first parameter

to RAX mov(rax, rdi) # Add second parameter to RAX add(rax, rsi) # Return from this function ret end “Ruby”
74. ### What We’d Really Like Automatic Conversion def add 5 +

3 end fisk = Fisk.new fisk.asm do # Copy 5 to RAX mov(rax, imm(5)) # Copy 3 to RSI mov(rsi, imm(3)) # Add RAX and RSI add(rax, rsi) # Return from this function ret end 🤔

76. ### Dump YARV Instructions ruby —dump=insns test.rb def add 5 +

3 end \$ ruby --dump=insns test.rb == disasm: #<ISeq:<main>@test.rb:1 (1,0)-(3,3)> (catch: FALSE) 0000 definemethod :add, add ( 1)[Li] 0003 putobject :add 0005 leave == disasm: #<ISeq:[email protected]:1 (1,0)-(3,3)> (catch: FALSE) 0000 putobject 5 ( 2)[LiCa] 0002 putobject 3 0004 opt_plus <calldata!mid:+, argc:1, ARGS_SIMPLE>[CcCr] 0006 leave ( 3)[Re]

78. ### Access Instruction From Ruby def add 5 + 3 end

iseq = RubyVM::InstructionSequence.of(method(:add)) pp iseq.to_a \$ ruby -rpp test.rb ["YARVInstructionSequence/SimpleDataFormat", 3, 1, 1, {:arg_size=>0, :local_size=>0, :stack_max=>2, :node_id=>7, :code_location=>[1, 0, 3, 3], :node_ids=>[3, 4, 6, -1]}, "add", "test.rb", "/Users/aaron/git/presentations/2021/RubyConf/test.rb", 1, :method, [], {}, [], [2, :RUBY_EVENT_LINE, :RUBY_EVENT_CALL, [:putobject, 5], [:putobject, 3], [:opt_plus, {:mid=>:+, :flag=>16, :orig_argc=>1}], 3, :RUBY_EVENT_RETURN, [:leave]]]
79. ### Access Instruction From Ruby def add 5 + 3 end

iseq = RubyVM::InstructionSequence.of(method(:add)) pp iseq.to_a \$ ruby -rpp test.rb ["YARVInstructionSequence/SimpleDataFormat", 3, 1, 1, {:arg_size=>0, :local_size=>0, :stack_max=>2, :node_id=>7, :code_location=>[1, 0, 3, 3], :node_ids=>[3, 4, 6, -1]}, "add", "test.rb", "/Users/aaron/git/presentations/2021/RubyConf/test.rb", 1, :method, [], {}, [], [2, :RUBY_EVENT_LINE, :RUBY_EVENT_CALL, [:putobject, 5], [:putobject, 3], [:opt_plus, {:mid=>:+, :flag=>16, :orig_argc=>1}], 3, :RUBY_EVENT_RETURN, [:leave]]]
80. ### Mini YARV def add 5 + 3 end stack =

[] iseq = RubyVM::InstructionSequence.of(method(:add)) instructions = iseq.to_a.last instructions.each do |insn| next unless insn.is_a?(Array) case insn in [:putobject, v] p PUSH: v stack << v in [:opt_plus, v] left = stack.shift right = stack.shift p POP: [left, right] p PUSH: left + right stack << (left + right) in [:leave] p RETURN: stack.shift end end \$ ruby test.rb {:PUSH=>5} {:PUSH=>3} {:POP=>[5, 3]} {:PUSH=>8} {:RETURN=>8} Stack Object!

83. ### Virtual Stack class Stack include Fisk::Registers REGISTERS = [R9, R10]

def initialize @depth = 0 end # Push on the stack. Returns the location # to write a temporary variable def push reg = REGISTERS.fetch(@depth) @depth += 1 reg end # Pop off the stack. Returns the location # to read the temporary variable def pop @depth -= 1 REGISTERS.fetch(@depth) end end stack = Stack.new location = stack.push write(location, temporary_value) Max Depth: 2
84. ### Integrate Virtual Stack stack = Stack.new instructions.each do |insn| next

unless insn.is_a?(Array) case insn in [:putobject, v] location = stack.push write(location, v) # Write the value in [:opt_plus, v] left = stack.pop right = stack.pop # Add left and right result = add(left, right) # Get the location to push location = stack.push write(location, result) in [:leave] result = stack.pop # Write the result to the return location write(return_location, result) # Then return ret end end Where should I write? Where should I read? Where should I write? Where should I read?
85. ### Add Fisk (for x86 code) stack = Stack.new fisk =

Fisk.new instructions.each do |insn| next unless insn.is_a?(Array) case insn in [:putobject, v] location = stack.push # Write the value fisk.mov(location, fisk.imm(v)) in [:opt_plus, v] left = stack.pop right = stack.pop # Add left and right fisk.add(left, right) # Get the location to push location = stack.push fisk.mov(location, left) in [:leave] result = stack.pop # Write the result to the return location fisk.mov(fisk.rax, result) # Then return fisk.ret end end fisk.write_to(\$stdout) Code \$ ruby test.rb | ndisasm -b 64 - 00000000 49C7C105000000 mov r9,0x5 00000007 49C7C203000000 mov r10,0x3 0000000E 4D01CA add r10,r9 00000011 4D89D1 mov r9,r10 00000014 4C89C8 mov rax,r9 00000017 C3 ret Output Write to the “stack” Add values Write to the “stack” Write to return location Print machine code
86. ### Execution Example def add 5 + 3 end Code \$

ruby test.rb | ndisasm -b 64 - 00000000 49C7C105000000 mov r9,0x5 00000007 49C7C203000000 mov r10,0x3 0000000E 4D01CA add r10,r9 00000011 4D89D1 mov r9,r10 00000014 4C89C8 mov rax,r9 00000017 C3 ret Output Push 5 on the stack Push 5 on the stack Push 3 on the stack Push 3 on the stack Pop, Pop, Add, Push Pop, Pop, Add, Push CPU State Register Value R9 R10 RAX 5 3 8 8 8 Copy to Return Location / Leave
87. ### Refactor To a Method def add 5 + 3 end

def compile(method) iseq = RubyVM::InstructionSequence.of(method) # [snip] # Insert translation code here # [/snip] # Allocate some executable memory jit_buffer = Fisk::Helpers.jitbuffer 4096 # Assemble the code and write to the JIT buffer fisk.write_to jit_buffer # Return a lambda jit_buffer.to_function([], Fiddle::TYPE_INT) end callable = compile(method(:add)) define_method :fast_add, &callable p add => fast_add Code \$ ruby test.rb {8=>8} Output

90. ### Too Many Copies stack = Stack.new fisk = Fisk.new instructions.each

do |insn| next unless insn.is_a?(Array) case insn in [:putobject, v] location = stack.push # Write the value fisk.mov(location, fisk.imm(v)) in [:opt_plus, v] left = stack.pop right = stack.pop # Add left and right fisk.add(left, right) # Get the location to push location = stack.push fisk.mov(location, left) in [:leave] result = stack.pop # Write the result to the return location fisk.mov(fisk.rax, result) # Then return fisk.ret end end fisk.write_to(\$stdout) Code \$ ruby test.rb | ndisasm -b 64 - 00000000 49C7C105000000 mov r9,0x5 00000007 49C7C203000000 mov r10,0x3 0000000E 4D01CA add r10,r9 00000011 4D89D1 mov r9,r10 00000014 4C89C8 mov rax,r9 00000017 C3 ret Output CPU State Register Value R9 R10 RAX 8 8 8

92. ### Too Many Copies stack = Stack.new fisk = Fisk.new instructions.each

do |insn| next unless insn.is_a?(Array) case insn in [:putobject, v] location = stack.push # Write the value fisk.mov(location, fisk.imm(v)) in [:opt_plus, v] left = stack.pop right = stack.pop # Add left and right fisk.add(right, left) # push stack.push in [:leave] result = stack.pop # Write the result to the return location fisk.mov(fisk.rax, result) # Then return fisk.ret end end fisk.write_to(\$stdout) Code \$ ruby test.rb | ndisasm -b 64 - 00000000 49C7C105000000 mov r9,0x5 00000007 49C7C203000000 mov r10,0x3 0000000E 4D01D1 add r9,r10 00000011 4C89C8 mov rax,r9 00000014 C3 ret Output CPU State Register Value R9 R10 RAX 3 8 8

94. ### Update Virtual Stack class Stack include Fisk::Registers # REGISTERS =

[R9, R10] REGISTERS = [RAX, R9] def initialize @depth = 0 end # Push on the stack. Returns the location # to write a temporary variable def push reg = REGISTERS.fetch(@depth) @depth += 1 reg end # Pop off the stack. Returns the location # to read the temporary variable def pop @depth -= 1 REGISTERS.fetch(@depth) end end Last value in RAX
95. ### Update Compiler stack = Stack.new fisk = Fisk.new instructions.each do

|insn| next unless insn.is_a?(Array) case insn in [:putobject, v] location = stack.push # Write the value fisk.mov(location, fisk.imm(v)) in [:opt_plus, v] left = stack.pop right = stack.pop # Add left and right fisk.add(right, left) # Get the location to push location = stack.push #fisk.mov(location, left) in [:leave] result = stack.pop # The last value is already in RAX # fisk.mov(fisk.rax, result) # Then return fisk.ret end end fisk.write_to(\$stdout) Code \$ ruby test.rb | ndisasm -b 64 - 00000000 48C7C005000000 mov rax,0x5 00000007 49C7C103000000 mov r9,0x3 0000000E 4C01C8 add rax,r9 00000011 C3 ret Output CPU State Register Value R9 R10 RAX 8 8

98. ### Defines a New Method def add 5 + 3 end

def compile(method) iseq = RubyVM::InstructionSequence.of(method) # [snip] # Insert translation code here # [/snip] # Allocate some executable memory jit_buffer = Fisk::Helpers.jitbuffer 4096 # Assemble the code and write to the JIT buffer fisk.write_to jit_buffer # Return a lambda jit_buffer.to_function([], Fiddle::TYPE_INT) end callable = compile(method(:add)) define_method :fast_add, &callable p add => fast_add Code

100. ### VM Entry Point VALUE vm_exec(rb_execution_context_t *ec, bool mjit_enable_p) { enum

ruby_tag_type state; VALUE result = Qundef; VALUE initial = 0; EC_PUSH_TAG(ec); _tag.retval = Qnil; if ((state = EC_EXEC_TAG()) == TAG_NONE) { if (!mjit_enable_p || (result = mjit_exec(ec)) == Qundef) { result = vm_exec_core(ec, initial); } goto vm_loop_start; /* fallback to the VM */ } else { result = ec->errinfo; rb_ec_raised_reset(ec, RAISED_STACKOVERFLOW | RAISED_NOMEMORY); while ((result = vm_exec_handle_exception(ec, state, result, &initial)) == Qundef) { /* caught a jump, exec the handler */ result = vm_exec_core(ec, initial); vm_loop_start: VM_ASSERT(ec->tag == &_tag); /* when caught `throw`, `tag.state` is set. */ if ((state = _tag.state) == TAG_NONE) break; _tag.state = TAG_NONE; } } EC_POP_TAG(); return result; }
101. ### VM Entry Point VALUE vm_exec(rb_execution_context_t *ec, bool mjit_enable_p) { enum

ruby_tag_type state; VALUE result = Qundef; VALUE initial = 0; EC_PUSH_TAG(ec); _tag.retval = Qnil; if ((state = EC_EXEC_TAG()) == TAG_NONE) { if (!mjit_enable_p || (result = mjit_exec(ec)) == Qundef) { result = vm_exec_core(ec, initial); } goto vm_loop_start; /* fallback to the VM */ } else { result = ec->errinfo; rb_ec_raised_reset(ec, RAISED_STACKOVERFLOW | RAISED_NOMEMORY); while ((result = vm_exec_handle_exception(ec, state, result, &initial)) == Qundef) { /* caught a jump, exec the handler */ result = vm_exec_core(ec, initial); vm_loop_start: VM_ASSERT(ec->tag == &_tag); /* when caught `throw`, `tag.state` is set. */ if ((state = _tag.state) == TAG_NONE) break; _tag.state = TAG_NONE; } } EC_POP_TAG(); return result; }
102. ### mjit_exec static inline VALUE mjit_exec(rb_execution_context_t *ec) { const rb_iseq_t *iseq

= ec->cfp->iseq; struct rb_iseq_constant_body *body = iseq->body; bool yjit_enabled = false; #ifndef MJIT_HEADER // Don't want to compile with YJIT or use code generated by YJIT // when running inside code generated by MJIT. yjit_enabled = rb_yjit_enabled_p(); #endif if (mjit_call_p || yjit_enabled) { body->total_calls++; } #ifndef MJIT_HEADER if (yjit_enabled && !mjit_call_p && body->total_calls == rb_yjit_call_threshold()) { // If we couldn't generate any code for this iseq, then return // Qundef so the interpreter will handle the call. if (!rb_yjit_compile_iseq(iseq, ec)) { return Qundef; } } #endif if (!(mjit_call_p || yjit_enabled)) return Qundef; RB_DEBUG_COUNTER_INC(mjit_exec); mjit_func_t func = body->jit_func; // YJIT tried compiling this function once before and couldn't do // it, so return Qundef so the interpreter handles it. if (yjit_enabled && func == 0) { return Qundef; } if (UNLIKELY((uintptr_t)func <= LAST_JIT_ISEQ_FUNC)) { # ifdef MJIT_HEADER RB_DEBUG_COUNTER_INC(mjit_frame_JT2VM); # else RB_DEBUG_COUNTER_INC(mjit_frame_VM2VM); # endif return mjit_exec_slowpath(ec, iseq, body); } # ifdef MJIT_HEADER RB_DEBUG_COUNTER_INC(mjit_frame_JT2JT); # else RB_DEBUG_COUNTER_INC(mjit_frame_VM2JT); # endif RB_DEBUG_COUNTER_INC(mjit_exec_call_func); // Under SystemV x64 calling convention // ec -> RDI // cfp -> RSI return func(ec, ec->cfp); }
103. ### mjit_exec (as Ruby) def mjit_exec(ec) iseq = ec.cfp.iseq body =

iseq.body return Qundef unless jit_enabled body.total_calls += 1 if body.total_calls >= call_threshold compile_iseq(iseq) end if body.jit_func body.jit_func.call else Qundef end end

call it”
105. ### mjit_exec (as Ruby) def mjit_exec(ec) iseq = ec.cfp.iseq body =

iseq.body return Qundef unless jit_enabled body.total_calls += 1 if body.total_calls >= call_threshold compile_iseq(iseq) end if body.jit_func body.jit_func.call else Qundef end end
106. ### EC: Execution Context There is only one! EC def recursive(n)

return if n == 0 recursive(n - 1) end recursive(3) Sample Code
107. ### CFP: Control Frame Pointer One per stack frame EC def

recursive(n) return if n == 0 recursive(n - 1) end recursive(3) Sample Code CFP CFP
108. ### ISeq: Instruction Sequence One per “executable code” EC def recursive(n)

return if n == 0 recursive(n - 1) end recursive(3) Sample Code CFP CFP CFP CFP ISeq
109. ### ISeq Body: Stuff One per ISeq EC def recursive(n) return

if n == 0 recursive(n - 1) end recursive(3) Sample Code CFP CFP CFP CFP ISeq Body jit_func is in here!!!!
110. ### RubyVM::InstructionSequence Access to ISeq object def recursive(n) return if n

== 0 recursive(n - 1) end recursive(3) Sample Code ISeq Body m = method(:recursive) iseq = RubyVM::InstructionSequence.of(m)

115. ### Are you sure you’re frozen? >> str = "hello".freeze =>

"hello" >> str = 'e' (irb):5:in `[]=': can't modify frozen String: "hello" (FrozenError) from (irb):5:in `<main>' from /Users/aaron/.rubies/ruby-trunk/lib/ruby/gems/3.1.0/gems/irb-1.3.8.pre.11/ exe/irb:11:in `<top (required)>' from /Users/aaron/.gem/ruby/3.1.0/bin/irb:25:in `load' from /Users/aaron/.gem/ruby/3.1.0/bin/irb:25:in `<main>' >> addr = Fiddle.dlwrap str => 4393221000 >> ptr = Fiddle::Pointer.new addr => #<Fiddle::Pointer:0x0000600000c38000 ptr=0x0000000105db3b88 size=0 free=0x0000000000000000> >> ptr = 'e'.bytes.first => 101 >> str => "eello" Get the address Make a pointer Write some bytes Pro fi t
116. ### Not Explaining This require "fiddle" def unfreeze str addr =

Fiddle.dlwrap str ptr = Fiddle::Pointer.new addr flags = ptr[0, Fiddle::SIZEOF_INT].unpack1("I") flags &= ~(1 << 11) ptr[0, Fiddle::SIZEOF_INT] = [flags].pack("I") end x = "foo".freeze p x.frozen? # => true unfreeze x p x.frozen? # => false

119. ### lldb / gdb know (lldb) p *reg_cfp->iseq (const rb_iseq_t) \$3

= { flags = 0x000000000018707a wrapper = 0x0000000000000000 body = 0x00007fc2b81164b0 aux = { compile_data = NULL loader = (obj = 0x0000000000000000, index = 0) exec = { local_hooks = NULL global_trace_events = 0 } } }

123. ### Read the ISeq JIT function def cool_method 1234 end m

= method(:cool_method) # Get the ISeq Object iseq = RubyVM::InstructionSequence.of(m) # Unwrap the iseq pointer from the T_DATA iseq_ptr = RData.data(Fiddle.dlwrap(iseq)) # Read the JIT function iseq_t = RbISeqT.new(iseq_ptr) p iseq_t.body.jit_func # => 0 test.rb struct RData { /** Basic part, including flags and class. */ struct RBasic basic; RUBY_DATA_FUNC dmark; RUBY_DATA_FUNC dfree; /** Pointer to the actual C level struct that you want to wrap. */ void *data; }; Ruby Internals RData
124. ### Read the ISeq JIT function def cool_method 1234 end m

= method(:cool_method) # Get the ISeq Object iseq = RubyVM::InstructionSequence.of(m) # Unwrap the iseq pointer from the T_DATA iseq_ptr = RData.data(Fiddle.dlwrap(iseq)) # Read the JIT function iseq_t = RbISeqT.new(iseq_ptr) p iseq_t.body.jit_func # => 0 test.rb struct rb_iseq_struct { VALUE flags; /* 1 */ VALUE wrapper; /* 2 */ struct rb_iseq_constant_body *body; /* 3 */ union { /* 4, 5 words */ struct iseq_compile_data *compile_data; /* used at compile time */ struct { VALUE obj; int index; } loader; struct { struct rb_hook_list_struct *local_hooks; rb_event_flag_t global_trace_events; } exec; } aux; }; Ruby Internals
125. ### Read the ISeq JIT function def cool_method 1234 end m

= method(:cool_method) # Get the ISeq Object iseq = RubyVM::InstructionSequence.of(m) # Unwrap the iseq pointer from the T_DATA iseq_ptr = RData.data(Fiddle.dlwrap(iseq)) # Read the JIT function iseq_t = RbISeqT.new(iseq_ptr) p iseq_t.body.jit_func # => 0 test.rb struct rb_iseq_constant_body { /* [SNIP] */ #if USE_MJIT /* The following fields are MJIT related info. */ VALUE (*jit_func)(struct rb_execution_context_struct *, struct rb_control_frame_struct *); /* function pointer for loaded native code */ long unsigned total_calls; /* number of total calls with `mjit_exec()` */ struct rb_mjit_unit *jit_unit; #endif }; Ruby Internals
126. ### Assemble a new JIT function # Assemble a new JIT

function fisk = Fisk.new fisk.asm do # Pop the current stack frame add(rsi, imm(RbControlFrameStruct.byte_size)) mov(m64(rdi, RbExecutionContextT.offsetof("cfp")), rsi) # Return 42 mov(rax, imm((42 << 1) | 1)) ret end buf = Fisk::Helpers.jitbuffer 1024 fisk.write_to(buf) # Assign the JIT function iseq_t.body.jit_func = buf.memory.to_i p cool_method
127. ### Try Running It! \$ be ruby -I lib:test thing.rb 1234

\$ be ruby --jit -I lib:test thing.rb 42 Running! def cool_method 1234 end # [snip] buf = Fisk::Helpers.jitbuffer 1024 fisk.write_to(buf) # Assign the JIT function iseq_t.body.jit_func = buf.memory.to_i p cool_method JIT Code

136. None
137. None