ffi-llvm-jit 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +97 -0
- data/ext/ffi_llvm_jit/extconf.rb +10 -0
- data/ext/ffi_llvm_jit/ffi_llvm_jit.c +41 -0
- data/ext/ffi_llvm_jit/ffi_llvm_jit.h +16 -0
- data/ext/llvm_bitcode/extconf.rb +19 -0
- data/ext/llvm_bitcode/llvm_bitcode.c +165 -0
- data/ext/llvm_bitcode/llvm_bitcode.h +8 -0
- data/lib/ffi/llvm_jit/version.rb +7 -0
- data/lib/ffi/llvm_jit.rb +233 -0
- data/sig/ffi_llvm_jit.rbs +6 -0
- metadata +241 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7330fb0bd311b6b6655240b9801e1af1951964f57210540c6d00db78aeddf001
|
4
|
+
data.tar.gz: a98d9784adebf9e4c4ecdbeb778a48ffd2a19161713f5f8a48c5dc3b00abac58
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2c311b57edf7649f606f5d983718de9659808d13d28dba0f5d34c20e56b796296fbd702c7e58e30271c525041973c1f4fa587d5963433131b0c7d785f9374e7d
|
7
|
+
data.tar.gz: ec31286dee9223c8b9ca64827b40783e97dcecc5a4ee06192b75afe125fc341a724f036851c520da4861e9dc2a03b4e7690ee672a85f27712619de210c5c1141
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2025 uvlad7
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
# FFI::LLVMJIT
|
2
|
+
|
3
|
+
Extends Ruby FFI and uses LLVM to generate JIT wrappers for attached native functions. Works only on MRI.
|
4
|
+
|
5
|
+
## Requirements
|
6
|
+
|
7
|
+
The gem depends on `ruby-llvm` gem, which requires `llvm` development package to be installed.
|
8
|
+
|
9
|
+
On Debian/Ubuntu you can install it with `apt install llvmXX-dev`, where `XX` is a major version of `ruby-llvm` gem.
|
10
|
+
For other systems, refer to `ruby-llvm` [README](https://github.com/ruby-llvm/ruby-llvm/blob/master/README.md).
|
11
|
+
|
12
|
+
## Installation
|
13
|
+
|
14
|
+
Install the gem and add to the application's Gemfile by executing:
|
15
|
+
|
16
|
+
```bash
|
17
|
+
bundle add ffi-llvm-jit
|
18
|
+
```
|
19
|
+
|
20
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
21
|
+
|
22
|
+
```bash
|
23
|
+
gem install ffi-llvm-jit
|
24
|
+
```
|
25
|
+
|
26
|
+
## Usage
|
27
|
+
|
28
|
+
This gem provides `FFI::LLVMJIT::Library` module that intends to be fully compatible with (`FFI::Library`)[https://www.rubydoc.info/gems/ffi/1.17.2/FFI/Library#attach_function-instance_method]. 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.
|
29
|
+
|
30
|
+
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.
|
31
|
+
|
32
|
+
Example:
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
require 'ffi/llvm_jit'
|
36
|
+
|
37
|
+
module LibCFFI
|
38
|
+
extend FFI::LLVMJIT::Library
|
39
|
+
ffi_lib FFI::Library::LIBC
|
40
|
+
end
|
41
|
+
|
42
|
+
begin
|
43
|
+
LibCFFI.attach_llvm_jit_function :printf, [:string, :varargs], :int
|
44
|
+
rescue NotImplementedError => e
|
45
|
+
e
|
46
|
+
end
|
47
|
+
# => #<NotImplementedError: Cannot create JIT function printf>
|
48
|
+
|
49
|
+
LibCFFI.attach_function :printf, [:string, :varargs], :int
|
50
|
+
# => #<FFI::VariadicInvoker:0x0000766a3ac4a200 @fixed=[#<FFI::Type::Builtin::STRING size=8 alignment=8>], @type_map=nil>
|
51
|
+
|
52
|
+
begin
|
53
|
+
LibCFFI.attach_llvm_jit_function :strcasecmp, [:string, :string], :int, blocking: true
|
54
|
+
rescue NotImplementedError => e
|
55
|
+
e
|
56
|
+
end
|
57
|
+
# => #<NotImplementedError: Cannot create JIT function strcasecmp>
|
58
|
+
|
59
|
+
LibCFFI.attach_llvm_jit_function :strcasecmp, [:string, :string], :int
|
60
|
+
# => nil
|
61
|
+
|
62
|
+
LibCFFI.strcasecmp('aBBa', 'AbbA')
|
63
|
+
# => 0
|
64
|
+
```
|
65
|
+
|
66
|
+
## Benchmarks
|
67
|
+
|
68
|
+
`FFI::LLVMJIT` can be up to 2x faster when used with fast native functions, where FFI overhead is especially significant.
|
69
|
+
|
70
|
+
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
|
71
|
+
|
72
|
+
```
|
73
|
+
Comparison:
|
74
|
+
ruby-direct: 15089241.4 i/s
|
75
|
+
strlen-ruby: 11353201.8 i/s - 1.33x slower
|
76
|
+
strlen-ffi-llvm-jit: 10839778.2 i/s - 1.39x slower
|
77
|
+
strlen-cext: 10822451.7 i/s - 1.39x slower
|
78
|
+
strlen-ffi: 5058105.5 i/s - 2.98x slower
|
79
|
+
```
|
80
|
+
|
81
|
+
## Development
|
82
|
+
|
83
|
+
After checking out the repo, run `bin/setup` to install dependencies.
|
84
|
+
|
85
|
+
LLVM 17 is used for development, install it via `apt install llvm17-dev` or change `ruby-llvm` version in [ffi-llvm-jit.gemspec](./ffi-llvm-jit.gemspec) if you want to use another version of LLVM.
|
86
|
+
|
87
|
+
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.
|
88
|
+
|
89
|
+
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](https://rubygems.org).
|
90
|
+
|
91
|
+
## Contributing
|
92
|
+
|
93
|
+
Bug reports and pull requests are welcome [on GitHub](https://github.com/uvlad7/ffi-llvm-jit).
|
94
|
+
|
95
|
+
## License
|
96
|
+
|
97
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'mkmf'
|
4
|
+
|
5
|
+
# Makes all symbols private by default to avoid unintended conflict
|
6
|
+
# with other gems. To explicitly export symbols you can use RUBY_FUNC_EXPORTED
|
7
|
+
# selectively, or entirely remove this flag.
|
8
|
+
append_cflags('-fvisibility=hidden')
|
9
|
+
|
10
|
+
create_makefile('llvm_jit/ffi_llvm_jit')
|
@@ -0,0 +1,41 @@
|
|
1
|
+
#include "ffi_llvm_jit.h"
|
2
|
+
|
3
|
+
VALUE rb_mFFI;
|
4
|
+
VALUE rb_mFFILLVMJIT;
|
5
|
+
VALUE rb_mFFILLVMJITLibrary;
|
6
|
+
|
7
|
+
// from https://github.com/ffi/ffi/blob/master/ext/ffi_c/Function.c
|
8
|
+
static VALUE
|
9
|
+
attach_rb_wrap_function(VALUE module, VALUE name_val, VALUE func_val, VALUE argc_val)
|
10
|
+
{
|
11
|
+
const char * name = StringValueCStr(name_val);
|
12
|
+
VALUE (*func)(ANYARGS);
|
13
|
+
int argc;
|
14
|
+
// if (!rb_obj_is_kind_of(module, rb_cModule))
|
15
|
+
// {
|
16
|
+
// rb_raise(rb_eRuntimeError, "trying to attach function to non-module");
|
17
|
+
// return Qnil;
|
18
|
+
// }
|
19
|
+
func = (VALUE (*)(VALUE))NUM2PTR(func_val);
|
20
|
+
if (func == NULL)
|
21
|
+
{
|
22
|
+
rb_raise(rb_eRuntimeError, "trying to attach NULL function");
|
23
|
+
return Qnil;
|
24
|
+
}
|
25
|
+
argc = NUM2INT(argc_val);
|
26
|
+
// rb_define_module_function uses rb_define_private_method instead of rb_define_method
|
27
|
+
rb_define_singleton_method(module, name, func, argc);
|
28
|
+
rb_define_method(module, name, func, argc);
|
29
|
+
|
30
|
+
// return self;
|
31
|
+
return module;
|
32
|
+
}
|
33
|
+
|
34
|
+
RUBY_FUNC_EXPORTED void
|
35
|
+
Init_ffi_llvm_jit(void)
|
36
|
+
{
|
37
|
+
rb_mFFI = rb_define_module("FFI");
|
38
|
+
rb_mFFILLVMJIT = rb_define_module_under(rb_mFFI, "LLVMJIT");
|
39
|
+
rb_mFFILLVMJITLibrary = rb_define_module_under(rb_mFFILLVMJIT, "Library");
|
40
|
+
rb_define_private_method(rb_mFFILLVMJITLibrary, "attach_rb_wrap_function", attach_rb_wrap_function, 3);
|
41
|
+
}
|
@@ -0,0 +1,16 @@
|
|
1
|
+
#ifndef FFI_LLVM_JIT_H
|
2
|
+
#define FFI_LLVM_JIT_H 1
|
3
|
+
|
4
|
+
#include "ruby.h"
|
5
|
+
|
6
|
+
|
7
|
+
#if SIZEOF_VOIDP == SIZEOF_LONG
|
8
|
+
# define PTR2NUM(x) (LONG2NUM((long)(x)))
|
9
|
+
# define NUM2PTR(x) ((void*)(NUM2ULONG(x)))
|
10
|
+
#else
|
11
|
+
/* # error --->> Ruby/DL2 requires sizeof(void*) == sizeof(long) to be compiled. <<--- */
|
12
|
+
# define PTR2NUM(x) (LL2NUM((LONG_LONG)(x)))
|
13
|
+
# define NUM2PTR(x) ((void*)(NUM2ULL(x)))
|
14
|
+
#endif
|
15
|
+
|
16
|
+
#endif /* FFI_LLVM_JIT_H */
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'mkmf'
|
4
|
+
|
5
|
+
RbConfig::MAKEFILE_CONFIG['CC'] = RbConfig::CONFIG['CC'] = 'clang'
|
6
|
+
RbConfig::MAKEFILE_CONFIG['CXX'] = RbConfig::CONFIG['CXX'] = 'clang++'
|
7
|
+
RbConfig::MAKEFILE_CONFIG['LDSHARED'] =
|
8
|
+
RbConfig::CONFIG['LDSHARED'] = "ruby -rfileutils -e 'FileUtils.cp(ARGV[2], ARGV[1])' -- "
|
9
|
+
# RbConfig::MAKEFILE_CONFIG['MKMF_VERBOSE'] = RbConfig::CONFIG['MKMF_VERBOSE'] = '1'
|
10
|
+
# cp into lib dir won't work; just use MAKEFILE_CONFIG later to find the extname
|
11
|
+
# RbConfig::MAKEFILE_CONFIG['DLEXT'] = RbConfig::CONFIG['DLEXT'] = 'bc'
|
12
|
+
|
13
|
+
# required to push flags without checking
|
14
|
+
$CFLAGS << ' -emit-llvm -c ' # rubocop:disable Style/GlobalVars
|
15
|
+
|
16
|
+
# MakeMakefile::COMPILE_C = config_string('COMPILE_C') ||
|
17
|
+
# '$(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG) -c $(CSRCFLAG)$<'
|
18
|
+
|
19
|
+
create_makefile('llvm_jit/llvm_bitcode')
|
@@ -0,0 +1,165 @@
|
|
1
|
+
#include "llvm_bitcode.h"
|
2
|
+
|
3
|
+
// See https://github.com/ffi/ffi/blob/master/ext/ffi_c/Call.c
|
4
|
+
// https://github.com/ffi/ffi/blob/master/ext/ffi_c/Function.c
|
5
|
+
// rbffi_SetupCallParams
|
6
|
+
// and
|
7
|
+
// See https://github.com/ffi/ffi/blob/master/ext/ffi_c/Types.c
|
8
|
+
// rbffi_NativeValue_ToRuby
|
9
|
+
// typedef union {
|
10
|
+
// #ifdef USE_RAW
|
11
|
+
// signed int s8, s16, s32;
|
12
|
+
// unsigned int u8, u16, u32;
|
13
|
+
// #else
|
14
|
+
// signed char s8;
|
15
|
+
// unsigned char u8;
|
16
|
+
// signed short s16;
|
17
|
+
// unsigned short u16;
|
18
|
+
// signed int s32;
|
19
|
+
// unsigned int u32;
|
20
|
+
// #endif
|
21
|
+
// signed long long i64;
|
22
|
+
// unsigned long long u64;
|
23
|
+
// signed long sl;
|
24
|
+
// unsigned long ul;
|
25
|
+
// void* ptr;
|
26
|
+
// float f32;
|
27
|
+
// double f64;
|
28
|
+
// long double ld;
|
29
|
+
// } FFIStorage;
|
30
|
+
// typedef enum {
|
31
|
+
// NATIVE_VOID,
|
32
|
+
VALUE ffi_llvm_jit_Qnil = Qnil;
|
33
|
+
// NATIVE_INT8,
|
34
|
+
__attribute__((always_inline)) signed char ffi_llvm_jit_value_to_int8(VALUE arg) {
|
35
|
+
return NUM2INT(arg);
|
36
|
+
}
|
37
|
+
__attribute__((always_inline)) VALUE ffi_llvm_jit_int8_to_value(signed char arg) {
|
38
|
+
return INT2NUM(arg);
|
39
|
+
}
|
40
|
+
// NATIVE_UINT8,
|
41
|
+
__attribute__((always_inline)) unsigned char ffi_llvm_jit_value_to_uint8(VALUE arg) {
|
42
|
+
return NUM2UINT(arg);
|
43
|
+
}
|
44
|
+
__attribute__((always_inline)) VALUE ffi_llvm_jit_uint8_to_value(unsigned char arg) {
|
45
|
+
return UINT2NUM(arg);
|
46
|
+
}
|
47
|
+
// NATIVE_INT16,
|
48
|
+
__attribute__((always_inline)) signed short ffi_llvm_jit_value_to_int16(VALUE arg) {
|
49
|
+
return NUM2INT(arg);
|
50
|
+
}
|
51
|
+
__attribute__((always_inline)) VALUE ffi_llvm_jit_int16_to_value(signed short arg) {
|
52
|
+
return INT2NUM(arg);
|
53
|
+
}
|
54
|
+
// NATIVE_UINT16,
|
55
|
+
__attribute__((always_inline)) unsigned short ffi_llvm_jit_value_to_uint16(VALUE arg) {
|
56
|
+
return NUM2UINT(arg);
|
57
|
+
}
|
58
|
+
__attribute__((always_inline)) VALUE ffi_llvm_jit_uint16_to_value(unsigned short arg) {
|
59
|
+
return UINT2NUM(arg);
|
60
|
+
}
|
61
|
+
// NATIVE_INT32,
|
62
|
+
__attribute__((always_inline)) signed int ffi_llvm_jit_value_to_int32(VALUE arg) {
|
63
|
+
return NUM2INT(arg);
|
64
|
+
}
|
65
|
+
__attribute__((always_inline)) VALUE ffi_llvm_jit_int32_to_value(signed int arg) {
|
66
|
+
return INT2NUM(arg);
|
67
|
+
}
|
68
|
+
// NATIVE_UINT32,
|
69
|
+
__attribute__((always_inline)) unsigned int ffi_llvm_jit_value_to_uint32(VALUE arg) {
|
70
|
+
return NUM2UINT(arg);
|
71
|
+
}
|
72
|
+
__attribute__((always_inline)) VALUE ffi_llvm_jit_uint32_to_value(unsigned int arg) {
|
73
|
+
return UINT2NUM(arg);
|
74
|
+
}
|
75
|
+
// NATIVE_INT64,
|
76
|
+
__attribute__((always_inline)) signed long long ffi_llvm_jit_value_to_int64(VALUE arg) {
|
77
|
+
return NUM2LL(arg);
|
78
|
+
}
|
79
|
+
// TODO: Ruby defines long long differently, see include/ruby/backward/2/long_long.h
|
80
|
+
// but FFI simply uses `long long`, and so do I
|
81
|
+
__attribute__((always_inline)) VALUE ffi_llvm_jit_int64_to_value(signed long long arg) {
|
82
|
+
return LL2NUM(arg);
|
83
|
+
}
|
84
|
+
// NATIVE_UINT64,
|
85
|
+
__attribute__((always_inline)) unsigned long long ffi_llvm_jit_value_to_uint64(VALUE arg) {
|
86
|
+
return NUM2ULL(arg);
|
87
|
+
}
|
88
|
+
__attribute__((always_inline)) VALUE ffi_llvm_jit_uint64_to_value(unsigned long long arg) {
|
89
|
+
return ULL2NUM(arg);
|
90
|
+
}
|
91
|
+
// NATIVE_LONG,
|
92
|
+
__attribute__((always_inline)) signed long ffi_llvm_jit_value_to_long(VALUE arg) {
|
93
|
+
return NUM2LONG(arg);
|
94
|
+
}
|
95
|
+
__attribute__((always_inline)) VALUE ffi_llvm_jit_long_to_value(signed long arg) {
|
96
|
+
return LONG2NUM(arg);
|
97
|
+
}
|
98
|
+
// NATIVE_ULONG,
|
99
|
+
__attribute__((always_inline)) unsigned long ffi_llvm_jit_value_to_ulong(VALUE arg) {
|
100
|
+
return NUM2ULONG(arg);
|
101
|
+
}
|
102
|
+
__attribute__((always_inline)) VALUE ffi_llvm_jit_ulong_to_value(unsigned long arg) {
|
103
|
+
return ULONG2NUM(arg);
|
104
|
+
}
|
105
|
+
// NATIVE_FLOAT32,
|
106
|
+
__attribute__((always_inline)) VALUE ffi_llvm_jit_float_to_value(float arg) {
|
107
|
+
// FFI uses rb_float_new, I prefer DBL2NUM - which is defined exactly like that - for consistency
|
108
|
+
return DBL2NUM(arg);
|
109
|
+
}
|
110
|
+
__attribute__((always_inline)) float ffi_llvm_jit_value_to_float(VALUE arg) {
|
111
|
+
return (float) NUM2DBL(arg);
|
112
|
+
}
|
113
|
+
// NATIVE_FLOAT64,
|
114
|
+
__attribute__((always_inline)) VALUE ffi_llvm_jit_double_to_value(double arg) {
|
115
|
+
return DBL2NUM(arg);
|
116
|
+
}
|
117
|
+
__attribute__((always_inline)) double ffi_llvm_jit_value_to_double(VALUE arg) {
|
118
|
+
return NUM2DBL(arg);
|
119
|
+
}
|
120
|
+
// NATIVE_LONGDOUBLE,
|
121
|
+
// NATIVE_POINTER,
|
122
|
+
// NATIVE_FUNCTION,
|
123
|
+
// NATIVE_BUFFER_IN,
|
124
|
+
// NATIVE_BUFFER_OUT,
|
125
|
+
// NATIVE_BUFFER_INOUT,
|
126
|
+
// NATIVE_BOOL,
|
127
|
+
// They use signed char as return value, but unsigned char as param when convert into Ruby, for some reason
|
128
|
+
__attribute__((always_inline)) bool ffi_llvm_jit_value_to_bool(VALUE arg) {
|
129
|
+
// return RTEST(arg);
|
130
|
+
// I'd use RTEST, but FFI enforces that the argument is a boolean.
|
131
|
+
switch (TYPE(arg)) {
|
132
|
+
case T_TRUE:
|
133
|
+
return true;
|
134
|
+
case T_FALSE:
|
135
|
+
return false;
|
136
|
+
default:
|
137
|
+
rb_raise(rb_eTypeError, "wrong argument type (expected a boolean parameter)");
|
138
|
+
}
|
139
|
+
}
|
140
|
+
__attribute__((always_inline)) VALUE ffi_llvm_jit_bool_to_value(bool arg) {
|
141
|
+
return arg ? Qtrue : Qfalse;
|
142
|
+
}
|
143
|
+
// /** An immutable string. Nul terminated, but only copies in to the native function */
|
144
|
+
// NATIVE_STRING,
|
145
|
+
//
|
146
|
+
// TODO: Since we generate code for every function, we could easily support safe
|
147
|
+
// non-nullable arguments with almost no overhead.
|
148
|
+
__attribute__((always_inline)) char * ffi_llvm_jit_value_to_string(VALUE arg) {
|
149
|
+
return NIL_P(arg) ? NULL : StringValueCStr(arg);
|
150
|
+
}
|
151
|
+
__attribute__((always_inline)) VALUE ffi_llvm_jit_string_to_value(char * arg) {
|
152
|
+
return arg != NULL ? rb_str_new2(arg) : Qnil;
|
153
|
+
}
|
154
|
+
// /** The function takes a variable number of arguments */
|
155
|
+
// NATIVE_VARARGS,
|
156
|
+
|
157
|
+
// /** Struct-by-value param or result */
|
158
|
+
// NATIVE_STRUCT,
|
159
|
+
|
160
|
+
// /** An array type definition */
|
161
|
+
// NATIVE_ARRAY,
|
162
|
+
|
163
|
+
// /** Custom native type */
|
164
|
+
// NATIVE_MAPPED,
|
165
|
+
// } NativeType;
|
data/lib/ffi/llvm_jit.rb
ADDED
@@ -0,0 +1,233 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ffi'
|
4
|
+
require 'llvm/core'
|
5
|
+
require 'llvm/execution_engine'
|
6
|
+
|
7
|
+
require_relative 'llvm_jit/version'
|
8
|
+
require_relative 'llvm_jit/ffi_llvm_jit'
|
9
|
+
|
10
|
+
module FFI
|
11
|
+
# https://llvm.org/doxygen/group__LLVMCCoreModule.html
|
12
|
+
# https://llvm.org/doxygen/group__LLVMCBitReader.html
|
13
|
+
# https://llvm.org/doxygen/group__LLVMCCoreMemoryBuffers.html
|
14
|
+
# see llvm/core/bitcode.rb
|
15
|
+
|
16
|
+
# Ruby FFI JIT using LLVM
|
17
|
+
module LLVMJIT
|
18
|
+
# Extension to FFI::Library to support JIT compilation using LLVM
|
19
|
+
module Library # rubocop:disable Metrics/ModuleLength
|
20
|
+
include ::FFI::Library
|
21
|
+
|
22
|
+
LLVM_MOD = LLVM::Module.parse_bitcode(
|
23
|
+
File.expand_path("llvm_jit/llvm_bitcode.#{RbConfig::MAKEFILE_CONFIG['DLEXT']}", __dir__),
|
24
|
+
)
|
25
|
+
LLVM.init_jit
|
26
|
+
LLVM_ENG = LLVM::JITCompiler.new(LLVM_MOD, opt_level: 3)
|
27
|
+
|
28
|
+
private_constant :LLVM_MOD, :LLVM_ENG
|
29
|
+
|
30
|
+
# LLVM_ENG.dispose is never called
|
31
|
+
# https://llvm.org/doxygen/group__LLVMCTarget.html#gaaa9ce583969eb8754512e70ec4b80061
|
32
|
+
# LLVM_MOD.dump
|
33
|
+
|
34
|
+
# # Native integer type
|
35
|
+
# bits = FFI.type_size(:int) * 8
|
36
|
+
# ::LLVM::Int = const_get("Int#{bits}")
|
37
|
+
# see @LLVMinst inttoptr
|
38
|
+
POINTER = LLVM.const_get("Int#{FFI.type_size(:pointer) * 8}")
|
39
|
+
VALUE = POINTER
|
40
|
+
LLVM_TYPES = {
|
41
|
+
# Again, not sure. Char resolves into int8, but internally it uses 'signed char'
|
42
|
+
void: LLVM.Void,
|
43
|
+
int8: LLVM.const_get("Int#{FFI.type_size(:int8) * 8}"),
|
44
|
+
uint8: LLVM.const_get("Int#{FFI.type_size(:uint8) * 8}"),
|
45
|
+
int16: LLVM.const_get("Int#{FFI.type_size(:int16) * 8}"),
|
46
|
+
uint16: LLVM.const_get("Int#{FFI.type_size(:uint16) * 8}"),
|
47
|
+
int32: LLVM.const_get("Int#{FFI.type_size(:int32) * 8}"),
|
48
|
+
uint32: LLVM.const_get("Int#{FFI.type_size(:uint32) * 8}"),
|
49
|
+
int64: LLVM.const_get("Int#{FFI.type_size(:int64) * 8}"),
|
50
|
+
uint64: LLVM.const_get("Int#{FFI.type_size(:uint64) * 8}"),
|
51
|
+
long: LLVM.const_get("Int#{FFI.type_size(:long) * 8}"),
|
52
|
+
ulong: LLVM.const_get("Int#{FFI.type_size(:ulong) * 8}"),
|
53
|
+
# These types are actually defined as float and double in FFI
|
54
|
+
# and despite they are called float32 and float64 in the definitions
|
55
|
+
# and having FFI::NativeType::FLOAT32/FFI::NativeType::FLOAT64 constants,
|
56
|
+
# you can't find them through FFI.find_type and therefore use in attach_function
|
57
|
+
# anyway, they are just aliases
|
58
|
+
float: LLVM::Float,
|
59
|
+
double: LLVM::Double,
|
60
|
+
bool: LLVM.const_get("Int#{FFI.type_size(:bool) * 8}"),
|
61
|
+
string: LLVM.Pointer(LLVM::Int8),
|
62
|
+
}.freeze
|
63
|
+
|
64
|
+
private_constant :POINTER, :VALUE, :LLVM_TYPES
|
65
|
+
|
66
|
+
# TODO: LLVM args
|
67
|
+
# FFI::Type::Builtin to LLVM types
|
68
|
+
# FFI::NativeType.constants
|
69
|
+
# https://github.com/ffi/ffi/blob/master/ext/ffi_c/Type.c#L410
|
70
|
+
|
71
|
+
# rubocop:disable Style/MutableConstant
|
72
|
+
# Frozen later
|
73
|
+
|
74
|
+
SUPPORTED_TO_NATIVE = {}
|
75
|
+
SUPPORTED_FROM_NATIVE = {}
|
76
|
+
|
77
|
+
# rubocop:enable Style/MutableConstant
|
78
|
+
|
79
|
+
LLVM_MOD.functions.each do |func|
|
80
|
+
name = func.name
|
81
|
+
if name[/\Affi_llvm_jit_value_to_(.*)\z/, 1]
|
82
|
+
type = Regexp.last_match(1).to_sym
|
83
|
+
SUPPORTED_TO_NATIVE[FFI.find_type(type)] = type
|
84
|
+
elsif name[/\Affi_llvm_jit_(.*)_to_value\z/]
|
85
|
+
type = Regexp.last_match(1).to_sym
|
86
|
+
SUPPORTED_FROM_NATIVE[FFI.find_type(type)] = type
|
87
|
+
end
|
88
|
+
|
89
|
+
raise "Conversion function #{name} defined, but LLVM type #{type} is unknown" if type && !LLVM_TYPES.key?(type)
|
90
|
+
end
|
91
|
+
|
92
|
+
SUPPORTED_FROM_NATIVE[FFI.find_type(:void)] = :void
|
93
|
+
SUPPORTED_TO_NATIVE.freeze
|
94
|
+
SUPPORTED_FROM_NATIVE.freeze
|
95
|
+
private_constant :SUPPORTED_TO_NATIVE, :SUPPORTED_FROM_NATIVE
|
96
|
+
|
97
|
+
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
98
|
+
|
99
|
+
# @note Return type doesn't match the original method, but it's usually not used
|
100
|
+
# @see https://www.rubydoc.info/gems/ffi/FFI/Library#attach_function-instance_method FFI::Library.attach_function
|
101
|
+
def attach_function(name, func, args, returns = nil, options = nil)
|
102
|
+
mname, cname, arg_types, ret_type, options = convert_params(name, func, args, returns, options)
|
103
|
+
return if attached_llvm_jit_function?(mname, cname, arg_types, ret_type, options)
|
104
|
+
|
105
|
+
super(mname, cname, arg_types, ret_type, options)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Same as +attach_function+, but raises an exception if cannot create JIT function
|
109
|
+
# instead of falling back to the regular FFI function
|
110
|
+
def attach_llvm_jit_function(name, func, args, returns = nil, options = nil)
|
111
|
+
mname, cname, arg_types, ret_type, options = convert_params(name, func, args, returns, options)
|
112
|
+
return if attached_llvm_jit_function?(mname, cname, arg_types, ret_type, options)
|
113
|
+
|
114
|
+
raise NotImplementedError, "Cannot create JIT function #{name}"
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
|
119
|
+
def convert_params(name, func, args, returns, options)
|
120
|
+
mname = name
|
121
|
+
a2 = func
|
122
|
+
a3 = args
|
123
|
+
a4 = returns
|
124
|
+
a5 = options
|
125
|
+
cname, arg_types, ret_type, opts = if a4 && (a2.is_a?(String) || a2.is_a?(Symbol))
|
126
|
+
[a2, a3, a4, a5]
|
127
|
+
else
|
128
|
+
[mname.to_s, a2, a3, a4]
|
129
|
+
end
|
130
|
+
# Convert :foo to the native type
|
131
|
+
arg_types = arg_types.map { |e| find_type(e) }
|
132
|
+
options = {
|
133
|
+
convention: ffi_convention,
|
134
|
+
type_map: defined?(@ffi_typedefs) ? @ffi_typedefs : nil,
|
135
|
+
blocking: defined?(@blocking) && @blocking,
|
136
|
+
enums: defined?(@ffi_enums) ? @ffi_enums : nil,
|
137
|
+
}
|
138
|
+
|
139
|
+
@blocking = false
|
140
|
+
options.merge!(opts) if opts.is_a?(Hash)
|
141
|
+
|
142
|
+
[mname, cname, arg_types, ret_type, options]
|
143
|
+
end
|
144
|
+
|
145
|
+
def attached_llvm_jit_function?(mname, cname, arg_types, ret_type, options)
|
146
|
+
# TODO: support stdcall convention (rb_func.call_conv=)
|
147
|
+
# TODO: support call_without_gvl
|
148
|
+
# Variadic functions are not supported; we could support known arguments,
|
149
|
+
# but we'd still need to know use libffi to create varargs
|
150
|
+
ret_type_name = SUPPORTED_FROM_NATIVE[find_type(ret_type)]
|
151
|
+
arg_type_names = arg_types.map { |arg_type| SUPPORTED_TO_NATIVE[arg_type] }
|
152
|
+
if options[:convention] != :default || !options[:type_map].nil? ||
|
153
|
+
options[:blocking] || options[:enums] || ret_type_name.nil? || arg_type_names.any?(&:nil?)
|
154
|
+
return false
|
155
|
+
end
|
156
|
+
|
157
|
+
function_handle = ffi_libraries.find do |lib|
|
158
|
+
fn = nil
|
159
|
+
begin
|
160
|
+
function_names(cname, arg_types).find do |fname|
|
161
|
+
fn = lib.find_function(fname)
|
162
|
+
end
|
163
|
+
rescue LoadError
|
164
|
+
# Ignored
|
165
|
+
end
|
166
|
+
break fn if fn
|
167
|
+
end
|
168
|
+
raise FFI::NotFoundError.new(cname.to_s, ffi_libraries.map(&:name)) unless function_handle
|
169
|
+
|
170
|
+
attach_llvm_jit_function_addr(mname, function_handle.address, arg_type_names, ret_type_name)
|
171
|
+
# singleton_class.alias_method rb_name, jit_name
|
172
|
+
# alias_method rb_name, jit_name
|
173
|
+
true
|
174
|
+
end
|
175
|
+
|
176
|
+
def attach_llvm_jit_function_addr(rb_name, c_address, arg_type_names, ret_type_name)
|
177
|
+
# AFAIK name doesn't need to be unique
|
178
|
+
llvm_mod = LLVM::Module.new('llvm_jit')
|
179
|
+
# string -> LLVM.Pointer; size_t -> LLVM::Int64
|
180
|
+
fn_type = LLVM.Function(
|
181
|
+
arg_type_names.map { |arg_type| LLVM_TYPES[arg_type] },
|
182
|
+
LLVM_TYPES[ret_type_name],
|
183
|
+
)
|
184
|
+
fn_ptr_type = LLVM.Pointer(fn_type)
|
185
|
+
# Unnamed, can change '' into :"#{cname}_ptr" for debugging, but unnamed is better to prevent name clashes
|
186
|
+
func_ptr = llvm_mod.globals.add(POINTER, '') do |var|
|
187
|
+
var.linkage = :private
|
188
|
+
var.global_constant = true
|
189
|
+
var.unnamed_addr = true
|
190
|
+
var.initializer = POINTER.from_i(c_address)
|
191
|
+
end
|
192
|
+
|
193
|
+
# Something is wrong in case of name collizion; and even though you can
|
194
|
+
# update rb_func.name=, function_address is still zero
|
195
|
+
# Upd: It happens if functions are the same even though their names are different
|
196
|
+
|
197
|
+
rb_func = llvm_mod.functions.add(
|
198
|
+
:"rb_llvm_jit_wrap_#{rb_name}_#{llvm_mod.to_ptr.address}", [VALUE] * (arg_type_names.size + 1), VALUE,
|
199
|
+
) do |llvm_function, _rb_self, *params|
|
200
|
+
llvm_function.basic_blocks.append('entry').build do |b|
|
201
|
+
converted_params = arg_type_names.zip(params).map do |arg_type, param|
|
202
|
+
b.call(LLVM_MOD.functions["ffi_llvm_jit_value_to_#{arg_type}"], param)
|
203
|
+
end
|
204
|
+
|
205
|
+
func_ptr_val = b.int2ptr(func_ptr, fn_ptr_type)
|
206
|
+
res = b.call2(fn_type, b.load2(fn_ptr_type, func_ptr_val), *converted_params)
|
207
|
+
b.ret(
|
208
|
+
if ret_type_name == :void
|
209
|
+
b.load2(VALUE, LLVM_MOD.globals['ffi_llvm_jit_Qnil'])
|
210
|
+
else
|
211
|
+
b.call(LLVM_MOD.functions["ffi_llvm_jit_#{ret_type_name}_to_value"], res)
|
212
|
+
end,
|
213
|
+
)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
# Ruby llvm_mod object isn't kept arount and might be GCed, but
|
218
|
+
# it doesn't call +dispose+ automatically, so it's ok.
|
219
|
+
# Note that in function name +llvm_mod.hash+ is used and it
|
220
|
+
# mustn't be reused until the module is disposed, unlike
|
221
|
+
# Ruby's object_id, which may be reused and cause name clashes in some rare cases.
|
222
|
+
LLVM_ENG.modules.add(llvm_mod)
|
223
|
+
# rb_func.name isn't always the same as rb_name, in case of name clashes
|
224
|
+
# it contains a postfix like "rb_llvm_jit_wrap_strlen.1"
|
225
|
+
# https://llvm.org/doxygen/group__LLVMCExecutionEngine.html
|
226
|
+
attach_rb_wrap_function(rb_name.to_s, LLVM_ENG.function_address(rb_func.name), arg_type_names.size)
|
227
|
+
nil
|
228
|
+
end
|
229
|
+
|
230
|
+
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
metadata
ADDED
@@ -0,0 +1,241 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ffi-llvm-jit
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- uvlad7
|
8
|
+
bindir: bin
|
9
|
+
cert_chain: []
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: ffi
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - "~>"
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '1.15'
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - "~>"
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '1.15'
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: ruby-llvm
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '14'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '14'
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
name: pry
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - '='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 0.14.2
|
47
|
+
type: :development
|
48
|
+
prerelease: false
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - '='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 0.14.2
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: pry-byebug
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - '='
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: 3.10.1
|
61
|
+
type: :development
|
62
|
+
prerelease: false
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - '='
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: 3.10.1
|
68
|
+
- !ruby/object:Gem::Dependency
|
69
|
+
name: benchmark-ips
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '2.14'
|
75
|
+
type: :development
|
76
|
+
prerelease: false
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '2.14'
|
82
|
+
- !ruby/object:Gem::Dependency
|
83
|
+
name: strlen
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - "~>"
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '1.0'
|
89
|
+
type: :development
|
90
|
+
prerelease: false
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - "~>"
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '1.0'
|
96
|
+
- !ruby/object:Gem::Dependency
|
97
|
+
name: ruby-llvm
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - "~>"
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '17'
|
103
|
+
type: :development
|
104
|
+
prerelease: false
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - "~>"
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '17'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: ffi-compiler
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - "~>"
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '1.3'
|
117
|
+
type: :development
|
118
|
+
prerelease: false
|
119
|
+
version_requirements: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - "~>"
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '1.3'
|
124
|
+
- !ruby/object:Gem::Dependency
|
125
|
+
name: rake
|
126
|
+
requirement: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - "~>"
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '13.0'
|
131
|
+
type: :development
|
132
|
+
prerelease: false
|
133
|
+
version_requirements: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - "~>"
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '13.0'
|
138
|
+
- !ruby/object:Gem::Dependency
|
139
|
+
name: rake-compiler
|
140
|
+
requirement: !ruby/object:Gem::Requirement
|
141
|
+
requirements:
|
142
|
+
- - ">="
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: '0'
|
145
|
+
type: :development
|
146
|
+
prerelease: false
|
147
|
+
version_requirements: !ruby/object:Gem::Requirement
|
148
|
+
requirements:
|
149
|
+
- - ">="
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
version: '0'
|
152
|
+
- !ruby/object:Gem::Dependency
|
153
|
+
name: rspec
|
154
|
+
requirement: !ruby/object:Gem::Requirement
|
155
|
+
requirements:
|
156
|
+
- - "~>"
|
157
|
+
- !ruby/object:Gem::Version
|
158
|
+
version: '3.0'
|
159
|
+
type: :development
|
160
|
+
prerelease: false
|
161
|
+
version_requirements: !ruby/object:Gem::Requirement
|
162
|
+
requirements:
|
163
|
+
- - "~>"
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: '3.0'
|
166
|
+
- !ruby/object:Gem::Dependency
|
167
|
+
name: rubocop
|
168
|
+
requirement: !ruby/object:Gem::Requirement
|
169
|
+
requirements:
|
170
|
+
- - "~>"
|
171
|
+
- !ruby/object:Gem::Version
|
172
|
+
version: '1.21'
|
173
|
+
type: :development
|
174
|
+
prerelease: false
|
175
|
+
version_requirements: !ruby/object:Gem::Requirement
|
176
|
+
requirements:
|
177
|
+
- - "~>"
|
178
|
+
- !ruby/object:Gem::Version
|
179
|
+
version: '1.21'
|
180
|
+
- !ruby/object:Gem::Dependency
|
181
|
+
name: yard
|
182
|
+
requirement: !ruby/object:Gem::Requirement
|
183
|
+
requirements:
|
184
|
+
- - "~>"
|
185
|
+
- !ruby/object:Gem::Version
|
186
|
+
version: 0.9.37
|
187
|
+
type: :development
|
188
|
+
prerelease: false
|
189
|
+
version_requirements: !ruby/object:Gem::Requirement
|
190
|
+
requirements:
|
191
|
+
- - "~>"
|
192
|
+
- !ruby/object:Gem::Version
|
193
|
+
version: 0.9.37
|
194
|
+
description: Extends Ruby FFI and uses LLVM to generate JIT wrappers for attached
|
195
|
+
native functions. Works only on MRI
|
196
|
+
email:
|
197
|
+
- uvlad7@gmail.com
|
198
|
+
executables: []
|
199
|
+
extensions:
|
200
|
+
- ext/ffi_llvm_jit/extconf.rb
|
201
|
+
- ext/llvm_bitcode/extconf.rb
|
202
|
+
extra_rdoc_files: []
|
203
|
+
files:
|
204
|
+
- LICENSE.txt
|
205
|
+
- README.md
|
206
|
+
- ext/ffi_llvm_jit/extconf.rb
|
207
|
+
- ext/ffi_llvm_jit/ffi_llvm_jit.c
|
208
|
+
- ext/ffi_llvm_jit/ffi_llvm_jit.h
|
209
|
+
- ext/llvm_bitcode/extconf.rb
|
210
|
+
- ext/llvm_bitcode/llvm_bitcode.c
|
211
|
+
- ext/llvm_bitcode/llvm_bitcode.h
|
212
|
+
- lib/ffi/llvm_jit.rb
|
213
|
+
- lib/ffi/llvm_jit/version.rb
|
214
|
+
- sig/ffi_llvm_jit.rbs
|
215
|
+
homepage: https://github.com/uvlad7/ffi-llvm-jit
|
216
|
+
licenses:
|
217
|
+
- MIT
|
218
|
+
metadata:
|
219
|
+
homepage_uri: https://github.com/uvlad7/ffi-llvm-jit
|
220
|
+
source_code_uri: https://github.com/uvlad7/ffi-llvm-jit/tree/v0.1.0
|
221
|
+
changelog_uri: https://github.com/uvlad7/ffi-llvm-jit/blob/main/CHANGELOG.md
|
222
|
+
documentation_uri: https://rubydoc.info/gems/ffi-llvm-jit/0.1.0
|
223
|
+
rdoc_options: []
|
224
|
+
require_paths:
|
225
|
+
- lib
|
226
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
227
|
+
requirements:
|
228
|
+
- - ">="
|
229
|
+
- !ruby/object:Gem::Version
|
230
|
+
version: 2.3.8
|
231
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
232
|
+
requirements:
|
233
|
+
- - ">="
|
234
|
+
- !ruby/object:Gem::Version
|
235
|
+
version: 3.2.3
|
236
|
+
requirements:
|
237
|
+
- llvm-14-dev or newer
|
238
|
+
rubygems_version: 3.6.9
|
239
|
+
specification_version: 4
|
240
|
+
summary: Ruby FFI JIT using LLVM
|
241
|
+
test_files: []
|