wardite 0.5.1 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/wardite/wasi.rb CHANGED
@@ -1,16 +1,161 @@
1
1
  # rbs_inline: enabled
2
+
3
+ require "wardite/wasm_module"
4
+ require "wardite/wasi/consts"
5
+ require "securerandom"
6
+ require "fcntl"
7
+
2
8
  module Wardite
3
9
  class WasiSnapshotPreview1
4
10
  include ValueHelper
11
+ include WasmModule
5
12
 
6
- attr_accessor :fd_table #: Array[IO]
13
+ attr_accessor :fd_table #: Array[(IO|File)]
14
+ attr_accessor :argv #: Array[String]
7
15
 
8
- def initialize
16
+ # @rbs argv: Array[String]
17
+ # @rbs return: void
18
+ def initialize(argv: [])
9
19
  @fd_table = [
10
20
  STDIN,
11
21
  STDOUT,
12
22
  STDERR,
13
23
  ]
24
+ @argv = argv
25
+ end
26
+
27
+ # @rbs store: Store
28
+ # @rbs args: Array[wasmValue]
29
+ # @rbs return: Object
30
+ def args_get(store, args)
31
+ arg_offsets_p = args[0].value
32
+ arg_data_buf_p = args[1].value
33
+ if !arg_data_buf_p.is_a?(Integer)
34
+ raise ArgumentError, "invalid type of args: #{args.inspect}"
35
+ end
36
+
37
+ arg_offsets = [] #: Array[Integer]
38
+ arg_data_slice = [] #: Array[String]
39
+ current_offset = arg_data_buf_p
40
+ @argv.each do |arg|
41
+ arg_offsets << current_offset
42
+ arg_data_slice << arg
43
+ current_offset += arg.size + 1
44
+ end
45
+ arg_data = arg_data_slice.join("\0") + "\0"
46
+
47
+ memory = store.memories[0]
48
+ memory.data[arg_data_buf_p...(arg_data_buf_p + arg_data.size)] = arg_data
49
+
50
+ arg_offsets.each_with_index do |offset, i|
51
+ data_begin = arg_offsets_p + i * 4
52
+ memory.data[data_begin...(data_begin + 4)] = [offset].pack("I!")
53
+ end
54
+
55
+ 0
56
+ end
57
+
58
+ # @rbs store: Store
59
+ # @rbs args: Array[wasmValue]
60
+ # @rbs return: Object
61
+ def args_sizes_get(store, args)
62
+ argc_p = args[0].value
63
+ arglen_p = args[1].value
64
+ argc = @argv.length
65
+ arglen = @argv.map{|arg| arg.size + 1}.sum
66
+
67
+ memory = store.memories[0]
68
+ memory.data[argc_p...(argc_p+4)] = [argc].pack("I!")
69
+ memory.data[arglen_p...(arglen_p+4)] = [arglen].pack("I!")
70
+ 0
71
+ end
72
+
73
+ # @rbs store: Store
74
+ # @rbs args: Array[wasmValue]
75
+ # @rbs return: Object
76
+ def environ_sizes_get(store, args)
77
+ envc_p = args[0].value
78
+ envlen_p = args[1].value
79
+ envc = ENV.length
80
+ envlen = ENV.map{|k,v| k.size + v.size + 1}.sum
81
+
82
+ memory = store.memories[0]
83
+ memory.data[envc_p...(envc_p+4)] = [envc].pack("I!")
84
+ memory.data[envlen_p...(envlen_p+4)] = [envlen].pack("I!")
85
+ 0
86
+ end
87
+
88
+ # @rbs store: Store
89
+ # @rbs args: Array[wasmValue]
90
+ # @rbs return: Object
91
+ def environ_get(store, args)
92
+ environ_offsets_p = args[0].value
93
+ environ_data_buf_p = args[1].value
94
+ if !environ_data_buf_p.is_a?(Integer)
95
+ raise ArgumentError, "invalid type of args: #{args.inspect}"
96
+ end
97
+
98
+ environ_offsets = [] #: Array[Integer]
99
+ environ_data_slice = [] #: Array[String]
100
+ current_offset = environ_data_buf_p
101
+ ENV.each do |k, v|
102
+ environ_offsets << current_offset
103
+ environ_data_slice << "#{k}=#{v}"
104
+ current_offset += "#{k}=#{v}".size + 1
105
+ end
106
+ environ_data = environ_data_slice.join("\0") + "\0"
107
+
108
+ memory = store.memories[0]
109
+ memory.data[environ_data_buf_p...(environ_data_buf_p + environ_data.size)] = environ_data
110
+
111
+ environ_offsets.each_with_index do |offset, i|
112
+ data_begin = environ_offsets_p + i * 4
113
+ memory.data[data_begin...(data_begin + 4)] = [offset].pack("I!")
114
+ end
115
+
116
+ 0
117
+ end
118
+
119
+ # @rbs store: Store
120
+ # @rbs args: Array[wasmValue]
121
+ # @rbs return: Object
122
+ def clock_time_get(store, args)
123
+ clock_id = args[0].value
124
+ # we dont use precision...
125
+ _precision = args[1].value
126
+ timebuf64 = args[2].value
127
+ if clock_id != 0 # - CLOCKID_REALTIME
128
+ # raise NotImplementedError, "CLOCKID_REALTIME is an only supported id"
129
+ return -255
130
+ end
131
+ # timestamp in nanoseconds
132
+ now = Time.now.to_i * 1_000_000
133
+
134
+ memory = store.memories[0]
135
+ now_packed = [now].pack("Q!")
136
+ memory.data[timebuf64...(timebuf64+8)] = now_packed
137
+ 0
138
+ end
139
+
140
+ # @rbs store: Store
141
+ # @rbs args: Array[wasmValue]
142
+ # @rbs return: Object
143
+ def fd_prestat_get(store, args)
144
+ fd = args[0].value.to_i
145
+ prestat_offset = args[1].value.to_i
146
+ if fd >= @fd_table.size
147
+ return Wasi::EBADF
148
+ end
149
+ file = @fd_table[fd]
150
+ if !file.is_a?(File)
151
+ return Wasi::EBADF
152
+ end
153
+ name = file.path
154
+ memory = store.memories[0]
155
+ # Zero-value 8-bit tag, and 3-byte zero-value padding
156
+ memory.data[prestat_offset...(prestat_offset+4)] = [0].pack("I!")
157
+ memory.data[(prestat_offset+4)...(prestat_offset+8)] = [name.size].pack("I!")
158
+ 0
14
159
  end
15
160
 
16
161
  # @rbs store: Store
@@ -29,6 +174,7 @@ module Wardite
29
174
  raise Wardite::ArgumentError, "args too short"
30
175
  end
31
176
  file = self.fd_table[fd]
177
+ return Wasi::EBADF if !file
32
178
  memory = store.memories[0]
33
179
  nwritten = 0
34
180
  iovs_len.times do
@@ -45,11 +191,126 @@ module Wardite
45
191
  0
46
192
  end
47
193
 
48
- # @rbs return: Hash[Symbol, wasmCallable]
49
- def to_module
50
- {
51
- fd_write: lambda{|store, args| self.fd_write(store, args) },
52
- }
194
+ # @rbs store: Store
195
+ # @rbs args: Array[wasmValue]
196
+ # @rbs return: Object
197
+ def fd_read(store, args)
198
+ iargs = args.map do |elm|
199
+ if elm.is_a?(I32)
200
+ elm.value
201
+ else
202
+ raise Wardite::ArgumentError, "invalid type of args: #{args.inspect}"
203
+ end
204
+ end #: Array[Integer]
205
+ fd, iovs, iovs_len, rp = *iargs
206
+ if !fd || !iovs || !iovs_len || !rp
207
+ raise Wardite::ArgumentError, "args too short"
208
+ end
209
+ file = self.fd_table[fd]
210
+ return Wasi::EBADF if !file
211
+ memory = store.memories[0]
212
+ nread = 0
213
+
214
+ iovs_len.times do
215
+ start = unpack_le_int(memory.data[iovs...(iovs+4)])
216
+ iovs += 4
217
+ slen = unpack_le_int(memory.data[iovs...(iovs+4)])
218
+ iovs += 4
219
+ buf = file.read(slen)
220
+ if !buf
221
+ return Wasi::EFAULT
222
+ end
223
+ memory.data[start...(start+slen)] = buf
224
+ nread += slen
225
+ end
226
+
227
+ memory.data[rp...(rp+4)] = [nread].pack("I!")
228
+ 0
229
+ end
230
+
231
+ # @rbs store: Store
232
+ # @rbs args: Array[wasmValue]
233
+ # @rbs return: Object
234
+ def fd_fdstat_get(store, args)
235
+ fd = args[0].value.to_i
236
+ fdstat_offset = args[1].value.to_i
237
+ if fd >= @fd_table.size
238
+ return Wasi::EBADF
239
+ end
240
+ file = @fd_table[fd]
241
+ fdflags = 0
242
+ if file.is_a?(IO)
243
+ fdflags |= Wasi::FD_APPEND
244
+ else
245
+ if (Fcntl::O_APPEND & file.fcntl(Fcntl::F_GETFL, 0)) != 0
246
+ fdflags |= Wasi::FD_APPEND
247
+ end
248
+ end
249
+
250
+ if (Fcntl::O_NONBLOCK & file.fcntl(Fcntl::F_GETFL, 0)) != 0
251
+ fdflags |= Wasi::FD_NONBLOCK
252
+ end
253
+
254
+ stat = file.stat #: File::Stat
255
+ ftype = Wasi.to_ftype(stat.ftype)
256
+
257
+ fs_right_base = 0
258
+ fs_right_inheriting = 0
259
+
260
+ case ftype
261
+ when Wasi::FILETYPE_DIRECTORY
262
+ fs_right_base = Wasi::RIGHT_DIR_RIGHT_BASE
263
+ fs_right_inheriting = Wasi::RIGHT_FILE_RIGHT_BASE | Wasi::RIGHT_DIR_RIGHT_BASE
264
+ when Wasi::FILETYPE_CHARACTER_DEVICE
265
+ fs_right_base = Wasi::RIGHT_FILE_RIGHT_BASE & \
266
+ (~Wasi::RIGHT_FD_SEEK) & (~Wasi::RIGHT_FD_TELL)
267
+ else
268
+ fs_right_base = Wasi::RIGHT_FILE_RIGHT_BASE
269
+ end
270
+
271
+ memory = store.memories[0]
272
+
273
+ binformat = [fdflags, ftype, 0, 0, 0, 0, fs_right_base, fs_right_inheriting]
274
+ .pack("SSC4QQ")
275
+ memory.data[fdstat_offset...(fdstat_offset+binformat.size)] = binformat
276
+ 0
277
+ end
278
+
279
+ # @rbs store: Store
280
+ # @rbs args: Array[wasmValue]
281
+ # @rbs return: Object
282
+ def fd_filestat_get(store, args)
283
+ fd = args[0].value.to_i
284
+ filestat_offset = args[1].value.to_i
285
+ if fd >= @fd_table.size
286
+ return Wasi::EBADF
287
+ end
288
+ file = @fd_table[fd]
289
+ stat = file.stat #: File::Stat
290
+ memory = store.memories[0]
291
+ binformat = [stat.dev, stat.ino, Wasi.to_ftype(stat.ftype), stat.nlink, stat.size, stat.atime.to_i, stat.mtime.to_i, stat.ctime.to_i].pack("Q8")
292
+ memory.data[filestat_offset...(filestat_offset+binformat.size)] = binformat
293
+ 0
294
+ end
295
+
296
+ # @rbs store: Store
297
+ # @rbs args: Array[wasmValue]
298
+ # @rbs return: Object
299
+ def proc_exit(store, args)
300
+ exit_code = args[0].value
301
+ exit(exit_code)
302
+ end
303
+
304
+ # @rbs store: Store
305
+ # @rbs args: Array[wasmValue]
306
+ # @rbs return: Object
307
+ def random_get(store, args)
308
+ buf = args[0].value.to_i
309
+ buflen = args[1].value.to_i
310
+ randoms = SecureRandom.random_bytes(buflen) #: String
311
+ memory = store.memories[0]
312
+ memory.data[buf...(buf+buflen)] = randoms
313
+ 0
53
314
  end
54
315
 
55
316
  private
@@ -0,0 +1,53 @@
1
+ # rbs_inline: enabled
2
+
3
+ module Wardite
4
+ # @rbs!
5
+ # interface _WasmCallable
6
+ # def call: (Store, Array[wasmValue]) -> wasmFuncReturn
7
+ # def []: (Store, Array[wasmValue]) -> wasmFuncReturn
8
+ # end
9
+
10
+ # @rbs!
11
+ # type wasmModuleSrc = Hash[Symbol, _WasmCallable] | WasmModule | HashModule
12
+ # type wasmModule = WasmModule | HashModule
13
+
14
+ module WasmModule
15
+ # @rbs fnname: Symbol
16
+ # @rbs store: Store
17
+ # @rbs args: Array[wasmValue]
18
+ # @rbs return: wasmFuncReturn
19
+ def invoke(fnname, store, *args)
20
+ self.__send__(fnname, store, args)
21
+ end
22
+
23
+ # @rbs fnname: Symbol
24
+ # @rbs return: _WasmCallable
25
+ def callable(fnname)
26
+ self.method(fnname)
27
+ end
28
+ end
29
+
30
+ class HashModule
31
+ attr_accessor :hash #: Hash[Symbol, _WasmCallable]
32
+
33
+ # @rbs ha: Hash[Symbol, _WasmCallable]
34
+ def initialize(hash)
35
+ @hash = hash
36
+ end
37
+
38
+ # @rbs fnname: Symbol
39
+ # @rbs store: Store
40
+ # @rbs args: Array[wasmValue]
41
+ # @rbs return: wasmFuncReturn
42
+ def invoke(fnname, store, *args)
43
+ fn = self.hash[fnname.to_sym]
44
+ fn.call(store, args)
45
+ end
46
+
47
+ # @rbs fnname: Symbol
48
+ # @rbs return: _WasmCallable
49
+ def callable(fnname)
50
+ self.hash[fnname.to_sym]
51
+ end
52
+ end
53
+ end
data/lib/wardite.rb CHANGED
@@ -20,8 +20,6 @@ require_relative "wardite/alu_f32.generated"
20
20
  require_relative "wardite/alu_f64.generated"
21
21
  require_relative "wardite/convert.generated"
22
22
 
23
- require_relative "wardite/wasi"
24
-
25
23
  require "stringio"
26
24
 
27
25
  module Wardite
@@ -38,13 +36,23 @@ module Wardite
38
36
 
39
37
  attr_accessor :exports #: Exports
40
38
 
41
- attr_reader :import_object #: Hash[Symbol, Hash[Symbol, wasmCallable]]
39
+ attr_reader :import_object #: Hash[Symbol, wasmModule]
40
+
41
+ attr_accessor :wasi #: WasiSnapshotPreview1?
42
42
 
43
- # @rbs import_object: Hash[Symbol, Hash[Symbol, wasmCallable]]
43
+ # @rbs import_object: Hash[Symbol, wasmModuleSrc]
44
44
  # @rbs &blk: (Instance) -> void
45
45
  def initialize(import_object, &blk)
46
+ @wasi = nil
47
+
46
48
  blk.call(self)
47
- @import_object = import_object
49
+ import_object.each_pair do |k, v|
50
+ if v.is_a?(Hash)
51
+ import_object[k] = HashModule.new(v)
52
+ end
53
+ end
54
+
55
+ @import_object = import_object #: Hash[Symbol, wasmModule]
48
56
 
49
57
  @store = Store.new(self)
50
58
  @exports = Exports.new(self.export_section, store)
@@ -281,7 +289,8 @@ module Wardite
281
289
 
282
290
  case fn
283
291
  when WasmFunction
284
- invoke_internal(fn)
292
+ r = invoke_internal(fn)
293
+ r
285
294
  when ExternalFunction
286
295
  invoke_external(fn)
287
296
  else
@@ -313,25 +322,7 @@ module Wardite
313
322
  raise LoadError, "stack too short"
314
323
  end
315
324
  self.stack = drained_stack(local_start)
316
-
317
- wasm_function.locals_count.each_with_index do |count, i|
318
- typ = wasm_function.locals_type[i]
319
- count.times do
320
- case typ
321
- when :i32, :u32
322
- locals.push I32(0)
323
- when :i64, :u64
324
- locals.push I64(0)
325
- when :f32
326
- locals.push F32(0.0)
327
- when :f64
328
- locals.push F64(0.0)
329
- else
330
- $stderr.puts "warning: unknown type #{typ.inspect}. default to I32"
331
- locals.push I32(0)
332
- end
333
- end
334
- end
325
+ locals.concat(wasm_function.default_locals)
335
326
 
336
327
  arity = wasm_function.retsig.size
337
328
  frame = Frame.new(-1, stack.size, wasm_function.body, arity, locals)
@@ -888,7 +879,7 @@ module Wardite
888
879
  class Store
889
880
  attr_accessor :funcs #: Array[WasmFunction|ExternalFunction]
890
881
 
891
- # FIXME: attr_accessor :modules
882
+ attr_accessor :modules #: Hash[Symbol, wasmModule]
892
883
 
893
884
  attr_accessor :memories #: Array[Memory]
894
885
 
@@ -907,21 +898,19 @@ module Wardite
907
898
 
908
899
  import_section = inst.import_section
909
900
  @funcs = []
901
+ @modules = inst.import_object
910
902
 
911
903
  if type_section && func_section && code_section
912
904
  import_section.imports.each do |desc|
913
905
  callsig = type_section.defined_types[desc.sig_index]
914
906
  retsig = type_section.defined_results[desc.sig_index]
915
- imported_module = inst.import_object[desc.module_name.to_sym]
916
- if !imported_module
907
+ target_module = self.modules[desc.module_name.to_sym]
908
+ if target_module.nil?
917
909
  raise ::NameError, "module #{desc.module_name} not found"
918
910
  end
919
- imported_proc = imported_module[desc.name.to_sym]
920
- if !imported_proc
921
- raise ::NameError, "function #{desc.module_name}.#{desc.name} not found"
922
- end
911
+ target_name = desc.name.to_sym
923
912
 
924
- ext_function = ExternalFunction.new(callsig, retsig, imported_proc)
913
+ ext_function = ExternalFunction.new(target_module, target_name, callsig, retsig)
925
914
  self.funcs << ext_function
926
915
  end
927
916
 
@@ -1213,7 +1202,10 @@ module Wardite
1213
1202
  end
1214
1203
 
1215
1204
  # TODO: common interface btw. WasmFunction and ExternalFunction?
1205
+ # may be _WasmCallable?
1216
1206
  class WasmFunction
1207
+ include ValueHelper
1208
+
1217
1209
  attr_accessor :callsig #: Array[Symbol]
1218
1210
 
1219
1211
  attr_accessor :retsig #: Array[Symbol]
@@ -1222,6 +1214,8 @@ module Wardite
1222
1214
 
1223
1215
  attr_accessor :findex #: Integer
1224
1216
 
1217
+ attr_accessor :default_locals #: Array[wasmValue]
1218
+
1225
1219
  # @rbs callsig: Array[Symbol]
1226
1220
  # @rbs retsig: Array[Symbol]
1227
1221
  # @rbs code_body: CodeSection::CodeBody
@@ -1232,6 +1226,7 @@ module Wardite
1232
1226
 
1233
1227
  @code_body = code_body
1234
1228
  @findex = 0 # for debug
1229
+ @default_locals = construct_default_locals
1235
1230
  end
1236
1231
 
1237
1232
  # @rbs return: Array[Op]
@@ -1249,6 +1244,30 @@ module Wardite
1249
1244
  code_body.locals_count
1250
1245
  end
1251
1246
 
1247
+ # @rbs return: Array[wasmValue]
1248
+ def construct_default_locals
1249
+ locals = [] #: Array[wasmValue]
1250
+ locals_count.each_with_index do |count, i|
1251
+ typ = locals_type[i]
1252
+ count.times do
1253
+ case typ
1254
+ when :i32, :u32
1255
+ locals.push I32(0)
1256
+ when :i64, :u64
1257
+ locals.push I64(0)
1258
+ when :f32
1259
+ locals.push F32(0.0)
1260
+ when :f64
1261
+ locals.push F64(0.0)
1262
+ else
1263
+ $stderr.puts "warning: unknown type #{typ.inspect}. default to I32"
1264
+ locals.push I32(0)
1265
+ end
1266
+ end
1267
+ end
1268
+ locals
1269
+ end
1270
+
1252
1271
  # @rbs override_type: Type?
1253
1272
  # @rbs return: WasmFunction
1254
1273
  def clone(override_type: nil)
@@ -1263,23 +1282,32 @@ module Wardite
1263
1282
 
1264
1283
  # @rbs!
1265
1284
  # type wasmFuncReturn = Object|nil
1266
- # type wasmCallable = ^(Store, Array[wasmValue]) -> wasmFuncReturn
1267
1285
 
1268
1286
  class ExternalFunction
1287
+ attr_accessor :target_module #: wasmModule
1288
+
1289
+ attr_accessor :name #: Symbol
1290
+
1269
1291
  attr_accessor :callsig #: Array[Symbol]
1270
1292
 
1271
1293
  attr_accessor :retsig #: Array[Symbol]
1272
1294
 
1273
- attr_accessor :callable #: wasmCallable
1295
+ #attr_accessor :callable #: _WasmCallable
1274
1296
 
1275
1297
  # @rbs callsig: Array[Symbol]
1276
1298
  # @rbs retsig: Array[Symbol]
1277
- # @rbs callable: wasmCallable
1299
+ # @rbs callable: _WasmCallable
1278
1300
  # @rbs return: void
1279
- def initialize(callsig, retsig, callable)
1301
+ def initialize(target_module, name, callsig, retsig)
1302
+ @target_module = target_module
1303
+ @name = name
1280
1304
  @callsig = callsig
1281
1305
  @retsig = retsig
1282
- @callable = callable
1306
+ end
1307
+
1308
+ # @rbs return: _WasmCallable
1309
+ def callable()
1310
+ target_module.callable(self.name)
1283
1311
  end
1284
1312
 
1285
1313
  # @rbs override_type: Type?
@@ -1287,9 +1315,9 @@ module Wardite
1287
1315
  def clone(override_type: nil)
1288
1316
  if override_type
1289
1317
  # callable is assumed to be frozen, so we can copy its ref
1290
- ExternalFunction.new(override_type.callsig, override_type.retsig, callable)
1318
+ ExternalFunction.new(target_module, name, override_type.callsig, override_type.retsig)
1291
1319
  else
1292
- ExternalFunction.new(callsig, retsig, callable)
1320
+ ExternalFunction.new(target_module, name, callsig, retsig)
1293
1321
  end
1294
1322
  end
1295
1323
  end
@@ -0,0 +1,25 @@
1
+ require "benchmark"
2
+ require "wardite"
3
+
4
+ N = 1000000
5
+
6
+ i32_100 = Wardite::I32.new(100)
7
+ i32_200 = Wardite::I32.new(200)
8
+ $RES = {}
9
+ $RES2 = {}
10
+
11
+ Benchmark.bmbm do |x|
12
+ x.report("add via value") do
13
+ N.times do |i|
14
+ res = Wardite::I32.new(i32_100.value + i32_200.value)
15
+ $RES[i%10] = res # avoid optimization
16
+ end
17
+ end
18
+
19
+ x.report("add immediate") do
20
+ N.times do |i|
21
+ res = 100 + 200
22
+ $RES2[i%10] = res
23
+ end
24
+ end
25
+ end
data/misc/slides/fib.c ADDED
@@ -0,0 +1,4 @@
1
+ int fib(int n) {
2
+ if (n <= 1) return n;
3
+ return fib(n - 1) + fib(n - 2);
4
+ }
@@ -0,0 +1,13 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <script>
5
+ WebAssembly.instantiateStreaming(fetch('./fib.wasm'))
6
+ .then(obj => {
7
+ alert(`fib(20) = ${obj.instance.exports.fib(20)}`);
8
+ });
9
+ </script>
10
+ </head>
11
+ <body>
12
+ </body>
13
+ </html>
Binary file
Binary file
Binary file