Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Assembly (x86-64, AArch64, RISC-V)  Module 3 – How a CPU runs your program

Registers on x86-64, AArch64 and RISC-V

The register files of the three ISAs: how many registers each has, the stack, zero and link registers, and why writing eax clears rax's top half.

  • Beginner
  • 10 minutes
  • Examples run with Python 3.14.8
  • By MySmartCoPilot
Free lesson, Pro answers: 10 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • List the general-purpose registers of each ISA and their narrower names
  • Identify the special registers: stack pointer, zero register, link register and flags
  • Predict what a 32-bit, 16-bit or 8-bit write leaves in the full 64-bit register
  • Explain why writing eax clears the top half of rax while writing al does not
  • Name the registers each platform reserves and why you must not clobber them

Before you start

What you’ll learn · Before you start

What you will learn

  • List the general-purpose registers of each ISA and their narrower names
  • Identify the special registers: stack pointer, zero register, link register and flags
  • Predict what a 32-bit, 16-bit or 8-bit write leaves in the full 64-bit register
  • Explain why writing eax clears the top half of rax while writing al does not
  • Name the registers each platform reserves and why you must not clobber them

Before you start

On this page

A register file: The small set of named slots inside a processor that instructions read and write directly. It is the fastest storage a program has and holds the machine's visible integer state between memory accesses. is the small set of named slots inside a processor that instructions read and write directly; it is the fastest storage a program has and the whole visible state of the machine between memory accesses. x86-64 gives you 16 general-purpose register: A register a program may freely use for integer values and addresses, as opposed to a special register with a fixed job. x86-64 has 16 (rax..r15), AArch64 31 (x0..x30) and RISC-V 32 (x0..x31). (rax to r15), AArch64 gives 31 (x0 to x30), and RISC-V gives 32 (x0 to x31). Each also keeps a few special registers: a stack pointer: The register that addresses the top of the stack: rsp on x86-64, sp on AArch64 and sp (x2) on RISC-V. Push, pop and function prologues move it; losing it loses the stack., a return-address or link register: The register that holds the return address a call left behind: x30 on AArch64 and ra (x1) on RISC-V. A leaf function can return through it without using the stack; x86-64 instead keeps the return address on the stack., and on two of them a flags register.

This lesson lays the three files side by side, names the special registers, and then settles the rule that trips up beginners most: on x86-64, writing the 32-bit name eax clears the top half of rax, while writing the 8-bit name al leaves the rest alone. A model shows the bits change, and a real AArch64 run proves the same clearing there.

How many registers, and their narrow names

Every register comes in more than one width. On x86-64, rax is 64 bits, eax is its low 32, ax its low 16 and al its low 8; the newer registers follow the pattern as r8/r8d/r8w/r8b. On AArch64, x0 is 64 bits and w0 is its low 32, and there is no 16- or 8-bit name. On RISC-V every integer register is XLEN bits wide (64 on RV64), and the instructions whose names end in w work on the low 32 bits. Walk through the three files one kind of register at a time:

Interactive · message by message The three register files, side by side Walk through each kind of register and watch how x86-64, AArch64 and RISC-V provide it.
The three register files, side by side, step 5 of 5. No messages yet.x86-64RFLAGSAArch64NZCVRISC-Vno flags

The drawing shows the whole scene; its 5 steps are listed below.

Every step as text
  1. General-purpose registers. x86-64 has 16 general registers, rax to r15. AArch64 has 31, x0 to x30. RISC-V has 32, x0 to x31, with ABI names like a0 and sp. Wider files mean fewer trips to memory.
  2. The stack pointer. Each file keeps one register for the top of the stack: rsp on x86-64, sp on AArch64 (register number 31 in some encodings), and sp (x2) on RISC-V. Functions grow and shrink the stack through it.
  3. The return address. x86-64 pushes the return address on the stack with call. AArch64 and RISC-V put it in a register first: x30 (the link register) and ra (x1). A leaf function then needs no stack at all.
  4. The zero register. RISC-V hard-wires x0 to zero: reads give 0 and writes are dropped. AArch64 has xzr/wzr in the same role for many instructions. x86-64 has no zero register; code uses xor eax, eax to make a zero.
  5. The flags. x86-64 keeps condition bits in RFLAGS and AArch64 in NZCV, both set by arithmetic. RISC-V has no flags register at all; it compares straight into a register with slt. The next lesson but one returns to this.

The figure counts the general-purpose registers, then the stack pointer, the return-address register, the zero register and the flags, for each ISA. x86-64’s 16 registers are a legacy of its history; AArch64 and RISC-V were designed later with room for about twice as many, which lets a function keep more values in registers before it has to spill them to memory.

Key idea

The narrow names are windows onto one register, not separate registers: al, ax and eax all name parts of the same rax, and w0 is the low half of x0.

Checkpoint

Check: How many registers and their narrow names

  1. Question 1 of 2 How many general-purpose registers does each ISA give you?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: x86-64: 16, AArch64: 31, RISC-V: 32

    x86-64 has 16 general registers (rax..r15), a legacy of its history; AArch64 has 31 (x0..x30) and RISC-V 32 (x0..x31). More registers let a function keep more values in registers before spilling to memory.

  2. Question 2 of 2 On x86-64, what are eax, ax and al?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Windows onto the low 32, 16 and 8 bits of the same register rax

    eax, ax and al are the low 32, 16 and 8 bits of rax; w0 is the low 32 bits of x0. Writing through a narrow name changes the one underlying register, which is why the partial-write rules matter.

Partial writes change the whole register

Because the narrow names are windows onto one 64-bit register, writing through a window raises a question: what happens to the bits outside it? The answer differs by width and by ISA, and it is the source of real bugs. A model holds one register and applies the writes each ISA allows:

How narrow writes change the full 64-bit register Python · regfile_model.py
"""Partial-register writes, modelled for MySmartCoPilot Learn.

x86-64 and AArch64 treat a narrow write of a register differently, and the rule surprises people. This model holds
one 64-bit register and applies the writes the two ISAs allow, printing the full 64-bit value after each. It states
the documented rule (Intel SDM for x86-64, Arm's A64 guide for AArch64); the AArch64 rule is also proved by a real
run in zeroext.s on this page.
"""

MASK64 = (1 << 64) - 1


def show(name, value):
    print(f"{name:<22} -> 0x{value & MASK64:016x}")


def x86_writes():
    # x86-64 rule (Intel SDM, vol. 1): writing a 32-bit name (eax) zero-extends into the full 64 bits; writing an
    # 8- or 16-bit name (al, ax) leaves the other bits of the 64-bit register unchanged.
    rax = 0xDEADBEEFCAFEF00D
    show("start rax", rax)
    rax = (rax & ~0xFF) | 0x11            # mov al, 0x11   : low 8 bits only
    show("after mov al, 0x11", rax)
    rax = (rax & ~0xFFFF) | 0x2222        # mov ax, 0x2222 : low 16 bits only
    show("after mov ax, 0x2222", rax)
    rax = 0x33333333                      # mov eax, 0x33333333 : zero-extends to 64 bits
    show("after mov eax, ...", rax)


def arm_writes():
    # AArch64 rule (Arm A64 guide): writing the 32-bit view w0 always zeroes the top 32 bits of x0. There is no 8-
    # or 16-bit register view that preserves the other bits; you use byte and halfword loads/stores for that.
    x0 = 0xDEADBEEFCAFEF00D
    show("start x0", x0)
    x0 = 0x00000000_000000AB & MASK64     # mov w0, 0xAB  : whole x0 becomes 0x...000000AB
    show("after mov w0, 0xAB", x0)


print("x86-64: narrow writes")
x86_writes()
print()
print("AArch64: a 32-bit write clears the top half")
arm_writes()

Output

x86-64: narrow writes
start rax              -> 0xdeadbeefcafef00d
after mov al, 0x11     -> 0xdeadbeefcafef011
after mov ax, 0x2222   -> 0xdeadbeefcafe2222
after mov eax, ...     -> 0x0000000033333333

AArch64: a 32-bit write clears the top half
start x0               -> 0xdeadbeefcafef00d
after mov w0, 0xAB     -> 0x00000000000000ab

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 regfile_model.py

On x86-64 the rule is split. Writing an 8-bit name (al) or a 16-bit name (ax) leaves the other bits of rax unchanged, so 0xDEADBEEFCAFEF00D becomes 0xDEADBEEFCAFEF011 after mov al, 0x11. But writing the 32-bit name eax zeroes the top 32 bits: the same register becomes 0x0000000033333333, not 0xDEADBEEF33333333. Intel’s manual defines exactly this: 32-bit operations clear bits 63:32, while 8- and 16-bit operations preserve them.

AArch64 has only the eax-style rule, and a real run proves it. x0 is set so only bit 32 is on; writing w0 to zero should clear the whole register, and the exit status confirms it did:

Writing w0 clears the top half of x0 (a native run) Python · zeroext_session.py
import pathlib
import sys

sys.path.insert(0, str(pathlib.Path(__file__).parent / "sessions"))
import session

# A real AArch64 run that proves a 32-bit write clears the top half of the 64-bit register. x0 is set to
# 0x0000000100000000 (only bit 32 set); then mov w0, #0 writes the 32-bit view. If the top half were preserved, x0
# would still be non-zero and the program would exit 1. It exits 0, so writing w0 cleared all 64 bits.
session.run("""
cat zeroext.s
clang -o zeroext zeroext.s
./zeroext; echo "exit=$?"
""")

Output

$ cat zeroext.s
.text
.globl _main
.align 2
_main:
    movz    x0, #1, lsl #32        // x0 = 0x0000000100000000 (bit 32 set, low 32 all zero)
    mov     w0, #0                 // write the 32-bit view w0: AArch64 clears the top 32 bits of x0
    cmp     x0, #0                 // is x0 now completely zero?
    cset    w0, ne                 // w0 = 1 if x0 is non-zero, 0 if the top half was cleared
    ret                            // exit status = w0
$ clang -o zeroext zeroext.s
$ ./zeroext; echo "exit=$?"
exit=0

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 zeroext_session.py

Interactive · step by step What a narrow write does to the whole register Follow the writes from the model, starting with rax full of 0xDEADBEEFCAFEF00D, and watch which bits each one touches.
What a narrow write does to the whole register, step 5 of 5. Showing rax: 64 bits (w0 write -> top half cleared), eax: low 32, ax: low 16, al: low 8. In focus: eax: low 32, rax: 64 bits.rax: 64 bitsw0 write -> top half clearedeax: low 32ax: low 16al: low 8

The drawing shows the whole scene; its 5 steps are listed below.

Every step as text
  1. The register starts full. rax holds 0xDEADBEEFCAFEF00D. The names al, ax and eax are not separate registers; they are windows onto the low 8, 16 and 32 bits of the same rax.
  2. mov al, 0x11. Writing the 8-bit name al changes only the low byte. The rest of rax is left alone, so it becomes 0xDEADBEEFCAFEF011. Intel's manual defines this preserve-the-rest behaviour for 8- and 16-bit writes.
  3. mov ax, 0x2222. Writing the 16-bit name ax changes only the low two bytes, leaving the top 48 bits: rax becomes 0xDEADBEEFCAFE2222. Still the same register, still the rest untouched.
  4. mov eax, 0x33333333. Writing the 32-bit name eax is the exception: x86-64 zeroes the top 32 bits. rax becomes 0x0000000033333333, not 0xDEADBEEF33333333. This one rule catches people out.
  5. AArch64 clears the top 32 bits. AArch64 offers just x0 (64 bits) and w0 (low 32). Writing w0 always clears the top 32 bits, like eax; there is no al/ax-style view that preserves them. zeroext.s on this page proves it with a real run.

The program exits with 0, which the function only reaches when x0 became completely zero after mov w0, #0. So a 32-bit write on AArch64 always clears the top half, the same choice x86-64 makes for eax. There is no AArch64 name that preserves the upper bits the way al and ax do; to change only part of a register you use byte and halfword loads and stores, which the next lesson but one covers.

The zeroing eax that eats your high bits

A common bug is to build a 64-bit value in rax, then do a 32-bit operation such as add eax, ecx to update the low half, expecting the top half to survive. It does not: the 32-bit result is zero-extended and the top 32 bits are gone. Use the 64-bit names (rax, rcx) when the value is 64 bits wide.

Checkpoint

Check: Partial-register writes

  1. Question 1 of 2 rax holds 0xDEADBEEFCAFEF00D. After mov eax, 0x33333333, what does rax hold?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: 0x0000000033333333

    Intel's manual says a 32-bit operation zero-extends into the full 64-bit register. The model shows 0xDEADBEEFCAFEF00D becoming 0x0000000033333333 after mov eax, 0x33333333.

  2. Question 2 of 2 rax holds 0xDEADBEEFCAFEF00D. After mov al, 0x11, what does rax hold?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: 0xDEADBEEFCAFEF011

    An 8-bit (al) or 16-bit (ax) write preserves the other bits of the register. The model shows 0xDEADBEEFCAFEF00D becoming 0xDEADBEEFCAFEF011 after mov al, 0x11.

A few registers have fixed jobs the hardware or the calling convention assigns. The stack pointer addresses the top of the stack: rsp on x86-64, sp on AArch64 and sp (which is x2) on RISC-V. The link register holds the return address a call left behind: AArch64’s x30 and RISC-V’s ra (x1); x86-64 has none and keeps the return address on the stack instead. The zero register is RISC-V’s x0, hard-wired so reads give 0 and writes are silently dropped, which is why addi x0, x0, 5 in the previous lesson’s encoding disassembled to li zero, 5 and left x0 at 0; AArch64 has xzr/wzr in the same role for many instructions. The figure above stepped through each of these.

RISC-V also fixes gp (the global pointer) and tp (the thread pointer) by convention. Because RISC-V has no flags register, it compares values straight into a general register with slt instead of setting condition bits, a design difference the flags lesson returns to.

Fixed job x86-64 AArch64 RISC-V
Stack pointer rsp sp sp (x2)
Return address on the stack x30 (link register) ra (x1)
Reads as zero none xzr / wzr x0, hard-wired
Condition flags rflags nzcv none: compare into a register

Read the table by row, not by column: every ISA needs a stack pointer, but only two keep the return address in a register, and only RISC-V answers “where are the flags?” with “there are none”.

Checkpoint

Check: Stack, zero and link registers

  1. Question 1 of 2 What happens when a RISC-V instruction writes a result to x0?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: The write is silently dropped; x0 always reads as 0

    RISC-V hard-wires x0 to zero: reads give 0 and writes are dropped. The previous lesson's encoding of addi x0, x0, 5 disassembled to li zero, 5 and left x0 at 0. AArch64's xzr/wzr play the same role.

  2. Question 2 of 2 Where does the return address of a call live on AArch64 and RISC-V, and where on x86-64?
    Show the answer: Question 2 of 2

    Answer: In the link register (x30 on AArch64, ra on RISC-V); x86-64 pushes it on the stack

    AArch64's bl writes the return address to x30 (the link register) and RISC-V's jal to ra (x1), so a leaf function can return without touching the stack. x86-64's call pushes the return address onto the stack instead.

Registers the platform reserves

Not every general register is yours to use freely. Each platform reserves some so that shared code and the operating system keep working:

  • AArch64 x18. AAPCS64 calls r18 the platform register. Apple’s ARM64 document says not to use x18 on Apple platforms, and on Windows on Arm it points at the thread environment block in user mode. On Linux it is not reserved, but portable code leaves it alone.
  • RISC-V gp and tp. The psABI gives the global pointer and thread pointer fixed roles; clobbering them breaks global-variable access and thread-local storage.
  • The stack pointer and link register. Overwriting sp loses the stack; overwriting the link register before you save it means a function cannot return.

Reading across platforms:

Reservation x86-64 (System V) AArch64 (AAPCS64) RISC-V (psABI)
Platform register — x18 (reserved by the ABI) —
Global pointer — — gp
Thread pointer — — tp
Must never clobber rsp; the pushed return address sp; x30 before you save it sp; ra (x1) before you save it

The track’s exercises guard against this by filling callee-saved registers with known values before a call and checking them afterwards, so a function that quietly clobbers a reserved or callee-saved register is caught.

Key idea

Treat x18 on AArch64 and gp/tp on RISC-V as off-limits unless you own the whole program, and never destroy the stack pointer or an unsaved link register.

Checkpoint

Check: Platform-reserved registers

  1. Question 1 of 2 Why should portable AArch64 code avoid using x18?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: It is the platform register: Apple forbids it and Windows on Arm uses it for the thread environment block

    AAPCS64 names r18 the platform register. Apple's ARM64 document says not to use x18, and on Windows on Arm it points at the thread environment block. Portable code treats it as off-limits.

  2. Question 2 of 2 Which registers must you not clobber, and why?

    Choose every answer that is right.

    Show the answer: Question 2 of 2

    Answer:

    • An unsaved link register: the function could not return
    • The stack pointer: losing it loses the stack
    • RISC-V gp and tp: global and thread-local access break

    Clobbering the stack pointer loses the stack, clobbering an unsaved link register means a function cannot return, and clobbering gp or tp on RISC-V breaks global and thread-local access. Writing x0 is harmless because the write is discarded.

Practise: read the register file

Use the model and the figures to answer these by hand. Start a register at 0x1122334455667788. What does it hold after mov al, 0xFF? After mov eax, 0xFF on x86-64? After mov w0, #0xFF on AArch64? The practice questions walk through each, then ask which ISA has no flags register and which register RISC-V hard-wires to zero.

Practice that adapts to you

Practise this lesson

10 questions about 4 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 10 A register holds 0x1122334455667788. What does it hold after mov al, 0xFF?

    Read the code, then choose one answer.

    rax = 0x1122334455667788
    Show the answer: Question 1 of 10

    Answer: 0x11223344556677FF

    An 8-bit write replaces only the low byte and preserves the rest, so 0x1122334455667788 becomes 0x11223344556677FF.

  2. Question 2 of 10 The same register holds 0x1122334455667788. What does it hold after mov eax, 0xFF on x86-64?

    Read the code, then choose one answer.

    rax = 0x1122334455667788
    Show the answer: Question 2 of 10

    Answer: 0x00000000000000FF

    Writing eax zero-extends into rax: the low 32 bits hold 0x000000FF and bits 63:32 are cleared, so the register holds 0x00000000000000FF, as the model's mov eax step shows.

  3. Question 3 of 10 On AArch64, x0 holds 0x1122334455667788. What does it hold after mov w0, #0xFF?

    Read the code, then choose one answer.

    x0 = 0x1122334455667788
    Show the answer: Question 3 of 10

    Answer: 0x00000000000000FF

    AArch64 offers x0 and w0 only, and a w0 write always clears the top 32 bits. The native run on this page proves it: setting bit 32 and then writing w0 left x0 completely zero, so the program exited 0.

  4. Question 4 of 10 Which of the three ISAs has no flags register?

    Choose one answer.

    Show the answer: Question 4 of 10

    Answer: RISC-V

    x86-64 has RFLAGS and AArch64 has NZCV, but RISC-V has no flags register and compares into a general register with slt. The flags lesson later in the track returns to this difference.

  5. Question 5 of 10 Beyond tidiness, why does zero-extending a 32-bit result (the eax rule) help a modern out-of-order core?

    Choose one answer.

    Show the answer: Question 5 of 10

    Answer: The result does not depend on the old top half, so the processor need not wait for the previous rax value

    If writing eax kept the top half, the new rax would depend on the old rax, forcing the core to wait for the previous value. Zero-extending makes the result depend only on the 32-bit inputs, which removes that false dependency and helps out-of-order execution, as the fetch-decode-execute lesson's "running in order" section explains.

  6. Question 6 of 10 How many general-purpose registers does AArch64 have (x0 through x30)?

    Type a number.

    A hint

    Count x0 up to and including x30.

    Show the answer: Question 6 of 10

    Answer: 31

    AArch64 has 31 general-purpose registers, x0 to x30; x30 is the link register. (Register number 31 encodes the stack pointer or the zero register depending on the instruction, so it is not counted as a general register.)

  7. Question 7 of 10 A function keeps eleven live values. Why is it likely to run fewer memory accesses when compiled for AArch64 or RISC-V than for x86-64?

    Choose one answer.

    Show the answer: Question 7 of 10

    Answer: Those ISAs have about twice as many general-purpose registers, so more live values fit in registers before any must be spilled to the stack

    x86-64 has 16 general-purpose registers, a legacy of its history; AArch64 and RISC-V were designed later with about twice as many. When live values outnumber the registers the compiler spills: it stores a value to the stack and loads it back later. More registers push that point further away, so the same function needs fewer memory accesses.

  8. Question 8 of 10 A call instruction has just run on x86-64. Where is the return address, and how does that differ from AArch64?

    Choose one answer.

    Show the answer: Question 8 of 10

    Answer: On the stack, pushed by the call; AArch64 instead leaves it in the link register x30

    The x86-64 call pushes the return address onto the stack and ret pops it. The AArch64 bl writes it into the link register x30, and the RISC-V jal writes it into ra (x1). That is why a leaf function on AArch64 or RISC-V can return without a single memory access, while on x86-64 the stack is always involved.

  9. Question 9 of 10 Which AArch64 register does AAPCS64 set aside as the platform register, so portable code must leave it alone?

    Choose one answer.

    Show the answer: Question 9 of 10

    Answer: x18

    AAPCS64 designates r18 as the platform register, and a platform may use it for anything it likes: Apple platforms forbid using x18 at all, and Windows on Arm makes it point at the thread environment block in user mode. Linux does not reserve it, but code meant to be portable must not touch it.

  10. Question 10 of 10 A hand-written RISC-V routine uses tp as a scratch register and restores it before returning. The program still crashes inside the routine. What is the most likely cause?

    Choose one answer.

    Show the answer: Question 10 of 10

    Answer: Anything the routine calls, or any signal handler that runs, reads thread-local storage through tp and now sees a wrong address

    The RISC-V psABI fixes gp as the global pointer and tp as the thread pointer. Thread-local-storage accesses are computed from tp, so while tp holds something else every such access reads the wrong memory, and a signal can arrive at any instruction. Restoring the register at the end does not close that window.

Interview questions

On x86-64, why does writing eax clear the top 32 bits of rax, while writing al leaves the rest of rax unchanged?

They are two different rules in the instruction set, and Intel’s manual states both. A 32-bit operation on a general register (writing eax, ebx, and so on) zero-extends its result into the full 64-bit register, clearing bits 63:32. An 8-bit or 16-bit operation (writing al or ax) writes only those low bits and leaves the rest of the register unchanged. The reason behind the 32-bit rule is that it removes a false dependency: because the result does not depend on the old top half, the processor need not wait for the previous value of rax before writing eax, which helps out-of-order execution. The practical effect is a classic bug: code that builds a 64-bit value and then uses a 32-bit operation such as add eax, ecx to tweak the low half loses the top half to zero. The model on this page shows 0xDEADBEEFCAFEF00D becoming 0x0000000033333333 after mov eax, 0x33333333, and 0xDEADBEEFCAFEF011 after mov al, 0x11. AArch64 made the simpler choice: it has only x0 and w0, and a w0 write always clears the top half, which the native run on this page proves.

These two pair a register fact with the slip it causes; the bank lists openers and follow-ups:

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Mid to senior

    A 64-bit counter in hand-written x86-64 assembly keeps losing its high 32 bits after an update. Walk through how you would find and fix the bug, and say what the AArch64 port must watch for.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    A function written in assembly returns correct results alone but corrupts its caller after a call. How do reserved and callee-saved registers explain this, and how would you prevent it?

    Model answer, Pro. First step and follow-ups

Flashcards · 6 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. How many general-purpose registers do x86-64, AArch64 and RISC-V have?

    16 on x86-64 (rax to r15), 31 on AArch64 (x0 to x30) and 32 on RISC-V (x0 to x31), where x0 always reads as zero.

  2. Are al, ax, eax and rax four registers?

    No: they are windows onto one 64-bit register, its low 8, 16, 32 and all 64 bits. In the same way w0 is the low half of x0 on AArch64.

  3. Why does writing eax clear the top half of rax on x86-64 while writing al leaves it alone?

    They are two separate rules in Intel's manual. A 32-bit write zero-extends into the whole register, which also spares the core from waiting on the old top half; 8-bit and 16-bit writes change only their own bits. Code that tweaks a 64-bit value with a 32-bit add loses the top half.

  4. What happens to x0 when an AArch64 instruction writes w0?

    The top 32 bits of x0 are cleared, every time. AArch64 has no 8-bit or 16-bit register names, so the 32-bit view is the only narrow write.

  5. Which registers have fixed jobs: the stack pointer, the link register and the zero register?

    The stack pointer is rsp on x86-64 and sp on AArch64 and RISC-V. The return address goes in x30 on AArch64 and ra on RISC-V, while x86-64 keeps it on the stack. RISC-V x0 and AArch64 xzr or wzr always read as zero, and RISC-V has no flags register.

  6. Which registers should ordinary code never clobber?

    x18 on AArch64, the platform register, and gp and tp on RISC-V, unless you own the whole program. Never destroy the stack pointer, and never lose a link register you have not saved.

Key takeaways

  • x86-64 has 16 general registers (rax..r15), AArch64 has 31 (x0..x30) and RISC-V has 32 (x0..x31); the narrow names (eax/ax/al, w0) are windows onto one register.
  • On x86-64, writing a 32-bit name clears the top 32 bits, but writing an 8- or 16-bit name leaves the other bits alone; the model shows both.
  • AArch64 has only the 32-bit view w0, and writing it always clears the top half of x0, as the native run proves by exiting 0.
  • Special registers: the stack pointer (rsp, sp, sp), the link register (x30, ra; x86-64 uses the stack), RISC-V’s zero register x0 and AArch64’s xzr/wzr; RISC-V has no flags register.
  • Each platform reserves registers: x18 on AArch64 (the platform register) and gp/tp on RISC-V; never clobber them, the stack pointer, or an unsaved link register.

Terms in this lesson

general-purpose register
A register a program may freely use for integer values and addresses, as opposed to a special register with a fixed job. x86-64 has 16 (rax..r15), AArch64 31 (x0..x30) and RISC-V 32 (x0..x31).
link register
The register that holds the return address a call left behind: x30 on AArch64 and ra (x1) on RISC-V. A leaf function can return through it without using the stack; x86-64 instead keeps the return address on the stack.
register file
The small set of named slots inside a processor that instructions read and write directly. It is the fastest storage a program has and holds the machine's visible integer state between memory accesses.
stack pointer
The register that addresses the top of the stack: rsp on x86-64, sp on AArch64 and sp (x2) on RISC-V. Push, pop and function prologues move it; losing it loses the stack.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.