opal-webassembly 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.
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,226 @@
1
+ module FFI
2
+ TypeDefs = {}
3
+
4
+ # TODO: clean me up
5
+ class Type
6
+ def self.alignment; size; end
7
+
8
+ class Numeric < self
9
+ def self.signed; false; end
10
+ def self.pack(x)
11
+ [x].pack(packformat).b
12
+ end
13
+ def self.unpack(x)
14
+ x.unpack(packformat.b).first
15
+ end
16
+ end
17
+
18
+ class Integer < Numeric
19
+ def self.from_native(x); x.to_i; end
20
+ def self.to_native(x); x.to_i; end
21
+
22
+ def self.packformat
23
+ case self.size
24
+ when 1
25
+ signed ? "c" : "C"
26
+ when 2
27
+ signed ? "s<" : "S<"
28
+ when 4
29
+ signed ? "l<" : "L<"
30
+ when 8
31
+ signed ? "q<" : "Q<"
32
+ end
33
+ end
34
+ end
35
+
36
+ class Float < Numeric
37
+ def self.from_native(x); x.to_f; end
38
+ def self.to_native(x); x.to_f; end
39
+
40
+ # Unfortunately, that is not supported by Opal. TODO: Upstream fix
41
+ def self.packformat
42
+ case self.size
43
+ when 2
44
+ "e"
45
+ when 4
46
+ "E"
47
+ when 8
48
+ raise TypeError, "We can't pack/unpack a long double unfortunately."
49
+ end
50
+ end
51
+ end
52
+
53
+ class VOID < self
54
+ def self.size; 1; end
55
+ def self.from_native(x); nil; end
56
+ def self.to_native(x); raise TypeError, "Can't convert VOID to a native value"; end
57
+ def self.pack(x); ""; end
58
+ def self.unpack(x); nil; end
59
+ end
60
+
61
+ class BOOL < self
62
+ def self.size; 1; end
63
+ def self.from_native(x); !!x; end
64
+ def self.to_native(x); x ? 1 : 0; end
65
+ def self.pack(x); x ? "\1" : "\0"; end
66
+ def self.unpack(x); x != "\0"; end
67
+ end
68
+
69
+ class POINTER < Integer
70
+ def self.size; 4; end
71
+
72
+ def self.from_native_mem(x, memory)
73
+ Pointer.new([memory, x])
74
+ end
75
+
76
+ def self.to_native(x)
77
+ if !x
78
+ 0
79
+ elsif x.respond_to? :value # WebAssembly::Global?
80
+ x.value
81
+ elsif x.respond_to? :address # Struct? Pointer?
82
+ x.address
83
+ elsif x.respond_to? :to_int # Integer?
84
+ x.to_int
85
+ elsif x.respond_to? :to_str # String? It leaks though.
86
+ FFI::Pointer.from_string(x.to_str).address
87
+ else
88
+ raise ArgumentError, "Wrong argument #{x} can't be coerced to a pointer."
89
+ end
90
+ end
91
+ end
92
+
93
+ # TODO: Implement DataConverter
94
+ class WrappedStruct < self
95
+ def alignment; FFI::Type::POINTER.alignment; end
96
+ def size; FFI::Type::POINTER.size; end
97
+ def pack x; FFI::Type::POINTER.pack(x); end
98
+ def unpack x; FFI::Type::POINTER.unpack(x); end
99
+
100
+ def initialize x
101
+ @struct = x
102
+ end
103
+
104
+ def from_native_mem(x, memory)
105
+ @struct.new(FFI::Pointer.new([memory, x], @struct))
106
+ end
107
+
108
+ def to_native(x)
109
+ x.pointer.address
110
+ end
111
+ end
112
+
113
+ class STRING < self
114
+ def self.size; 4; end
115
+
116
+ def self.from_native_mem(x, memory)
117
+ FFI::Pointer.new([memory, x]).read_string
118
+ end
119
+
120
+ # WARNING: it leaks!
121
+ def self.to_native x
122
+ FFI::Pointer.from_string(x).address
123
+ end
124
+
125
+ def self.pack x; x.to_s; end
126
+ def self.unpack x; x.to_s; end
127
+ end
128
+
129
+ class CHAR < Integer; def self.size; 1; end; def self.signed; true; end; end
130
+ class UCHAR < Integer; def self.size; 1; end; end
131
+ class SHORT < Integer; def self.size; 2; end; def self.signed; true; end; end
132
+ class USHORT < Integer; def self.size; 2; end; end
133
+ class INT < Integer; def self.size; 4; end; def self.signed; true; end; end
134
+ class UINT < Integer; def self.size; 4; end; end
135
+ class LONG < INT; end
136
+ class ULONG < INT; end
137
+ class LONG_LONG < Integer; def self.size; 8; end; def self.signed; true; end; end
138
+ class ULONG_LONG < Integer; def self.size; 8; end; end
139
+ class FLOAT < Float; def self.size; 4; end; end
140
+ class DOUBLE < Float; def self.size; 8; end; end
141
+ class LONG_DOUBLE < Float; def self.size; 16; end; end
142
+
143
+ class INT8 < CHAR; end
144
+ class UINT8 < CHAR; end
145
+ class INT16 < SHORT; end
146
+ class UINT16 < USHORT; end
147
+ class INT32 < INT; end
148
+ class UINT32 < UINT; end
149
+ class INT64 < LONG_LONG; end
150
+ class UINT64 < ULONG_LONG; end
151
+
152
+ def self.[] name, purpose = nil
153
+ FFI.find_type name, purpose: purpose
154
+ end
155
+ end
156
+
157
+ def self.typedef old, new
158
+ TypeDefs[new] = find_type(old)
159
+ end
160
+
161
+ def self.add_typedef(old, new); typedef old, new; end
162
+
163
+ def self.find_type name, type_map = nil, purpose: nil
164
+ if name.is_a?(FFI::Type) || (name.is_a?(Class) && name <= FFI::Type)
165
+ name
166
+ elsif type_map && type_map.has_key?(name)
167
+ type_map[name]
168
+ elsif TypeDefs.has_key?(name)
169
+ TypeDefs[name]
170
+ elsif name.is_a?(Class) && name <= FFI::Struct
171
+ if purpose == nil
172
+ Type::WrappedStruct.new(name)
173
+ elsif purpose == :struct
174
+ name
175
+ else
176
+ raise ArgumentError, "Wrong purpose provided. Can't resolve type '#{name}'."
177
+ end
178
+ else
179
+ raise TypeError, "unable to resolve type '#{name}'"
180
+ end
181
+ end
182
+
183
+ def self.type_size name
184
+ find_type(name).size
185
+ end
186
+
187
+ typedef Type::VOID, nil
188
+
189
+ typedef Type::VOID, :void
190
+ typedef Type::BOOL, :bool
191
+ typedef Type::CHAR, :char
192
+ typedef Type::UCHAR, :uchar
193
+ typedef Type::SHORT, :short
194
+ typedef Type::USHORT, :ushort
195
+ typedef Type::INT, :int
196
+ typedef Type::UINT, :uint
197
+ typedef Type::LONG, :long
198
+ typedef Type::ULONG, :ulong
199
+ typedef Type::LONG_LONG, :long_long
200
+ typedef Type::ULONG_LONG, :ulong_long
201
+ typedef Type::FLOAT, :float
202
+ typedef Type::DOUBLE, :double
203
+ typedef Type::LONG_DOUBLE, :long_double
204
+
205
+ typedef Type::INT8, :int8
206
+ typedef Type::UINT8, :uint8
207
+ typedef Type::INT16, :int16
208
+ typedef Type::UINT16, :uint16
209
+ typedef Type::INT32, :int32
210
+ typedef Type::UINT32, :uint32
211
+ typedef Type::INT64, :int64
212
+ typedef Type::UINT64, :uint64
213
+
214
+ typedef Type::POINTER, :pointer
215
+ typedef Type::STRING, :string
216
+
217
+ typedef :uint8, :byte
218
+ typedef :long, :size_t
219
+
220
+ typedef :pointer, :buffer_in
221
+ typedef :pointer, :buffer_out
222
+ typedef :pointer, :buffer_inout
223
+
224
+ # varargs and strptr are not currently supported
225
+ # same with enums.
226
+ end
@@ -0,0 +1,68 @@
1
+ require 'native'
2
+
3
+ require 'webassembly/instance'
4
+ require 'webassembly/module'
5
+ require 'webassembly/table'
6
+ require 'webassembly/memory'
7
+ require 'webassembly/global'
8
+
9
+ module WebAssembly
10
+ @libs = {}
11
+
12
+ def self.libs
13
+ @libs
14
+ end
15
+
16
+ def self.load_from_buffer(buffer, name)
17
+ mod = Module.new(buffer)
18
+ @libs[name] = Instance.new(mod)
19
+ end
20
+
21
+ %x{
22
+ // Taken from https://github.com/niklasvh/base64-arraybuffer/blob/master/lib/base64-arraybuffer.js
23
+ var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
24
+
25
+ // Use a lookup table to find the index.
26
+ var lookup = new Uint8Array(256);
27
+ for (var i = 0; i < chars.length; i++) {
28
+ lookup[chars.charCodeAt(i)] = i;
29
+ }
30
+
31
+ function base64_to_arraybuffer(base64) {
32
+ var bufferLength = base64.length * 0.75,
33
+ len = base64.length, i, p = 0,
34
+ encoded1, encoded2, encoded3, encoded4;
35
+
36
+ if (base64[base64.length - 1] === "=") {
37
+ bufferLength--;
38
+ if (base64[base64.length - 2] === "=") {
39
+ bufferLength--;
40
+ }
41
+ }
42
+
43
+ var arraybuffer = new ArrayBuffer(bufferLength),
44
+ bytes = new Uint8Array(arraybuffer);
45
+
46
+ for (i = 0; i < len; i+=4) {
47
+ encoded1 = lookup[base64.charCodeAt(i)];
48
+ encoded2 = lookup[base64.charCodeAt(i+1)];
49
+ encoded3 = lookup[base64.charCodeAt(i+2)];
50
+ encoded4 = lookup[base64.charCodeAt(i+3)];
51
+
52
+ bytes[p++] = (encoded1 << 2) | (encoded2 >> 4);
53
+ bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2);
54
+ bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63);
55
+ }
56
+
57
+ return arraybuffer;
58
+ }
59
+ }
60
+
61
+ def self.load_from_base64(base64, name)
62
+ self.load_from_buffer(`base64_to_arraybuffer(#{base64})`, name)
63
+ end
64
+
65
+ def self.[](name)
66
+ self.libs[name]
67
+ end
68
+ end
@@ -0,0 +1,15 @@
1
+ module WebAssembly
2
+ class Global < `WebAssembly.Global`
3
+ def self.new(value, type="i32", mutable=true)
4
+ `new WebAssembly.Global({value: #{type}, mutable: #{mutable}}, #{value})`
5
+ end
6
+
7
+ def value
8
+ self.JS[:value]
9
+ end
10
+
11
+ def value= new_val
12
+ self.JS[:value] = new_val
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,44 @@
1
+ require 'native'
2
+
3
+ module WebAssembly
4
+ class Instance < `WebAssembly.Instance`
5
+ include Enumerable
6
+
7
+ def self.new(mod, imports={})
8
+ `new WebAssembly.Instance(#{mod}, #{imports.to_n})`
9
+ end
10
+
11
+ def exports
12
+ @exports_cache ||= {}.tap do |hash|
13
+ %x{
14
+ var hasOwnProperty = Object.prototype.hasOwnProperty.bind(#{self}.exports);
15
+ for (var key in #{self}.exports) {
16
+ if (hasOwnProperty(key)) {
17
+ #{hash[`key`] = `#{self}.exports[key]`}
18
+ }
19
+ }
20
+ }
21
+ end
22
+ end
23
+
24
+ def method_missing meth, *args
25
+ exports[meth].call(*args)
26
+ end
27
+
28
+ def [] export
29
+ exports[export]
30
+ end
31
+
32
+ def to_h
33
+ exports
34
+ end
35
+
36
+ def memory
37
+ exports[:memory]
38
+ end
39
+
40
+ def each(&block)
41
+ exports.each(&block)
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,18 @@
1
+ require 'buffer'
2
+
3
+ module WebAssembly
4
+ class Memory < `WebAssembly.Memory`
5
+ # 64kB pages
6
+ def self.new(initial=1, maximum=100, shared=false)
7
+ `new WebAssembly.Memory({initial: #{initial}, maximum: #{maximum}, shared: #{shared}})`
8
+ end
9
+
10
+ def grow(by)
11
+ self.JS.grow(by)
12
+ end
13
+
14
+ def buffer
15
+ Buffer.new(self.JS[:buffer])
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,10 @@
1
+ require 'native'
2
+
3
+ module WebAssembly
4
+ class Module < `WebAssembly.Module`
5
+ def self.new(buffer)
6
+ buffer = buffer.to_n unless native? buffer
7
+ `new WebAssembly.Module(#{buffer})`
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,35 @@
1
+ module WebAssembly
2
+ class Table < `WebAssembly.Table`
3
+ include Enumerable
4
+
5
+ def self.new(initial=1, maximum=100)
6
+ `new WebAssembly.Table({initial: #{initial}, maximum: #{maximum}, element: "anyfunc"})`
7
+ end
8
+
9
+ def to_a
10
+ Array(self)
11
+ end
12
+
13
+ def each(&block)
14
+ self.to_a.each(&block)
15
+ end
16
+
17
+ def get(i)
18
+ self.JS.get(i)
19
+ end
20
+ alias [] get
21
+
22
+ def set(i, val)
23
+ self.JS.set(i, val)
24
+ end
25
+ alias []= set
26
+
27
+ def grow(by)
28
+ self.JS.grow(by)
29
+ end
30
+
31
+ def length
32
+ self.JS[:length]
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,139 @@
1
+ require 'spec_helper'
2
+ require 'experiment'
3
+
4
+ RSpec.describe FFI do
5
+ it "has correct assumptions about types (wasm32)" do
6
+ expect(Experiment.longsize).to eq FFI::Type[:long].size
7
+ expect(Experiment.longlongsize).to eq FFI::Type[:long_long].size
8
+ expect(Experiment.ptrsize).to eq FFI::Type[:pointer].size
9
+ expect(Experiment.floatsize).to eq FFI::Type[:float].size
10
+ expect(Experiment.doublesize).to eq FFI::Type[:double].size
11
+ expect(Experiment.longdoublesize).to eq FFI::Type[:long_double].size
12
+ end
13
+
14
+ it "has correct assumptions about struct alignment" do
15
+ expect(Experiment.structalignld).to eq FFI::Type[:long_double].alignment
16
+ expect(Experiment.structalignll).to eq FFI::Type[:long_long].alignment
17
+ expect(Experiment.structaligni).to eq FFI::Type[:int].alignment
18
+ expect(Experiment.structalignp).to eq FFI::Type[:pointer].alignment
19
+ end
20
+
21
+ it "passes pointers correctly" do
22
+ ptr = Experiment.retpointer(FFI::Pointer.new([nil, 0x1133dd]))
23
+ expect(ptr.address).to eq(0x1133dd)
24
+ expect(ptr.memory).to eq(Experiment.library.memory)
25
+ end
26
+
27
+ it "passes floats correctly" do
28
+ expect(Experiment.retfloat(0.11)).to be_within(0.00001).of(-0.22)
29
+ expect(Experiment.retdouble(0.33)).to be_within(0.00000001).of(-0.66)
30
+ end
31
+
32
+ it "passes strings correctly" do
33
+ require 'corelib/array/pack'
34
+ Experiment.context do
35
+ expect(Experiment.retstring("hello world!".b)).to be("lmo!world!")
36
+ end
37
+ end
38
+
39
+ it "packs and unpacks values correctly" do
40
+ require 'corelib/array/pack'
41
+ require 'corelib/string/unpack'
42
+ for type in %i[uint8 int8 uint16 int16 int32 uint32 pointer]
43
+ type = FFI::Type[type]
44
+ expect(type.unpack(type.pack(123))).to eq(123)
45
+ end
46
+ type = FFI::Type[:string]
47
+ expect(type.unpack(type.pack(123))).to eq("123")
48
+ end
49
+
50
+ it "supports structs" do
51
+ require 'corelib/array/pack'
52
+ require 'corelib/string/unpack'
53
+
54
+ class MyStr < FFI::Struct
55
+ layout :a, :char,
56
+ :b, :int,
57
+ :c, [:int, 20]
58
+ end
59
+
60
+ Experiment.context do
61
+ expect(MyStr.members[:a].offset).to eq(0)
62
+ expect(MyStr.members[:b].offset).to eq(4)
63
+ expect(MyStr.members[:c].offset).to eq(8)
64
+
65
+ str = MyStr.new
66
+
67
+ expect(str[:c]).to be_a(FFI::Pointer)
68
+ expect(str[:a]).to be_a(Integer)
69
+
70
+ str[:a] = 10
71
+ str[:c][4] = 2000000
72
+
73
+ expect(str[:a]).to eq(10)
74
+ expect(str[:c][4]).to eq(2000000)
75
+
76
+ expect(MyStr.size).to eq(22 * 4)
77
+ end
78
+ end
79
+
80
+ it "supports nested pointer structs" do
81
+ require 'corelib/array/pack'
82
+ require 'corelib/string/unpack'
83
+
84
+ class Struct2 < FFI::Struct
85
+ layout :age, :int,
86
+ :a, :int,
87
+ :b, :int,
88
+ :c, :int
89
+ end
90
+
91
+ class Struct1 < FFI::Struct
92
+ layout :id, :int,
93
+ :data, Struct2.ptr
94
+ end
95
+
96
+ Experiment.context do
97
+ expect(Struct2.size).to eq(16)
98
+ expect(Struct1.size).to eq(8)
99
+
100
+ struct = Struct1.new
101
+ struct2 = Struct2.new
102
+ struct[:data] = struct2
103
+ struct[:data][:age] = 27
104
+
105
+ expect(struct).to be_a(Struct1)
106
+ expect(struct[:data]).to be_a(Struct2)
107
+ expect(struct[:data][:age]).to eq(27)
108
+ end
109
+ end
110
+
111
+ it "supports nested structs" do
112
+ require 'corelib/array/pack'
113
+ require 'corelib/string/unpack'
114
+
115
+ class Struct4 < FFI::Struct
116
+ layout :age, :int,
117
+ :a, :int,
118
+ :b, :int,
119
+ :c, :int
120
+ end
121
+
122
+ class Struct3 < FFI::Struct
123
+ layout :id, :int,
124
+ :data, Struct4
125
+ end
126
+
127
+ Experiment.context do
128
+ expect(Struct4.size).to eq(16)
129
+ expect(Struct3.size).to eq(20)
130
+
131
+ struct = Struct3.new
132
+ struct[:data][:age] = 27
133
+
134
+ expect(struct).to be_a(Struct3)
135
+ expect(struct[:data]).to be_a(Struct4)
136
+ expect(struct[:data][:age]).to eq(27)
137
+ end
138
+ end
139
+ end