Logging

Logging and formatting.

Overview

The initial phase of a project that generates machine code is not always smooth. Failure cases are common not just at the beginning phase, but also during the development or refactoring. AsmJit provides logging functionality to address this issue. AsmJit does already a good job with function overloading to prevent from emitting unencodable instructions, but it can't prevent from emitting machine code that is correct at instruction level, but doesn't work when it's executed asa whole. Logging has always been an important part of AsmJit's infrastructure and looking at logs can sometimes reveal code generation issues quickly.

AsmJit provides API for logging and formatting:

AsmJit's Logger serves the following purposes:

  • Provides a basic foundation for logging.
  • Abstract class leaving the implementation on users. The following built-in implementations are provided for simplicity:

AsmJit's FormatOptions provides the following to customize the formatting of instructions and operands through:

Logging

A Logger is typically attached to a CodeHolder, which propagates it to all attached emitters automatically. The example below illustrates how to use FileLogger that outputs to standard output:

#include <asmjit/core.h>
#include <stdio.h>
using namespace asmjit;
int main() {
JitRuntime rt; // Runtime specialized for JIT code execution.
FileLogger logger(stdout); // Logger should always survive CodeHolder.
CodeHolder code; // Holds code and relocation information.
code.init(rt.environment(), // Initialize code to match the JIT environment.
rt.cpuFeatures());
code.setLogger(&logger); // Attach the `logger` to `code` holder.
// ... code as usual, everything emitted will be logged to `stdout` ...
return 0;
}

If output to FILE stream is not desired it's possible to use StringLogger, which concatenates everything into a multi-line string:

#include <asmjit/core.h>
#include <stdio.h>
#include <utility>
using namespace asmjit;
int main() {
JitRuntime rt; // Runtime specialized for JIT code execution.
StringLogger logger; // Logger should always survive CodeHolder.
CodeHolder code; // Holds code and relocation information.
code.init(rt.environment(), // Initialize code to match the JIT environment.
rt.cpuFeatures());
code.setLogger(&logger); // Attach the `logger` to `code` holder.
// ... code as usual, logging will be concatenated to logger string ...
// You can either use the string from StringLogger directly or you can
// move it. Logger::data() returns its content as null terminated char[].
printf("Logger content: %s\n", logger.data());
// It can be moved into your own string like this:
String content = std::move(logger.content());
printf("The same content: %s\n", content.data());
return 0;
}

Formatting

AsmJit uses Formatter to format inputs that are then passed to Logger. Formatting is public and can be used by AsmJit users as well. The most important thing to know regarding formatting is that Formatter always appends to the output string, so it can be used to build complex strings without having to concatenate intermediate strings.

The first example illustrates how to format operands:

#include <asmjit/x86.h>
#include <stdio.h>
using namespace asmjit;
void logOperand(Arch arch, const Operand_& op) {
// The emitter is optional (named labels and virtual registers need it).
BaseEmitter* emitter = nullptr;
// No flags by default.
Formatter::formatOperand(sb, formatFlags, emitter, arch, op);
printf("%s\n", sb.data());
}
void formattingExample() {
using namespace x86;
// Architecture is not part of operand, it must be passed explicitly.
// Format flags. We pass it explicitly also to 'logOperand' to make
// compatible with what AsmJit normally does.
Arch arch = Arch::kX64;
logOperand(arch, rax); // Prints 'rax'.
logOperand(arch, ptr(rax, rbx, 2)); // Prints '[rax + rbx * 4]`.
logOperand(arch, dword_ptr(rax, rbx, 2)); // Prints 'dword [rax + rbx * 4]`.
logOperand(arch, imm(42)); // Prints '42'.
}

Next example illustrates how to format whole instructions:

#include <asmjit/x86.h>
#include <stdio.h>
#include <utility>
using namespace asmjit;
template<typename... Args>
void logInstruction(Arch arch, const BaseInst& inst, Args&&... args) {
// The emitter is optional (named labels and virtual registers need it).
BaseEmitter* emitter = nullptr;
// No flags by default.
// The formatter expects operands in an array.
Operand_ operands[] { std::forward<Args>(args)... };
sb, formatFlags, emitter, arch, inst, operands, sizeof...(args));
printf("%s\n", sb.data());
}
void formattingExample() {
using namespace x86;
// Architecture is not part of operand, it must be passed explicitly.
// Format flags. We pass it explicitly also to 'logOperand' to make
// compatible with what AsmJit normally does.
Arch arch = Arch::kX64;
// Prints 'mov rax, rcx'.
logInstruction(arch, BaseInst(Inst::kIdMov), rax, rcx);
// Prints 'vaddpd zmm0, zmm1, [rax] {1to8}'.
logInstruction(arch,
BaseInst(Inst::kIdVaddpd),
zmm0, zmm1, ptr(rax)._1to8());
// BaseInst abstracts instruction id, instruction options, and extraReg.
// Prints 'lock add [rax], rcx'.
logInstruction(arch,
ptr(rax), rcx);
// Similarly an extra register (like AVX-512 selector) can be used.
// Prints 'vaddpd zmm0 {k2} {z}, zmm1, [rax]'.
logInstruction(arch,
BaseInst(Inst::kIdAdd, InstOptions::kX86_ZMask, k2),
zmm0, zmm1, ptr(rax));
}

And finally, the example below illustrates how to use a built-in function to format the content of BaseBuilder, which consists of nodes:

#include <asmjit/core.h>
#include <stdio.h>
using namespace asmjit;
void formattingExample(BaseBuilder* builder) {
FormatOptions formatOptions {};
// This also shows how temporary strings can be used.
// FormatNodeList requires the String for output, formatting flags, which
// were zero (no extra flags), and the builder instance, which we have
// provided. An overloaded version also exists, which accepts begin and
// and end nodes, which can be used to only format a range of nodes.
Formatter::formatNodeList(sb, formatOptions, builder);
// You can do whatever else with the string, it's always null terminated,
// so it can be passed to C functions like printf().
printf("%s\n", sb.data());
}

Namespaces

Classes

Enumerations

Enumeration Type Documentation

class FormatFlags : uint32_tenumstrong◆ 

Format flags used by Logger and FormatOptions.

ConstantDescription
kNone 

No formatting flags.

kMachineCode 

Show also binary form of each logged instruction (Assembler).

kExplainImms 

Show a text explanation of some immediate values.

kHexImms 

Use hexadecimal notation of immediate values.

kHexOffsets 

Use hexadecimal notation of addresses and offsets in addresses.

kRegCasts 

Show casts between virtual register types (Compiler output).

kPositions 

Show positions associated with nodes (Compiler output).

kRegType 

Always format a register type (Compiler output).

class FormatIndentationGroup : uint32_tenumstrong◆ 

Format indentation group, used by FormatOptions.

ConstantDescription
kCode 

Indentation used for instructions and directives.

kLabel 

Indentation used for labels and function nodes.

kComment 

Indentation used for comments (not inline comments).

kMaxValue 

Maximum value of FormatIndentationGroup.

class FormatPaddingGroup : uint32_tenumstrong◆ 

Format padding group, used by FormatOptions.

ConstantDescription
kRegularLine 

Describes padding of a regular line, which can represent instruction, data, or assembler directives.

kMachineCode 

Describes padding of machine code dump that is visible next to the instruction, if enabled.

kMaxValue 

Maximum value of FormatPaddingGroup.