opal-webassembly 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.rspec +3 -0
  4. data/.rspec-opal +3 -0
  5. data/.travis.yml +6 -0
  6. data/Gemfile +25 -0
  7. data/LICENSE.adoc +31 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.adoc +45 -0
  10. data/Rakefile +13 -0
  11. data/bin/console +14 -0
  12. data/bin/opal-repl-wasm +4 -0
  13. data/bin/setup +8 -0
  14. data/examples/experiment/Makefile +2 -0
  15. data/examples/experiment/experiment-wasm.c +74 -0
  16. data/examples/experiment/experiment-wasm.wasm +0 -0
  17. data/examples/experiment/experiment.rb +25 -0
  18. data/examples/experiment/experiment.sh +2 -0
  19. data/examples/onigmo/onigmo-wasm.wasm +0 -0
  20. data/examples/onigmo/onigmo.rb +4 -0
  21. data/examples/onigmo/onigmo.sh +2 -0
  22. data/examples/simple_ffi/Rakefile +14 -0
  23. data/examples/simple_ffi/simple-wasm.wasm +0 -0
  24. data/examples/simple_ffi/simple-wasm.wat +8 -0
  25. data/examples/simple_ffi/simple.rb +14 -0
  26. data/exe/opal-repl-wasm +9 -0
  27. data/lib/opal/webassembly.rb +9 -0
  28. data/lib/opal/webassembly/processor.rb +49 -0
  29. data/lib/opal/webassembly/version.rb +5 -0
  30. data/opal-webassembly.gemspec +31 -0
  31. data/opal/ffi.rb +22 -0
  32. data/opal/ffi/library.rb +67 -0
  33. data/opal/ffi/pointer.rb +289 -0
  34. data/opal/ffi/struct.rb +125 -0
  35. data/opal/ffi/types.rb +226 -0
  36. data/opal/webassembly.rb +68 -0
  37. data/opal/webassembly/global.rb +15 -0
  38. data/opal/webassembly/instance.rb +44 -0
  39. data/opal/webassembly/memory.rb +18 -0
  40. data/opal/webassembly/module.rb +10 -0
  41. data/opal/webassembly/table.rb +35 -0
  42. data/spec-opal/ffi_spec.rb +139 -0
  43. data/spec-opal/spec_helper.rb +18 -0
  44. data/spec-opal/webassembly_spec.rb +12 -0
  45. metadata +104 -0
@@ -0,0 +1,5 @@
1
+ module Opal
2
+ module WebAssembly
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,31 @@
1
+ require_relative 'lib/opal/webassembly/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "opal-webassembly"
5
+ spec.version = Opal::WebAssembly::VERSION
6
+ spec.authors = ["Ribose Inc."]
7
+ spec.email = ["open.source@ribose.com"]
8
+
9
+ spec.summary = %q{Opal with WebAssembly support}
10
+ #spec.description = %q{TODO: Write a longer description or delete this line.}
11
+ spec.homepage = "https://github.com/interscript/opal-webassembly"
12
+ spec.license = "BSD-2-Clause"
13
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
14
+
15
+ #spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = "https://github.com/interscript/opal-webassembly"
19
+ spec.metadata["changelog_uri"] = "https://github.com/interscript/opal-webassembly/commits/master"
20
+
21
+ # Specify which files should be added to the gem when it is released.
22
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
24
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
25
+ end
26
+ spec.bindir = "exe"
27
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
+ spec.require_paths = ["lib"]
29
+
30
+ spec.add_dependency "opal"
31
+ end
@@ -0,0 +1,22 @@
1
+ require 'webassembly'
2
+
3
+ # An API trying to be compatible with Ruby-FFI.
4
+ module FFI
5
+ def self.contexts
6
+ (@contexts ||= [])
7
+ end
8
+
9
+ def self.context
10
+ (@contexts ||= []).last.tap do |lib|
11
+ unless lib
12
+ raise RuntimeError, "This call needs to be done in a FFI::Library#context "+
13
+ "block (Opal-WebAssembly limitation)."
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ require 'ffi/types'
20
+ require 'ffi/pointer'
21
+ require 'ffi/struct'
22
+ require 'ffi/library'
@@ -0,0 +1,67 @@
1
+ module FFI
2
+ module Library
3
+ def self.extended(mod)
4
+ raise RuntimeError.new("must only be extended by module") unless mod.kind_of?(Module)
5
+ end
6
+
7
+ def ffi_lib(*names)
8
+ # Expect the library (first one) to be already loaded in "Opal.WebAssembly.modules"
9
+ @ffi_lib = WebAssembly.libs[names.first]
10
+ raise LoadError, "Library #{names.first} not loaded" if !@ffi_lib
11
+
12
+ begin
13
+ attach_function :malloc, [:long], :pointer
14
+ attach_function :free, [:pointer], :void
15
+ attach_function :realloc, [:pointer, :long], :pointer
16
+ rescue LoadError
17
+ # It's ok, a library doesn't really need to provide memory functions.
18
+ # But we will need those.
19
+ end
20
+ end
21
+
22
+ def library
23
+ @ffi_lib
24
+ end
25
+
26
+ def attach_function(name, func, args = nil, returns = nil, **options)
27
+ if func.is_a? Array # Don't rename a function
28
+ func, args, returns = name, func, args
29
+ end
30
+
31
+ raise LoadError, "No library responds to #{func}" unless @ffi_lib.exports.has_key?(func)
32
+ fun = @ffi_lib.exports[func]
33
+
34
+ self.define_singleton_method name do |*as|
35
+ if as.count != args.count # :varargs?
36
+ raise ArgumentError, "Provided #{as.count} arguments, expected #{args.count}"
37
+ end
38
+
39
+ as = as.each_with_index.map do |a,ind|
40
+ type = Type[args[ind]]
41
+ if type.respond_to? :to_native_mem
42
+ type.to_native_mem(a, @ffi_lib.memory)
43
+ else
44
+ type.to_native(a)
45
+ end
46
+ end
47
+
48
+ ret = fun.call(*as)
49
+ type = Type[returns]
50
+ if type.respond_to? :from_native_mem
51
+ type.from_native_mem(ret, @ffi_lib.memory)
52
+ else
53
+ type.from_native(ret)
54
+ end
55
+ end
56
+ end
57
+
58
+ # Launch a block in a current context. Needed for memory management.
59
+ # Think: segmented memory.
60
+ def context &block
61
+ FFI.contexts.push self
62
+ out = yield
63
+ FFI.contexts.pop
64
+ out
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,289 @@
1
+ # Large portions of this file are adapted from
2
+ # https://github.com/ffi/ffi/blob/master/lib/ffi/pointer.rb
3
+
4
+ module FFI
5
+ class Pointer
6
+ SIZE = 4
7
+
8
+ # Return the size of a pointer on the current platform, in bytes
9
+ # @return [Numeric]
10
+ def self.size
11
+ SIZE
12
+ end
13
+
14
+ # @param [nil,Numeric] len length of string to return
15
+ # @return [String]
16
+ # Read pointer's contents as a string, or the first +len+ bytes of the
17
+ # equivalent string if +len+ is not +nil+.
18
+ def read_string(len=nil)
19
+ if len
20
+ return ''.b if len == 0
21
+ get_bytes(0, len)
22
+ else
23
+ get_string(0)
24
+ end
25
+ end
26
+
27
+ # @param [Numeric] len length of string to return
28
+ # @return [String]
29
+ # Read the first +len+ bytes of pointer's contents as a string.
30
+ #
31
+ # Same as:
32
+ # ptr.read_string(len) # with len not nil
33
+ def read_string_length(len)
34
+ get_bytes(0, len)
35
+ end
36
+
37
+ # @return [String]
38
+ # Read pointer's contents as a string.
39
+ #
40
+ # Same as:
41
+ # ptr.read_string # with no len
42
+ def read_string_to_null
43
+ get_string(0)
44
+ end
45
+
46
+ # @param [String] str string to write
47
+ # @param [Numeric] len length of string to return
48
+ # @return [self]
49
+ # Write +len+ first bytes of +str+ in pointer's contents.
50
+ #
51
+ # Same as:
52
+ # ptr.write_string(str, len) # with len not nil
53
+ def write_string_length(str, len)
54
+ put_bytes(0, str, 0, len)
55
+ end
56
+
57
+ # @param [String] str string to write
58
+ # @param [Numeric] len length of string to return
59
+ # @return [self]
60
+ # Write +str+ in pointer's contents, or first +len+ bytes if
61
+ # +len+ is not +nil+.
62
+ def write_string(str, len=nil)
63
+ #len = str.bytesize unless len
64
+ # Write the string data without NUL termination
65
+ put_bytes(0, str, 0, len)
66
+ end
67
+
68
+ # @param [Type] type type of data to read from pointer's contents
69
+ # @param [Symbol] reader method to send to +self+ to read +type+
70
+ # @param [Numeric] length
71
+ # @return [Array]
72
+ # Read an array of +type+ of length +length+.
73
+ # @example
74
+ # ptr.read_array_of_type(TYPE_UINT8, :read_uint8, 4) # -> [1, 2, 3, 4]
75
+ def read_array_of_type(type, reader, length)
76
+ ary = []
77
+ size = FFI.type_size(type)
78
+ tmp = self
79
+ length.times { |j|
80
+ ary << tmp.send(reader)
81
+ tmp += size unless j == length-1 # avoid OOB
82
+ }
83
+ ary
84
+ end
85
+
86
+ # @param [Type] type type of data to write to pointer's contents
87
+ # @param [Symbol] writer method to send to +self+ to write +type+
88
+ # @param [Array] ary
89
+ # @return [self]
90
+ # Write +ary+ in pointer's contents as +type+.
91
+ # @example
92
+ # ptr.write_array_of_type(TYPE_UINT8, :put_uint8, [1, 2, 3 ,4])
93
+ def write_array_of_type(type, writer, ary)
94
+ size = FFI.type_size(type)
95
+ ary.each_with_index { |val, i|
96
+ break unless i < self.size
97
+ self.send(writer, i * size, val)
98
+ }
99
+ self
100
+ end
101
+
102
+ # @return [self]
103
+ def to_ptr
104
+ self
105
+ end
106
+
107
+ def pointer
108
+ self
109
+ end
110
+
111
+ # @param [Symbol,Type] type of data to read
112
+ # @return [Object]
113
+ # Read pointer's contents as +type+
114
+ #
115
+ # Same as:
116
+ # ptr.get(type, 0)
117
+ def read(type)
118
+ get(type, 0)
119
+ end
120
+
121
+ # @param [Symbol,Type] type of data to read
122
+ # @param [Object] value to write
123
+ # @return [nil]
124
+ # Write +value+ of type +type+ to pointer's content
125
+ #
126
+ # Same as:
127
+ # ptr.put(type, 0)
128
+ def write(type, value)
129
+ put(type, 0, value)
130
+ end
131
+
132
+ def get_bytes(pos, len)
133
+ ary = @memory.buffer.to_a
134
+
135
+ out = []
136
+ i = 0
137
+ while i < len
138
+ out << ary[@address + pos + i]
139
+ i += 1
140
+ end
141
+
142
+ # We expect a binary string as an output of this function.
143
+ out.pack("c*").b # TODO upstream: #bytes is wrong!
144
+ end
145
+
146
+ def put_bytes(pos, src, src_pos=0, len=nil)
147
+ if src.respond_to? :bytes
148
+ src = if src.encoding == Encoding::BINARY
149
+ src.chars.map(&:ord)
150
+ else
151
+ src.bytes
152
+ end
153
+ end
154
+ len ||= src.length - src_pos
155
+
156
+ ary = @memory.buffer.to_a
157
+ i = 0
158
+ while i < len
159
+ ary[@address + i + pos] = src[src_pos + i]
160
+ i += 1
161
+ end
162
+ end
163
+
164
+ def get(type, pos)
165
+ type = FFI::Type[type]
166
+ bytes = get_bytes(pos, type.size)
167
+ if type.respond_to? :from_native_mem
168
+ type.from_native_mem(type.unpack(bytes), @memory)
169
+ else
170
+ type.from_native(type.unpack(bytes))
171
+ end
172
+ end
173
+
174
+ def put(type, pos, value)
175
+ type = FFI::Type[type]
176
+ value = if type.respond_to? :to_native_mem
177
+ type.to_native_mem(value, @memory)
178
+ else
179
+ type.to_native(value)
180
+ end
181
+ value = type.pack(value)
182
+ put_bytes(pos, value)
183
+ end
184
+
185
+ def get_string(pos)
186
+ out = []
187
+ ary = @memory.buffer.to_a
188
+ pos += @address
189
+ loop do
190
+ byte = ary[pos]
191
+ break if byte == 0
192
+ out << byte
193
+ pos += 1
194
+ end
195
+ out.pack("c*").b
196
+ end
197
+
198
+ def put_string(pos, string)
199
+ put_bytes(pos, string)
200
+ end
201
+
202
+ def self.from_string src
203
+ bs = if src.encoding == Encoding::BINARY
204
+ src.chars.map(&:ord)
205
+ else
206
+ src.bytes
207
+ end
208
+ bs = bs + [0, 0]
209
+ out = new(:uint8, bs.count)
210
+ out.put_bytes(0, bs)
211
+ out
212
+ end
213
+
214
+ def self.alloc_out size, count, smh=false
215
+ new(:uint8, size*count)
216
+ end
217
+
218
+ def self.alloc_in size, count, smh=false
219
+ new(:uint8, size*count)
220
+ end
221
+
222
+ def method_missing method, *args
223
+ method = method.to_s
224
+ super
225
+ end
226
+
227
+ # Pointer in our case is more complex than a regular one: it's
228
+ # [WebAssembly::Memory instance, uint32 address] since WebAssembly
229
+ # "processes" run in separate address spaces.
230
+ #
231
+ # Alternatively, memory can be deduced from wrapping inside
232
+ # Library#context.
233
+ def initialize(address, type=nil, size=nil)
234
+ if address.respond_to? :address
235
+ if address.respond_to? :memory
236
+ @memory = address.memory || FFI.context.library.memory
237
+ else
238
+ @memory = FFI.context.library.memory
239
+ end
240
+ @address = address.address
241
+ elsif address.respond_to?(:to_sym) || address.is_a?(Type) || address.is_a?(Class) # Allocation call
242
+ type, count = address, type
243
+ @memory = FFI.context.library.memory
244
+ @address = FFI.context.malloc(FFI::Type[type].size * (count || 1)).address
245
+ elsif address.respond_to? :to_ary
246
+ @memory, @address = address.to_ary
247
+ @size = size
248
+ elsif address.respond_to? :to_int
249
+ @memory = FFI.context.library.memory
250
+ @address = address.to_int
251
+ @size = size
252
+ else
253
+ raise TypeError, "Address has an invalid type"
254
+ end
255
+ @type = type ? FFI::Type[type] : FFI::Type[:uint8]
256
+ end
257
+
258
+ attr_accessor :memory, :address, :type
259
+
260
+ def == other
261
+ (self.address == other.address) &&
262
+ (self.address == 0 || (self.memory == other.memory))
263
+ end
264
+
265
+ def + offset
266
+ self.dup.tap { |i| i.address += offset * type.size }
267
+ end
268
+
269
+ def [] offset
270
+ self.get(type, offset * type.size)
271
+ end
272
+
273
+ def []= offset, value
274
+ self.put(type, offset * type.size, value)
275
+ end
276
+
277
+ def resize(new_size)
278
+ self.address = FFI.context.realloc(self, new_size).address
279
+ end
280
+
281
+ def inspect
282
+ out = "#<#{self.class.name}:#{"0x%08x" % self.address} of #{self.type.inspect}>"
283
+ end
284
+
285
+ NULL = new([nil, 0])
286
+ end
287
+
288
+ AutoPointer = MemoryPointer = Buffer = Pointer
289
+ end
@@ -0,0 +1,125 @@
1
+ module FFI
2
+ class Struct
3
+ def self.size
4
+ @size || 4
5
+ end
6
+
7
+ def self.alignment
8
+ @alignment ||= @layout&.values&.map(&:type)&.map(&:alignment)&.max || 4
9
+ end
10
+
11
+ def self.by_value; self; end
12
+ def self.by_ptr; FFI::Type::WrappedStruct.new(self); end
13
+
14
+ def self.pack x; raise ArgumentError, "Packing Struct directly is forbidden"; end
15
+ def self.unpack x; raise ArgumentError, "Unpacking Struct directly is forbidden"; end
16
+
17
+ def from_native_mem(x, memory)
18
+ new(FFI::Pointer.new([memory, x], self))
19
+ end
20
+
21
+ def to_native(x)
22
+ x.pointer.address
23
+ end
24
+
25
+ def self.ptr; by_ptr; end
26
+
27
+ def self.layout *fs, **fields
28
+ if fs.length > 0
29
+ while (z = fs.shift(2)).length == 2
30
+ k, v = z
31
+ fields[k] = v
32
+ end
33
+ end
34
+
35
+ offset = 0
36
+ @layout = {}
37
+ fields.each do |name, type|
38
+ count = nil
39
+ if type.is_a? Array
40
+ type, count = type
41
+ end
42
+ type = FFI::Type[type, :struct]
43
+
44
+ offset += 1 until (offset % type.alignment) == 0
45
+ @layout[name] = Field.new(self, name, type, offset, count)
46
+
47
+ if count
48
+ offset += type.size * count
49
+ else
50
+ offset += type.size
51
+ end
52
+ end
53
+ @size = offset
54
+ end
55
+
56
+ def self.members; @layout; end
57
+ def members; self.class.members; end
58
+
59
+ def [] field
60
+ members[field].get(self)
61
+ end
62
+
63
+ def []= field, value
64
+ members[field].set(self, value)
65
+ end
66
+
67
+ def offset_of field
68
+ members[field].offset
69
+ end
70
+
71
+ attr_reader :pointer
72
+
73
+ def address
74
+ pointer.address
75
+ end
76
+
77
+ def initialize pointer=nil
78
+ if pointer
79
+ @pointer = pointer
80
+ else
81
+ @pointer = FFI.context.malloc(self.class.size)
82
+ @pointer.type = FFI::Type[self.class]
83
+ @pointer
84
+ end
85
+ end
86
+
87
+ def inspect
88
+ out = ["#<#{self.class.name}:#{"0x%08x" % self.address}: "]
89
+ out << self.members.keys.map do |key|
90
+ ":#{key} => #{self.[](key).inspect}"
91
+ end.join(", ")
92
+ out << ">"
93
+ out.join
94
+ end
95
+ end
96
+
97
+ class Field
98
+ attr_accessor :struct, :name, :type, :offset, :count
99
+ def initialize struct, name, type, offset, count
100
+ @struct, @name, @type, @offset, @count = struct, name, type, offset, count
101
+ end
102
+
103
+ def get from
104
+ if count
105
+ FFI::Pointer.new([from.pointer.memory, from.address + offset], type)
106
+ elsif type.is_a?(Class) && type <= FFI::Struct
107
+ type.new(FFI::Pointer.new([from.pointer.memory, from.address + offset], type))
108
+ else
109
+ from.pointer.get(type, offset)
110
+ end
111
+ end
112
+
113
+ def set from, value
114
+ if count
115
+ raise ArgumentError, "Can't set an array #{name} of #{struct}."
116
+ elsif type.is_a?(Class) && type <= FFI::Struct
117
+ raise ArgumentError, "Can't set a nested struct #{name}."
118
+ else
119
+ from.pointer.put(type, offset, value)
120
+ end
121
+ end
122
+ end
123
+
124
+ ManagedStruct = Struct
125
+ end