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.
@@ -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