local wasmlib = {} local constants = require("constants") local ops = require("ops") local frame = require("frame") local memory = require("memory") local intutil = require("intutil") local fileutil = require("fileutil") local strutil = require("strutil") local function trace(...) if wasmlib.TRACING then print(...) end end wasmlib.VM = { numImportedFuncs = 0, } function wasmlib.VM:new() local vm = { stack = {}, stackFrames = {}, functions = {}, table = {}, types = {}, memory = {}, globals = {}, imports = {}, exports = {}, } setmetatable(vm, {__index = self}) return vm end function wasmlib.VM:curFrame() return self.stackFrames[#self.stackFrames] end function wasmlib.VM:curBody() return self.functions[self:curFrame().funcIndex].body end function wasmlib.VM:curLocals() return self:curFrame().locals end function wasmlib.VM:triop(f) local c = table.remove(self.stack) local b = table.remove(self.stack) local a = table.remove(self.stack) table.insert(self.stack, f(a, b, c)) end function wasmlib.VM:binop(f) local b = table.remove(self.stack) local a = table.remove(self.stack) table.insert(self.stack, f(a, b)) end function wasmlib.VM:unop(f) local a = table.remove(self.stack) table.insert(self.stack, f(a)) end function wasmlib.VM:nextArg() local curFrame = self:curFrame() curFrame.pc = curFrame.pc + 1 return self:curBody()[curFrame.pc] end function wasmlib.VM:local_get() local localIdx = self:nextArg() local localVal = self:curLocals()[localIdx] or 0 -- FIXME bounds check table.insert(self.stack, localVal) end function wasmlib.VM:local_set() local localIdx = self:nextArg() local localVal = table.remove(self.stack) self:curFrame().locals[localIdx] = localVal end function wasmlib.VM:local_tee() local localIdx = self:nextArg() local localVal = self.stack[#self.stack] self:curFrame().locals[localIdx] = localVal end function wasmlib.VM:global_get() local globalIdx = self:nextArg() local globalVal = self.globals[globalIdx] if globalVal == nil then error("read invalid global") end table.insert(self.stack, globalVal) end function wasmlib.VM:global_set() local globalIdx = self:nextArg() local globalVal = table.remove(self.stack) self.globals[globalIdx] = globalVal end function wasmlib.VM:call() local funcIdx = self:nextArg() self:invoke(funcIdx) end function wasmlib.VM:call_indirect() local tabOffset = table.remove(self.stack) self:nextArg() -- FIXME ignoring typeIdx self:nextArg() -- always zero in wasm 1.0 local funcIdx = self.table[tabOffset] if funcIdx == nil then error("invalid call_indirect") end self:invoke(funcIdx) end function wasmlib.VM:memory_size() if self:nextArg() ~= 0 then error("memory.size operand must be zero") end table.insert(self.stack, self.memory.pages) end function wasmlib.VM:memory_grow() if self:nextArg() ~= 0 then error("memory.grow operand must be zero") end local amount = table.remove(self.stack) if ( (self.memory.maxpages > 0) and (self.memory.pages + amount > self.memory.maxpages) ) or (self.memory.pages + amount > constants.MAX_PAGES) then table.insert(self.stack, constants.I32_MAX) -- i32 -1 else table.insert(self.stack, self.memory.pages) self.memory:grow(amount) end end function wasmlib.VM:i32_load() self:nextArg() -- alignment, ignored for now local offset = self:nextArg() local argument = table.remove(self.stack) local value = intutil.fromle32(self.memory, offset + argument) trace("i32 loaded "..value.." from "..(offset+argument)) table.insert(self.stack, value) end function wasmlib.VM:i64_load() self:nextArg() -- alignment, ignored for now local offset = self:nextArg() local argument = table.remove(self.stack) local value = intutil.fromle64(self.memory, offset + argument) trace("i64 loaded "..value.." from "..(offset+argument)) table.insert(self.stack, value) end function wasmlib.VM:i32_load8_s() self:nextArg() -- alignment, ignored for now local offset = self:nextArg() local argument = table.remove(self.stack) local value = intutil.signexti8(self.memory[offset + argument]) & constants.I32_MAX trace("i32 8s loaded "..value.." from "..(offset+argument)) table.insert(self.stack, value) end function wasmlib.VM:i32_load8_u() self:nextArg() -- alignment, ignored for now local offset = self:nextArg() local argument = table.remove(self.stack) local value = self.memory[offset + argument] trace("i32 8u loaded "..value.." from "..(offset+argument)) table.insert(self.stack, value) end function wasmlib.VM:i32_load16_s() self:nextArg() -- alignment, ignored for now local offset = self:nextArg() local argument = table.remove(self.stack) local value = intutil.signexti16(intutil.fromle16(self.memory, offset + argument)) & constants.I32_MAX trace("i32 16s loaded "..value.." from "..(offset+argument)) table.insert(self.stack, value) end function wasmlib.VM:i32_load16_u() self:nextArg() -- alignment, ignored for now local offset = self:nextArg() local argument = table.remove(self.stack) local value = intutil.fromle16(self.memory, offset + argument) trace("i32 16u loaded "..value.." from "..(offset+argument)) table.insert(self.stack, value) end function wasmlib.VM:i64_load8_s() self:nextArg() -- alignment, ignored for now local offset = self:nextArg() local argument = table.remove(self.stack) local value = intutil.signexti8(self.memory[offset + argument]) trace("i64 8s loaded "..value.." from "..(offset+argument)) table.insert(self.stack, value) end function wasmlib.VM:i64_load8_u() self:nextArg() -- alignment, ignored for now local offset = self:nextArg() local argument = table.remove(self.stack) local value = self.memory[offset + argument] trace("i64 8u loaded "..value.." from "..(offset+argument)) table.insert(self.stack, value) end function wasmlib.VM:i64_load16_s() self:nextArg() -- alignment, ignored for now local offset = self:nextArg() local argument = table.remove(self.stack) local value = intutil.signexti16(intutil.fromle16(self.memory, offset + argument)) trace("i64 16s loaded "..value.." from "..(offset+argument)) table.insert(self.stack, value) end function wasmlib.VM:i64_load16_u() self:nextArg() -- alignment, ignored for now local offset = self:nextArg() local argument = table.remove(self.stack) local value = intutil.fromle16(self.memory, offset + argument) trace("i64 16u loaded "..value.." from "..(offset+argument)) table.insert(self.stack, value) end function wasmlib.VM:i64_load32_s() self:nextArg() -- alignment, ignored for now local offset = self:nextArg() local argument = table.remove(self.stack) local value = intutil.signexti32(intutil.fromle32(self.memory, offset + argument)) trace("i64 32s loaded "..value.." from "..(offset+argument)) table.insert(self.stack, value) end function wasmlib.VM:i64_load32_u() self:nextArg() -- alignment, ignored for now local offset = self:nextArg() local argument = table.remove(self.stack) local value = intutil.fromle32(self.memory, offset + argument) trace("i64 32u loaded "..value.." from "..(offset+argument)) table.insert(self.stack, value) end function wasmlib.VM:i32_store() self:nextArg() -- alignment, ignored for now local offset = self:nextArg() local value = table.remove(self.stack) local argument = table.remove(self.stack) trace("i32 stored "..value.." at "..(offset+argument)) intutil.tole32(self.memory, offset + argument, value) end function wasmlib.VM:i64_store() self:nextArg() -- alignment, ignored for now local offset = self:nextArg() local value = table.remove(self.stack) local argument = table.remove(self.stack) trace("i64 stored "..value.." at "..(offset+argument)) intutil.tole64(self.memory, offset + argument, value) end function wasmlib.VM:i32_store8() self:nextArg() -- alignment, ignored for now local offset = self:nextArg() local value = table.remove(self.stack) local argument = table.remove(self.stack) trace("i32 8 stored "..value.." at "..(offset+argument)) self.memory[offset + argument] = value & 0xFF end function wasmlib.VM:i32_store16() self:nextArg() -- alignment, ignored for now local offset = self:nextArg() local value = table.remove(self.stack) local argument = table.remove(self.stack) trace("i32 16 stored "..value.." at "..(offset+argument)) intutil.tole16(self.memory, offset + argument, value) end function wasmlib.VM:i64_store8() self:nextArg() -- alignment, ignored for now local offset = self:nextArg() local value = table.remove(self.stack) local argument = table.remove(self.stack) trace("i64 8 stored "..value.." at "..(offset+argument)) self.memory[offset + argument] = value & 0xFF end function wasmlib.VM:i64_store16() self:nextArg() -- alignment, ignored for now local offset = self:nextArg() local value = table.remove(self.stack) local argument = table.remove(self.stack) trace("i64 16 stored "..value.." at "..(offset+argument)) intutil.tole16(self.memory, offset + argument, value) end function wasmlib.VM:i64_store32() self:nextArg() -- alignment, ignored for now local offset = self:nextArg() local value = table.remove(self.stack) local argument = table.remove(self.stack) trace("i64 32 stored "..value.." at "..(offset+argument)) intutil.tole32(self.memory, offset + argument, value) end function wasmlib.VM:i32_const() table.insert(self.stack, self:nextArg()) end function wasmlib.VM:i64_const() table.insert(self.stack, self:nextArg()) end function wasmlib.VM:invoke(funcIndex) local f = self.functions[funcIndex] local sig = self.types[f.typeidx] trace(funcIndex, f.typeidx, table.unpack(sig.arguments)) trace(sig.ret) if f.import ~= nil then local args = {} for i = 1, #sig.arguments do args[#sig.arguments + 1 - i] = table.remove(self.stack) end local ret = f.import(table.unpack(args)) if sig.ret ~= nil then table.insert(self.stack, ret) end return end local fr = frame.StackFrame:new(funcIndex) for i = 1, #sig.arguments do fr.locals[#sig.arguments + 1 - i] = table.remove(self.stack) end table.insert(self.stackFrames, fr) end function wasmlib.VM:ret() table.remove(self.stackFrames) end -- Get the index of the next occurence of the instruction with opcode `target` -- starting from `i` at the same level of nesting local function findMatchingEndOrElse(body, i, depth) depth = depth or 0 repeat local opcode = body[i] local length = constants.ilengths[opcode] if length == 0 then if constants.blockOpcodes[opcode] ~= nil then i = findMatchingEndOrElse(body, i + 2, depth + 1) + (depth > 0 and 1 or 0) elseif opcode == constants.opcodes.OP_ELSE then i = i + 1 end else i = i + length end until (body[i] == constants.opcodes.OP_END) or (depth <= 1 and body[i] == constants.opcodes.OP_ELSE) return i end function wasmlib.VM:block() local curFrame = self:curFrame() local startIdx = curFrame.pc self:nextArg() -- block result type, ignored for now local endIdx = findMatchingEndOrElse(self:curBody(), startIdx) table.insert(curFrame.labelStack, endIdx + 1) end function wasmlib.VM:loop() local curFrame = self:curFrame() local startIdx = curFrame.pc self:nextArg() -- block result type, ignored for now table.insert(curFrame.labelStack, startIdx) end function wasmlib.VM:_if() local curFrame = self:curFrame() local pc = curFrame.pc self:nextArg() -- block result type, ignored for now local c = table.remove(self.stack) local body = self.functions[curFrame.funcIndex].body local endOrElse = findMatchingEndOrElse(body, pc) if body[endOrElse] == constants.opcodes.OP_ELSE then local elseIdx = endOrElse local endIdx = findMatchingEndOrElse(body, endOrElse) if c == 0 then curFrame.pc = elseIdx -- instruction after ELSE end table.insert(curFrame.labelStack, endIdx + 1) else local endIdx = endOrElse if c == 0 then curFrame.pc = endIdx -- instruction after END else table.insert(curFrame.labelStack, endIdx + 1) end end end function wasmlib.VM:_else() -- jump to label on top of stack local curFrame = self:curFrame() local label = table.remove(curFrame.labelStack) self:curFrame().pc = label - 1 end function wasmlib.VM:brInner(labelIdx) local curFrame = self:curFrame() local label = curFrame.labelStack[#curFrame.labelStack - labelIdx] for _ = 1, labelIdx + 1 do table.remove(curFrame.labelStack) end self:curFrame().pc = label - 1 end function wasmlib.VM:br() local labelIdx = self:nextArg() self:brInner(labelIdx) end function wasmlib.VM:br_if() local labelIdx = self:nextArg() local c = table.remove(self.stack) if c ~= 0 then self:brInner(labelIdx) end end function wasmlib.VM:br_table() local tab = self:nextArg() local other = self:nextArg() local i = table.remove(self.stack) if i < #table then self:brInner(tab[i + 1]) else self:brInner(other) end end function wasmlib.VM:_end() local curFrame = self:curFrame() table.remove(curFrame.labelStack) end function wasmlib.VM:step() if self:curFrame().pc >= #self:curBody() then trace("returning", self:curFrame().pc, #self:curBody()) self:ret() return end local opcode = self:nextArg() trace(self:curFrame().funcIndex, self:curFrame().pc,opcode, "###", table.unpack(self.stack)) trace(#self:curFrame().labelStack, "---", table.unpack(self:curFrame().labelStack)) local c = constants.opcodes local optable = { -- control [c.OP_UNREACHABLE] = function() error("unreachable") end, [c.OP_NOP] = function() end, [c.OP_BLOCK] = function() self:block() end, [c.OP_LOOP] = function() self:loop() end, [c.OP_IF] = function() self:_if() end, [c.OP_ELSE] = function() self:_else() end, [c.OP_END] = function() self:_end() end, [c.OP_BR] = function() self:br() end, [c.OP_BR_IF] = function() self:br_if() end, [c.OP_BR_TABLE] = function() self:br_table() end, [c.OP_RETURN] = function() self:ret() end, [c.OP_CALL] = function() self:call() end, [c.OP_CALL_INDIRECT] = function() self:call_indirect() end, -- parametric [c.OP_DROP] = function() table.remove(self.stack) end, [c.OP_SELECT] = function() self:triop(ops.select) end, -- variable [c.OP_LOCAL_GET] = function() self:local_get() end, [c.OP_LOCAL_SET] = function() self:local_set() end, [c.OP_LOCAL_TEE] = function() self:local_tee() end, [c.OP_GLOBAL_GET] = function() self:global_get() end, [c.OP_GLOBAL_SET] = function() self:global_set() end, -- memory [c.OP_I32_LOAD] = function() self:i32_load() end, [c.OP_I64_LOAD] = function() self:i64_load() end, -- [float instrs] [c.OP_I32_LOAD8_S] = function() self:i32_load8_s() end, [c.OP_I32_LOAD8_U] = function() self:i32_load8_u() end, [c.OP_I32_LOAD16_S] = function() self:i32_load16_s() end, [c.OP_I32_LOAD16_U] = function() self:i32_load16_u() end, [c.OP_I64_LOAD8_S] = function() self:i64_load8_s() end, [c.OP_I64_LOAD8_U] = function() self:i64_load8_u() end, [c.OP_I64_LOAD16_S] = function() self:i64_load16_s() end, [c.OP_I64_LOAD16_U] = function() self:i64_load16_u() end, [c.OP_I64_LOAD32_S] = function() self:i64_load32_s() end, [c.OP_I64_LOAD32_U] = function() self:i64_load32_u() end, [c.OP_I32_STORE] = function() self:i32_store() end, [c.OP_I64_STORE] = function() self:i64_store() end, -- [float instrs] [c.OP_I32_STORE8] = function() self:i32_store8() end, [c.OP_I32_STORE16] = function() self:i32_store16() end, [c.OP_I64_STORE8] = function() self:i64_store8() end, [c.OP_I64_STORE16] = function() self:i64_store16() end, [c.OP_I64_STORE32] = function() self:i64_store32() end, [c.OP_MEMORY_SIZE] = function() self:memory_size() end, [c.OP_MEMORY_GROW] = function() self:memory_grow() end, -- constants [c.OP_I32_CONST] = function() self:i32_const() end, [c.OP_I64_CONST] = function() self:i64_const() end, -- [float consts] -- i32 comparisons [c.OP_I32_EQZ] = function() self:unop (ops.i32_eqz) end, [c.OP_I32_EQ] = function() self:binop(ops.i32_eq) end, [c.OP_I32_NE] = function() self:binop(ops.i32_ne) end, [c.OP_I32_LT_S] = function() self:binop(ops.i32_lt_s) end, [c.OP_I32_LT_U] = function() self:binop(ops.i32_lt_u) end, [c.OP_I32_GT_S] = function() self:binop(ops.i32_gt_s) end, [c.OP_I32_GT_U] = function() self:binop(ops.i32_gt_u) end, [c.OP_I32_LE_S] = function() self:binop(ops.i32_le_s) end, [c.OP_I32_LE_U] = function() self:binop(ops.i32_le_u) end, [c.OP_I32_GE_S] = function() self:binop(ops.i32_ge_s) end, [c.OP_I32_GE_U] = function() self:binop(ops.i32_ge_u) end, -- i64 comparisons [c.OP_I64_EQZ] = function() self:unop (ops.i64_eqz) end, [c.OP_I64_EQ] = function() self:binop(ops.i64_eq) end, [c.OP_I64_NE] = function() self:binop(ops.i64_ne) end, [c.OP_I64_LT_S] = function() self:binop(ops.i64_lt_s) end, -- I64_LT_U [c.OP_I64_GT_S] = function() self:binop(ops.i64_gt_s) end, -- I64_GT_U [c.OP_I64_LE_S] = function() self:binop(ops.i64_le_s) end, -- I64_LE_U [c.OP_I64_GE_S] = function() self:binop(ops.i64_ge_s) end, -- I64_GE_U -- [float comparisons] -- i32 operations [c.OP_I32_CLZ] = function() self:unop (ops.i32_clz) end, [c.OP_I32_CTZ] = function() self:unop (ops.i32_ctz) end, [c.OP_I32_POPCNT] = function() self:unop (ops.i32_popcnt) end, [c.OP_I32_ADD] = function() self:binop(ops.i32_add) end, [c.OP_I32_SUB] = function() self:binop(ops.i32_sub) end, [c.OP_I32_MUL] = function() self:binop(ops.i32_mul) end, [c.OP_I32_DIV_S] = function() self:binop(ops.i32_div_s) end, [c.OP_I32_DIV_U] = function() self:binop(ops.i32_div_u) end, [c.OP_I32_REM_S] = function() self:binop(ops.i32_rem_s) end, [c.OP_I32_REM_U] = function() self:binop(ops.i32_rem_u) end, [c.OP_I32_AND] = function() self:binop(ops.i32_and) end, [c.OP_I32_OR] = function() self:binop(ops.i32_or) end, [c.OP_I32_XOR] = function() self:binop(ops.i32_xor) end, [c.OP_I32_SHL] = function() self:binop(ops.i32_shl) end, [c.OP_I32_SHR_S] = function() self:binop(ops.i32_shr_s) end, [c.OP_I32_SHR_U] = function() self:binop(ops.i32_shr_u) end, [c.OP_I32_ROTL] = function() self:binop(ops.i32_rotl) end, [c.OP_I32_ROTR] = function() self:binop(ops.i32_rotr) end, -- i64 operations [c.OP_I64_CLZ] = function() self:unop (ops.i64_clz) end, [c.OP_I64_CTZ] = function() self:unop (ops.i64_ctz) end, [c.OP_I64_POPCNT] = function() self:unop (ops.i64_popcnt) end, [c.OP_I64_ADD] = function() self:binop(ops.i64_add) end, [c.OP_I64_SUB] = function() self:binop(ops.i64_sub) end, [c.OP_I64_MUL] = function() self:binop(ops.i64_mul) end, [c.OP_I64_DIV_S] = function() self:binop(ops.i64_div_s) end, -- I64_DIV_U [c.OP_I64_REM_S] = function() self:binop(ops.i64_rem_s) end, -- I64_REM_U [c.OP_I64_AND] = function() self:binop(ops.i64_and) end, [c.OP_I64_OR] = function() self:binop(ops.i64_or) end, [c.OP_I64_XOR] = function() self:binop(ops.i64_xor) end, [c.OP_I64_SHL] = function() self:binop(ops.i64_shl) end, -- I64_SHR_S [c.OP_I64_SHR_U] = function() self:binop(ops.i64_shr_u) end, [c.OP_I64_ROTL] = function() self:binop(ops.i64_rotl) end, [c.OP_I64_ROTR] = function() self:binop(ops.i64_rotr) end, -- [float operations] -- conversions [c.OP_I32_WRAP_I64] = function() self:unop (ops.i32_wrap_i64) end, -- [float stuff] [c.OP_I64_EXTEND_I32_S] = function() self:unop (ops.i64_extend_i32_s) end, [c.OP_I64_EXTEND_I32_U] = function() self:unop (ops.i64_extend_i32_u) end, -- [float stuff] -- sign ext [c.OP_I32_EXTEND8_S] = function() self:unop (ops.i32_extend8_s) end, [c.OP_I32_EXTEND16_S] = function() self:unop (ops.i32_extend16_s) end, [c.OP_I64_EXTEND8_S] = function() self:unop (ops.i64_extend8_s) end, [c.OP_I64_EXTEND16_S] = function() self:unop (ops.i64_extend16_s) end, [c.OP_I64_EXTEND32_S] = function() self:unop (ops.i64_extend32_s) end, } local opfunc = optable[opcode] if opfunc == nil then error("unimplemented") end opfunc() end --[[ Binary format parsing ]] local function checkMagic(bytes, idx) if (bytes[idx ] ~= 0x00) or (bytes[idx+1] ~= 0x61) or (bytes[idx+2] ~= 0x73) or (bytes[idx+3] ~= 0x6D) then error("invalid magic") end return idx + 4 end local function checkVersion(bytes, idx) if (bytes[idx ] ~= 0x01) or (bytes[idx+1] ~= 0x00) or (bytes[idx+2] ~= 0x00) or (bytes[idx+3] ~= 0x00) then error("invalid format version") end return idx + 4 end function wasmlib.VM:parseCustom(bytes, idx) -- luacheck: no unused args -- ignore custom sections local szlen, size = intutil.fromuleb128(bytes, idx) return idx + szlen + size end function wasmlib.VM:parseTypes(bytes, idx) local szlen, _ = intutil.fromuleb128(bytes, idx) idx = idx + szlen local ntlen, numTypes = intutil.fromuleb128(bytes, idx) idx = idx + ntlen for i = 1, numTypes do if bytes[idx] ~= 0x60 then error("types section contained a type that is not a function type") end idx = idx + 1 local type = {} local nalen, numArgs = intutil.fromuleb128(bytes, idx) idx = idx + nalen type.arguments = {} for j = 1, numArgs do type.arguments[j] = bytes[idx] idx = idx + 1 end local nrlen, numReturns = intutil.fromuleb128(bytes, idx) idx = idx + nrlen if numReturns > 1 then error("more than 1 return value") elseif numReturns == 1 then type.ret = bytes[idx] idx = idx + 1 end self.types[i] = type end return idx end function wasmlib.VM:parseImports(bytes, idx, imports) trace("parsing imports section") local szlen, _ = intutil.fromuleb128(bytes, idx) idx = idx + szlen local nilen, numImports = intutil.fromuleb128(bytes, idx) idx = idx + nilen for _ = 1, numImports do local mnllen, mnameLen = intutil.fromuleb128(bytes, idx) idx = idx + mnllen local moduleName = strutil.bytestostr(bytes, idx, mnameLen) idx = idx + mnameLen local nllen, nameLen = intutil.fromuleb128(bytes, idx) idx = idx + nllen local importName = strutil.bytestostr(bytes, idx, nameLen) idx = idx + nameLen local importType = bytes[idx] idx = idx + 1 if importType == 0x00 then local typeidx = bytes[idx] idx = idx + 1 table.insert(self.functions, { typeidx = typeidx + 1, import = imports[moduleName][importName], }) elseif importType == 0x01 then error("table imports not supported") elseif importType == 0x02 then error("memory imports not supported") elseif importType == 0x03 then error("global imports not supported") else error("invalid import type "..importType) end end self.numImportedFuncs = #self.functions return idx end function wasmlib.VM:parseFunctions(bytes, idx) trace("parsing functions section") local szlen, _ = intutil.fromuleb128(bytes, idx) idx = idx + szlen local nflen, numFuncs = intutil.fromuleb128(bytes, idx) idx = idx + nflen for i = self.numImportedFuncs + 1, self.numImportedFuncs + numFuncs do local tilen, typeidx = intutil.fromuleb128(bytes, idx) idx = idx + tilen self.functions[i] = { typeidx = typeidx + 1 } end return idx end function wasmlib.VM:parseTables(bytes, idx) -- luacheck: no unused args -- FIXME: we just assume there is always a table, and don't check the size/types at all local szlen, size = intutil.fromuleb128(bytes, idx) return idx + szlen + size end function wasmlib.VM:parseMemory(bytes, idx) trace("parsing memory section") local szlen, _ = intutil.fromuleb128(bytes, idx) idx = idx + szlen local nmlen, numMems = intutil.fromuleb128(bytes, idx) idx = idx + nmlen if numMems > 1 then error("more than one memory") elseif numMems == 1 then local limitFlag = bytes[idx] idx = idx + 1 local minlen, min = intutil.fromuleb128(bytes, idx) idx = idx + minlen self.memory = memory.Memory:new(min) if limitFlag == 1 then local maxlen, max = intutil.fromuleb128(bytes, idx) idx = idx + maxlen self.memory.maxpages = max end end return idx end local function parseConstexpr(bytes, idx) -- FIXME we assume that the initialiser will only be one instruction local result local initOpcode = bytes[idx] idx = idx + 1 if initOpcode == constants.opcodes.OP_I32_CONST then local reslen, res = intutil.fromsleb128(bytes, idx, 32) idx = idx + reslen result = res elseif initOpcode == constants.opcodes.OP_I64_CONST then local reslen, res = intutil.fromsleb128(bytes, idx) idx = idx + reslen result = res else error("invalid global initialiser instruction") end if bytes[idx] ~= constants.opcodes.OP_END then error("END instruction not found in global initialiser") end idx = idx + 1 return idx, result end function wasmlib.VM:parseGlobals(bytes, idx) trace("parsing globals section") local szlen, _ = intutil.fromuleb128(bytes, idx) idx = idx + szlen local nglen, numGlobs = intutil.fromuleb128(bytes, idx) idx = idx + nglen for i = 1, numGlobs do -- TODO type and mutability ignored for now idx = idx + 2 local nidx, result = parseConstexpr(bytes, idx) idx = nidx self.globals[i] = result end return idx end function wasmlib.VM:parseExports(bytes, idx) trace("parsing exports section") local szlen, _ = intutil.fromuleb128(bytes, idx) idx = idx + szlen local nelen, numExports = intutil.fromuleb128(bytes, idx) idx = idx + nelen for _ = 1, numExports do local nllen, nameLen = intutil.fromuleb128(bytes, idx) idx = idx + nllen local exportName = strutil.bytestostr(bytes, idx, nameLen) idx = idx + nameLen local exportDesc = bytes[idx] idx = idx + 1 if exportDesc == 0x00 then local funcIdx = bytes[idx] + 1 idx = idx + 1 self.exports[exportName] = function(...) trace("called export "..exportName) local args = {...} local nargs = #self.types[self.functions[funcIdx].typeidx].arguments if #args ~= nargs then error("exported function "..exportName.." called with incorrect number of arguments") end for i = 1, nargs do table.insert(self.stack, args[i]) end self:invoke(funcIdx) while #self.stackFrames > 0 do self:step() end if self.types[self.functions[funcIdx].typeidx].ret ~= nil then return table.remove(self.stack) end end elseif exportDesc == 0x01 then idx = idx + 1 print("warning: ignoring table export") elseif exportDesc == 0x02 then idx = idx + 1 print("warning: ignoring memory export") elseif exportDesc == 0x03 then idx = idx + 1 print("warning: ignoring global export") else error("invalid export type "..exportDesc) end end return idx end function wasmlib.VM:parseStart(bytes, idx) -- luacheck: no unused args print("warning: skipping start section") local szlen, size = intutil.fromuleb128(bytes, idx) return idx + szlen + size end function wasmlib.VM:parseElements(bytes, idx) local szlen, _ = intutil.fromuleb128(bytes, idx) idx = idx + szlen local nelen, numElems = intutil.fromuleb128(bytes, idx) idx = idx + nelen for _ = 1, numElems do local tilen, tabIdx = intutil.fromuleb128(bytes, idx) idx = idx + tilen if tabIdx ~= 0 then error("tableidx must be zero") end local nidx, offset = parseConstexpr(bytes, idx) idx = nidx local nflen, numFunctions = intutil.fromuleb128(bytes, idx) idx = idx + nflen for i = 0, numFunctions - 1 do local filen, funcIdx = intutil.fromuleb128(bytes, idx) idx = idx + filen self.table[offset+i] = funcIdx + 1 end end return idx end function wasmlib.VM:parseCode(bytes, idx) trace("parsing code section") local szlen, _ = intutil.fromuleb128(bytes, idx) idx = idx + szlen local nclen, numCode = intutil.fromuleb128(bytes, idx) idx = idx + nclen for i = self.numImportedFuncs + 1, self.numImportedFuncs + numCode do -- size (ignored, not needed for decoding) local cszlen, _ = intutil.fromuleb128(bytes, idx) idx = idx + cszlen -- locals local nllen, numLocals = intutil.fromuleb128(bytes, idx) idx = idx + nllen local locals = {} local args = self.types[self.functions[i].typeidx].arguments for j = 1, #args do locals[j] = args[j] end local localIdx = #args for _ = 1, numLocals do local nlen, n = intutil.fromuleb128(bytes, idx) idx = idx + nlen local valtype = bytes[idx] idx = idx + 1 for _ = 1, n do localIdx = localIdx + 1 locals[localIdx] = valtype end end self.functions[i].locals = locals -- code local body = {} local nestDepth = 0 repeat local opcode = bytes[idx] idx = idx + 1 table.insert(body, opcode) if constants.blockOpcodes[opcode] ~= nil then nestDepth = nestDepth + 1 elseif opcode == constants.opcodes.OP_END then nestDepth = nestDepth - 1 end local operands = constants.operandTypes[opcode] or {} for j = 1, #operands do local operand = operands[j] if operand == "b" then local a = bytes[idx] idx = idx + 1 table.insert(body, a) elseif operand == "u" or operand == "U" or operand == "d" then local alen, a = intutil.fromuleb128(bytes, idx) if operand == "d" then a = a + 1 end idx = idx + alen table.insert(body, a) elseif operand == "i" then local alen, a = intutil.fromsleb128(bytes, idx, 32) idx = idx + alen table.insert(body, a) elseif operand == "I" then local alen, a = intutil.fromsleb128(bytes, idx) idx = idx + alen table.insert(body, a) elseif operand:sub(1,1) == "V" then local v = {} local vecType = operand:sub(2,2) if vecType ~= "u" then error("unimplemented") end local vllen, vecLen = intutil.fromuleb128(bytes, idx) idx = idx + vllen for _ = 1, vecLen do local alen, a = intutil.fromuleb128(bytes, idx) idx = idx + alen table.insert(v, a) end table.insert(body, v) end end until nestDepth == -1 -- expr is terminated with an END opcode which isn't part of any control flow structure, -- therefore the nestDepth should end up at -1 body[#body] = nil -- remove the END terminator self.functions[i].body = body end return idx end function wasmlib.VM:parseData(bytes, idx) trace("parsing data section") local szlen, _ = intutil.fromuleb128(bytes, idx) idx = idx + szlen local ndlen, numData = intutil.fromuleb128(bytes, idx) idx = idx + ndlen for _ = 1, numData do local milen, memIdx = intutil.fromuleb128(bytes, idx) idx = idx + milen if memIdx ~= 0 then error("memidx must be zero") end local nidx, offset = parseConstexpr(bytes, idx) idx = nidx local nblen, numBytes = intutil.fromuleb128(bytes, idx) idx = idx + nblen for i = 0, numBytes - 1 do self.memory[offset+i] = bytes[idx] idx = idx + 1 end end return idx end function wasmlib.VM:instantiate(bytes, imports) self.imports = imports or {} local idx = 1 idx = checkMagic(bytes, idx) idx = checkVersion(bytes, idx) local s = constants.sectionIds local sectParsers = { [s.SECT_CUSTOM] = self.parseCustom, [s.SECT_TYPE] = self.parseTypes, [s.SECT_IMPORT] = self.parseImports, [s.SECT_FUNC] = self.parseFunctions, [s.SECT_TABLE] = self.parseTables, [s.SECT_MEM] = self.parseMemory, [s.SECT_GLOBAL] = self.parseGlobals, [s.SECT_EXPORT] = self.parseExports, [s.SECT_START] = self.parseStart, [s.SECT_ELEM] = self.parseElements, [s.SECT_CODE] = self.parseCode, [s.SECT_DATA] = self.parseData, } while idx < #bytes do local sectId = bytes[idx] idx = idx + 1 local sectParser = sectParsers[sectId] if sectParser == nil then error("unsupported section type " .. sectId) end idx = sectParser(self, bytes, idx, imports) end end function wasmlib.VM:instantiateFile(path, imports) self:instantiate(fileutil.readBytes(path), imports) end return wasmlib