IndexAssembler

Assembler [¶]

Assembler interface and operands.

Overview

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:

  • BaseAssembler and architecture-specific assemblers:
    • x86::Assembler - Assembler implementation targeting X86 and X86_64 architectures.
    • a64::Assembler - Assembler implementation targeting AArch64 architecture.
  • Operand and its variations:
    • Reg - Base class for a register operand, inherited by:
      • UniGp - Universal abstraction of a general purpose register, inherited by:
        • x86::Gp - GP register operand specific to X86 and X86_64 architectures.
        • a64::Gp - GP Register operand specific to AArch64 architecture.
      • UniVec - Universal abstraction of a vector register, inherited by:
        • x86::Vec - Vector register operand specific to X86 and X86_64 architectures.
        • a64::Vec - Vector register operand specific to AArch64 architecture.
      • x86::Mm, x86::KReg, x86::Tmm, and other architecture specific register operands.
    • BaseMem - Base class for a memory operand, inherited by:
      • x86::Mem - Memory operand specific to X86 and X86_64 architectures.
      • a64::Mem - Memory operand specific to AArch64 architecture.
    • Imm - Immediate (value) operand.
    • Label - Label operand.
Note
Assembler examples use x86::Assembler as abstract interfaces cannot be used to generate code.

Operand Basics [¶]

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:

  • Operand - Base operand, which only provides accessors that are common to all operand types.
  • Reg - Describes either physical or virtual register. Physical registers have ids that match the target's machine id directly whereas virtual registers must be allocated into physical registers by a register allocator pass. Register operand provides:
    • Register Type (RegType) - Unique id that describes each possible register provided by the target architecture - for example X86 backend provides general purpose registers (GPB-LO, GPB-HI, GPW, GPD, and GPQ) and various types of other registers like K, MM, BND, XMM, YMM, ZMM, and TMM.
    • Register Group (RegGroup) - Groups multiple register types under a single group - for example all general-purpose registers (of all sizes) on X86 are part of RegGroup::kGp and all SIMD registers (XMM, YMM, ZMM) are part of RegGroup::kVec.
    • Register Size - Contains the size of the register in bytes. If the size depends on the mode (32-bit vs 64-bit) then generally the higher size is used (for example RIP register has size 8 by default).
    • Register Id - Contains physical or virtual id of the register.
    • Unified interface of general purpose registers is provided by UniGp, which acts as a base of all architecture specific GP registers such as x86::Gp and a64::Gp.
    • Unified interface of vector registers is provided by UniVec, which acts as a base of all architecture specific vector registers such as x86::Vec and a64::Vec. Please note that X86 MMX registers are not part of x86::Vec, instead they are modeled as x86::Mm.
  • BaseMem - Used to reference a memory location. Memory operand provides:
    • Base Register - A base register type and id (physical or virtual).
    • Index Register - An index register type and id (physical or virtual).
    • Offset - Displacement or absolute address to be referenced (32-bit if base register is used and 64-bit if base register is not used).
    • Flags that can describe various architecture dependent information (like scale and segment-override on X86).
  • Imm - Immediate values are usually part of instructions (encoded within the instruction itself) or data.
  • Label - used to reference a location in code or data. Labels must be created by the BaseEmitter or by CodeHolder. Each label has its unique id per CodeHolder instance.

Operand Manipulation [¶]

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 memcpy() and 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.

#include <asmjit/x86.h>
using namespace asmjit;
// Registers can be copied, it's a common practice.
x86::Gp dstRegByValue() { return x86::ecx; }
void usingOperandsExample(x86::Assembler& a) {
// Gets `ecx` register returned by a function.
x86::Gp dst = dstRegByValue();
// Gets `rax` register directly from the provided `x86` namespace.
x86::Gp src = x86::rax;
// Constructs `r10` dynamically.
x86::Gp idx = x86::gpq(10);
// Constructs [src + idx] memory address - referencing [rax + r10].
x86::Mem m = x86::ptr(src, idx);
// Examine `m`: Returns `RegType::kGp64`.
m.indexType();
// Examine `m`: Returns 10 (`r10`).
m.indexId();
// Reconstruct `idx` stored in mem:
// True, `idx` and idx_2` are identical.
idx == idx_2;
// Possible - op will still be the same as `m`.
Operand op = m;
// True (can be casted to BaseMem or architecture-specific Mem).
op.isMem();
// True, `op` is just a copy of `m`.
m == op;
// Static cast is fine and valid here.
static_cast<BaseMem&>(op).addOffset(1);
// However, using `as<T>()` to cast to a derived type is preferred.
op.as<BaseMem>().addOffset(1);
// False, `op` now points to [rax + r10 + 2], which is not [rax + r10].
m == op;
// Emitting 'mov' - type safe way.
a.mov(dst, m);
// Not possible, `mov` doesn't provide mov(x86::Gp, Operand) overload.
a.mov(dst, op);
// Type-unsafe, but possible.
a.emit(x86::Inst::kIdMov, dst, m);
// Also possible, `emit()` is type-less and can be used with raw Operand.
a.emit(x86::Inst::kIdMov, dst, op);
}

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.

Memory Operands [¶]

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.

#include <asmjit/x86.h>
using namespace asmjit;
void testX86Mem() {
// Makes it easier to access x86 stuff...
using namespace asmjit::x86;
// BASE + OFFSET.
Mem a = ptr(rax); // a = [rax]
Mem b = ptr(rax, 15); // b = [rax + 15]
// BASE + INDEX << SHIFT - Shift is in BITS as used by X86!
Mem c = ptr(rax, rbx); // c = [rax + rbx]
Mem d = ptr(rax, rbx, 2); // d = [rax + rbx << 2]
Mem e = ptr(rax, rbx, 2, 15); // e = [rax + rbx << 2 + 15]
// BASE + VM (Vector Index) (encoded as MOD+VSIB).
Mem f = ptr(rax, xmm1); // f = [rax + xmm1]
Mem g = ptr(rax, xmm1, 2); // g = [rax + xmm1 << 2]
Mem h = ptr(rax, xmm1, 2, 15); // h = [rax + xmm1 << 2 + 15]
// Absolute address:
uint64_t addr = (uint64_t)0x1234;
Mem i = ptr(addr); // i = [0x1234]
Mem j = ptr(addr, rbx); // j = [0x1234 + rbx]
Mem k = ptr(addr, rbx, 2); // k = [0x1234 + rbx << 2]
// LABEL - Will be encoded as RIP (64-bit) or absolute address (32-bit).
Label L = ...;
Mem m = ptr(L); // m = [L]
Mem n = ptr(L, rbx); // n = [L + rbx]
Mem o = ptr(L, rbx, 2); // o = [L + rbx << 2]
Mem p = ptr(L, rbx, 2, 15); // p = [L + rbx << 2 + 15]
// RIP - 64-bit only (RIP can't use INDEX).
Mem q = ptr(rip, 24); // q = [rip + 24]
}

Memory operands can optionally contain memory size. This is required by instructions where the memory size cannot be deduced from other operands, like inc and dec on X86:

#include <asmjit/x86.h>
using namespace asmjit;
void testX86Mem() {
// The same as: dword ptr [rax + rbx].
x86::Mem a = x86::dword_ptr(x86::rax, x86::rbx);
// The same as: qword ptr [rdx + rsi << 0 + 1].
x86::Mem b = x86::qword_ptr(x86::rdx, x86::rsi, 0, 1);
}

Memory operands provide API that can be used to access its properties:

#include <asmjit/x86.h>
using namespace asmjit;
void testX86Mem() {
// The same as: dword ptr [rax + 12].
x86::Mem mem = x86::dword_ptr(x86::rax, 12);
mem.hasBase(); // true.
mem.hasIndex(); // false.
mem.size(); // 4.
mem.offset(); // 12.
mem.setSize(0); // Sets the size to 0 (makes it size-less).
mem.addOffset(-1); // Adds -1 to the offset and makes it 11.
mem.setOffset(0); // Sets the offset to 0.
mem.setBase(x86::rcx); // Changes BASE to RCX.
mem.setIndex(x86::rax); // Changes INDEX to RAX.
mem.hasIndex(); // true.
}

Making changes to memory operand is very comfortable when emitting loads and stores:

#include <asmjit/x86.h>
using namespace asmjit;
void testX86Mem(CodeHolder& code) {
x86::Assembler a(code); // Your initialized x86::Assembler.
x86::Mem mSrc = x86::ptr(eax); // Construct [eax] memory operand.
// One way of emitting bunch of loads is to use `mem.adjusted()`, which
// returns a new memory operand and keeps the source operand unchanged.
a.movaps(x86::xmm0, mSrc); // No adjustment needed to load [eax].
a.movaps(x86::xmm1, mSrc.adjusted(16)); // Loads from [eax + 16].
a.movaps(x86::xmm2, mSrc.adjusted(32)); // Loads from [eax + 32].
a.movaps(x86::xmm3, mSrc.adjusted(48)); // Loads from [eax + 48].
// ... do something with xmm0-3 ...
// Another way of adjusting memory is to change the operand in-place.
// If you want to keep the original operand you can simply clone it.
x86::Mem mDst = mSrc.clone(); // Clone mSrc.
a.movaps(mDst, x86::xmm0); // Stores xmm0 to [eax].
mDst.addOffset(16); // Adds 16 to `mDst`.
a.movaps(mDst, x86::xmm1); // Stores to [eax + 16] .
mDst.addOffset(16); // Adds 16 to `mDst`.
a.movaps(mDst, x86::xmm2); // Stores to [eax + 32].
mDst.addOffset(16); // Adds 16 to `mDst`.
a.movaps(mDst, x86::xmm3); // Stores to [eax + 48].
}

Assembler Examples [¶]

Classes

Macros

Typedefs

Enumerations

Functions

Variables

#define ASMJIT_DEFINE_ABSTRACT_REG(
REG, BASE
)[¶]

Adds constructors and member functions to a class that implements abstract register.

Abstract register is register that doesn't have type or signature yet, it's a base class like x86::Reg or arm::Reg.

#define ASMJIT_DEFINE_FINAL_REG(
REG, BASE, TRAITS
)[¶]

Value:
public: \
static inline constexpr RegType kThisType = TRAITS::kType; \
static inline constexpr RegGroup kThisGroup = TRAITS::kGroup; \
static inline constexpr uint32_t kThisSize = TRAITS::kSize; \
static inline constexpr uint32_t kSignature = TRAITS::kSignature; \
\
ASMJIT_DEFINE_ABSTRACT_REG(REG, BASE) \
\ \
ASMJIT_INLINE_CONSTEXPR explicit REG(uint32_t id) noexcept \
: BASE(Signature{kSignature}, id) {}

Adds constructors and member functions to a class that implements final register.

Final registers MUST HAVE a valid signature.

using RegMask = typedef uint32_t[¶]

Register mask is a convenience typedef that describes a mask where each bit describes a physical register id in the same RegGroup.

At the moment 32 bits are enough as AsmJit doesn't support any architecture that would provide more than 32 registers for a register group.

class OperandType : uint32_tenumstrong[¶]

Operand type used by Operand_.

ConstantDescription
kNone 

Not an operand or not initialized.

kReg 

Operand is a register.

kMem 

Operand is a memory.

kRegList 

Operand is a register-list.

kImm 

Operand is an immediate value.

kLabel 

Operand is a label.

kMaxValue 

Maximum value of OperandType.

class RegType : uint8_tenumstrong[¶]

Register type.

Provides a unique type that can be used to identify a register or its view.

ConstantDescription
kNone 

No register - unused, invalid, multiple meanings.

kLabelTag 

This is not a register type.

This value is reserved for a Label that's used in BaseMem as a base.

Label tag is used as a sub-type, forming a unique signature across all operand types as 0x1 is never associated with any register type. This means that a memory operand's BASE register can be constructed from virtually any operand (register vs. label) by just assigning its type (register type or label-tag) and operand id.

kGp8Lo 

8-bit low general purpose register (X86|X86_64).

kGp8Hi 

8-bit high general purpose register (X86|X86_64).

kGp16 

16-bit general purpose register (X86|X86_64).

kGp32 

32-bit general purpose register (X86|X86_64|AArch32|AArch64).

kGp64 

64-bit general purpose register (X86_64|AArch64).

kVec8 

8-bit view of a vector register (AArch64).

kVec16 

16-bit view of a vector register (AArch64).

kVec32 

32-bit view of a vector register (AArch32|AArch64).

kVec64 

64-bit view of a vector register (AArch32|AArch64).

Note
This is never used for MMX registers on X86, MMX registers have its own category.
kVec128 

128-bit view of a vector register (X86|X86_64|AArch32|AArch64).

kVec256 

256-bit view of a vector register (X86|X86_64).

kVec512 

512-bit view of a vector register (X86|X86_64).

kVec1024 

1024-bit view of a vector register (future).

kVecNLen 

View of a vector register, which width is implementation specific (AArch64).

kMask 

Mask register (X86|X86_64|AArch64).

kTile 

Tile register (X86_64: TMM).

kSegment 

Segment register (X86|X86_64: None, ES, CS, SS, DS, FS, GS).

kControl 

Control register (X86|X86_64: CR).

kDebug 

Debug register (X86|X86_64: DR).

kX86_Mm 

MMX register (X86|X86_64: MM).

kX86_St 

FPU (x87) register (X86|X86_64: ST).

kX86_Bnd 

Bound register (X86|X86_64: BND).

kPC 

Universal type describing program counter (PC) or instruction pointer (EIP/RIP) register, if the target architecture actually exposes it as a separate register type, which most modern architectures do.

X86 Specific

Instruction pointer (RIP), only addressable in x86::Mem in 64-bit targets.

kMaxValue 

Maximum value of RegType.

class RegGroup : uint8_tenumstrong[¶]

Register group.

Provides a unique value that identifies groups of registers and their views.

ConstantDescription
kGp 

General purpose register group compatible with all backends.

kVec 

Vector register group compatible with all backends.

Describes XMM|YMM|ZMM registers on X86|X86_64 targets and V|Q|D|S|H|B registers on ARM/AArch64 targets.

kMask 

Mask register group compatible with all backends that can use masking.

Describes K registers on X86|X86_64 targets (AVX-512) and P registers on AArch64 targets (SVE/SVE2).

kExtraVirt3 

Extra virtual group #3 that can be used by Compiler for register allocation.

kTile 

TMM register group (X86|X86_64).

kSegment 

Segment register group (X86|X86_64).

kControl 

Control register group (X86|X86_64).

kDebug 

Debug register group (X86|X86_64).

kX86_MM 

MMX register group (MM) - maps to RegGroup::kExtraVirt3 (X86|X86_64).

kX86_St 

FPU register group (X86|X86_64).

kX86_Bnd 

BND register group (X86|X86_64).

kPC 

Program counter group (represents also EIP/RIP on X86|X86_64 targets).

kMaxValue 

Maximum value of RegGroup.

kMaxVirt 

Last value of a virtual register that is managed by BaseCompiler.

class ImmType : uint32_tenumstrong[¶]

Type of the an immediate value.

ConstantDescription
kInt 

Immediate is integer.

kDouble 

Immediate is a floating point stored as double-precision.

template<typename T>
Imm imm(
const T& val
)constexprstaticnoexcept[¶]

Creates a new immediate operand.

const Operand Globals::noneconstexprstatic[¶]

A default-constructed operand of Operand_::kOpNone type.