Skip to content

Generation and handling of bytecode

Philipp Schulz edited this page May 18, 2022 · 2 revisions

NOTE: This feature is only available with mruby, not with MRI.

The combination of Crystal macros and mruby leads to the powerful option of converting Ruby scripts to bytecode files and even integrating the generated bytecode into the executable at compiletime.

To manage this, Anyolite implements the Anyolite::Preloader module. To see how it works, let us assume that you have a script file "scripts/test.rb" in your source directory.

Compiling and calling bytecode at runtime

If you want to compile our example script to bytecode during runtime, you can use the following line:

Anyolite::Preloader.transform_script_to_bytecode("scripts/test.rb", "bytecode_test.mrb")

It can then be called using:

Anyolite::RbInterpreter.create do |rb|
  rb.load_bytecode_from_file("bytecode_test.mrb")
end

The bytecode file does not need to be generated in this particular runtime. Another possibility is to create an array:

ary = Anyolite::Preloader.transform_script_to_bytecode_array("scripts/test.rb")

# ...

Anyolite::RbInterpreter.create do |rb|
  rb.execute_bytecode(ary)
end

Compiling bytecode at compiletime

Even though compiling bytecode at the beginning of runtime can be extremely useful, it might also be beneficial to compile the bytecode at compiletime, so the Ruby script can be directly integrated into the executable.

This works similar to runtime compilation, but uses the Anyolite::Preloader::AtCompiletime module:

Anyolite::Preloader::AtCompiletime.transform_script_to_bytecode("scripts/test.rb", "bytecode_test.mrb")

It is then possible to cache the bytecode file into the executable:

Anyolite::Preloader::AtCompiletime.load_bytecode_file("bytecode_test.mrb")

To call the cached bytecode, you need to call the following code:

Anyolite::RbInterpreter.create do |rb|
  Anyolite::Preloader.execute_bytecode_from_cache_or_file(rb, "bytecode_test.mrb")
end

This will check the cache for any cached bytecode file with name "bytecode_test.mrb" and execute it. If there is no file with that name in the cache, it will attempt to execute the original file under the given file path.

If required, the whole procedure can be simplified slightly:

Anyolite::Preloader::AtCompiletime.load_bytecode_array_from_file("scripts/test.rb")

# ...

Anyolite::RbInterpreter.create do |rb|
  Anyolite::Preloader.execute_bytecode_from_cache_or_file(rb, "scripts/test.rb")
end

Now, the bytecode file will be skipped and the bytecode will be stored directly into the executable. Note that the compiled script will be identified using its original file name here.

Finally, it is also possible to obtain the bytecode at compiletime as a string:

Anyolite::Preloader::AtCompiletime.transform_script_to_bytecode_string("scripts/test.rb")

The bytecode will not be cached and the function will return a string containing the whole bytecode content. It can be used in the following way:

BYTECODE_STRING = Anyolite::Preloader::AtCompiletime.transform_script_to_bytecode_string("scripts/test.rb")

# ...

Anyolite::RbInterpreter.create do |rb|
  rb.execute_bytecode(BYTECODE_STRING.bytes)
end

The BYTECODE_STRING constant is then still determined at compiletime. Note that manipulating any bytecode Array, String or file will most likely result in crashes unless you really know what you are doing.