mirror of
https://github.com/LuaJIT/LuaJIT.git
synced 2025-02-08 23:44:08 +00:00
DynASM: Lua mode
This commit is contained in:
parent
4b76a00f65
commit
c4181af08d
274
dynasm/dasm.lua
Normal file
274
dynasm/dasm.lua
Normal file
@ -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
|
5
dynasm/dasm_extern.h
Normal file
5
dynasm/dasm_extern.h
Normal file
@ -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)
|
144
dynasm/dasm_mm.lua
Normal file
144
dynasm/dasm_mm.lua
Normal file
@ -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}
|
@ -8,5 +8,15 @@
|
|||||||
-- All the interesting stuff is there.
|
-- 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.
|
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
|
||||||
|
|
||||||
|
11
dynasm/dasm_x86.c
Normal file
11
dynasm/dasm_x86.c
Normal file
@ -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"
|
@ -5,7 +5,7 @@
|
|||||||
-- See dynasm.lua for full copyright notice.
|
-- See dynasm.lua for full copyright notice.
|
||||||
------------------------------------------------------------------------------
|
------------------------------------------------------------------------------
|
||||||
|
|
||||||
local x64 = x64
|
local x64 = rawget(_G, "x64") --rawget so it works with strict.lua
|
||||||
|
|
||||||
-- Module information:
|
-- Module information:
|
||||||
local _info = {
|
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
|
local band, bxor, shl, shr = bit.band, bit.bxor, bit.lshift, bit.rshift
|
||||||
|
|
||||||
-- Inherited tables and callbacks.
|
-- Inherited tables and callbacks.
|
||||||
local g_opt, g_arch
|
local g_opt, g_arch, g_map_def
|
||||||
local wline, werror, wfatal, wwarn
|
local wline, werror, wfatal, wwarn
|
||||||
|
|
||||||
|
-- Global flag to generate Lua code instead of C code.
|
||||||
|
local luamode
|
||||||
|
|
||||||
-- Action name list.
|
-- Action name list.
|
||||||
-- CHECK: Keep this in sync with the C code!
|
-- CHECK: Keep this in sync with the C code!
|
||||||
local action_names = {
|
local action_names = {
|
||||||
@ -74,14 +77,20 @@ local map_action = {}
|
|||||||
local actfirst = 256-#action_names
|
local actfirst = 256-#action_names
|
||||||
|
|
||||||
-- Action list buffer and string (only used to remove dupes).
|
-- Action list buffer and string (only used to remove dupes).
|
||||||
local actlist = {}
|
local actlist, actstr
|
||||||
local actstr = ""
|
|
||||||
|
|
||||||
-- Argument list for next dasm_put(). Start with offset 0 into action list.
|
-- Argument list for next dasm_put().
|
||||||
local actargs = { 0 }
|
local actargs
|
||||||
|
|
||||||
-- Current number of section buffer positions for dasm_put().
|
-- 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
|
local last = actlist[nn] or 255
|
||||||
actlist[nn] = nil -- Remove last byte.
|
actlist[nn] = nil -- Remove last byte.
|
||||||
if nn == 0 then nn = 1 end
|
if nn == 0 then nn = 1 end
|
||||||
|
if luamode then
|
||||||
|
out:write("local ", name, " = ffi.new('const uint8_t[", nn, "]', {\n")
|
||||||
|
else
|
||||||
out:write("static const unsigned char ", name, "[", nn, "] = {\n")
|
out:write("static const unsigned char ", name, "[", nn, "] = {\n")
|
||||||
|
end
|
||||||
local s = " "
|
local s = " "
|
||||||
for n,b in ipairs(actlist) do
|
for n,b in ipairs(actlist) do
|
||||||
s = s..b..","
|
s = s..b..","
|
||||||
@ -116,8 +129,12 @@ local function writeactions(out, name)
|
|||||||
s = " "
|
s = " "
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
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.
|
out:write(s, last, "\n};\n\n") -- Add last byte back.
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
------------------------------------------------------------------------------
|
------------------------------------------------------------------------------
|
||||||
|
|
||||||
@ -136,8 +153,12 @@ end
|
|||||||
|
|
||||||
-- Add call to embedded DynASM C code.
|
-- Add call to embedded DynASM C code.
|
||||||
local function wcall(func, args)
|
local function wcall(func, args)
|
||||||
|
if luamode then
|
||||||
|
wline(format("dasm.%s(Dst, %s)", func, concat(args, ", ")), true)
|
||||||
|
else
|
||||||
wline(format("dasm_%s(Dst, %s);", func, concat(args, ", ")), true)
|
wline(format("dasm_%s(Dst, %s);", func, concat(args, ", ")), true)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- Delete duplicate action list chunks. A tad slow, but so what.
|
-- Delete duplicate action list chunks. A tad slow, but so what.
|
||||||
local function dedupechunk(offset)
|
local function dedupechunk(offset)
|
||||||
@ -172,15 +193,21 @@ end
|
|||||||
------------------------------------------------------------------------------
|
------------------------------------------------------------------------------
|
||||||
|
|
||||||
-- Global label name -> global label number. With auto assignment on 1st use.
|
-- Global label name -> global label number. With auto assignment on 1st use.
|
||||||
local next_global = 10
|
local next_global, map_global
|
||||||
local map_global = setmetatable({}, { __index = function(t, name)
|
|
||||||
|
local globals_meta = { __index = function(t, name)
|
||||||
if not match(name, "^[%a_][%w_@]*$") then werror("bad global label") end
|
if not match(name, "^[%a_][%w_@]*$") then werror("bad global label") end
|
||||||
local n = next_global
|
local n = next_global
|
||||||
if n > 246 then werror("too many global labels") end
|
if n > 246 then werror("too many global labels") end
|
||||||
next_global = n + 1
|
next_global = n + 1
|
||||||
t[name] = n
|
t[name] = n
|
||||||
return n
|
return n
|
||||||
end})
|
end}
|
||||||
|
|
||||||
|
local function init_map_global()
|
||||||
|
next_global = 10
|
||||||
|
map_global = setmetatable({}, globals_meta)
|
||||||
|
end
|
||||||
|
|
||||||
-- Dump global labels.
|
-- Dump global labels.
|
||||||
local function dumpglobals(out, lvl)
|
local function dumpglobals(out, lvl)
|
||||||
@ -197,36 +224,60 @@ end
|
|||||||
local function writeglobals(out, prefix)
|
local function writeglobals(out, prefix)
|
||||||
local t = {}
|
local t = {}
|
||||||
for name, n in pairs(map_global) do t[n] = name end
|
for name, n in pairs(map_global) do t[n] = name end
|
||||||
|
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")
|
out:write("enum {\n")
|
||||||
for i=10,next_global-1 do
|
for i=10,next_global-1 do
|
||||||
out:write(" ", prefix, gsub(t[i], "@.*", ""), ",\n")
|
out:write(" ", prefix, gsub(t[i], "@.*", ""), ",\n")
|
||||||
end
|
end
|
||||||
out:write(" ", prefix, "_MAX\n};\n")
|
out:write(" ", prefix, "_MAX\n};\n")
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- Write global label names.
|
-- Write global label names.
|
||||||
local function writeglobalnames(out, name)
|
local function writeglobalnames(out, name)
|
||||||
local t = {}
|
local t = {}
|
||||||
for name, n in pairs(map_global) do t[n] = name end
|
for name, n in pairs(map_global) do t[n] = name end
|
||||||
|
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")
|
out:write("static const char *const ", name, "[] = {\n")
|
||||||
for i=10,next_global-1 do
|
for i=10,next_global-1 do
|
||||||
out:write(" \"", t[i], "\",\n")
|
out:write(" \"", t[i], "\",\n")
|
||||||
end
|
end
|
||||||
out:write(" (const char *)0\n};\n")
|
out:write(" (const char *)0\n};\n")
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
------------------------------------------------------------------------------
|
------------------------------------------------------------------------------
|
||||||
|
|
||||||
-- Extern label name -> extern label number. With auto assignment on 1st use.
|
-- Extern label name -> extern label number. With auto assignment on 1st use.
|
||||||
local next_extern = -1
|
local next_extern, map_extern
|
||||||
local map_extern = setmetatable({}, { __index = function(t, name)
|
|
||||||
|
local extern_meta = { __index = function(t, name)
|
||||||
-- No restrictions on the name for now.
|
-- No restrictions on the name for now.
|
||||||
local n = next_extern
|
local n = next_extern
|
||||||
if n < -256 then werror("too many extern labels") end
|
if n < -256 then werror("too many extern labels") end
|
||||||
next_extern = n - 1
|
next_extern = n - 1
|
||||||
t[name] = n
|
t[name] = n
|
||||||
return n
|
return n
|
||||||
end})
|
end}
|
||||||
|
|
||||||
|
local function init_map_extern()
|
||||||
|
next_extern = -1
|
||||||
|
map_extern = setmetatable({}, extern_meta)
|
||||||
|
end
|
||||||
|
|
||||||
-- Dump extern labels.
|
-- Dump extern labels.
|
||||||
local function dumpexterns(out, lvl)
|
local function dumpexterns(out, lvl)
|
||||||
@ -243,12 +294,20 @@ end
|
|||||||
local function writeexternnames(out, name)
|
local function writeexternnames(out, name)
|
||||||
local t = {}
|
local t = {}
|
||||||
for name, n in pairs(map_extern) do t[-n] = name end
|
for name, n in pairs(map_extern) do t[-n] = name end
|
||||||
|
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")
|
out:write("static const char *const ", name, "[] = {\n")
|
||||||
for i=1,-next_extern-1 do
|
for i=1,-next_extern-1 do
|
||||||
out:write(" \"", t[i], "\",\n")
|
out:write(" \"", t[i], "\",\n")
|
||||||
end
|
end
|
||||||
out:write(" (const char *)0\n};\n")
|
out:write(" (const char *)0\n};\n")
|
||||||
end
|
end
|
||||||
|
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 map_reg_needrex = {} -- Int. register name -> need rex vs. no rex.
|
||||||
local reg_list = {} -- Canonical list of int. register names.
|
local reg_list = {} -- Canonical list of int. register names.
|
||||||
|
|
||||||
local map_type = {} -- Type name -> { ctype, reg }
|
local map_type -- Type name -> { ctype, reg }
|
||||||
local ctypenum = 0 -- Type number (for _PTx macros).
|
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.
|
local addrsize = x64 and "q" or "d" -- Size for address operands.
|
||||||
|
|
||||||
@ -631,7 +695,7 @@ end
|
|||||||
local function immexpr(expr)
|
local function immexpr(expr)
|
||||||
-- &expr (pointer)
|
-- &expr (pointer)
|
||||||
if sub(expr, 1, 1) == "&" then
|
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
|
end
|
||||||
|
|
||||||
local prefix = sub(expr, 1, 2)
|
local prefix = sub(expr, 1, 2)
|
||||||
@ -837,7 +901,11 @@ local function parseoperand(param)
|
|||||||
-- type[idx], type[idx].field, type->field -> [reg+offset_expr]
|
-- type[idx], type[idx].field, type->field -> [reg+offset_expr]
|
||||||
if not tp then werror("bad operand `"..param.."'") end
|
if not tp then werror("bad operand `"..param.."'") end
|
||||||
t.mode = "xm"
|
t.mode = "xm"
|
||||||
|
if luamode then
|
||||||
|
t.disp = tp.ctypefmt(tailr)
|
||||||
|
else
|
||||||
t.disp = format(tp.ctypefmt, tailr)
|
t.disp = format(tp.ctypefmt, tailr)
|
||||||
|
end
|
||||||
else
|
else
|
||||||
t.mode, t.imm = immexpr(expr)
|
t.mode, t.imm = immexpr(expr)
|
||||||
if sub(t.mode, -1) == "J" then
|
if sub(t.mode, -1) == "J" then
|
||||||
@ -1202,6 +1270,8 @@ local map_op = {
|
|||||||
andnps_2 = "rmo:0F55rM",
|
andnps_2 = "rmo:0F55rM",
|
||||||
andpd_2 = "rmo:660F54rM",
|
andpd_2 = "rmo:660F54rM",
|
||||||
andps_2 = "rmo:0F54rM",
|
andps_2 = "rmo:0F54rM",
|
||||||
|
fxsave_1 = "x.:0FAE0m",
|
||||||
|
fxrstor_1 = "x.:0FAE1m",
|
||||||
clflush_1 = "x.:0FAE7m",
|
clflush_1 = "x.:0FAE7m",
|
||||||
cmppd_3 = "rmio:660FC2rMU",
|
cmppd_3 = "rmio:660FC2rMU",
|
||||||
cmpps_3 = "rmio:0FC2rMU",
|
cmpps_3 = "rmio:0FC2rMU",
|
||||||
@ -1990,10 +2060,15 @@ if x64 then
|
|||||||
end
|
end
|
||||||
wputop(sz, opcode, rex)
|
wputop(sz, opcode, rex)
|
||||||
if vreg then waction("VREG", vreg); wputxb(0) end
|
if vreg then waction("VREG", vreg); wputxb(0) end
|
||||||
|
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)", op64))
|
||||||
waction("IMM_D", format("(unsigned int)((%s)>>32)", op64))
|
waction("IMM_D", format("(unsigned int)((%s)>>32)", op64))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
------------------------------------------------------------------------------
|
------------------------------------------------------------------------------
|
||||||
|
|
||||||
@ -2137,16 +2212,41 @@ map_op[".type_3"] = function(params, nparams)
|
|||||||
if reg and not map_reg_valid_base[reg] then
|
if reg and not map_reg_valid_base[reg] then
|
||||||
werror("bad base register `"..(map_reg_rev[reg] or reg).."'")
|
werror("bad base register `"..(map_reg_rev[reg] or reg).."'")
|
||||||
end
|
end
|
||||||
-- Add #type to defines. A bit unclean to put it in map_archdef.
|
-- Add #type to current defines table.
|
||||||
map_archdef["#"..name] = "sizeof("..ctype..")"
|
g_map_def["#"..name] = luamode and "ffi.sizeof(\""..ctype.."\")" or "sizeof("..ctype..")"
|
||||||
-- Add new type and emit shortcut define.
|
-- Add new type and emit shortcut define.
|
||||||
local num = ctypenum + 1
|
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] = {
|
map_type[name] = {
|
||||||
ctype = ctype,
|
ctype = ctype,
|
||||||
ctypefmt = format("Dt%X(%%s)", num),
|
ctypefmt = ctypefmt,
|
||||||
reg = reg,
|
reg = reg,
|
||||||
}
|
}
|
||||||
|
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))
|
wline(format("#define Dt%X(_V) (int)(ptrdiff_t)&(((%s *)0)_V)", num, ctype))
|
||||||
|
end
|
||||||
ctypenum = num
|
ctypenum = num
|
||||||
end
|
end
|
||||||
map_op[".type_2"] = map_op[".type_3"]
|
map_op[".type_2"] = map_op[".type_3"]
|
||||||
@ -2202,12 +2302,19 @@ end
|
|||||||
-- Setup the arch-specific module.
|
-- Setup the arch-specific module.
|
||||||
function _M.setup(arch, opt)
|
function _M.setup(arch, opt)
|
||||||
g_arch, g_opt = 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
|
end
|
||||||
|
|
||||||
-- Merge the core maps and the arch-specific maps.
|
-- Merge the core maps and the arch-specific maps.
|
||||||
function _M.mergemaps(map_coreop, map_def)
|
function _M.mergemaps(map_coreop, map_def)
|
||||||
setmetatable(map_op, { __index = map_coreop })
|
setmetatable(map_op, { __index = map_coreop })
|
||||||
setmetatable(map_def, { __index = map_archdef })
|
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
|
return map_op, map_def
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -45,6 +45,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||||||
-- Cache library functions.
|
-- Cache library functions.
|
||||||
local type, pairs, ipairs = type, pairs, ipairs
|
local type, pairs, ipairs = type, pairs, ipairs
|
||||||
local pcall, error, assert = pcall, error, assert
|
local pcall, error, assert = pcall, error, assert
|
||||||
|
local select, tostring = select, tostring
|
||||||
local _s = string
|
local _s = string
|
||||||
local sub, match, gmatch, gsub = _s.sub, _s.match, _s.gmatch, _s.gsub
|
local sub, match, gmatch, gsub = _s.sub, _s.match, _s.gmatch, _s.gsub
|
||||||
local format, rep, upper = _s.format, _s.rep, _s.upper
|
local format, rep, upper = _s.format, _s.rep, _s.upper
|
||||||
@ -54,18 +55,70 @@ local exit = os.exit
|
|||||||
local io = io
|
local io = io
|
||||||
local stdin, stdout, stderr = io.stdin, io.stdout, io.stderr
|
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.
|
-- Program options.
|
||||||
local g_opt = {}
|
local g_opt
|
||||||
|
|
||||||
-- Global state for current file.
|
-- Global state for current file.
|
||||||
local g_fname, g_curline, g_indent, g_lineno, g_synclineno, g_arch
|
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.
|
-- Write buffer for output file.
|
||||||
local g_wbuffer, g_capbuffer
|
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.
|
-- Write an output line (or callback function) to the buffer.
|
||||||
@ -85,15 +138,13 @@ end
|
|||||||
-- Resync CPP line numbers.
|
-- Resync CPP line numbers.
|
||||||
local function wsync()
|
local function wsync()
|
||||||
if g_synclineno ~= g_lineno and g_opt.cpp then
|
if g_synclineno ~= g_lineno and g_opt.cpp then
|
||||||
|
if not g_opt.lang == "lua" then
|
||||||
wline("#line "..g_lineno..' "'..g_fname..'"')
|
wline("#line "..g_lineno..' "'..g_fname..'"')
|
||||||
|
end
|
||||||
g_synclineno = g_lineno
|
g_synclineno = g_lineno
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Dummy action flush function. Replaced with arch-specific function later.
|
|
||||||
local function wflush(term)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Dump all buffered output lines.
|
-- Dump all buffered output lines.
|
||||||
local function wdumplines(out, buf)
|
local function wdumplines(out, buf)
|
||||||
for _,line in ipairs(buf) do
|
for _,line in ipairs(buf) do
|
||||||
@ -171,8 +222,6 @@ end
|
|||||||
|
|
||||||
-- Core pseudo-opcodes.
|
-- Core pseudo-opcodes.
|
||||||
local map_coreop = {}
|
local map_coreop = {}
|
||||||
-- Dummy opcode map. Replaced by arch-specific map.
|
|
||||||
local map_op = {}
|
|
||||||
|
|
||||||
-- Forward declarations.
|
-- Forward declarations.
|
||||||
local dostmt
|
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.
|
-- Pseudo-opcode to define a substitution.
|
||||||
map_coreop[".define_2"] = function(params, nparams)
|
map_coreop[".define_2"] = function(params, nparams)
|
||||||
if not params then return nparams == 1 and "name" or "name, subst" end
|
if not params then return nparams == 1 and "name" or "name, subst" end
|
||||||
@ -379,11 +425,11 @@ map_coreop[".include_1"] = function(params)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Make .include and conditionals initially available, too.
|
-- Make .include and conditionals initially available, too.
|
||||||
map_op[".include_1"] = map_coreop[".include_1"]
|
map_initop[".include_1"] = map_coreop[".include_1"]
|
||||||
map_op[".if_1"] = map_coreop[".if_1"]
|
map_initop[".if_1"] = map_coreop[".if_1"]
|
||||||
map_op[".elif_1"] = map_coreop[".elif_1"]
|
map_initop[".elif_1"] = map_coreop[".elif_1"]
|
||||||
map_op[".else_0"] = map_coreop[".else_0"]
|
map_initop[".else_0"] = map_coreop[".else_0"]
|
||||||
map_op[".endif_0"] = map_coreop[".endif_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.
|
-- Pseudo-opcode to define code sections.
|
||||||
-- TODO: Data sections, BSS sections. Needs extra C code and API.
|
-- TODO: Data sections, BSS sections. Needs extra C code and API.
|
||||||
map_coreop[".section_*"] = function(params)
|
map_coreop[".section_*"] = function(params)
|
||||||
@ -599,11 +642,19 @@ map_coreop[".section_*"] = function(params)
|
|||||||
werror("bad section name `"..name.."'")
|
werror("bad section name `"..name.."'")
|
||||||
end
|
end
|
||||||
map_sections[#map_sections+1] = name
|
map_sections[#map_sections+1] = name
|
||||||
|
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))
|
wline(format("#define DASM_SECTION_%s\t%d", upper(name), sn-1))
|
||||||
|
end
|
||||||
map_op[opname] = function(params) g_arch.section(sn-1) end
|
map_op[opname] = function(params) g_arch.section(sn-1) end
|
||||||
end
|
end
|
||||||
|
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))
|
wline(format("#define DASM_MAXSECTION\t\t%d", #map_sections))
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- Dump all sections.
|
-- Dump all sections.
|
||||||
local function dumpsections(out, lvl)
|
local function dumpsections(out, lvl)
|
||||||
@ -635,7 +686,9 @@ local function loadarch(arch)
|
|||||||
g_arch = m_arch
|
g_arch = m_arch
|
||||||
wflush = m_arch.passcb(wline, werror, wfatal, wwarn)
|
wflush = m_arch.passcb(wline, werror, wfatal, wwarn)
|
||||||
m_arch.setup(arch, g_opt)
|
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
|
end
|
||||||
|
|
||||||
-- Dump architecture description.
|
-- Dump architecture description.
|
||||||
@ -691,19 +744,27 @@ end
|
|||||||
|
|
||||||
-- Pseudo-opcode to set the architecture.
|
-- Pseudo-opcode to set the architecture.
|
||||||
-- Only initially available (map_op is replaced when called).
|
-- 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
|
if not params then return "name" end
|
||||||
local err = loadarch(params[1])
|
local err = loadarch(params[1])
|
||||||
if err then wfatal(err) end
|
if err then wfatal(err) end
|
||||||
|
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(format("#if DASM_VERSION != %d", _info.vernum))
|
||||||
wline('#error "Version mismatch between DynASM and included encoding engine"')
|
wline('#error "Version mismatch between DynASM and included encoding engine"')
|
||||||
wline("#endif")
|
wline("#endif")
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- Dummy .arch pseudo-opcode to improve the error report.
|
-- Dummy .arch pseudo-opcode to improve the error report.
|
||||||
map_coreop[".arch_1"] = function(params)
|
map_coreop[".arch_1"] = function(params)
|
||||||
if not params then return "name" end
|
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
|
end
|
||||||
|
|
||||||
------------------------------------------------------------------------------
|
------------------------------------------------------------------------------
|
||||||
@ -859,6 +920,9 @@ local function doline(line)
|
|||||||
|
|
||||||
-- Strip assembler comments.
|
-- Strip assembler comments.
|
||||||
aline = gsub(aline, "//.*$", "")
|
aline = gsub(aline, "//.*$", "")
|
||||||
|
if g_opt.lang == "lua" then
|
||||||
|
aline = gsub(aline, "%-%-.*$", "")
|
||||||
|
end
|
||||||
|
|
||||||
-- Split line into statements at semicolons.
|
-- Split line into statements at semicolons.
|
||||||
if match(aline, ";") then
|
if match(aline, ";") then
|
||||||
@ -872,6 +936,20 @@ end
|
|||||||
|
|
||||||
-- Write DynASM header.
|
-- Write DynASM header.
|
||||||
local function dasmhead(out)
|
local function dasmhead(out)
|
||||||
|
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([[
|
out:write(format([[
|
||||||
/*
|
/*
|
||||||
** This file has been pre-processed with DynASM.
|
** This file has been pre-processed with DynASM.
|
||||||
@ -884,13 +962,10 @@ local function dasmhead(out)
|
|||||||
_info.version, g_arch._info.arch, g_arch._info.version,
|
_info.version, g_arch._info.arch, g_arch._info.version,
|
||||||
g_fname))
|
g_fname))
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- Read input file.
|
-- Read input file.
|
||||||
readfile = function(fin)
|
readfile = function(fin)
|
||||||
g_indent = ""
|
|
||||||
g_lineno = 0
|
|
||||||
g_synclineno = -1
|
|
||||||
|
|
||||||
-- Process all lines.
|
-- Process all lines.
|
||||||
for line in fin:lines() do
|
for line in fin:lines() do
|
||||||
g_lineno = g_lineno + 1
|
g_lineno = g_lineno + 1
|
||||||
@ -911,8 +986,10 @@ local function writefile(outfile)
|
|||||||
-- Open output file.
|
-- Open output file.
|
||||||
if outfile == nil or outfile == "-" then
|
if outfile == nil or outfile == "-" then
|
||||||
fout = stdout
|
fout = stdout
|
||||||
else
|
elseif type(outfile) == "string" then
|
||||||
fout = assert(io.open(outfile, "w"))
|
fout = assert(io.open(outfile, "w"))
|
||||||
|
else
|
||||||
|
fout = outfile
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Write all buffered lines
|
-- Write all buffered lines
|
||||||
@ -927,10 +1004,6 @@ end
|
|||||||
|
|
||||||
-- Translate an input file to an output file.
|
-- Translate an input file to an output file.
|
||||||
local function translate(infile, outfile)
|
local function translate(infile, outfile)
|
||||||
g_wbuffer = {}
|
|
||||||
g_indent = ""
|
|
||||||
g_lineno = 0
|
|
||||||
g_synclineno = -1
|
|
||||||
|
|
||||||
-- Put header.
|
-- Put header.
|
||||||
wline(dasmhead)
|
wline(dasmhead)
|
||||||
@ -940,9 +1013,12 @@ local function translate(infile, outfile)
|
|||||||
if infile == "-" then
|
if infile == "-" then
|
||||||
g_fname = "(stdin)"
|
g_fname = "(stdin)"
|
||||||
fin = stdin
|
fin = stdin
|
||||||
else
|
elseif type(infile) == "string" then
|
||||||
g_fname = infile
|
g_fname = infile
|
||||||
fin = assert(io.open(infile, "r"))
|
fin = assert(io.open(infile, "r"))
|
||||||
|
else
|
||||||
|
g_fname = "=(translate)"
|
||||||
|
fin = infile
|
||||||
end
|
end
|
||||||
readfile(fin)
|
readfile(fin)
|
||||||
|
|
||||||
@ -974,7 +1050,7 @@ function opt_map.help()
|
|||||||
stdout:write("DynASM ", _info.version, " ", _info.release, " ", _info.url, "\n")
|
stdout:write("DynASM ", _info.version, " ", _info.release, " ", _info.url, "\n")
|
||||||
stdout:write[[
|
stdout:write[[
|
||||||
|
|
||||||
Usage: dynasm [OPTION]... INFILE.dasc|-
|
Usage: dynasm [OPTION]... INFILE.dasc|INFILE.dasl|-
|
||||||
|
|
||||||
-h, --help Display this help text.
|
-h, --help Display this help text.
|
||||||
-V, --version Display version and copyright information.
|
-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).
|
-o, --outfile FILE Output file name (default is stdout).
|
||||||
-I, --include DIR Add directory to the include search path.
|
-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, --ccomment Use /* */ comments for assembler lines.
|
||||||
-C, --cppcomment Use // comments for assembler lines (default).
|
-C, --cppcomment Use // comments for assembler lines (default).
|
||||||
-N, --nocomment Suppress assembler lines in output.
|
-N, --nocomment Suppress assembler lines in output.
|
||||||
@ -1007,6 +1085,7 @@ function opt_map.version()
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Misc. options.
|
-- 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.outfile(args) g_opt.outfile = optparam(args) end
|
||||||
function opt_map.include(args) insert(g_opt.include, 1, 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
|
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 = {
|
local opt_alias = {
|
||||||
h = "help", ["?"] = "help", V = "version",
|
h = "help", ["?"] = "help", V = "version",
|
||||||
o = "outfile", I = "include",
|
o = "outfile", I = "include",
|
||||||
|
l = "lang",
|
||||||
c = "ccomment", C = "cppcomment", N = "nocomment", M = "maccomment",
|
c = "ccomment", C = "cppcomment", N = "nocomment", M = "maccomment",
|
||||||
L = "nolineno", F = "flushline",
|
L = "nolineno", F = "flushline",
|
||||||
P = "dumpdef", A = "dumparch",
|
P = "dumpdef", A = "dumparch",
|
||||||
@ -1038,14 +1118,43 @@ local function parseopt(opt, args)
|
|||||||
f(args)
|
f(args)
|
||||||
end
|
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.
|
-- Parse arguments.
|
||||||
local function parseargs(args)
|
local function parseargs(args)
|
||||||
-- Default options.
|
|
||||||
g_opt.comment = "//|"
|
--Reset globals.
|
||||||
g_opt.endcomment = ""
|
reset()
|
||||||
g_opt.cpp = true
|
|
||||||
g_opt.dumpdef = 0
|
|
||||||
g_opt.include = { "" }
|
|
||||||
|
|
||||||
-- Process all option arguments.
|
-- Process all option arguments.
|
||||||
args.argn = 1
|
args.argn = 1
|
||||||
@ -1073,13 +1182,130 @@ local function parseargs(args)
|
|||||||
opt_map.help()
|
opt_map.help()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local infile = args[args.argn]
|
||||||
|
|
||||||
|
-- Set language options.
|
||||||
|
setlang(infile)
|
||||||
|
|
||||||
-- Translate a single input file to a single output file
|
-- Translate a single input file to a single output file
|
||||||
-- TODO: Handle multiple files?
|
-- TODO: Handle multiple files?
|
||||||
translate(args[args.argn], g_opt.outfile)
|
translate(infile, g_opt.outfile)
|
||||||
end
|
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.
|
-- Add the directory dynasm.lua resides in to the Lua module search path.
|
||||||
local arg = arg
|
local arg = arg
|
||||||
if arg and arg[0] then
|
if arg and arg[0] then
|
||||||
@ -1090,5 +1316,6 @@ end
|
|||||||
-- Start DynASM.
|
-- Start DynASM.
|
||||||
parseargs{...}
|
parseargs{...}
|
||||||
|
|
||||||
------------------------------------------------------------------------------
|
end
|
||||||
|
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
Loading…
Reference in New Issue
Block a user