relaton-un 2.1.0 → 2.1.1

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.
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Relaton
4
+ module Un
5
+ module Wasm
6
+ class Instance
7
+ attr_reader :memory, :funcs, :types, :globals, :table
8
+
9
+ # imports: { "modname" => { "fieldname" => Proc } }
10
+ def initialize(mod, imports = {})
11
+ @mod = mod
12
+ @types = mod.types
13
+ @imports = imports
14
+ @funcs = []
15
+ @globals = []
16
+ @table = []
17
+ link_funcs
18
+ init_memory
19
+ init_globals
20
+ init_table
21
+ init_data
22
+ @interpreter = Interpreter.new(self)
23
+ end
24
+
25
+ def invoke(name, *args)
26
+ idx = export_index(name, :func)
27
+ invoke_by_index(idx, args)
28
+ end
29
+
30
+ def invoke_by_index(func_idx, args)
31
+ @interpreter.invoke(func_idx, args)
32
+ end
33
+
34
+ def export_index(name, kind)
35
+ exp = @mod.exports.find { |e| e.name == name && e.kind == kind }
36
+ raise LinkError, "no export named #{name} of kind #{kind}" unless exp
37
+
38
+ exp.index
39
+ end
40
+
41
+ private
42
+
43
+ def link_funcs
44
+ @mod.imports.each do |imp|
45
+ next unless imp.kind == :func
46
+
47
+ type = @types[imp.desc]
48
+ proc_ = @imports.dig(imp.mod, imp.field)
49
+ raise LinkError, "missing import #{imp.mod}.#{imp.field}" unless proc_
50
+
51
+ @funcs << FuncRef.new(type, :import, nil, proc_)
52
+ end
53
+
54
+ @mod.code.each_with_index do |code, i|
55
+ type_idx = @mod.function_type_indices[@mod.imported_function_count + i]
56
+ type = @types[type_idx]
57
+ @funcs << FuncRef.new(type, :wasm, code, nil)
58
+ end
59
+ end
60
+
61
+ def init_memory
62
+ mem = @mod.memories.first
63
+ @memory = mem ? Memory.new(initial_pages: mem.initial, max_pages: mem.max) : nil
64
+ end
65
+
66
+ def init_globals
67
+ @mod.globals.each do |g|
68
+ @globals << eval_const(g.init_expr)
69
+ end
70
+ end
71
+
72
+ def init_table
73
+ tbl = @mod.tables.first
74
+ @table = Array.new(tbl.initial, nil) if tbl
75
+
76
+ @mod.elements.each do |e|
77
+ offset = eval_const(e.offset_expr)
78
+ e.func_indices.each_with_index do |fi, i|
79
+ @table[offset + i] = fi
80
+ end
81
+ end
82
+ end
83
+
84
+ def init_data
85
+ @mod.data.each do |d|
86
+ offset = eval_const(d.offset_expr)
87
+ @memory.write(offset, d.bytes)
88
+ end
89
+ end
90
+
91
+ def eval_const(expr)
92
+ case expr[0]
93
+ when :i32_const, :i64_const then expr[1]
94
+ when :global_get then @globals[expr[1]]
95
+ else raise LinkError, "bad const expr #{expr.inspect}"
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,613 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Relaton
4
+ module Un
5
+ module Wasm
6
+ I32_MASK = 0xFFFF_FFFF
7
+ I64_MASK = 0xFFFF_FFFF_FFFF_FFFF
8
+ I32_SIGN = 0x8000_0000
9
+ I64_SIGN = 0x8000_0000_0000_0000
10
+ I32_RANGE = 0x1_0000_0000
11
+ I64_RANGE = 0x1_0000_0000_0000_0000
12
+
13
+ # Convert unsigned i32/i64 to signed.
14
+ def self.to_s32(x); x >= I32_SIGN ? x - I32_RANGE : x; end
15
+ def self.to_s64(x); x >= I64_SIGN ? x - I64_RANGE : x; end
16
+
17
+ # A wasm function — module-defined or imported.
18
+ FuncRef = Struct.new(:type, :kind, :code, :proc) do
19
+ def imported?; kind == :import; end
20
+ end
21
+
22
+ # The `caller` object passed to imported procs. Mirrors the shape the
23
+ # existing token_generator.rb expects: `caller.export("memory").to_memory`
24
+ # and `caller.export("__wbindgen_malloc").to_func`.
25
+ class Caller
26
+ def initialize(instance)
27
+ @instance = instance
28
+ end
29
+
30
+ def export(name)
31
+ ExportRef.new(@instance, name)
32
+ end
33
+ end
34
+
35
+ ExportRef = Struct.new(:instance, :name) do
36
+ def to_memory; MemoryAdapter.new(instance.memory); end
37
+
38
+ def to_func
39
+ func_idx = instance.export_index(name, :func)
40
+ ->(*args) { instance.invoke_by_index(func_idx, args) }
41
+ end
42
+ end
43
+
44
+ # Thin shim presenting the wasmtime Memory API the existing import
45
+ # bodies expect: #read(addr, len) returning bytes, #write(addr, bytes).
46
+ MemoryAdapter = Struct.new(:memory) do
47
+ def read(addr, len); memory.read(addr, len); end
48
+ def write(addr, bytes); memory.write(addr, bytes); end
49
+ end
50
+
51
+ class Interpreter
52
+ def initialize(instance)
53
+ @instance = instance
54
+ end
55
+
56
+ def invoke(func_idx, args)
57
+ func = @instance.funcs[func_idx]
58
+ if func.imported?
59
+ return func.proc.call(Caller.new(@instance), *args)
60
+ end
61
+
62
+ run_function(func, args)
63
+ end
64
+
65
+ private
66
+
67
+ def run_function(func, args) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength,Metrics/PerceivedComplexity,Metrics/CyclomaticComplexity
68
+ ftype = func.type
69
+ # locals = function args + declared locals (zero-initialized)
70
+ locals = Array.new(ftype.params.size + func.code.locals.size, 0)
71
+ ftype.params.each_with_index { |_t, i| locals[i] = args[i] }
72
+
73
+ body = func.code.body
74
+ vstack = []
75
+ # Each label: [target_pc, arity, value_stack_height_at_entry, is_loop]
76
+ # Function-level implicit label: target_pc past end, arity = result arity.
77
+ label_stack = [[body.size, ftype.results.size, 0, false]]
78
+ pc = 0
79
+
80
+ while pc < body.size
81
+ instr = body[pc]
82
+ op = instr[0]
83
+
84
+ case op
85
+ when :local_get
86
+ vstack << locals[instr[1]]
87
+ pc += 1
88
+ when :i32_const, :i64_const
89
+ vstack << instr[1]
90
+ pc += 1
91
+ when :i32_add
92
+ b = vstack.pop; a = vstack.pop
93
+ vstack << ((a + b) & I32_MASK)
94
+ pc += 1
95
+ when :i32_xor
96
+ b = vstack.pop; a = vstack.pop
97
+ vstack << ((a ^ b) & I32_MASK)
98
+ pc += 1
99
+ when :i32_and
100
+ b = vstack.pop; a = vstack.pop
101
+ vstack << ((a & b) & I32_MASK)
102
+ pc += 1
103
+ when :i32_or
104
+ b = vstack.pop; a = vstack.pop
105
+ vstack << ((a | b) & I32_MASK)
106
+ pc += 1
107
+ when :i32_sub
108
+ b = vstack.pop; a = vstack.pop
109
+ vstack << ((a - b) & I32_MASK)
110
+ pc += 1
111
+ when :i32_mul
112
+ b = vstack.pop; a = vstack.pop
113
+ vstack << ((a * b) & I32_MASK)
114
+ pc += 1
115
+ when :i32_shl
116
+ b = vstack.pop; a = vstack.pop
117
+ vstack << ((a << (b & 31)) & I32_MASK)
118
+ pc += 1
119
+ when :i32_shr_u
120
+ b = vstack.pop; a = vstack.pop
121
+ vstack << ((a & I32_MASK) >> (b & 31))
122
+ pc += 1
123
+ when :i32_shr_s
124
+ b = vstack.pop; a = Wasm.to_s32(vstack.pop)
125
+ shift = b & 31
126
+ vstack << ((a >> shift) & I32_MASK)
127
+ pc += 1
128
+ when :i32_rotl
129
+ b = vstack.pop; a = vstack.pop & I32_MASK
130
+ n = b & 31
131
+ vstack << (((a << n) | (a >> (32 - n))) & I32_MASK)
132
+ pc += 1
133
+ when :i32_rotr
134
+ b = vstack.pop; a = vstack.pop & I32_MASK
135
+ n = b & 31
136
+ vstack << (((a >> n) | (a << (32 - n))) & I32_MASK)
137
+ pc += 1
138
+ when :i32_load
139
+ addr = vstack.pop + instr[2]
140
+ vstack << @instance.memory.load_u32(addr)
141
+ pc += 1
142
+ when :i32_store
143
+ v = vstack.pop; addr = vstack.pop + instr[2]
144
+ @instance.memory.store_u32(addr, v)
145
+ pc += 1
146
+ when :i32_load8_u
147
+ addr = vstack.pop + instr[2]
148
+ vstack << @instance.memory.load_u8(addr)
149
+ pc += 1
150
+ when :i32_load8_s
151
+ addr = vstack.pop + instr[2]
152
+ vstack << (@instance.memory.load_i8(addr) & I32_MASK)
153
+ pc += 1
154
+ when :i32_load16_u
155
+ addr = vstack.pop + instr[2]
156
+ vstack << @instance.memory.load_u16(addr)
157
+ pc += 1
158
+ when :i32_load16_s
159
+ addr = vstack.pop + instr[2]
160
+ vstack << (@instance.memory.load_i16(addr) & I32_MASK)
161
+ pc += 1
162
+ when :i32_store8
163
+ v = vstack.pop; addr = vstack.pop + instr[2]
164
+ @instance.memory.store_u8(addr, v)
165
+ pc += 1
166
+ when :i32_store16
167
+ v = vstack.pop; addr = vstack.pop + instr[2]
168
+ @instance.memory.store_u16(addr, v)
169
+ pc += 1
170
+ when :i64_load
171
+ addr = vstack.pop + instr[2]
172
+ vstack << @instance.memory.load_u64(addr)
173
+ pc += 1
174
+ when :i64_store
175
+ v = vstack.pop; addr = vstack.pop + instr[2]
176
+ @instance.memory.store_u64(addr, v)
177
+ pc += 1
178
+ when :i64_load8_u
179
+ addr = vstack.pop + instr[2]
180
+ vstack << @instance.memory.load_u8(addr)
181
+ pc += 1
182
+ when :i64_load8_s
183
+ addr = vstack.pop + instr[2]
184
+ vstack << (@instance.memory.load_i8(addr) & I64_MASK)
185
+ pc += 1
186
+ when :i64_load16_u
187
+ addr = vstack.pop + instr[2]
188
+ vstack << @instance.memory.load_u16(addr)
189
+ pc += 1
190
+ when :i64_load16_s
191
+ addr = vstack.pop + instr[2]
192
+ vstack << (@instance.memory.load_i16(addr) & I64_MASK)
193
+ pc += 1
194
+ when :i64_load32_u
195
+ addr = vstack.pop + instr[2]
196
+ vstack << @instance.memory.load_u32(addr)
197
+ pc += 1
198
+ when :i64_load32_s
199
+ addr = vstack.pop + instr[2]
200
+ vstack << (@instance.memory.load_i32(addr) & I64_MASK)
201
+ pc += 1
202
+ when :i64_store8
203
+ v = vstack.pop; addr = vstack.pop + instr[2]
204
+ @instance.memory.store_u8(addr, v)
205
+ pc += 1
206
+ when :i64_store16
207
+ v = vstack.pop; addr = vstack.pop + instr[2]
208
+ @instance.memory.store_u16(addr, v)
209
+ pc += 1
210
+ when :i64_store32
211
+ v = vstack.pop; addr = vstack.pop + instr[2]
212
+ @instance.memory.store_u32(addr, v)
213
+ pc += 1
214
+ when :br_if
215
+ cond = vstack.pop
216
+ if cond.zero?
217
+ pc += 1
218
+ else
219
+ pc = do_branch(label_stack, vstack, instr[1])
220
+ return finish(vstack, ftype) if label_stack.empty?
221
+ end
222
+ when :br
223
+ pc = do_branch(label_stack, vstack, instr[1])
224
+ return finish(vstack, ftype) if label_stack.empty?
225
+ when :block
226
+ # [:block, blocktype, end_pc]
227
+ arity = block_result_arity(instr[1])
228
+ label_stack << [instr[2] + 1, arity, vstack.size - block_param_arity(instr[1]), false]
229
+ pc += 1
230
+ when :loop
231
+ # [:loop, blocktype, end_pc]; br to a loop re-executes the loop
232
+ # op (which freshly pushes a label), so target_pc = the loop op
233
+ # itself.
234
+ arity = block_param_arity(instr[1])
235
+ label_stack << [pc, arity, vstack.size - arity, true]
236
+ pc += 1
237
+ when :if
238
+ # [:if, blocktype, else_pc, end_pc]
239
+ cond = vstack.pop
240
+ arity = block_result_arity(instr[1])
241
+ label_stack << [instr[3] + 1, arity, vstack.size - block_param_arity(instr[1]), false]
242
+ if cond.zero?
243
+ else_pc = instr[2]
244
+ end_pc = instr[3]
245
+ pc = (else_pc == end_pc ? end_pc : else_pc + 1)
246
+ else
247
+ pc += 1
248
+ end
249
+ when :else
250
+ # Reached at end of the then-branch: skip the else body to end+1.
251
+ pc = instr[1] + 1
252
+ when :end
253
+ label_stack.pop
254
+ return finish(vstack, ftype) if label_stack.empty?
255
+
256
+ pc += 1
257
+ when :local_set
258
+ locals[instr[1]] = vstack.pop
259
+ pc += 1
260
+ when :local_tee
261
+ locals[instr[1]] = vstack.last
262
+ pc += 1
263
+ when :global_get
264
+ vstack << @instance.globals[instr[1]]
265
+ pc += 1
266
+ when :global_set
267
+ @instance.globals[instr[1]] = vstack.pop
268
+ pc += 1
269
+ when :i32_eqz
270
+ vstack << (vstack.pop.zero? ? 1 : 0)
271
+ pc += 1
272
+ when :i32_eq
273
+ b = vstack.pop; a = vstack.pop
274
+ vstack << (a == b ? 1 : 0)
275
+ pc += 1
276
+ when :i32_ne
277
+ b = vstack.pop; a = vstack.pop
278
+ vstack << (a == b ? 0 : 1)
279
+ pc += 1
280
+ when :i32_lt_u
281
+ b = vstack.pop; a = vstack.pop
282
+ vstack << (a < b ? 1 : 0)
283
+ pc += 1
284
+ when :i32_lt_s
285
+ b = Wasm.to_s32(vstack.pop); a = Wasm.to_s32(vstack.pop)
286
+ vstack << (a < b ? 1 : 0)
287
+ pc += 1
288
+ when :i32_gt_u
289
+ b = vstack.pop; a = vstack.pop
290
+ vstack << (a > b ? 1 : 0)
291
+ pc += 1
292
+ when :i32_gt_s
293
+ b = Wasm.to_s32(vstack.pop); a = Wasm.to_s32(vstack.pop)
294
+ vstack << (a > b ? 1 : 0)
295
+ pc += 1
296
+ when :i32_le_u
297
+ b = vstack.pop; a = vstack.pop
298
+ vstack << (a <= b ? 1 : 0)
299
+ pc += 1
300
+ when :i32_le_s
301
+ b = Wasm.to_s32(vstack.pop); a = Wasm.to_s32(vstack.pop)
302
+ vstack << (a <= b ? 1 : 0)
303
+ pc += 1
304
+ when :i32_ge_u
305
+ b = vstack.pop; a = vstack.pop
306
+ vstack << (a >= b ? 1 : 0)
307
+ pc += 1
308
+ when :i32_ge_s
309
+ b = Wasm.to_s32(vstack.pop); a = Wasm.to_s32(vstack.pop)
310
+ vstack << (a >= b ? 1 : 0)
311
+ pc += 1
312
+ when :i32_clz
313
+ v = vstack.pop & I32_MASK
314
+ vstack << (v.zero? ? 32 : 32 - v.bit_length)
315
+ pc += 1
316
+ when :i32_ctz
317
+ v = vstack.pop & I32_MASK
318
+ if v.zero?
319
+ vstack << 32
320
+ else
321
+ n = 0
322
+ n += 1 while ((v >> n) & 1).zero?
323
+ vstack << n
324
+ end
325
+ pc += 1
326
+ when :i32_popcnt
327
+ vstack << (vstack.pop & I32_MASK).to_s(2).count("1")
328
+ pc += 1
329
+ when :i32_div_u
330
+ b = vstack.pop; a = vstack.pop
331
+ raise Trap, "integer divide by zero" if b.zero?
332
+
333
+ vstack << (a / b)
334
+ pc += 1
335
+ when :i32_div_s
336
+ b = Wasm.to_s32(vstack.pop); a = Wasm.to_s32(vstack.pop)
337
+ raise Trap, "integer divide by zero" if b.zero?
338
+ raise Trap, "integer overflow" if a == -0x8000_0000 && b == -1
339
+
340
+ # Truncate toward zero
341
+ q = a.abs / b.abs
342
+ q = -q if (a < 0) ^ (b < 0)
343
+ vstack << (q & I32_MASK)
344
+ pc += 1
345
+ when :i32_rem_u
346
+ b = vstack.pop; a = vstack.pop
347
+ raise Trap, "integer divide by zero" if b.zero?
348
+
349
+ vstack << (a % b)
350
+ pc += 1
351
+ when :i32_rem_s
352
+ b = Wasm.to_s32(vstack.pop); a = Wasm.to_s32(vstack.pop)
353
+ raise Trap, "integer divide by zero" if b.zero?
354
+
355
+ r = a.abs % b.abs
356
+ r = -r if a < 0
357
+ vstack << (r & I32_MASK)
358
+ pc += 1
359
+ when :i64_add
360
+ b = vstack.pop; a = vstack.pop
361
+ vstack << ((a + b) & I64_MASK)
362
+ pc += 1
363
+ when :i64_sub
364
+ b = vstack.pop; a = vstack.pop
365
+ vstack << ((a - b) & I64_MASK)
366
+ pc += 1
367
+ when :i64_mul
368
+ b = vstack.pop; a = vstack.pop
369
+ vstack << ((a * b) & I64_MASK)
370
+ pc += 1
371
+ when :i64_and
372
+ b = vstack.pop; a = vstack.pop
373
+ vstack << ((a & b) & I64_MASK)
374
+ pc += 1
375
+ when :i64_or
376
+ b = vstack.pop; a = vstack.pop
377
+ vstack << ((a | b) & I64_MASK)
378
+ pc += 1
379
+ when :i64_xor
380
+ b = vstack.pop; a = vstack.pop
381
+ vstack << ((a ^ b) & I64_MASK)
382
+ pc += 1
383
+ when :i64_shl
384
+ b = vstack.pop; a = vstack.pop
385
+ vstack << ((a << (b & 63)) & I64_MASK)
386
+ pc += 1
387
+ when :i64_shr_u
388
+ b = vstack.pop; a = vstack.pop
389
+ vstack << ((a & I64_MASK) >> (b & 63))
390
+ pc += 1
391
+ when :i64_shr_s
392
+ b = vstack.pop; a = Wasm.to_s64(vstack.pop)
393
+ shift = b & 63
394
+ vstack << ((a >> shift) & I64_MASK)
395
+ pc += 1
396
+ when :i64_rotl
397
+ b = vstack.pop; a = vstack.pop & I64_MASK
398
+ n = b & 63
399
+ vstack << (((a << n) | (a >> (64 - n))) & I64_MASK)
400
+ pc += 1
401
+ when :i64_rotr
402
+ b = vstack.pop; a = vstack.pop & I64_MASK
403
+ n = b & 63
404
+ vstack << (((a >> n) | (a << (64 - n))) & I64_MASK)
405
+ pc += 1
406
+ when :i64_eqz
407
+ vstack << (vstack.pop.zero? ? 1 : 0)
408
+ pc += 1
409
+ when :i64_eq
410
+ b = vstack.pop; a = vstack.pop
411
+ vstack << (a == b ? 1 : 0)
412
+ pc += 1
413
+ when :i64_ne
414
+ b = vstack.pop; a = vstack.pop
415
+ vstack << (a == b ? 0 : 1)
416
+ pc += 1
417
+ when :i64_lt_u
418
+ b = vstack.pop; a = vstack.pop
419
+ vstack << (a < b ? 1 : 0)
420
+ pc += 1
421
+ when :i64_lt_s
422
+ b = Wasm.to_s64(vstack.pop); a = Wasm.to_s64(vstack.pop)
423
+ vstack << (a < b ? 1 : 0)
424
+ pc += 1
425
+ when :i64_gt_u
426
+ b = vstack.pop; a = vstack.pop
427
+ vstack << (a > b ? 1 : 0)
428
+ pc += 1
429
+ when :i64_gt_s
430
+ b = Wasm.to_s64(vstack.pop); a = Wasm.to_s64(vstack.pop)
431
+ vstack << (a > b ? 1 : 0)
432
+ pc += 1
433
+ when :i64_le_u
434
+ b = vstack.pop; a = vstack.pop
435
+ vstack << (a <= b ? 1 : 0)
436
+ pc += 1
437
+ when :i64_le_s
438
+ b = Wasm.to_s64(vstack.pop); a = Wasm.to_s64(vstack.pop)
439
+ vstack << (a <= b ? 1 : 0)
440
+ pc += 1
441
+ when :i64_ge_u
442
+ b = vstack.pop; a = vstack.pop
443
+ vstack << (a >= b ? 1 : 0)
444
+ pc += 1
445
+ when :i64_ge_s
446
+ b = Wasm.to_s64(vstack.pop); a = Wasm.to_s64(vstack.pop)
447
+ vstack << (a >= b ? 1 : 0)
448
+ pc += 1
449
+ when :i64_div_u
450
+ b = vstack.pop; a = vstack.pop
451
+ raise Trap, "integer divide by zero" if b.zero?
452
+
453
+ vstack << (a / b)
454
+ pc += 1
455
+ when :i64_clz
456
+ v = vstack.pop & I64_MASK
457
+ vstack << (v.zero? ? 64 : 64 - v.bit_length)
458
+ pc += 1
459
+ when :i64_ctz
460
+ v = vstack.pop & I64_MASK
461
+ if v.zero?
462
+ vstack << 64
463
+ else
464
+ n = 0
465
+ n += 1 while ((v >> n) & 1).zero?
466
+ vstack << n
467
+ end
468
+ pc += 1
469
+ when :i32_wrap_i64
470
+ vstack << (vstack.pop & I32_MASK)
471
+ pc += 1
472
+ when :i64_extend_i32_u
473
+ vstack << (vstack.pop & I32_MASK)
474
+ pc += 1
475
+ when :i64_extend_i32_s
476
+ vstack << (Wasm.to_s32(vstack.pop) & I64_MASK)
477
+ pc += 1
478
+ when :i32_extend8_s
479
+ v = vstack.pop & 0xFF
480
+ v -= 0x100 if v >= 0x80
481
+ vstack << (v & I32_MASK)
482
+ pc += 1
483
+ when :i32_extend16_s
484
+ v = vstack.pop & 0xFFFF
485
+ v -= 0x10000 if v >= 0x8000
486
+ vstack << (v & I32_MASK)
487
+ pc += 1
488
+ when :i64_extend8_s
489
+ v = vstack.pop & 0xFF
490
+ v -= 0x100 if v >= 0x80
491
+ vstack << (v & I64_MASK)
492
+ pc += 1
493
+ when :i64_extend16_s
494
+ v = vstack.pop & 0xFFFF
495
+ v -= 0x10000 if v >= 0x8000
496
+ vstack << (v & I64_MASK)
497
+ pc += 1
498
+ when :i64_extend32_s
499
+ v = vstack.pop & I32_MASK
500
+ v -= I32_RANGE if v >= I32_SIGN
501
+ vstack << (v & I64_MASK)
502
+ pc += 1
503
+ when :drop
504
+ vstack.pop
505
+ pc += 1
506
+ when :select
507
+ cond = vstack.pop
508
+ v2 = vstack.pop
509
+ v1 = vstack.pop
510
+ vstack << (cond.zero? ? v2 : v1)
511
+ pc += 1
512
+ when :call
513
+ callee_idx = instr[1]
514
+ callee = @instance.funcs[callee_idx]
515
+ n = callee.type.params.size
516
+ args2 = vstack.pop(n)
517
+ result = invoke(callee_idx, args2)
518
+ push_results(vstack, result, callee.type.results.size)
519
+ pc += 1
520
+ when :call_indirect
521
+ type_idx = instr[1]
522
+ table_entry = vstack.pop
523
+ callee_idx = @instance.table[table_entry]
524
+ raise Trap, "null funcref" if callee_idx.nil?
525
+
526
+ callee = @instance.funcs[callee_idx]
527
+ expected = @instance.types[type_idx]
528
+ if callee.type.params != expected.params || callee.type.results != expected.results
529
+ raise Trap, "indirect call type mismatch"
530
+ end
531
+
532
+ n = callee.type.params.size
533
+ args2 = vstack.pop(n)
534
+ result = invoke(callee_idx, args2)
535
+ push_results(vstack, result, callee.type.results.size)
536
+ pc += 1
537
+ when :return_
538
+ return finish(vstack, ftype)
539
+ when :br_table
540
+ idx = vstack.pop
541
+ depth = idx < instr[1].size ? instr[1][idx] : instr[2]
542
+ pc = do_branch(label_stack, vstack, depth)
543
+ return finish(vstack, ftype) if label_stack.empty?
544
+ when :memory_size
545
+ vstack << @instance.memory.size_pages
546
+ pc += 1
547
+ when :memory_grow
548
+ vstack << @instance.memory.grow(vstack.pop)
549
+ pc += 1
550
+ when :unreachable
551
+ raise Trap, "unreachable executed"
552
+ when :nop
553
+ pc += 1
554
+ else
555
+ raise Trap, "unimplemented op #{op.inspect} at pc=#{pc}"
556
+ end
557
+ end
558
+
559
+ finish(vstack, ftype)
560
+ end
561
+
562
+ # Branch to label depth N: pop arity vals, drop N+1 labels, restore
563
+ # value stack to that label's base height, re-push the saved vals,
564
+ # return the target pc. For loops, target points back at the loop op
565
+ # which will re-push a fresh label on re-execution.
566
+ def do_branch(label_stack, vstack, depth)
567
+ label = label_stack[label_stack.size - 1 - depth]
568
+ target_pc, arity, base_height, _is_loop = label
569
+ saved = arity.positive? ? vstack.last(arity) : []
570
+ (depth + 1).times { label_stack.pop }
571
+ vstack.slice!(base_height..)
572
+ saved.each { |v| vstack << v }
573
+ target_pc
574
+ end
575
+
576
+ def block_result_arity(blocktype)
577
+ case blocktype[0]
578
+ when :empty then 0
579
+ when :value then 1
580
+ when :typeidx
581
+ @instance.types[blocktype[1]].results.size
582
+ end
583
+ end
584
+
585
+ def block_param_arity(blocktype)
586
+ case blocktype[0]
587
+ when :empty, :value then 0
588
+ when :typeidx
589
+ @instance.types[blocktype[1]].params.size
590
+ end
591
+ end
592
+
593
+ def push_results(vstack, result, expected)
594
+ case expected
595
+ when 0 then nil
596
+ when 1
597
+ vstack << result
598
+ else
599
+ result.each { |v| vstack << v }
600
+ end
601
+ end
602
+
603
+ def finish(vstack, ftype)
604
+ case ftype.results.size
605
+ when 0 then nil
606
+ when 1 then vstack.last
607
+ else vstack.last(ftype.results.size)
608
+ end
609
+ end
610
+ end
611
+ end
612
+ end
613
+ end