From c4181af08d3c3282e889e30a3eb11fff9a392d2e Mon Sep 17 00:00:00 2001 From: Cosmin Apreutesei Date: Fri, 30 Oct 2015 13:41:52 +0200 Subject: [PATCH] DynASM: Lua mode --- dynasm/dasm.lua | 274 ++++++++++++++++++++++++++++++++++ dynasm/dasm_extern.h | 5 + dynasm/dasm_mm.lua | 144 ++++++++++++++++++ dynasm/dasm_x64.lua | 12 +- dynasm/dasm_x86.c | 11 ++ dynasm/dasm_x86.lua | 183 ++++++++++++++++++----- dynasm/dynasm.lua | 339 ++++++++++++++++++++++++++++++++++++------- 7 files changed, 873 insertions(+), 95 deletions(-) create mode 100644 dynasm/dasm.lua create mode 100644 dynasm/dasm_extern.h create mode 100644 dynasm/dasm_mm.lua create mode 100644 dynasm/dasm_x86.c diff --git a/dynasm/dasm.lua b/dynasm/dasm.lua new file mode 100644 index 00000000..fc17500a --- /dev/null +++ b/dynasm/dasm.lua @@ -0,0 +1,274 @@ + +--Binding to the DynASM encoding engine. +--Written by Cosmin Apreutesei. Public Domain. + +local ffi = require'ffi' +local bit = require'bit' +local arch = ffi.arch +if arch == 'x64' then arch = 'x86' end --same linker for x64 +local C = ffi.load('dasm_'..arch) +local M = {C = C} + +M._VERSION = 10400 + +ffi.cdef[[ +enum { + DASM_S_OK = 0x00000000, + DASM_S_NOMEM = 0x01000000, + DASM_S_PHASE = 0x02000000, + DASM_S_MATCH_SEC = 0x03000000, + DASM_S_RANGE_I = 0x11000000, + DASM_S_RANGE_SEC = 0x12000000, + DASM_S_RANGE_LG = 0x13000000, + DASM_S_RANGE_PC = 0x14000000, + DASM_S_RANGE_VREG = 0x15000000, + DASM_S_UNDEF_L = 0x21000000, + DASM_S_UNDEF_PC = 0x22000000, +}; + +/* Internal DynASM encoder state. */ +typedef struct dasm_State_Ref { struct dasm_State *p; } dasm_State_Ref; +typedef dasm_State_Ref *Dst_DECL; + +/* Initialize and free DynASM state. */ +void dasm_init(Dst_DECL, int maxsection); +void dasm_free(Dst_DECL); + +/* Setup global array. Must be called before dasm_setup(). */ +void dasm_setupglobal(Dst_DECL, void **gl, unsigned int maxgl); + +/* Grow PC label array. Can be called after dasm_setup(), too. */ +void dasm_growpc(Dst_DECL, unsigned int maxpc); + +/* Setup encoder. */ +void dasm_setup(Dst_DECL, const void *actionlist); + +/* Feed encoder with actions. Calls are generated by pre-processor. */ +void dasm_put(Dst_DECL, int start, ...); + +/* Link sections and return the resulting size. */ +int dasm_link(Dst_DECL, size_t *szp); + +/* Encode sections into buffer. */ +int dasm_encode(Dst_DECL, void *buffer); + +/* Get PC label offset. */ +int dasm_getpclabel(Dst_DECL, unsigned int pc); + +/* Optional sanity checker to call between isolated encoding steps. */ +int dasm_checkstep(Dst_DECL, int secmatch); + +typedef int (*DASM_EXTERN_TYPE) (void *ctx, unsigned char *addr, int idx, int type); +DASM_EXTERN_TYPE DASM_EXTERN_FUNC; +]] + +local function err(...) + io.stderr:setvbuf'no' + io.stderr:write('dasm error: ', ...) + io.stderr:write'\n' + os.exit(1) +end + +--status check helper + +local status_map = { + [C.DASM_S_NOMEM] = 'out of memory', + [C.DASM_S_PHASE] = 'phase error', + [C.DASM_S_MATCH_SEC] = 'section not found', + [C.DASM_S_RANGE_I] = 'immediate value out of range', + [C.DASM_S_RANGE_SEC] = 'too many sections', + [C.DASM_S_RANGE_LG] = 'too many global labels', + [C.DASM_S_RANGE_PC] = 'too many pclabels', + [C.DASM_S_RANGE_VREG] = 'variable register out of range', + [C.DASM_S_UNDEF_L] = 'undefined global label', + [C.DASM_S_UNDEF_PC] = 'undefined pclabel', +} + +local function checkst(st) + if st == C.DASM_S_OK then return end + local status, arg = status_map[bit.band(st, 0xff000000)], bit.band(st, 0x00ffffff) + if status then + err(status, '. :', arg) + else + err(string.format('0x%08X', st)) + end +end + +--low level API + +M.init = C.dasm_init +M.free = C.dasm_free +M.setupglobal = C.dasm_setupglobal +M.growpc = C.dasm_growpc +M.setup = C.dasm_setup + +local int_ct = ffi.typeof'int' +local function convert_arg(arg) --dasm_put() accepts only int32 varargs. + if type(arg) == "number" then --but we make it accept uint32 too by normalizing the arg. + arg = bit.tobit(arg) --non-number args are converted to int32 according to ffi rules. + end + return ffi.cast(int_ct, arg) +end +local function convert_args(...) --not a tailcall but at least it doesn't make any garbage + if select('#', ...) == 0 then return end + return convert_arg(...), convert_args(select(2, ...)) +end +function M.put(state, start, ...) + C.dasm_put(state, start, convert_args(...)) +end + +function M.link(state, sz) + sz = sz or ffi.new'size_t[1]' + checkst(C.dasm_link(state, sz)) + return tonumber(sz[0]) +end + +function M.encode(state, buf) + checkst(C.dasm_encode(state, buf)) +end +jit.off(M.encode) --calls the DASM_EXTERN_FUNC callback + +local voidptr_ct = ffi.typeof'void*' +local byteptr_ct = ffi.typeof'int8_t*' +function M.getpclabel(state, pc, buf) + local offset = C.dasm_getpclabel(state, pc) + if buf then + return ffi.cast(voidptr_ct, ffi.cast(byteptr_ct, buf) + offset) + end + return offset +end + +function M.checkstep(state, section) + checkst(C.dasm_checkstep(state, section or -1)) +end + +--get the address of a standard symbol. +--TODO: ask Mike to expose clib_getsym() in ffi so we can get the address +--of symbols without having to declare them first. +local function getsym(name) + return ffi.C[name] +end +local getsym = function(name) + local ok, sym = pcall(getsym, name) + if not ok then --not found or not defined: define it and try again + ffi.cdef(string.format('void %s();', name)) + return getsym(name) + else + return sym + end +end + +--DASM_EXTERN callback plumbing + +local extern_names --t[idx] -> name +local extern_get --f(name) -> ptr + +local byteptr_ct = ffi.typeof'uint8_t*' + +local function DASM_EXTERN_FUNC(ctx, addr, idx, type) + if not extern_names or not extern_get then + err'extern callback not initialized.' + end + local name = extern_names[idx] + local ptr = extern_get(name) + if ptr == nil then + err('extern not found: ', name, '.') + end + if type ~= 0 then + return ffi.cast(byteptr_ct, ptr) - addr - 4 + else + return ptr + end +end + +function M.setupextern(_, names, getter) + extern_names = names + extern_get = getter or getsym + if C.DASM_EXTERN_FUNC == nil then + C.DASM_EXTERN_FUNC = DASM_EXTERN_FUNC + end +end + +--hi-level API + +function M.new(actionlist, externnames, sectioncount, globalcount, externget, globals) + local state = ffi.new'dasm_State_Ref' + M.init(state, sectioncount or 1) + globalcount = globalcount or 256 + globals = globals or ffi.new('void*[?]', globalcount) + ffi.gc(state, function(state) + local _ = actionlist, externnames, globals, externget --anchor those: don't rely on the user doing so + M.free(state) + end) + M.setupglobal(state, globals, globalcount) + M.setupextern(state, externnames, externget) + M.setup(state, actionlist) + return state, globals +end + +function M.build(state) + state:checkstep(-1) + local sz = state:link() + if sz == 0 then err'no code?' end + local mm = require'dasm_mm' --runtime dependency + local buf = mm.new(sz) + state:encode(buf) + mm.protect(buf, sz) + return buf, sz +end + +function M.dump(addr, size, out) + local disass = require('jit.dis_'..jit.arch).disass + disass(ffi.string(addr, size), tonumber(ffi.cast('uintptr_t', addr)), out) +end + +--given the globals array from dasm.new() and the globalnames list +--from the `.globalnames` directive, return a map {global_name -> global_addr}. +function M.globals(globals, globalnames) + local t = {} + for i = 0, #globalnames do + if globals[i] ~= nil then + t[globalnames[i]] = globals[i] + end + end + return t +end + +--object interface + +ffi.metatype('dasm_State_Ref', {__index = { + --low-level API + init = M.init, + free = M.free, + setupglobal = M.setupglobal, + setupextern = M.setupextern, + growpc = M.growpc, + setup = M.setup, + put = M.put, + link = M.link, + encode = M.encode, + getpclabel = M.getpclabel, + checkstep = M.checkstep, + --hi-level API + build = M.build, +}}) + +if not ... then --demo + local dasm = M + local actions = ffi.new('const uint8_t[19]', {254,0,102,184,5,0,254,1,102,187,3,0,254,2,102,187,3,0,255}) + local Dst, globals = dasm.new(actions, nil, 3) + --|.code + dasm.put(Dst, 0) + --| mov ax, 5 + --|.sub1 + dasm.put(Dst, 2) + --| mov bx, 3 + --|.sub2 + dasm.put(Dst, 8) + --| mov bx, 3 + dasm.put(Dst, 14) + local addr, size = Dst:build() + dasm.dump(addr, size) +end + +return M diff --git a/dynasm/dasm_extern.h b/dynasm/dasm_extern.h new file mode 100644 index 00000000..856da17f --- /dev/null +++ b/dynasm/dasm_extern.h @@ -0,0 +1,5 @@ +// DASM_EXTERN_FUNC is a global function pointer to be set at runtime. +// It will be used in place of the DASM_EXTERN() macro. +typedef int (*DASM_EXTERN_TYPE) (void *ctx, unsigned char *addr, int idx, int type); +DASM_EXTERN_TYPE DASM_EXTERN_FUNC; +#define DASM_EXTERN(a,b,c,d) DASM_EXTERN_FUNC(a,b,c,d) diff --git a/dynasm/dasm_mm.lua b/dynasm/dasm_mm.lua new file mode 100644 index 00000000..76dedc7e --- /dev/null +++ b/dynasm/dasm_mm.lua @@ -0,0 +1,144 @@ + +--wrappers around mmap to support dynamic code exection. +--Written by Cosmin Apreutesei. Public Domain. +--Tested with Windows, Linux and OSX, x86 and x86-64. + +local ffi = require'ffi' +local C = ffi.C + +local function checkh(ptr) return assert(ptr ~= nil and ptr) end +local function checkz(ret) assert(ret == 0) end +local function checknz(ret) assert(ret ~= 0) end + +local new, free, protect + +--Using VirtualAlloc allows memory protection, but can only allocate memory in multiple-of-64K chunks. +local USE_VIRTUALALLOC = false + +if ffi.os == 'Windows' then + + if USE_VIRTUALALLOC then + + ffi.cdef[[ + void* VirtualAlloc(void* lpAddress, size_t dwSize, uint32_t flAllocationType, uint32_t flProtect); + int VirtualFree(void* lpAddress, size_t dwSize, uint32_t dwFreeType); + int VirtualProtect(void* lpAddress, size_t dwSize, uint32_t flNewProtect, uint32_t* lpflOldProtect); + ]] + + local PAGE_READWRITE = 0x04 + local PAGE_EXECUTE_READ = 0x20 + local MEM_COMMIT = 0x1000 + local MEM_RESERVE = 0x2000 + local MEM_RELEASE = 0x8000 + + function new(size) + return checkh(C.VirtualAlloc(nil, size, bit.bor(MEM_RESERVE, MEM_COMMIT), PAGE_READWRITE)) + end + + function protect(addr, size) + local oldprotect = ffi.new'uint32_t[1]' --because null not accepted + checknz(C.VirtualProtect(addr, size, PAGE_EXECUTE_READ, oldprotect)) + end + + function free(addr, size) + assert(size, 'size required') --on other platforms + checknz(C.VirtualFree(addr, 0, MEM_RELEASE)) + end + + else + + local HEAP_NO_SERIALIZE = 0x00000001 + local HEAP_ZERO_MEMORY = 0x00000008 + local HEAP_CREATE_ENABLE_EXECUTE = 0x00040000 + + ffi.cdef[[ + void* HeapCreate(uint32_t flOptions, size_t dwInitialSize, size_t dwMaximumSize); + void* HeapAlloc(void* hHeap, uint32_t dwFlags, size_t dwBytes); + int HeapFree(void* hHeap, uint32_t dwFlags, void* lpMem); + ]] + + local heap + + function new(size) + heap = heap or checkh(C.HeapCreate(bit.bor(HEAP_NO_SERIALIZE, HEAP_CREATE_ENABLE_EXECUTE), 0, 0)) + return checkh(C.HeapAlloc(heap, HEAP_ZERO_MEMORY, size)) + end + + function protect(addr, size) end + + function free(addr, size) + assert(size, 'size required') --on other platforms + checknz(C.HeapFree(heap, HEAP_NO_SERIALIZE, addr)) + end + + end + +elseif ffi.os == 'Linux' or ffi.os == 'OSX' then + + if ffi.os == 'OSX' then + ffi.cdef'typedef int64_t off_t;' + else + ffi.cdef'typedef long int off_t;' + end + + ffi.cdef[[ + void* mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); + int munmap(void *addr, size_t length); + int mprotect(void *addr, size_t len, int prot); + ]] + + local PROT_READ = 1 + local PROT_WRITE = 2 + local PROT_EXEC = 4 + local MAP_PRIVATE = 2 + local MAP_ANON = ffi.os == 'Linux' and 0x20 or 0x1000 + + function new(size) + local ret = C.mmap(nil, size, bit.bor(PROT_READ, PROT_WRITE), bit.bor(MAP_PRIVATE, MAP_ANON), -1, 0) + if ffi.cast('intptr_t', ret) == ffi.cast('intptr_t', -1) then + error(string.format('mmap errno: %d', ffi.errno())) + end + return checkh(ret) + end + + function protect(addr, size) + checkz(C.mprotect(addr, size, bit.bor(PROT_READ, PROT_EXEC))) + end + + function free(addr, size) + checkz(C.munmap(addr, size)) + end + +end + +local new = function(size) --override for hooking to gc + local addr = new(size) + ffi.gc(addr, function(addr) + free(addr, size) + ffi.gc(addr, nil) + end) + return addr +end + +if not ... then + local function test(size) + local addr = new(size) + print(addr) + addr = ffi.cast('int32_t*', addr) + assert(addr[0] == 0) + addr[0] = 1234 --writable + assert(addr[0] == 1234) + protect(addr, size) + --addr[0] = 4321 --uncomment this to get a crash (r/o memory); TODO: test if executable + --addr = nil; collectgarbage() --enable this to fail the assertion below + return addr + end + local a1 = test(64*1024*1000) --64MB + local a2 = test(16) --16 bytes + assert(a1 ~= a2) --different pages + a1 = nil + a2 = nil + collectgarbage() --TODO: test if finalizer was called +end + +return {new = new, free = free, protect = protect} diff --git a/dynasm/dasm_x64.lua b/dynasm/dasm_x64.lua index b1b62022..c22ddcfd 100644 --- a/dynasm/dasm_x64.lua +++ b/dynasm/dasm_x64.lua @@ -8,5 +8,15 @@ -- All the interesting stuff is there. ------------------------------------------------------------------------------ +--unload dasm_x86 if it's already loaded. +if not package then package = {loaded = {}} end --for compat. with minilua +local dasm_x86 = package.loaded.dasm_x86 +package.loaded.dasm_x86 = nil + x64 = true -- Using a global is an ugly, but effective solution. -return require("dasm_x86") +local dasm_x64 = require("dasm_x86") + +package.loaded.dasm_x86 = dasm_x86 --put it back + +return dasm_x64 + diff --git a/dynasm/dasm_x86.c b/dynasm/dasm_x86.c new file mode 100644 index 00000000..59c3bb63 --- /dev/null +++ b/dynasm/dasm_x86.c @@ -0,0 +1,11 @@ +/* + Encoding engine to use with dasm.lua. + + Compile with: + + gcc dasm_x86.c -DDASM_CHECKS -shared -s -o dasm_x86.so +*/ + +#include "dasm_extern.h" +#include "dasm_proto.h" +#include "dasm_x86.h" diff --git a/dynasm/dasm_x86.lua b/dynasm/dasm_x86.lua index 322d77f7..20e207bb 100644 --- a/dynasm/dasm_x86.lua +++ b/dynasm/dasm_x86.lua @@ -5,7 +5,7 @@ -- See dynasm.lua for full copyright notice. ------------------------------------------------------------------------------ -local x64 = x64 +local x64 = rawget(_G, "x64") --rawget so it works with strict.lua -- Module information: local _info = { @@ -32,9 +32,12 @@ local bit = bit or require("bit") local band, bxor, shl, shr = bit.band, bit.bxor, bit.lshift, bit.rshift -- Inherited tables and callbacks. -local g_opt, g_arch +local g_opt, g_arch, g_map_def local wline, werror, wfatal, wwarn +-- Global flag to generate Lua code instead of C code. +local luamode + -- Action name list. -- CHECK: Keep this in sync with the C code! local action_names = { @@ -74,14 +77,20 @@ local map_action = {} local actfirst = 256-#action_names -- Action list buffer and string (only used to remove dupes). -local actlist = {} -local actstr = "" +local actlist, actstr --- Argument list for next dasm_put(). Start with offset 0 into action list. -local actargs = { 0 } +-- Argument list for next dasm_put(). +local actargs -- Current number of section buffer positions for dasm_put(). -local secpos = 1 +local secpos + +local function init_actionlist() + actlist = {} + actstr = "" + actargs = { 0 } -- Start with offset 0 into the action list. + secpos = 1 +end ------------------------------------------------------------------------------ @@ -107,7 +116,11 @@ local function writeactions(out, name) local last = actlist[nn] or 255 actlist[nn] = nil -- Remove last byte. if nn == 0 then nn = 1 end - out:write("static const unsigned char ", name, "[", nn, "] = {\n") + if luamode then + out:write("local ", name, " = ffi.new('const uint8_t[", nn, "]', {\n") + else + out:write("static const unsigned char ", name, "[", nn, "] = {\n") + end local s = " " for n,b in ipairs(actlist) do s = s..b.."," @@ -116,7 +129,11 @@ local function writeactions(out, name) s = " " end end - out:write(s, last, "\n};\n\n") -- Add last byte back. + if luamode then + out:write(s, last, "\n})\n\n") -- Add last byte back. + else + out:write(s, last, "\n};\n\n") -- Add last byte back. + end end ------------------------------------------------------------------------------ @@ -136,7 +153,11 @@ end -- Add call to embedded DynASM C code. local function wcall(func, args) - wline(format("dasm_%s(Dst, %s);", func, concat(args, ", ")), true) + if luamode then + wline(format("dasm.%s(Dst, %s)", func, concat(args, ", ")), true) + else + wline(format("dasm_%s(Dst, %s);", func, concat(args, ", ")), true) + end end -- Delete duplicate action list chunks. A tad slow, but so what. @@ -172,15 +193,21 @@ end ------------------------------------------------------------------------------ -- Global label name -> global label number. With auto assignment on 1st use. -local next_global = 10 -local map_global = setmetatable({}, { __index = function(t, name) +local next_global, map_global + +local globals_meta = { __index = function(t, name) if not match(name, "^[%a_][%w_@]*$") then werror("bad global label") end local n = next_global if n > 246 then werror("too many global labels") end next_global = n + 1 t[name] = n return n -end}) +end} + +local function init_map_global() + next_global = 10 + map_global = setmetatable({}, globals_meta) +end -- Dump global labels. local function dumpglobals(out, lvl) @@ -197,36 +224,60 @@ end local function writeglobals(out, prefix) local t = {} for name, n in pairs(map_global) do t[n] = name end - out:write("enum {\n") - for i=10,next_global-1 do - out:write(" ", prefix, gsub(t[i], "@.*", ""), ",\n") + if luamode then + local n = 0 + for i=10,next_global-1 do + out:write("local ", prefix, gsub(t[i], "@.*", ""), "\t= ", n, "\n") + n = n + 1 + end + out:write("local ", prefix, "_MAX\t= ", n, "\n") --for compatibility with the C protocol + out:write("local DASM_MAXGLOBAL\t= ", n, "\n") + else + out:write("enum {\n") + for i=10,next_global-1 do + out:write(" ", prefix, gsub(t[i], "@.*", ""), ",\n") + end + out:write(" ", prefix, "_MAX\n};\n") end - out:write(" ", prefix, "_MAX\n};\n") end -- Write global label names. local function writeglobalnames(out, name) local t = {} for name, n in pairs(map_global) do t[n] = name end - out:write("static const char *const ", name, "[] = {\n") - for i=10,next_global-1 do - out:write(" \"", t[i], "\",\n") + if luamode then + out:write("local ", name, " = {\n") + for i=10,next_global-1 do + out:write(" ", i == 10 and "[0] = " or "", "\"", t[i], "\",\n") + end + out:write("}\n") + else + out:write("static const char *const ", name, "[] = {\n") + for i=10,next_global-1 do + out:write(" \"", t[i], "\",\n") + end + out:write(" (const char *)0\n};\n") end - out:write(" (const char *)0\n};\n") end ------------------------------------------------------------------------------ -- Extern label name -> extern label number. With auto assignment on 1st use. -local next_extern = -1 -local map_extern = setmetatable({}, { __index = function(t, name) +local next_extern, map_extern + +local extern_meta = { __index = function(t, name) -- No restrictions on the name for now. local n = next_extern if n < -256 then werror("too many extern labels") end next_extern = n - 1 t[name] = n return n -end}) +end} + +local function init_map_extern() + next_extern = -1 + map_extern = setmetatable({}, extern_meta) +end -- Dump extern labels. local function dumpexterns(out, lvl) @@ -243,11 +294,19 @@ end local function writeexternnames(out, name) local t = {} for name, n in pairs(map_extern) do t[-n] = name end - out:write("static const char *const ", name, "[] = {\n") - for i=1,-next_extern-1 do - out:write(" \"", t[i], "\",\n") + if luamode then + out:write("local ", name, " = {\n") + for i=1,-next_extern-1 do + out:write(i==1 and "[0] = " or "", "\"", t[i], "\",\n") + end + out:write("}\n") + else + out:write("static const char *const ", name, "[] = {\n") + for i=1,-next_extern-1 do + out:write(" \"", t[i], "\",\n") + end + out:write(" (const char *)0\n};\n") end - out:write(" (const char *)0\n};\n") end ------------------------------------------------------------------------------ @@ -262,8 +321,13 @@ local map_reg_valid_index = {} -- Int. register name -> valid index register? local map_reg_needrex = {} -- Int. register name -> need rex vs. no rex. local reg_list = {} -- Canonical list of int. register names. -local map_type = {} -- Type name -> { ctype, reg } -local ctypenum = 0 -- Type number (for _PTx macros). +local map_type -- Type name -> { ctype, reg } +local ctypenum -- Type number (for _PTx macros). + +local function init_map_type() + map_type = {} + ctypenum = 0 +end local addrsize = x64 and "q" or "d" -- Size for address operands. @@ -631,7 +695,7 @@ end local function immexpr(expr) -- &expr (pointer) if sub(expr, 1, 1) == "&" then - return "iPJ", format("(ptrdiff_t)(%s)", sub(expr,2)) + return "iPJ", format(luamode and "(%s)" or "(ptrdiff_t)(%s)", sub(expr,2)) end local prefix = sub(expr, 1, 2) @@ -837,7 +901,11 @@ local function parseoperand(param) -- type[idx], type[idx].field, type->field -> [reg+offset_expr] if not tp then werror("bad operand `"..param.."'") end t.mode = "xm" - t.disp = format(tp.ctypefmt, tailr) + if luamode then + t.disp = tp.ctypefmt(tailr) + else + t.disp = format(tp.ctypefmt, tailr) + end else t.mode, t.imm = immexpr(expr) if sub(t.mode, -1) == "J" then @@ -1202,6 +1270,8 @@ local map_op = { andnps_2 = "rmo:0F55rM", andpd_2 = "rmo:660F54rM", andps_2 = "rmo:0F54rM", + fxsave_1 = "x.:0FAE0m", + fxrstor_1 = "x.:0FAE1m", clflush_1 = "x.:0FAE7m", cmppd_3 = "rmio:660FC2rMU", cmpps_3 = "rmio:0FC2rMU", @@ -1990,8 +2060,13 @@ if x64 then end wputop(sz, opcode, rex) if vreg then waction("VREG", vreg); wputxb(0) end - waction("IMM_D", format("(unsigned int)(%s)", op64)) - waction("IMM_D", format("(unsigned int)((%s)>>32)", op64)) + if luamode then + waction("IMM_D", format("ffi.cast(\"uintptr_t\", %s) %% 2^32", op64)) + waction("IMM_D", format("ffi.cast(\"uintptr_t\", %s) / 2^32", op64)) + else + waction("IMM_D", format("(unsigned int)(%s)", op64)) + waction("IMM_D", format("(unsigned int)((%s)>>32)", op64)) + end end end @@ -2137,16 +2212,41 @@ map_op[".type_3"] = function(params, nparams) if reg and not map_reg_valid_base[reg] then werror("bad base register `"..(map_reg_rev[reg] or reg).."'") end - -- Add #type to defines. A bit unclean to put it in map_archdef. - map_archdef["#"..name] = "sizeof("..ctype..")" + -- Add #type to current defines table. + g_map_def["#"..name] = luamode and "ffi.sizeof(\""..ctype.."\")" or "sizeof("..ctype..")" -- Add new type and emit shortcut define. local num = ctypenum + 1 + local ctypefmt + if luamode then + ctypefmt = function(tailr) + local index, field + index, field = match(tailr, "^(%b[])(.*)") + index = index and sub(index, 2, -2) + field = field or tailr + field = match(field, "^%->(.*)") or match(field, "^%.(.*)") + if not (index or field) then + werror("invalid syntax `"..tailr.."`") + end + local Da = index and format("Da%X(%s)", num, index) + local Dt = field and format("Dt%X(\"%s\")", num, field) + return Da and Dt and Da.."+"..Dt or Da or Dt + end + else + ctypefmt = format("Dt%X(%%s)", num) + end map_type[name] = { ctype = ctype, - ctypefmt = format("Dt%X(%%s)", num), + ctypefmt = ctypefmt, reg = reg, } - wline(format("#define Dt%X(_V) (int)(ptrdiff_t)&(((%s *)0)_V)", num, ctype)) + if luamode then + wline(format("local Dt%X; do local ct=ffi.typeof(\"%s\"); function Dt%X(f) return ffi.offsetof(ct,f) or error(string.format(\"'struct %s' has no member named '%%s'\", f)) end; end", + num, ctype, num, ctype)) + wline(format("local Da%X; do local sz=ffi.sizeof(\"%s\"); function Da%X(i) return i*sz end; end", + num, ctype, num)) + else + wline(format("#define Dt%X(_V) (int)(ptrdiff_t)&(((%s *)0)_V)", num, ctype)) + end ctypenum = num end map_op[".type_2"] = map_op[".type_3"] @@ -2202,12 +2302,19 @@ end -- Setup the arch-specific module. function _M.setup(arch, opt) g_arch, g_opt = arch, opt + luamode = g_opt.lang == "lua" + init_actionlist() + init_map_global() + init_map_extern() + init_map_type() end -- Merge the core maps and the arch-specific maps. function _M.mergemaps(map_coreop, map_def) setmetatable(map_op, { __index = map_coreop }) setmetatable(map_def, { __index = map_archdef }) + -- Hold a ref. to map_def to store `#type` defines in. + g_map_def = map_def return map_op, map_def end diff --git a/dynasm/dynasm.lua b/dynasm/dynasm.lua index 145fb0cc..bdade87e 100644 --- a/dynasm/dynasm.lua +++ b/dynasm/dynasm.lua @@ -45,6 +45,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -- Cache library functions. local type, pairs, ipairs = type, pairs, ipairs local pcall, error, assert = pcall, error, assert +local select, tostring = select, tostring local _s = string local sub, match, gmatch, gsub = _s.sub, _s.match, _s.gmatch, _s.gsub local format, rep, upper = _s.format, _s.rep, _s.upper @@ -54,18 +55,70 @@ local exit = os.exit local io = io local stdin, stdout, stderr = io.stdin, io.stdout, io.stderr +-- Helper to update a table with the elements another table. +local function update(dst, src) + if src then + for k,v in pairs(src) do + dst[k] = v + end + end + return dst +end + ------------------------------------------------------------------------------ -- Program options. -local g_opt = {} +local g_opt -- Global state for current file. local g_fname, g_curline, g_indent, g_lineno, g_synclineno, g_arch -local g_errcount = 0 +local g_errcount -- Write buffer for output file. local g_wbuffer, g_capbuffer +-- Map for defines (initially empty, chains to arch-specific map). +local map_def + +-- Sections names. +local map_sections + +-- The opcode map. Begins as an empty map set to inherit from map_initop, +-- It's later changed to inherit from the arch-specific map, which itself is set +-- to inherit from map_coreop. +local map_op + +-- The initial opcode map to initialize map_op with on each global reset. +local map_initop = {} + +-- Dummy action flush function. Replaced with arch-specific function later. +local function dummy_wflush(term) end +local wflush + +-- Init/reset the global state for processing a new file. +local function reset() + g_opt = {} + g_opt.dumpdef = 0 + g_opt.include = { "" } + g_opt.lang = nil + g_opt.comment = "//|" + g_opt.endcomment = "" + g_opt.cpp = true -- Write `#line` directives + g_fname = nil + g_curline = nil + g_indent = "" + g_lineno = 0 + g_synclineno = -1 + g_errcount = 0 + g_arch = nil + g_wbuffer = {} + g_capbuffer = nil + map_def = {} + map_sections = {} + map_op = setmetatable({}, { __index = map_initop }) + wflush = dummy_wflush +end + ------------------------------------------------------------------------------ -- Write an output line (or callback function) to the buffer. @@ -85,15 +138,13 @@ end -- Resync CPP line numbers. local function wsync() if g_synclineno ~= g_lineno and g_opt.cpp then - wline("#line "..g_lineno..' "'..g_fname..'"') + if not g_opt.lang == "lua" then + wline("#line "..g_lineno..' "'..g_fname..'"') + end g_synclineno = g_lineno end end --- Dummy action flush function. Replaced with arch-specific function later. -local function wflush(term) -end - -- Dump all buffered output lines. local function wdumplines(out, buf) for _,line in ipairs(buf) do @@ -171,8 +222,6 @@ end -- Core pseudo-opcodes. local map_coreop = {} --- Dummy opcode map. Replaced by arch-specific map. -local map_op = {} -- Forward declarations. local dostmt @@ -180,9 +229,6 @@ local readfile ------------------------------------------------------------------------------ --- Map for defines (initially empty, chains to arch-specific map). -local map_def = {} - -- Pseudo-opcode to define a substitution. map_coreop[".define_2"] = function(params, nparams) if not params then return nparams == 1 and "name" or "name, subst" end @@ -379,11 +425,11 @@ map_coreop[".include_1"] = function(params) end -- Make .include and conditionals initially available, too. -map_op[".include_1"] = map_coreop[".include_1"] -map_op[".if_1"] = map_coreop[".if_1"] -map_op[".elif_1"] = map_coreop[".elif_1"] -map_op[".else_0"] = map_coreop[".else_0"] -map_op[".endif_0"] = map_coreop[".endif_0"] +map_initop[".include_1"] = map_coreop[".include_1"] +map_initop[".if_1"] = map_coreop[".if_1"] +map_initop[".elif_1"] = map_coreop[".elif_1"] +map_initop[".else_0"] = map_coreop[".else_0"] +map_initop[".endif_0"] = map_coreop[".endif_0"] ------------------------------------------------------------------------------ @@ -583,9 +629,6 @@ end ------------------------------------------------------------------------------ --- Sections names. -local map_sections = {} - -- Pseudo-opcode to define code sections. -- TODO: Data sections, BSS sections. Needs extra C code and API. map_coreop[".section_*"] = function(params) @@ -599,10 +642,18 @@ map_coreop[".section_*"] = function(params) werror("bad section name `"..name.."'") end map_sections[#map_sections+1] = name - wline(format("#define DASM_SECTION_%s\t%d", upper(name), sn-1)) + if g_opt.lang == "lua" then + wline(format("local DASM_SECTION_%s\t= %d", upper(name), sn-1)) + else + wline(format("#define DASM_SECTION_%s\t%d", upper(name), sn-1)) + end map_op[opname] = function(params) g_arch.section(sn-1) end end - wline(format("#define DASM_MAXSECTION\t\t%d", #map_sections)) + if g_opt.lang == "lua" then + wline(format("local DASM_MAXSECTION\t= %d", #map_sections)) + else + wline(format("#define DASM_MAXSECTION\t\t%d", #map_sections)) + end end -- Dump all sections. @@ -635,7 +686,9 @@ local function loadarch(arch) g_arch = m_arch wflush = m_arch.passcb(wline, werror, wfatal, wwarn) m_arch.setup(arch, g_opt) - map_op, map_def = m_arch.mergemaps(map_coreop, map_def) + local arch_map_op + arch_map_op, map_def = m_arch.mergemaps(map_coreop, map_def) + map_op = setmetatable(map_op, { __index = arch_map_op }) end -- Dump architecture description. @@ -691,19 +744,27 @@ end -- Pseudo-opcode to set the architecture. -- Only initially available (map_op is replaced when called). -map_op[".arch_1"] = function(params) +map_initop[".arch_1"] = function(params) if not params then return "name" end local err = loadarch(params[1]) if err then wfatal(err) end - wline(format("#if DASM_VERSION != %d", _info.vernum)) - wline('#error "Version mismatch between DynASM and included encoding engine"') - wline("#endif") + if g_opt.lang == "lua" then + wline(format("if dasm._VERSION ~= %d then", _info.vernum)) + wline(' error("Version mismatch between DynASM and included encoding engine")') + wline("end") + else + wline(format("#if DASM_VERSION != %d", _info.vernum)) + wline('#error "Version mismatch between DynASM and included encoding engine"') + wline("#endif") + end end -- Dummy .arch pseudo-opcode to improve the error report. map_coreop[".arch_1"] = function(params) if not params then return "name" end - wfatal("duplicate .arch statement") + if g_arch._info.arch ~= params[1] then + wfatal("invalid .arch statement. arch already loaded: `", g_arch._info.arch, "`.") + end end ------------------------------------------------------------------------------ @@ -859,6 +920,9 @@ local function doline(line) -- Strip assembler comments. aline = gsub(aline, "//.*$", "") + if g_opt.lang == "lua" then + aline = gsub(aline, "%-%-.*$", "") + end -- Split line into statements at semicolons. if match(aline, ";") then @@ -872,7 +936,21 @@ end -- Write DynASM header. local function dasmhead(out) - out:write(format([[ + if not g_opt.comment then return end + if g_opt.lang == "lua" then + out:write(format([[ +-- +-- This file has been pre-processed with DynASM. +-- %s +-- DynASM version %s, DynASM %s version %s +-- DO NOT EDIT! The original file is in "%s". +-- + +]], _info.url, + _info.version, g_arch._info.arch, g_arch._info.version, + g_fname)) + else + out:write(format([[ /* ** This file has been pre-processed with DynASM. ** %s @@ -883,14 +961,11 @@ local function dasmhead(out) ]], _info.url, _info.version, g_arch._info.arch, g_arch._info.version, g_fname)) + end end -- Read input file. readfile = function(fin) - g_indent = "" - g_lineno = 0 - g_synclineno = -1 - -- Process all lines. for line in fin:lines() do g_lineno = g_lineno + 1 @@ -911,8 +986,10 @@ local function writefile(outfile) -- Open output file. if outfile == nil or outfile == "-" then fout = stdout - else + elseif type(outfile) == "string" then fout = assert(io.open(outfile, "w")) + else + fout = outfile end -- Write all buffered lines @@ -927,10 +1004,6 @@ end -- Translate an input file to an output file. local function translate(infile, outfile) - g_wbuffer = {} - g_indent = "" - g_lineno = 0 - g_synclineno = -1 -- Put header. wline(dasmhead) @@ -940,9 +1013,12 @@ local function translate(infile, outfile) if infile == "-" then g_fname = "(stdin)" fin = stdin - else + elseif type(infile) == "string" then g_fname = infile fin = assert(io.open(infile, "r")) + else + g_fname = "=(translate)" + fin = infile end readfile(fin) @@ -974,7 +1050,7 @@ function opt_map.help() stdout:write("DynASM ", _info.version, " ", _info.release, " ", _info.url, "\n") stdout:write[[ -Usage: dynasm [OPTION]... INFILE.dasc|- +Usage: dynasm [OPTION]... INFILE.dasc|INFILE.dasl|- -h, --help Display this help text. -V, --version Display version and copyright information. @@ -982,6 +1058,8 @@ Usage: dynasm [OPTION]... INFILE.dasc|- -o, --outfile FILE Output file name (default is stdout). -I, --include DIR Add directory to the include search path. + -l, --lang C|Lua Generate C or Lua code (default C for dasc, Lua for dasl). + -c, --ccomment Use /* */ comments for assembler lines. -C, --cppcomment Use // comments for assembler lines (default). -N, --nocomment Suppress assembler lines in output. @@ -1007,6 +1085,7 @@ function opt_map.version() end -- Misc. options. +function opt_map.lang(args) g_opt.lang = optparam(args):lower() end function opt_map.outfile(args) g_opt.outfile = optparam(args) end function opt_map.include(args) insert(g_opt.include, 1, optparam(args)) end function opt_map.ccomment() g_opt.comment = "/*|"; g_opt.endcomment = " */" end @@ -1023,6 +1102,7 @@ function opt_map.dumpdef() g_opt.dumpdef = g_opt.dumpdef + 1 end local opt_alias = { h = "help", ["?"] = "help", V = "version", o = "outfile", I = "include", + l = "lang", c = "ccomment", C = "cppcomment", N = "nocomment", M = "maccomment", L = "nolineno", F = "flushline", P = "dumpdef", A = "dumparch", @@ -1038,14 +1118,43 @@ local function parseopt(opt, args) f(args) end +local languages = {c = true, lua = true} +local langext = {dasc = "c", dasl = "lua"} + +--Set language options (Lua or C code gen) based on file extension. +local function setlang(infile) + + -- Infer language from file extension, if `lang` not set. + if not g_opt.lang and type(infile) == "string" then + g_opt.lang = langext[match(infile, "%.([^%.]+)$")] or "c" + end + + -- Check that the `lang` option is valid. + if not languages[g_opt.lang] then + opterror("invalid language `", tostring(g_opt.lang), "`.") + end + + -- Adjust comment options for Lua mode. + if g_opt.lang == "lua" then + if g_opt.comment then + g_opt.cpp = false + g_opt.comment = "--|" + g_opt.endcomment = "" + end + -- Set initial defines only available in Lua mode. + local ffi = require("ffi") + map_def.ARCH = ffi.arch --for `.arch ARCH` + map_def[upper(ffi.arch)] = 1 --for `.if X86 ...` + map_def.OS = ffi.os --for `.if OS == 'Windows'` + map_def[upper(ffi.os)] = 1 --for `.if WINDOWS ...` + end +end + -- Parse arguments. local function parseargs(args) - -- Default options. - g_opt.comment = "//|" - g_opt.endcomment = "" - g_opt.cpp = true - g_opt.dumpdef = 0 - g_opt.include = { "" } + + --Reset globals. + reset() -- Process all option arguments. args.argn = 1 @@ -1073,22 +1182,140 @@ local function parseargs(args) opt_map.help() end + local infile = args[args.argn] + + -- Set language options. + setlang(infile) + -- Translate a single input file to a single output file -- TODO: Handle multiple files? - translate(args[args.argn], g_opt.outfile) + translate(infile, g_opt.outfile) end ------------------------------------------------------------------------------ --- Add the directory dynasm.lua resides in to the Lua module search path. -local arg = arg -if arg and arg[0] then - prefix = match(arg[0], "^(.*[/\\])") - if package and prefix then package.path = prefix.."?.lua;"..package.path end +if ... == "dynasm" then -- use as module + + -- Make a reusable translate() function with support for setting options. + local translate = function(infile, outfile, opt) + reset() + update(g_opt, opt) + setlang(infile) + if g_opt.subst then + for name, subst in pairs(g_opt.subst) do + map_def[name] = tostring(subst) + end + end + translate(infile, outfile) + end + + -- Dummy file:close() method. + local function dummyclose() + return true + end + + -- Create a pseudo-file object that implements the file:lines() method + -- which reads data from a string. + local function string_infile(s) + local lines = function() + local term = + match(s, "\r\n") and "\r\n" or + match(s, "\r") and "\r" or + match(s, "\n") and "\n" or "" + return gmatch(s, "([^\n\r]*)"..term) + end + return {lines = lines, close = dummyclose} + end + + -- Create a pseudo-file object that implements the file:write() method + -- which forwards each non-empty value to a function. + local function func_outfile(func) + local function write(_, ...) + for i = 1, select('#', ...) do + local v = select(i, ...) + assert(type(v) == "string" or type(v) == "number", "invalid value") + local s = tostring(v) + if #s > 0 then + func(s) + end + end + return true + end + return {write = write, close = dummyclose} + end + + -- Create a pseudo-file object that accumulates writes to a table. + local function table_outfile(t) + return func_outfile(function(s) + t[#t+1] = s + end) + end + + -- Translate an input file to a string. + local function translate_tostring(infile, opt) + local t = {} + translate(infile, table_outfile(t), opt) + return table.concat(t) + end + + -- Create an iterator that translates an input file + -- and returns the translated file in chunks. + local function translate_toiter(infile, opt) + return coroutine.wrap(function() + translate(infile, func_outfile(coroutine.yield), opt) + end) + end + + -- Load a dasl file and return it as a Lua chunk. + local function dasl_loadfile(infile, opt) + local opt = update({lang = "lua"}, opt) + local read = translate_toiter(infile, opt) + local filename = type(infile) == "string" and infile or "=(load)" + return load(read, filename) + end + + -- Load a dasl string and return it as a Lua chunk. + local function dasl_loadstring(s, opt) + return dasl_loadfile(string_infile(s), opt) + end + + -- Register a module loader for *.dasl files. + insert(package.loaders, function(modname) + local daslpath = gsub(gsub(package.path, "%.lua;", ".dasl;"), "%.lua$", ".dasl") + local path, reason = package.searchpath(modname, daslpath) + if not path then return reason end + return function() + local chunk = assert(dasl_loadfile(path, {comment = false})) + return chunk(modname) + end + end) + + -- Make and return the DynASM API. + return { + --low-level intf. + translate = translate, + string_infile = string_infile, + func_outfile = func_outfile, + table_outfile = table_outfile, + translate_tostring = translate_tostring, + translate_toiter = translate_toiter, + --hi-level intf. + loadfile = dasl_loadfile, + loadstring = dasl_loadstring, + } + +else -- use as standalone script + + -- Add the directory dynasm.lua resides in to the Lua module search path. + local arg = arg + if arg and arg[0] then + prefix = match(arg[0], "^(.*[/\\])") + if package and prefix then package.path = prefix.."?.lua;"..package.path end + end + + -- Start DynASM. + parseargs{...} + end --- Start DynASM. -parseargs{...} - ------------------------------------------------------------------------------ -