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 as a 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 inplementations are provided for simplicty:

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 to the same arch as JIT runtime.
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 to the same arch as JIT runtime.
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/core.h>
#include <stdio.h>
using namespace asmjit;
void logOperand(uint32_t arch, const Operand_& op) {
// The emitter is optional (named labels and virtual registers need it).
BaseEmitter* emitter = nullptr;
// No flags by default.
uint32_t formatFlags = FormatOptions::kNoFlags;
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.
uint32_t arch = Environment::kArchX64;
log(arch, rax); // Prints 'rax'.
log(arch, ptr(rax, rbx, 2)); // Prints '[rax + rbx * 4]`.
log(arch, dword_ptr(rax, rbx, 2)); // Prints 'dword [rax + rbx * 4]`.
log(arch, imm(42)); // Prints '42'.
}

Next example illustrates how to format whole instructions:

#include <asmjit/core.h>
#include <stdio.h>
#include <utility>
using namespace asmjit;
template<typename... Args>
void logInstruction(uint32_t arch, const BaseInst& inst, Args&&... args) {
// The emitter is optional (named labels and virtual registers need it).
BaseEmitter* emitter = nullptr;
// No flags by default.
uint32_t formatFlags = FormatOptions::kNoFlags;
// 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.
uint32_t arch = Environment::kArchX64;
// Prints 'mov rax, rcx'.
logInstruction(arch, BaseInst(Inst::kIdMov), rax, rcx);
// Prints 'vaddpd zmm0, zmm1, [rax] {1to8}'.
logInstruction(arch,
zmm0, zmm1, ptr(rax)._1toN());
// BaseInst abstracts instruction id, instruction options, and extraReg.
// Prints 'lock add [rax], rcx'.
logInstruction(arch,
x86::ptr(rax), rcx);
// Similarly an extra register (like AVX-512 selector) can be used.
// Prints 'vaddpd zmm0 {k2} {z}, zmm1, [rax]'.
logInstruction(arch,
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) {
uint32_t formatFlags = FormatOptions::kNoFlags;
// 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, formatFlags, 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