How can I store and read a RubyVM::InstructionSequence?
Asked Answered
R

3

7

is there a way to store a RubyVM::InstructionSequence to a file and read it in later?

I tried Marshal.dump without success. Im getting the following error:

`dump': no _dump_data is defined for class RubyVM::InstructionSequence (TypeError)
Reactor answered 31/5, 2015 at 20:25 Comment(0)
R
14

Yes, there is a way.

First, you need make accessible load method of InstructionSequence, which is disabled by default:

require 'fiddle'

class RubyVM::InstructionSequence
  # Retrieve Ruby Core's C-ext `iseq_load' function address
  load_fn_addr  = Fiddle::Handle::DEFAULT['rb_iseq_load']
  # Retrieve `iseq_load' C function representation
  load_fn       = Fiddle::Function.new(load_fn_addr,
                                       [Fiddle::TYPE_VOIDP] * 3,
                                       Fiddle::TYPE_VOIDP)

  # Make `iseq_load' accessible as `load' class method
  define_singleton_method(:load) do |data, parent = nil, opt = nil|
    load_fn.call(Fiddle.dlwrap(data), parent, opt).to_value
  end
end

Because the RubyVM::InstructionSequence.load method can load compiled VM instructions as an array, you can freely use this for (de)serialization purposes:

irb> # compile simple ruby program into its instruction sequence
irb> seq = RubyVM::InstructionSequence.new <<-EOS
irb:   p 'Hello, world !'
irb:   EOS
=> <RubyVM::InstructionSequence:<compiled>@<compiled>

irb> # serialize sequence as Array instance representation
irb> data = Marshal.dump seq.to_a
=> "\x04\b[\x13\"-YARVInstructionSequence/SimpleDataFormat … ]"

irb> # de-serialize previously serialized sequence
irb> seq_loaded = Marshal.load data
=> ["YARVInstructionSequence/SimpleDataFormat", 2, 2, 1, { … ]

irb> # load deserialized Array back into instruction sequence
irb> new_iseq = RubyVM::InstructionSequence.load seq_loaded
=> <RubyVM::InstructionSequence:<compiled>@<compiled>>

irb> # execute instruction sequence in current context
irb> new_iseq.eval
"Hello, world !"
=> "Hello, world !"

That's all folks ;)

Rove answered 31/5, 2015 at 23:21 Comment(1)
This is very nifty! I like that it can all be done with FFI. One question: this technique seems to only work if type is ISEQ_TYPE_TOP (so obtaining a an instruction sequence via RubyVM::InstructionSequence.of(method(:foo)) raises an exception with message "not a toplevel InstructionSequence". Is there a well-defined way to make this work when the instruction sequence is obtained from a proc or a method? Setting data[9] = :top seems to work, but I'm not sure what the consequences might be.Carotenoid
D
2

Given that the class has limited methods, there is limited things you can try. Probably the only thing you can do is save its instance as a string:

puts RubyVM::InstructionSequence.disasm(proc{puts "foo"})

Result:

== disasm: <RubyVM::InstructionSequence:block in irb_binding@(irb)>=====
== catch table
| catch type: redo   st: 0002 ed: 0009 sp: 0000 cont: 0002
| catch type: next   st: 0002 ed: 0009 sp: 0000 cont: 0009
|------------------------------------------------------------------------
0000 trace            256                                             (   1)
0002 trace            1
0004 putself          
0005 putstring        \"foo\"
0007 opt_send_without_block <callinfo!mid:puts, argc:1, FCALL|ARGS_SIMPLE>
0009 trace            512
0011 leave            

and when you want to deserialize it, you need to parse this string.

Demi answered 31/5, 2015 at 22:0 Comment(0)
P
2

It is very simple.

iseq = RubyVM::InstructionSequence.compile("a = 1 + 2") # just an example
File.open('iseq.bin', 'wb') { |file| file.write iseq.to_binary }

# later
RubyVM::InstructionSequence.load_from_binary(File.read('iseq.bin')).eval # returns 3
Pierian answered 12/5, 2020 at 14:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.