0.0
No release in over 3 years
Extends Ruby FFI and uses LLVM to generate JIT wrappers for attached native functions. Works only on MRI
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
 Dependencies

Development

= 0.14.2
= 3.10.1
~> 13.0
~> 3.0
~> 1.21
~> 1.0
~> 0.9.37

Runtime

~> 1.15
 Project Readme

FFI::LLVMJIT

Extends Ruby FFI and uses LLVM to generate JIT wrappers for attached native functions. Works only on MRI.

Requirements

The gem depends on ruby-llvm gem, which requires llvm development package to be installed.

On Debian/Ubuntu you can install it with apt install llvmXX-dev, where XX is a major version of ruby-llvm gem. For other systems, refer to ruby-llvm README.

Installation

Install the gem and add to the application's Gemfile by executing:

bundle add ffi-llvm-jit

If bundler is not being used to manage dependencies, install the gem by executing:

gem install ffi-llvm-jit

Usage

This gem provides FFI::LLVMJIT::Library module that intends to be fully compatible with FFI::Library. It defines its own attach_function method to create a faster JIT fuction instead of a FFI wrapper. The only difference for the caller is that attach_function returns nil instead of FFI::Function/FFI::VariadicInvoker when JIT function is created.

Only basic types and none configuration options are supported; in case of unsupported parameters ffi-llvm-jit simply calls ffi. It also provides attach_llvm_jit_function method that raises an exception instead in that case.

Example:

require 'ffi/llvm_jit'

module LibCFFI
  extend FFI::LLVMJIT::Library
  ffi_lib FFI::Library::LIBC
end

begin
  LibCFFI.attach_llvm_jit_function :printf, [:string, :varargs], :int
rescue NotImplementedError => e
  e
end
# => #<NotImplementedError: Cannot create JIT function printf>

LibCFFI.attach_function :printf, [:string, :varargs], :int
# => #<FFI::VariadicInvoker:0x0000766a3ac4a200 @fixed=[#<FFI::Type::Builtin::STRING size=8 alignment=8>], @type_map=nil>

begin
  LibCFFI.attach_llvm_jit_function :strcasecmp, [:string, :string], :int, blocking: true
rescue NotImplementedError => e
  e
end
# => #<NotImplementedError: Cannot create JIT function strcasecmp>

LibCFFI.attach_llvm_jit_function :strcasecmp, [:string, :string], :int
# => nil

LibCFFI.strcasecmp('aBBa', 'AbbA')
# => 0

Benchmarks

FFI::LLVMJIT can be up to 2x faster when used with fast native functions, where FFI overhead is especially significant.

Below is a benchmark that compares Ruby's bytesize method called directly and indirectly with C strlen method called via LLVMJIT, C extension and FFI respectively

Comparison:
         ruby-direct: 15089241.4 i/s
         strlen-ruby: 11353201.8 i/s - 1.33x  slower
 strlen-ffi-llvm-jit: 10839778.2 i/s - 1.39x  slower
         strlen-cext: 10822451.7 i/s - 1.39x  slower
          strlen-ffi:  5058105.5 i/s - 2.98x  slower

Development

After checking out the repo, run bin/setup to install dependencies.

LLVM 17 is used for development, install it via apt install llvm17-dev or change ruby-llvm version in ffi-llvm-jit.gemspec if you want to use another version of LLVM.

Then, run bundle exec rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and the created tag, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub.

License

The gem is available as open source under the terms of the MIT License.