Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Appendix F · AArch64 for RISC-V Readers

An appendix in this book is a reference, not a chapter: no argument, no narrative, and everything in it either cites a primary source or comes from a stamped result under bench/results/.

This is a translation rather than a reference. It is written for someone who has read ch14 and is about to read ch26, and it is organised as you know this already, here it is again. For anything not in the book’s path, the architecture reference manual Arm Ltd (n.d.) is the document; this page will not substitute for it and does not try.

Why there are two

Part III and Part IV are RISC-V because the kernel small enough to read in an afternoon is a RISC-V kernel. Part V is AArch64 because that is where the performance counters work: sampling needs a PMU that can raise an interrupt on counter overflow, and no affordable RISC-V core can both count and sample. Appendix H has the evidence, and ch28 is where using two architectures pays off: a reader shown one weak memory model concludes that model is memory ordering.

So the crossing is deliberate, and this page is where you pay for it, once.

Registers

Thirty-one general-purpose registers plus a zero register and a stack pointer, which is nearly the same count as RISC-V and arrived at differently.

RISC-VAArch64
General purposex0–x31, x0 hardwired zerox0–x30, plus xzr/wzr
32-bit formnone — use w-suffixed instructionsw0–w30, the low half of x0–x30
Zerox0, a registerxzr, an operand encoding
Stack pointersp = x2, an ordinary registersp, separate and not x31
Return addressra = x1, by conventionx30/lr, written by the branch instruction
Argumentsa0–a7x0–x7
Return valuea0, a1x0, x1
Callee-saveds0–s11x19–x28
Frame pointers0/fp, by conventionx29/fp, by convention

Two differences that change how listings read.

The w registers are not a convention, they are the instruction. add w0, w1, w2 is a 32-bit add that zero-extends into the full register; add x0, x1, x2 is a 64-bit add. RISC-V spells this with separate mnemonics (addw against add). You will see w registers constantly in ch26’s listings wherever an int is involved, and the zero-extension is free rather than an extra instruction.

x31 is two registers depending on the instruction. In most positions the encoding means xzr; in a few it means sp. This is why the zero register is described here as an operand encoding rather than a register — it does not exist as a thing you can point at.

Loads and stores

RISC-VAArch64
Load a wordlw a0, 8(a1)ldr w0, [x1, #8]
Load a doublewordld a0, 8(a1)ldr x0, [x1, #8]
Storesd a0, 8(a1)str x0, [x1, #8]
Indexedcompute the address firstldr w0, [x1, x2, lsl #2]
Pair—stp x29, x30, [sp, #-16]!
Post-increment—ldr q1, [x2], #16

Three things to know before reading any AArch64 listing.

The addressing modes do arithmetic. [x1, x2, lsl #2] scales an index by four and adds it, in the load. RISC-V would need a shift and an add first. This is why an AArch64 inner loop over an array is often two instructions shorter than the RISC-V one for the same C, and it is visible in every listing in ch26.

stp and ldp move two registers at once. Almost every non-leaf function prologue you will see is stp x29, x30, [sp, #-16]! — save the frame pointer and the return address, and decrement the stack pointer, in one instruction. The ! is the write-back. ch14’s RISC-V prologue makes the room and stores the return address as two separate instructions, and would need a third if it also kept a frame pointer.

The suffix after the bracket is where the increment went. [x2], #16 adds sixteen to x2 after the access; [x2, #16]! adds it before. Neither exists in RISC-V, and both appear in vectorised loops in ch31.

Branches and conditions

This is the largest difference and the one that changes how you read a loop.

RISC-V compares and branches in one instruction: blt a0, a1, label. AArch64 sets flags and then branches on them:

cmp  x0, x1
b.lt label

The flags are a side effect that persists. They buy the conditional select, which has no RISC-V equivalent; compare-and-branch-on-zero sidesteps them entirely.

Conditional select. csel x0, x1, x2, lt writes one of two registers depending on the flags, with no branch at all. ch27 is about what that is worth: a branch the predictor cannot learn costs a pipeline flush every time, and a csel costs one instruction always.

Compare-and-branch-on-zero. cbz/cbnz are the exception that does not use the flags, and they are extremely common because testing against zero is extremely common.

RISC-VAArch64
Compare and branchblt a0, a1, Lcmp x0, x1 then b.lt L
Branch if zerobeqz a0, Lcbz x0, L
Select without branching—csel x0, x1, x2, cond
Calljal ra, fbl f
Tail callj fb f
Returnret (jalr x0, 0(ra))ret (uses x30)

Atomics and ordering

ch20 prints both of these from real disassembly and ch28 is the chapter about what the difference means.

RISC-VAArch64
Atomic read-modify-writeamoswap.w.aq, amoadd.d, …ldadd, swp, … (LSE), or ldxr/stxr
Acquirefence r,rw after the loadldar — the load itself
Releasefence rw,w before the storestlr — the store itself
Full barrierfence rw,rwdmb ish

The difference is where the ordering goes. RISC-V puts an instruction between the two operations being ordered; AArch64 folds the ordering into one of them. A reader who learned that a barrier is something you put between two things will not recognise stlr as a barrier at all, which is exactly why ch28 puts them side by side rather than teaching one.

Both spell the same requirement. Memory ordering is that requirement, not either way of writing it.

Vectors

NEON, in one paragraph, because ch31 is the chapter.

Thirty-two registers, v0–v31, 128 bits each, addressed by an arrangement specifier that says how to divide them: v0.4s is four 32-bit lanes, v0.2d is two 64-bit ones, v0.16b is sixteen bytes. The same registers are named q0–q31 when the whole 128 bits are meant, and s0/d0 when a single scalar float or double is meant.

That last point is a trap ch31 fell into and records: an instruction naming a v register is not evidence of vectorisation. This compiler builds a floating-point zero with movi v0.2s, #0, in a function whose loop it has refused to widen.

What this cannot tell you

How the two architectures differ where this book does not go. Exceptions, privilege levels, the MMU, system registers — all different, none of it here, because Part V does not read the kernel and Part IV does not run on AArch64.

Which is better. They make different choices and the book uses both for what each is good for. Nothing on this page is an argument.

What any of it costs. This is a translation table. Every number about cost in this book is measured, and none of it is here.

Where to go next

The ARM architecture reference manual for AArch64 Arm Ltd (n.d.) is the document, and its A64 instruction index is the part to keep open. The procedure call standard for AArch64 Arm Ltd (n.d.) is the equivalent of the RISC-V psABI RISC-V International (n.d.) and is what decides the register roles above.

References
  1. Arm Ltd. (n.d.). Arm Architecture Reference Manual for A-profile Architecture. https://developer.arm.com/documentation/ddi0487/latest/
  2. Arm Ltd. (n.d.). Procedure Call Standard for the Arm 64-bit Architecture (AAPCS64). https://github.com/ARM-software/abi-aa/blob/main/aapcs64/aapcs64.rst
  3. RISC-V International. (n.d.). RISC-V ELF psABI Specification. https://github.com/riscv-non-isa/riscv-elf-psabi-doc