Files

DynASM consists of several files, some of which are Lua files, and some of which are C.


dynasm.lua

This file forms the core of the DynASM preprocessor. It takes a DynASM source file (which is a mixture of assembly and C) file as input, and produces a C file as output, therein performing the bulk of the conversion from assembly to machine code. Lines from the DynASM source file that do not start with a vertical bar are copied verbatim to the output file. Lines that start with one vertical bar can contain assembly instructions or assembler directives, and will generally be rewritten to C lines of the form dasm_put(Dst, ...). Lines that start with two vertical bars are treated as C code, but undergo prepreprocessor substitutions created by .define, and can be part of .macro definitions.

If a Lua interpreter with a bit library (such as LuaJIT) is present, dynasm.lua can be invoked with a command like:

lua dynasm.lua FLAGS INFILE.dasc

If the presence of such a Lua interpreter cannot be assumed, LuaJIT2's minilua can be built and used to invoke dynasm.lua:

cd LUAJIT2_ROOT/dynasm
gcc -o minilua ../src/host/minilua.c
./minilua dynasm.lua FLAGS INFILE.dasc

The available flags are:

ShortLongEffect
-h--helpDisplay help text similar to this table.
-v--versionDisplay version and copyright information.
-o FILE--outfile FILESet the output file name (defaults to stdout if not given).
-I DIR--include DIRAdd a directory to the .include search path.
-c--ccommentUse C style /* */ comments for assembler lines in output.
-C--cppcommentUse C++ style // comments for assembler lines in output (the default).
-N--nocommentSuppress assembler lines in output.
-M--maccommentShow macro expansions as comments in output (default off).
-L--nolinenoDo not emit C preprocessor #line directives in output (default on).
-F--flushlineDo not aggressively coalesce adjacent dasm_put calls in output (default on).
-D NAME[=SUBST]Equivalent to .define NAME [, SUBST].
-U NAMEUndefines a prior -D NAME.
-P--dumpdefDump defines, macros, etc. Repeat for more output.
-A ARCH--dumparch ARCHLoad architecture ARCH and dump description, similar to the instruction listing page.

If -A ARCH or .arch ARCH is used, dynasm.lua will load the architecture module dasm_ARCH.lua. Furthermore, the architecture module dasm_x64.lua will load the architecture module dasm_x86.lua. This set of two or three Lua files form the complete DynASM preprocessor for the given architecture. Said files are only required for converting DynASM source files to plain C files; there is no runtime requirement on them or on a Lua interpreter.


dasm_proto.h

This C header file contains the default definitions for various macros and the type signatures for various functions, which collectively form the DynASM runtime library. It should be pulled in by a #include directive in every C/C++ file that wants to use DynASM to generate machine code.

If custom definitions of any macros are desired, said macros should be defined prior to dasm_proto.h being included. If multiple files #include dasm_proto.h, then they should all consistently #define any custom macro definitions. For this reason, if DynASM is used from multiple files, it is conventional to write a header which wraps dasm_proto.h and provides the custom macro definitions.


dasm_x86.h

This C file contains the implementations of the various functions in the DynASM runtime library. It should be pulled in by a #include directive in precisely one C/C++ file, and said directive must be preceded by a #include directive for dasm_proto.h.

If DynASM is being used for an architecture other than x86 or x64, that architecture's corresponding header should be pulled in instead of dynasm_x86.h, though this documentation does not cover such uses of DynASM in detail.


Macros

A selection of macros can be defined at compile-time to modify the behaviour of DynASM. If the default definition for a particular macro isn't appropriate, it should be redefined prior to including any DynASM headers.


DASM_ALIGNED_WRITES

If defined (to any value), dasm_x86.h will use multiple byte-granularity writes rather than a single multi-byte write in cases where the destination of the write might not be aligned.

This should be defined when emitting x86/x64 machine code on a host platform other than x86/x64.


DASM_CHECKS

If defined (to any value), the implementations of dasm_link, dasm_encode, and dasm_checkstep in dasm_x86.h will perform additional sanity checking to ensure that DynASM is being used correctly.

If encoding speed is a concern, it might be desirable to define DASM_CHECKS during development and in debug builds, and undefine it otherwise.


DASM_EXTERN

#define DASM_EXTERN(ctx, addr, idx, type) 0

This user-defined macro is used by dasm_x86.h when dasm_encode needs to encode an extern address, and must be redefined if extern addresses are used.

The ctx parameter has the same type as the variable defined by Dst_DECL, and will be set to the first argument to dasm_encode.

The addr parameter has type unsigned char*, and gives the address of a int32_t field which is to be written as part of an x86/x64 instruction.

The idx parameter has type unsigned int, and gives an index into the .externnames array, therein identifying the particular extern address being written.

The type parameter is non-zero to indicate that the int32_t field being written represents a signed 32-bit relative offset from the end of the instruction being encoded (i.e. relative to addr+4). It is zero to indicate an absolute address x86, or an absolute address in the low 2GB of address space x64.

The macro should evaluate to a 32-bit integer value, which will subsequently be written to (int32_t*)addr.

For example, if extern names are dynamically findable symbols, this might be defined as:

#define DASM_EXTERN(ctx, addr, idx, type) (\
    (type) ? (int)((unsigned char*)dlsym(RTLD_DEFAULT, externs[idx]) - (addr) - 4) \
           : (int)dlsym(RTLD_DEFAULT, externs[idx]))

DASM_FDEF

#define DASM_FDEF extern

This user-defined macro is used to control the specifiers on all dasm_ functions. For example, Windows applications which embed DynASM within a shared library might redefine this to either __declspec(dllimport) or __declspec(dllexport). Conversely, Linux applications which use DynASM in just one place and don't want to pollute the symbol namespace might redefine this to __attribute__((visibility("hidden"))) or just static.


DASM_M_FREE

#define DASM_M_FREE(ctx, p, sz) free(p)

This user-defined macro is used by dasm_x86.h in order to implement dasm_free and free pieces of memory previously allocated by DASM_M_GROW.

The ctx parameter has the same type as the variable defined by Dst_DECL, and will be set to the first argument to dasm_free.

On entry, the p and sz arguments will match the p and sz arguments from the exit of a prior invocation of DASM_M_GROW, i.e. p gives a pointer to the memory which needs to be freed, and sz gives the number of bytes previously allocated at p.


DASM_M_GROW

#define DASM_M_GROW(ctx, t, p, sz, need) \
  do { \
    size_t _sz = (sz), _need = (need); \
    if (_sz < _need) { \
      if (_sz < 16) _sz = 16; \
      while (_sz < _need) _sz += _sz; \
      (p) = (t *)realloc((p), _sz); \
      if ((p) == NULL) exit(1); \
      (sz) = _sz; \
    } \
  } while(0)

This user-defined macro is used by dasm_x86.h whenever memory needs to be allocated or re-allocated - which dasm_init always needs to, and various other dasm_ functions sometimes need to. It should be redefined, along with DASM_M_FREE, when the default C realloc/free allocation mechanism isn't appropriate.

The ctx parameter has the same type as the variable defined by Dst_DECL, and will be set to the first argument to the dasm_ function performing the allocation.

On entry, the p argument gives an lvalue of type t* which is sz bytes in size. In particular, note that sz can be zero to indicate an allocation operation should be performed (in which case p will be NULL), or non-zero to indicate that a re-allocation operation should be performed (in which case p will not be NULL).

Upon exit, the p argument should give an lvalue of type t* which is at least need bytes in size, and the sz argument should give an lvalue containing the actual number of bytes.


Dst_DECL

#define Dst_DECL dasm_State **Dst

This user-defined macro affects the signature of all dasm_ functions, in particular giving the type and name of the first formal parameter. The name of said parameter must either remain as Dst, or there must be a preprocessor macro which redefines Dst to match the name used here. The type of said parameter can be anything that allows Dst_REF to give an lvalue of type dasm_State*.

By convention, non-trivial users of DynASM that have a collection of state tied to a dasm_State* will define a structure containing a dasm_State* and the related state, and will redefine Dst_DECL to be pointer to said structure.

For example, when using DynASM within a C++ project, the following definitions might be used to ensure RAII semantics and to pass other fields to emitter functions:

struct my_dasm_State {
  my_dasm_State(int maxsection) {
    dasm_init(this, maxsection);
  }
  ~my_dasm_State() {
    dasm_free(this);
  }
  struct dasm_State* ds;
  /* ... other fields ... */
};
#define Dst_DECL my_dasm_State* Dst
#define Dst_REF Dst->ds

Dst_REF

#define Dst_REF (*Dst)

This user-defined macro is used by dasm_x86.h to extract an lvalue of type dasm_State* from a variable called Dst. It should be redefined if, and only if, Dst_DECL is also redefined.


Functions

The DynASM API consists of 10 functions, which must be used in a fairly strict order:

PhaseCallable Functions
1dasm_init
2dasm_setupglobal
3dasm_setup
4Any of dasm_checkstep, dasm_growpc, dasm_put, each zero or more times
5dasm_link
6dasm_encode
7dasm_getpclabel, zero or more times
8dasm_free

dasm_checkstep

DASM_FDEF int dasm_checkstep(Dst_DECL, int secmatch);

This function can be called after a sequence of calls to dasm_put to perform extra sanity checking.

If DASM_CHECKS is defined, and a prior call to dasm_put encountered an error, this function will return a non-zero result indicating the error.

If DASM_CHECKS is defined, and any of the labels 1: through 9: have been referenced but not defined, this function will return DASM_S_UNDEF_L|i, where i is the problematic label number. If there are no problems with the labels, then this function will undefine the labels 1: through 9:.

If DASM_CHECKS is defined, the secmatch argument is non-negative, and the index of the section currently being written (see .section) isn't equal to secmatch, then this function will return DASM_S_MATCH_SEC|i, where i is the index of the current section.

Otherwise, this function will return the value 0 to indicate success.


dasm_encode

DASM_FDEF int dasm_encode(Dst_DECL, void *buffer);

This function is called after dasm_link in order to actually generate machine code.

On entry, the buffer argument should point to a block of memory whose size in bytes is at least the size returned by dasm_link. For the duration of the call to dasm_encode, this block of memory must be at least readable and writable.

On exit, the block of memory at buffer will have been filled with machine code, and the globals array passed to dasm_setupglobal will have been populated with pointers into said block. If the machine code is to be executed (as opposed to just written to disk), then the block of memory needs to be at least readable and executable after dasm_encode. Operating system APIs such as VirtualProtect (Windows) or mprotect (POSIX) may be appropriate for marking memory as executable.

This function should always return 0, but it can return non-zero if a DynASM bug is triggered.


dasm_free

DASM_FDEF void dasm_free(Dst_DECL);

This function frees a DynASM state previously allocated by dasm_init. Its behaviour can be modified by redefining DASM_M_FREE.


dasm_getpclabel

DASM_FDEF int dasm_getpclabel(Dst_DECL, unsigned int pc);

This function can be called after dasm_encode in order to retreive the address of the label =>pc:.

On success, the return value is the non-negative offset from the start of the buffer passed to dasm_encode to the label =>pc:.

On error (for example =>pc: not being defined), the return value is negative.


dasm_growpc

DASM_FDEF void dasm_growpc(Dst_DECL, unsigned int maxpc);

This function can be called after dasm_setup and before dasm_link in order to increase the number of labels usable with the =>pc syntax. In particular, after a call to this function, the labels =>0 through =>(maxpc-1) can be used.


dasm_init

DASM_FDEF void dasm_init(Dst_DECL, int maxsection);

This function performs the first stage of initialisation of a DynASM state. Its behaviour can be modified by redefining DASM_M_GROW.

If .section is used, then DASM_MAXSECTION should be passed as maxsection. Otherwise, the value 1 should be passed.

A call to this function must be followed by a call to dasm_setupglobal to perform the second stage of initialisation, and at some point later dasm_free should be called to free the DynASM state.


DASM_FDEF int dasm_link(Dst_DECL, size_t *szp);

This function is called prior to dasm_encode to calculate the size of the machine code that will be generated. Consumers of the API will typically call dasm_link, allocate a suitably sized piece of memory, and then call dasm_encode.

If DASM_CHECKS is defined, and a prior call to dasm_put or dasm_checkstep encountered an error, this function will return a non-zero result indicating the error. If DASM_CHECKS is defined, and the label =>pc is used but not defined, this function will return DASM_S_UNDEF_PC|pc. In all other cases, this function will return the value 0 to indicate success.


dasm_put

DASM_FDEF void dasm_put(Dst_DECL, int start, ...);

Lines in DynASM source files that start with one vertical bar are translated into calls to dasm_put by the dynasm.lua preprocessor. Conceptually, calls to this function are appending some assembly code to the DynASM state which dasm_link / dasm_encode will later turn into machine code.

Calls to this function should not be written by humans; they should only be written by the dynasm.lua preprocessor. That said, the start parameter gives an offset into the action list passed to dasm_setup, and the variadic parameter is used to pass concrete values for encoding-time constants in the original assembly code.

If DASM_CHECKS is defined, this function performs additional sanity checking. If any of these checks fail, subsequent calls to dasm_checkstep and dasm_link will return a non-zero value.


dasm_setup

DASM_FDEF void dasm_setup(Dst_DECL, const void *actionlist);

This function must be called after dasm_setupglobal to complete the initialisation of a DynASM state.

The array defined by .actionlist must be passed as the actionlist parameter, and will be used by subsequent calls to dasm_put.


dasm_setupglobal

DASM_FDEF void dasm_setupglobal(Dst_DECL, void **gl, unsigned int maxgl);

This function must be called after dasm_init, and before dasm_setup, to perform the second stage of initialisation of a DynASM state.

The ident_MAX value defined by .globals ident should be passed as maxgl, and a void* array of maxgl elements should be passed as gl. It is common to use a local variable as said array, for example:

void* globals[ident_MAX];
dasm_setupglobal(&d, globals, ident_MAX);

The array is used as scratch space up until dasm_encode is called, and then afterwards the elements of the array give the addresses of -> labels. Continuing the prior example, the address of the global label ->bar: would be given by globals[ident_bar].


Directives

A wide selection of directives can be used in DynASM source files, though they fall into a small number of categories:

CategoryDirectives
Features .arch, .capture, .dumpcapture, .macro, .nop, .section, .type
Reflection .actionlist, .externnames, .globalnames, .globals
Data .align, .byte, .dword, .sbyte, .space, .word
Prepreprocessor .define, .elif, .else, .endif, .error, .fatal, .if, .include

As with instructions, arguments to directives are comma-separated, subject to balanced parentheses ((), [], and {}). For example, .byte x,y,z,w has four arguments, while .byte x(y,z), w has two.


.actionlist

| .actionlist ident

This directive causes the dynasm.lua preprocessor to emit a piece of C code similar to:

static const unsigned char ident[] = {
  /* ... */
};

This directive must be used exactly once in a DynASM source file, and the array it defines must be passed to dasm_setup. The contents of the array reflects the assembly used in the DynASM source file, though in an unspecified format.


.align

| .align (2|4|8|16|32|64|128|256|word|dword|aword|qword|oword)

This directive causes zero or more bytes containing 0x90 to be emitted by dasm_encode, such that the offset of the next byte, relative to the start of the buffer passed to dasm_encode, is a multiple of the argument. Said argument must be a power of two between 2 and 256, or one of the following type size aliases:

AliasActs like
.align word.align 2
.align dword.align 4
.align aword.align 4 x86 or .align 8 x64
.align qword.align 8
.align oword.align 16

.arch

| .arch (arm|mips|ppc|x64|x86)

This directive specifies the architecture of the assembly code in a DynASM source file. It must be used exactly once in a DynASM source file, and must be the first directive in a DynASM source file. Note that this documentation only covers .arch x86 and .arch x64, though other architectures have similar macros and directives. Where the two documented architectures differ, this page marks architecture-specific behaviour with x86 or x64.


.byte

| .byte imm8 [, imm8 [, ...]]

This data directive emits one or more unsigned 8-bit values.


.capture

| .capture name
/* ... */
| .endcapture

This directive affects everything up until the next .endcapture, and causes everything that would be written to the output file to instead be appended to an internal buffer. The buffer is identified by name, and multiple .capture directives with the same name will append to the same buffer. The internal buffer must, at some later point in time, be written to the output file by means of .dumpcapture name.


.define

| .define name [, substitution]

This directive defines a prepreprocessor substitution. For the remainder of the DynASM source file, on lines that start with a vertical bar, name is treated as if it were substitution (or 1 if a substitution is not given).


.dumpcapture

| .dumpcapture name

This directive causes all output captured by prior .capture name directives to be written to the output file.


.dword

| .dword imm32 [, imm32 [, ...]]

This data directive emits one or more unsigned 32-bit values.


.elif

| .elif condition

This prepreprocessor directive is to #elif what .if is to #if.


.else

| .else

This prepreprocessor directive is to #else what .if is to #if.


.endif

| .endif

This prepreprocessor directive is to #endif what .if is to #if.


.error

| .error message

This prepreprocessor directive causes dynasm.lua to print message, continue processing the remainder of the DynASM source file, and then exit with a failure code.


.externnames

| .externnames ident

This directive causes the dynasm.lua preprocessor to emit a piece of C code similar to:

static const char *const ident[] = {
  /* ... */
  (const char *)0
};

The array defined by this directive contains the name of every extern address used in the DynASM source file. For example, if a DynASM source file refers to (just) the extern addresses extern foo and extern bar, then this directive would cause the following to be emitted:

static const char *const ident[] = {
  "foo",
  "bar",
  (const char *)0
};

Subsequently, when extern foo is encoded, DASM_EXTERN would be invoked with index 0, and then when extern bar is encoded, it would be invoked with index 1.


.fatal

| .fatal message

This prepreprocessor directive causes dynasm.lua to print message, and then immediately exit with a failure code.


.globalnames

| .globalnames ident

This directive causes the dynasm.lua preprocessor to emit a piece of C code similar to:

static const char *const ident[] = {
  /* ... */
  (const char *)0
};

The array defined by this directive contains the name of every -> label used in the DynASM source file. For example, if a DynASM source file refers to (just) the global labels ->foo and ->bar, then this directive would cause the following to be emitted:

static const char *const ident[] = {
  "foo",
  "bar",
  (const char *)0
};

.globals

| .globals prefix

This directive causes the dynasm.lua preprocessor to emit a piece of C code similar to:

enum {
  /* ... */
  prefix_MAX
};

The enum defined by this directive implicitly maps every -> label used in the DynASM source file to an integer. For example, if a DynASM source file refers to (just) the global labels ->foo and ->bar, then this directive would cause the following to be emitted:

enum {
  prefixfoo,
  prefixbar,
  prefix_MAX
};

This directive must be used exactly once in a DynASM source file, and the prefix_MAX value it defines must be passed as the third argument to dasm_setupglobal. The other values it defines can be used to obtain the address of the respective label by indexing the array passed as the second argument to dasm_setupglobal after dasm_encode has been called.


.if

| .if condition

If the given condition evaluates to 0, false, or nil, then all lines from the DynASM source file up until the next .elif, .else, or .endif directive are discarded. If the given condition evaluates to any other value, said lines are kept.

The condition can be any Lua expression (and therefore use arithmetic, not, and, or, etc.), and can make reference to names defined with .define, though it is evaluated in an otherwise empty sandbox.


.include

| .include filename

This prepreprocessor directive causes the contents of the named file (which should itself be a DynASM source file) to be treated as if it appeared in place of the .include directive. If filename is a relative path, the current working directory will be searched for the file, followed by any include paths specified using the -I flag to dynasm.lua.


.macro

| .macro name [, param1 [, param2 [, ...]]]
| /* ... */
| .endmacro

This directive causes name to be recognised as a macro for the remainder of the DynASM source file. Macros are invoked as if they were normal assembly instructions, using the syntax name arg1, arg2, ....

The body of the macro can contain assembly instructions and DynASM directives on lines starting with one vertical bar, and it can contain C/C++ code on lines starting with two vertical bars. Any such lines can contain parameter names, which will be substituted for the provided arguments, and they can also contain .., which acts as a token pasting operator.

Note that lines which do not start with a vertical bar are untouched by dynasm.lua, and this remains true between .macro and .endmacro. As such, C/C++ code that is meant to be emitted when the macro is invoked needs to be prefixed with two vertical bars rather than no vertical bars.


.nop

| .nop ...

This directive does nothing.

Note the difference between the directive .nop, which does nothing, and the instruction nop, which is equivalent to .byte 0x90.


.sbyte

| .sbyte imm8 [, imm8 [, ...]]

This data directive emits one or more signed 8-bit values.


.section

| .section name1 [, name2 [, ...]]

This directive allows a DynASM source file to write multiple code segments rather than just a single code segment. Every argument name results in the introduction of a new directive .name, and using one of these introduced directives causes all subsequent assembly and data directives to append to the named section. When dasm_encode is called, the sections are concatenated to form a single contiguous block of machine code.

This directive causes the dynasm.lua preprocessor to emit a piece of C code similar to:

#define DASM_SECTION_NAME1 0
#define DASM_SECTION_NAME2 1
/* ... */
#define DASM_MAXSECTION 2

This directive can be used at most once per DynASM source file, and if it is used, the DASM_MAXSECTION macro that it defines should be passed to dasm_init. The other macros that it defines can be used with dasm_checkstep.


.space

| .space imm32 [, filler]

This data directive emits imm32 copies of the byte filler. If specified, filler must be an integer between 0 and 255, and defaults to zero if not specified.


.type

| .type name, ctype [, default_reg]

This directive makes it easier to manipulate registers of type ctype* by defining the following syntactic sugar for the remainder of the DynASM source file:

SugarExpansion
#namesizeof(ctype)
name:reg->field[reg + offsetof(ctype,field)]
name:reg[imm32][reg + sizeof(ctype)*imm32]
name:reg[imm32].field[reg + sizeof(ctype)*imm32 + offsetof(ctype,field)]
name:reg...[reg + (int)(ptrdiff_t)&(((ctype*)0)...)]

Note that the middle three forms of sugar are just special cases of the last form of sugar, and therefore there is no ambiguity between the last form and the earlier forms.

If default_reg is specified, :reg can be omitted from the sugar, and the expansion will use default_reg.


.word

| .word imm16 [, imm16 [, ...]]

This data directive emits one or more unsigned 16-bit values.