From: Brendan Hansen Date: Sun, 1 Mar 2020 16:53:29 +0000 (-0600) Subject: Fixed some bugs and added features X-Git-Url: https://git.brendanfh.com/?a=commitdiff_plain;h=9808ed64b8dda49c6c9691d81d6231dc72e457a1;p=wasm-analyzer.git Fixed some bugs and added features --- diff --git a/conf.lua b/conf.lua index 3521b8e..e01fe28 100644 --- a/conf.lua +++ b/conf.lua @@ -1,6 +1,53 @@ + +local COLOR_SCHEMES = { + DARK = { + keyword = { 0.7, 0.7, 1.0 }; + value = { 0.8, 1.0, 0.8 }; + jumppoint = { 1, 0.5, 0.5 }; + + dark_background = { 0.1, 0.1, 0.1 }; + background = { 0.15, 0.15, 0.15 }; + code = { 0.9, 0.9, 0.9 }; + + primary = { (0x0d / 0xff), (0x47 / 0xff), (0xa1 / 0xff) }; + primary_light = { (0x54 / 0xff), (0x72 / 0xff), (0xd3 / 0xff) }; + primary_dark = { (0x00 / 0xff), (0x21 / 0xff), (0x71 / 0xff) }; + secondary = { (0x00 / 0xff), (0x60 / 0xff), (0x64 / 0xff) }; + secondary_light = { (0x42 / 0xff), (0x8e / 0xff), (0x92 / 0xff) }; + secondary_dark = { (0x00 / 0xff), (0x36 / 0xff), (0x3a / 0xff) }; + primary_text = { 1, 1, 1 }; + secondary_text = { 1, 1, 1 }; + }; + + LIGHT = { + keyword = { 0, 0, 1 }; + value = { 0.0, 0.5, 0.0 }; + jumppoint = { 1, 0, 0 }; + + dark_background = { 0.8, 0.8, 0.8 }; + background = { 1, 1, 1 }; + code = { 0.0, 0.0, 0.0 }; + + primary = { (0x90 / 0xff), (0xca / 0xff), (0xf9 / 0xff) }; + primary_light = { (0xc3 / 0xff), (0xfd / 0xff), (0xff / 0xff) }; + primary_dark = { (0x5d / 0xff), (0x99 / 0xff), (0xc6 / 0xff) }; + secondary = { (0x80 / 0xff), (0xde / 0xff), (0xea / 0xff) }; + secondary_light = { (0xb4 / 0xff), (0xff / 0xff), (0xff / 0xff) }; + secondary_dark = { (0x4b / 0xff), (0xba / 0xff), (0xb8 / 0xff) }; + primary_text = { 0, 0, 0 }; + secondary_text = { 0, 0, 0 }; + } +} + function love.conf(t) t.window.title = "TBD" t.window.width = 1200 t.window.height = 900 t.window.resizable = false end + +return { + COLOR_SCHEMES = COLOR_SCHEMES; + + COLOR_SCHEME = COLOR_SCHEMES.LIGHT; +} diff --git a/decompile.lua b/decompile.lua deleted file mode 100644 index b08a4a1..0000000 --- a/decompile.lua +++ /dev/null @@ -1,740 +0,0 @@ --- Recursive decent parser for the WASM v1.0 binary format --- Brendan Hansen 2020 - --- Look in clib folder for C-based libraries -package.cpath = package.cpath .. [[;./clib/?.so]] - -import { - parse_helper = "parse_helper"; - pprint = "lualib.pprint"; - - build_str = "utils:build_str"; - random_str = "utils:random_str"; - Stack = "utils:Stack"; -} - -function parse_valtype(r) - local val = r:read_byte() - - local valtypes_map = { - [0x7f] = "i32"; - [0x7E] = "i64"; - [0x7D] = "f32"; - [0x7C] = "f64"; - } - - return valtypes_map[val] -end - -function parse_vector(tp) - return function(r) - local n = r:read_uint(32) - - local v = {} - for i=1, n do - table.insert(v, tp(r)) - end - - return v - end -end - -function parse_byte(r) - return r:read_byte() -end - -function parse_name(r) - local name = parse_vector(parse_byte)(r) - return name -end - -function parse_blocktype(r) - if r:peek_byte() == 0x40 then - r:read_byte() - return {} - else - return { parse_valtype(r) } - end -end - -function parse_functype(r) - assert(r:read_byte() == 0x60, "functype expected 0x60 as first byte") - - local params = parse_vector(parse_valtype)(r) - local results = parse_vector(parse_valtype)(r) - - return { param_types = params, result_types = results } -end - -function parse_limits(r) - local t = r:read_byte() - - local min = r:read_uint(32) - local max = nil - if t == 0x01 then - max = r:read_uint(32) - end - - return { min = min, max = max } -end - -function parse_memtype(r) - local lims = parse_limits(r) - - return lims -end - -function parse_tabletype(r) - local elemtype = parse_elemtype(r) - local limits = parse_limits(r) - - return { lim = limits, et = elemtype } -end - -function parse_elemtype(r) - assert(r:read_byte() == 0x70, "elemtype should be 0x70") - return "funcref" -end - -function parse_globaltype(r) - local valtype = parse_valtype(r) - local ismut = parse_mut(r) == 0x01 - - return { t = valtype, m = ismut } -end - -function parse_mut(r) - local v = r:read_byte() - assert(v == 0x00 or v == 0x01, "mut should be 0x00 or 0x01") - return v -end - -function parse_instr(r) - local instr = r:read_byte() - - return match(instr) { - [0x00] = function() return { "unreachable" } end; - [0x01] = function() return { "nop" } end; - [0x02] = function() - local rt = parse_blocktype(r) - local instrs = {} - local name = random_str(8) - - local block = { "block", label = name, rt = rt } - r.label_stack:push(block) - - while true do - if r:peek_byte() == 0x0B then - r:read_byte() - break - else - table.insert(instrs, parse_instr(r)) - end - end - - r.label_stack:pop() - - block.instrs = instrs - return block - end; - [0x03] = function() - local rt = parse_blocktype(r) - local instrs = {} - local name = random_str(8) - - local block = { "loop", label = name, rt = rt } - r.label_stack:push(block) - - while true do - if r:peek_byte() == 0x0B then - r:read_byte() - break - else - table.insert(instrs, parse_instr(r)) - end - end - - r.label_stack:pop() - - block.instrs = instrs - return block - end; - [0x04] = function() - local rt = parse_blocktype(r) - local instrs = {} - local else_instrs = {} - local inelse = false - - while true do - local peek = r:peek_byte() - if peek == 0x0B then - r:read_byte() - break - elseif peek == 0x05 then - r:read_byte() - inelse = true - else - if not inelse then - table.insert(instrs, parse_instr(r)) - else - table.insert(else_instrs, parse_instr(r)) - end - end - end - - return { "if", rt = rt, instrs = instrs, else_instrs = else_instrs } - end; - - [0x0C] = function() return { "br", x = parse_labelidx(r) } end; - [0x0D] = function() return { "br_if", x = parse_labelidx(r) } end; - [0x0E] = function() - local labels = parse_vector(parse_labelidx)(r) - local labeln = parse_labelidx(r) - - return { "br_table", labels = labels, labeln = labeln } - end; - - [0x0F] = function() return { "return" } end; - - [0x10] = function() return { "call", x = parse_funcidx(r) } end; - [0x11] = function() - local x = parse_typeidx(r) - assert(r:read_byte() ~= 0x00, "call_indirect expects 0x00 at end") - return { "call_indirect", x = x } - end; - - [0x1A] = function() return { "drop" } end; - [0x1B] = function() return { "select" } end; - - [0x20] = function() return { "local.get", x = parse_localidx(r) } end; - [0x21] = function() return { "local.set", x = parse_localidx(r) } end; - [0x22] = function() return { "local.tee", x = parse_localidx(r) } end; - [0x23] = function() return { "global.get", x = parse_globalidx(r) } end; - [0x24] = function() return { "global.set", x = parse_globalidx(r) } end; - - [0x28] = function() return { "i32.load", x = parse_memarg(r) } end; - [0x29] = function() return { "i64.load", x = parse_memarg(r) } end; - [0x2A] = function() return { "f32.load", x = parse_memarg(r) } end; - [0x2B] = function() return { "f64.load", x = parse_memarg(r) } end; - [0x2C] = function() return { "i32.load8_s", x = parse_memarg(r) } end; - [0x2D] = function() return { "i32.load8_u", x = parse_memarg(r) } end; - [0x2E] = function() return { "i32.load16_s", x = parse_memarg(r) } end; - [0x2F] = function() return { "i32.load16_u", x = parse_memarg(r) } end; - [0x30] = function() return { "i64.load8_s", x = parse_memarg(r) } end; - [0x31] = function() return { "i64.load8_u", x = parse_memarg(r) } end; - [0x32] = function() return { "i64.load16_s", x = parse_memarg(r) } end; - [0x33] = function() return { "i64.load16_u", x = parse_memarg(r) } end; - [0x34] = function() return { "i64.load32_s", x = parse_memarg(r) } end; - [0x35] = function() return { "i64.load32_u", x = parse_memarg(r) } end; - [0x36] = function() return { "i32.store", x = parse_memarg(r) } end; - [0x37] = function() return { "i64.store", x = parse_memarg(r) } end; - [0x38] = function() return { "f32.store", x = parse_memarg(r) } end; - [0x39] = function() return { "f64.store", x = parse_memarg(r) } end; - [0x3A] = function() return { "i32.store8", x = parse_memarg(r) } end; - [0x3B] = function() return { "i32.store16", x = parse_memarg(r) } end; - [0x3C] = function() return { "i64.store8", x = parse_memarg(r) } end; - [0x3D] = function() return { "i64.store16", x = parse_memarg(r) } end; - [0x3E] = function() return { "i64.store32", x = parse_memarg(r) } end; - [0x3F] = function() - assert(r:read_byte() == 0x00, "memory.size expects 0x00") - return { "memory.size" } - end; - [0x40] = function() - assert(r:read_byte() == 0x00, "memory.grow expects 0x00") - return { "memory.grow" } - end; - - [0x41] = function() return { "i32.const", x = r:read_sint(32) } end; - [0x42] = function() return { "i64.const", x = r:read_sint(64) } end; - [0x43] = function() return { "f32.const", x = r:read_float(32) } end; - [0x44] = function() return { "f64.const", x = r:read_float(64) } end; - - [0x45] = function() return { "i32.eqz" } end; - [0x46] = function() return { "i32.eq" } end; - [0x47] = function() return { "i32.ne" } end; - [0x48] = function() return { "i32.lt_s" } end; - [0x49] = function() return { "i32.lt_u" } end; - [0x4A] = function() return { "i32.gt_s" } end; - [0x4B] = function() return { "i32.gt_u" } end; - [0x4C] = function() return { "i32.le_s" } end; - [0x4D] = function() return { "i32.le_u" } end; - [0x4E] = function() return { "i32.ge_s" } end; - [0x4F] = function() return { "i32.ge_u" } end; - [0x50] = function() return { "i64.eqz" } end; - [0x51] = function() return { "i64.eq" } end; - [0x52] = function() return { "i64.ne" } end; - [0x53] = function() return { "i64.lt_s" } end; - [0x54] = function() return { "i64.lt_u" } end; - [0x55] = function() return { "i64.gt_s" } end; - [0x56] = function() return { "i64.gt_u" } end; - [0x57] = function() return { "i64.le_s" } end; - [0x58] = function() return { "i64.le_u" } end; - [0x59] = function() return { "i64.ge_s" } end; - [0x5A] = function() return { "i64.ge_u" } end; - [0x5B] = function() return { "f32.eq" } end; - [0x5C] = function() return { "f32.ne" } end; - [0x5D] = function() return { "f32.lt" } end; - [0x5E] = function() return { "f32.gt" } end; - [0x5F] = function() return { "f32.le" } end; - [0x60] = function() return { "f32.ge" } end; - [0x61] = function() return { "f64.eq" } end; - [0x62] = function() return { "f64.ne" } end; - [0x63] = function() return { "f64.lt" } end; - [0x64] = function() return { "f64.gt" } end; - [0x65] = function() return { "f64.le" } end; - [0x66] = function() return { "f64.ge" } end; - [0x67] = function() return { "i32.clz" } end; - [0x68] = function() return { "i32.ctz" } end; - [0x69] = function() return { "i32.popcnt" } end; - [0x6A] = function() return { "i32.add" } end; - [0x6B] = function() return { "i32.sub" } end; - [0x6C] = function() return { "i32.mul" } end; - [0x6D] = function() return { "i32.div_s" } end; - [0x6E] = function() return { "i32.div_u" } end; - [0x6F] = function() return { "i32.rem_s" } end; - [0x70] = function() return { "i32.rem_u" } end; - [0x71] = function() return { "i32.and" } end; - [0x72] = function() return { "i32.or" } end; - [0x73] = function() return { "i32.xor" } end; - [0x74] = function() return { "i32.shl" } end; - [0x75] = function() return { "i32.shr_s" } end; - [0x76] = function() return { "i32.shr_u" } end; - [0x77] = function() return { "i32.rotl" } end; - [0x78] = function() return { "i32.rotr" } end; - [0x79] = function() return { "i64.clz" } end; - [0x7A] = function() return { "i64.ctz" } end; - [0x7B] = function() return { "i64.popcnt" } end; - [0x7C] = function() return { "i64.add" } end; - [0x7D] = function() return { "i64.sub" } end; - [0x7E] = function() return { "i64.mul" } end; - [0x7F] = function() return { "i64.div_s" } end; - [0x80] = function() return { "i64.div_u" } end; - [0x81] = function() return { "i64.rem_s" } end; - [0x82] = function() return { "i64.rem_u" } end; - [0x83] = function() return { "i64.and" } end; - [0x84] = function() return { "i64.or" } end; - [0x85] = function() return { "i64.xor" } end; - [0x86] = function() return { "i64.shl" } end; - [0x87] = function() return { "i64.shr_s" } end; - [0x88] = function() return { "i64.shr_u" } end; - [0x89] = function() return { "i64.rotl" } end; - [0x8A] = function() return { "i64.rotr" } end; - [0x8B] = function() return { "f32.abs" } end; - [0x8C] = function() return { "f32.neg" } end; - [0x8D] = function() return { "f32.ceil" } end; - [0x8E] = function() return { "f32.floor" } end; - [0x8F] = function() return { "f32.trunc" } end; - [0x90] = function() return { "f32.nearest" } end; - [0x91] = function() return { "f32.sqrt" } end; - [0x92] = function() return { "f32.add" } end; - [0x93] = function() return { "f32.sub" } end; - [0x94] = function() return { "f32.mul" } end; - [0x95] = function() return { "f32.div" } end; - [0x96] = function() return { "f32.min" } end; - [0x97] = function() return { "f32.max" } end; - [0x98] = function() return { "f32.copysign" } end; - [0x99] = function() return { "f64.abs" } end; - [0x9A] = function() return { "f64.neg" } end; - [0x9B] = function() return { "f64.ceil" } end; - [0x9C] = function() return { "f64.floor" } end; - [0x9D] = function() return { "f64.trunc" } end; - [0x9E] = function() return { "f64.nearest" } end; - [0x9F] = function() return { "f64.sqrt" } end; - [0xA0] = function() return { "f64.add" } end; - [0xA1] = function() return { "f64.sub" } end; - [0xA2] = function() return { "f64.mul" } end; - [0xA3] = function() return { "f64.div" } end; - [0xA4] = function() return { "f64.min" } end; - [0xA5] = function() return { "f64.max" } end; - [0xA6] = function() return { "f64.copysign" } end; - - [0xA7] = function() return { "i32.wrap_i64" } end; - [0xA8] = function() return { "i32.trunc_f32_s" } end; - [0xA9] = function() return { "i32.trunc_f32_u" } end; - [0xAA] = function() return { "i32.trunc_f64_s" } end; - [0xAB] = function() return { "i32.trunc_f64_u" } end; - [0xAC] = function() return { "i64.extend_i32_s" } end; - [0xAD] = function() return { "i64.extend_i32_u" } end; - [0xAE] = function() return { "i64.trunc_f32_s" } end; - [0xAF] = function() return { "i64.trunc_f32_u" } end; - [0xB0] = function() return { "i64.trunc_f64_s" } end; - [0xB1] = function() return { "i64.trunc_f64_u" } end; - [0xB2] = function() return { "f32.convert_i32_s" } end; - [0xB3] = function() return { "f32.convert_i32_u" } end; - [0xB4] = function() return { "f32.convert_i64_s" } end; - [0xB5] = function() return { "f32.convert_i64_u" } end; - [0xB6] = function() return { "f32.demote_f64" } end; - [0xB7] = function() return { "f64.convert_i32_s" } end; - [0xB8] = function() return { "f64.convert_i32_u" } end; - [0xB9] = function() return { "f64.convert_i64_s" } end; - [0xBA] = function() return { "f64.convert_i64_u" } end; - [0xBB] = function() return { "f64.promote_f32" } end; - [0xBC] = function() return { "i32.reinterpret_f32" } end; - [0xBD] = function() return { "i64.reinterpret_f64" } end; - [0xBE] = function() return { "f32.reinterpret_i32" } end; - [0xBF] = function() return { "f64.reinterpret_i64" } end; - } -end - -function parse_memarg(r) - local a = r:read_uint(32) - local o = r:read_uint(32) - - return { "memarg"; align = a; offset = o } -end - -function parse_expr(r) - local instrs = {} - while true do - if r:peek_byte() == 0x0B then - r:read_byte() - break - else - table.insert(instrs, parse_instr(r)) - end - end - - return instrs -end - -function parse_typeidx(r) return r:read_uint(32) end -function parse_funcidx(r) return r:read_uint(32) end -function parse_tableidx(r) return r:read_uint(32) end -function parse_memidx(r) return r:read_uint(32) end -function parse_globalidx(r) return r:read_uint(32) end -function parse_localidx(r) return r:read_uint(32) end -function parse_labelidx(r) - local idx = r:read_uint(32) - local block = r.label_stack:at(idx) - return { "labelidx", labelidx = idx, block = block } -end - -function parse_section(r, expectN, B) - if r:peek_byte() ~= expectN then return end - - local N = r:read_byte() - local size = r:read_uint(32) - local cont = B(r) - - return { "section", contents = cont, size = size } -end - -function parse_customsec(r) - local csect = parse_section(r, 0, parse_custom) - if not csect then return nil end - - for i=1, csect.size do - -- Discard csect.size bytes - r:read_byte() - end - return csect -end - -function parse_custom(r) - local name = parse_name(r) - - return { "custom", name = name } -end - -function parse_typesec(r) - return parse_section(r, 1, parse_vector(parse_functype)) -end - -function parse_importsec(r) - return parse_section(r, 2, parse_vector(parse_import)) -end - -function parse_import(r) - local mod = parse_name(r) - local nm = parse_name(r) - local d = parse_importdesc(r) - - return { "import", mod = mod, name = nm, desc = d } -end - -function parse_importdesc(r) - local t = r:read_byte() - if t == 0x00 then return { "func", x = parse_typeidx(r) } - elseif t == 0x01 then return { "table", tt = parse_tabletype(r) } - elseif t == 0x02 then return { "mem", mt = parse_memtype(r) } - elseif t == 0x03 then return { "global", gt = parse_globaltype(r) } - end - - error("bad importdesc") -end - -function parse_funcsec(r) - return parse_section(r, 3, parse_vector(parse_typeidx)) -end - -function parse_tablesec(r) - return parse_section(r, 4, parse_vector(parse_table)) -end - -function parse_table(r) - local tt = parse_tabletype(r) - return { "table", type_ = tt } -end - -function parse_memsec(r) - return parse_section(r, 5, parse_vector(parse_mem)) -end - -function parse_mem(r) - local mt = parse_memtype(r) - return { "mem", type_ = mt } -end - -function parse_globalsec(r) - return parse_section(r, 6, parse_vector(parse_global)) -end - -function parse_global(r) - local gt = parse_globaltype(r) - local e = parse_expr(r) - return { "global", type_ = gt, init = e } -end - -function parse_exportsec(r) - return parse_section(r, 7, parse_vector(parse_export)) -end - -function parse_export(r) - local nm = parse_name(r) - local d = parse_exportdesc(r) - return { "export", name = nm, desc = d } -end - -function parse_exportdesc(r) - local t = r:read_byte() - if t == 0x00 then return { "func", x = parse_typeidx(r) } - elseif t == 0x01 then return { "table", tt = parse_tableidx(r) } - elseif t == 0x02 then return { "mem", mt = parse_memidx(r) } - elseif t == 0x03 then return { "global", gt = parse_globalidx(r) } - end - - error("bad exportdesc: ", t) -end - -function parse_startsec(r) - return parse_section(r, 8, parse_start) -end - -function parse_start(r) - local x = parse_funcidx(r) - return { "start", func = x } -end - -function parse_elemsec(r) - return parse_section(r, 9, parse_vector(parse_elem)) -end - -function parse_elem(r) - local x = parse_tableidx(r) - local e = parse_expr(r) - local y = parse_vector(parse_funcidx)(r) - - return { "elem", table = x, offset = e, init = y } -end - -function parse_codesec(r) - return parse_section(r, 10, parse_vector(parse_code)) -end - -function parse_code(r) - local size = r:read_uint(32) - local code = parse_func(r) - return code -end - -function parse_func(r) - local t = parse_vector(parse_locals)(r) - local e = parse_expr(r) - - local localidx = 0 - local locals = {} - for _, v in ipairs(t) do - for _, l in ipairs(v) do - l.localidx = localidx - table.insert(locals, l) - localidx = localidx + 1 - end - end - - return { "func", locals = locals, body = e } -end - -function parse_locals(r) - local n = r:read_uint(32) - local t = parse_valtype(r) - - --TODO: Make a list of values with names like local0, local1, ... - - local locals = {} - for i = 0, n - 1 do - table.insert(locals, { - name = "local" .. i, - type_ = t, - localidx = i - }) - end - - return locals -end - -function parse_datasec(r) - return parse_section(r, 11, parse_vector(parse_data)) -end - -function parse_data(r) - local x = parse_memidx(r) - local e = parse_expr(r) - local b = parse_vector(parse_byte)(r) - - return { "data", data = x, offset = e, init = b } -end - -function parse_magic(r) - assert(r:read_byte() == 0x00, "magic string is wrong") - assert(r:read_byte() == 0x61, "magic string is wrong") - assert(r:read_byte() == 0x73, "magic string is wrong") - assert(r:read_byte() == 0x6D, "magic string is wrong") -end - -function parse_version(r) - assert(r:read_byte() == 0x01, "version is wrong") - assert(r:read_byte() == 0x00, "version is wrong") - assert(r:read_byte() == 0x00, "version is wrong") - assert(r:read_byte() == 0x00, "version is wrong") -end - -function parse_module(r) - parse_magic(r) - parse_version(r) - - local functypes = parse_typesec(r) - local imports = parse_importsec(r) - local typeidxs = parse_funcsec(r) - local tables = parse_tablesec(r) - local mems = parse_memsec(r) - local globals = parse_globalsec(r) - local exports = parse_exportsec(r) - local start = parse_startsec(r) - local elems = parse_elemsec(r) - local codes = parse_codesec(r) - local data = parse_datasec(r) - - local funcs = {} - local funcidx = 0 - - if imports then - for k, v in ipairs(imports.contents) do - if v.desc[1] == "func" then - funcs[funcidx] = { - name = build_str(v.mod) .. "." .. build_str(v.name); - funcidx = funcidx; - type_ = functypes.contents[v.desc.x + 1]; - } - funcidx = funcidx + 1 - end - end - end - - if codes then - for i=1, #codes.contents do - local locals = codes.contents[i].locals; - local type_ = functypes.contents[typeidxs.contents[i] + 1]; - local param_types = type_.param_types; - - local new_locals = {} - local new_local_idx = 0 - for _, p in ipairs(param_types) do - table.insert(new_locals, { - name = "param" .. new_local_idx, - type_ = p; - localidx = new_local_idx - }) - new_local_idx = new_local_idx + 1 - end - for _, l in ipairs(locals) do - l.localidx = new_local_idx - table.insert(new_locals, l) - new_local_idx = new_local_idx + 1 - end - - funcs[funcidx] = { - funcidx = funcidx; - name = "func" .. funcidx; - type_ = functypes.contents[typeidxs.contents[i] + 1]; - locals = new_locals; - body = codes.contents[i].body - } - funcidx = funcidx + 1 - end - end - - return { - "module"; - types = functypes; - tables = tables; - mems = mems; - globals = globas; - exports = exports; - start = start; - elems = elems; - data = data; - imports = imports; - funcs = funcs; - } -end - - --- Reader util class used for interfacing --- with the parse_helper c library -class "Parser" { - init = function(self, filename) - self.wasm_file = parse_helper.open_file(filename) - self.label_stack = Stack() - end; - - read_byte = function(self) - return parse_helper.read_byte(self.wasm_file) - end; - - peek_byte = function(self) - return parse_helper.peek_byte(self.wasm_file) - end; - - -- NOTE: N is unused - read_uint = function(self, N) - return parse_helper.parse_uleb128(self.wasm_file) - end; - - read_sint = function(self, N) - return parse_helper.parse_sleb128(self.wasm_file, N) - end; - - read_float = function(self, N) - if N >= 32 then - self:read_byte() - self:read_byte() - self:read_byte() - self:read_byte() - end - if N >= 64 then - self:read_byte() - self:read_byte() - self:read_byte() - self:read_byte() - end - return 0.0; - end; -} - -function decompile(filepath) - local reader = Parser(filepath) - - return parse_module(reader) -end - -return module { decompile } diff --git a/docs/todo b/docs/todo index f3fb67e..52e7488 100644 --- a/docs/todo +++ b/docs/todo @@ -1,2 +1,6 @@ -After experimenting with using Lua for the parser, I think it will be better to write the parser in C / C++ -and then have a function that converts all the data to a lua table that will be used for visualization / examination. +[X] Rewrite function text builder into a more abstract system +[ ] Implement arrows from call sites to other functions +[ ] Implement internal arrows from "br" to destination +[ ] Implement right click context menu + * Own region dismissed on mouse leave + * Starts centered on mouse diff --git a/main.lua b/main.lua index 67be148..81c37b3 100644 --- a/main.lua +++ b/main.lua @@ -2,179 +2,49 @@ require "lualib.oop" import { - decompile = "decompile:"; - - Ui = "ui:Ui"; - UiRegion = "ui:UiRegion"; - ScrollingUiRegion = "ui:ScrollingUiRegion"; - DraggableRect = "ui:DraggableRect"; - - render_text = "ui:render_text"; - - scissor_points = "utils:scissor_points"; pprint = "lualib.pprint"; -} - -TEXT_COLORS = { - keyword = { 0.7, 0.7, 1.0 }; - value = { 0.8, 1.0, 0.8 }; - text = { 0.9, 0.9, 0.9 }; - jumppoint = { 1, 0.5, 0.5 }; - - header_background = { 0.1, 0.1, 0.1 }; - background = { 0.15, 0.15, 0.15 }; -} -function build_func_header_text(func, mod) - local text = {} + decompile = "src.wasm.decompile:"; + WasmCodeGenerator = "src.wasm.text:"; - table.insert(text, { text = "[" .. func.funcidx .. "] ", color = TEXT_COLORS.text }) - table.insert(text, { text = func.name, color = TEXT_COLORS.keyword }) - table.insert(text, { text = ": ", color = TEXT_COLORS.text }) - - local type_ = func.type_ - for _, t in ipairs(type_.param_types) do - table.insert(text, { text = t .. " " }) - end - - if #type_.param_types == 0 then - table.insert(text, { text = "void " }) - end - - table.insert(text, { text = "-> " }) - - for _, t in ipairs(type_.result_types) do - table.insert(text, { text = t .. " " }) - end + Ui = "src.ui.ui:Ui"; + UiRegion = "src.ui.ui:UiRegion"; + ScrollingUiRegion = "src.ui.ui:ScrollingUiRegion"; + DraggableRect = "src.ui.ui:DraggableRect"; + render_text = "src.ui.ui:render_text"; - if #type_.result_types == 0 then - table.insert(text, { text = "void" }) - end - - table.insert(text, { newline = true }) - - return text -end - -local line = 1 - -function add_line_number(textlist, newline) - if newline == nil then newline = true end - table.insert(textlist, { newline = newline }) - table.insert(textlist, { text = ("%4d"):format(line), color = TEXT_COLORS.text, background = TEXT_COLORS.header_background, post_indent = true }) - table.insert(textlist, { text = " ", post_indent = true }) - line = line + 1 -end - -function instr_to_text(textlist, instr, func, mod) - if instr[1] == "block" then - for _, ins in ipairs(instr.instrs) do - instr_to_text(textlist, ins, func, mod) - add_line_number(textlist) - end - table.insert(textlist, { text = "label ", color = TEXT_COLORS.jumppoint }) - table.insert(textlist, { text = instr.label, color = TEXT_COLORS.value }) - - - elseif instr[1] == "loop" then - table.insert(textlist, { text = "loop ", color = TEXT_COLORS.jumppoint }) - table.insert(textlist, { text = instr.label, color = TEXT_COLORS.value, change_indent = 1 }) - - for _, ins in ipairs(instr.instrs) do - add_line_number(textlist) - instr_to_text(textlist, ins, func, mod) - end - table.insert(textlist, { change_indent = -1 }) + scissor_points = "src.utils:scissor_points"; - elseif instr[1] == "if" then - table.insert(textlist, { text = "if", color = TEXT_COLORS.jumppoint, change_indent = 1 }) - for _, ins in ipairs(instr.instrs) do - add_line_number(textlist) - instr_to_text(textlist, ins, func, mod) - end - table.insert(textlist, { change_indent = -1 }) - - if #instr.else_instrs > 0 then - add_line_number(textlist) - table.insert(textlist, { text = "else", color = TEXT_COLORS.jumppoint, change_indent = 1 }) - for _, ins in ipairs(instr.else_instrs) do - add_line_number(textlist) - instr_to_text(textlist, ins, func, mod) - end - table.insert(textlist, { change_indent = -1 }) - end - - elseif instr[1] == "call" then - table.insert(textlist, { text = instr[1] .. " ", color = TEXT_COLORS.keyword }) - table.insert(textlist, { text = mod.funcs[instr.x].name, color = TEXT_COLORS.value }) - - elseif instr[1]:match("local") then - table.insert(textlist, { text = instr[1] .. " ", color = TEXT_COLORS.keyword }) - table.insert(textlist, { text = func.locals[instr.x + 1].name, color = TEXT_COLORS.value }) - - else - table.insert(textlist, { text = instr[1] .. " ", color = TEXT_COLORS.keyword }) - if instr.x then - if type(instr.x) == "table" then - if instr.x[1] == "memarg" then - table.insert(textlist, { - text = ("align=0x%02x offset=0x%04x"):format(instr.x.align, instr.x.offset); - color = TEXT_COLORS.value - }) - end - - if instr.x[1] == "labelidx" then - table.insert(textlist, { text = instr.x.block.label .. " ", color = TEXT_COLORS.value }) - table.insert(textlist, { text = ("[0x%02x]"):format(instr.x.labelidx), color = TEXT_COLORS.text }) - end - else - table.insert(textlist, { text = tostring(instr.x), color = TEXT_COLORS.value }) - end - end - end - - return line -end - -function build_func_body_text(func, mod) - local text = {} - line = 1 - - table.insert(text, { text = " Locals:", color = TEXT_COLORS.keyword, newline = true, change_indent = 2 }) - for _, loc in ipairs(func.locals) do - table.insert(text, { text = loc.type_ .. " " .. loc.name, color = TEXT_COLORS.text , newline = true }) - end - - if #func.locals == 0 then - table.insert(text, { text = "no locals", color = TEXT_COLORS.text, newline = true }) - end - - table.insert(text, { newline = true, change_indent = -2 }) - table.insert(text, { text = " Body:", color = TEXT_COLORS.keyword }) - - for _, instr in ipairs(func.body) do - add_line_number(text) - instr_to_text(text, instr, func, mod) - end - - return text -end + COLORS = "conf:COLOR_SCHEME"; +} class "FunctionBlock" [DraggableRect] { init = function(self, func, x, y) - DraggableRect.init(self, x, y, 400, 400) + DraggableRect.init(self, x, y, 400, 22) - self.header_text = build_func_header_text(func, mod) + WasmCodeGenerator:setup(func, mod, COLORS) + self.header_text = WasmCodeGenerator:build_header() if func.body then - self.body_text = build_func_body_text(func, mod) + self.body_text = WasmCodeGenerator:build_body() else - self.rect.h = 48 self.body_text = { - { text = "imported function", color = TEXT_COLORS.text } + { text = "imported function", color = COLORS.code } } end + self.scroll = 0 + self.body_visible = false + self.old_height = 400 + end; + + onclick = function(self, button, x, y) + DraggableRect.onclick(self, button, x, y) + + if y <= 24 and button == 2 then + self.body_visible = not self.body_visible + self.rect.h = self.body_visible and self.old_height or 22 + end end; onwheel = function(self, _, dy) @@ -187,22 +57,25 @@ class "FunctionBlock" [DraggableRect] { if y >= 24 and button == 2 then self.rect.w = math.max(100, x) - self.rect.h = math.max(22, y) + if self.body_visible then + self.rect.h = math.max(22, y) + self.old_height = self.rect.h + end end end; draw = function(self) love.graphics.setColor(0, 0, 0) love.graphics.rectangle("fill", 0, 0, self.rect.w, self.rect.h) - love.graphics.setColor(self.selected and TEXT_COLORS.background or TEXT_COLORS.header_background) + love.graphics.setColor(self.selected and COLORS.background or COLORS.dark_background) love.graphics.rectangle("fill", 2, 2, self.rect.w - 4, self.rect.h - 4) - love.graphics.setColor(TEXT_COLORS.header_background) + love.graphics.setColor(COLORS.dark_background) love.graphics.rectangle("fill", 2, 2, self.rect.w - 4, 18) love.graphics.intersectScissor(scissor_points(0, 0, self.rect.w, self.rect.h)) local x, y = render_text(2, 2, self.header_text) - if self.body_text then + if self.body_visible and self.body_text then love.graphics.intersectScissor(scissor_points(2, y - 1, self.rect.w - 4, self.rect.h - 22)) render_text(2, y + 4 - self.scroll, self.body_text) end @@ -221,14 +94,14 @@ function love.load() ui_region.rect.y = 0 ui_region.rect.w = 1200 ui_region.rect.h = 900 - ui_region.background_color = { 0.1, 0.1, 0.1 } - ui_region.line_color = { 0.15, 0.15, 0.2 } + ui_region.background_color = COLORS.background + ui_region.line_color = COLORS.dark_background local x = 0 local y = 0 for _, func in pairs(mod.funcs) do local block = FunctionBlock(func, x, y) - block.order = 600 - y + block.order = #mod.funcs * 20 + 1 - y ui_region:add_object(block) y = y + 20 end diff --git a/src/ui/ui.lua b/src/ui/ui.lua new file mode 100644 index 0000000..3b6b7c4 --- /dev/null +++ b/src/ui/ui.lua @@ -0,0 +1,461 @@ +import { + Rectangle = "src.utils:Rectangle"; + uuid = "src.utils:uuidv4"; + revipairs = "src.utils:revipairs"; + scissor_points = "src.utils:scissor_points"; +} + +class "Ui" { + init = function(self) + self.regions = {} + self.active_region = nil + end; + + add_region = function(self, region) + table.insert(self.regions, region) + table.sort(self.regions) + end; + + onmousedown = function(self, button, x, y) + local region = nil + for _, r in revipairs(self.regions) do + if r.rect:contains(x, y) then + region = r + break + end + end + + if region ~= nil then + region:onmousedown(button, x - region.rect.x, y - region.rect.y) + end + end; + + onmouseup = function(self, button, x, y) + local region = nil + for _, r in revipairs(self.regions) do + if r.rect:contains(x, y) then + region = r + break + end + end + + if region ~= nil then + region:onmouseup(button, x - region.rect.x, y - region.rect.y) + end + end; + + onmousemove = function(self, x, y, dx, dy) + local region = nil + for _, r in revipairs(self.regions) do + if r.rect:contains(x, y) then + region = r + break + end + end + + if region ~= nil then + if region ~= self.active_region then + if self.active_region ~= nil then + self.active_region:onmouseleave() + end + + region:onmouseenter() + end + region:onmousemove(x - region.rect.x, y - region.rect.y, dx, dy) + end + + self.active_region = region + end; + + onmousewheel = function(self, dx, dy) + if not self.active_region then return end + self.active_region:onmousewheel(dx, dy) + end; + + onkeydown = function(self, key) + if not self.active_region then return end + self.active_region:onkeydown(key) + end; + + onkeyup = function(self, key) + if not self.active_region then return end + self.active_region:onkeyup(key) + end; + + onresize = function(self, new_width, new_height) + end; + + update = function(self, dt) + for _, region in ipairs(self.regions) do + region:update(dt); + end + end; + + draw = function(self) + for _, region in ipairs(self.regions) do + love.graphics.push() + love.graphics.setScissor(scissor_points(region.rect.x, region.rect.y, region.rect.w, region.rect.h)) + love.graphics.translate(region.rect.x, region.rect.y) + region:draw() + love.graphics.pop() + end + end; +} + +class "UiRegion" { + init = function(self) + self.active = false + + self.selected_object = nil + self.objects = {} + + self.rect = Rectangle(0, 0, 0, 0) + self.background_color = { 0, 0, 0 } + self.layer = 0 + + -- Internals + self.is_mouse_down = false + self.fire_onclick = false + end; + + add_object = function(self, obj) + table.insert(self.objects, obj) + end; + + -- Used for sorting in the Ui + __lt = function(self, other) + return self.layer < other.layer + end; + + _obj_rect = function(self, obj) + return obj.rect + end; + + onmousedown = function(self, button, x, y) + self.is_mouse_down = button + self.fire_onclick = true + local new_selected = nil + + for _, obj in revipairs(self.objects) do + if self:_obj_rect(obj):contains(x, y) then + new_selected = obj + break + end + end + + if self.selected_object ~= new_selected then + if self.selected_object ~= nil then + self.selected_object:onunselect() + end + + self.selected_object = new_selected + + if self.selected_object ~= nil then + self.selected_object.order = 0 + + for _, obj in ipairs(self.objects) do + if obj ~= self.selected_obj then + obj.order = obj.order + 1 + end + end + + table.sort(self.objects, function(a, b) + return a.order > b.order + end) + + self.selected_object:onselect() + end + end + end; + + onmouseup = function(self, button, x, y) + self.is_mouse_down = false + + if self.selected_object ~= nil and self.fire_onclick then + local obj_rect = self:_obj_rect(self.selected_object) + self.selected_object:onclick(button, x - obj_rect.x, y - obj_rect.y) + end + end; + onmousemove = function(self, x, y, dx, dy) + if self.is_mouse_down and self.selected_object ~= nil then + local obj_rect = self:_obj_rect(self.selected_object) + self.selected_object:ondrag(self.is_mouse_down, x - obj_rect.x, y - obj_rect.y, dx, dy) + self.fire_onclick = false + end + end; + onmouseenter = function(self) + self.active = true + end; + + onmouseleave = function(self) + self.active = false; + self.is_mouse_down = false + self.fire_onclick = false + self.selected_object = nil + end; + + onmousewheel = function(self, dx, dy) + if self.selected_object ~= nil then + self.selected_object:onwheel(dx, dy) + end + end; + + onkeydown = function(self, key) end; + onkeyup = function(self, key) end; + + update = function(self, dt) + for _, obj in ipairs(self.objects) do + obj:update(dt) + end + end; + + draw = function(self) + if self.background_color ~= nil then + love.graphics.setColor(self.background_color) + love.graphics.rectangle("fill", 0, 0, self.rect.w, self.rect.h) + end + + for _, obj in ipairs(self.objects) do + love.graphics.push() + love.graphics.translate(obj.rect.x, obj.rect.y) + obj:draw() + love.graphics.setScissor() + love.graphics.pop() + end + end; +} + +class "ScrollingUiRegion" [UiRegion] { + init = function(self) + UiRegion.init(self) + + self.offset = { x = 0; y = 0; } + self.line_color = { 0, 0, 0 } + + self.zoom = 1 + end; + + _obj_rect = function(self, obj) + return obj.rect + end; + + _transform_mouse = function(self, x, y) + return + ((x + self.rect.x) - self.rect.w / 2) / self.zoom + self.rect.w / 2 - self.offset.x - self.rect.x, + ((y + self.rect.y) - self.rect.h / 2) / self.zoom + self.rect.h / 2 - self.offset.y - self.rect.y + end; + + onmousedown = function(self, button, x, y) + local mx, my = self:_transform_mouse(x, y) + UiRegion.onmousedown(self, button, mx, my) + end; + + onmouseup = function(self, button, x, y) + local mx, my = self:_transform_mouse(x, y) + UiRegion.onmouseup(self, button, mx, my) + end; + + onmousemove = function(self, x, y, dx, dy) + local mx, my = self:_transform_mouse(x, y) + UiRegion.onmousemove(self, mx, my, dx / self.zoom, dy / self.zoom) + + if self.is_mouse_down and self.selected_object == nil then + self.offset.x = self.offset.x + dx / self.zoom + self.offset.y = self.offset.y + dy / self.zoom + end + end; + + onmousewheel = function(self, dx, dy) + UiRegion.onmousewheel(self, dx, dy) + + if self.selected_object == nil then + self.zoom = self.zoom * (dy > 0 and 1.05 or (1 / 1.05)) + if self.zoom >= 1 then self.zoom = 1 end + if self.zoom <= (1 / 1.05) ^ 30 then self.zoom = (1 / 1.05) ^ 30 end + end + end; + + update = function(self, dt) + UiRegion.update(self, dt) + + if self.active then + local speed = 300 / self.zoom + if love.keyboard.isDown "left" then + self.offset.x = self.offset.x + speed * dt + end + if love.keyboard.isDown "right" then + self.offset.x = self.offset.x - speed * dt + end + if love.keyboard.isDown "up" then + self.offset.y = self.offset.y + speed * dt + end + if love.keyboard.isDown "down" then + self.offset.y = self.offset.y - speed * dt + end + end + end; + + draw = function(self) + love.graphics.push() + + if self.background_color ~= nil then + love.graphics.setColor(self.background_color) + love.graphics.rectangle("fill", 0, 0, self.rect.w, self.rect.h) + end + local tmpbgcolor = self.background_color + self.background_color = nil + + love.graphics.translate(self.rect.w / 2, self.rect.h / 2) + love.graphics.scale(self.zoom, self.zoom) + love.graphics.translate(-self.rect.w / 2, -self.rect.h / 2) + + + local xoff = self.offset.x + local yoff = self.offset.y + local spacing = 64 + + while xoff >= spacing do xoff = xoff - spacing end + while yoff >= spacing do yoff = yoff - spacing end + while xoff <= -spacing do xoff = xoff + spacing end + while yoff <= -spacing do yoff = yoff + spacing end + + love.graphics.setColor(self.line_color) + for x = -60, 100 do + love.graphics.line(x * spacing + xoff, -self.rect.h * 2, x * spacing + xoff, self.rect.h * 4) + end + for y = -60, 100 do + love.graphics.line(-self.rect.w * 2, y * spacing + yoff, self.rect.w * 4, y * spacing + yoff) + end + + love.graphics.translate(self.offset.x, self.offset.y) + UiRegion.draw(self) + love.graphics.pop() + + self.background_color = tmpbgcolor + end; +} + +class "UiObject" { + init = function(self) + self.rect = Rectangle(0, 0, 0, 0) + self.uuid = uuid() + self.selected = false + self.order = math.random(1, 1024) + end; + + onclick = function(self, button, x, y) end; + ondrag = function(self, button, x, y, dx, dy) end; + onwheel = function(self, dx, dy) end; + + onselect = function(self) + self.selected = true + end; + onunselect = function(self) + self.selected = false + end; + + onkey = function(self, button) end; + + update = function(dt) end; + draw = function() end; +} + +class "DraggableRect" [UiObject] { + init = function(self, x, y, w, h) + UiObject.init(self) + + self.rect = Rectangle(x, y, w, h) + end; + + ondrag = function(self, button, x, y, dx, dy) + if button == 1 then + self.rect.x = self.rect.x + dx + self.rect.y = self.rect.y + dy + end + end; + + draw = function(self) + if self.selected then + love.graphics.setColor(0, 0, 1) + love.graphics.rectangle("fill", -2, -2, self.rect.w + 4, self.rect.h + 4) + end + love.graphics.setColor(1, 1, 1) + love.graphics.rectangle("fill", 0, 0, self.rect.w, self.rect.h) + + render_text(0, 0, { + { text = "1 Hello ", color = { 1, 0, 0 } }; + { text = "world", color = { 0, 0, 1 } }; + { newline = true }; + { text = "2 Nother line", color = { 0, 0.5, 0 } }; + { newline = true }; + { newline = true }; + { text = "3 nother Nother line", color = { 0, 0.5, 0 } }; + }) + end; +} + +programming_font = false +function render_text(x, y, text) + if not programming_font then + programming_font = love.graphics.newFont("res/FiraCode-Regular.ttf", 14) + love.graphics.setFont(programming_font) + end + + local origx = x + local fheight = programming_font:getHeight() + local indent_level = 0 + local indentation = "" + local fresh_newline = true + + for _, txt in ipairs(text) do + if txt.text then + local printed = txt.text + if fresh_newline then + if txt.post_indent then + printed = txt.text .. indentation + else + printed = indentation .. txt.text + end + fresh_newline = false + end + + if txt.background then + love.graphics.setColor(txt.background) + love.graphics.rectangle("fill", x, y, programming_font:getWidth(txt.text), fheight + 2); + end + + if txt.color then + love.graphics.setColor(txt.color) + end + love.graphics.print(printed, x, y) + + x = x + programming_font:getWidth(printed) + end + + if txt.newline then + y = y + fheight + 2; + x = origx + fresh_newline = true + end + + if txt.change_indent then + indent_level = indent_level + txt.change_indent + indentation = "" + for i=1,indent_level do + indentation = indentation .. " " + end + end + end + + return x, y +end + +return module { + Ui = Ui; + UiRegion = UiRegion; + ScrollingUiRegion = ScrollingUiRegion; + UiObject = UiObject; + DraggableRect = DraggableRect; + + render_text = render_text; +} diff --git a/src/utils.lua b/src/utils.lua new file mode 100644 index 0000000..d5ff427 --- /dev/null +++ b/src/utils.lua @@ -0,0 +1,137 @@ +import {} + +-- This is unsued in favor of the C equivalent +function buffered_read(file, buffer_size) + buffer_size = buffer_size or 4 * 1024 + + local buffer = {} + local ptr = -1 + + return coroutine.wrap(function() + while true do + if ptr <= 0 or ptr >= buffer_size then + ptr = 0 + local str = file:read(buffer_size) + if not str then break end + + buffer = {} + for c in (str or ''):gmatch '.' do + buffer[#buffer + 1] = c + end + end + + ptr = ptr + 1 + if ptr > #buffer then break end + coroutine.yield(buffer[ptr]:byte()) + end + return + end) +end + +function build_str(tbl_int) + if #tbl_int < 30 then + return string.char(unpack(tbl_int)) + else + -- This is slower but may be needed for long strings + local str = "" + for _, c in ipairs(tbl_int) do + str = str .. string.char(c) + end + return str + end +end + +function uuidv4() + local template ='xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx' + return string.gsub(template, '[xy]', function (c) + local v = (c == 'x') and math.random(0, 0xf) or math.random(8, 0xb) + return string.format('%x', v) + end) +end + +function random_str(n) + local str = "" + for i=1, n do + str = str .. string.char(math.random(0,25) + 0x41 + 0x20 * math.random(0, 1)) + end + return str +end + +function revipairs(t) + local max = 1 + while t[max] ~= nil do + max = max + 1 + end + local function ripairs_it(t, i) + i = i - 1 + local v = t[i] + if v ~= nil then + return i, v + else + return nil + end + end + return ripairs_it, t, max +end + +class "Rectangle" { + init = function(self, x, y, w, h) + self.x = x + self.y = y + self.w = w + self.h = h + end; + + contains = function(self, x, y) + return x >= self.x and x <= self.x + self.w + and y >= self.y and y <= self.y + self.h + end; + + intersects = function(self, other) + return self.x <= other.x + other.w + and self.x + self.w >= other.x + and self.y <= other.y + other.h + and self.y + self.h >= other.y + end; + + __lt = function(self, other) return self:intersects(other) end +} + +class "Stack" { + init = function(self) + self.data = {} + end; + + push = function(self, it) + table.insert(self.data, it) + end; + + pop = function(self) + local rt = table.remove(self.data) + return rt + end; + + at = function(self, x) + return self.data[#self.data - x] + end +} + +function scissor_points(x, y, w, h) + local nx, ny = love.graphics.transformPoint(x, y) + local tx, ty = love.graphics.transformPoint(w + x, h + y) + return nx, ny, tx - nx, ty - ny +end + +return module { + buffered_read = buffered_read; + build_str = build_str; + random_str = random_str; + + uuidv4 = uuidv4; + revipairs = revipairs; + + scissor_points = scissor_points; + + Rectangle = Rectangle; + Stack = Stack; +} diff --git a/src/wasm/decompile.lua b/src/wasm/decompile.lua new file mode 100644 index 0000000..08534db --- /dev/null +++ b/src/wasm/decompile.lua @@ -0,0 +1,741 @@ +-- Recursive decent parser for the WASM v1.0 binary format +-- Brendan Hansen 2020 + +-- Look in clib folder for C-based libraries +package.cpath = package.cpath .. [[;./clib/?.so]] + +import { + parse_helper = "parse_helper"; + pprint = "lualib.pprint"; + + build_str = "src.utils:build_str"; + random_str = "src.utils:random_str"; + Stack = "src.utils:Stack"; +} + +function parse_valtype(r) + local val = r:read_byte() + + local valtypes_map = { + [0x7f] = "i32"; + [0x7E] = "i64"; + [0x7D] = "f32"; + [0x7C] = "f64"; + } + + return valtypes_map[val] +end + +function parse_vector(tp) + return function(r) + local n = r:read_uint(32) + + local v = {} + for i=1, n do + table.insert(v, tp(r)) + end + + return v + end +end + +function parse_byte(r) + return r:read_byte() +end + +function parse_name(r) + local name = parse_vector(parse_byte)(r) + return name +end + +function parse_blocktype(r) + if r:peek_byte() == 0x40 then + r:read_byte() + return {} + else + return { parse_valtype(r) } + end +end + +function parse_functype(r) + assert(r:read_byte() == 0x60, "functype expected 0x60 as first byte") + + local params = parse_vector(parse_valtype)(r) + local results = parse_vector(parse_valtype)(r) + + return { param_types = params, result_types = results } +end + +function parse_limits(r) + local t = r:read_byte() + + local min = r:read_uint(32) + local max = nil + if t == 0x01 then + max = r:read_uint(32) + end + + return { min = min, max = max } +end + +function parse_memtype(r) + local lims = parse_limits(r) + + return lims +end + +function parse_tabletype(r) + local elemtype = parse_elemtype(r) + local limits = parse_limits(r) + + return { lim = limits, et = elemtype } +end + +function parse_elemtype(r) + assert(r:read_byte() == 0x70, "elemtype should be 0x70") + return "funcref" +end + +function parse_globaltype(r) + local valtype = parse_valtype(r) + local ismut = parse_mut(r) == 0x01 + + return { t = valtype, m = ismut } +end + +function parse_mut(r) + local v = r:read_byte() + assert(v == 0x00 or v == 0x01, "mut should be 0x00 or 0x01") + return v +end + +function parse_instr(r) + local instr = r:read_byte() + + return match(instr) { + [0x00] = function() return { "unreachable" } end; + [0x01] = function() return { "nop" } end; + [0x02] = function() + local rt = parse_blocktype(r) + local instrs = {} + local name = random_str(8) + + local block = { "block", label = name, rt = rt } + r.label_stack:push(block) + + while true do + if r:peek_byte() == 0x0B then + r:read_byte() + break + else + table.insert(instrs, parse_instr(r)) + end + end + + r.label_stack:pop() + + block.instrs = instrs + return block + end; + [0x03] = function() + local rt = parse_blocktype(r) + local instrs = {} + local name = random_str(8) + + local block = { "loop", label = name, rt = rt } + r.label_stack:push(block) + + while true do + if r:peek_byte() == 0x0B then + r:read_byte() + break + else + table.insert(instrs, parse_instr(r)) + end + end + + r.label_stack:pop() + + block.instrs = instrs + return block + end; + [0x04] = function() + local rt = parse_blocktype(r) + local instrs = {} + local else_instrs = {} + local inelse = false + + while true do + local peek = r:peek_byte() + if peek == 0x0B then + r:read_byte() + break + elseif peek == 0x05 then + r:read_byte() + inelse = true + else + if not inelse then + table.insert(instrs, parse_instr(r)) + else + table.insert(else_instrs, parse_instr(r)) + end + end + end + + return { "if", rt = rt, instrs = instrs, else_instrs = else_instrs } + end; + + [0x0C] = function() return { "br", x = parse_labelidx(r) } end; + [0x0D] = function() return { "br_if", x = parse_labelidx(r) } end; + [0x0E] = function() + local labels = parse_vector(parse_labelidx)(r) + local labeln = parse_labelidx(r) + + return { "br_table", labels = labels, labeln = labeln } + end; + + [0x0F] = function() return { "return" } end; + + [0x10] = function() return { "call", x = parse_funcidx(r) } end; + [0x11] = function() + local x = parse_typeidx(r) + -- assert(r:read_byte() ~= 0x00, "call_indirect expects 0x00 at end") + return { "call_indirect", x = x, table = r:read_byte() } + end; + + [0x1A] = function() return { "drop" } end; + [0x1B] = function() return { "select" } end; + + [0x20] = function() return { "local.get", x = parse_localidx(r) } end; + [0x21] = function() return { "local.set", x = parse_localidx(r) } end; + [0x22] = function() return { "local.tee", x = parse_localidx(r) } end; + [0x23] = function() return { "global.get", x = parse_globalidx(r) } end; + [0x24] = function() return { "global.set", x = parse_globalidx(r) } end; + + [0x28] = function() return { "i32.load", x = parse_memarg(r) } end; + [0x29] = function() return { "i64.load", x = parse_memarg(r) } end; + [0x2A] = function() return { "f32.load", x = parse_memarg(r) } end; + [0x2B] = function() return { "f64.load", x = parse_memarg(r) } end; + [0x2C] = function() return { "i32.load8_s", x = parse_memarg(r) } end; + [0x2D] = function() return { "i32.load8_u", x = parse_memarg(r) } end; + [0x2E] = function() return { "i32.load16_s", x = parse_memarg(r) } end; + [0x2F] = function() return { "i32.load16_u", x = parse_memarg(r) } end; + [0x30] = function() return { "i64.load8_s", x = parse_memarg(r) } end; + [0x31] = function() return { "i64.load8_u", x = parse_memarg(r) } end; + [0x32] = function() return { "i64.load16_s", x = parse_memarg(r) } end; + [0x33] = function() return { "i64.load16_u", x = parse_memarg(r) } end; + [0x34] = function() return { "i64.load32_s", x = parse_memarg(r) } end; + [0x35] = function() return { "i64.load32_u", x = parse_memarg(r) } end; + [0x36] = function() return { "i32.store", x = parse_memarg(r) } end; + [0x37] = function() return { "i64.store", x = parse_memarg(r) } end; + [0x38] = function() return { "f32.store", x = parse_memarg(r) } end; + [0x39] = function() return { "f64.store", x = parse_memarg(r) } end; + [0x3A] = function() return { "i32.store8", x = parse_memarg(r) } end; + [0x3B] = function() return { "i32.store16", x = parse_memarg(r) } end; + [0x3C] = function() return { "i64.store8", x = parse_memarg(r) } end; + [0x3D] = function() return { "i64.store16", x = parse_memarg(r) } end; + [0x3E] = function() return { "i64.store32", x = parse_memarg(r) } end; + [0x3F] = function() + assert(r:read_byte() == 0x00, "memory.size expects 0x00") + return { "memory.size" } + end; + [0x40] = function() + assert(r:read_byte() == 0x00, "memory.grow expects 0x00") + return { "memory.grow" } + end; + + [0x41] = function() return { "i32.const", x = r:read_sint(32) } end; + [0x42] = function() return { "i64.const", x = r:read_sint(64) } end; + [0x43] = function() return { "f32.const", x = r:read_float(32) } end; + [0x44] = function() return { "f64.const", x = r:read_float(64) } end; + + [0x45] = function() return { "i32.eqz" } end; + [0x46] = function() return { "i32.eq" } end; + [0x47] = function() return { "i32.ne" } end; + [0x48] = function() return { "i32.lt_s" } end; + [0x49] = function() return { "i32.lt_u" } end; + [0x4A] = function() return { "i32.gt_s" } end; + [0x4B] = function() return { "i32.gt_u" } end; + [0x4C] = function() return { "i32.le_s" } end; + [0x4D] = function() return { "i32.le_u" } end; + [0x4E] = function() return { "i32.ge_s" } end; + [0x4F] = function() return { "i32.ge_u" } end; + [0x50] = function() return { "i64.eqz" } end; + [0x51] = function() return { "i64.eq" } end; + [0x52] = function() return { "i64.ne" } end; + [0x53] = function() return { "i64.lt_s" } end; + [0x54] = function() return { "i64.lt_u" } end; + [0x55] = function() return { "i64.gt_s" } end; + [0x56] = function() return { "i64.gt_u" } end; + [0x57] = function() return { "i64.le_s" } end; + [0x58] = function() return { "i64.le_u" } end; + [0x59] = function() return { "i64.ge_s" } end; + [0x5A] = function() return { "i64.ge_u" } end; + [0x5B] = function() return { "f32.eq" } end; + [0x5C] = function() return { "f32.ne" } end; + [0x5D] = function() return { "f32.lt" } end; + [0x5E] = function() return { "f32.gt" } end; + [0x5F] = function() return { "f32.le" } end; + [0x60] = function() return { "f32.ge" } end; + [0x61] = function() return { "f64.eq" } end; + [0x62] = function() return { "f64.ne" } end; + [0x63] = function() return { "f64.lt" } end; + [0x64] = function() return { "f64.gt" } end; + [0x65] = function() return { "f64.le" } end; + [0x66] = function() return { "f64.ge" } end; + [0x67] = function() return { "i32.clz" } end; + [0x68] = function() return { "i32.ctz" } end; + [0x69] = function() return { "i32.popcnt" } end; + [0x6A] = function() return { "i32.add" } end; + [0x6B] = function() return { "i32.sub" } end; + [0x6C] = function() return { "i32.mul" } end; + [0x6D] = function() return { "i32.div_s" } end; + [0x6E] = function() return { "i32.div_u" } end; + [0x6F] = function() return { "i32.rem_s" } end; + [0x70] = function() return { "i32.rem_u" } end; + [0x71] = function() return { "i32.and" } end; + [0x72] = function() return { "i32.or" } end; + [0x73] = function() return { "i32.xor" } end; + [0x74] = function() return { "i32.shl" } end; + [0x75] = function() return { "i32.shr_s" } end; + [0x76] = function() return { "i32.shr_u" } end; + [0x77] = function() return { "i32.rotl" } end; + [0x78] = function() return { "i32.rotr" } end; + [0x79] = function() return { "i64.clz" } end; + [0x7A] = function() return { "i64.ctz" } end; + [0x7B] = function() return { "i64.popcnt" } end; + [0x7C] = function() return { "i64.add" } end; + [0x7D] = function() return { "i64.sub" } end; + [0x7E] = function() return { "i64.mul" } end; + [0x7F] = function() return { "i64.div_s" } end; + [0x80] = function() return { "i64.div_u" } end; + [0x81] = function() return { "i64.rem_s" } end; + [0x82] = function() return { "i64.rem_u" } end; + [0x83] = function() return { "i64.and" } end; + [0x84] = function() return { "i64.or" } end; + [0x85] = function() return { "i64.xor" } end; + [0x86] = function() return { "i64.shl" } end; + [0x87] = function() return { "i64.shr_s" } end; + [0x88] = function() return { "i64.shr_u" } end; + [0x89] = function() return { "i64.rotl" } end; + [0x8A] = function() return { "i64.rotr" } end; + [0x8B] = function() return { "f32.abs" } end; + [0x8C] = function() return { "f32.neg" } end; + [0x8D] = function() return { "f32.ceil" } end; + [0x8E] = function() return { "f32.floor" } end; + [0x8F] = function() return { "f32.trunc" } end; + [0x90] = function() return { "f32.nearest" } end; + [0x91] = function() return { "f32.sqrt" } end; + [0x92] = function() return { "f32.add" } end; + [0x93] = function() return { "f32.sub" } end; + [0x94] = function() return { "f32.mul" } end; + [0x95] = function() return { "f32.div" } end; + [0x96] = function() return { "f32.min" } end; + [0x97] = function() return { "f32.max" } end; + [0x98] = function() return { "f32.copysign" } end; + [0x99] = function() return { "f64.abs" } end; + [0x9A] = function() return { "f64.neg" } end; + [0x9B] = function() return { "f64.ceil" } end; + [0x9C] = function() return { "f64.floor" } end; + [0x9D] = function() return { "f64.trunc" } end; + [0x9E] = function() return { "f64.nearest" } end; + [0x9F] = function() return { "f64.sqrt" } end; + [0xA0] = function() return { "f64.add" } end; + [0xA1] = function() return { "f64.sub" } end; + [0xA2] = function() return { "f64.mul" } end; + [0xA3] = function() return { "f64.div" } end; + [0xA4] = function() return { "f64.min" } end; + [0xA5] = function() return { "f64.max" } end; + [0xA6] = function() return { "f64.copysign" } end; + + [0xA7] = function() return { "i32.wrap_i64" } end; + [0xA8] = function() return { "i32.trunc_f32_s" } end; + [0xA9] = function() return { "i32.trunc_f32_u" } end; + [0xAA] = function() return { "i32.trunc_f64_s" } end; + [0xAB] = function() return { "i32.trunc_f64_u" } end; + [0xAC] = function() return { "i64.extend_i32_s" } end; + [0xAD] = function() return { "i64.extend_i32_u" } end; + [0xAE] = function() return { "i64.trunc_f32_s" } end; + [0xAF] = function() return { "i64.trunc_f32_u" } end; + [0xB0] = function() return { "i64.trunc_f64_s" } end; + [0xB1] = function() return { "i64.trunc_f64_u" } end; + [0xB2] = function() return { "f32.convert_i32_s" } end; + [0xB3] = function() return { "f32.convert_i32_u" } end; + [0xB4] = function() return { "f32.convert_i64_s" } end; + [0xB5] = function() return { "f32.convert_i64_u" } end; + [0xB6] = function() return { "f32.demote_f64" } end; + [0xB7] = function() return { "f64.convert_i32_s" } end; + [0xB8] = function() return { "f64.convert_i32_u" } end; + [0xB9] = function() return { "f64.convert_i64_s" } end; + [0xBA] = function() return { "f64.convert_i64_u" } end; + [0xBB] = function() return { "f64.promote_f32" } end; + [0xBC] = function() return { "i32.reinterpret_f32" } end; + [0xBD] = function() return { "i64.reinterpret_f64" } end; + [0xBE] = function() return { "f32.reinterpret_i32" } end; + [0xBF] = function() return { "f64.reinterpret_i64" } end; + } +end + +function parse_memarg(r) + local a = r:read_uint(32) + local o = r:read_uint(32) + + return { "memarg"; align = a; offset = o } +end + +function parse_expr(r) + local instrs = {} + while true do + if r:peek_byte() == 0x0B then + r:read_byte() + break + else + table.insert(instrs, parse_instr(r)) + end + end + + return instrs +end + +function parse_typeidx(r) return r:read_uint(32) end +function parse_funcidx(r) return r:read_uint(32) end +function parse_tableidx(r) return r:read_uint(32) end +function parse_memidx(r) return r:read_uint(32) end +function parse_globalidx(r) return r:read_uint(32) end +function parse_localidx(r) return r:read_uint(32) end +function parse_labelidx(r) + local idx = r:read_uint(32) + local block = r.label_stack:at(idx) + if block == nil then block = { label = "return " } end + return { "labelidx", labelidx = idx, block = block } +end + +function parse_section(r, expectN, B) + if r:peek_byte() ~= expectN then return end + + local N = r:read_byte() + local size = r:read_uint(32) + local cont = B(r) + + return { "section", contents = cont, size = size } +end + +function parse_customsec(r) + local csect = parse_section(r, 0, parse_custom) + if not csect then return nil end + + for i=1, csect.size do + -- Discard csect.size bytes + r:read_byte() + end + return csect +end + +function parse_custom(r) + local name = parse_name(r) + + return { "custom", name = name } +end + +function parse_typesec(r) + return parse_section(r, 1, parse_vector(parse_functype)) +end + +function parse_importsec(r) + return parse_section(r, 2, parse_vector(parse_import)) +end + +function parse_import(r) + local mod = parse_name(r) + local nm = parse_name(r) + local d = parse_importdesc(r) + + return { "import", mod = mod, name = nm, desc = d } +end + +function parse_importdesc(r) + local t = r:read_byte() + if t == 0x00 then return { "func", x = parse_typeidx(r) } + elseif t == 0x01 then return { "table", tt = parse_tabletype(r) } + elseif t == 0x02 then return { "mem", mt = parse_memtype(r) } + elseif t == 0x03 then return { "global", gt = parse_globaltype(r) } + end + + error("bad importdesc") +end + +function parse_funcsec(r) + return parse_section(r, 3, parse_vector(parse_typeidx)) +end + +function parse_tablesec(r) + return parse_section(r, 4, parse_vector(parse_table)) +end + +function parse_table(r) + local tt = parse_tabletype(r) + return { "table", type_ = tt } +end + +function parse_memsec(r) + return parse_section(r, 5, parse_vector(parse_mem)) +end + +function parse_mem(r) + local mt = parse_memtype(r) + return { "mem", type_ = mt } +end + +function parse_globalsec(r) + return parse_section(r, 6, parse_vector(parse_global)) +end + +function parse_global(r) + local gt = parse_globaltype(r) + local e = parse_expr(r) + return { "global", type_ = gt, init = e } +end + +function parse_exportsec(r) + return parse_section(r, 7, parse_vector(parse_export)) +end + +function parse_export(r) + local nm = parse_name(r) + local d = parse_exportdesc(r) + return { "export", name = nm, desc = d } +end + +function parse_exportdesc(r) + local t = r:read_byte() + if t == 0x00 then return { "func", x = parse_typeidx(r) } + elseif t == 0x01 then return { "table", tt = parse_tableidx(r) } + elseif t == 0x02 then return { "mem", mt = parse_memidx(r) } + elseif t == 0x03 then return { "global", gt = parse_globalidx(r) } + end + + error("bad exportdesc: ", t) +end + +function parse_startsec(r) + return parse_section(r, 8, parse_start) +end + +function parse_start(r) + local x = parse_funcidx(r) + return { "start", func = x } +end + +function parse_elemsec(r) + return parse_section(r, 9, parse_vector(parse_elem)) +end + +function parse_elem(r) + local x = parse_tableidx(r) + local e = parse_expr(r) + local y = parse_vector(parse_funcidx)(r) + + return { "elem", table = x, offset = e, init = y } +end + +function parse_codesec(r) + return parse_section(r, 10, parse_vector(parse_code)) +end + +function parse_code(r) + local size = r:read_uint(32) + local code = parse_func(r) + return code +end + +function parse_func(r) + local t = parse_vector(parse_locals)(r) + local e = parse_expr(r) + + local localidx = 0 + local locals = {} + for _, v in ipairs(t) do + for _, l in ipairs(v) do + l.localidx = localidx + table.insert(locals, l) + localidx = localidx + 1 + end + end + + return { "func", locals = locals, body = e } +end + +function parse_locals(r) + local n = r:read_uint(32) + local t = parse_valtype(r) + + --TODO: Make a list of values with names like local0, local1, ... + + local locals = {} + for i = 0, n - 1 do + table.insert(locals, { + name = "local" .. i, + type_ = t, + localidx = i + }) + end + + return locals +end + +function parse_datasec(r) + return parse_section(r, 11, parse_vector(parse_data)) +end + +function parse_data(r) + local x = parse_memidx(r) + local e = parse_expr(r) + local b = parse_vector(parse_byte)(r) + + return { "data", data = x, offset = e, init = b } +end + +function parse_magic(r) + assert(r:read_byte() == 0x00, "magic string is wrong") + assert(r:read_byte() == 0x61, "magic string is wrong") + assert(r:read_byte() == 0x73, "magic string is wrong") + assert(r:read_byte() == 0x6D, "magic string is wrong") +end + +function parse_version(r) + assert(r:read_byte() == 0x01, "version is wrong") + assert(r:read_byte() == 0x00, "version is wrong") + assert(r:read_byte() == 0x00, "version is wrong") + assert(r:read_byte() == 0x00, "version is wrong") +end + +function parse_module(r) + parse_magic(r) + parse_version(r) + + local functypes = parse_typesec(r) + local imports = parse_importsec(r) + local typeidxs = parse_funcsec(r) + local tables = parse_tablesec(r) + local mems = parse_memsec(r) + local globals = parse_globalsec(r) + local exports = parse_exportsec(r) + local start = parse_startsec(r) + local elems = parse_elemsec(r) + local codes = parse_codesec(r) + local data = parse_datasec(r) + + local funcs = {} + local funcidx = 0 + + if imports then + for k, v in ipairs(imports.contents) do + if v.desc[1] == "func" then + funcs[funcidx] = { + name = build_str(v.mod) .. "." .. build_str(v.name); + funcidx = funcidx; + type_ = functypes.contents[v.desc.x + 1]; + } + funcidx = funcidx + 1 + end + end + end + + if codes then + for i=1, #codes.contents do + local locals = codes.contents[i].locals; + local type_ = functypes.contents[typeidxs.contents[i] + 1]; + local param_types = type_.param_types; + + local new_locals = {} + local new_local_idx = 0 + for _, p in ipairs(param_types) do + table.insert(new_locals, { + name = "param" .. new_local_idx, + type_ = p; + localidx = new_local_idx + }) + new_local_idx = new_local_idx + 1 + end + for _, l in ipairs(locals) do + l.localidx = new_local_idx + table.insert(new_locals, l) + new_local_idx = new_local_idx + 1 + end + + funcs[funcidx] = { + funcidx = funcidx; + name = "func" .. funcidx; + type_ = functypes.contents[typeidxs.contents[i] + 1]; + locals = new_locals; + body = codes.contents[i].body + } + funcidx = funcidx + 1 + end + end + + return { + "module"; + types = functypes; + tables = tables; + mems = mems; + globals = globas; + exports = exports; + start = start; + elems = elems; + data = data; + imports = imports; + funcs = funcs; + } +end + + +-- Reader util class used for interfacing +-- with the parse_helper c library +class "Parser" { + init = function(self, filename) + self.wasm_file = parse_helper.open_file(filename) + self.label_stack = Stack() + end; + + read_byte = function(self) + return parse_helper.read_byte(self.wasm_file) + end; + + peek_byte = function(self) + return parse_helper.peek_byte(self.wasm_file) + end; + + -- NOTE: N is unused + read_uint = function(self, N) + return parse_helper.parse_uleb128(self.wasm_file) + end; + + read_sint = function(self, N) + return parse_helper.parse_sleb128(self.wasm_file, N) + end; + + read_float = function(self, N) + if N >= 32 then + self:read_byte() + self:read_byte() + self:read_byte() + self:read_byte() + end + if N >= 64 then + self:read_byte() + self:read_byte() + self:read_byte() + self:read_byte() + end + return 0.0; + end; +} + +function decompile(filepath) + local reader = Parser(filepath) + + return parse_module(reader) +end + +return module { decompile } diff --git a/src/wasm/text.lua b/src/wasm/text.lua new file mode 100644 index 0000000..ed9d61e --- /dev/null +++ b/src/wasm/text.lua @@ -0,0 +1,162 @@ +import { +} + +WasmCodeGenerator = singleton { + init = function(self) end; + + setup = function(self, func, mod, colors) + self.func = func + self.mod = mod + self.colors = colors; + self.line = 1 + end; + + build_header = function(self) + local text = {} + + table.insert(text, { text = "[" .. self.func.funcidx .. "] ", color = self.colors.code }) + table.insert(text, { text = self.func.name, color = self.colors.keyword }) + table.insert(text, { text = ": ", color = self.colors.code }) + + local type_ = self.func.type_ + for _, t in ipairs(type_.param_types) do + table.insert(text, { text = t .. " " }) + end + + if #type_.param_types == 0 then + table.insert(text, { text = "void " }) + end + + table.insert(text, { text = "-> " }) + + for _, t in ipairs(type_.result_types) do + table.insert(text, { text = t .. " " }) + end + + if #type_.result_types == 0 then + table.insert(text, { text = "void" }) + end + + table.insert(text, { newline = true }) + + return text + end; + + add_line_number = function(self, textlist, newline) + if newline == nil then newline = true end + + table.insert(textlist, { newline = newline }) + table.insert(textlist, { + text = ("%4d"):format(line), + color = self.colors.code, + background = self.colors.dark_background, + post_indent = true + }) + table.insert(textlist, { text = " ", post_indent = true }) + + line = line + 1 + end; + + instr_to_text = function(self, textlist, instr) + if instr[1] == "block" then + table.insert(textlist, { change_indent = 1 }) + for _, ins in ipairs(instr.instrs) do + self:instr_to_text(textlist, ins) + end + table.insert(textlist, { change_indent = -1 }) + self:add_line_number(textlist) + table.insert(textlist, { text = "label ", color = self.colors.jumppoint }) + table.insert(textlist, { text = instr.label, color = self.colors.value }) + + elseif instr[1] == "loop" then + self:add_line_number(textlist) + table.insert(textlist, { text = "loop ", color = self.colors.jumppoint }) + table.insert(textlist, { text = instr.label, color = self.colors.value, change_indent = 1 }) + + for _, ins in ipairs(instr.instrs) do + self:instr_to_text(textlist, ins) + end + table.insert(textlist, { change_indent = -1 }) + + elseif instr[1] == "if" then + self:add_line_number(textlist) + table.insert(textlist, { text = "if", color = self.colors.jumppoint, change_indent = 1 }) + for _, ins in ipairs(instr.instrs) do + self:instr_to_text(textlist, ins) + end + table.insert(textlist, { change_indent = -1 }) + + if #instr.else_instrs > 0 then + self:add_line_number(textlist) + table.insert(textlist, { text = "else", color = self.colors.jumppoint, change_indent = 1 }) + for _, ins in ipairs(instr.else_instrs) do + self:instr_to_text(textlist, ins) + end + table.insert(textlist, { change_indent = -1 }) + end + + elseif instr[1] == "call" then + self:add_line_number(textlist) + table.insert(textlist, { text = instr[1] .. " ", color = self.colors.keyword }) + table.insert(textlist, { text = self.mod.funcs[instr.x].name, color = self.colors.value }) + + elseif instr[1]:match("local") then + self:add_line_number(textlist) + table.insert(textlist, { text = instr[1] .. " ", color = self.colors.keyword }) + table.insert(textlist, { text = self.func.locals[instr.x + 1].name, color = self.colors.value }) + + else + self:add_line_number(textlist) + table.insert(textlist, { text = instr[1] .. " ", color = self.colors.keyword }) + if instr.x then + if type(instr.x) == "table" then + if instr.x[1] == "memarg" then + table.insert(textlist, { + text = ("align=0x%02x offset=0x%04x"):format(instr.x.align, instr.x.offset); + color = self.colors.value + }) + end + + if instr.x[1] == "labelidx" then + if instr.x.block then + table.insert(textlist, { text = instr.x.block.label .. " ", color = self.colors.value }) + end + table.insert(textlist, { text = ("[0x%02x]"):format(instr.x.labelidx), color = self.colors.code }) + end + else + table.insert(textlist, { text = tostring(instr.x), color = self.colors.value }) + end + end + end + + return line + end; + + build_body = function(self) + local text = {} + line = 1 + + table.insert(text, { text = " Locals:", color = self.colors.keyword, newline = true, change_indent = 2 }) + for _, loc in ipairs(self.func.locals) do + table.insert(text, { text = loc.type_ .. " " .. loc.name, color = self.colors.code , newline = true }) + end + + if #self.func.locals == 0 then + table.insert(text, { text = "no locals", color = self.colors.code, newline = true }) + end + + table.insert(text, { newline = true, change_indent = -2 }) + table.insert(text, { text = " Body:", color = self.colors.keyword }) + + for _, instr in ipairs(self.func.body) do + self:instr_to_text(text, instr) + end + + return text + end; +} + +return module { + WasmCodeGenerator; +} + diff --git a/ui.lua b/ui.lua deleted file mode 100644 index 1d3f1b7..0000000 --- a/ui.lua +++ /dev/null @@ -1,463 +0,0 @@ -import { - Rectangle = "utils:Rectangle"; - uuid = "utils:uuidv4"; - revipairs = "utils:revipairs"; - scissor_points = "utils:scissor_points"; -} - -class "Ui" { - init = function(self) - self.regions = {} - self.active_region = nil - end; - - add_region = function(self, region) - table.insert(self.regions, region) - table.sort(self.regions) - end; - - onmousedown = function(self, button, x, y) - local region = nil - for _, r in revipairs(self.regions) do - if r.rect:contains(x, y) then - region = r - break - end - end - - if region ~= nil then - region:onmousedown(button, x - region.rect.x, y - region.rect.y) - end - end; - - onmouseup = function(self, button, x, y) - local region = nil - for _, r in revipairs(self.regions) do - if r.rect:contains(x, y) then - region = r - break - end - end - - if region ~= nil then - region:onmouseup(button, x - region.rect.x, y - region.rect.y) - end - end; - - onmousemove = function(self, x, y, dx, dy) - local region = nil - for _, r in revipairs(self.regions) do - if r.rect:contains(x, y) then - region = r - break - end - end - - if region ~= nil then - if region ~= self.active_region then - if self.active_region ~= nil then - self.active_region:onmouseleave() - end - - region:onmouseenter() - end - region:onmousemove(x - region.rect.x, y - region.rect.y, dx, dy) - end - - self.active_region = region - end; - - onmousewheel = function(self, dx, dy) - if not self.active_region then return end - self.active_region:onmousewheel(dx, dy) - end; - - onkeydown = function(self, key) - if not self.active_region then return end - self.active_region:onkeydown(key) - end; - - onkeyup = function(self, key) - if not self.active_region then return end - self.active_region:onkeyup(key) - end; - - onresize = function(self, new_width, new_height) - end; - - update = function(self, dt) - for _, region in ipairs(self.regions) do - region:update(dt); - end - end; - - draw = function(self) - for _, region in ipairs(self.regions) do - love.graphics.push() - love.graphics.setScissor(scissor_points(region.rect.x, region.rect.y, region.rect.w, region.rect.h)) - love.graphics.translate(region.rect.x, region.rect.y) - region:draw() - love.graphics.pop() - end - end; -} - -class "UiRegion" { - init = function(self) - self.active = false - - self.selected_object = nil - self.objects = {} - - self.rect = Rectangle(0, 0, 0, 0) - self.background_color = { 0, 0, 0 } - self.layer = 0 - - -- Internals - self.is_mouse_down = false - self.fire_onclick = false - end; - - add_object = function(self, obj) - table.insert(self.objects, obj) - end; - - -- Used for sorting in the Ui - __lt = function(self, other) - return self.layer < other.layer - end; - - _obj_rect = function(self, obj) - return obj.rect - end; - - onmousedown = function(self, button, x, y) - self.is_mouse_down = button - self.fire_onclick = true - local new_selected = nil - - for _, obj in revipairs(self.objects) do - if self:_obj_rect(obj):contains(x, y) then - new_selected = obj - break - end - end - - if self.selected_object ~= new_selected then - if self.selected_object ~= nil then - self.selected_object:onunselect() - end - - self.selected_object = new_selected - - if self.selected_object ~= nil then - self.selected_object.order = 0 - - for _, obj in ipairs(self.objects) do - if obj ~= self.selected_obj then - obj.order = obj.order + 1 - end - end - - table.sort(self.objects, function(a, b) - return a.order > b.order - end) - - self.selected_object:onselect() - end - end - end; - - onmouseup = function(self, button, x, y) - self.is_mouse_down = false - - if self.selected_object ~= nil and self.fire_onclick then - local obj_rect = self:_obj_rect(self.selected_object) - self.selected_object:onclick(button, x - obj_rect.x, y - obj_rect.y) - end - end; - onmousemove = function(self, x, y, dx, dy) - if self.is_mouse_down and self.selected_object ~= nil then - local obj_rect = self:_obj_rect(self.selected_object) - self.selected_object:ondrag(self.is_mouse_down, x - obj_rect.x, y - obj_rect.y, dx, dy) - self.fire_onclick = false - end - end; - onmouseenter = function(self) - self.active = true - end; - - onmouseleave = function(self) - self.active = false; - self.is_mouse_down = false - self.fire_onclick = false - self.selected_object = nil - end; - - onmousewheel = function(self, dx, dy) - if self.selected_object ~= nil then - self.selected_object:onwheel(dx, dy) - end - end; - - onkeydown = function(self, key) end; - onkeyup = function(self, key) end; - - update = function(self, dt) - for _, obj in ipairs(self.objects) do - obj:update(dt) - end - end; - - draw = function(self) - if self.background_color ~= nil then - love.graphics.setColor(self.background_color) - love.graphics.rectangle("fill", 0, 0, self.rect.w, self.rect.h) - end - - for _, obj in ipairs(self.objects) do - love.graphics.push() - love.graphics.translate(obj.rect.x, obj.rect.y) - obj:draw() - love.graphics.setScissor() - love.graphics.pop() - end - end; -} - -class "ScrollingUiRegion" [UiRegion] { - init = function(self) - UiRegion.init(self) - - self.offset = { x = 0; y = 0; } - self.line_color = { 0, 0, 0 } - - self.zoom = 1 - end; - - _obj_rect = function(self, obj) - return obj.rect - end; - - _transform_mouse = function(self, x, y) - return - ((x + self.rect.x) - self.rect.w / 2) / self.zoom + self.rect.w / 2 - self.offset.x - self.rect.x, - ((y + self.rect.y) - self.rect.h / 2) / self.zoom + self.rect.h / 2 - self.offset.y - self.rect.y - end; - - onmousedown = function(self, button, x, y) - print(x, y) - local mx, my = self:_transform_mouse(x, y) - print(mx, my) - UiRegion.onmousedown(self, button, mx, my) - end; - - onmouseup = function(self, button, x, y) - local mx, my = self:_transform_mouse(x, y) - UiRegion.onmouseup(self, button, mx, my) - end; - - onmousemove = function(self, x, y, dx, dy) - local mx, my = self:_transform_mouse(x, y) - UiRegion.onmousemove(self, mx, my, dx / self.zoom, dy / self.zoom) - - if self.is_mouse_down and self.selected_object == nil then - self.offset.x = self.offset.x + dx / self.zoom - self.offset.y = self.offset.y + dy / self.zoom - end - end; - - onmousewheel = function(self, dx, dy) - UiRegion.onmousewheel(self, dx, dy) - - if self.selected_object == nil then - self.zoom = self.zoom * (dy > 0 and 1.05 or (1 / 1.05)) - if self.zoom >= 1 then self.zoom = 1 end - if self.zoom <= (1 / 1.05) ^ 20 then self.zoom = (1 / 1.05) ^ 20 end - end - end; - - update = function(self, dt) - UiRegion.update(self, dt) - - if self.active then - local speed = 300 / self.zoom - if love.keyboard.isDown "left" then - self.offset.x = self.offset.x + speed * dt - end - if love.keyboard.isDown "right" then - self.offset.x = self.offset.x - speed * dt - end - if love.keyboard.isDown "up" then - self.offset.y = self.offset.y + speed * dt - end - if love.keyboard.isDown "down" then - self.offset.y = self.offset.y - speed * dt - end - end - end; - - draw = function(self) - love.graphics.push() - - if self.background_color ~= nil then - love.graphics.setColor(self.background_color) - love.graphics.rectangle("fill", 0, 0, self.rect.w, self.rect.h) - end - local tmpbgcolor = self.background_color - self.background_color = nil - - love.graphics.translate(self.rect.w / 2, self.rect.h / 2) - love.graphics.scale(self.zoom, self.zoom) - love.graphics.translate(-self.rect.w / 2, -self.rect.h / 2) - - - local xoff = self.offset.x - local yoff = self.offset.y - local spacing = 64 - - while xoff >= spacing do xoff = xoff - spacing end - while yoff >= spacing do yoff = yoff - spacing end - while xoff <= -spacing do xoff = xoff + spacing end - while yoff <= -spacing do yoff = yoff + spacing end - - love.graphics.setColor(self.line_color) - for x = -40, 80 do - love.graphics.line(x * spacing + xoff, -self.rect.h, x * spacing + xoff, self.rect.h * 2) - end - for y = -40, 80 do - love.graphics.line(-self.rect.w, y * spacing + yoff, self.rect.w * 2, y * spacing + yoff) - end - - love.graphics.translate(self.offset.x, self.offset.y) - UiRegion.draw(self) - love.graphics.pop() - - self.background_color = tmpbgcolor - end; -} - -class "UiObject" { - init = function(self) - self.rect = Rectangle(0, 0, 0, 0) - self.uuid = uuid() - self.selected = false - self.order = math.random(1, 1024) - end; - - onclick = function(self, button, x, y) end; - ondrag = function(self, button, x, y, dx, dy) end; - onwheel = function(self, dx, dy) end; - - onselect = function(self) - self.selected = true - end; - onunselect = function(self) - self.selected = false - end; - - onkey = function(self, button) end; - - update = function(dt) end; - draw = function() end; -} - -class "DraggableRect" [UiObject] { - init = function(self, x, y, w, h) - UiObject.init(self) - - self.rect = Rectangle(x, y, w, h) - end; - - ondrag = function(self, button, x, y, dx, dy) - if button == 1 then - self.rect.x = self.rect.x + dx - self.rect.y = self.rect.y + dy - end - end; - - draw = function(self) - if self.selected then - love.graphics.setColor(0, 0, 1) - love.graphics.rectangle("fill", -2, -2, self.rect.w + 4, self.rect.h + 4) - end - love.graphics.setColor(1, 1, 1) - love.graphics.rectangle("fill", 0, 0, self.rect.w, self.rect.h) - - render_text(0, 0, { - { text = "1 Hello ", color = { 1, 0, 0 } }; - { text = "world", color = { 0, 0, 1 } }; - { newline = true }; - { text = "2 Nother line", color = { 0, 0.5, 0 } }; - { newline = true }; - { newline = true }; - { text = "3 nother Nother line", color = { 0, 0.5, 0 } }; - }) - end; -} - -programming_font = false -function render_text(x, y, text) - if not programming_font then - programming_font = love.graphics.newFont("res/FiraCode-Regular.ttf", 14) - love.graphics.setFont(programming_font) - end - - local origx = x - local fheight = programming_font:getHeight() - local indent_level = 0 - local indentation = "" - local fresh_newline = true - - for _, txt in ipairs(text) do - if txt.text then - local printed = txt.text - if fresh_newline then - if txt.post_indent then - printed = txt.text .. indentation - else - printed = indentation .. txt.text - end - fresh_newline = false - end - - if txt.background then - love.graphics.setColor(txt.background) - love.graphics.rectangle("fill", x, y, programming_font:getWidth(txt.text), fheight + 2); - end - - if txt.color then - love.graphics.setColor(txt.color) - end - love.graphics.print(printed, x, y) - - x = x + programming_font:getWidth(printed) - end - - if txt.newline then - y = y + fheight + 2; - x = origx - fresh_newline = true - end - - if txt.change_indent then - indent_level = indent_level + txt.change_indent - indentation = "" - for i=1,indent_level do - indentation = indentation .. " " - end - end - end - - return x, y -end - -return module { - Ui = Ui; - UiRegion = UiRegion; - ScrollingUiRegion = ScrollingUiRegion; - UiObject = UiObject; - DraggableRect = DraggableRect; - - render_text = render_text; -} diff --git a/utils.lua b/utils.lua deleted file mode 100644 index d5ff427..0000000 --- a/utils.lua +++ /dev/null @@ -1,137 +0,0 @@ -import {} - --- This is unsued in favor of the C equivalent -function buffered_read(file, buffer_size) - buffer_size = buffer_size or 4 * 1024 - - local buffer = {} - local ptr = -1 - - return coroutine.wrap(function() - while true do - if ptr <= 0 or ptr >= buffer_size then - ptr = 0 - local str = file:read(buffer_size) - if not str then break end - - buffer = {} - for c in (str or ''):gmatch '.' do - buffer[#buffer + 1] = c - end - end - - ptr = ptr + 1 - if ptr > #buffer then break end - coroutine.yield(buffer[ptr]:byte()) - end - return - end) -end - -function build_str(tbl_int) - if #tbl_int < 30 then - return string.char(unpack(tbl_int)) - else - -- This is slower but may be needed for long strings - local str = "" - for _, c in ipairs(tbl_int) do - str = str .. string.char(c) - end - return str - end -end - -function uuidv4() - local template ='xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx' - return string.gsub(template, '[xy]', function (c) - local v = (c == 'x') and math.random(0, 0xf) or math.random(8, 0xb) - return string.format('%x', v) - end) -end - -function random_str(n) - local str = "" - for i=1, n do - str = str .. string.char(math.random(0,25) + 0x41 + 0x20 * math.random(0, 1)) - end - return str -end - -function revipairs(t) - local max = 1 - while t[max] ~= nil do - max = max + 1 - end - local function ripairs_it(t, i) - i = i - 1 - local v = t[i] - if v ~= nil then - return i, v - else - return nil - end - end - return ripairs_it, t, max -end - -class "Rectangle" { - init = function(self, x, y, w, h) - self.x = x - self.y = y - self.w = w - self.h = h - end; - - contains = function(self, x, y) - return x >= self.x and x <= self.x + self.w - and y >= self.y and y <= self.y + self.h - end; - - intersects = function(self, other) - return self.x <= other.x + other.w - and self.x + self.w >= other.x - and self.y <= other.y + other.h - and self.y + self.h >= other.y - end; - - __lt = function(self, other) return self:intersects(other) end -} - -class "Stack" { - init = function(self) - self.data = {} - end; - - push = function(self, it) - table.insert(self.data, it) - end; - - pop = function(self) - local rt = table.remove(self.data) - return rt - end; - - at = function(self, x) - return self.data[#self.data - x] - end -} - -function scissor_points(x, y, w, h) - local nx, ny = love.graphics.transformPoint(x, y) - local tx, ty = love.graphics.transformPoint(w + x, h + y) - return nx, ny, tx - nx, ty - ny -end - -return module { - buffered_read = buffered_read; - build_str = build_str; - random_str = random_str; - - uuidv4 = uuidv4; - revipairs = revipairs; - - scissor_points = scissor_points; - - Rectangle = Rectangle; - Stack = Stack; -}