rusby 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|