dynamic x86-64 generation & jit

July 12, 2015

My linear genetic programming engine relies on dynamic generation of x86-64 machine code to create thousands of functions which are evolved over generations. My initial version of the code generation backend was a small and simple C library which was capable of assembling 30 core instructions to a memory mapped region. Using the Intel manuals, the X86 Opcode and Instruction Reference, and Agner Fog’s tables, I patched together a usable library. Adding more features to the GP engine necessitated extending the machine code generator. When the complexities of implementing the later SSE opcodes and AVX extensions became a major timesink, I began looking around for replacements.

AsmJit is a stellar x86/x64 JIT and Remote Assembler implemented in C++. It has support for the entire Intel instruction set up to AVX2, features a high level cross-platform compiler which makes memory addressing, labels, and the stack painless. Moreover, AsmJit has a wonderful logging engine and error handling mechanism to enable debugging generated code.

Example
#include <asmjit/asmjit.h>

using namespace asmjit;

int main(int argc, char* argv[]) {
  JitRuntime runtime;
  X86Compiler c(&runtime);

  // Function returning 'int' accepting pointer and two indexes.
  c.addFunc(kFuncConvHost, FuncBuilder3<int, const int*, intptr_t, intptr_t>());

  X86GpVar p(c, kVarTypeIntPtr, "p");
  X86GpVar aIndex(c, kVarTypeIntPtr, "aIndex");
  X86GpVar bIndex(c, kVarTypeIntPtr, "bIndex");

  c.setArg(0, p);
  c.setArg(1, aIndex);
  c.setArg(2, bIndex);

  X86GpVar a(c, kVarTypeInt32, "a");
  X86GpVar b(c, kVarTypeInt32, "b");

  // Read 'a' by using a memory operand having base register, index register
  // and scale. Translates to 'mov a, dword ptr [p + aIndex << 2]'.
  c.mov(a, ptr(p, aIndex, 2));

  // Read 'b' by using a memory operand having base register only. Variables
  // 'p' and 'bIndex' are both modified.

  // Shift bIndex by 2 (exactly the same as multiplying by 4).
  // And add scaled 'bIndex' to 'p' resulting in 'p = p + bIndex * 4'.
  c.shl(bIndex, 2);
  c.add(p, bIndex);

  // Read 'b'.
  c.mov(b, ptr(p));

  // a = a + b;
  c.add(a, b);

  c.ret(a);
  c.endFunc();

  // The prototype of the generated function changed also here.
  typedef int (*FuncType)(const int*, intptr_t, intptr_t);
  FuncType func = asmjit_cast<FuncType>(c.make());

  // Array passed to 'func'
  const int array[] = { 1, 2, 3, 5, 8, 13 };

  int x = func(array, 1, 2);
  int y = func(array, 3, 5);

  printf("x=%d\n", x); // Outputs "x=5".
  printf("y=%d\n", y); // Outputs "y=18".

  runtime.release((void*)func);
  return 0;
}
References
comments powered by Disqus