rusby 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/.gitignore +3 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +51 -0
- data/LICENSE.txt +21 -0
- data/README.md +64 -0
- data/Rakefile +12 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/doc/img1.png +0 -0
- data/doc/slides.key +0 -0
- data/examples/Gemfile +3 -0
- data/examples/Gemfile.lock +31 -0
- data/examples/fanatic_greeter.rb +8 -0
- data/examples/fanatic_pluser.rb +8 -0
- data/examples/levenshtein_distance.rb +31 -0
- data/examples/quicksort.rb +32 -0
- data/examples/run_examples.rb +38 -0
- data/examples/weighted_random.rb +22 -0
- data/lib/rusby.rb +15 -0
- data/lib/rusby/builder.rb +149 -0
- data/lib/rusby/core.rb +113 -0
- data/lib/rusby/ffi/bridge.rb +42 -0
- data/lib/rusby/generators/assignments.rb +40 -0
- data/lib/rusby/generators/base.rb +66 -0
- data/lib/rusby/generators/conditionals.rb +38 -0
- data/lib/rusby/generators/loops.rb +54 -0
- data/lib/rusby/generators/misc.rb +26 -0
- data/lib/rusby/generators/strings.rb +10 -0
- data/lib/rusby/generators/types.rb +33 -0
- data/lib/rusby/postprocessor.rb +19 -0
- data/lib/rusby/preprocessor.rb +15 -0
- data/lib/rusby/profiler.rb +32 -0
- data/lib/rusby/proxy.rb +14 -0
- data/lib/rusby/rust.rb +69 -0
- data/lib/rusby/version.rb +3 -0
- data/rusby.gemspec +32 -0
- data/rust.yaml +38 -0
- metadata +223 -0
data/lib/rusby.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'parser/ruby22'
|
2
|
+
require 'yaml'
|
3
|
+
require 'hashie'
|
4
|
+
require 'method_source'
|
5
|
+
require 'colorize'
|
6
|
+
require 'benchmark'
|
7
|
+
require 'ffi'
|
8
|
+
|
9
|
+
require "rusby/version"
|
10
|
+
|
11
|
+
rusby_dir = File.expand_path('../rusby', __FILE__)
|
12
|
+
Dir["#{rusby_dir}/**/*.rb"].each { |file| require file }
|
13
|
+
|
14
|
+
module Rusby
|
15
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
module Rusby
|
2
|
+
module Builder
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def root_path
|
6
|
+
@root ||= File.expand_path('../../..', __FILE__)
|
7
|
+
end
|
8
|
+
|
9
|
+
def lib_path
|
10
|
+
return @lib_path if @lib_path
|
11
|
+
path = './tmp/rusby'
|
12
|
+
Dir.mkdir('./tmp') unless Dir.exists?('./tmp')
|
13
|
+
Dir.mkdir(path) unless Dir.exists?(path)
|
14
|
+
@lib_path = path
|
15
|
+
end
|
16
|
+
|
17
|
+
def rust
|
18
|
+
@rust ||= Hashie::Mash.new YAML.load_file("#{root_path}/rust.yaml")
|
19
|
+
end
|
20
|
+
|
21
|
+
def convert_to_rust(meta, method_name, orig_method, owner)
|
22
|
+
code = rust.file_header
|
23
|
+
code += construct_method(
|
24
|
+
Preprocessor.apply(orig_method.source),
|
25
|
+
meta[method_name]
|
26
|
+
)
|
27
|
+
code = expand_internal_methods(meta, code, owner)
|
28
|
+
code = Postrocessor.apply(code, meta[method_name])
|
29
|
+
|
30
|
+
File.open("#{lib_path}/#{method_name}.rs", 'w') do |file|
|
31
|
+
file.write(code)
|
32
|
+
end
|
33
|
+
`rustfmt #{lib_path}/#{method_name}.rs`
|
34
|
+
|
35
|
+
puts 'Done generating source code'.colorize(:yellow)
|
36
|
+
File.open("#{lib_path}/#{method_name}.rs") do |file|
|
37
|
+
code = file.read
|
38
|
+
code.split("\n").each_with_index do |line, i|
|
39
|
+
puts "#{(i + 1).to_s.rjust(3).colorize(:light_black)}. #{line}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
compile_and_load_rust(method_name, meta)
|
44
|
+
end
|
45
|
+
|
46
|
+
def compile_and_load_rust(method_name, meta)
|
47
|
+
signature = construct_signature(
|
48
|
+
meta[method_name][:args],
|
49
|
+
meta[method_name][:result]
|
50
|
+
)
|
51
|
+
puts "Compiling #{signature.last} #{method_name}(#{signature.first.join(', ')})".colorize(:yellow)
|
52
|
+
puts `rustc -A unused_imports --crate-type=dylib -O -o #{lib_path}/#{method_name}.dylib #{lib_path}/#{method_name}.rs`
|
53
|
+
|
54
|
+
Proxy.rusby_load "#{lib_path}/#{method_name}"
|
55
|
+
Proxy.attach_function "ffi_#{method_name}", *signature
|
56
|
+
|
57
|
+
Proxy.method("ffi_#{method_name}")
|
58
|
+
end
|
59
|
+
|
60
|
+
def rust_method_body(meta, ast)
|
61
|
+
name = ast.children.first
|
62
|
+
rust = Rust.new(@rust.rust_types[meta[:result]])
|
63
|
+
rust.remember_method(name)
|
64
|
+
|
65
|
+
result = ''
|
66
|
+
ast.children[2..-1].each do |node|
|
67
|
+
result += rust.generate(node)
|
68
|
+
end
|
69
|
+
result
|
70
|
+
end
|
71
|
+
|
72
|
+
def expand_internal_methods(meta, code, owner)
|
73
|
+
method_names = code.scan(/internal_method_(\w+)\([^\)]+\)/)[0]
|
74
|
+
return code unless method_names
|
75
|
+
|
76
|
+
result = code
|
77
|
+
method_names.each do |method_name|
|
78
|
+
source = owner.method(method_name).source
|
79
|
+
result += construct_method(
|
80
|
+
source,
|
81
|
+
meta[method_name.to_sym],
|
82
|
+
false
|
83
|
+
)
|
84
|
+
end
|
85
|
+
result
|
86
|
+
end
|
87
|
+
|
88
|
+
def ffi_wrapper(method_name, arg_names, arg_types, return_type)
|
89
|
+
args = arg_names.each_with_index.map do |arg_name, i|
|
90
|
+
if rust.ffi_to_rust_types[arg_types[i]]
|
91
|
+
rust.ffi_to_rust_types[arg_types[i]].gsub('<name>', arg_name)
|
92
|
+
else
|
93
|
+
"#{arg_name}: #{rust.rust_types[arg_types[i]]}"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
result = [
|
97
|
+
'',
|
98
|
+
'// this function folds ffi arguments and unfolds result to ffi types'
|
99
|
+
]
|
100
|
+
result << "#{rust.exposed_method_prefix} fn ffi_#{method_name}(#{args.join(', ')}) -> #{rust.rust_to_ffi_types[return_type] || rust.rust_types[return_type]} {"
|
101
|
+
arg_names.each_with_index.map do |arg_name, i|
|
102
|
+
next unless rust.ffi_to_rust[arg_types[i]] # for simple args we don't need any convertion
|
103
|
+
result << rust.ffi_to_rust[arg_types[i]].gsub('<name>', arg_name).to_s
|
104
|
+
end
|
105
|
+
result << "let result = #{method_name}(#{arg_names.join(', ')});" # calls the real method with ffi args folded
|
106
|
+
result << "return #{rust.rust_to_ffi[return_type] || 'result'};"
|
107
|
+
result << '}'
|
108
|
+
|
109
|
+
result
|
110
|
+
end
|
111
|
+
|
112
|
+
def construct_method(source, meta, exposed = true)
|
113
|
+
arg_types = meta[:args]
|
114
|
+
return_type = meta[:result]
|
115
|
+
|
116
|
+
ast = Parser::Ruby22.parse(source)
|
117
|
+
method_name = ast.children[0]
|
118
|
+
arg_names = ast.children[1].children.map { |ch| ch.children[0].to_s }
|
119
|
+
meta[:names] = arg_names
|
120
|
+
|
121
|
+
result = exposed ? ffi_wrapper(method_name, arg_names, arg_types, return_type) : []
|
122
|
+
result << rust.method_prefix
|
123
|
+
|
124
|
+
args = arg_names.each_with_index.map { |arg_name, i| "#{arg_name}: #{rust.rust_types[arg_types[i]]}" }
|
125
|
+
|
126
|
+
result << "fn #{exposed ? '' : 'internal_method_'}#{method_name}(#{args.join(', ')}) -> #{rust.rust_types[return_type]} {"
|
127
|
+
result << rust_method_body(meta, ast)
|
128
|
+
result << '}'
|
129
|
+
|
130
|
+
result.join("\n")
|
131
|
+
end
|
132
|
+
|
133
|
+
def construct_signature(arg_types, return_type)
|
134
|
+
args = arg_types.map do |arg|
|
135
|
+
rust_type = rust.ffi_types[arg]
|
136
|
+
unless rust_type
|
137
|
+
puts "Please define mapping from '#{arg}' " \
|
138
|
+
'to rust equivalent in rust.yaml'.colorize(:red)
|
139
|
+
raise 'Missing mapping'
|
140
|
+
end
|
141
|
+
rust_type.to_sym
|
142
|
+
end
|
143
|
+
[
|
144
|
+
args.map { |arg| arg == :pointer ? [arg, :int] : arg }.flatten,
|
145
|
+
rust.ffi_types[return_type].to_sym
|
146
|
+
]
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
data/lib/rusby/core.rb
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
module Rusby
|
2
|
+
module Core
|
3
|
+
BOOST_THRESHOLD = -1e6 # TODO: turn on
|
4
|
+
|
5
|
+
# next method should be added to the rusby table
|
6
|
+
def rusby!
|
7
|
+
@rusby_awaits_method = true
|
8
|
+
end
|
9
|
+
|
10
|
+
def rusby_replace_method(method_name, method_reference)
|
11
|
+
@rusby_skips_method = true
|
12
|
+
define_method(method_name, method_reference)
|
13
|
+
end
|
14
|
+
|
15
|
+
def rusby_type(object)
|
16
|
+
object.class == Array ? "Array[#{object[0].class}]" : object.class.to_s
|
17
|
+
end
|
18
|
+
|
19
|
+
# proxy method that records arg and result types
|
20
|
+
def rusby_method_proxy(object, method_name, method_reference, args)
|
21
|
+
bound_method = method_reference.bind(object)
|
22
|
+
result = bound_method.call(*args)
|
23
|
+
|
24
|
+
@rusby_method_table[method_name][:args] = args.map { |arg| rusby_type(arg) }
|
25
|
+
@rusby_method_table[method_name][:result] = rusby_type(result)
|
26
|
+
|
27
|
+
if @rusby_method_table[method_name][:exposed]
|
28
|
+
# try to convert to rust or return back the original method
|
29
|
+
rusby_convert_or_bust(method_name, method_reference, object, args)
|
30
|
+
else
|
31
|
+
# if we don't need to convert method to rust return back the original method
|
32
|
+
rusby_replace_method(method_name, method_reference)
|
33
|
+
end
|
34
|
+
|
35
|
+
result
|
36
|
+
end
|
37
|
+
|
38
|
+
def rusby_ffi_wrapper(method_name, method_reference, args)
|
39
|
+
bridge = ::Rusby::FFI::Bridge.new
|
40
|
+
arg_types = @rusby_method_table[method_name][:args]
|
41
|
+
ffi_args = arg_types.each_with_index.map do |arg_type, i|
|
42
|
+
bridge.to_ffi(arg_type, args[i])
|
43
|
+
end.flatten
|
44
|
+
ffi_result = method_reference.call(*ffi_args)
|
45
|
+
result_type = @rusby_method_table[method_name][:result]
|
46
|
+
bridge.from_ffi(result_type, ffi_result)
|
47
|
+
end
|
48
|
+
|
49
|
+
def rusby_convert_or_bust(method_name, method_reference, object, args)
|
50
|
+
# if we are converting recursive function
|
51
|
+
# we need to wait for it to exit all recursive calls
|
52
|
+
return if caller.any? { |entry| entry.include?("`#{method_name}'") }
|
53
|
+
|
54
|
+
rust_method = Builder.convert_to_rust(
|
55
|
+
@rusby_method_table,
|
56
|
+
method_name,
|
57
|
+
method_reference,
|
58
|
+
object
|
59
|
+
)
|
60
|
+
|
61
|
+
wrapped_rust_method = lambda do |*wrapped_args|
|
62
|
+
object.class.rusby_ffi_wrapper(method_name, rust_method, wrapped_args)
|
63
|
+
end
|
64
|
+
|
65
|
+
# check if rust method is running faster than the original one
|
66
|
+
puts 'Benchmarking native and rust methods (intertwined pattern)'.colorize(:yellow)
|
67
|
+
boost = Profiler.benchit(object, method_reference, wrapped_rust_method, args)
|
68
|
+
|
69
|
+
# coose between rust and ruby methods
|
70
|
+
resulting_method = method_reference
|
71
|
+
if boost > BOOST_THRESHOLD
|
72
|
+
resulting_method = wrapped_rust_method
|
73
|
+
puts 'Choose Rust. Choose speed.'.colorize(:green)
|
74
|
+
end
|
75
|
+
|
76
|
+
# set chosen method permanently
|
77
|
+
rusby_replace_method(method_name, resulting_method)
|
78
|
+
end
|
79
|
+
|
80
|
+
# module callbacks
|
81
|
+
def method_added(method_name)
|
82
|
+
super
|
83
|
+
|
84
|
+
if @rusby_skips_method
|
85
|
+
@rusby_skips_method = false
|
86
|
+
return
|
87
|
+
end
|
88
|
+
|
89
|
+
@rusby_method_table ||= {}
|
90
|
+
@rusby_method_table[method_name] = {}
|
91
|
+
|
92
|
+
if @rusby_awaits_method
|
93
|
+
@rusby_awaits_method = false
|
94
|
+
@rusby_method_table[method_name][:exposed] = true
|
95
|
+
end
|
96
|
+
|
97
|
+
original_method = instance_method(method_name)
|
98
|
+
new_method = lambda do |*args|
|
99
|
+
self.class.rusby_method_proxy(
|
100
|
+
self,
|
101
|
+
method_name,
|
102
|
+
original_method,
|
103
|
+
args
|
104
|
+
)
|
105
|
+
end
|
106
|
+
rusby_replace_method(method_name, new_method)
|
107
|
+
end
|
108
|
+
|
109
|
+
def singleton_method_added(method_name)
|
110
|
+
# TODO
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Rusby
|
2
|
+
module FFI
|
3
|
+
class Bridge
|
4
|
+
def self.parameterize(string)
|
5
|
+
string.gsub(/\W/, '_').gsub(/_{2,}/, '_').downcase
|
6
|
+
end
|
7
|
+
|
8
|
+
def to_ffi(name, value)
|
9
|
+
method_name = self.class.parameterize(name + '_to_ffi')
|
10
|
+
send(method_name, value)
|
11
|
+
end
|
12
|
+
|
13
|
+
def from_ffi(name, value)
|
14
|
+
method_name = self.class.parameterize(name + '_from_ffi')
|
15
|
+
send(method_name, value)
|
16
|
+
end
|
17
|
+
|
18
|
+
def array_fixnum_to_ffi(arg)
|
19
|
+
pointer = ::FFI::MemoryPointer.new :int, arg.size
|
20
|
+
pointer.put_array_of_int 0, arg
|
21
|
+
@size = arg.size
|
22
|
+
[pointer, arg.size]
|
23
|
+
end
|
24
|
+
|
25
|
+
def array_float_to_ffi(arg)
|
26
|
+
pointer = ::FFI::MemoryPointer.new :double, arg.size
|
27
|
+
pointer.put_array_of_double 0, arg
|
28
|
+
@size = arg.size
|
29
|
+
[pointer, arg.size]
|
30
|
+
end
|
31
|
+
|
32
|
+
def array_float_from_ffi(result)
|
33
|
+
result.read_array_of_double(@size)
|
34
|
+
end
|
35
|
+
|
36
|
+
# for arguments which don't need convertion
|
37
|
+
def method_missing(_method_name, *args)
|
38
|
+
args.first
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Rusby
|
2
|
+
module Generators
|
3
|
+
module Assignments
|
4
|
+
def generate_lvasgn(ast)
|
5
|
+
variable = ast.children[0]
|
6
|
+
return variable if ast.children.size == 1
|
7
|
+
|
8
|
+
result = "#{variable} = #{generate(ast.children[1])};"
|
9
|
+
result = "let mut #{result}" unless known_variable?(variable)
|
10
|
+
remember_variable(variable)
|
11
|
+
|
12
|
+
result
|
13
|
+
end
|
14
|
+
|
15
|
+
def generate_lvar(ast)
|
16
|
+
variable = ast.children[0]
|
17
|
+
variable
|
18
|
+
end
|
19
|
+
|
20
|
+
def generate_masgn(ast)
|
21
|
+
left = ast.children[0].children
|
22
|
+
right = ast.children[1].children
|
23
|
+
|
24
|
+
result = []
|
25
|
+
result += right.each_with_index.map do |_statement, i|
|
26
|
+
"let lv#{i} = #{generate(right[i])};"
|
27
|
+
end
|
28
|
+
result += left.each_with_index.map do |statement, i|
|
29
|
+
statement = generate(statement)
|
30
|
+
"#{statement =~ /=$/ ? statement : "#{statement} = "}lv#{i};"
|
31
|
+
end
|
32
|
+
result.join("\n")
|
33
|
+
end
|
34
|
+
|
35
|
+
def generate_op_asgn(ast)
|
36
|
+
"#{generate(ast.children[0])} #{ast.children[1]}= #{generate(ast.children[2])}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Rusby
|
2
|
+
module Generators
|
3
|
+
module Base
|
4
|
+
def generate(ast)
|
5
|
+
unless ast.respond_to?(:type)
|
6
|
+
# ok, it's not ast node, but it could be a method
|
7
|
+
# redefine internal ruby methods as needed
|
8
|
+
return case ast
|
9
|
+
when :<< || :>>
|
10
|
+
ast
|
11
|
+
when :length
|
12
|
+
'.len()'
|
13
|
+
when :min
|
14
|
+
'.iter().min()'
|
15
|
+
else
|
16
|
+
ast.to_s
|
17
|
+
end
|
18
|
+
end
|
19
|
+
send("generate_#{ast.type.to_s.tr('-', '_')}", ast)
|
20
|
+
end
|
21
|
+
|
22
|
+
def generate_send(ast)
|
23
|
+
if ast.children[0]
|
24
|
+
fold_arrays(ast.children).join
|
25
|
+
else
|
26
|
+
verb = ast.children[1]
|
27
|
+
case verb
|
28
|
+
when :puts
|
29
|
+
"\nprintln!(\"{}\", #{generate(ast.children[2])});io::stdout().flush().unwrap();\n"
|
30
|
+
# argument of this ruby method is rust code "as is"
|
31
|
+
when :rust
|
32
|
+
ast.children[2].children[0] + ' // generated by Rusby::Preprocessor'
|
33
|
+
# mark variabale as already defined
|
34
|
+
when :rust_variable
|
35
|
+
remember_variable(ast.children[2].children[0])
|
36
|
+
nil
|
37
|
+
else
|
38
|
+
return verb if known_variable?(verb)
|
39
|
+
|
40
|
+
arguments = fold_arrays(ast.children[2..-1])
|
41
|
+
result = "#{verb}(#{arguments.join(', ')});"
|
42
|
+
result = 'internal_method_' + result unless known_method?(ast.children[1])
|
43
|
+
result
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def generate_block(ast)
|
49
|
+
block_operator = ast.children[0].children[1]
|
50
|
+
case block_operator
|
51
|
+
when :loop
|
52
|
+
generate_loop(ast)
|
53
|
+
when :each
|
54
|
+
generate_each_loop(ast)
|
55
|
+
when :each_with_index
|
56
|
+
generate_each_with_index_loop(ast)
|
57
|
+
when :rust
|
58
|
+
ast.children[1..-1].to_s
|
59
|
+
else
|
60
|
+
statements = ast.children[1..-1].map { |node| generate(node) }.compact
|
61
|
+
"#{ast.children[0].children[1]} {\n#{statements.join("\n")}\n}"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Rusby
|
2
|
+
module Generators
|
3
|
+
module Conditionals
|
4
|
+
def generate_if(ast)
|
5
|
+
result = if ast.children[1]
|
6
|
+
generate_regular_if(ast)
|
7
|
+
else
|
8
|
+
generate_unless(ast)
|
9
|
+
end
|
10
|
+
result.gsub(/^\s+/, '')
|
11
|
+
end
|
12
|
+
|
13
|
+
def generate_unless(ast)
|
14
|
+
result = <<-EOF
|
15
|
+
if !(#{generate(ast.children[0])}) {
|
16
|
+
#{generate(ast.children[2])}
|
17
|
+
}
|
18
|
+
EOF
|
19
|
+
end
|
20
|
+
|
21
|
+
def generate_regular_if(ast)
|
22
|
+
result = <<-EOF
|
23
|
+
if #{generate(ast.children[0])} {
|
24
|
+
#{generate(ast.children[1])}
|
25
|
+
}
|
26
|
+
EOF
|
27
|
+
if ast.children[2]
|
28
|
+
result += <<-EOF
|
29
|
+
else {
|
30
|
+
#{generate(ast.children[2])}
|
31
|
+
}
|
32
|
+
EOF
|
33
|
+
end
|
34
|
+
result
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|