Skip to content

Commit f6d313a

Browse files
koueregon
andauthored
Use JRuby implementation for TruffleRuby (#149)
Fix GH-145 Rename `lib/fiddle/jruby.rb` to `lib/fiddle/ffi_backend.rb` as a generic ffi gem API based implementation. JRuby and TruffleRuby use `lib/fiddle/ffi_backend.rb`. --------- Co-authored-by: Benoit Daloze <eregontp@gmail.com>
1 parent 6421e31 commit f6d313a

14 files changed

+158
-109
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,15 @@ jobs:
2626
- '3.2'
2727
- debug
2828
- jruby
29+
- truffleruby
2930
include:
3031
- { os: windows-latest , ruby: mingw }
3132
- { os: windows-latest , ruby: mswin }
3233
exclude:
3334
- { os: macos-14 , ruby: '2.5' }
3435
- { os: windows-latest , ruby: '3.0' }
3536
- { os: windows-latest , ruby: debug }
37+
- { os: windows-latest , ruby: truffleruby }
3638

3739
steps:
3840
- uses: actions/checkout@v4

ext/fiddle/extconf.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# frozen_string_literal: true
22
require 'mkmf'
33

4-
if RUBY_ENGINE == "jruby"
4+
unless RUBY_ENGINE == "ruby"
55
File.write('Makefile', dummy_makefile("").join)
66
return
77
end

fiddle.gemspec

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,10 @@ Gem::Specification.new do |spec|
3838
"lib/fiddle.rb",
3939
"lib/fiddle/closure.rb",
4040
"lib/fiddle/cparser.rb",
41+
"lib/fiddle/ffi_backend.rb",
4142
"lib/fiddle/function.rb",
4243
"lib/fiddle/import.rb",
43-
"lib/fiddle/jruby.rb",
4444
"lib/fiddle/pack.rb",
45-
"lib/fiddle/ruby.rb",
4645
"lib/fiddle/struct.rb",
4746
"lib/fiddle/types.rb",
4847
"lib/fiddle/value.rb",

lib/fiddle.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# frozen_string_literal: true
22

3-
require "fiddle/#{RUBY_ENGINE}"
3+
if RUBY_ENGINE == 'ruby'
4+
require 'fiddle.so'
5+
else
6+
require 'fiddle/ffi_backend'
7+
end
48
require 'fiddle/closure'
59
require 'fiddle/function'
610
require 'fiddle/version'

lib/fiddle/jruby.rb renamed to lib/fiddle/ffi_backend.rb

Lines changed: 79 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# This is part of JRuby's FFI-based fiddle implementation.
1+
# This is based on JRuby's FFI-based fiddle implementation.
22

33
require 'ffi'
44

@@ -74,7 +74,7 @@ module Types
7474

7575
WINDOWS = FFI::Platform.windows?
7676

77-
module JRuby
77+
module FFIBackend
7878
FFITypes = {
7979
'c' => FFI::Type::INT8,
8080
'h' => FFI::Type::INT16,
@@ -104,16 +104,16 @@ module JRuby
104104
Types::VARIADIC => FFI::Type::Builtin::VARARGS,
105105
}
106106

107-
def self.__ffi_type__(dl_type)
108-
if dl_type.is_a?(Symbol)
109-
dl_type = Types.const_get(dl_type.to_s.upcase)
107+
def self.to_ffi_type(fiddle_type)
108+
if fiddle_type.is_a?(Symbol)
109+
fiddle_type = Types.const_get(fiddle_type.to_s.upcase)
110110
end
111-
if !dl_type.is_a?(Integer) && dl_type.respond_to?(:to_int)
112-
dl_type = dl_type.to_int
111+
if !fiddle_type.is_a?(Integer) && fiddle_type.respond_to?(:to_int)
112+
fiddle_type = fiddle_type.to_int
113113
end
114-
ffi_type = FFITypes[dl_type]
115-
ffi_type = FFITypes[-dl_type] if ffi_type.nil? && dl_type.is_a?(Integer) && dl_type < 0
116-
raise TypeError.new("cannot convert #{dl_type} to ffi") unless ffi_type
114+
ffi_type = FFITypes[fiddle_type]
115+
ffi_type = FFITypes[-fiddle_type] if ffi_type.nil? && fiddle_type.is_a?(Integer) && fiddle_type < 0
116+
raise TypeError.new("cannot convert #{fiddle_type} to ffi") unless ffi_type
117117
ffi_type
118118
end
119119
end
@@ -133,8 +133,8 @@ def initialize(ptr, args, return_type, abi = DEFAULT, kwargs = nil)
133133
@ptr, @args, @return_type, @abi = ptr, args, return_type, abi
134134
raise TypeError.new "invalid argument types" unless args.is_a?(Array)
135135

136-
ffi_return_type = Fiddle::JRuby::__ffi_type__(@return_type)
137-
ffi_args = @args.map { |t| Fiddle::JRuby.__ffi_type__(t) }
136+
ffi_return_type = Fiddle::FFIBackend.to_ffi_type(@return_type)
137+
ffi_args = @args.map { |t| Fiddle::FFIBackend.to_ffi_type(t) }
138138
pointer = FFI::Pointer.new(ptr.to_i)
139139
options = {convention: @abi}
140140
if ffi_args.last == FFI::Type::Builtin::VARARGS
@@ -149,14 +149,25 @@ def initialize(ptr, args, return_type, abi = DEFAULT, kwargs = nil)
149149
end
150150
end
151151

152-
def call(*args, &block);
152+
def call(*args, &block)
153153
if @function.is_a?(FFI::VariadicInvoker)
154154
n_fixed_args = @args.size - 1
155155
n_fixed_args.step(args.size - 1, 2) do |i|
156156
if args[i] == :const_string || args[i] == Types::CONST_STRING
157157
args[i + 1] = String.try_convert(args[i + 1]) || args[i + 1]
158158
end
159-
args[i] = Fiddle::JRuby.__ffi_type__(args[i])
159+
args[i] = Fiddle::FFIBackend.to_ffi_type(args[i])
160+
end
161+
else
162+
args.map! do |arg|
163+
if arg.respond_to?(:to_ptr)
164+
begin
165+
arg = arg.to_ptr
166+
end until arg.is_a?(FFI::Pointer) || !arg.respond_to?(:to_ptr)
167+
arg
168+
else
169+
arg
170+
end
160171
end
161172
end
162173
result = @function.call(*args, &block)
@@ -170,19 +181,21 @@ def initialize(ret, args, abi = Function::DEFAULT)
170181
raise TypeError.new "invalid argument types" unless args.is_a?(Array)
171182

172183
@ctype, @args = ret, args
173-
ffi_args = @args.map { |t| Fiddle::JRuby.__ffi_type__(t) }
184+
ffi_args = @args.map { |t| Fiddle::FFIBackend.to_ffi_type(t) }
174185
if ffi_args.size == 1 && ffi_args[0] == FFI::Type::Builtin::VOID
175186
ffi_args = []
176187
end
177-
@function = FFI::Function.new(
178-
Fiddle::JRuby.__ffi_type__(@ctype),
179-
ffi_args,
180-
self,
181-
:convention => abi
182-
)
188+
return_type = Fiddle::FFIBackend.to_ffi_type(@ctype)
189+
raise "#{self.class} must implement #call" unless respond_to?(:call)
190+
callable = method(:call)
191+
@function = FFI::Function.new(return_type, ffi_args, callable, convention: abi)
183192
@freed = false
184193
end
185194

195+
def to_ptr
196+
@function
197+
end
198+
186199
def to_i
187200
@function.to_i
188201
end
@@ -550,51 +563,51 @@ def cleared?
550563
RUBY_FREE = Fiddle::Pointer::LibC::FREE.address
551564
NULL = Fiddle::Pointer.new(0)
552565

553-
ALIGN_VOIDP = Fiddle::JRuby::FFITypes[Types::VOIDP].alignment
554-
ALIGN_CHAR = Fiddle::JRuby::FFITypes[Types::CHAR].alignment
555-
ALIGN_SHORT = Fiddle::JRuby::FFITypes[Types::SHORT].alignment
556-
ALIGN_INT = Fiddle::JRuby::FFITypes[Types::INT].alignment
557-
ALIGN_LONG = Fiddle::JRuby::FFITypes[Types::LONG].alignment
558-
ALIGN_LONG_LONG = Fiddle::JRuby::FFITypes[Types::LONG_LONG].alignment
559-
ALIGN_INT8_T = Fiddle::JRuby::FFITypes[Types::INT8_T].alignment
560-
ALIGN_INT16_T = Fiddle::JRuby::FFITypes[Types::INT16_T].alignment
561-
ALIGN_INT32_T = Fiddle::JRuby::FFITypes[Types::INT32_T].alignment
562-
ALIGN_INT64_T = Fiddle::JRuby::FFITypes[Types::INT64_T].alignment
563-
ALIGN_FLOAT = Fiddle::JRuby::FFITypes[Types::FLOAT].alignment
564-
ALIGN_DOUBLE = Fiddle::JRuby::FFITypes[Types::DOUBLE].alignment
565-
ALIGN_BOOL = Fiddle::JRuby::FFITypes[Types::BOOL].alignment
566-
ALIGN_SIZE_T = Fiddle::JRuby::FFITypes[Types::SIZE_T].alignment
566+
ALIGN_VOIDP = Fiddle::FFIBackend::FFITypes[Types::VOIDP].alignment
567+
ALIGN_CHAR = Fiddle::FFIBackend::FFITypes[Types::CHAR].alignment
568+
ALIGN_SHORT = Fiddle::FFIBackend::FFITypes[Types::SHORT].alignment
569+
ALIGN_INT = Fiddle::FFIBackend::FFITypes[Types::INT].alignment
570+
ALIGN_LONG = Fiddle::FFIBackend::FFITypes[Types::LONG].alignment
571+
ALIGN_LONG_LONG = Fiddle::FFIBackend::FFITypes[Types::LONG_LONG].alignment
572+
ALIGN_INT8_T = Fiddle::FFIBackend::FFITypes[Types::INT8_T].alignment
573+
ALIGN_INT16_T = Fiddle::FFIBackend::FFITypes[Types::INT16_T].alignment
574+
ALIGN_INT32_T = Fiddle::FFIBackend::FFITypes[Types::INT32_T].alignment
575+
ALIGN_INT64_T = Fiddle::FFIBackend::FFITypes[Types::INT64_T].alignment
576+
ALIGN_FLOAT = Fiddle::FFIBackend::FFITypes[Types::FLOAT].alignment
577+
ALIGN_DOUBLE = Fiddle::FFIBackend::FFITypes[Types::DOUBLE].alignment
578+
ALIGN_BOOL = Fiddle::FFIBackend::FFITypes[Types::BOOL].alignment
579+
ALIGN_SIZE_T = Fiddle::FFIBackend::FFITypes[Types::SIZE_T].alignment
567580
ALIGN_SSIZE_T = ALIGN_SIZE_T
568-
ALIGN_PTRDIFF_T = Fiddle::JRuby::FFITypes[Types::PTRDIFF_T].alignment
569-
ALIGN_INTPTR_T = Fiddle::JRuby::FFITypes[Types::INTPTR_T].alignment
570-
ALIGN_UINTPTR_T = Fiddle::JRuby::FFITypes[Types::UINTPTR_T].alignment
571-
572-
SIZEOF_VOIDP = Fiddle::JRuby::FFITypes[Types::VOIDP].size
573-
SIZEOF_CHAR = Fiddle::JRuby::FFITypes[Types::CHAR].size
574-
SIZEOF_UCHAR = Fiddle::JRuby::FFITypes[Types::UCHAR].size
575-
SIZEOF_SHORT = Fiddle::JRuby::FFITypes[Types::SHORT].size
576-
SIZEOF_USHORT = Fiddle::JRuby::FFITypes[Types::USHORT].size
577-
SIZEOF_INT = Fiddle::JRuby::FFITypes[Types::INT].size
578-
SIZEOF_UINT = Fiddle::JRuby::FFITypes[Types::UINT].size
579-
SIZEOF_LONG = Fiddle::JRuby::FFITypes[Types::LONG].size
580-
SIZEOF_ULONG = Fiddle::JRuby::FFITypes[Types::ULONG].size
581-
SIZEOF_LONG_LONG = Fiddle::JRuby::FFITypes[Types::LONG_LONG].size
582-
SIZEOF_ULONG_LONG = Fiddle::JRuby::FFITypes[Types::ULONG_LONG].size
583-
SIZEOF_INT8_T = Fiddle::JRuby::FFITypes[Types::INT8_T].size
584-
SIZEOF_UINT8_T = Fiddle::JRuby::FFITypes[Types::UINT8_T].size
585-
SIZEOF_INT16_T = Fiddle::JRuby::FFITypes[Types::INT16_T].size
586-
SIZEOF_UINT16_T = Fiddle::JRuby::FFITypes[Types::UINT16_T].size
587-
SIZEOF_INT32_T = Fiddle::JRuby::FFITypes[Types::INT32_T].size
588-
SIZEOF_UINT32_T = Fiddle::JRuby::FFITypes[Types::UINT32_T].size
589-
SIZEOF_INT64_T = Fiddle::JRuby::FFITypes[Types::INT64_T].size
590-
SIZEOF_UINT64_T = Fiddle::JRuby::FFITypes[Types::UINT64_T].size
591-
SIZEOF_FLOAT = Fiddle::JRuby::FFITypes[Types::FLOAT].size
592-
SIZEOF_DOUBLE = Fiddle::JRuby::FFITypes[Types::DOUBLE].size
593-
SIZEOF_BOOL = Fiddle::JRuby::FFITypes[Types::BOOL].size
594-
SIZEOF_SIZE_T = Fiddle::JRuby::FFITypes[Types::SIZE_T].size
581+
ALIGN_PTRDIFF_T = Fiddle::FFIBackend::FFITypes[Types::PTRDIFF_T].alignment
582+
ALIGN_INTPTR_T = Fiddle::FFIBackend::FFITypes[Types::INTPTR_T].alignment
583+
ALIGN_UINTPTR_T = Fiddle::FFIBackend::FFITypes[Types::UINTPTR_T].alignment
584+
585+
SIZEOF_VOIDP = Fiddle::FFIBackend::FFITypes[Types::VOIDP].size
586+
SIZEOF_CHAR = Fiddle::FFIBackend::FFITypes[Types::CHAR].size
587+
SIZEOF_UCHAR = Fiddle::FFIBackend::FFITypes[Types::UCHAR].size
588+
SIZEOF_SHORT = Fiddle::FFIBackend::FFITypes[Types::SHORT].size
589+
SIZEOF_USHORT = Fiddle::FFIBackend::FFITypes[Types::USHORT].size
590+
SIZEOF_INT = Fiddle::FFIBackend::FFITypes[Types::INT].size
591+
SIZEOF_UINT = Fiddle::FFIBackend::FFITypes[Types::UINT].size
592+
SIZEOF_LONG = Fiddle::FFIBackend::FFITypes[Types::LONG].size
593+
SIZEOF_ULONG = Fiddle::FFIBackend::FFITypes[Types::ULONG].size
594+
SIZEOF_LONG_LONG = Fiddle::FFIBackend::FFITypes[Types::LONG_LONG].size
595+
SIZEOF_ULONG_LONG = Fiddle::FFIBackend::FFITypes[Types::ULONG_LONG].size
596+
SIZEOF_INT8_T = Fiddle::FFIBackend::FFITypes[Types::INT8_T].size
597+
SIZEOF_UINT8_T = Fiddle::FFIBackend::FFITypes[Types::UINT8_T].size
598+
SIZEOF_INT16_T = Fiddle::FFIBackend::FFITypes[Types::INT16_T].size
599+
SIZEOF_UINT16_T = Fiddle::FFIBackend::FFITypes[Types::UINT16_T].size
600+
SIZEOF_INT32_T = Fiddle::FFIBackend::FFITypes[Types::INT32_T].size
601+
SIZEOF_UINT32_T = Fiddle::FFIBackend::FFITypes[Types::UINT32_T].size
602+
SIZEOF_INT64_T = Fiddle::FFIBackend::FFITypes[Types::INT64_T].size
603+
SIZEOF_UINT64_T = Fiddle::FFIBackend::FFITypes[Types::UINT64_T].size
604+
SIZEOF_FLOAT = Fiddle::FFIBackend::FFITypes[Types::FLOAT].size
605+
SIZEOF_DOUBLE = Fiddle::FFIBackend::FFITypes[Types::DOUBLE].size
606+
SIZEOF_BOOL = Fiddle::FFIBackend::FFITypes[Types::BOOL].size
607+
SIZEOF_SIZE_T = Fiddle::FFIBackend::FFITypes[Types::SIZE_T].size
595608
SIZEOF_SSIZE_T = SIZEOF_SIZE_T
596-
SIZEOF_PTRDIFF_T = Fiddle::JRuby::FFITypes[Types::PTRDIFF_T].size
597-
SIZEOF_INTPTR_T = Fiddle::JRuby::FFITypes[Types::INTPTR_T].size
598-
SIZEOF_UINTPTR_T = Fiddle::JRuby::FFITypes[Types::UINTPTR_T].size
599-
SIZEOF_CONST_STRING = Fiddle::JRuby::FFITypes[Types::VOIDP].size
609+
SIZEOF_PTRDIFF_T = Fiddle::FFIBackend::FFITypes[Types::PTRDIFF_T].size
610+
SIZEOF_INTPTR_T = Fiddle::FFIBackend::FFITypes[Types::INTPTR_T].size
611+
SIZEOF_UINTPTR_T = Fiddle::FFIBackend::FFITypes[Types::UINTPTR_T].size
612+
SIZEOF_CONST_STRING = Fiddle::FFIBackend::FFITypes[Types::VOIDP].size
600613
end

lib/fiddle/ruby.rb

Lines changed: 0 additions & 1 deletion
This file was deleted.

test/fiddle/helper.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,10 @@ def teardown
183183
end
184184
end
185185

186+
def ffi_backend?
187+
RUBY_ENGINE != 'ruby'
188+
end
189+
186190
def under_gc_stress
187191
stress, GC.stress = GC.stress, true
188192
yield

test/fiddle/test_closure.rb

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,9 @@ def call thing
5858
end
5959

6060
def test_const_string
61-
if RUBY_ENGINE == "jruby"
61+
if ffi_backend?
6262
omit("Closure with :const_string works but " +
63-
"Function with :const_string doesn't work with JRuby")
63+
"Function with :const_string doesn't work with FFI backend")
6464
end
6565

6666
closure_class = Class.new(Closure) do
@@ -119,9 +119,14 @@ def test_memsize_ruby_dev_42480
119119
end
120120

121121
require 'objspace'
122+
closure_class = Class.new(Closure) do
123+
def call
124+
10
125+
end
126+
end
122127
n = 10000
123128
n.times do
124-
Closure.create(:int, [:void]) do |closure|
129+
closure_class.create(:int, [:void]) do |closure|
125130
ObjectSpace.memsize_of(closure)
126131
end
127132
end

test/fiddle/test_fiddle.rb

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66

77
class TestFiddle < Fiddle::TestCase
88
def test_nil_true_etc
9-
if RUBY_ENGINE == "jruby"
10-
omit("Fiddle::Q* aren't supported with JRuby")
9+
if ffi_backend?
10+
omit("Fiddle::Q* aren't supported with FFI backend")
1111
end
1212

1313
assert_equal Fiddle::Qtrue, Fiddle.dlwrap(true)
@@ -30,6 +30,10 @@ def test_dlopen_linker_script_input_linux
3030
if Dir.glob("/usr/lib/*/libncurses.so").empty?
3131
omit("libncurses.so is needed")
3232
end
33+
if ffi_backend?
34+
omit("Fiddle::Handle#file_name doesn't exist in FFI backend")
35+
end
36+
3337
# libncurses.so uses INPUT() on Debian GNU/Linux
3438
# $ cat /usr/lib/x86_64-linux-gnu/libncurses.so
3539
# INPUT(libncurses.so.6 -ltinfo)
@@ -44,6 +48,10 @@ def test_dlopen_linker_script_input_linux
4448

4549
def test_dlopen_linker_script_group_linux
4650
omit("This is only for Linux") unless RUBY_PLATFORM.match?("linux")
51+
if ffi_backend?
52+
omit("Fiddle::Handle#file_name doesn't exist in FFI backend")
53+
end
54+
4755
# libc.so uses GROUP() on Debian GNU/Linux
4856
# $ cat /usr/lib/x86_64-linux-gnu/libc.so
4957
# /* GNU ld script

test/fiddle/test_func.rb

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ def test_strtod
6464
end
6565

6666
def test_qsort1
67+
if RUBY_ENGINE == "jruby"
68+
omit("The untouched sanity check is broken on JRuby: https://github.com/jruby/jruby/issues/8365")
69+
end
70+
6771
closure_class = Class.new(Closure) do
6872
def call(x, y)
6973
Pointer.new(x)[0] <=> Pointer.new(y)[0]
@@ -74,27 +78,32 @@ def call(x, y)
7478
qsort = Function.new(@libc['qsort'],
7579
[TYPE_VOIDP, TYPE_SIZE_T, TYPE_SIZE_T, TYPE_VOIDP],
7680
TYPE_VOID)
77-
buff = "9341"
81+
untouched = "9341"
82+
buff = +"9341"
7883
qsort.call(buff, buff.size, 1, callback)
7984
assert_equal("1349", buff)
8085

8186
bug4929 = '[ruby-core:37395]'
82-
buff = "9341"
87+
buff = +"9341"
8388
under_gc_stress do
8489
qsort.call(buff, buff.size, 1, callback)
8590
end
8691
assert_equal("1349", buff, bug4929)
92+
93+
# Ensure the test didn't mutate String literals
94+
assert_equal("93" + "41", untouched)
8795
end
8896
ensure
8997
# We can't use ObjectSpace with JRuby.
90-
return if RUBY_ENGINE == "jruby"
91-
# Ensure freeing all closures.
92-
# See https://github.com/ruby/fiddle/issues/102#issuecomment-1241763091 .
93-
not_freed_closures = []
94-
ObjectSpace.each_object(Fiddle::Closure) do |closure|
95-
not_freed_closures << closure unless closure.freed?
98+
unless RUBY_ENGINE == "jruby"
99+
# Ensure freeing all closures.
100+
# See https://github.com/ruby/fiddle/issues/102#issuecomment-1241763091 .
101+
not_freed_closures = []
102+
ObjectSpace.each_object(Fiddle::Closure) do |closure|
103+
not_freed_closures << closure unless closure.freed?
104+
end
105+
assert_equal([], not_freed_closures)
96106
end
97-
assert_equal([], not_freed_closures)
98107
end
99108

100109
def test_snprintf

0 commit comments

Comments
 (0)