Core

Globals, code storage, and emitter interface.

Overview

AsmJit library uses CodeHolder to hold code and emitters inheriting from BaseEmitter to emit code. CodeHolder uses containers to manage its data:

  • Section - stores information about a code or data section.
  • CodeBuffer - stores actual code or data, part of Section.
  • LabelEntry - stores information about a label - its name, offset, section where it belongs to, and other bits.
  • LabelLink - stores information about yet unbound label, which was already used by the assembler.
  • RelocEntry - stores information about a relocation.
  • AddressTableEntry - stores information about an address, which was used in a jump or call. Such address may need relocation.

To generate code you would need to instantiate at least the following classes:

Globals:

  • Globals - namespace that provides global constants.
  • ByteOrder - byte-order constants and functionality.
Note
CodeHolder examples use x86::Assembler as abstract interfaces cannot be used to generate code.

CodeHolder & Emitters

The example below shows how the mentioned classes interact to generate X86 code:

#include <asmjit/x86.h>
#include <stdio.h>
using namespace asmjit;
// Signature of the generated function.
typedef int (*Func)(void);
int main() {
JitRuntime rt; // Runtime specialized for JIT code execution.
CodeHolder code; // Holds code and relocation information.
code.init(rt.codeInfo()); // Initialize code to match the JIT runtime.
x86::Assembler a(&code); // Create and attach x86::Assembler to code.
a.mov(x86::eax, 1); // Move one to eax register.
a.ret(); // Return from function.
// ----> x86::Assembler is no longer needed from here and can be destroyed <----
Func fn; // Holds address to the generated function.
Error err = rt.add(&fn, &code); // Add the generated code to the runtime.
if (err) return 1; // Handle a possible error returned by AsmJit.
// ----> CodeHolder is no longer needed from here and can be destroyed <----
int result = fn(); // Execute the generated code.
printf("%d\n", result); // Print the resulting "1".
// All classes use RAII, all resources will be released before `main()` returns,
// the generated function can be, however, released explicitly if you intend to
// reuse or keep the runtime alive, which you should in a production-ready code.
rt.release(fn);
return 0;
}

The example above used x86::Assembler as an emitter. AsmJit provides the following emitters that offer various levels of abstraction:

Targets and JitRuntime

AsmJit's Target is an interface that provides basic target abstraction. At the moment AsmJit provides only one implementation called JitRuntime, which as the name suggests provides JIT code target and execution runtime. JitRuntime provides all the necessary stuff to implement a simple JIT compiler with basic memory management. It only provides JitRuntime::add() and JitRuntime::release() functions that are used to either add code to the runtime or release it. JitRuntime doesn't do any decisions on when the code should be released, the decision is up to the developer.

See more at Virtual Memory group.

More About CodeInfo

In the previous example the CodeInfo is retrieved from JitRuntime. It's logical as JitRuntime always returns a CodeInfo that is compatible with the host runtime environment. For example if your application runs in 64-bit mode the CodeInfo returned will use ArchInfo::kIdX64 architecture in contrast to ArchInfo::kIdX86, which will be used in 32-bit mode.

AsmJit allows to setup CodeInfo manually and to select a different architecture when necessary. So let's do something else this time, let's always generate a 32-bit code and print its binary representation. To do that, we can create our own CodeInfo and initialize it to ArchInfo::kIdX86. CodeInfo will populate all basic fields just based on the architecture we provide, so it's super-easy:

#include <asmjit/x86.h>
#include <stdio.h>
using namespace asmjit;
int main(int argc, char* argv[]) {
using namespace asmjit::x86; // Easier access to x86/x64 registers.
CodeHolder code; // Create a CodeHolder.
CodeInfo info(ArchInfo::kIdX86); // Initialize CodeInfo for 32-bit X86.
code.init(info); // Initialize CodeHolder for 32-bit X86.
// Generate a 32-bit function that sums 4 floats and looks like:
// void func(float* dst, const float* a, const float* b)
x86::Assembler a(&code); // Create and attach x86::Assembler to `code`.
a.mov(eax, dword_ptr(esp, 4)); // Load the destination pointer.
a.mov(ecx, dword_ptr(esp, 8)); // Load the first source pointer.
a.mov(edx, dword_ptr(esp, 12)); // Load the second source pointer.
a.movups(xmm0, ptr(ecx)); // Load 4 floats from [ecx] to XMM0.
a.movups(xmm1, ptr(edx)); // Load 4 floats from [edx] to XMM1.
a.addps(xmm0, xmm1); // Add 4 floats in XMM1 to XMM0.
a.movups(ptr(eax), xmm0); // Store the result to [eax].
a.ret(); // Return from function.
// We have no Runtime this time, it's on us what we do with the code.
// CodeHolder stores code in Section, which provides some basic properties
// and CodeBuffer structure. We are interested in section's CodeBuffer.
//
// NOTE: The first section is always '.text', it can be retrieved by
// code.sectionById(0) or simply by code.textSection().
CodeBuffer& buffer = code.textSection()->buffer();
// Print the machine-code generated or do something else with it...
// 8B4424048B4C24048B5424040F28010F58010F2900C3
for (size_t i = 0; i < buffer.length; i++)
printf("%02X", buffer.data[i]);
return 0;
}

Explicit Code Relocation

CodeInfo contains much more information than just the target architecture. It can be configured to specify a base-address (or a virtual base-address in a linker terminology), which could be static (useful when you know the location where the target's machine code will be) or dynamic. AsmJit assumes dynamic base-address by default and relocates the code held by CodeHolder to a user provided address on-demand. To be able to relocate to a user provided address it needs to store some information about relocations, which is represented by RelocEntry. Relocation entries are only required if you call external functions from the generated code that cannot be encoded by using a 32-bit displacement (64-bit displacements are not provided by aby supported architecture).

There is also a concept called LabelLink - label link is a lightweight data structure that doesn't have any identifier and is stored in LabelEntry as a single-linked list. Label link represents either unbound yet used label and cross-sections links (only relevant to code that uses multiple sections). Since crossing sections is something that cannot be resolved immediately these links persist until offsets of these sections are assigned and until CodeHolder::resolveUnresolvedLinks() is called. It's an error if you end up with code that has unresolved label links after flattening. You can verify it by calling CodeHolder::hasUnresolvedLinks(), which inspects the value returned by CodeHolder::unresolvedLinkCount().

AsmJit can flatten code that uses multiple sections by assigning each section an incrementing offset that respects its alignment. Use CodeHolder::flatten() to do that. After the sections are flattened their offsets and virtual-sizes are adjusted to respect each section's buffer size and alignment. The CodeHolder::resolveUnresolvedLinks() function must be called before relocating the code held by CodeHolder. You can also flatten your code manually by iterating over all sections and calculating their offsets (relative to base) by your own algorithm. In that case CodeHolder::flatten() should not be called, however, CodeHolder::resolveUnresolvedLinks() should be.

The example below shows how to use a built-in virtual memory allocator JitAllocator instead of using JitRuntime (just in case you want to use your own memory management) and how to relocate the generated code into your own memory block - you can use your own virtual memory allocator if you prefer that, but that's OS specific and not covered by the documentation.

The following code is similar to the previous one, but implements a function working in both 32-bit and 64-bit environments:

#include <asmjit/x86.h>
#include <stdio.h>
using namespace asmjit;
typedef void (*SumIntsFunc)(int* dst, const int* a, const int* b);
int main() {
CodeHolder code; // Create a CodeHolder.
CodeInfo info(ArchInfo::kIdX86); // Initialize CodeInfo for 32-bit X86.
code.init(info); // Initialize CodeHolder for 32-bit X86.
x86::Assembler a(&code); // Create and attach x86::Assembler to `code`.
// Generate a function runnable in both 32-bit and 64-bit architectures:
bool isX86 = ASMJIT_ARCH_X86 == 32;
// Signature: 'void func(int* dst, const int* a, const int* b)'.
x86::Gp dst;
x86::Gp src_a;
x86::Gp src_b;
// Handle the difference between 32-bit and 64-bit calling conventions
// (arguments passed through stack vs. arguments passed by registers).
if (isX86) {
dst = x86::eax;
src_a = x86::ecx;
src_b = x86::edx;
a.mov(dst , x86::dword_ptr(x86::esp, 4));
a.mov(src_a, x86::dword_ptr(x86::esp, 8));
a.mov(src_b, x86::dword_ptr(x86::esp, 12));
}
else {
#if defined(_WIN32)
dst = x86::rcx; // First argument (destination pointer).
src_a = x86::rdx; // Second argument (source 'a' pointer).
src_b = x86::r8; // Third argument (source 'b' pointer).
#else
dst = x86::rdi; // First argument (destination pointer).
src_a = x86::rsi; // Second argument (source 'a' pointer).
src_b = x86::rdx; // Third argument (source 'b' pointer).
#endif
}
a.movdqu(x86::xmm0, x86::ptr(src_a)); // Load 4 ints from [src_a] to XMM0.
a.movdqu(x86::xmm1, x86::ptr(src_b)); // Load 4 ints from [src_b] to XMM1.
a.paddd(x86::xmm0, x86::xmm1); // Add 4 ints in XMM1 to XMM0.
a.movdqu(x86::ptr(dst), x86::xmm0); // Store the result to [dst].
a.ret(); // Return from function.
// Even when we didn't use multiple sections AsmJit could insert one section
// called '.addrtab' (address table section), which would be filled by data
// required by relocations (absolute jumps and calls). You can omit this code
// if you are 100% sure your code doesn't contain multiple sections and
// such relocations. You can use `CodeHolder::hasAddressTable()` to verify
// whether the address table section does exist.
code.flatten();
// After the code was generated it can be relocated manually to any memory
// location, however, we need to know it's size before we perform memory
// allocation. `CodeHolder::codeSize()` returns the worst estimated code
// size in case that relocations are not possible without trampolines (in
// that case some extra code at the end of the current code buffer is
// generated during relocation).
size_t estimatedSize = code.codeSize();
// Instead of rolling up our own memory allocator we can use the one AsmJit
// provides. It's decoupled so you don't need to use `JitRuntime` for that.
JitAllocator allocator;
// Allocate an executable virtual memory and handle a possible failure.
void* p = allocator.alloc(estimatedSize);
if (!p)
return 0;
// Now relocate the code to the address provided by the memory allocator.
// Please note that this DOESN'T COPY anything to `p`. This function will
// store the address in CodeInfo and use relocation entries to patch the
// existing code in all sections to respect the base address provided.
code.relocateToBase((uint64_t)p);
// This is purely optional. There are cases in which the relocation can omit
// unneeded data, which would shrink the size of address table. If that
// happened the codeSize returned after relocateToBase() would be smaller
// than the originally `estimatedSize`.
size_t codeSize = code.codeSize();
// This will copy code from all sections to `p`. Iterating over all sections
// and calling `memcpy()` would work as well, however, this function supports
// additional options that can be used to also zero pad sections' virtual
// size, etc.
//
// With some additional features, copyFlattenData() does roughly this:
// for (Section* section : code.sections())
// memcpy((uint8_t*)p + section->offset(),
// section->data(),
// section->bufferSize());
// Execute the generated function.
int inA[4] = { 4, 3, 2, 1 };
int inB[4] = { 1, 5, 2, 8 };
int out[4];
// This code uses AsmJit's ptr_as_func<> to cast between void* and SumIntsFunc.
ptr_as_func<SumIntsFunc>(p)(out, inA, inB);
// Prints {5 8 4 9}
printf("{%d %d %d %d}\n", out[0], out[1], out[2], out[3]);
// Release 'p' is it's no longer needed. It will be destroyed with 'vm'
// instance anyway, but it's a good practice to release it explicitly
// when you know that the function will not be needed anymore.
allocator.release(p);
return 0;
}

If you know the base-address in advance (before the code generation) the CodeInfo::setBaseAddress() function can be used to setup it. In that case the Assembler will know the absolute position of each instruction and would be able to use it during instruction encoding to prevent relocations where possible. The following example shows how to configure the base address:

#include <asmjit/x86.h>
#include <stdio.h>
void initializeCodeHolder(CodeHolder& code) {
// Configure CodeInfo with base address.
CodeInfo info(...);
info.setBaseAddress(uint64_t(0x1234));
// initialize CodeHolder with custom CodeInfo.
code.init(ci);
}

Namespaces

Classes

Enumerations

Functions

Enumeration Type Documentation

AlignMode : uint32_tenum

Align mode.

ConstantDescription
kAlignCode 

Align executable code.

kAlignData 

Align non-executable code.

kAlignZero 

Align by a sequence of zeros.

kAlignCount 

Count of alignment modes.