Home

Awesome

Build Status

Xbyak_aarch64 ; JIT assembler for AArch64 CPUs by C++

Abstract

Xbyak_aarch64 is a C++ library which enables run-time assemble coding with the AArch64 instruction set of Arm(R)v8-A architecture. Xbyak_aarch64 is based on Xbyak developed for x86_64 CPUs by MITSUNARI Shigeo.

Feature

Requirement

Xbyak_aarch64 uses no external library and it is written as standard C++ files so that we believe Xbyak_aarch64 works various environment with various compilers.

News

Break backward compatibility:

Supported Compilers

Almost C++11 or later compilers for AArch64 such as g++, clang++.

Install

The command make builds lib/libxbyak_aarch64.a.

make install installs headers and a library into /usr/local/ (default path). Or add the location of the xbyak_aarch64 directory to your compiler's include and link paths.

Execution environment

You can execute programs using xbyak_aarch64 on systems running on Arm(R)v8-A architecure CPUs. Even if you can't access such systems, you can try Xbyak_aarch64 on QEMU (generic and open source machine emulator and virtualizer).

We have checked programs built with xbyak_aarch64 can be executed with qemu version 3.1.0 on Linux running on x86_64 CPUs. The following dpkgs are required.

Then execute the following commands.

cd xbyak_aarch64/sample
aarch64-linux-gnu-g++ add.cpp
env QEMU_LD_PREFIX=/usr/aarch64-linux-gnu qemu-aarch64 ./a.out

On Fedora linux use

sudo dnf install qemu qemu-user qemu-system-aarch64 qemu-user-static-aarch64
make clean
CXX=aarch64-redhat-linux-g++ make
cd sample
CXX=aarch64-redhat-linux-g++ make
aarch64-redhat-linux-g++ add.cpp -I ../ -L../lib/libxbyak_aarch64.a

M1 mac

For test, aarch64-gas is necessary. Make the binary from GNU-A Downloads or use aarch64-unknown-linux-gnu.

How to make lib

make

makes lib/libxbyak_aarch64.a.

How to use cmake

on Windows ARM64

mkdir build
cd build
cmake .. -A arm64
msbuild xbyak_aarch64.sln /p:Configuration=Release

How to use Xbyak_aarch64

Inherit Xbyak::CodeGenerator class and make the class method. Make an instance of the class and get the function pointer by calling getCode() and call it. The following example 1) generates a JIT-ed function which simply adds two integer values passed as arguments and returns an integer value as a result, and 2) calls the function. This example outputs "7" to STDOUT.

compile options:

#include "xbyak_aarch64.h"
using namespace Xbyak_aarch64;
class Generator : public CodeGenerator {
public:
  Generator() {
    Label L1, L2;
    L(L1);
    add(w0, w1, w0);
    cmp(w0, 13);
    b(EQ, L2);
    sub(w1, w1, 1);
    b(L1);
    L(L2);
    ret();
  }
};
int main() {
  Generator gen;
  gen.ready();
  auto f = gen.getCode<int (*)(int, int)>();
  std::cout << f(3, 4) << std::endl;
  return 0;
}

Syntax

Synatx is similar to "AS" (GNU assembler). Each AArch64 instruction is correspond to each function written in "xbyak_aarch64_mnemonic.h", we call it a mnemonic function. Please refer files in sample/mnemonic_syntax directory for usage of mnemonic functions. The below example shows correspondence between "AS" syntax and Xbyak_aarch64 mnemonic functions.

"AS"                  Xbyak_aarch64
add w0, w0, w1  --> add(w0, w0, w1);
add x0, x0, x1  --> add(x0, x0, x1);
add x0, x0, 5   --> add(x0, x0, 5);
mov w0, 1       --> mov(w0, 1);
ret             --> ret();

Mnemonic functions

Each mnemonic function corresponds to one AArch64 instruction. Function name represents corresponding mnemonic of instruction. Because "and", "or", "not" are reserved keywords C++ and "." can't be used in C++ function name, the following special cases are exist.

Mnemonic of instructionName of mnemonic funciton
andand_
oror_
notnot_
b.condb

Operand

This section explains operands, which are given to mnemonic functions as their arguments.

General purpose registers

As general purpose registers, the following table shows example of mnemonic functions' arguments ("Instance name" column).

Instance nameC++ class nameRemarks
w0, w1, ..., w30WReg32-bit general purpose registers
x0, x1, ..., x30WReg64-bit general purpose registers
wzrWReg32-bit zero register
xzrXReg64-bit zero register
wspWReg32-bit stack pointer
spzXReg64-bit stack pointer

You can also use your original instance as mnemonic functions argumetns. Please refer constructor of "C++ class name" in Xbyak_aarch64 files.

WReg dstReg(0);
WReg srcReg0(1);
WReg srcReg1(2);

add(dstReg, srcReg0, srcReg1);  <--- (1)
add(w0, w1, w2);                <--- Output is same JIT code of (1)
SIMD/Floating point registers as scalar registers

As SIMD/Floating point registers with scalar use, the following table shows example of mnemonic functions' arguments ("Instance name" column).

Instance nameC++ class nameRemarks
b0, b1, ..., b31BReg8-bit scalar registers
h0, h1, ..., h31HReg16-bit scalar registers
s0, s1, ..., s31SReg32-bit scalar registers
d0, d1, ..., d31DReg64-bit scalar registers
q0, q1, ..., q31QReg128-bit scalar registers

You can also use your original instance as mnemonic functions argumetns. Please refer constructor of "C++ class name" in Xbyak_aarch64 files.

BReg dstReg(0);

mov(b0, v0.b[5]);       <--- (1)
mov(dstReg, v0.b[5]);   <--- Output is same JIT code of (1)

SIMD/Floating point registers as vector registers

As SIMD/Floating point registers with vector use, the following table shows example of mnemonic functions' arguments ("Instance name" column).

Instance nameC++ class nameRemarks
v0.b8, v1.b8, ..., v31.b8VReg8B8-bit x8 elements vector registers
v0.b16, v1.b16, ..., v31.b16VReg16B8-bit x16 elements vector registers
v0.h4, v1.h4, ..., v31.h4VReg4H16-bit x4 elements vector registers
v0.h8, v1.h8, ..., v31.h8VReg8H16-bit x8 elements vector registers
v0.s2, v1.s2, ..., v31.s2VReg2S32-bit x2 elements vector registers
v0.s4, v1.s4, ..., v31.s4VReg4S32-bit x4 elements vector registers
v0.d1, v1.d1, ..., v31.d1VReg1D64-bit x1 elements vector registers
v0.d2, v1.d2, ..., v31.d2VReg2D64-bit x2 elements vector registers

You can also use your original instance as mnemonic functions argumetns. Please refer constructor of "C++ class name" in Xbyak_aarch64 files.

VReg16B dstReg(0);
VReg16B srcReg(1);

mov(v0.b16, v1.b16);   <--- (1)
mov(dstReg, srcReg);   <--- Output is same JIT code of (1)

Element of SIMD/Floating point registers

As SIMD/Floating point registers with element-wise use, the following table shows example of mnemonic functions' arguments ("Instance name" column).

Instance nameC++ class nameRemarks
v0.b[0], v0.b[1], ..., v0.b[15]VRegBElem8-bit element of vector register #0
vN.b[0], vN.b[1], ..., vN.b[15]VRegBElem8-bit element of vector register #N
v0.h[0], v0.h[1], ..., v0.h[15]VRegHElem16-bit element of vector register #0
vN.h[0], vN.h[1], ..., vN.h[15]VRegHElem16-bit element of vector register #N
v0.s[0], v0.s[1], ..., v0.s[15]VRegSElem32-bit element of vector register #0
vN.s[0], vN.s[1], ..., vN.s[15]VRegSElem32-bit element of vector register #N
v0.d[0], v0.d[1], ..., v0.d[15]VRegDElem64-bit element of vector register #0
vN.d[0], vN.d[1], ..., vN.d[15]VRegDElem64-bit element of vector register #N

You can also use your original instance as mnemonic functions argumetns. Please refer constructor of "C++ class name" in Xbyak_aarch64 files.

VRegBElem dstReg(0, 3, 16);
VRegBElem srcReg(1, 4, 16);
VRegB     hoge(0);

mov(v0.b[3], v1.b[4]); <--- (1)
mov(dstReg,  srcReg);  <--- Output is same JIT code of (1)
mov(hoge[3], srcReg);  <--- Output is same JIT code of (1)

SIMD/Floating point register lists

As SIMD/Floating point register lists, the following table shows example of mnemonic functions' arguments ("Instance name" column).

Register index can be cyclic.

Instance nameC++ class nameRemarks
v0.b16 - v0.b16VReg16BListSIMD/Floating point register list containing 8-bit elements x 16 x 1
v0.b16 - v1.b16VReg16BListSIMD/Floating point register list containing 8-bit elements x 16 x 2
v0.b16 - v2.b16VReg16BListSIMD/Floating point register list containing 8-bit elements x 16 x 3
v0.b16 - v3.b16VReg16BListSIMD/Floating point register list containing 8-bit elements x 16 x 4
v31.b16 - v2.b16VReg16BListSIMD/Floating point register list containing 8-bit elements x 16 x 4
v0.h8 - v3.h8VReg8HListSIMD/Floating point register list containing 16-bit elements x 8 x 4
v0.s4 - v3.s4VReg4SListSIMD/Floating point register list containing 32-bit elements x 4 x 4
v0.d2 - v3.d2VReg2DListSIMD/Floating point register list containing 64-bit elements x 2 x 4

You can also use your original instance as mnemonic functions arguments. Please refer constructor of "C++ class name" in Xbyak_aarch64 files.

VReg16BList dstList(v0.b16, v3.b16);
VReg16BList hoge(VReg16B(0), VReg16B(3));

ld4((v0.b16 - v3.b16), ptr(x0)); <--- (1)
ld4(dstLsit, ptr(x0));   <--- Output is same JIT code of (1)
ld4(hoge, ptr(x0));      <--- Output is same JIT code of (1)

Element of vector register lists

As elements of SIMD/Floating point register lists, the following table shows example of mnemonic functions' arguments ("Instance name" column).

Instance nameC++ class nameRemarks
(v0.b16 - v0.b16)[0]VRegBElemFirst 8-bit element of one-vector-register list
(v0.b16 - v0.b16)[N]VRegBElemN-th 8-bit element of one-vector-register list
(v0.b16 - v3.b16)[N]VRegBElemN-th 8-bit element of four-vector-register list
(v0.h8 - v3.h8)[N]VRegHElemN-th 16-bit element of four-vector-register list
(v0.s4 - v3.s4)[N]VRegSElemN-th 32-bit element of four-vector-register list
(v0.d2 - v3.d2)[N]VRegDElemN-th 64-bit element of four-vector-register list

You can also use your original instance as mnemonic functions argumetns. Please refer constructor of "C++ class name" in Xbyak_aarch64 files.

VRegBElem dstReg(0, 3, 16);
VRegBElem hoge((VReg16B(0) - VReg16B(3))[3]);

ld4((v0.b16 - v3.b16)[3], ptr(x0)); <--- (1)
ld4(dstReg, ptr(x0));       <--- Output is same JIT code of (1)
ld4(hoge, ptr(x0));         <--- Output is same JIT code of (1)

SVE registers as vector registers

AS SVE registers, the following table shows example of mnemonic functions' arguments ("Instance name" column).

Instance nameC++ class nameRemarks
Z0.b, z1.b, ..., z31.bZRegB8-bit x 64 elements SVE registers.
Z0.h, z1.h, ..., z31.hZRegB16-bit x 32 elements SVE registers.
Z0.s, z1.s, ..., z31.sZRegB32-bit x 16 elements SVE registers.
Z0.d, z1.d, ..., z31.dZRegB64-bit x 8 elements SVE registers.
Z0.q, z1.q, ..., z31.qZRegB128-bit x 4 elements SVE registers.

You can also use your original instance as mnemonic functions argumetns. Please refer constructor of "C++ class name" in Xbyak_aarch64 files.

ZRegB dstReg(0);

add(z0.b, z1.b, z2.b);      <--- (1)
add(ZRegB(0), z1.b, z2.b);  <--- Output is same JIT code of (1)
add(dstReg, z1.b, z2.b);    <--- Output is same JIT code of (1)

Element of SVE registers

As SVE registers with element-wise use, the following table shows example of mnemonic functions' arguments ("Instance name" column).

Instance nameC++ class nameRemarks
z0.b[0], z0.b[1], ..., z0.b[15]ZRegBElem8-bit element of SVE register #0
vN.b[0], vN.b[1], ..., vN.b[15]ZRegBElem8-bit element of SVE register #N
z0.h[0], z0.h[1], ..., z0.h[15]ZRegHElem16-bit element of SVE register #0
vN.h[0], vN.h[1], ..., vN.h[15]ZRegHElem16-bit element of SVE register #N
z0.s[0], z0.s[1], ..., z0.s[15]ZRegSElem32-bit element of SVE register #0
vN.s[0], vN.s[1], ..., vN.s[15]ZRegSElem32-bit element of SVE register #N
z0.d[0], z0.d[1], ..., z0.d[15]ZRegDElem64-bit element of SVE register #0
vN.d[0], vN.d[1], ..., vN.d[15]ZRegDElem64-bit element of SVE register #N
z0.q[0], z0.q[1], ..., z0.q[15]ZRegDElem128-bit element of SVE register #0
vN.q[0], vN.q[1], ..., vN.q[15]ZRegDElem128-bit element of SVE register #N

You can also use your original instance as mnemonic functions argumetns. Please refer constructor of "C++ class name" in Xbyak_aarch64 files.

ZRegBElem hoge(1, 7, 8);

dup(z0.b, z1.b[7]);  <--- (1)
add(z0.b, hoge);     <--- Output is same JIT code of (1)

SVE register lists

As SVE register lists, the following table shows example of mnemonic functions' arguments ("Instance name" column).

Register index can be cyclic.

Instance nameC++ class nameRemarks
z0.b - z0.bZRegBListSVE register list containing 8-bit elements x 64 x 1
z0.b - z1.bZRegBListSVE register list containing 8-bit elements x 64 x 2
z0.b - z2.bZRegBListSVE register list containing 8-bit elements x 64 x 3
z0.b - z3.bZRegBListSVE register list containing 8-bit elements x 64 x 4
z31.b - z2.bZRegBListSVE register list containing 8-bit elements x 64 x 4
z0.h - z3.hZRegHListSVE register list containing 16-bit elements x 32 x 4
z0.s - z3.sZRegSListSVE register list containing 32-bit elements x 16 x 4
z0.d - z3.dZRegDListSVE register list containing 64-bit elements x 8 x 4
z0.q - z3.qZRegQListSVE register list containing 128-bit elements x 4 x 4
ZRegBList dstList(z0.b, z3.b);
ZRegBList hoge(ZRegB(0) - ZRegB(3));

ld4((z0.b - z3.b), p0, ptr(x0)); <--- (1)
ld4(dstList, p0, ptr(x0));       <--- Output is same JIT code of (1)
ld4(hoge, p0, ptr(x0));          <--- Output is same JIT code of (1)

Element of SVE register lists

As elements of SVE register lists, the following table shows example of mnemonic functions' arguments ("Instance name" column).

Instance nameC++ class nameRemarks
(z0.b - z0.b)[0]ZRegBElemFirst 8-bit elements of one-SVE-register list
(z0.b - z0.b)[N]ZRegBElemN-th 8-bit elements of one-SVE-register list
(z0.b - z3.b)[N]ZRegBElemN-th 8-bit elements of four-SVE-register list
(z0.h - z3.h)[N]ZRegHElemN-th 16-bit elements of four-SVE-register list
(z0.s - z3.s)[N]ZRegSElemN-th 32-bit elements of four-SVE-register list
(z0.d - z3.d)[N]ZRegDElemN-th 64-bit elements of four-SVE-register list

You can also use your original instance as mnemonic functions argumetns analogous with the element of vector register lists.

Predicate registers

As predicate registers, the following table shows example of mnemonic functions' arguments ("Instance name" column).

Instance nameC++ class nameRemarks
p0, p1, ..., p7PRegPredicate registers
p0.b, p1.b, ..., p7.bPRegBPredicate registers for 8-bit elements
p0.h, p1.h, ..., p7.hPRegHPredicate registers for 16-bit elements
p0.s, p1.s, ..., p7.sPRegSPredicate registers for 32-bit elements
p0.d, p1.d, ..., p7.dPRegDPredicate registers for 64-bit elements

Though Xbyak_aarch64 defines PRegB, PRegH, PRegS, PRegD classes, you can use PReg class instances where mnemonic functions take predicate register operands. The AArch64 instructin set has two predication types, "merging" and "zeroing" predicate. You can use "T_m" or "T_z" to inform mnemonic functions which type you choses ((1), (2)).

You can ommit "T_m" and "T_z" for mnemonic functions whose correspond predicate instructions can take either one. For example, "CLS" instruction in the AARch64 instruction set is only defined for merging predicate, both (3) and (4) are OK.

Some instructions are defined for both predicated and unpredicated. Output JIT-ed code of (5) and (6) are deferent.

mov(z0.b, p0/T_m, 1);              <--- (1), merging predicate
mov(z0.b, p0/T_z, 1);              <--- (2), zeroing predicate

cls(z0.b, p0/T_m, z0.b);           <--- (3)
cls(z0.b, p0, z0.b);               <--- (4), output is same JIT code of (3)

add(z0.b, p0/T_m, z1.b, z2.b);     <--- (5), predicated add
add(z0.b, p0, z1.b, z2.b);         <--- (6), output is same JIT code of (5)
add(z0.b, z1.b, z2.b);             <--- (7), unpredicated add, output is NOT same JIT code of (5)

You can also use your original instance as mnemonic functions argumetns. Please refer constructor of "C++ class name" in Xbyak_aarch64 files.

PReg hoge(0);

add(z0.b, p0, z1.b, z2.b);         <--- (8)
add(z0.b, PReg(0), z1.b, z2.b);    <--- Output is same JIT code of (8)
add(z0.b, hoge, z1.b, z2.b);       <--- Output is same JIT code of (8)

Immediate values

You can use immediate values for arguments of mnemonic functions in the form that C++ syntax allows, such as, "10", "-128", "0xFF", "1<<32", "3.5", etc.

Please care for range of values. For example, "ADD (immediate)" instruction can receive signed 12-bit value so that you have to ensure that the value passed to mnemonic function is inside the range. Mnemonic functions of Xbyak_aarch64 checks immediate values at runtime, and throws exception if it detects range over.

void genAddFunc() {
     int a = 1<<16;
     add(x0, x0, a);    <--- This mnemonic function throws exception at runtime.
     ret();
}

Some immediate values may not decided at coding time but runtime. You should check the immediate values and handle them.

void gen_Summation_From_One_To_Parameter_Func(unsigned int N) {

    if(N < (1<<11)) {
        for(int i=0; i<N; i++) {
            add(x0, x0, N);
        }
        ret();
    } else {
        printf("Invalid parameter N=%d\n", N);
        printf("This function supports less than 2048.\n");
    }
}

Symbols

Some instructions of the AArch64 instruction set are used with sybols such as "UXTW", "LSL", "SXTW", "SXTX", "MUL VL", etc. The menomic functions for those instructions can receive keywords of "UXTW", "LSL", "SXTW", "SXTX", "MUL_VL", etc. Please "grep" keywords, for example

grep LSL sample/mnemonic_syntax/*

, to find usage of those menomic functions.

Addressing

The AArch64 instruction set has various addressing modes such as "No offset", "Post-index", "Pre-index", "Signed offset", "Unsigned offset". Please use "ptr()", "pre_ptr()", "post_ptr()" functions to specify which addressing mode you want to use. Please "grep" the functions, for example

grep -w ptr sample/mnemonic_syntax/*

, to find usage of these addressing functions.

Use examplecorresponding assembler syntaxRemarks
ldr(x0, ptr(x1, x7))ldr x0, [x1, x7]Register offset
str(w0, ptr(x7, w8, UXTW, 2))str w0, [x7, w8, UXTW 2]Register offset with shift amount
ldr(w0, post_ptr(x5, -16))ldr w0, [x5], -16Post-index
ldrb(w16, pre_ptr(x5, 127))ldrb w16, [x5, 127]!Pre-index

Label

You can use "Label" to direct where branch instruction jump to. The following example shows how to use "Label".

Label L1;           // (1), instance of Label class

mov(w4, w0);
mov(w3, 0);
mov(w0, 0);
L(L1);              // (2), "L" function registers JIT code address of this position to label L1.
add(w0, w0, w4);
add(w3, w3, 1);
cmp(w2, w3);
ccmp(w1, w3, 4, NE);
bgt(L1);            // (3), set destination of branch instruction to the address stored in L1.
  1. Prepare Label class instance.
  2. Call the L function to register destination address to the instance.
  3. Pass the instance to mnemonic functions correspond to branch instructions.

You can copy the address stored in "Label" instance by using assignL function.

Label L1,L2,L3;
....
L(L1);               // JIT code address of this position is stored to L1.
....
L(L2);               // JIT code address of this position is stored to L1.
....
if (flag) {
assignL(L3,L1);      // The address stored in L1 is copied to L3.
} else {
assignL(L3,L2);      // The address stored in L1 is copied to L3.
}
b(L3);               // If flag == true, branch destination is L1, otherwise L2.

Code size

The default max size of JIT-ed code is 4096 bytes. Specify the size in constructor of CodeGenerator() if necessary.

class Quantize : public Xbyak_aarch64::CodeGenerator {
public:
  Quantize()
    : CodeGenerator(8192)
  {
  }
  ...
};

User allocated memory

You can make JIT-ed code on prepared memory.

Call setProtectModeRE yourself to change memory mode if using the prepared memory.

uint8_t alignas(4096) buf[8192]; // C++11 or later

struct Code : Xbyak_aarch64::CodeGenerator {
    Code() : Xbyak_aarch64::CodeGenerator(sizeof(buf), buf)
    {
        mov(rax, 123);
        ret();
    }
};

int main()
{
    Code c;
    c.setProtectModeRE(); // set memory to Read/Exec
    printf("%d\n", c.getCode<int(*)()>()());
}

Note: See sample/test0.cpp.

AutoGrow

If AutoGrow is specified in a constructor of CodeGenerator, the memory region for JIT-ed code is automatically extended if needed.

Call ready() or readyRE() before calling getCode() to fix jump address.

struct Code : Xbyak_aarch64::CodeGenerator {
  Code()
    : Xbyak_aarch64::CodeGenerator(<default memory size>, Xbyak_aarch64::AutoGrow)
  {
     ...
  }
};
Code c;
// generate code for jit
c.ready(); // mode = Read/Write/Exec

Note:

Read/Exec mode

Xbyak_aarch64 set Read/Write/Exec mode to memory to run JIT-ed code. If you want to use Read/Exec mode for security, then specify DontSetProtectRWE for CodeGenerator and call setProtectModeRE() after generating JIT-ed code.

struct Code : Xbyak_aarch64::CodeGenerator {
    Code()
        : Xbyak_aarch64::CodeGenerator(4096, Xbyak_aarch64::DontSetProtectRWE)
    {
        mov(eax, 123);
        ret();
    }
};

Code c;
c.setProtectModeRE();

Call readyRE() instead of ready() when using AutoGrow mode.

How to pass arguments to JIT generated function

To be written...

Macro

To be written...

Sample

To be written...

License

Copyright FUJITSU LIMITED 2019-2020

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Reference

Notice

Acknowledgement

We are grateful to MITSUNARI-san (Cybozu Labs, Inc.) for release Xbyak as an open source software and his advice for development of Xbyak_aarch64.

History

DateVersionRemarks
December 9, 20190.9.0First public release version.

Copyright

Copyright FUJITSU LIMITED 2019-2020