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,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