TruffleRuby is an implementation of the Ruby programming language on top of GraalVM that aims to:

  • Run idiomatic Ruby code faster
  • Run Ruby code in parallel
  • Boot Ruby applications in less time
  • Execute C extensions in a managed environment
  • Add fast and low-overhead interoperability with languages like Java, JavaScript, Python and R
  • Provide new tooling such as debuggers and monitoring
  • All while maintaining very high compatibility with the standard implementation of Ruby

Warning: The support for Ruby is experimental. Experimental features might never be included in a production version, or might change significantly before being considered production-ready.

If you are attempting to experiment with deploying TruffleRuby to production we would encourage you to contact us so we can help you understand what is possible at the moment and to help solve any issues for you.

TruffleRuby repository

The TruffleRuby project is open source and located on GitHub: oracle/truffleruby. If you encounter any issues or want to leave feedback or feature requests please create an issue in this repository.

Installing Ruby

TruffleRuby can be installed to a GraalVM build with the gu command. See graalvm/bin/gu --help for more information.

Running Ruby applications

Run Ruby applications with the standard ruby command:

$ ruby [options] program.rb

Other Ruby commands are available, including gem, irb, rake, rdoc, and ri.

TruffleRuby uses the same options as the standard implementation of Ruby with some additions.

Installing a Ruby gem

$ gem install chunky_png
$ ruby -r chunky_png -e "puts ChunkyPNG::Color.to_hex(ChunkyPNG::Color('mintcream @ 0.5'))"
#f5fffa80

What does TruffleRuby do?

Run idiomatic Ruby code faster

TruffleRuby in conjunction with the GraalVM produces very fast machine code. Traditionally, performance-critical Ruby code has had to be extracted out to language extensions implemented in native code. This approach is cumbersome and error-prone. TruffleRuby on the GraalVM is able to optimize Ruby code. More importantly, it can optimize idiomatic Ruby code.

The following example template rendering benchmark is taken from the MRI benchmark suite. It has been modified to work with the benchmark-ips benchmarking harness, and to write the result of the render to a file in order to include IO.

require 'benchmark/ips'
require 'erb'

data = <<erb
<html>
<head> <%= title %> </head>
<body>
  <h1> <%= title %> </h1>
  <p>
    <%= content %>
  </p>
</body>
</html>
erb

title = 'hello world!'
content = "hello world!\n" * 10
erb = ERB.new(data)
out = File.open('test.out', 'w')

Benchmark.ips do |x|
  x.iterations = 3
  x.report("erb") { out.write(erb.result(binding)) }
end
$ gem install benchmark-ips
$ ruby benchmark.rb
MRI 2.5.3 JRuby 9.2.5.0 TruffleRuby
CE native
TruffleRuby
EE JVM
34 K renders/s 29 K renders/s 542 K renders/s
(16x faster)
1 M renders/s
(31x faster)

Run Ruby code in parallel

TruffleRuby runs threads in parallel, without a global-interpreter-lock. It includes sophisticated techniques to automatically make objects thread-safe only when they are going to be used by more than one thread, leaving them unsynchronized when used from only one thread to give you parallelism without an overhead when you do not want to use it.

Boot Ruby applications in less time

A hello-world script in TruffleRuby runs about as fast as MRI, or over ten times faster than JRuby. TruffleRuby can run without a heavy-weight JVM so that you start running your Ruby code almost immediately.

Execute C extensions in a managed environment

TruffleRuby executes C extensions using a managed C interpreter and just-in-time compiler, not by running native code.

Provide new tooling

TruffleRuby includes a built-in debugger that allows you to use Chrome DevTools on your Ruby application.

$ ruby --inspect test.rb
Debugger listening on port 9229.
To start debugging, open the following URL in Chrome:
    chrome-devtools://devtools/bundled/inspector.html?ws=127.0.0.1:9229/ffffffffffffffff-fffffffffffffffff

Add fast and low-overhead interoperability

TruffleRuby allows you to write polyglot programs using the other languages from GraalVM.

High compatibility with the standard implementation of Ruby

TruffleRuby aims to be fully compatible with the standard implementation of Ruby, MRI, version 2.6.2. We test extensively using the MRI test suite and the Ruby Spec project.

Interoperability

GraalVM supports several other programming languages, including JavaScript, R, Python, and LLVM. TruffleRuby lets you run code from these languages, and use objects and methods from one language in another.

As an example, we can pass a JSON-encoded string into Graal.js, decode it into a JavaScript object, and then inspect the decoded object in Ruby:

require 'json'

obj = {
  time: Time.now,
  msg: 'Hello World',
  payload: (1..10).to_a
}

encoded = JSON.dump(obj)

js_obj = Polyglot.eval('js', 'JSON.parse').call(encoded)

puts js_obj[:time]
puts js_obj[:msg]
puts js_obj[:payload].join(' ')

We use the --jvm --polyglot option to say we want to run a polyglot program.

$ ruby --jvm --polyglot test.rb
2018-04-11 15:25:08 -0700
Hello World
1 2 3 4 5 6 7 8 9 10

Compatibility

TruffleRuby aims to be fully compatible with the standard implementation of Ruby, MRI, version 2.6.2, revision 67232.

Any incompatibility with MRI is considered a bug, except for rare cases detailed below. If you find an incompatibility with MRI, please report it to us.

Our policy is to match the behaviour of MRI, except where we do not know how to do so with good performance for typical Ruby programs. Some features work but will have very low performance whenever they are used and we advise against using them on TruffleRuby if you can. Some features are missing entirely and may never be implemented. In a few limited cases, we are deliberately incompatible with MRI in order to provide a greater capability.

In general, we are not looking to debate whether Ruby features are good, bad, or if we could design the language better. If we can support a feature, we will do.

In the future, we aim to provide compatibility with extra functionality provided by JRuby, but at the moment we do not.

Identification

TruffleReport defines these constants for identification:

  • RUBY_ENGINE is 'truffleruby'
  • RUBY_VERSION is the compatible MRI version
  • RUBY_REVISION is the compatible MRI version revision
  • RUBY_PATCHLEVEL is always zero
  • RUBY_RELEASE_DATE is the Git commit date
  • RUBY_ENGINE_VERSION is the GraalVM version, or 0.0- and the Git commit hash if your build is not part of a GraalVM release.

Additionally, TruffleRuby defines:

  • TruffleRuby.revision which is the Git commit hash

In the C API, we define a preprocessor macro TRUFFLERUBY.

Features entirely missing

Continuations and callcc

Continuations and callcc are unlikely to ever be implemented in TruffleRuby, as their semantics fundamentally do not match the technology that we are using.

Fork

You cannot fork the TruffleRuby interpreter. The feature is unlikely to ever be supported when running on the JVM but could be supported in the future in the native configuration. The correct and portable way to test if fork is available is:

Process.respond_to?(:fork)

Standard libraries

The following standard libraries are unsupported.

  • continuation
  • dbm
  • gdbm
  • sdbm
  • debug (could be implemented in the future, use --inspect instead)
  • profile (could be implemented in the future, use --cpusampler instead)
  • profiler (could be implemented in the future, use --cpusampler instead)
  • io/console (partially implemented, could be implemented in the future)
  • io/wait (partially implemented, could be implemented in the future)
  • pty (could be implemented in the future)
  • ripper (has a no-op implementation, and could be implemented in the future)
  • win32
  • win32ole

fiddle is not yet implemented - the module and some methods are there but not enough to run anything serious.

We provide our own included implementation of the interface of the ffi gem, like JRuby and Rubinius. The implementation should be fairly complete and passes all the specs of the ffi gem except for some rarely-used corner cases.

Safe levels

$SAFE and Thread#safe_level are 0 and no other levels are implemented. Trying to use level 1 will raise a SecurityError. Other levels will raise ArgumentError as in standard Ruby. See our security notes for more explanation on this.

Internal MRI functionality

RubyVM is not intended for users and is not implemented.

RDoc HTML generation

TruffleRuby does not include the Darkfish theme for RDoc.

Features with major differences

Threads run in parallel

In MRI, threads are scheduled concurrently but not in parallel. In TruffleRuby threads are scheduled in parallel. As in JRuby and Rubinius, you are responsible for correctly synchronising access to your own shared mutable data structures, and we will be responsible for correctly synchronising the state of the interpreter.

Threads detect interrupts at different points

TruffleRuby threads may detect that they have been interrupted at different points in the program to where it would on MRI. In general, TruffleRuby seems to detect an interrupt sooner than MRI. JRuby and Rubinius are also different to MRI, the behaviour is not documented in MRI, and it is likely to change between MRI versions, so we would not recommend depending on interrupt points at all.

Fibers do not have the same performance characteristics as in MRI

Most use cases of fibers rely on them being easy and cheap to start up and having low memory overheads. In TruffleRuby we implement fibers using operating system threads, so they have the same performance characteristics as Ruby threads. As with coroutines and continuations, a conventional implementation of fibers fundamentally is not compatible with the execution model we are currently using.

Some classes marked as internal will be different

MRI provides some classes that are described in the documentation as being only available on MRI (C Ruby). We implement these classes if it is practical to do so, but this is not always the case. For example RubyVM is not available.

Features with subtle differences

Command line switches

-y, --yydebug, --dump=, --debug-frozen-string-literal are ignored with a warning as they are unsupported development tools.

Programs passed in -e arguments with magic-comments must have an encoding that is UTF-8 or a subset of UTF-8, as the JVM has already decoded arguments by the time we get them.

--jit options and the jit feature are not supported because TruffleRuby uses the GraalVM compiler as a JIT.

Time is limited to millisecond precision

Ruby normally provides microsecond (millionths of a second) clock precision, but TruffleRuby is currently limited to millisecond (thousands of a second) precision. This applies to Time.now and Process.clock_gettime(Process::CLOCK_REALTIME).

Setting the process title does not always work

Setting the process title (via $0 or Process.setproctitle in Ruby) is done as best-effort. It may not work, or the title you try to set may be truncated.

Line numbers other than 1 work differently

In an eval where a custom line number can be specified, line numbers below 1 are treated as 1, and line numbers above 1 are implemented by inserting blank lines in front of the source before parsing it.

The erb standard library has been modified to not use negative line numbers.

Polyglot standard IO streams

If you use standard IO streams provided by the Polyglot engine, via the experimental --polyglot-stdio option, reads and writes to file descriptors 1, 2 and 3 will be redirected to these streams. That means that other IO operations on these file descriptors, such as isatty may not be relevant for where these streams actually end up, and operations like dup may lose the connection to the polyglot stream. For example, if you $stdout.reopen, as some logging frameworks do, you will get the native standard-out, not the polyglot out.

Also, IO buffer drains, writes on IO objects with sync set, and write_nonblock, will not retry the write on EAGAIN and EWOULDBLOCK, as the streams do not provide a way to detect this.

Error messages

Error message strings will sometimes differ from MRI, as these are not generally covered by the Ruby Specification suite or tests.

Signals

The set of signals that TruffleRuby can handle is different from MRI. When launched as a GraalVM Native Image, TruffleRuby allows trapping all the same signals that MRI does, as well as a few that MRI does not. The only signals that cannot be trapped are KILL, STOP, and VTALRM. Consequently, any signal handling code that runs on MRI can run on TruffleRuby without modification in the GraalVM Native Image.

However, when run on the JVM, TruffleRuby is unable to trap USR1 or QUIT, as these are reserved by the JVM itself. Any code that relies on being able to trap those signals will need to fallover to another available signal. Additionally, FPE, ILL, KILL, SEGV, STOP, and VTALRM cannot be trapped, but these signals are also unavailable on MRI.

When TruffleRuby is run as part of a polyglot application, any signals that are handled by another language become unavailable for TruffleRuby to trap.

Features with very low performance

ObjectSpace

Using most methods on ObjectSpace will temporarily lower the performance of your program. Using them in test cases and other similar ‘offline’ operations is fine, but you probably do not want to use them in the inner loop of your production application.

set_trace_func

Using set_trace_func will temporarily lower the performance of your program. As with ObjectSpace, we would recommend that you do not use this in the inner loop of your production application.

Backtraces

Throwing exceptions, and other operations which need to create a backtrace, are slower than on MRI. This is because we have to undo optimizations that we have applied to run your Ruby code fast in order to recreate the backtrace entries. We would not recommend using exceptions for control flow on any implementation of Ruby anyway.

To help alleviate this problem in some cases backtraces are automatically disabled where we dynamically detect that they probably will not be used. See the experimental --backtraces-omit-unused option.

C Extension Compatibility

VALUE is a pointer

In TruffleRuby VALUE is a pointer type (void *) rather than a integer type (long). This means that switch statements cannot be done using a raw VALUE as they can with MRI. You can normally replace any switch statement with if statements with little difficulty if required.

Identifiers may be macros or functions

Identifiers which are normally macros may be functions, functions may be macros, and global variables may be macros. This may cause problems where they are used in a context which relies on a particular implementation (e.g., taking the address of it, assigning to a function pointer variable and using defined() to check if a macro exists). These issues should all be considered bugs and be fixed, please report these cases.

rb_scan_args

rb_scan_args only supports up to ten pointers.

RDATA

The mark function of RDATA and RTYPEDDATA is not called during garbage collection. Instead we simulate this by caching information about objects as they are assigned to structs, and periodically run all mark functions when the cache has become full to represent those object relationships in a way that the our garbage collector will understand. The process should behave identically to MRI.

Compatibility with JRuby

Ruby to Java interop

TruffleRuby does not support the same interop to Java interface as JRuby does. We provide an alternate polyglot API for interoperating with multiple languages, including Java, instead.

Java to Ruby interop

Calling Ruby code from Java is supported by the GraalVM polyglot API.

Java extensions

Use Java extensions written for JRuby is not supported. We could apply the same techniques as we have developed to run C extensions to this problem, but it is not clear if this will ever be a priority.

Compatibility with Rubinius

We do not have any plans at the moment to provide support for Rubinius’ extensions to Ruby.

Features not yet supported in native configuration

  • Java interop

Running TruffleRuby in the native configuration is mostly the same as running on the JVM. There are differences in resource management, as both VMs use different garbage collectors. But, functionality-wise, they are essentially on par with one another. The big difference is support for Java interop, which currently relies on reflection. TruffleRuby’s implementation of Java interop does not work with the GraalVM Native Image Generator’s limited support for runtime reflection.

Spec Completeness

‘How many specs are there?’ is not a question with an easy precise answer. The number of specs varies for different versions of the Ruby language, different platforms, different versions of the specs, and different configurations of the specs. The specs for the standard library and C extension API are also very uneven and they so can give misleading results.

For the command line interface, the language, and the core library specs, which covers the bulk of what TruffleRuby reimplements, this is how many spec examples TruffleRuby runs successfully compared to our compatible version of MRI running the version of specs from TruffleRuby:

  • Command line 112 / 136, 82%
  • Language 2270 / 2332, 97%
  • Core library 19453 / 20644, 94%

Compatibility with gems

You can use the compatibility checker to find whether the gems you are interested in are tested on GraalVM, whether the tests pass successfully and so on. Additionally, you can drop your Gemfile.lock file into that tool and it will analyze all the gems you are using at once. Note that the processing is done on the client-side, so no information is uploaded to any servers.

TruffleRuby Options and Command Line

TruffleRuby has the same command line interface as our compatible MRI version.

Usage: truffleruby [switches] [--] [programfile] [arguments]
  -0[octal]       specify record separator (\0, if no argument)
  -a              autosplit mode with -n or -p (splits $_ into $F)
  -c              check syntax only
  -Cdirectory     cd to directory before executing your script
  -d, --debug     set debugging flags (set $DEBUG to true)
  -e 'command'    one line of script. Several -e's allowed. Omit [programfile]
  -Eex[:in], --encoding=ex[:in]
                  specify the default external and internal character encodings
  -Fpattern       split() pattern for autosplit (-a)
  -i[extension]   edit ARGV files in place (make backup if extension supplied)
  -Idirectory     specify $LOAD_PATH directory (may be used more than once)
  -l              enable line ending processing
  -n              assume 'while gets(); ... end' loop around your script
  -p              assume loop like -n but print line also like sed
  -rlibrary       require the library before executing your script
  -s              enable some switch parsing for switches after script name
  -S              look for the script using PATH environment variable
  -T[level=1]     turn on tainting checks
  -v              print the version number, then turn on verbose mode
  -w              turn warnings on for your script
  -W[level=2]     set warning level; 0=silence, 1=medium, 2=verbose
  -x[directory]   strip off text before #!ruby line and perhaps cd to directory
  --copyright     print the copyright
  --enable={gems|rubyopt|...}[,...], --disable={gems|rubyopt|...}[,...]
                  enable or disable features. see below for available features
  --external-encoding=encoding, --internal-encoding=encoding
                  specify the default external or internal character encoding
  --verbose       turn on verbose mode and disable script from stdin
  --version       print the version number, then exit
  --help          show this message, -h for short message

Features:
  gems            rubygems (default: enabled)
  did_you_mean    did_you_mean (default: enabled)
  rubyopt         RUBYOPT environment variable (default: enabled)
  frozen-string-literal
                  freeze all string literals (default: disabled)

TruffleRuby also reads the RUBYOPT environment variable, as in standard Ruby, if run from the Ruby launcher.

Unlisted Ruby switches

MRI has some extra Ruby switches which are are not normally listed in help output but are documented in the Ruby manual page.

  -Xdirectory     cd to directory before executing your script (same as -C)
  -U              set the internal encoding to UTF-8
  -K[EeSsUuNnAa]  sets the source and external encoding
  --encoding=external[:internal]
                  the same as --external-encoding=external and optionally --internal-encoding=internal

TruffleRuby options

TruffleRuby options are set via --option=value, or you can use --ruby.option=value from any launcher. You can omit =value to set to true.

Available options and documentation can be seen with --help:languages. Additionally set --help:expert and --help:internal to see those categories of options. Warning: All options are experimental and subject to change at any time. Experimental features might never be included in a production version, or might change significantly before being considered production-ready.

Options can also be set as JVM system properties, where they have a prefix polyglot.ruby.. For example --vm.Dpolyglot.ruby.cexts.remap=true, or via any other way of setting JVM system properties. Finally, options can be set as GraalVM polyglot API configuration options.

The priority for options is the command line first, then the Graal-SDK polyglot API configuration, then system properties last.

TruffleRuby options, as well as conventional Ruby options and VM options, can also bet set in the TRUFFLERUBYOPT and RUBYOPT environment variables, if run from the Ruby launcher.

-- or the first non-option argument stops processing of TrufflRuby and VM options in the same way it stops processing of Ruby arguments.

VM options

To set options in the underlying VM, use --vm., valid for both the native configuration and the JVM configuration.

For example --vm.Dsystem_property=value or --vm.ea.

To set the classpath, use the = notation, rather than two separate arguments. For example --vm.cp=lib.jar or --vm.classpath=lib.jar.

Other binary switches

Other binaries, such as irb, gem, and so on, support exactly the same switches as in standard Ruby.

Determining the TruffleRuby home

TruffleRuby needs to know where to locate files such as the standard library. These are stored in the TruffleRuby home directory.

The search priority for finding Ruby home is:

  • The value of the TruffleRuby home option (i.e., --home=path/to/truffleruby_home).
  • The home that the Truffle framework reports.
  • The parent of the directory containing the Ruby launcher executable.

If the home option is set, it is used even if it does not appear to be a correct home location. Other options are tried until one is found that appears to be a correct home location. If none appears to be correct a warning will be given but the program will continue and you will not be able to require standard libraries. You can tell TruffleRuby not to try to find a home at all using the no-home-provided option.

Operations Manual

If you are attempting to experiment with deploying TruffleRuby to production we would encourage you to contact us so we can help you understand what is possible at the moment and to help solve any issues for you.

TruffleRuby configurations

There are two main configurations of TruffleRuby - Native Image and JVM. It is important to understand the different configurations of TruffleRuby, as each has different capabilities and performance characteristics. You should pick the execution mode that is appropriate for your application.

When distributed as part of GraalVM, TruffleRuby by default runs in the Native Image configuration. In this configuration, TruffleRuby is ahead-of-time compiled to a standalone native executable. This means that you do not need a JVM installed on your system to use it. The advantage of the native configuration is that it starts about as fast as MRI, it may use less memory, and it becomes fast in less time than the JVM configuration. The disadvantage of the native configuration is that you cannot use Java tools like VisualVM, you you cannot use Java interoperability, and peak performance may be lower than on the JVM. The Native Image configuration is used by default, but you can also request it using --native. To use polyglot programming with the native configuration, you need to use the --polyglot flag.

TruffleRuby can also be used in the JVM configuration, where it runs as a normal Java application on the JVM, as any other Java application would. The advantage of the JVM configuration is that you can use Java interoperability, and peak performance may be higher than the Native Image configuration. The disadvantage of the JVM configuration is that it takes much longer to start and to get fast, and may use more memory. The JVM configuration is requested using --jvm.

If you are running a short-running program you probably want the default, Native Image, configuration. If you are running a long-running program and want the highest possible performance you probably want the JVM configuration, by using --jvm.

Getting the best startup time performance

To get the best startup time performance in most cases you want to use the native configuration, which is the default.

Getting the lowest memory footprint

To get the lowest memory footprint you probably initially want to use the native configuration, but as you get a larger working set of objects you may find that the simpler garbage collector and current lack of compressed ordinary object pointers (OOPS) actually increases your memory footprint and you will be better off with the JVM configuration using --jvm to reduce memory use.

Getting the best peak performance from TruffleRuby

To get the best peak performance from TruffleRuby for longer-running applications we would in most cases recommend the JVM configuration with --jvm.

However to reach this peak performance you need to warm-up TruffleRuby, as you do with most heavily optimizing virtual machines. This is done by running the application under load for a period of time. If you monitor the performance (by measuring operation time, or response time) you will see it reduce over time and then probably stabilize.

Tuning TruffleRuby

To tune TruffleRuby you will need to consider the options of either your JVM or the Native Image, and then the Truffle framework, and the GraalVM compiler.

TruffleRuby has a large set of options, which you can see with the --help:languages flag.

Logging

Ruby application logging and warning works as in the standard implementation of Ruby.

For logging of TruffleRuby internals, standard Java logging is used. The logging level can be set with --log.level=INFO, =FINEST, or so on.

Troubleshooting TruffleRuby

Warning: The GraalVM implementation of Ruby is experimental and we cannot guarantee it to be bug free. Experimental features might never be included in a production version, or might change significantly before being considered production-ready. TruffleRuby uses sophisticated techniques to optimise a Ruby program and its users are strongly encouraged to submit useful bug reports to Truffleruby issues. 
If you encounter a performance problem, please consider the recommendations in Reporting Performance Problems document

.