Home | History | Annotate | Download | only in aarch64
      1 Getting Started with VIXL AArch64
      2 =================================
      3 
      4 
      5 This guide will show you how to use the VIXL framework for AArch64. We will see
      6 how to set up the VIXL assembler and generate some code. We will also go into
      7 details on a few useful features provided by VIXL and see how to run the
      8 generated code in the VIXL simulator.
      9 
     10 The source code of the example developed in this guide can be found in the
     11 `examples/aarch64` directory (`examples/aarch64/getting-started.cc`).
     12 
     13 
     14 Creating the macro assembler and the simulator.
     15 -----------------------------------------------
     16 
     17 First of all you need to make sure that the header files for the assembler and
     18 the simulator are included. You should have the following lines at the beginning
     19 of your source file:
     20 
     21     #include "aarch64/simulator-aarch64.h"
     22     #include "aarch64/macro-assembler-aarch64.h"
     23 
     24 All VIXL components are declared in the `vixl::aarch64` namespace, so let's add
     25 this to the beginning of the file for convenience:
     26 
     27     using namespace vixl::aarch64;
     28 
     29 Creating a macro assembler is as simple as
     30 
     31     MacroAssembler masm;
     32 
     33 VIXL's assembler will generate some code at run-time, and this code needs to
     34 be stored in a buffer. By default the assembler will automatically manage
     35 the code buffer. However constructors are available that allow manual management
     36 of the code buffer.
     37 
     38 We also need to set up the simulator. The simulator uses a Decoder object to
     39 read and decode the instructions from the code buffer. We need to create a
     40 decoder and bind our simulator to this decoder.
     41 
     42     Decoder decoder;
     43     Simulator simulator(&decoder);
     44 
     45 
     46 Generating some code.
     47 ---------------------
     48 
     49 We are now ready to generate some code. The macro assembler provides methods
     50 for all the instructions that you can use. As it's a macro assembler,
     51 the instructions that you tell it to generate may not directly map to a single
     52 hardware instruction. Instead, it can produce a short sequence of instructions
     53 that has the same effect.
     54 
     55 For instance, the hardware `add` instruction can only take a 12-bit immediate
     56 optionally shifted by 12, but the macro assembler can generate one or more
     57 instructions to handle any 64-bit immediate. For example, `Add(x0, x0, -1)`
     58 will be turned into `Sub(x0, x0, 1)`.
     59 
     60 Before looking at how to generate some code, let's introduce a simple but handy
     61 macro:
     62 
     63     #define __ masm->
     64 
     65 It allows us to write `__ Mov(x0, 42);` instead of `masm->Mov(x0, 42);` to
     66 generate code.
     67 
     68 Now we are going to write a C++ function to generate our first assembly
     69 code fragment.
     70 
     71     void GenerateDemoFunction(MacroAssembler *masm) {
     72       __ Ldr(x1, 0x1122334455667788);
     73       __ And(x0, x0, x1);
     74       __ Ret();
     75     }
     76 
     77 The generated code corresponds to a function with the following C prototype:
     78 
     79     uint64_t demo_function(uint64_t x);
     80 
     81 This function doesn't perform any useful operation. It loads the value
     82 0x1122334455667788 into x1 and performs a bitwise `and` operation with
     83 the function's argument (stored in x0). The result of this `and` operation
     84 is returned by the function in x0.
     85 
     86 Now in our program main function, we only need to create a label to represent
     87 the entry point of the assembly function and to call `GenerateDemoFunction` to
     88 generate the code.
     89 
     90     Label demo_function;
     91     masm.Bind(&demo_function);
     92     GenerateDemoFunction(&masm);
     93     masm.Finalize();
     94 
     95 Now we are going to learn a bit more on a couple of interesting VIXL features
     96 which are used in this example.
     97 
     98 ### Label
     99 
    100 VIXL's assembler provides a mechanism to represent labels with `Label` objects.
    101 They are easy to use: simply create the C++ object and bind it to a location in
    102 the generated instruction stream.
    103 
    104 Creating a label is easy, since you only need to define the variable and bind it
    105 to a location using the macro assembler.
    106 
    107     Label my_label;      // Create the label object.
    108     __ Bind(&my_label);  // Bind it to the current location.
    109 
    110 The target of a branch using a label will be the address to which it has been
    111 bound. For example, let's consider the following code fragment:
    112 
    113     Label foo;
    114 
    115     __ B(&foo);     // Branch to foo.
    116     __ Mov(x0, 42);
    117     __ Bind(&foo);  // Actual address of foo is here.
    118     __ Mov(x1, 0xc001);
    119 
    120 If we run this code fragment the `Mov(x0, 42)` will never be executed since
    121 the first thing this code does is to jump to `foo`, which correspond to the
    122 `Mov(x1, 0xc001)` instruction.
    123 
    124 When working with labels you need to know that they are only to be used for
    125 local branches, and should be passed around with care. There are two reasons
    126 for this:
    127 
    128   - They can't safely be passed or returned by value because this can trigger
    129     multiple constructor and destructor calls. The destructor has assertions
    130     to check that we don't try to branch to a label that hasn't been bound.
    131 
    132   - The `B` instruction does not branch to labels which are out of range of the
    133     branch. The `B` instruction has a range of 2^28 bytes, but other variants
    134     (such as conditional or `CBZ`-like branches) have smaller ranges. Confining
    135     them to local ranges doesn't mean that we won't hit these limits, but it
    136     makes the lifetime of the labels much shorter and eases the debugging of
    137     these kinds of issues.
    138 
    139 
    140 ### Literal Pool
    141 
    142 On ARMv8 instructions are 32 bits long, thus immediate values encoded in the
    143 instructions have limited size. If you want to load a constant bigger than this
    144 limit you have two possibilities:
    145 
    146 1. Use multiple instructions to load the constant in multiple steps. This
    147   solution is already handled in VIXL. For instance you can write:
    148 
    149   `__ Mov(x0, 0x1122334455667788);`
    150 
    151   The previous instruction would not be legal since the immediate value is too
    152   big. However, VIXL's macro assembler will automatically rewrite this line into
    153   multiple instructions to efficiently generate the value.
    154 
    155 
    156 2. Store the constant in memory and load this value from the memory. The value
    157   needs to be written near the code that will load it since we use a PC-relative
    158   offset to indicate the address of this value. This solution has the advantage
    159   of making the value easily modifiable at run-time; since it does not reside
    160   in the instruction stream, it doesn't require cache maintenance when updated.
    161 
    162   VIXL also provides a way to do this:
    163 
    164   `__ Ldr(x0, 0x1122334455667788);`
    165 
    166   The assembler will store the immediate value in a "literal pool", a set of
    167   constants embedded in the code. VIXL will emit literal pools after natural
    168   breaks in the control flow, such as unconditional branches or return
    169   instructions.
    170 
    171   Literal pools are emitted regularly, such that they are within range of the
    172   instructions that refer to them. However, you can force a literal pool to be
    173   emitted using `masm.EmitLiteralPool()`.
    174 
    175 
    176 Running the code in the simulator.
    177 ----------------------------------
    178 
    179 Now we are going to see how to use the simulator to run the code that we
    180 generated previously.
    181 
    182 Use the simulator to assign a value to the registers. Our previous code example
    183 uses the register x0 as an input, so let's set the value of this register.
    184 
    185     simulator.WriteXRegister(0, 0x8899aabbccddeeff);
    186 
    187 Now we can jump to the "entry" label to execute the code:
    188 
    189     simulator.RunFrom(entry.target());
    190 
    191 When the execution is finished and the simulator returned, you can inspect
    192 the value of the registers after the execution. For instance:
    193 
    194     printf("x0 = %" PRIx64 "\n", simulator.ReadXRegister(0));
    195 
    196 The example shown in this tutorial is very simple, because the goal was to
    197 demonstrate the basics of the VIXL framework. There are more complex code
    198 examples in the VIXL `examples/aarch64` directory showing more features of both the
    199 macro assembler and the ARMv8 architecture.
    200 
    201 
    202 Extras
    203 ------
    204 
    205 In addition to this document and the [examples](/examples/aarch64), you can find
    206 documentation and guides on various topics that may be helpful
    207 [here](/doc/aarch64/topics/index.md).
    208