Machine Code Generation for C++
Assembler interface and operands.
AsmJit's Assembler is used to emit machine code directly into a CodeBuffer. In general, code generation with assembler requires the knowledge of the following:
Let's start with operands. Operand is a data structure that defines a data layout of any operand. It can be inherited, but any class inheriting it cannot add any members to it, only the existing layout can be reused. AsmJit allows to construct operands dynamically, to store them, and to query a complete information about them at run-time. Operands are small (always 16 bytes per Operand) and can be copied and passed by value. Please never allocate individual operands dynamically by using a
new keyword - it would work, but then you would have to be responsible for deleting such operands. In AsmJit operands are always part of some other data structures like InstNode, which is part of Builder tool.
Operands contain only identifiers, but not pointers to any code-generation data. For example Label operand only provides label identifier, but not a pointer to LabelEntry structure. In AsmJit such IDs are used to link stuff together without having to deal with pointers.
AsmJit's operands all inherit from a base class called Operand. Operands have the following properties that are commonly accessible by getters and setters:
AsmJit allows to construct operands dynamically, to store them, and to query a complete information about them at run-time. Operands are small (always 16 bytes per
Operand) and should be always copied (by value) if you intend to store them (don't create operands by using
new keyword, it's not recommended). Operands are safe to be passed to
memset(), which becomes handy when working with arrays of operands. If you set all members of an Operand to zero the operand would become NONE operand, which is the same as a default constructed Operand.
The example below illustrates how operands can be used and modified even without using any other code generation classes. The example uses X86 architecture-specific operands.
Some operands have to be created explicitly by emitters. For example labels must be created by BaseEmitter::newLabel(), which creates a label entry and returns a Label operand with the id that refers to it. Such label then can be used by emitters.
Some architectures like X86 provide a complex memory addressing model that allows to encode addresses having a BASE register, INDEX register with a possible scale (left shift), and displacement (called offset in AsmJit). Memory address on X86 can also specify memory segment (segment-override in X86 terminology) and some instructions (gather / scatter) require INDEX to be a x86::Vec register instead of a general-purpose register.
AsmJit allows to encode and work with all forms of addresses mentioned and implemented by X86. In addition, it also allows to construct absolute 64-bit memory address operands, which is only allowed in one form of 'mov' instruction.
Memory operands can optionally contain memory size. This is required by instructions where the memory size cannot be deduced from other operands, like
dec on X86:
Memory operands provide API that can be used to access its properties:
Making changes to memory operand is very comfortable when emitting loads and stores:
Creates a new immediate operand.
imm(x) is much nicer than using
Imm(x) as this is a template which can accept any integer including pointers and function pointers.
A default-constructed operand of