Core

Globals, code storage, and emitter interface.

Overview

AsmJit library uses CodeHolder to hold code during code generation 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:

There are also other core classes that are important:

  • Environment - describes where the code will run. Environment brings the concept of target triples or tuples into AsmJit, which means that users can specify target architecture, platform, and ABI.
  • Type - encapsulates lightweight type functionality that can be used to describe primitive and vector types. Types are used by higher level utilities, for example by Function and Compiler.
  • CpuInfo - encapsulates CPU information - stores both CPU information and features described by BaseFeatures.

AsmJit also provides global constants:

  • 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.environment()); // Initialize code to match the JIT environment.
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 Environment

In the previous example the Environment is retrieved from JitRuntime. It's logical as JitRuntime always returns an Environment that is compatible with the host. For example if your application runs in 64-bit mode the Environment returned will use Environment::kArchX64 architecture in contrast to Environment::kArchX86, which will be used in 32-bit mode on any X86 platform.

AsmJit allows to setup the Environment manually and to select a different architecture and ABI 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 Environment and initialize it to Environment::kArchX86.

#include <asmjit/x86.h>
#include <stdio.h>
using namespace asmjit;
int main(int argc, char* argv[]) {
using namespace asmjit::x86;
// Create a custom environment initialized to 32-bit X86 architecture.
CodeHolder code; // Create a CodeHolder.
code.init(env); // Initialize CodeHolder with custom environment.
// 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

In addition to Environment, CodeHolder 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() {
// Create a custom environment that matches the current host environment.
CodeHolder code; // Create a CodeHolder.
code.init(env); // Initialize CodeHolder with environment.
x86::Assembler a(&code); // Create and attach x86::Assembler to `code`.
// 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 (env.is32Bit()) {
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 (env.isPlatformWindows()) {
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).
}
}
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 CodeHolder 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) it can be passed as a second argument to CodeHolder::init(). 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) {
Environment env = hostEnvironment();
uint64_t baseAddress = uint64_t(0x1234);
// initialize CodeHolder with environment and custom base address.
code.init(env, baseAddress);
}

Label Offsets and Links

When a label that is not yet bound is used by the Assembler, it creates a LabelLink, which is then added to a LabelEntry. These links are also created if a label is used in a different section than in which it was bound. Let's examine some functions that can be used to check whether there are any unresolved links.

#include <asmjit/core.h>
#include <stdio.h>
void labelLinksExample(CodeHolder& code, const Label& label) {
// Tests whether the `label` is bound.
bool isBound = code.isLabelBound(label);
printf("Label %u is %s\n", label.id(), isBound ? "bound" : "not bound");
// Returns true if the code contains either referenced, but unbound
// labels, or cross-section label links that are not resolved yet.
bool hasUnresolved = code.hasUnresolvedLinks(); // Boolean answer.
size_t nUnresolved = code.unresolvedLinkCount(); // Count of unresolved links.
printf("Number of unresolved links: %zu\n", nUnresolved);
}

There is no function that would return the number of unbound labels as this is completely unimportant from CodeHolder's perspective. If a label is not used then it doesn't matter whether it's bound or not, only actually used labels matter. After a Label is bound it's possible to query its offset offset relative to the start of the section where it was bound:

#include <asmjit/core.h>
#include <stdio.h>
void labelOffsetExample(CodeHolder& code, const Label& label) {
// Label offset is known after it's bound. The offset provided is relative
// to the start of the section, see below for alternative. If the given
// label is not bound the offset returned will be zero. It's recommended
// to always check whether the label is bound before using its offset.
uint64_t sectionOffset = code.labelOffset(label);
printf("Label offset relative to section: %llu\n", (unsigned long long)sectionOffset);
// If you use multiple sections and want the offset relative to the base.
// NOTE: This function expects that the section has already an offset and
// the label-link was resolved (if this is not true you will still get an
// offset relative to the start of the section).
uint64_t baseOffset = code.labelOffsetFromBase(label);
printf("Label offset relative to base: %llu\n", (unsigned long long)baseOffset);
}

Sections

AsmJit allows to create multiple sections within the same CodeHolder. A test-case asmjit_test_x86_sections.cpp can be used as a reference point although the following example should also provide a useful insight:

#include <asmjit/x86.h>
#include <stdio.h>
void sectionsExample(CodeHolder& code) {
// Text section is always provided as the first section.
Section* text = code.textSection(); // or code.sectionById(0);
// To create another section use CodeHolder::newSection().
Section* data;
Error err = code.newSection(&data,
".data", // Section name
SIZE_MAX, // Name length if the name is not null terminated (or SIZE_MAX).
0, // Section flags, see Section::Flags.
8); // Section alignment, must be power of 2.
// When you switch sections in Assembler, Builder, or Compiler the cursor
// will always move to the end of that section. When you create an Assembler
// the cursor would be placed at the end of the first (.text) section, which
// is initially empty.
x86::Assembler a(&code);
Label L_Data = a.newLabel();
a.mov(x86::eax, x86::ebx); // Emits in .text section.
a.section(data); // Switches to the end of .data section.
a.bind(L_Data); // Binds label in this .data section
a.db(0x01); // Emits byte in .data section.
a.section(text); // Switches to the end of .text section.
a.add(x86::ebx, x86::eax); // Emits in .text section.
// References a label in .text section, which was bound in .data section.
// This would create a LabelLink even when the L_Data is already bound,
// because the reference crosses sections. See below...
a.lea(x86::rsi, x86::ptr(L_Data));
}

The last line in the example above shows that a LabelLink would be created even for bound labels that cross sections. In this case a referenced label was bound in another section, which means that the link couldn't be resolved at that moment. If your code uses sections, but you wish AsmJit to flatten these sections (you don't plan to flatten them manually) then there is an API for that.

#include <asmjit/x86.h>
#include <stdio.h>
// ... (continuing the previous example) ...
void sectionsExampleContinued(CodeHolder& code) {
// Suppose we have some code that contains multiple sections and
// we would like to flatten it by using AsmJit's built-in API:
Error err = code.flatten();
if (err) {
// There are many reasons it can fail, so always handle a possible error.
printf("Failed to flatten the code: %s\n", DebugUtils::errorAsString(err));
exit(1);
}
// After flattening all sections would contain assigned offsets
// relative to base. Offsets are 64-bit unsigned integers so we
// cast them to `size_t` for simplicity. On 32-bit targets it's
// guaranteed that the offset cannot be greater than `2^32 - 1`.
printf("Data section offset %zu", size_t(data->offset()));
// The flattening doesn't resolve unresolved label links, this
// has to be done manually as flattening can be done separately.
err = code.resolveUnresolvedLinks();
if (err) {
// This is the kind of error that should always be handled...
printf("Failed to resolve label links: %s\n", DebugUtils::errorAsString(err));
exit(1);
}
if (code.hasUnresolvedLinks()) {
// This would mean either unbound label or some other issue.
printf("The code has %zu unbound labels\n", code.unresovedLinkCount());
exit(1);
}
}

Namespaces

Classes

Macros

Enumerations

Functions

Macro Definition Documentation

#define ASMJIT_LIBRARY_VERSION 0x010400 /* 1.4.0* /

AsmJit library version in (Major << 16) | (Minor << 8) | (Patch) format.

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.

Function Documentation

Environment hostEnvironment()staticnoexcept

Returns the host environment constructed from preprocessor macros defined by the compiler.

The returned environment should precisely match the target host architecture, sub-architecture, platform, and ABI.