Something went wrong. Try again.
quick and dirty pure lua webassembly interpreter
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065local 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(...) endend
wasmlib.VM = { numImportedFuncs = 0,}
function wasmlib.VM:new() local vm = { stack = {}, stackFrames = {}, functions = {}, table = {}, types = {}, memory = {}, globals = {}, imports = {}, exports = {}, } setmetatable(vm, {__index = self}) return vmend
function wasmlib.VM:curFrame() return self.stackFrames[#self.stackFrames]end
function wasmlib.VM:curBody() return self.functions[self:curFrame().funcIndex].bodyend
function wasmlib.VM:curLocals() return self:curFrame().localsend
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] = localValend
function wasmlib.VM:local_tee() local localIdx = self:nextArg() local localVal = self.stack[#self.stack] self:curFrame().locals[localIdx] = localValend
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] = globalValend
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) endend
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 & 0xFFend
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 & 0xFFend
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 nestinglocal 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 iend
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 endend
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 - 1end
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 - 1end
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) endend
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) endend
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 + 4end
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 + 4end
function wasmlib.VM:parseCustom(bytes, idx) -- luacheck: no unused args -- ignore custom sections local szlen, size = intutil.fromuleb128(bytes, idx) return idx + szlen + sizeend
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 idxend
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 idxend
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 idxend
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 + sizeend
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 idxend
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, resultend
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 idxend
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 idxend
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 + sizeend
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 idxend
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 idxend
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 idxend
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) endend
function wasmlib.VM:instantiateFile(path, imports) self:instantiate(fileutil.readBytes(path), imports)end
return wasmlib