rusby 0.1.0

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