quick and dirty pure lua webassembly interpreter
1local wasmlib = {}
2
3local constants = require("constants")
4local ops = require("ops")
5local frame = require("frame")
6local memory = require("memory")
7local intutil = require("intutil")
8local fileutil = require("fileutil")
9local strutil = require("strutil")
10
11local function trace(...)
12 if wasmlib.TRACING then print(...) end
13end
14
15wasmlib.VM = {
16 numImportedFuncs = 0,
17}
18
19function wasmlib.VM:new()
20 local vm = {
21 stack = {},
22 stackFrames = {},
23 functions = {},
24 table = {},
25 types = {},
26 memory = {},
27 globals = {},
28 imports = {},
29 exports = {},
30 }
31 setmetatable(vm, {__index = self})
32 return vm
33end
34
35function wasmlib.VM:curFrame()
36 return self.stackFrames[#self.stackFrames]
37end
38
39function wasmlib.VM:curBody()
40 return self.functions[self:curFrame().funcIndex].body
41end
42
43function wasmlib.VM:curLocals()
44 return self:curFrame().locals
45end
46
47function wasmlib.VM:triop(f)
48 local c = table.remove(self.stack)
49 local b = table.remove(self.stack)
50 local a = table.remove(self.stack)
51 table.insert(self.stack, f(a, b, c))
52end
53
54function wasmlib.VM:binop(f)
55 local b = table.remove(self.stack)
56 local a = table.remove(self.stack)
57 table.insert(self.stack, f(a, b))
58end
59
60function wasmlib.VM:unop(f)
61 local a = table.remove(self.stack)
62 table.insert(self.stack, f(a))
63end
64
65function wasmlib.VM:nextArg()
66 local curFrame = self:curFrame()
67 curFrame.pc = curFrame.pc + 1
68 return self:curBody()[curFrame.pc]
69end
70
71function wasmlib.VM:local_get()
72 local localIdx = self:nextArg()
73 local localVal = self:curLocals()[localIdx] or 0 -- FIXME bounds check
74 table.insert(self.stack, localVal)
75end
76
77function wasmlib.VM:local_set()
78 local localIdx = self:nextArg()
79 local localVal = table.remove(self.stack)
80 self:curFrame().locals[localIdx] = localVal
81end
82
83function wasmlib.VM:local_tee()
84 local localIdx = self:nextArg()
85 local localVal = self.stack[#self.stack]
86 self:curFrame().locals[localIdx] = localVal
87end
88
89function wasmlib.VM:global_get()
90 local globalIdx = self:nextArg()
91 local globalVal = self.globals[globalIdx]
92 if globalVal == nil then
93 error("read invalid global")
94 end
95 table.insert(self.stack, globalVal)
96end
97
98function wasmlib.VM:global_set()
99 local globalIdx = self:nextArg()
100 local globalVal = table.remove(self.stack)
101 self.globals[globalIdx] = globalVal
102end
103
104function wasmlib.VM:call()
105 local funcIdx = self:nextArg()
106 self:invoke(funcIdx)
107end
108
109function wasmlib.VM:call_indirect()
110 local tabOffset = table.remove(self.stack)
111 self:nextArg() -- FIXME ignoring typeIdx
112 self:nextArg() -- always zero in wasm 1.0
113 local funcIdx = self.table[tabOffset]
114 if funcIdx == nil then
115 error("invalid call_indirect")
116 end
117 self:invoke(funcIdx)
118end
119
120function wasmlib.VM:memory_size()
121 if self:nextArg() ~= 0 then
122 error("memory.size operand must be zero")
123 end
124
125 table.insert(self.stack, self.memory.pages)
126end
127
128function wasmlib.VM:memory_grow()
129 if self:nextArg() ~= 0 then
130 error("memory.grow operand must be zero")
131 end
132
133 local amount = table.remove(self.stack)
134 if (
135 (self.memory.maxpages > 0) and (self.memory.pages + amount > self.memory.maxpages)
136 ) or (self.memory.pages + amount > constants.MAX_PAGES)
137 then
138 table.insert(self.stack, constants.I32_MAX) -- i32 -1
139 else
140 table.insert(self.stack, self.memory.pages)
141 self.memory:grow(amount)
142 end
143end
144
145function wasmlib.VM:i32_load()
146 self:nextArg() -- alignment, ignored for now
147 local offset = self:nextArg()
148 local argument = table.remove(self.stack)
149 local value = intutil.fromle32(self.memory, offset + argument)
150 trace("i32 loaded "..value.." from "..(offset+argument))
151 table.insert(self.stack, value)
152end
153
154function wasmlib.VM:i64_load()
155 self:nextArg() -- alignment, ignored for now
156 local offset = self:nextArg()
157 local argument = table.remove(self.stack)
158 local value = intutil.fromle64(self.memory, offset + argument)
159 trace("i64 loaded "..value.." from "..(offset+argument))
160 table.insert(self.stack, value)
161end
162
163function wasmlib.VM:i32_load8_s()
164 self:nextArg() -- alignment, ignored for now
165 local offset = self:nextArg()
166 local argument = table.remove(self.stack)
167 local value = intutil.signexti8(self.memory[offset + argument]) & constants.I32_MAX
168 trace("i32 8s loaded "..value.." from "..(offset+argument))
169 table.insert(self.stack, value)
170end
171
172function wasmlib.VM:i32_load8_u()
173 self:nextArg() -- alignment, ignored for now
174 local offset = self:nextArg()
175 local argument = table.remove(self.stack)
176 local value = self.memory[offset + argument]
177 trace("i32 8u loaded "..value.." from "..(offset+argument))
178 table.insert(self.stack, value)
179end
180
181function wasmlib.VM:i32_load16_s()
182 self:nextArg() -- alignment, ignored for now
183 local offset = self:nextArg()
184 local argument = table.remove(self.stack)
185 local value = intutil.signexti16(intutil.fromle16(self.memory, offset + argument)) & constants.I32_MAX
186 trace("i32 16s loaded "..value.." from "..(offset+argument))
187 table.insert(self.stack, value)
188end
189
190function wasmlib.VM:i32_load16_u()
191 self:nextArg() -- alignment, ignored for now
192 local offset = self:nextArg()
193 local argument = table.remove(self.stack)
194 local value = intutil.fromle16(self.memory, offset + argument)
195 trace("i32 16u loaded "..value.." from "..(offset+argument))
196 table.insert(self.stack, value)
197end
198
199function wasmlib.VM:i64_load8_s()
200 self:nextArg() -- alignment, ignored for now
201 local offset = self:nextArg()
202 local argument = table.remove(self.stack)
203 local value = intutil.signexti8(self.memory[offset + argument])
204 trace("i64 8s loaded "..value.." from "..(offset+argument))
205 table.insert(self.stack, value)
206end
207
208function wasmlib.VM:i64_load8_u()
209 self:nextArg() -- alignment, ignored for now
210 local offset = self:nextArg()
211 local argument = table.remove(self.stack)
212 local value = self.memory[offset + argument]
213 trace("i64 8u loaded "..value.." from "..(offset+argument))
214 table.insert(self.stack, value)
215end
216
217function wasmlib.VM:i64_load16_s()
218 self:nextArg() -- alignment, ignored for now
219 local offset = self:nextArg()
220 local argument = table.remove(self.stack)
221 local value = intutil.signexti16(intutil.fromle16(self.memory, offset + argument))
222 trace("i64 16s loaded "..value.." from "..(offset+argument))
223 table.insert(self.stack, value)
224end
225
226function wasmlib.VM:i64_load16_u()
227 self:nextArg() -- alignment, ignored for now
228 local offset = self:nextArg()
229 local argument = table.remove(self.stack)
230 local value = intutil.fromle16(self.memory, offset + argument)
231 trace("i64 16u loaded "..value.." from "..(offset+argument))
232 table.insert(self.stack, value)
233end
234
235function wasmlib.VM:i64_load32_s()
236 self:nextArg() -- alignment, ignored for now
237 local offset = self:nextArg()
238 local argument = table.remove(self.stack)
239 local value = intutil.signexti32(intutil.fromle32(self.memory, offset + argument))
240 trace("i64 32s loaded "..value.." from "..(offset+argument))
241 table.insert(self.stack, value)
242end
243
244function wasmlib.VM:i64_load32_u()
245 self:nextArg() -- alignment, ignored for now
246 local offset = self:nextArg()
247 local argument = table.remove(self.stack)
248 local value = intutil.fromle32(self.memory, offset + argument)
249 trace("i64 32u loaded "..value.." from "..(offset+argument))
250 table.insert(self.stack, value)
251end
252
253function wasmlib.VM:i32_store()
254 self:nextArg() -- alignment, ignored for now
255 local offset = self:nextArg()
256 local value = table.remove(self.stack)
257 local argument = table.remove(self.stack)
258 trace("i32 stored "..value.." at "..(offset+argument))
259 intutil.tole32(self.memory, offset + argument, value)
260end
261
262function wasmlib.VM:i64_store()
263 self:nextArg() -- alignment, ignored for now
264 local offset = self:nextArg()
265 local value = table.remove(self.stack)
266 local argument = table.remove(self.stack)
267 trace("i64 stored "..value.." at "..(offset+argument))
268 intutil.tole64(self.memory, offset + argument, value)
269end
270
271function wasmlib.VM:i32_store8()
272 self:nextArg() -- alignment, ignored for now
273 local offset = self:nextArg()
274 local value = table.remove(self.stack)
275 local argument = table.remove(self.stack)
276 trace("i32 8 stored "..value.." at "..(offset+argument))
277 self.memory[offset + argument] = value & 0xFF
278end
279
280function wasmlib.VM:i32_store16()
281 self:nextArg() -- alignment, ignored for now
282 local offset = self:nextArg()
283 local value = table.remove(self.stack)
284 local argument = table.remove(self.stack)
285 trace("i32 16 stored "..value.." at "..(offset+argument))
286 intutil.tole16(self.memory, offset + argument, value)
287end
288
289function wasmlib.VM:i64_store8()
290 self:nextArg() -- alignment, ignored for now
291 local offset = self:nextArg()
292 local value = table.remove(self.stack)
293 local argument = table.remove(self.stack)
294 trace("i64 8 stored "..value.." at "..(offset+argument))
295 self.memory[offset + argument] = value & 0xFF
296end
297
298function wasmlib.VM:i64_store16()
299 self:nextArg() -- alignment, ignored for now
300 local offset = self:nextArg()
301 local value = table.remove(self.stack)
302 local argument = table.remove(self.stack)
303 trace("i64 16 stored "..value.." at "..(offset+argument))
304 intutil.tole16(self.memory, offset + argument, value)
305end
306
307function wasmlib.VM:i64_store32()
308 self:nextArg() -- alignment, ignored for now
309 local offset = self:nextArg()
310 local value = table.remove(self.stack)
311 local argument = table.remove(self.stack)
312 trace("i64 32 stored "..value.." at "..(offset+argument))
313 intutil.tole32(self.memory, offset + argument, value)
314end
315
316function wasmlib.VM:i32_const()
317 table.insert(self.stack, self:nextArg())
318end
319
320function wasmlib.VM:i64_const()
321 table.insert(self.stack, self:nextArg())
322end
323
324function wasmlib.VM:invoke(funcIndex)
325 local f = self.functions[funcIndex]
326 local sig = self.types[f.typeidx]
327 trace(funcIndex, f.typeidx, table.unpack(sig.arguments))
328 trace(sig.ret)
329
330 if f.import ~= nil then
331 local args = {}
332 for i = 1, #sig.arguments do
333 args[#sig.arguments + 1 - i] = table.remove(self.stack)
334 end
335 local ret = f.import(table.unpack(args))
336 if sig.ret ~= nil then
337 table.insert(self.stack, ret)
338 end
339 return
340 end
341
342 local fr = frame.StackFrame:new(funcIndex)
343 for i = 1, #sig.arguments do
344 fr.locals[#sig.arguments + 1 - i] = table.remove(self.stack)
345 end
346 table.insert(self.stackFrames, fr)
347end
348
349function wasmlib.VM:ret()
350 table.remove(self.stackFrames)
351end
352
353-- Get the index of the next occurence of the instruction with opcode `target`
354-- starting from `i` at the same level of nesting
355local function findMatchingEndOrElse(body, i, depth)
356 depth = depth or 0
357 repeat
358 local opcode = body[i]
359 local length = constants.ilengths[opcode]
360 if length == 0 then
361 if constants.blockOpcodes[opcode] ~= nil then
362 i = findMatchingEndOrElse(body, i + 2, depth + 1) + (depth > 0 and 1 or 0)
363 elseif opcode == constants.opcodes.OP_ELSE then
364 i = i + 1
365 end
366 else
367 i = i + length
368 end
369 until (body[i] == constants.opcodes.OP_END) or (depth <= 1 and body[i] == constants.opcodes.OP_ELSE)
370 return i
371end
372
373function wasmlib.VM:block()
374 local curFrame = self:curFrame()
375 local startIdx = curFrame.pc
376 self:nextArg() -- block result type, ignored for now
377 local endIdx = findMatchingEndOrElse(self:curBody(), startIdx)
378 table.insert(curFrame.labelStack, endIdx + 1)
379end
380
381function wasmlib.VM:loop()
382 local curFrame = self:curFrame()
383 local startIdx = curFrame.pc
384 self:nextArg() -- block result type, ignored for now
385 table.insert(curFrame.labelStack, startIdx)
386end
387
388function wasmlib.VM:_if()
389 local curFrame = self:curFrame()
390 local pc = curFrame.pc
391
392 self:nextArg() -- block result type, ignored for now
393 local c = table.remove(self.stack)
394 local body = self.functions[curFrame.funcIndex].body
395 local endOrElse = findMatchingEndOrElse(body, pc)
396
397 if body[endOrElse] == constants.opcodes.OP_ELSE then
398 local elseIdx = endOrElse
399 local endIdx = findMatchingEndOrElse(body, endOrElse)
400 if c == 0 then
401 curFrame.pc = elseIdx -- instruction after ELSE
402 end
403 table.insert(curFrame.labelStack, endIdx + 1)
404 else
405 local endIdx = endOrElse
406 if c == 0 then
407 curFrame.pc = endIdx -- instruction after END
408 else
409 table.insert(curFrame.labelStack, endIdx + 1)
410 end
411 end
412end
413
414function wasmlib.VM:_else()
415 -- jump to label on top of stack
416 local curFrame = self:curFrame()
417 local label = table.remove(curFrame.labelStack)
418 self:curFrame().pc = label - 1
419end
420
421function wasmlib.VM:brInner(labelIdx)
422 local curFrame = self:curFrame()
423 local label = curFrame.labelStack[#curFrame.labelStack - labelIdx]
424 for _ = 1, labelIdx + 1 do
425 table.remove(curFrame.labelStack)
426 end
427 self:curFrame().pc = label - 1
428end
429
430function wasmlib.VM:br()
431 local labelIdx = self:nextArg()
432 self:brInner(labelIdx)
433end
434
435function wasmlib.VM:br_if()
436 local labelIdx = self:nextArg()
437 local c = table.remove(self.stack)
438 if c ~= 0 then
439 self:brInner(labelIdx)
440 end
441end
442
443function wasmlib.VM:br_table()
444 local tab = self:nextArg()
445 local other = self:nextArg()
446 local i = table.remove(self.stack)
447
448 if i < #table then
449 self:brInner(tab[i + 1])
450 else
451 self:brInner(other)
452 end
453end
454
455function wasmlib.VM:_end()
456 local curFrame = self:curFrame()
457 table.remove(curFrame.labelStack)
458end
459
460function wasmlib.VM:step()
461 if self:curFrame().pc >= #self:curBody() then
462 trace("returning", self:curFrame().pc, #self:curBody())
463 self:ret()
464 return
465 end
466
467 local opcode = self:nextArg()
468 trace(self:curFrame().funcIndex, self:curFrame().pc,opcode, "###", table.unpack(self.stack))
469 trace(#self:curFrame().labelStack, "---", table.unpack(self:curFrame().labelStack))
470
471 local c = constants.opcodes
472 local optable = {
473 -- control
474 [c.OP_UNREACHABLE] = function() error("unreachable") end,
475 [c.OP_NOP] = function() end,
476 [c.OP_BLOCK] = function() self:block() end,
477 [c.OP_LOOP] = function() self:loop() end,
478 [c.OP_IF] = function() self:_if() end,
479 [c.OP_ELSE] = function() self:_else() end,
480 [c.OP_END] = function() self:_end() end,
481 [c.OP_BR] = function() self:br() end,
482 [c.OP_BR_IF] = function() self:br_if() end,
483 [c.OP_BR_TABLE] = function() self:br_table() end,
484 [c.OP_RETURN] = function() self:ret() end,
485 [c.OP_CALL] = function() self:call() end,
486 [c.OP_CALL_INDIRECT] = function() self:call_indirect() end,
487 -- parametric
488 [c.OP_DROP] = function() table.remove(self.stack) end,
489 [c.OP_SELECT] = function() self:triop(ops.select) end,
490 -- variable
491 [c.OP_LOCAL_GET] = function() self:local_get() end,
492 [c.OP_LOCAL_SET] = function() self:local_set() end,
493 [c.OP_LOCAL_TEE] = function() self:local_tee() end,
494 [c.OP_GLOBAL_GET] = function() self:global_get() end,
495 [c.OP_GLOBAL_SET] = function() self:global_set() end,
496 -- memory
497 [c.OP_I32_LOAD] = function() self:i32_load() end,
498 [c.OP_I64_LOAD] = function() self:i64_load() end,
499 -- [float instrs]
500 [c.OP_I32_LOAD8_S] = function() self:i32_load8_s() end,
501 [c.OP_I32_LOAD8_U] = function() self:i32_load8_u() end,
502 [c.OP_I32_LOAD16_S] = function() self:i32_load16_s() end,
503 [c.OP_I32_LOAD16_U] = function() self:i32_load16_u() end,
504 [c.OP_I64_LOAD8_S] = function() self:i64_load8_s() end,
505 [c.OP_I64_LOAD8_U] = function() self:i64_load8_u() end,
506 [c.OP_I64_LOAD16_S] = function() self:i64_load16_s() end,
507 [c.OP_I64_LOAD16_U] = function() self:i64_load16_u() end,
508 [c.OP_I64_LOAD32_S] = function() self:i64_load32_s() end,
509 [c.OP_I64_LOAD32_U] = function() self:i64_load32_u() end,
510 [c.OP_I32_STORE] = function() self:i32_store() end,
511 [c.OP_I64_STORE] = function() self:i64_store() end,
512 -- [float instrs]
513 [c.OP_I32_STORE8] = function() self:i32_store8() end,
514 [c.OP_I32_STORE16] = function() self:i32_store16() end,
515 [c.OP_I64_STORE8] = function() self:i64_store8() end,
516 [c.OP_I64_STORE16] = function() self:i64_store16() end,
517 [c.OP_I64_STORE32] = function() self:i64_store32() end,
518 [c.OP_MEMORY_SIZE] = function() self:memory_size() end,
519 [c.OP_MEMORY_GROW] = function() self:memory_grow() end,
520 -- constants
521 [c.OP_I32_CONST] = function() self:i32_const() end,
522 [c.OP_I64_CONST] = function() self:i64_const() end,
523 -- [float consts]
524 -- i32 comparisons
525 [c.OP_I32_EQZ] = function() self:unop (ops.i32_eqz) end,
526 [c.OP_I32_EQ] = function() self:binop(ops.i32_eq) end,
527 [c.OP_I32_NE] = function() self:binop(ops.i32_ne) end,
528 [c.OP_I32_LT_S] = function() self:binop(ops.i32_lt_s) end,
529 [c.OP_I32_LT_U] = function() self:binop(ops.i32_lt_u) end,
530 [c.OP_I32_GT_S] = function() self:binop(ops.i32_gt_s) end,
531 [c.OP_I32_GT_U] = function() self:binop(ops.i32_gt_u) end,
532 [c.OP_I32_LE_S] = function() self:binop(ops.i32_le_s) end,
533 [c.OP_I32_LE_U] = function() self:binop(ops.i32_le_u) end,
534 [c.OP_I32_GE_S] = function() self:binop(ops.i32_ge_s) end,
535 [c.OP_I32_GE_U] = function() self:binop(ops.i32_ge_u) end,
536 -- i64 comparisons
537 [c.OP_I64_EQZ] = function() self:unop (ops.i64_eqz) end,
538 [c.OP_I64_EQ] = function() self:binop(ops.i64_eq) end,
539 [c.OP_I64_NE] = function() self:binop(ops.i64_ne) end,
540 [c.OP_I64_LT_S] = function() self:binop(ops.i64_lt_s) end,
541 -- I64_LT_U
542 [c.OP_I64_GT_S] = function() self:binop(ops.i64_gt_s) end,
543 -- I64_GT_U
544 [c.OP_I64_LE_S] = function() self:binop(ops.i64_le_s) end,
545 -- I64_LE_U
546 [c.OP_I64_GE_S] = function() self:binop(ops.i64_ge_s) end,
547 -- I64_GE_U
548 -- [float comparisons]
549 -- i32 operations
550 [c.OP_I32_CLZ] = function() self:unop (ops.i32_clz) end,
551 [c.OP_I32_CTZ] = function() self:unop (ops.i32_ctz) end,
552 [c.OP_I32_POPCNT] = function() self:unop (ops.i32_popcnt) end,
553 [c.OP_I32_ADD] = function() self:binop(ops.i32_add) end,
554 [c.OP_I32_SUB] = function() self:binop(ops.i32_sub) end,
555 [c.OP_I32_MUL] = function() self:binop(ops.i32_mul) end,
556 [c.OP_I32_DIV_S] = function() self:binop(ops.i32_div_s) end,
557 [c.OP_I32_DIV_U] = function() self:binop(ops.i32_div_u) end,
558 [c.OP_I32_REM_S] = function() self:binop(ops.i32_rem_s) end,
559 [c.OP_I32_REM_U] = function() self:binop(ops.i32_rem_u) end,
560 [c.OP_I32_AND] = function() self:binop(ops.i32_and) end,
561 [c.OP_I32_OR] = function() self:binop(ops.i32_or) end,
562 [c.OP_I32_XOR] = function() self:binop(ops.i32_xor) end,
563 [c.OP_I32_SHL] = function() self:binop(ops.i32_shl) end,
564 [c.OP_I32_SHR_S] = function() self:binop(ops.i32_shr_s) end,
565 [c.OP_I32_SHR_U] = function() self:binop(ops.i32_shr_u) end,
566 [c.OP_I32_ROTL] = function() self:binop(ops.i32_rotl) end,
567 [c.OP_I32_ROTR] = function() self:binop(ops.i32_rotr) end,
568 -- i64 operations
569 [c.OP_I64_CLZ] = function() self:unop (ops.i64_clz) end,
570 [c.OP_I64_CTZ] = function() self:unop (ops.i64_ctz) end,
571 [c.OP_I64_POPCNT] = function() self:unop (ops.i64_popcnt) end,
572 [c.OP_I64_ADD] = function() self:binop(ops.i64_add) end,
573 [c.OP_I64_SUB] = function() self:binop(ops.i64_sub) end,
574 [c.OP_I64_MUL] = function() self:binop(ops.i64_mul) end,
575 [c.OP_I64_DIV_S] = function() self:binop(ops.i64_div_s) end,
576 -- I64_DIV_U
577 [c.OP_I64_REM_S] = function() self:binop(ops.i64_rem_s) end,
578 -- I64_REM_U
579 [c.OP_I64_AND] = function() self:binop(ops.i64_and) end,
580 [c.OP_I64_OR] = function() self:binop(ops.i64_or) end,
581 [c.OP_I64_XOR] = function() self:binop(ops.i64_xor) end,
582 [c.OP_I64_SHL] = function() self:binop(ops.i64_shl) end,
583 -- I64_SHR_S
584 [c.OP_I64_SHR_U] = function() self:binop(ops.i64_shr_u) end,
585 [c.OP_I64_ROTL] = function() self:binop(ops.i64_rotl) end,
586 [c.OP_I64_ROTR] = function() self:binop(ops.i64_rotr) end,
587 -- [float operations]
588 -- conversions
589 [c.OP_I32_WRAP_I64] = function() self:unop (ops.i32_wrap_i64) end,
590 -- [float stuff]
591 [c.OP_I64_EXTEND_I32_S] = function() self:unop (ops.i64_extend_i32_s) end,
592 [c.OP_I64_EXTEND_I32_U] = function() self:unop (ops.i64_extend_i32_u) end,
593 -- [float stuff]
594 -- sign ext
595 [c.OP_I32_EXTEND8_S] = function() self:unop (ops.i32_extend8_s) end,
596 [c.OP_I32_EXTEND16_S] = function() self:unop (ops.i32_extend16_s) end,
597 [c.OP_I64_EXTEND8_S] = function() self:unop (ops.i64_extend8_s) end,
598 [c.OP_I64_EXTEND16_S] = function() self:unop (ops.i64_extend16_s) end,
599 [c.OP_I64_EXTEND32_S] = function() self:unop (ops.i64_extend32_s) end,
600 }
601
602 local opfunc = optable[opcode]
603 if opfunc == nil then
604 error("unimplemented")
605 end
606 opfunc()
607end
608
609--[[ Binary format parsing ]]
610
611local function checkMagic(bytes, idx)
612 if (bytes[idx ] ~= 0x00)
613 or (bytes[idx+1] ~= 0x61)
614 or (bytes[idx+2] ~= 0x73)
615 or (bytes[idx+3] ~= 0x6D)
616 then
617 error("invalid magic")
618 end
619 return idx + 4
620end
621
622local function checkVersion(bytes, idx)
623 if (bytes[idx ] ~= 0x01)
624 or (bytes[idx+1] ~= 0x00)
625 or (bytes[idx+2] ~= 0x00)
626 or (bytes[idx+3] ~= 0x00)
627 then
628 error("invalid format version")
629 end
630 return idx + 4
631end
632
633function wasmlib.VM:parseCustom(bytes, idx) -- luacheck: no unused args
634 -- ignore custom sections
635 local szlen, size = intutil.fromuleb128(bytes, idx)
636 return idx + szlen + size
637end
638
639function wasmlib.VM:parseTypes(bytes, idx)
640 local szlen, _ = intutil.fromuleb128(bytes, idx)
641 idx = idx + szlen
642 local ntlen, numTypes = intutil.fromuleb128(bytes, idx)
643 idx = idx + ntlen
644
645 for i = 1, numTypes do
646 if bytes[idx] ~= 0x60 then
647 error("types section contained a type that is not a function type")
648 end
649 idx = idx + 1
650
651 local type = {}
652
653 local nalen, numArgs = intutil.fromuleb128(bytes, idx)
654 idx = idx + nalen
655 type.arguments = {}
656 for j = 1, numArgs do
657 type.arguments[j] = bytes[idx]
658 idx = idx + 1
659 end
660
661 local nrlen, numReturns = intutil.fromuleb128(bytes, idx)
662 idx = idx + nrlen
663 if numReturns > 1 then
664 error("more than 1 return value")
665 elseif numReturns == 1 then
666 type.ret = bytes[idx]
667 idx = idx + 1
668 end
669
670 self.types[i] = type
671 end
672
673 return idx
674end
675
676function wasmlib.VM:parseImports(bytes, idx, imports)
677 trace("parsing imports section")
678 local szlen, _ = intutil.fromuleb128(bytes, idx)
679 idx = idx + szlen
680 local nilen, numImports = intutil.fromuleb128(bytes, idx)
681 idx = idx + nilen
682
683 for _ = 1, numImports do
684 local mnllen, mnameLen = intutil.fromuleb128(bytes, idx)
685 idx = idx + mnllen
686 local moduleName = strutil.bytestostr(bytes, idx, mnameLen)
687 idx = idx + mnameLen
688 local nllen, nameLen = intutil.fromuleb128(bytes, idx)
689 idx = idx + nllen
690 local importName = strutil.bytestostr(bytes, idx, nameLen)
691 idx = idx + nameLen
692
693 local importType = bytes[idx]
694 idx = idx + 1
695
696 if importType == 0x00 then
697 local typeidx = bytes[idx]
698 idx = idx + 1
699 table.insert(self.functions, {
700 typeidx = typeidx + 1,
701 import = imports[moduleName][importName],
702 })
703 elseif importType == 0x01 then
704 error("table imports not supported")
705 elseif importType == 0x02 then
706 error("memory imports not supported")
707 elseif importType == 0x03 then
708 error("global imports not supported")
709 else
710 error("invalid import type "..importType)
711 end
712 end
713
714 self.numImportedFuncs = #self.functions
715 return idx
716end
717
718function wasmlib.VM:parseFunctions(bytes, idx)
719 trace("parsing functions section")
720 local szlen, _ = intutil.fromuleb128(bytes, idx)
721 idx = idx + szlen
722 local nflen, numFuncs = intutil.fromuleb128(bytes, idx)
723 idx = idx + nflen
724
725 for i = self.numImportedFuncs + 1, self.numImportedFuncs + numFuncs do
726 local tilen, typeidx = intutil.fromuleb128(bytes, idx)
727 idx = idx + tilen
728 self.functions[i] = { typeidx = typeidx + 1 }
729 end
730
731 return idx
732end
733
734function wasmlib.VM:parseTables(bytes, idx) -- luacheck: no unused args
735 -- FIXME: we just assume there is always a table, and don't check the size/types at all
736 local szlen, size = intutil.fromuleb128(bytes, idx)
737 return idx + szlen + size
738end
739
740function wasmlib.VM:parseMemory(bytes, idx)
741 trace("parsing memory section")
742 local szlen, _ = intutil.fromuleb128(bytes, idx)
743 idx = idx + szlen
744 local nmlen, numMems = intutil.fromuleb128(bytes, idx)
745 idx = idx + nmlen
746
747 if numMems > 1 then
748 error("more than one memory")
749 elseif numMems == 1 then
750 local limitFlag = bytes[idx]
751 idx = idx + 1
752
753 local minlen, min = intutil.fromuleb128(bytes, idx)
754 idx = idx + minlen
755 self.memory = memory.Memory:new(min)
756
757 if limitFlag == 1 then
758 local maxlen, max = intutil.fromuleb128(bytes, idx)
759 idx = idx + maxlen
760 self.memory.maxpages = max
761 end
762 end
763
764 return idx
765end
766
767local function parseConstexpr(bytes, idx)
768 -- FIXME we assume that the initialiser will only be one instruction
769 local result
770 local initOpcode = bytes[idx]
771 idx = idx + 1
772
773 if initOpcode == constants.opcodes.OP_I32_CONST then
774 local reslen, res = intutil.fromsleb128(bytes, idx, 32)
775 idx = idx + reslen
776 result = res
777 elseif initOpcode == constants.opcodes.OP_I64_CONST then
778 local reslen, res = intutil.fromsleb128(bytes, idx)
779 idx = idx + reslen
780 result = res
781 else
782 error("invalid global initialiser instruction")
783 end
784
785 if bytes[idx] ~= constants.opcodes.OP_END then
786 error("END instruction not found in global initialiser")
787 end
788 idx = idx + 1
789
790 return idx, result
791end
792
793function wasmlib.VM:parseGlobals(bytes, idx)
794 trace("parsing globals section")
795 local szlen, _ = intutil.fromuleb128(bytes, idx)
796 idx = idx + szlen
797 local nglen, numGlobs = intutil.fromuleb128(bytes, idx)
798 idx = idx + nglen
799
800 for i = 1, numGlobs do
801 -- TODO type and mutability ignored for now
802 idx = idx + 2
803 local nidx, result = parseConstexpr(bytes, idx)
804 idx = nidx
805 self.globals[i] = result
806 end
807
808 return idx
809end
810
811function wasmlib.VM:parseExports(bytes, idx)
812 trace("parsing exports section")
813 local szlen, _ = intutil.fromuleb128(bytes, idx)
814 idx = idx + szlen
815 local nelen, numExports = intutil.fromuleb128(bytes, idx)
816 idx = idx + nelen
817
818 for _ = 1, numExports do
819 local nllen, nameLen = intutil.fromuleb128(bytes, idx)
820 idx = idx + nllen
821 local exportName = strutil.bytestostr(bytes, idx, nameLen)
822 idx = idx + nameLen
823
824 local exportDesc = bytes[idx]
825 idx = idx + 1
826 if exportDesc == 0x00 then
827 local funcIdx = bytes[idx] + 1
828 idx = idx + 1
829
830 self.exports[exportName] = function(...)
831 trace("called export "..exportName)
832 local args = {...}
833 local nargs = #self.types[self.functions[funcIdx].typeidx].arguments
834 if #args ~= nargs then
835 error("exported function "..exportName.." called with incorrect number of arguments")
836 end
837
838 for i = 1, nargs do
839 table.insert(self.stack, args[i])
840 end
841 self:invoke(funcIdx)
842 while #self.stackFrames > 0 do
843 self:step()
844 end
845
846 if self.types[self.functions[funcIdx].typeidx].ret ~= nil then
847 return table.remove(self.stack)
848 end
849 end
850 elseif exportDesc == 0x01 then
851 idx = idx + 1
852 print("warning: ignoring table export")
853 elseif exportDesc == 0x02 then
854 idx = idx + 1
855 print("warning: ignoring memory export")
856 elseif exportDesc == 0x03 then
857 idx = idx + 1
858 print("warning: ignoring global export")
859 else
860 error("invalid export type "..exportDesc)
861 end
862 end
863
864 return idx
865end
866
867function wasmlib.VM:parseStart(bytes, idx) -- luacheck: no unused args
868 print("warning: skipping start section")
869 local szlen, size = intutil.fromuleb128(bytes, idx)
870 return idx + szlen + size
871end
872
873function wasmlib.VM:parseElements(bytes, idx)
874 local szlen, _ = intutil.fromuleb128(bytes, idx)
875 idx = idx + szlen
876 local nelen, numElems = intutil.fromuleb128(bytes, idx)
877 idx = idx + nelen
878
879 for _ = 1, numElems do
880 local tilen, tabIdx = intutil.fromuleb128(bytes, idx)
881 idx = idx + tilen
882 if tabIdx ~= 0 then
883 error("tableidx must be zero")
884 end
885
886 local nidx, offset = parseConstexpr(bytes, idx)
887 idx = nidx
888
889 local nflen, numFunctions = intutil.fromuleb128(bytes, idx)
890 idx = idx + nflen
891 for i = 0, numFunctions - 1 do
892 local filen, funcIdx = intutil.fromuleb128(bytes, idx)
893 idx = idx + filen
894 self.table[offset+i] = funcIdx + 1
895 end
896 end
897
898 return idx
899end
900
901function wasmlib.VM:parseCode(bytes, idx)
902 trace("parsing code section")
903 local szlen, _ = intutil.fromuleb128(bytes, idx)
904 idx = idx + szlen
905 local nclen, numCode = intutil.fromuleb128(bytes, idx)
906 idx = idx + nclen
907
908 for i = self.numImportedFuncs + 1, self.numImportedFuncs + numCode do
909 -- size (ignored, not needed for decoding)
910 local cszlen, _ = intutil.fromuleb128(bytes, idx)
911 idx = idx + cszlen
912
913 -- locals
914 local nllen, numLocals = intutil.fromuleb128(bytes, idx)
915 idx = idx + nllen
916
917 local locals = {}
918 local args = self.types[self.functions[i].typeidx].arguments
919 for j = 1, #args do
920 locals[j] = args[j]
921 end
922 local localIdx = #args
923 for _ = 1, numLocals do
924 local nlen, n = intutil.fromuleb128(bytes, idx)
925 idx = idx + nlen
926 local valtype = bytes[idx]
927 idx = idx + 1
928
929 for _ = 1, n do
930 localIdx = localIdx + 1
931 locals[localIdx] = valtype
932 end
933 end
934
935 self.functions[i].locals = locals
936
937 -- code
938 local body = {}
939 local nestDepth = 0
940 repeat
941 local opcode = bytes[idx]
942 idx = idx + 1
943 table.insert(body, opcode)
944
945 if constants.blockOpcodes[opcode] ~= nil then
946 nestDepth = nestDepth + 1
947 elseif opcode == constants.opcodes.OP_END then
948 nestDepth = nestDepth - 1
949 end
950
951 local operands = constants.operandTypes[opcode] or {}
952 for j = 1, #operands do
953 local operand = operands[j]
954 if operand == "b" then
955 local a = bytes[idx]
956 idx = idx + 1
957 table.insert(body, a)
958 elseif operand == "u" or operand == "U" or operand == "d" then
959 local alen, a = intutil.fromuleb128(bytes, idx)
960 if operand == "d" then a = a + 1 end
961 idx = idx + alen
962 table.insert(body, a)
963 elseif operand == "i" then
964 local alen, a = intutil.fromsleb128(bytes, idx, 32)
965 idx = idx + alen
966 table.insert(body, a)
967 elseif operand == "I" then
968 local alen, a = intutil.fromsleb128(bytes, idx)
969 idx = idx + alen
970 table.insert(body, a)
971 elseif operand:sub(1,1) == "V" then
972 local v = {}
973 local vecType = operand:sub(2,2)
974 if vecType ~= "u" then
975 error("unimplemented")
976 end
977
978 local vllen, vecLen = intutil.fromuleb128(bytes, idx)
979 idx = idx + vllen
980
981 for _ = 1, vecLen do
982 local alen, a = intutil.fromuleb128(bytes, idx)
983 idx = idx + alen
984 table.insert(v, a)
985 end
986
987 table.insert(body, v)
988 end
989 end
990 until nestDepth == -1 -- expr is terminated with an END opcode which isn't part of any control flow structure,
991 -- therefore the nestDepth should end up at -1
992 body[#body] = nil -- remove the END terminator
993 self.functions[i].body = body
994 end
995
996 return idx
997end
998
999function wasmlib.VM:parseData(bytes, idx)
1000 trace("parsing data section")
1001 local szlen, _ = intutil.fromuleb128(bytes, idx)
1002 idx = idx + szlen
1003 local ndlen, numData = intutil.fromuleb128(bytes, idx)
1004 idx = idx + ndlen
1005
1006 for _ = 1, numData do
1007 local milen, memIdx = intutil.fromuleb128(bytes, idx)
1008 idx = idx + milen
1009 if memIdx ~= 0 then
1010 error("memidx must be zero")
1011 end
1012
1013 local nidx, offset = parseConstexpr(bytes, idx)
1014 idx = nidx
1015
1016 local nblen, numBytes = intutil.fromuleb128(bytes, idx)
1017 idx = idx + nblen
1018 for i = 0, numBytes - 1 do
1019 self.memory[offset+i] = bytes[idx]
1020 idx = idx + 1
1021 end
1022 end
1023
1024 return idx
1025end
1026
1027function wasmlib.VM:instantiate(bytes, imports)
1028 self.imports = imports or {}
1029
1030 local idx = 1
1031 idx = checkMagic(bytes, idx)
1032 idx = checkVersion(bytes, idx)
1033
1034 local s = constants.sectionIds
1035 local sectParsers = {
1036 [s.SECT_CUSTOM] = self.parseCustom,
1037 [s.SECT_TYPE] = self.parseTypes,
1038 [s.SECT_IMPORT] = self.parseImports,
1039 [s.SECT_FUNC] = self.parseFunctions,
1040 [s.SECT_TABLE] = self.parseTables,
1041 [s.SECT_MEM] = self.parseMemory,
1042 [s.SECT_GLOBAL] = self.parseGlobals,
1043 [s.SECT_EXPORT] = self.parseExports,
1044 [s.SECT_START] = self.parseStart,
1045 [s.SECT_ELEM] = self.parseElements,
1046 [s.SECT_CODE] = self.parseCode,
1047 [s.SECT_DATA] = self.parseData,
1048 }
1049
1050 while idx < #bytes do
1051 local sectId = bytes[idx]
1052 idx = idx + 1
1053 local sectParser = sectParsers[sectId]
1054 if sectParser == nil then
1055 error("unsupported section type " .. sectId)
1056 end
1057 idx = sectParser(self, bytes, idx, imports)
1058 end
1059end
1060
1061function wasmlib.VM:instantiateFile(path, imports)
1062 self:instantiate(fileutil.readBytes(path), imports)
1063end
1064
1065return wasmlib