Skip to content

Commit 241c516

Browse files
committed
[GR-15264] Implement Fiddle.
PullRequest: truffleruby/931
2 parents 3274038 + cd0f19d commit 241c516

40 files changed

+1727
-201
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# 19.2.0
22

3+
New features:
4+
5+
* `Fiddle` has been implemented.
6+
37
Bug fixes:
48

59
* Set `RbConfig::CONFIG['ruby_version']` to the same value as the TruffleRuby version. This fixes reusing C extensions between different versions of TruffleRuby with Bundler (#1715).

doc/contributor/stdlib.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
| `fcntl` | | C extension | Java extension | Ruby | None | |
2727
| `ffi` | | | Java extension | Ruby using `Polyglot` but it's limited | In the third-party FFi gem repository | Non-standard - third-party gem but defaults to included version on other implementations |
2828
| `fiber` | | C extension | Java extension | Java primitives | Specs | |
29-
| `fiddle` | FFI | C extension | Ruby using `FFI` | Ruby using `Polyglot` and `FFI` but it's limited | MRI | |
29+
| `fiddle` | FFI | C extension | Ruby using `FFI` | Ruby using `Polyglot`. | MRI | |
3030
| `fileutils` | | Ruby | As MRI (not in source repo) | As MRI | MRI | |
3131
| `find` | Traverse file systems | Ruby | As MRI | As MRI | Specs, MRI | |
3232
| `forwardable` | Method-call forwarding | Ruby | As MRI | As MRI | MRI | |

doc/user/compatibility.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,6 @@ The following standard libraries are unsupported.
7272
* `win32`
7373
* `win32ole`
7474

75-
`fiddle` is not yet implemented - the module and some methods are there
76-
but not enough to run anything serious.
77-
7875
We provide our own included implementation of the interface of the `ffi` gem,
7976
like JRuby and Rubinius. The implementation should be fairly complete and passes
8077
all the specs of the `ffi` gem except for some rarely-used corner cases.

lib/mri/fiddle.rb

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# frozen_string_literal: true
2+
require 'truffle/fiddle_backend' # 'fiddle.so' in MRI
3+
require 'fiddle/function'
4+
require 'fiddle/closure'
5+
6+
module Fiddle
7+
if WINDOWS
8+
# Returns the last win32 +Error+ of the current executing +Thread+ or nil
9+
# if none
10+
def self.win32_last_error
11+
Thread.current[:__FIDDLE_WIN32_LAST_ERROR__]
12+
end
13+
14+
# Sets the last win32 +Error+ of the current executing +Thread+ to +error+
15+
def self.win32_last_error= error
16+
Thread.current[:__FIDDLE_WIN32_LAST_ERROR__] = error
17+
end
18+
end
19+
20+
# Returns the last +Error+ of the current executing +Thread+ or nil if none
21+
def self.last_error
22+
Thread.current[:__FIDDLE_LAST_ERROR__]
23+
end
24+
25+
# Sets the last +Error+ of the current executing +Thread+ to +error+
26+
def self.last_error= error
27+
Thread.current[:__DL2_LAST_ERROR__] = error
28+
Thread.current[:__FIDDLE_LAST_ERROR__] = error
29+
end
30+
31+
# call-seq: dlopen(library) => Fiddle::Handle
32+
#
33+
# Creates a new handler that opens +library+, and returns an instance of
34+
# Fiddle::Handle.
35+
#
36+
# If +nil+ is given for the +library+, Fiddle::Handle::DEFAULT is used, which
37+
# is the equivalent to RTLD_DEFAULT. See <code>man 3 dlopen</code> for more.
38+
#
39+
# lib = Fiddle.dlopen(nil)
40+
#
41+
# The default is dependent on OS, and provide a handle for all libraries
42+
# already loaded. For example, in most cases you can use this to access
43+
# +libc+ functions, or ruby functions like +rb_str_new+.
44+
#
45+
# See Fiddle::Handle.new for more.
46+
def dlopen library
47+
Fiddle::Handle.new library
48+
end
49+
module_function :dlopen
50+
51+
# Add constants for backwards compat
52+
53+
RTLD_GLOBAL = Handle::RTLD_GLOBAL # :nodoc:
54+
RTLD_LAZY = Handle::RTLD_LAZY # :nodoc:
55+
RTLD_NOW = Handle::RTLD_NOW # :nodoc:
56+
end

lib/mri/fiddle/closure.rb

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# frozen_string_literal: true
2+
module Fiddle
3+
class Closure
4+
5+
# the C type of the return of the FFI closure
6+
attr_reader :ctype
7+
8+
# arguments of the FFI closure
9+
attr_reader :args
10+
11+
# Extends Fiddle::Closure to allow for building the closure in a block
12+
class BlockCaller < Fiddle::Closure
13+
14+
# == Description
15+
#
16+
# Construct a new BlockCaller object.
17+
#
18+
# * +ctype+ is the C type to be returned
19+
# * +args+ are passed the callback
20+
# * +abi+ is the abi of the closure
21+
#
22+
# If there is an error in preparing the +ffi_cif+ or +ffi_prep_closure+,
23+
# then a RuntimeError will be raised.
24+
#
25+
# == Example
26+
#
27+
# include Fiddle
28+
#
29+
# cb = Closure::BlockCaller.new(TYPE_INT, [TYPE_INT]) do |one|
30+
# one
31+
# end
32+
#
33+
# func = Function.new(cb, [TYPE_INT], TYPE_INT)
34+
#
35+
def initialize ctype, args, abi = Fiddle::Function::DEFAULT, &block
36+
super(ctype, args, abi)
37+
@block = block
38+
end
39+
40+
# Calls the constructed BlockCaller, with +args+
41+
#
42+
# For an example see Fiddle::Closure::BlockCaller.new
43+
#
44+
def call *args
45+
@block.call(*args)
46+
end
47+
end
48+
end
49+
end

lib/mri/fiddle/cparser.rb

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
# frozen_string_literal: true
2+
module Fiddle
3+
# A mixin that provides methods for parsing C struct and prototype signatures.
4+
#
5+
# == Example
6+
# require 'fiddle/import'
7+
#
8+
# include Fiddle::CParser
9+
# #=> Object
10+
#
11+
# parse_ctype('int')
12+
# #=> Fiddle::TYPE_INT
13+
#
14+
# parse_struct_signature(['int i', 'char c'])
15+
# #=> [[Fiddle::TYPE_INT, Fiddle::TYPE_CHAR], ["i", "c"]]
16+
#
17+
# parse_signature('double sum(double, double)')
18+
# #=> ["sum", Fiddle::TYPE_DOUBLE, [Fiddle::TYPE_DOUBLE, Fiddle::TYPE_DOUBLE]]
19+
#
20+
module CParser
21+
# Parses a C struct's members
22+
#
23+
# Example:
24+
# require 'fiddle/import'
25+
#
26+
# include Fiddle::CParser
27+
# #=> Object
28+
#
29+
# parse_struct_signature(['int i', 'char c'])
30+
# #=> [[Fiddle::TYPE_INT, Fiddle::TYPE_CHAR], ["i", "c"]]
31+
#
32+
# parse_struct_signature(['char buffer[80]'])
33+
# #=> [[[Fiddle::TYPE_CHAR, 80]], ["buffer"]]
34+
#
35+
def parse_struct_signature(signature, tymap=nil)
36+
if signature.is_a?(String)
37+
signature = split_arguments(signature, /[,;]/)
38+
end
39+
mems = []
40+
tys = []
41+
signature.each{|msig|
42+
msig = compact(msig)
43+
case msig
44+
when /^[\w\*\s]+[\*\s](\w+)$/
45+
mems.push($1)
46+
tys.push(parse_ctype(msig, tymap))
47+
when /^[\w\*\s]+\(\*(\w+)\)\(.*?\)$/
48+
mems.push($1)
49+
tys.push(parse_ctype(msig, tymap))
50+
when /^([\w\*\s]+[\*\s])(\w+)\[(\d+)\]$/
51+
mems.push($2)
52+
tys.push([parse_ctype($1.strip, tymap), $3.to_i])
53+
when /^([\w\*\s]+)\[(\d+)\](\w+)$/
54+
mems.push($3)
55+
tys.push([parse_ctype($1.strip, tymap), $2.to_i])
56+
else
57+
raise(RuntimeError,"can't parse the struct member: #{msig}")
58+
end
59+
}
60+
return tys, mems
61+
end
62+
63+
# Parses a C prototype signature
64+
#
65+
# If Hash +tymap+ is provided, the return value and the arguments from the
66+
# +signature+ are expected to be keys, and the value will be the C type to
67+
# be looked up.
68+
#
69+
# Example:
70+
# require 'fiddle/import'
71+
#
72+
# include Fiddle::CParser
73+
# #=> Object
74+
#
75+
# parse_signature('double sum(double, double)')
76+
# #=> ["sum", Fiddle::TYPE_DOUBLE, [Fiddle::TYPE_DOUBLE, Fiddle::TYPE_DOUBLE]]
77+
#
78+
# parse_signature('void update(void (*cb)(int code))')
79+
# #=> ["update", Fiddle::TYPE_VOID, [Fiddle::TYPE_VOIDP]]
80+
#
81+
# parse_signature('char (*getbuffer(void))[80]')
82+
# #=> ["getbuffer", Fiddle::TYPE_VOIDP, []]
83+
#
84+
def parse_signature(signature, tymap=nil)
85+
tymap ||= {}
86+
case compact(signature)
87+
when /^(?:[\w\*\s]+)\(\*(\w+)\((.*?)\)\)(?:\[\w*\]|\(.*?\));?$/
88+
func, args = $1, $2
89+
return [func, TYPE_VOIDP, split_arguments(args).collect {|arg| parse_ctype(arg, tymap)}]
90+
when /^([\w\*\s]+[\*\s])(\w+)\((.*?)\);?$/
91+
ret, func, args = $1.strip, $2, $3
92+
return [func, parse_ctype(ret, tymap), split_arguments(args).collect {|arg| parse_ctype(arg, tymap)}]
93+
else
94+
raise(RuntimeError,"can't parse the function prototype: #{signature}")
95+
end
96+
end
97+
98+
# Given a String of C type +ty+, returns the corresponding Fiddle constant.
99+
#
100+
# +ty+ can also accept an Array of C type Strings, and will be returned in
101+
# a corresponding Array.
102+
#
103+
# If Hash +tymap+ is provided, +ty+ is expected to be the key, and the
104+
# value will be the C type to be looked up.
105+
#
106+
# Example:
107+
# require 'fiddle/import'
108+
#
109+
# include Fiddle::CParser
110+
# #=> Object
111+
#
112+
# parse_ctype('int')
113+
# #=> Fiddle::TYPE_INT
114+
#
115+
# parse_ctype('double diff')
116+
# #=> Fiddle::TYPE_DOUBLE
117+
#
118+
# parse_ctype('unsigned char byte')
119+
# #=> -Fiddle::TYPE_CHAR
120+
#
121+
# parse_ctype('const char* const argv[]')
122+
# #=> -Fiddle::TYPE_VOIDP
123+
#
124+
def parse_ctype(ty, tymap=nil)
125+
tymap ||= {}
126+
case ty
127+
when Array
128+
return [parse_ctype(ty[0], tymap), ty[1]]
129+
when 'void'
130+
return TYPE_VOID
131+
when /^(?:(?:signed\s+)?long\s+long(?:\s+int\s+)?|int64_t)(?:\s+\w+)?$/
132+
if( defined?(TYPE_LONG_LONG) )
133+
return TYPE_LONG_LONG
134+
else
135+
raise(RuntimeError, "unsupported type: #{ty}")
136+
end
137+
when /^(?:unsigned\s+long\s+long(?:\s+int\s+)?|uint64_t)(?:\s+\w+)?$/
138+
if( defined?(TYPE_LONG_LONG) )
139+
return -TYPE_LONG_LONG
140+
else
141+
raise(RuntimeError, "unsupported type: #{ty}")
142+
end
143+
when /^(?:signed\s+)?long(?:\s+int\s+)?(?:\s+\w+)?$/
144+
return TYPE_LONG
145+
when /^unsigned\s+long(?:\s+int\s+)?(?:\s+\w+)?$/
146+
return -TYPE_LONG
147+
when /^(?:signed\s+)?int(?:\s+\w+)?$/
148+
return TYPE_INT
149+
when /^(?:unsigned\s+int|uint)(?:\s+\w+)?$/
150+
return -TYPE_INT
151+
when /^(?:signed\s+)?short(?:\s+int\s+)?(?:\s+\w+)?$/
152+
return TYPE_SHORT
153+
when /^unsigned\s+short(?:\s+int\s+)?(?:\s+\w+)?$/
154+
return -TYPE_SHORT
155+
when /^(?:signed\s+)?char(?:\s+\w+)?$/
156+
return TYPE_CHAR
157+
when /^unsigned\s+char(?:\s+\w+)?$/
158+
return -TYPE_CHAR
159+
when /^float(?:\s+\w+)?$/
160+
return TYPE_FLOAT
161+
when /^double(?:\s+\w+)?$/
162+
return TYPE_DOUBLE
163+
when /^size_t(?:\s+\w+)?$/
164+
return TYPE_SIZE_T
165+
when /^ssize_t(?:\s+\w+)?$/
166+
return TYPE_SSIZE_T
167+
when /^ptrdiff_t(?:\s+\w+)?$/
168+
return TYPE_PTRDIFF_T
169+
when /^intptr_t(?:\s+\w+)?$/
170+
return TYPE_INTPTR_T
171+
when /^uintptr_t(?:\s+\w+)?$/
172+
return TYPE_UINTPTR_T
173+
when /\*/, /\[[\s\d]*\]/
174+
return TYPE_VOIDP
175+
else
176+
ty = ty.split(' ', 2)[0]
177+
if( tymap[ty] )
178+
return parse_ctype(tymap[ty], tymap)
179+
else
180+
raise(DLError, "unknown type: #{ty}")
181+
end
182+
end
183+
end
184+
185+
private
186+
187+
def split_arguments(arguments, sep=',')
188+
return [] if arguments.strip == 'void'
189+
arguments.scan(/([\w\*\s]+\(\*\w*\)\(.*?\)|[\w\*\s\[\]]+)(?:#{sep}\s*|$)/).collect {|m| m[0]}
190+
end
191+
192+
def compact(signature)
193+
signature.gsub(/\s+/, ' ').gsub(/\s*([\(\)\[\]\*,;])\s*/, '\1').strip
194+
end
195+
196+
end
197+
end

lib/mri/fiddle/function.rb

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# frozen_string_literal: true
2+
module Fiddle
3+
class Function
4+
# The ABI of the Function.
5+
attr_reader :abi
6+
7+
# The address of this function
8+
attr_reader :ptr
9+
10+
# The name of this function
11+
attr_reader :name
12+
13+
# The integer memory location of this function
14+
def to_i
15+
ptr.to_i
16+
end
17+
end
18+
end

0 commit comments

Comments
 (0)