diff --git a/constants.lua b/constants.lua index 1864101..b6ce861 100644 --- a/constants.lua +++ b/constants.lua @@ -195,6 +195,66 @@ constants.opcodes = { } local c = constants.opcodes + +constants.blockOpcodes = { + [c.OP_BLOCK] = true, + [c.OP_LOOP] = true, + [c.OP_IF] = true, +} + +-- Operand types of each instruction. +-- Types are represented as an array where each element represents an operand: +-- * "b": byte +-- * "u": u32 +-- * "U": u64 +-- * "i": i32 +-- * "I": i64 +-- * "V" before one of these types represents a vector of that type. +-- Opcodes that are not in this table are assumed to take no operands. +constants.operandTypes = { +-- control + [c.OP_BLOCK] = {"b"}, + [c.OP_LOOP] = {"b"}, + [c.OP_IF] = {"b"}, + [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"}, +-- 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"}, +-- memory + [c.OP_I32_LOAD] = {"u", "u"}, + [c.OP_I64_LOAD] = {"u", "u"}, + [c.OP_I32_LOAD8_S] = {"u", "u"}, + [c.OP_I32_LOAD8_U] = {"u", "u"}, + [c.OP_I32_LOAD16_S] = {"u", "u"}, + [c.OP_I32_LOAD16_U] = {"u", "u"}, + [c.OP_I64_LOAD8_S] = {"u", "u"}, + [c.OP_I64_LOAD16_S] = {"u", "u"}, + [c.OP_I64_LOAD16_U] = {"u", "u"}, + [c.OP_I64_LOAD32_S] = {"u", "u"}, + [c.OP_I64_LOAD32_U] = {"u", "u"}, + [c.OP_I32_STORE] = {"u", "u"}, + [c.OP_I64_STORE] = {"u", "u"}, + [c.OP_I32_STORE8] = {"u", "u"}, + [c.OP_I32_STORE16] = {"u", "u"}, + [c.OP_I64_STORE8] = {"u", "u"}, + [c.OP_I64_STORE16] = {"u", "u"}, + [c.OP_I64_STORE32] = {"u", "u"}, + [c.OP_MEMORY_SIZE] = {"b"}, + [c.OP_MEMORY_GROW] = {"b"}, +-- constants + [c.OP_I32_CONST] = {"i"}, + [c.OP_I64_CONST] = {"I"}, +-- all remaining instructions are arithmetic instructions with no operands +} + -- Lengths of each instruction in bytes -- (zero represents variable length) constants.ilengths = { @@ -206,48 +266,48 @@ constants.ilengths = { [c.OP_IF] = 0, [c.OP_ELSE] = 0, [c.OP_END] = 1, - [c.OP_BR] = 5, - [c.OP_BR_IF] = 5, - [c.OP_BR_TABLE] = 0, + [c.OP_BR] = 2, + [c.OP_BR_IF] = 2, + [c.OP_BR_TABLE] = 3, [c.OP_RETURN] = 1, - [c.OP_CALL] = 5, - [c.OP_CALL_INDIRECT] = 6, + [c.OP_CALL] = 2, + [c.OP_CALL_INDIRECT] = 3, -- parametric [c.OP_DROP] = 1, [c.OP_SELECT] = 1, -- variable - [c.OP_LOCAL_GET] = 5, - [c.OP_LOCAL_SET] = 5, - [c.OP_LOCAL_TEE] = 5, - [c.OP_GLOBAL_GET] = 5, - [c.OP_GLOBAL_SET] = 5, + [c.OP_LOCAL_GET] = 2, + [c.OP_LOCAL_SET] = 2, + [c.OP_LOCAL_TEE] = 2, + [c.OP_GLOBAL_GET] = 2, + [c.OP_GLOBAL_SET] = 2, -- memory - [c.OP_I32_LOAD] = 9, - [c.OP_I64_LOAD] = 9, + [c.OP_I32_LOAD] = 3, + [c.OP_I64_LOAD] = 3, -- [float instrs] - [c.OP_I32_LOAD8_S] = 9, - [c.OP_I32_LOAD8_U] = 9, - [c.OP_I32_LOAD16_S] = 9, - [c.OP_I32_LOAD16_U] = 9, - [c.OP_I64_LOAD8_S] = 9, - [c.OP_I64_LOAD8_U] = 9, - [c.OP_I64_LOAD16_S] = 9, - [c.OP_I64_LOAD16_U] = 9, - [c.OP_I64_LOAD32_S] = 9, - [c.OP_I64_LOAD32_U] = 9, - [c.OP_I32_STORE] = 9, - [c.OP_I64_STORE] = 9, + [c.OP_I32_LOAD8_S] = 3, + [c.OP_I32_LOAD8_U] = 3, + [c.OP_I32_LOAD16_S] = 3, + [c.OP_I32_LOAD16_U] = 3, + [c.OP_I64_LOAD8_S] = 3, + [c.OP_I64_LOAD8_U] = 3, + [c.OP_I64_LOAD16_S] = 3, + [c.OP_I64_LOAD16_U] = 3, + [c.OP_I64_LOAD32_S] = 3, + [c.OP_I64_LOAD32_U] = 3, + [c.OP_I32_STORE] = 3, + [c.OP_I64_STORE] = 3, -- [float instrs] - [c.OP_I32_STORE8] = 9, - [c.OP_I32_STORE16] = 9, - [c.OP_I64_STORE8] = 9, - [c.OP_I64_STORE16] = 9, - [c.OP_I64_STORE32] = 9, + [c.OP_I32_STORE8] = 3, + [c.OP_I32_STORE16] = 3, + [c.OP_I64_STORE8] = 3, + [c.OP_I64_STORE16] = 3, + [c.OP_I64_STORE32] = 3, [c.OP_MEMORY_SIZE] = 2, [c.OP_MEMORY_GROW] = 2, -- constants - [c.OP_I32_CONST] = 5, - [c.OP_I64_CONST] = 9, + [c.OP_I32_CONST] = 2, + [c.OP_I64_CONST] = 2, -- [float consts] -- i32 comparisons [c.OP_I32_EQZ] = 1, @@ -335,4 +395,19 @@ constants.blocktypes = { } +constants.sectionIds = { + SECT_CUSTOM = 0, + SECT_TYPE = 1, + SECT_IMPORT = 2, + SECT_FUNC = 3, + SECT_TABLE = 4, + SECT_MEM = 5, + SECT_GLOBAL = 6, + SECT_EXPORT = 7, + SECT_START = 8, + SECT_ELEM = 9, + SECT_CODE = 10, + SECT_DATA = 11, +} + return constants \ No newline at end of file diff --git a/fileutil.lua b/fileutil.lua new file mode 100644 index 0000000..acc5cee --- /dev/null +++ b/fileutil.lua @@ -0,0 +1,17 @@ +local fileutil = {} + +function fileutil.readBytes(path) + local file = assert(io.open(path, "rb")) + local bytes = {} + + repeat + local s = file:read(4096) + for c in (s or ""):gmatch(".") do + table.insert(bytes, c:byte()) + end + until not s + file:close() + return bytes +end + +return fileutil \ No newline at end of file diff --git a/frame.lua b/frame.lua index 4d227e7..335f2cb 100644 --- a/frame.lua +++ b/frame.lua @@ -3,7 +3,7 @@ local frame = {} frame.StackFrame = { funcIndex = 0, locals = {}, - pc = 1, -- program counter + pc = 0, -- program counter } function frame.StackFrame:new(funcIndex) diff --git a/intutil.lua b/intutil.lua index e05f732..aec5318 100644 --- a/intutil.lua +++ b/intutil.lua @@ -70,4 +70,36 @@ function intutil.signexti8(n) return n end +function intutil.fromuleb128(tab, idx) + local result = 0 + local shift = 0 + local len = 0 + repeat + local byte = tab[idx+len] + result = result | ((byte & 0x7F) << shift) + shift = shift + 7 + len = len + 1 + until (byte & 0x80) == 0 + return len, result +end + +function intutil.fromsleb128(tab, idx, bits) + bits = bits or 64 + local result = 0 + local shift = 0 + local len = 0 + + repeat + local byte = tab[idx+len] + result = result | ((byte & 0x7F) << shift) + shift = shift + 7 + len = len + 1 + until (byte & 0x80) == 0 + + if (shift < bits) and ((tab[idx+len-1] & 0x40) ~= 0) then + result = result | (((1 << bits) - 1) << shift) + end + return len, result +end + return intutil \ No newline at end of file diff --git a/wasmlib.lua b/wasmlib.lua index 0ffaabf..4a5dcf5 100644 --- a/wasmlib.lua +++ b/wasmlib.lua @@ -3,7 +3,9 @@ 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") wasmlib.VM = { stack = {}, @@ -21,10 +23,18 @@ function wasmlib.VM:new() return vm end -function wasmlib.VM:topFrame() +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) @@ -43,34 +53,15 @@ function wasmlib.VM:unop(f) table.insert(self.stack, f(a)) end -function wasmlib.VM:readarg8() - local curFrame = self:topFrame() - local body = self.functions[curFrame.funcIndex].body - local result = body[curFrame.pc] +function wasmlib.VM:nextArg() + local curFrame = self:curFrame() curFrame.pc = curFrame.pc + 1 - return result -end - -function wasmlib.VM:readarg32() - local curFrame = self:topFrame() - local body = self.functions[curFrame.funcIndex].body - local result = intutil.fromle32(body, curFrame.pc) - curFrame.pc = curFrame.pc + 4 - return result -end - -function wasmlib.VM:readarg64() - local curFrame = self:topFrame() - local body = self.functions[curFrame.funcIndex].body - local result = intutil.fromle64(body, curFrame.pc) - curFrame.pc = curFrame.pc + 8 - return result + return self:curBody()[curFrame.pc] end function wasmlib.VM:local_get() - local curFrame = self:topFrame() - local localIdx = self:readarg32() - local localVal = curFrame.locals[localIdx] + local localIdx = self:nextArg() + local localVal = self:curLocals()[localIdx] if localVal == nil then error("read uninitialised or out of bounds local") end @@ -78,21 +69,19 @@ function wasmlib.VM:local_get() end function wasmlib.VM:local_set() - local curFrame = self:topFrame() - local localIdx = self:readarg32() + local localIdx = self:nextArg() local localVal = table.remove(self.stack) - curFrame.locals[localIdx] = localVal + self:curFrame().locals[localIdx] = localVal end function wasmlib.VM:local_tee() - local curFrame = self:topFrame() - local localIdx = self:readarg32() + local localIdx = self:nextArg() local localVal = self.stack[#self.stack] - curFrame.locals[localIdx] = localVal + self:curFrame().locals[localIdx] = localVal end function wasmlib.VM:global_get() - local globalIdx = self:readarg32() + local globalIdx = self:nextArg() local globalVal = self.globals[globalIdx] if globalVal == nil then error("read invalid global") @@ -101,23 +90,29 @@ function wasmlib.VM:global_get() end function wasmlib.VM:global_set() - local globalIdx = self:readarg32() + local globalIdx = self:nextArg() local globalVal = table.remove(self.stack) self.globals[globalIdx] = globalVal end function wasmlib.VM:call() - local funcIdx = self:readarg32() + local funcIdx = self:nextArg() self:invoke(funcIdx) end function wasmlib.VM:memory_size() - self:readarg8() -- always zero in WASM 1.0 + if self:nextArg() ~= 0 then + error("memory.size operand must be zero") + end + return #self.memory end function wasmlib.VM:memory_grow() - self:readarg8() -- always zero in WASM 1.0 + if self:nextArg() ~= 0 then + error("memory.grow operand must be zero") + end + local amount = table.remove(self.stack) if ( (self.memory.maxpages ~= nil) and (#self.memory + amount > self.memory.maxpages) @@ -131,183 +126,181 @@ function wasmlib.VM:memory_grow() end function wasmlib.VM:i32_load() - self:readarg32() -- alignment, ignored for now - local offset = self:readarg32() + self:nextArg() -- alignment, ignored for now + local offset = self:nextArg() local argument = table.remove(self.stack) local value = intutil.fromle32(self.memory, offset + argument) table.insert(self.stack, value) end function wasmlib.VM:i64_load() - self:readarg32() -- alignment, ignored for now - local offset = self:readarg32() + self:nextArg() -- alignment, ignored for now + local offset = self:nextArg() local argument = table.remove(self.stack) local value = intutil.fromle64(self.memory, offset + argument) table.insert(self.stack, value) end function wasmlib.VM:i32_load8_s() - self:readarg32() -- alignment, ignored for now - local offset = self:readarg32() + 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 table.insert(self.stack, value) end function wasmlib.VM:i32_load8_u() - self:readarg32() -- alignment, ignored for now - local offset = self:readarg32() + self:nextArg() -- alignment, ignored for now + local offset = self:nextArg() local argument = table.remove(self.stack) local value = self.memory[offset + argument] table.insert(self.stack, value) end function wasmlib.VM:i32_load16_s() - self:readarg32() -- alignment, ignored for now - local offset = self:readarg32() + 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 table.insert(self.stack, value) end function wasmlib.VM:i32_load16_u() - self:readarg32() -- alignment, ignored for now - local offset = self:readarg32() + self:nextArg() -- alignment, ignored for now + local offset = self:nextArg() local argument = table.remove(self.stack) local value = intutil.fromle16(self.memory, offset + argument) table.insert(self.stack, value) end function wasmlib.VM:i64_load8_s() - self:readarg32() -- alignment, ignored for now - local offset = self:readarg32() + self:nextArg() -- alignment, ignored for now + local offset = self:nextArg() local argument = table.remove(self.stack) local value = intutil.signexti8(self.memory[offset + argument]) table.insert(self.stack, value) end function wasmlib.VM:i64_load8_u() - self:readarg32() -- alignment, ignored for now - local offset = self:readarg32() + self:nextArg() -- alignment, ignored for now + local offset = self:nextArg() local argument = table.remove(self.stack) local value = self.memory[offset + argument] table.insert(self.stack, value) end function wasmlib.VM:i64_load16_s() - self:readarg32() -- alignment, ignored for now - local offset = self:readarg32() + 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)) table.insert(self.stack, value) end function wasmlib.VM:i64_load16_u() - self:readarg32() -- alignment, ignored for now - local offset = self:readarg32() + self:nextArg() -- alignment, ignored for now + local offset = self:nextArg() local argument = table.remove(self.stack) local value = intutil.fromle16(self.memory, offset + argument) table.insert(self.stack, value) end function wasmlib.VM:i64_load32_s() - self:readarg32() -- alignment, ignored for now - local offset = self:readarg32() + 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)) table.insert(self.stack, value) end function wasmlib.VM:i64_load32_u() - self:readarg32() -- alignment, ignored for now - local offset = self:readarg32() + self:nextArg() -- alignment, ignored for now + local offset = self:nextArg() local argument = table.remove(self.stack) local value = intutil.fromle32(self.memory, offset + argument) table.insert(self.stack, value) end function wasmlib.VM:i64_load() - self:readarg32() -- alignment, ignored for now - local offset = self:readarg32() + 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)) table.insert(self.stack, value) end function wasmlib.VM:i32_store() - self:readarg32() -- alignment, ignored for now - local offset = self:readarg32() + self:nextArg() -- alignment, ignored for now + local offset = self:nextArg() local argument = table.remove(self.stack) local value = table.remove(self.stack) intutil.tole32(self.memory, offset + argument, value) end function wasmlib.VM:i64_store() - self:readarg32() -- alignment, ignored for now - local offset = self:readarg32() + self:nextArg() -- alignment, ignored for now + local offset = self:nextArg() local argument = table.remove(self.stack) local value = table.remove(self.stack) intutil.tole64(self.memory, offset + argument, value) end function wasmlib.VM:i32_store8() - self:readarg32() -- alignment, ignored for now - local offset = self:readarg32() + self:nextArg() -- alignment, ignored for now + local offset = self:nextArg() local argument = table.remove(self.stack) local value = table.remove(self.stack) self.memory[offset + argument] = value & 0xFF end function wasmlib.VM:i32_store16() - self:readarg32() -- alignment, ignored for now - local offset = self:readarg32() + self:nextArg() -- alignment, ignored for now + local offset = self:nextArg() local argument = table.remove(self.stack) local value = table.remove(self.stack) intutil.tole16(self.memory, offset + argument, value) end function wasmlib.VM:i64_store8() - self:readarg32() -- alignment, ignored for now - local offset = self:readarg32() + self:nextArg() -- alignment, ignored for now + local offset = self:nextArg() local argument = table.remove(self.stack) local value = table.remove(self.stack) self.memory[offset + argument] = value & 0xFF end function wasmlib.VM:i64_store16() - self:readarg32() -- alignment, ignored for now - local offset = self:readarg32() + self:nextArg() -- alignment, ignored for now + local offset = self:nextArg() local argument = table.remove(self.stack) local value = table.remove(self.stack) intutil.tole16(self.memory, offset + argument, value) end function wasmlib.VM:i64_store32() - self:readarg32() -- alignment, ignored for now - local offset = self:readarg32() + self:nextArg() -- alignment, ignored for now + local offset = self:nextArg() local argument = table.remove(self.stack) local value = table.remove(self.stack) intutil.tole32(self.memory, offset + argument, value) end function wasmlib.VM:i32_const() - table.insert(self.stack, self:readarg32()) + table.insert(self.stack, self:nextArg()) end function wasmlib.VM:i64_const() - table.insert(self.stack, self:readarg64()) + table.insert(self.stack, self:nextArg()) end function wasmlib.VM:invoke(funcIndex) - local fr = frame.StackFrame:new(funcIndex) local f = self.functions[funcIndex] local sig = self.types[f.typeidx] - -- NOTE: sig.arguments is zero-indexed, so #sig.arguments is one less than the real length if f.import ~= nil then local args = {} - for i = 1, #sig.arguments + 1 do - args[#sig.arguments + 2 - i] = table.remove(self.stack) + 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 @@ -316,7 +309,8 @@ function wasmlib.VM:invoke(funcIndex) return end - for i = 1, #sig.arguments + 1 do + 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) @@ -328,24 +322,16 @@ end -- Get the index of the next occurence of the instruction with opcode `target` -- starting from `i` at the same level of nesting -function wasmlib.VM:findMatchingEndOrElse(i, depth) +local function findMatchingEndOrElse(body, i, depth) depth = depth or 0 - local curFrame = self:topFrame() - local body = self.functions[curFrame.funcIndex].body repeat local opcode = body[i] local length = constants.ilengths[opcode] if length == 0 then - if (opcode == constants.opcodes.OP_BLOCK) - or (opcode == constants.opcodes.OP_LOOP) - or (opcode == constants.opcodes.OP_IF) - then - i = self:findMatchingEndOrElse(i + 1, depth + 1) + (depth > 0 and 1 or 0) + if constants.blockOpcodes[opcode] ~= nil then + i = findMatchingEndOrElse(body, i + 1, depth + 1) + (depth > 0 and 1 or 0) elseif opcode == constants.opcodes.OP_ELSE then i = i + 1 - elseif opcode == constants.opcodes.OP_BR_TABLE then - local vecLen = intutil.fromle32(body, i + 1) - i = i + (1 + 4 * (vecLen + 1)) -- 1 byte opcode, (vecLen + 1) 4-byte label indices end else i = i + length @@ -355,30 +341,30 @@ function wasmlib.VM:findMatchingEndOrElse(i, depth) end function wasmlib.VM:block() - local curFrame = self:topFrame() + local curFrame = self:curFrame() local startIdx = curFrame.pc - 1 -- pc has already been incremented in step - self:readarg8() -- block result type, ignored for now - local endIdx = self:findMatchingEndOrElse(startIdx) + self:nextArg() -- block result type, ignored for now + local endIdx = findMatchingEndOrElse(self:curBody(), startIdx) table.insert(self.labelStack, endIdx) end function wasmlib.VM:loop() - local curFrame = self:topFrame() + local curFrame = self:curFrame() local startIdx = curFrame.pc - 1 -- pc has already been incremented in step - self:readarg8() -- block result type, ignored for now + self:nextArg() -- block result type, ignored for now table.insert(self.labelStack, startIdx) end function wasmlib.VM:_if() - self:readarg8() -- block result type, ignored for now + self:nextArg() -- block result type, ignored for now local c = table.remove(self.stack) - local curFrame = self:topFrame() + local curFrame = self:curFrame() local body = self.functions[curFrame.funcIndex].body - local endOrElse = self:findMatchingEndOrElse(curFrame.pc - 1) -- pc has already been incremented in step + local endOrElse = findMatchingEndOrElse(body, curFrame.pc - 1) -- pc has already been incremented in step if body[endOrElse] == constants.opcodes.OP_ELSE then local elseIdx = endOrElse - local endIdx = self:findMatchingEndOrElse(endOrElse) + local endIdx = findMatchingEndOrElse(body, endOrElse) if c == 0 then curFrame.pc = elseIdx end @@ -398,16 +384,16 @@ function wasmlib.VM:brInner(labelIdx) for _ = 1, labelIdx + 1 do table.remove(self.labelStack) end - self:topFrame().pc = label + self:curFrame().pc = label end function wasmlib.VM:br() - local labelIdx = self:readarg32() + local labelIdx = self:nextArg() self:brInner(labelIdx) end function wasmlib.VM:br_if() - local labelIdx = self:readarg32() + local labelIdx = self:nextArg() local c = table.remove(self.stack) if c ~= 0 then self:brInner(labelIdx) @@ -415,25 +401,19 @@ function wasmlib.VM:br_if() end function wasmlib.VM:br_table() - local curFrame = self:topFrame() - local body = self.functions[curFrame.funcIndex].body - local vLen = self:readarg32() + local table = self:nextArg() + local other = self:nextArg() local i = table.remove(self.stack) - if i < vLen then - local labelIdx = intutil.fromle32(body, curFrame.pc + i * 4) - self:brInner(labelIdx) + if i < #table then + self:brInner(table[i + 1]) else - local labelIdx = intutil.fromle32(body, curFrame.pc + vLen * 4) - self:brInner(labelIdx) + self:brInner(other) end end function wasmlib.VM:step() - local curFrame = self:topFrame() - local body = self.functions[curFrame.funcIndex].body - local opcode = body[curFrame.pc] - curFrame.pc = curFrame.pc + 1 + local opcode = self:nextArg() local c = constants.opcodes local optable = { @@ -565,4 +545,339 @@ function wasmlib.VM:step() 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) + -- 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) -- TODO + print("warning: skipping imports section") + local szlen, size = intutil.fromuleb128(bytes, idx) + return idx + szlen + size +end + +function wasmlib.VM:parseFunctions(bytes, idx) + local szlen, _ = intutil.fromuleb128(bytes, idx) + idx = idx + szlen + local nflen, numFuncs = intutil.fromuleb128(bytes, idx) + idx = idx + nflen + + for i = 1, numFuncs do -- TODO handle imported functions + local tilen, typeidx = intutil.fromuleb128(bytes, idx) + idx = idx + tilen + self.functions[i] = { typeidx = typeidx } + end + + return idx +end + +function wasmlib.VM:parseTables(bytes, idx) -- TODO + print("warning: skipping tables section") + local szlen, size = intutil.fromuleb128(bytes, idx) + return idx + szlen + size +end + +function wasmlib.VM:parseMemory(bytes, idx) + 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.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) + 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) -- TODO + print("warning: skipping exports section") + local szlen, size = intutil.fromuleb128(bytes, idx) + return idx + szlen + size +end + +function wasmlib.VM:parseStart(bytes, idx) -- TODO + print("warning: skipping start section") + local szlen, size = intutil.fromuleb128(bytes, idx) + return idx + szlen + size +end + +function wasmlib.VM:parseElements(bytes, idx) -- TODO + print("warning: skipping elements section") + local szlen, size = intutil.fromuleb128(bytes, idx) + return idx + szlen + size +end + +function wasmlib.VM:parseCode(bytes, idx) -- TODO + 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 + -- 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 _ = 1, #operands do + local operand = operands[i] + if operand == "b" then + local a = bytes[idx] + idx = idx + 1 + table.insert(body, a) + elseif operand == "u" or operand == "U" then + local alen, a = intutil.fromuleb128(bytes, idx) + 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 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(body, a) + end + 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 + end + + return idx +end + +function wasmlib.VM:parseData(bytes, idx) -- TODO + 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) + 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) + end +end + +function wasmlib.VM:instantiateFile(path) + self:instantiate(fileutil.readBytes(path)) +end + return wasmlib \ No newline at end of file