diff --git a/constants.lua b/constants.lua index b6ce861..d7e72f2 100644 --- a/constants.lua +++ b/constants.lua @@ -206,6 +206,8 @@ constants.blockOpcodes = { -- Types are represented as an array where each element represents an operand: -- * "b": byte -- * "u": u32 +-- * "d": u32 index (needs to be incremented by 1 to account for lua 1-indexed arrays) +-- (used for all index types except label indices, and _not_ memory offsets) -- * "U": u64 -- * "i": i32 -- * "I": i64 @@ -219,15 +221,15 @@ constants.operandTypes = { [c.OP_BR] = {"u"}, [c.OP_BR_IF] = {"u"}, [c.OP_BR_TABLE] = {"Vu", "u"}, - [c.OP_CALL] = {"u"}, - [c.OP_CALL_INDIRECT] = {"u", "b"}, + [c.OP_CALL] = {"d"}, + [c.OP_CALL_INDIRECT] = {"d", "b"}, -- parametric -- variable - [c.OP_LOCAL_GET] = {"u"}, - [c.OP_LOCAL_SET] = {"u"}, - [c.OP_LOCAL_TEE] = {"u"}, - [c.OP_GLOBAL_GET] = {"u"}, - [c.OP_GLOBAL_SET] = {"u"}, + [c.OP_LOCAL_GET] = {"d"}, + [c.OP_LOCAL_SET] = {"d"}, + [c.OP_LOCAL_TEE] = {"d"}, + [c.OP_GLOBAL_GET] = {"d"}, + [c.OP_GLOBAL_SET] = {"d"}, -- memory [c.OP_I32_LOAD] = {"u", "u"}, [c.OP_I64_LOAD] = {"u", "u"}, diff --git a/strutil.lua b/strutil.lua new file mode 100644 index 0000000..64b3435 --- /dev/null +++ b/strutil.lua @@ -0,0 +1,7 @@ +local strutil = {} + +function strutil.bytestostr(bytes, idx, len) + return string.char(table.unpack(bytes, idx, idx + len - 1)) +end + +return strutil \ No newline at end of file diff --git a/wasmlib.lua b/wasmlib.lua index 4a5dcf5..0b4de4f 100644 --- a/wasmlib.lua +++ b/wasmlib.lua @@ -6,8 +6,10 @@ local frame = require("frame") local memory = require("memory") local intutil = require("intutil") local fileutil = require("fileutil") +local strutil = require("strutil") wasmlib.VM = { + numImportedFuncs = 0, stack = {}, stackFrames = {}, labelStack = {}, @@ -15,6 +17,7 @@ wasmlib.VM = { types = {}, memory = {}, globals = {}, + imports = {}, } function wasmlib.VM:new() @@ -413,6 +416,11 @@ function wasmlib.VM:br_table() end function wasmlib.VM:step() + if self:curFrame().pc >= #self:curBody() then + self:ret() + return + end + local opcode = self:nextArg() local c = constants.opcodes @@ -569,7 +577,7 @@ local function checkVersion(bytes, idx) return idx + 4 end -function wasmlib.VM:parseCustom(bytes, idx) +function wasmlib.VM:parseCustom(bytes, idx) -- luacheck: no unused args -- ignore custom sections local szlen, size = intutil.fromuleb128(bytes, idx) return idx + szlen + size @@ -612,10 +620,45 @@ function wasmlib.VM:parseTypes(bytes, idx) return idx end -function wasmlib.VM:parseImports(bytes, idx) -- TODO - print("warning: skipping imports section") - local szlen, size = intutil.fromuleb128(bytes, idx) - return idx + szlen + size +function wasmlib.VM:parseImports(bytes, idx, imports) + 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) @@ -624,16 +667,16 @@ function wasmlib.VM:parseFunctions(bytes, idx) local nflen, numFuncs = intutil.fromuleb128(bytes, idx) idx = idx + nflen - for i = 1, numFuncs do -- TODO handle imported functions + for i = self.numImportedFuncs + 1, self.numImportedFuncs + numFuncs do local tilen, typeidx = intutil.fromuleb128(bytes, idx) idx = idx + tilen - self.functions[i] = { typeidx = typeidx } + self.functions[i] = { typeidx = typeidx + 1 } end return idx end -function wasmlib.VM:parseTables(bytes, idx) -- TODO +function wasmlib.VM:parseTables(bytes, idx, imports) -- luacheck: no unused args print("warning: skipping tables section") local szlen, size = intutil.fromuleb128(bytes, idx) return idx + szlen + size @@ -708,31 +751,31 @@ function wasmlib.VM:parseGlobals(bytes, idx) return idx end -function wasmlib.VM:parseExports(bytes, idx) -- TODO +function wasmlib.VM:parseExports(bytes, idx) -- luacheck: no unused args print("warning: skipping exports section") local szlen, size = intutil.fromuleb128(bytes, idx) return idx + szlen + size end -function wasmlib.VM:parseStart(bytes, idx) -- TODO +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) -- TODO +function wasmlib.VM:parseElements(bytes, idx) -- luacheck: no unused args print("warning: skipping elements section") local szlen, size = intutil.fromuleb128(bytes, idx) return idx + szlen + size end -function wasmlib.VM:parseCode(bytes, idx) -- TODO +function wasmlib.VM:parseCode(bytes, idx) local szlen, _ = intutil.fromuleb128(bytes, idx) idx = idx + szlen local nclen, numCode = intutil.fromuleb128(bytes, idx) idx = idx + nclen - for i = 1, numCode do -- TODO handle imported functions + for i = self.numImportedFuncs + 1, self.numImportedFuncs + numCode do -- size (ignored, not needed for decoding) local cszlen, _ = intutil.fromuleb128(bytes, idx) idx = idx + cszlen @@ -776,14 +819,15 @@ function wasmlib.VM:parseCode(bytes, idx) -- TODO end local operands = constants.operandTypes[opcode] or {} - for _ = 1, #operands do - local operand = operands[i] + 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" then + 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 @@ -812,12 +856,13 @@ function wasmlib.VM:parseCode(bytes, idx) -- TODO 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 + self.functions[i].body = body end return idx end -function wasmlib.VM:parseData(bytes, idx) -- TODO +function wasmlib.VM:parseData(bytes, idx) local szlen, _ = intutil.fromuleb128(bytes, idx) idx = idx + szlen local ndlen, numData = intutil.fromuleb128(bytes, idx) @@ -844,7 +889,9 @@ function wasmlib.VM:parseData(bytes, idx) -- TODO return idx end -function wasmlib.VM:instantiate(bytes) +function wasmlib.VM:instantiate(bytes, imports) + self.imports = imports or {} + local idx = 1 idx = checkMagic(bytes, idx) idx = checkVersion(bytes, idx) @@ -872,12 +919,12 @@ function wasmlib.VM:instantiate(bytes) if sectParser == nil then error("unsupported section type " .. sectId) end - idx = sectParser(self, bytes, idx) + idx = sectParser(self, bytes, idx, imports) end end -function wasmlib.VM:instantiateFile(path) - self:instantiate(fileutil.readBytes(path)) +function wasmlib.VM:instantiateFile(path, imports) + self:instantiate(fileutil.readBytes(path), imports) end return wasmlib \ No newline at end of file