Main Definitions
ARM is an acronym for Advanced RISC Machines and if it is not followed by a noun, it refers to a family of processors (CPUs) that are designed based on the architecture developed by Arm Ltd., a British company based in Cambridge, England. RISC is another acronym which stands for Reduced Instruction Set Computer and comes as an alternative to CISC which stands for Complex Instruction Set Computer, used by Intel processors. You can find the most important differences of these two architectures in the table bellow, including the reduced instruction set (as the term implies), the heavy use of RAM, the fixed length instructions, as well as the larger number of general purpose registers of the RISC based machines.
Following up with arm versioning related terms is rather overwhelming, but the most important thing to remember is that besides Arm Ltd., third party vendors are also producing CPUs based on this architecture, including Apple, Nvidia, Qualcomm, Samsung etc. Each family of processor supports a specific instruction set which corresponds to an ARM architecture version. So we have ARMv1, ARMv2,…,ARMv8 etc. with ARMv9 being the most recent at the time that this post is written. Cortex-A from Arm Ltd., Qualcomm’s Snapdragon, Samsung’s Exynos, Apple’s Mx are some examples of CPU families designed to support the latest versions of this architecture. You can find an updated list here.
From a programmers point of view, a term that you might find useful is the instruction set architecture (ISA) which defines the set of instructions and the set of registers that the hardware must support. Being familiar with the ISA is the first step in learning the arm assembly language. In this series of posts we are going to focus on 64-bit ISA for AArch64 processors.
The Registers
The AArch64 ISA defines 31 general purpose registers which can be used to store 64 or 32-bit values and you may find them referenced either as Xn, Wn or Rn (capitalisation is optional), where n is the index of register (a number from 0 to 30).
- When used to store 64 bits they are referenced with the letter X
- When used for 32 bits they are referenced with the letter W
- or more generally (irrelevant to the size) you may find them referenced with the letter R
For example, to refer to the lower part of the register with 0 index use the symbol W0 and for all the 64 bits you may use X0 :
Another acronym you mind find useful is the application binary interface — ABI which defines how these registers are used. So, this conventional rather than constructional related usage, defines the following rules:
- Registers R0 to R7: are used to save arguments when calling a function (see below), while the R0 is used also to store the result which is returned by a function.
Note: When a function is called, the compiler uses, a stack frame (allocated within the program’s runtime stack) in order store all the temporary information that the function requires to operate. Depending on the calling convention the caller function will place this information to specific registers or in the stack or in both. For example the C calling convention (cdecl in short), places up to six arguments to the RDI, RSI, RDX, RCX, R8 and R9 registers and anything additional is placed into the stack.
- Register R8: (Indirect result location register), used in C++ for returning non-trivial objects (set by the caller).
- Registers R9 to R15: (known as scratch registers) can be used any time without any assumptions about their contents.
- Registers R16, R17: (intra-procedure-call temporary registers) the linker may use these in PLT code. Can be used as temporary registers between calls.
- Register R18: (platform register) reserved for the use of platform ABI. For example, for the windows ABI, in kernel mode, points to KPCR for the current processor; in user mode, points to TEB.
- Registers R19-R28: can also be used as scratch registers, but their contents must be saved before usage and restored afterwards.
- Register R29: is used as a Frame Pointer, pointing to the current stack frame and it is usually useful when the program runs under a debugger. The GNU C compiler has an option to use R29 as a general purpose register via the -fomit-frame-pointer option.
- Register R30: is known as the link register (lr) and it can be used to store the return address during a function call, an alternative of saving the address to the call stack. Certain branch and link instructions store the current address to the link register before the program counter (the register that holds the address of the next instruction) loads the new address.
- The stack pointer (sp) stores the address corresponding to the end of the stack (or as commonly said pointing to the top of the stack). This address will change when registers are pushed into the stack or during the allocation or deletion of local variables.
- The program counter (pc) holds the next address which contains the code to be executed. It is increased automatically by four after each instruction while it can be modified by a small number of instructions (e.g. adr or ldr). This gives the ability of jumping to a new address and start executing code from there.
- The zero register (referenced as zr, xzr or wzr)is a special register that is hard-wired to the integer value
0
. Writing to that register is always discarded and reading its value will always result in a0
being read:
- The PSTATE register is a collection of fields which are mostly used by the OS. The user programs make use of the first four bits, which are marked as N,Z,C and V respectively (referred as condition flags) where each one of them is interpreted as follows:
N → Negative , the flag is set to 1 when the (signed) result of an operation is negative.
Z → Zero, the flag is set to 1 when the result of an operation is 0 and set to 0 when the result is not 0.
C →Carry, the flag is set to 1 if an add operation results in a carry or a subtraction operation results in a borrow.
O →Overflow, set to 1 if a signed overflow occurs during an addition or subtraction.
The AArch64 Registers of an Apple Silicon Macs (M1):
The AArch64 architecture also supports 32 floating-point/SIMD registers, numbered from 0 to 31, where each can be accessed as a full 128 bit value (using v0 to v31 or q0 to q31), 64-bit value (using d0 to d31), 32-bit value (using s0 to s31) as a 16-bit value (using h0 to h31), or as an 8-bit value (using b0 to b31). Referencing less than 128 bits only access the lower bits of the full 128-bit register while leaving the remaining bits untouched unless specified otherwise.
Summary:
- ARM is the acronym for Advanced RISC Machines
- RISC, used by arm processors stands for Reduced Instruction Set Computer and comes as an alternative to CISC which stands for Complex Instruction Set Computer, used by Intel processors.
- The instruction set architecture (ISA) defines the set of instructions and the set of registers that the hardware must support.
- The 64bit ISA for AArch64 (AArch64 ISA) defines 31 general purpose 64 bit registers.
- The application binary interface — ABI is a convention on how the registers are used.
- The Program Counter and Stack Pointer are no longer general purpose and therefore cannot be used by most instructions
- The AArch64 architecture also supports 32 floating-point/SIMD registers, numbered from 0 to 31 that can be accessed in different sizes: 8, 16, 32, 64 and 128 bits.