virtual_module 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4bfab3d164f02ef899d40766d55420467ece2e23
4
+ data.tar.gz: 8f125f80cc967b5e40fa59879a53dcb97bbca308
5
+ SHA512:
6
+ metadata.gz: ec550f409511efd94ecaf381a5f5e1e73ba6c1000a0d663f142dc7582d487db8ee2cb04c51c7b709c81dd3b3416f5a1a8c1cd206a740fad4f9787e5d0b80c364
7
+ data.tar.gz: 40a0f820f59e823896b9b20b4b1c4c85f1f4c05902b2692e37c2c6821004831298834a4d1712b209cff6afba8c73da08f174d38ba5fbad18fd83f4230c9b8532
data/README.md ADDED
@@ -0,0 +1,28 @@
1
+ ## VirtualModule
2
+
3
+ If you feel good with Ruby's language design but *totally* disappointed with Ruby's computational performance such as terribly slow speed of loop or Math functions, then VirtualModule may save you a little.
4
+
5
+ With VirtualModule, you can run your Ruby code on the other language VM process(currently only Julia is supported).
6
+
7
+ ```
8
+ vm = VirtualModule.new(<<EOS)
9
+ def hi
10
+ "ho"
11
+ end
12
+
13
+ # @param [Fixnum] num
14
+ def hello(num)
15
+ num*num
16
+ end
17
+ EOS
18
+ p vm.hi #"ho"
19
+ include vm
20
+ p hello(33) #1089
21
+ ```
22
+
23
+ ## Other explanation
24
+
25
+ TBC
26
+
27
+ ## License
28
+ MIT
@@ -0,0 +1,10 @@
1
+ function +(x::ASCIIString, y::ASCIIString)
2
+ string(x, y)
3
+ end
4
+
5
+ function *(x::ASCIIString, y::Int)
6
+ repeated = map(1:3) do i
7
+ x
8
+ end
9
+ join(repeated, "")
10
+ end
@@ -0,0 +1,3 @@
1
+ module VirtualModule
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,297 @@
1
+ require 'msgpack'
2
+ require 'msgpack-rpc'
3
+ require 'ripper'
4
+ require 'binding_of_caller'
5
+ require 'julializer'
6
+ require 'open3'
7
+ require 'tmpdir'
8
+
9
+ module VirtualModule
10
+ require 'virtual_module/version'
11
+
12
+ class << self
13
+ def new(methods, config={})
14
+ if !config.is_a?(BaseBuilder)
15
+ config[:lang] = :julia if config[:lang].nil?
16
+ vm_builder = instance_eval("#{config[:lang].to_s.capitalize}Builder").new(config)
17
+ else
18
+ vm_builder = config
19
+ end
20
+ vm_builder.add(methods)
21
+ vm_builder.build
22
+ end
23
+ end
24
+
25
+ class BaseBuilder
26
+ def initialize(config={})
27
+ @source = []
28
+ if !config.is_a?(BaseIpcInterface)
29
+ props = {:ipc=>:file, :work_dir=>nil}
30
+ props.each do |k,v|
31
+ instance_variable_set("@#{k}", config[k] || v)
32
+ end
33
+ @ipc = instance_eval("#{@ipc.to_s.capitalize}IpcInterface").new(props.merge(config))
34
+ else
35
+ @ipc = config
36
+ end
37
+ end
38
+
39
+ def add(methods)
40
+ @source << methods
41
+ @ipc.reset get_compiled_code
42
+ end
43
+
44
+ def build
45
+ vm_builder = self
46
+ ipc = @ipc
47
+ vm = Module.new{
48
+ @vm_builder = vm_builder
49
+ @ipc = ipc
50
+ def self.virtual_module_eval(*args)
51
+ @vm_builder.send(:virtual_module_eval, *args)
52
+ end
53
+ def self.method_missing(key, *args, &block)
54
+ @vm_builder.send(:call, key, *args)
55
+ end
56
+ }
57
+ extract_defs(Ripper.sexp(@source.join(";"))).split(",").each{|e|
58
+ vm.class_eval {
59
+ define_method e.to_sym, Proc.new { |*args|
60
+ vm_builder.call(e.to_sym, *args)
61
+ }
62
+ }
63
+ }
64
+ vm
65
+ end
66
+
67
+ def call(name, *args)
68
+ Dir.mktmpdir(nil, Dir.home) do |tempdir|
69
+ @work_dir = @ipc.work_dir = tempdir
70
+ @ipc.call(name, *args)
71
+ end
72
+ end
73
+
74
+ private
75
+ def extract_defs(s)
76
+ if s.instance_of?(Array) && s[0].instance_of?(Symbol) then
77
+ if [:def].include?(s[0])
78
+ "#{s[1][1]},"
79
+ else
80
+ s.map{|e| extract_defs(e)}.join
81
+ end
82
+ elsif s.instance_of?(Array) && s[0].instance_of?(Array) then
83
+ s.map{|e| extract_defs(e)}.join
84
+ end
85
+ end
86
+
87
+ def extract_args(s)
88
+ if s.instance_of?(Array) && s[0].instance_of?(Symbol) then
89
+ if [:vcall, :var_field].include?(s[0])
90
+ "#{s[1][1]},"
91
+ else
92
+ s.map{|e| extract_args(e)}.join
93
+ end
94
+ elsif s.instance_of?(Array) && s[0].instance_of?(Array) then
95
+ s.map{|e| extract_args(e)}.join
96
+ end
97
+ end
98
+
99
+ def inspect_local_vars(context, script)
100
+ vars = extract_args(Ripper.sexp(script)).split(",").uniq.map{|e| e.to_sym} & context.eval("local_variables")
101
+ #p "vars=#{vars}"
102
+
103
+ type_info = {}
104
+ type_info[:params] = context.eval("Hash[ *#{vars}.collect { |e| [ e, eval(e.to_s).class.to_s ] }.flatten ]").select{|k,v| ["FloatArray", "IntArray"].include?(v)}
105
+ #p "type_info=#{type_info}"
106
+
107
+ params = context.eval(<<EOS)
108
+ require 'msgpack'
109
+ ___params = {}
110
+ ___params = local_variables.map{|e| [e, eval(e.to_s)]}.each{|e| ___params[e[0]]=e[1] if #{vars}.include?(e[0])}[0][1]
111
+ ___params
112
+ EOS
113
+ #File.write("#{@work_dir}.polykit.params", params)
114
+ [vars, type_info, params]
115
+ end
116
+ end
117
+
118
+
119
+ class JuliaBuilder < BaseBuilder
120
+ def virtual_module_eval(script, auto_binding=true)
121
+ vars, type_info, params = inspect_local_vars(binding.of_caller(2), script)
122
+ boot_script = <<EOS
123
+ function ___convert_type(name, typename, params)
124
+ if length(findin(#{type_info[:params].keys.map{|e| e.to_s}},[name]))>0
125
+ if typename=="FloatArray"
126
+ convert(Array{Float64,1}, params[name])
127
+ elseif typename=="IntArray"
128
+ convert(Array{Int64,1}, params[name])
129
+ end
130
+ else
131
+ params[name]
132
+ end
133
+ end
134
+
135
+ function ___main(params, type_info)
136
+ #{vars.map{|e| e.to_s + '=___convert_type("'+e.to_s+'","'+(type_info[:params][e]||"")+'", params)'}.join(";")}
137
+ ##{vars.map{|e| 'println("'+e.to_s+'=", typeof('+e.to_s+'))' }.join(";")}
138
+ ___evaluated = #{Julializer.ruby2julia(script)}
139
+
140
+ #{vars.map{|e| 'params["'+e.to_s+'"]='+e.to_s }.join(";") if auto_binding}
141
+
142
+ (___evaluated,#{auto_binding ? "params" : "-1" })
143
+ end
144
+ EOS
145
+
146
+ # File.write("#{@work_dir}.polykit.rb", <<EOS)
147
+ # require 'msgpack-rpc'
148
+ # client = MessagePack::RPC::Client.new('127.0.0.1', #{@port})
149
+ # client.timeout = #{@timeout}
150
+ # p client.call(:___main, #{params}, #{type_info})
151
+ #EOS
152
+
153
+ @ipc.reset get_compiled_code + ";#{boot_script}"
154
+ evaluated = self.call(:___main, params, type_info)
155
+ if auto_binding
156
+ binding.of_caller(2).eval(evaluated[1].map{|k,v| "#{k}=#{v};" if !v.nil? }.join)
157
+ end
158
+
159
+ return evaluated[0]
160
+ end
161
+
162
+ alias_method :virtual_instance_eval, :virtual_module_eval
163
+ alias_method :virtual_eval, :virtual_module_eval
164
+
165
+ private
166
+ def get_compiled_code
167
+ File.read(File.dirname(__FILE__)+"/virtual_module/bridge.jl") + ";" + Julializer.ruby2julia(@source.join(";\n"))
168
+ end
169
+
170
+ end
171
+
172
+ class BaseIpcInterface
173
+ INPUT_ARGS = "virtualmodule-input.msgpack"
174
+ OUTPUT_ARGS = "virtualmodule-output.msgpack"
175
+ ENTRYPOINT_SCRIPT = "virtualmodule-entrypoint.jl"
176
+ LIB_SCRIPT = "virtualmodule-lib.jl"
177
+
178
+ attr_accessor :work_dir
179
+
180
+ def initialize(config)
181
+ #do nothing
182
+ end
183
+ def call(name, *args)
184
+ #do nothing
185
+ end
186
+ def reset(source)
187
+ #do nothing
188
+ end
189
+ end
190
+
191
+ class FileIpcInterface < BaseIpcInterface
192
+ def call(name, *args)
193
+ File.write("#{@work_dir}/#{LIB_SCRIPT}", @lib_source)
194
+ File.write("#{@work_dir}/#{INPUT_ARGS}", MessagePack.pack(args))
195
+
196
+ if is_installed?(:julia)
197
+ File.write("#{@work_dir}/#{ENTRYPOINT_SCRIPT}", generate_entrypoint(@work_dir, "#{@work_dir}/#{OUTPUT_ARGS}", name, *args))
198
+ command = "julia --depwarn=no -L #{@work_dir}/#{LIB_SCRIPT} #{@work_dir}/#{ENTRYPOINT_SCRIPT}"
199
+ elsif is_installed?(:docker)
200
+ File.write("#{@work_dir}/#{ENTRYPOINT_SCRIPT}", generate_entrypoint("/opt/", "/opt/#{OUTPUT_ARGS}", name, *args))
201
+ command = "docker run -v #{@work_dir}/:/opt/ remore/virtual_module julia --depwarn=no -L /opt/virtualmodule-lib.jl /opt/virtualmodule-entrypoint.jl"
202
+ else
203
+ raise Exception.new("Either julia or docker command is required to run virtual_module")
204
+ end
205
+ out, err, status = Open3.capture3(command)
206
+ #byebug
207
+ # FIXME: Only For Debug
208
+ # printf out, err
209
+ MessagePack.unpack(File.read("#{@work_dir}/#{OUTPUT_ARGS}"))
210
+ end
211
+
212
+ def reset(source)
213
+ @lib_source = source
214
+ end
215
+
216
+ private
217
+ def is_installed?(command)
218
+ Open3.capture3("which #{command.to_s}")[0].size > 0
219
+ end
220
+
221
+ def generate_entrypoint(basedir, target, name, *args)
222
+ if args.count>0
223
+ script =<<EOS
224
+ using MsgPack
225
+ params = open( "#{basedir}/#{INPUT_ARGS}", "r" ) do fp
226
+ readall( fp )
227
+ end
228
+ result = #{name}(unpack(params)...)
229
+ EOS
230
+ else
231
+ script =<<EOS
232
+ using MsgPack
233
+ result = #{name}()
234
+ EOS
235
+ end
236
+
237
+ script + ";" + <<EOS
238
+ open( "#{target}", "w" ) do fp
239
+ write( fp, pack(result) )
240
+ end
241
+ EOS
242
+ end
243
+
244
+ end
245
+
246
+ class RpcIpcInterface < BaseIpcInterface
247
+ def initialize(config={})
248
+ init_connection
249
+ props = {:server=>"127.0.0.1", :port=>8746, :timeout=>10}
250
+ props.each do |k,v|
251
+ instance_variable_set("@#{k}", config[k] || v)
252
+ end
253
+ end
254
+
255
+ def call(name, *args)
256
+ restart_server_process @lib_source
257
+ while `echo exit | telnet #{@server} #{@port} 2>&1`.chomp[-5,5]!="host." do
258
+ sleep(0.05)
259
+ end
260
+ @client = MessagePack::RPC::Client.new(@server, @port) if @client.nil?
261
+ @client.timeout = @timeout
262
+ args.count>0 ? @client.call(name, *args) : @client.call(name)
263
+ end
264
+
265
+ def reset(source)
266
+ @lib_source = source
267
+ end
268
+
269
+ private
270
+ def init_connection
271
+ @pid = nil
272
+ @client.close if !@client.nil?
273
+ @client = nil
274
+ end
275
+
276
+ def restart_server_process(source)
277
+ Process.kill(:TERM, @pid) if !@pid.nil?
278
+ `lsof -wni tcp:#{@port} | cut -f 4 -d ' ' | sed -ne '2,$p' | xargs kill -9`
279
+ init_connection
280
+ compiled = <<EOS
281
+ import MsgPackRpcServer
282
+ module RemoteFunctions
283
+ #{source}
284
+ end
285
+ MsgPackRpcServer.run(#{@port}, RemoteFunctions)
286
+ EOS
287
+ File.write("#{@work_dir}/#{LIB_SCRIPT}", compiled)
288
+ @pid = Process.spawn("julia --depwarn=no #{@work_dir}/#{LIB_SCRIPT}")
289
+ end
290
+
291
+ at_exit do
292
+ @client.close if !@client.nil?
293
+ Process.kill(:TERM, @pid) if !@pid.nil?
294
+ end
295
+ end
296
+
297
+ end
metadata ADDED
@@ -0,0 +1,104 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: virtual_module
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Kei Sawada(@remore)
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-08-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: msgpack
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: msgpack-rpc
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: binding_of_caller
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: julializer
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Born to make your Ruby Code 3x faster. Hopefully.
70
+ email:
71
+ - k@swd.cc
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - README.md
77
+ - lib/virtual_module.rb
78
+ - lib/virtual_module/bridge.jl
79
+ - lib/virtual_module/version.rb
80
+ homepage: https://github.com/remore/virtual_module
81
+ licenses:
82
+ - MIT
83
+ metadata: {}
84
+ post_install_message:
85
+ rdoc_options: []
86
+ require_paths:
87
+ - lib
88
+ required_ruby_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: 1.9.1
93
+ required_rubygems_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ requirements: []
99
+ rubyforge_project:
100
+ rubygems_version: 2.5.1
101
+ signing_key:
102
+ specification_version: 4
103
+ summary: Born to make your Ruby Code 3x faster. Hopefully.
104
+ test_files: []