dynasmDynASM with Lua mode |
|
local dynasm = require'dynasm'
This is a modified version of DynASM that allows generating, compiling, and running x86 and x86-64 assembly code directly from Lua. It also exposes the DynASM assembler and linker to be used as Lua modules.
Jump To: Examples | DynASM API | DASM API | Changes to DynASM | List of Instructions | List of Directives
require()
DynASM is not an inline assembler, it's a code generator. The following code:
function codegen(Dst)
for i = 1, 3 do
| mov ax, i
end
end
does not run the assembly instruction 3 times when codegen is called, instead, it merely adds the instruction sequence mov ax, 1; mov ax, 2; mov ax, 3
to the dynasm state Dst
when codegen is called. Mixing Lua and ASM code like this has the effect of generating code, not running it.
DynASM has two parts: the assembler/preprocessor, written in Lua, and the the linker/encoder, written in C. dynasm.lua
is the preprocessor. It takes mixed C/ASM code as input (from a file, string or file-like object) and generates C code (to a file, string, or file-like object). Alternatively, it can take mixed Lua/ASM code (like the above example) and generate Lua code, which is what the "Lua mode" part is all about. dasm.lua
is the binding to the C part of DynASM (the linker/encoder) which deals with building the code into executable memory that can be called into.
.dasl
files refer to Lua/ASM files, .dasc
files refer to C/ASM files. dasl files can be used transparently as Lua modules (they are translated on-the-fly).
This simple, self-contained module publishes the function multiply(x, y) -> x * y.
multiply_x86.dasl:
local ffi = require'ffi' --required
local dasm = require'dasm' --required
|.arch x86 --must be the first instruction
|.actionlist actions --make an action list called `actions`
local Dst = dasm.new(actions) --make a dasm state
-- the next chunk of asm code will be added to the action list, and a call
-- to `dasm.put(Dst, 0)` will be generated in its place, which will be copying
-- the code from the start of the action list into the Dst state.
| mov eax, [esp+4]
| imul dword [esp+8]
| ret
local code = Dst:build() --check, link and encode the code
local fptr = ffi.cast('int32_t __cdecl (*) (int32_t x, int32_t y)', code) --take a callable pointer to it
return function(x, y)
local _ = code --pin the code buffer so it doesn't get collected
return fptr(x, y)
end
The best way to understand how the above code is supposed to work is to translate it:
> lua dynasm.lua multiply_x86.dasl
main.lua
:require'dynasm' --hook in the `require` loader for .dasl files
local multiply = require'multiply_x86' --translate, load and run `multiply_x86.dasl`
assert(multiply(-7, 5) == -35)
This is an idea on how you can keep your asm code separated from the plumbing required to build it, and also how you can make separate functions out of different asm chunks from the same dasl file.
funcs_x86.dasl
:local ffi = require'ffi'
local dasm = require'dasm'
|.arch x86
|.actionlist actions
|.globalnames globalnames
local gen = {}
function gen.mul(Dst) --function which generates code into the dynasm state called `Dst`
|->mul: --and returns a "make" function which gets a dasm.globals() map
| mov eax, [esp+4] --and returns a function that knows how to call into its code.
| imul dword [esp+8]
| ret
return function(globals)
return ffi.cast('int32_t __cdecl (*) (int32_t x, int32_t y)', globals.mul)
end
end
function gen.add(Dst)
|->add:
| mov eax, [esp+4]
| add eax, dword [esp+8]
| ret
return function(globals)
return ffi.cast('int32_t __cdecl (*) (int32_t x, int32_t y)', globals.add)
end
end
return {gen = gen, actions = actions, globalnames = globalnames}
funcs.lua
:local dynasm = require'dynasm'
local dasm = require'dasm'
local funcs = require'funcs_x86'
local state, globals = dasm.new(funcs.actions) --create a dynasm state with the generated action list
local M = {} --generate the code, collecting the make functions
for name, gen in pairs(funcs.gen) do
M[name] = gen(state)
end
local buf, size = state:build() --check, link and encode the code
local globals = dasm.globals(globals, funcs.globalnames) --get the map of global_name -> global_addr
for name, make in pairs(M) do --make the callable functions
M[name] = make(globals)
end
M.__buf = buf --pin buf so it doesn't get collected
return M
main.lua
local funcs = require'funcs'
assert(funcs.mul(-7, 5) == -35)
assert(funcs.add(-7, 5) == -2)
local dynasm = require'dynasm'
local gencode, actions = dynasm.loadstring([[
local ffi = require'ffi'
local dasm = require'dasm'
|.arch x86
|.actionlist actions
local function gencode(Dst)
| mov ax, bx
end
return gencode, actions
]])()
local dynasm = require'dynasm'
print(dynasm.translate_tostring'multiply_x86.dasl')
The above is equivalent to the command line:
> lua dynasm.lua multiply_x86.dasl
Tip: You can pre-assemble
foo.dasl
intofoo.lua
--require()
will then choosefoo.lua
overfoo.dasl
, so you basically get transparent caching for free. This speeds up app loading a bit, and you can ship your app without the assembler (you still need to ship the linker/encoder for all the platforms that you support).
Check out the included dynasm_demo_x86.dasl and dynasm_demo.lua modules for more in-depth knowledge about DynASM/Lua interaction. It works on Windows, Linux and OSX, both x86 and x64.
The examples above don't do DynASM enough justice, because DynASM was after all made for building JIT compilers. The bf project contains a Lua/ASM translation of the code from Josh Haberman's tutorial on DynASM and JITs, and probably the simplest JIT compiler you could write. It too works on Windows, Linux and OSX, x86 and x64.
hi-level | |
dynasm.loadfile(infile[, opt]) -> chunk | load a dasl file and return it as a Lua chunk |
dynasm.loadstring(s[, opt]) -> chunk | load a dasl string and return it as a Lua chunk |
low-level | |
dynasm.translate(infile, outfile[, opt]) | translate a dasc or dasl file |
dynasm.string_infile(s) -> infile | use a string as an infile to translate() |
dynasm.func_outfile(func) -> outfile | make an outfile that calls func(s) for each piece |
dynasm.table_outfile(t) -> outfile | make an outfile that writes pieces to a table |
dynasm.translate_tostring(infile[, opt]) -> s | translate to a string |
dynasm.translate_toiter(infile[, opt]) -> iter() -> s | translate to an iterator of string pieces |
hi-level | |
dasm.new( actionlist, [externnames], [sectioncount], [globalcount], [externget], [globals]) -> state, globals |
make a dasm state for an action list. -> per .actionlist directive.-> per .externnames directive.-> DASM_MAXSECTION from .sections directive.-> DASM_MAXGLOBAL from .globals directive.-> func(externname) -> addr , for solving extern s-> void*[DASM_MAXGLOBAL] , to hold globals |
state:build() -> buf, size | check, link, alloc, encode and mprotect the code |
dasm.dump(buf, size) | dump the code using the included disassembler in luajit |
dasm.globals(globals, globalnames) -> {name -> addr} | given the globals array returned by dasm.new() and the globalnames list per .globalnames directive, return a table that maps the names to their address. |
low-level | |
state:init(maxsection) | init a state |
state:free() | free the state |
state:setupglobal(globals, globalcount) | set up the globals buffer |
state:growpc(maxpc) | grow the number of available pc labels |
state:setup(actionlist) | set up the state with an action list |
state:put(state, ...) | the assembler generates these calls |
state:link() -> size | link the code and get its size |
state:encode(buf) | encode the code into a buffer |
state:getpclabel(pclabel[, buf]) | get pc label offset, or pointer if buf is passed |
state:checkstep(secmatch) | check code before encoding |
state:setupextern(externnames, getter) | set up a new extern handler |
The source code changes made to DynASM were kept to a minimum to preserve DynASM semantics, and to make it easy to add the Lua mode to other architectures supported by DynASM. As for the user-facing changes, the list is again small:
-l, --lang C|Lua
command line option (set automatically for dasl and dasc files).--
and //
in Lua mode..globals
directive generates DASM_MAXGLOBAL in Lua mode..type
usage is limited in Lua mode: FOO.field
, FOO[expr]
and FOO[expr].field
are ok, but arbitrary expressions like FOO[5].bar[2].baz
are not.extern foo
resolves to ffi.C.foo
by default; if foo has no cdef, ffi.cdef'void foo()'
is called (i.e. a dummy cdef is made for it - caveat emptor).