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 7 – Functions, the stack and calling conventions

The stack: push, pop and red zones

How push and pop move the stack pointer on x86-64, how AArch64 and RISC-V save registers without them, and which ABIs let a leaf keep data below sp.

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

What you will learn

  • Explain how the stack grows and how push and pop move rsp on x86-64
  • Save and restore registers in reverse order with push and pop, stp and ldp, and sd and ld
  • Trace the stack pointer through a function's saves and restores on x86-64, AArch64 and RISC-V
  • Identify the ABIs that give a function a red zone, and recognise one in compiler output
  • Explain why x86-64 kernel code is compiled without a red zone

Before you start

What you’ll learn · Before you start

What you will learn

  • Explain how the stack grows and how push and pop move rsp on x86-64
  • Save and restore registers in reverse order with push and pop, stp and ldp, and sd and ld
  • Trace the stack pointer through a function's saves and restores on x86-64, AArch64 and RISC-V
  • Identify the ABIs that give a function a red zone, and recognise one in compiler output
  • Explain why x86-64 kernel code is compiled without a red zone

Before you start

On this page

save_two needs two registers that its caller is still using, so it copies their values to memory first and puts them back before it returns. On Apple silicon the stack pointer sits 16 bytes lower while it works and is back where it started once it returns, and a version that saves one register in an 8-byte step, the way x86-64’s push does, ends with a bus error at its very next load.

That memory is the stack on every ISA, but each ISA moves its stack pointer its own way: x86-64 with push and pop, AArch64 with pair stores that update sp, RISC-V with an addi and plain stores. You will trace the stack pointer through all three, put saved registers back in the right order, and see which ABIs let a function keep data below the stack pointer without moving it at all, and why kernel code is built never to do that.

The stack grows down, and push lowers rsp before it stores

A stack: Memory a program uses last in, first out: the value stored most recently is the first taken back. The stack pointer holds the address of that value, and on x86-64, AArch64 and RISC-V the stack grows towards lower addresses. is memory used last in, first out: the value stored most recently is the first one taken back. The 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. More in “Registers on x86-64, AArch64 and RISC-V” holds the address of that most recent value, the top of the stack, and on x86-64, AArch64 and RISC-V the stack grows towards lower addresses. To push: Putting a value on the stack: lower the stack pointer, then store the value at the new address. x86-64's push instruction does both and moves rsp by 8 for a 64-bit register; AArch64 and RISC-V use a store and a stack-pointer update. a value therefore means to lower the stack pointer and write at the new address, and to pop: Taking the most recent value off the stack: load it from the address in the stack pointer, then raise the stack pointer past it. The bytes stay in memory, below the stack pointer, until something overwrites them. one means to read it and raise the stack pointer again. Intel’s manual defines the x86-64 instructions in exactly those steps, the RISC-V psABI says its stack grows downwards, and AAPCS64 calls Arm’s stack full-descending: the part in use runs from sp up to the stack’s base, and everything below sp is inactive.

save_two(long *seen) borrows two registers whose values its caller still needs, writes the stack pointer it has after saving them through seen, returns 40 + 2 and hands both registers back. Which registers a function must hand back is a rule of its own, covered in callee-saved registers and stack frames; here the three versions borrow rbx and r12, x19 and x20, and s1 and s2:

save_two on three ISAs

AArch64 · save_two_aarch64.s

// save_two(long *seen) for AArch64 on Apple platforms: borrows x19 and x20, then hands them back.
// *seen receives sp as it is after the save, so the C driver can print how far sp moved.
    .text
    .globl _save_two
    .p2align 2
_save_two:
    stp x19, x20, [sp, #-16]!     // sp = sp - 16, then x19 to [sp] and x20 to [sp, #8]
    mov x1, sp
    str x1, [x0]                  // *seen = sp after the save
    mov x19, #40                  // the old values are saved: reuse both
    mov x20, #2
    add x0, x19, x20              // return value: 42
    ldp x19, x20, [sp], #16       // x19 from [sp], x20 from [sp, #8], then sp = sp + 16
    ret

// get_sp(): returns sp as its caller has it; this function moves sp nowhere
    .globl _get_sp
    .p2align 2
_get_sp:
    mov x0, sp
    ret

x86-64 · save_two_x86_64.s

# save_two(long *seen) for x86-64 Linux (System V): borrows rbx and r12, then hands them back.
.intel_syntax noprefix
.text
.globl save_two
save_two:
    push rbx                 # rsp = rsp - 8, then rbx to [rsp]
    push r12                 # rsp = rsp - 8, then r12 to [rsp]
    mov [rdi], rsp           # *seen = rsp after the saves
    mov rbx, 40              # the old values are saved: reuse both
    mov r12, 2
    lea rax, [rbx + r12]     # return value: 42
    pop r12                  # r12 from [rsp], then rsp = rsp + 8: the last one saved comes back first
    pop rbx
    ret

RISC-V · save_two_riscv64.s

# save_two(long *seen) for RISC-V Linux (LP64D): borrows s1 and s2, then hands them back.
.text
.globl save_two
save_two:
    addi sp, sp, -16         # make room: two 8-byte slots
    sd s1, 0(sp)             # s1 to the slot at sp
    sd s2, 8(sp)             # s2 to the slot above it
    sd sp, 0(a0)             # *seen = sp after the saves
    li s1, 40                # the old values are saved: reuse both
    li s2, 2
    add a0, s1, s2           # return value: 42
    ld s1, 0(sp)             # each register comes back from its own slot
    ld s2, 8(sp)
    addi sp, sp, 16          # sp is back where it started
    ret

The C driver reads the stack pointer just before the call and just after it with get_sp, a two-instruction helper in the AArch64 file, and prints differences only, because the addresses themselves change from one run to the next:

save_two_main.c: how far sp moved C · save_two_main.c
/* Calls save_two once and prints how far sp moved: inside save_two after its save, and after it returned.
   Only differences are printed, because the addresses themselves change from one run to the next. */
#include <stdio.h>

long save_two(long *seen);
long get_sp(void);

int main(void) {
    long seen = 0;
    long before = get_sp();
    long result = save_two(&seen);
    long after = get_sp();
    printf("save_two returned %ld\n", result);
    printf("sp inside, after the save: %+ld bytes\n", seen - before);
    printf("sp after the return:       %+ld bytes\n", after - before);
    return 0;
}

The session builds and runs the AArch64 version on Apple silicon, then assembles the x86-64 and RISC-V versions into Linux object files and lists them with the bytes of each instruction:

save_two run on AArch64, and listed for x86-64 and RISC-V Python · save_two_session.py
"""Builds save_two for AArch64 and runs it, then assembles the x86-64 and RISC-V versions and lists their bytes."""

import pathlib
import sys

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

session.run("""
# AArch64: compile the C driver, assemble save_two_aarch64.s, link them into one program and run it
clang -O1 -o save_two save_two_main.c save_two_aarch64.s
./save_two
# x86-64: assemble the System V version into a Linux object file, then list it with the bytes of each instruction
clang --target=x86_64-linux-gnu -c -o save_two_x86_64.o save_two_x86_64.s
llvm-objdump -d -M intel save_two_x86_64.o
# RISC-V: assemble the LP64D version into a Linux object file and list it the same way
llvm-mc --triple=riscv64-linux-gnu -filetype=obj -o save_two_riscv64.o save_two_riscv64.s
llvm-objdump -d save_two_riscv64.o
""")

Output

# AArch64: compile the C driver, assemble save_two_aarch64.s, link them into one program and run it
$ clang -O1 -o save_two save_two_main.c save_two_aarch64.s
$ ./save_two
save_two returned 42
sp inside, after the save: -16 bytes
sp after the return:       +0 bytes
# x86-64: assemble the System V version into a Linux object file, then list it with the bytes of each instruction
$ clang --target=x86_64-linux-gnu -c -o save_two_x86_64.o save_two_x86_64.s
$ llvm-objdump -d -M intel save_two_x86_64.o

save_two_x86_64.o:	file format elf64-x86-64

Disassembly of section .text:

0000000000000000 <save_two>:
       0: 53                           	push	rbx
       1: 41 54                        	push	r12
       3: 48 89 27                     	mov	qword ptr [rdi], rsp
       6: 48 c7 c3 28 00 00 00         	mov	rbx, 0x28
       d: 49 c7 c4 02 00 00 00         	mov	r12, 0x2
      14: 4a 8d 04 23                  	lea	rax, [rbx + r12]
      18: 41 5c                        	pop	r12
      1a: 5b                           	pop	rbx
      1b: c3                           	ret
# RISC-V: assemble the LP64D version into a Linux object file and list it the same way
$ llvm-mc --triple=riscv64-linux-gnu -filetype=obj -o save_two_riscv64.o save_two_riscv64.s
$ llvm-objdump -d save_two_riscv64.o

save_two_riscv64.o:	file format elf64-littleriscv

Disassembly of section .text:

0000000000000000 <save_two>:
       0: ff010113     	addi	sp, sp, -0x10
       4: 00913023     	sd	s1, 0x0(sp)
       8: 01213423     	sd	s2, 0x8(sp)
       c: 00253023     	sd	sp, 0x0(a0)
      10: 02800493     	li	s1, 0x28
      14: 00200913     	li	s2, 0x2
      18: 01248533     	add	a0, s1, s2
      1c: 00013483     	ld	s1, 0x0(sp)
      20: 00813903     	ld	s2, 0x8(sp)
      24: 01010113     	addi	sp, sp, 0x10
      28: 00008067     	ret

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

x86-64 has one instruction for each half of the job. Intel’s manual defines push as lowering the stack pointer and then storing the operand on the top of the stack, and pop as loading the top of the stack into the operand and then raising the stack pointer; for a 64-bit register both move rsp by 8. They are short, too. In the listing push rbx is the single byte 53 and push r12 the two bytes 41 54, where 41 is the prefix that selects one of r8 to r15, and the pops at the end are 41 5c and 5b. save_two pushes rbx first, so r12 ends up lowest, at the top of the stack, and the first pop has to take r12 back. Step through it with rsp written relative to S, the address where the call left its return address: The address a call records so that the callee can come back: the address of the instruction right after the call. x86-64's call pushes it onto the stack; AArch64's bl and blr and RISC-V's jal and call write it to x30 or ra. More in “Calls, returns and the link register” (calls and returns follows that address from the call to the ret):

Interactive · step by step push and pop in save_two, x86-64 System V Step through save_two as Intel's manual defines push and pop. S is rsp at the first instruction; slots lower on the page are lower addresses.
push and pop in save_two, x86-64 System V, step 8 of 8. Showing rbx (the caller's value), r12 (the caller's value), rsp (S again), rax (42, the result), S: return address, S - 8 (below rsp now), S - 16 (below rsp now). In focus: S: return address.rbxthe caller's valuer12the caller's valuerspS againrax42, the resultS: return addresspushed by callS - 8below rsp nowS - 16below rsp now

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

Every step as text
  1. On entry. call has just pushed the return address, so rsp holds S, the address of that slot. rbx and r12 hold values the caller still needs after save_two returns.
  2. push rbx. rsp drops by 8 first, to S - 8, and then the 8 bytes of rbx are stored there. The whole instruction is one byte, 53.
  3. push r12. Again rsp drops by 8 before the store, to S - 16, and r12 goes into the new lowest slot. This one is two bytes, 41 54.
  4. mov rbx, 40 and mov r12, 2. Both old values are safe in the stack, so save_two may now write the two registers. Just before this, mov [rdi], rsp handed S - 16 to the caller through the pointer in rdi.
  5. lea rax, [rbx + r12]. rax receives 40 + 2 = 42, the value save_two returns.
  6. pop r12. The word at rsp, S - 16, is loaded into r12, and then rsp rises by 8 to S - 8. The last register pushed is the first one popped.
  7. pop rbx. S - 8 goes back into rbx and rsp returns to S. Both old values are still in memory, but they now lie below rsp, where nothing keeps them safe.
  8. ret. rsp points at the return address again, which is what ret pops. The caller gets rbx, r12 and rsp back exactly as it left them.

A pop leaves the old value where it was. After pop rbx both saved values are still in memory, but below rsp, in the unused part of the stack, where the next push or the next call may overwrite them at any moment. Popping gives a slot up; it does not clear it.

Writing to [rsp] to add a value

mov [rsp], rax does not put a new value on the stack. rsp already addresses the most recent value, which at a function’s first instruction is its return address, and the mov overwrites it. Lower rsp first, as push does in one instruction, or with sub rsp, 8 before the store.

x86-64 is the only one of the three ISAs with dedicated push and pop instructions. AArch64 builds the same two steps from a store that updates sp, RISC-V from two separate instructions, and both move the stack pointer in bigger steps.

Key idea

The stack grows towards lower addresses on all three ISAs, and the stack pointer addresses the most recent value. x86-64’s push lowers rsp by 8 and then stores, and pop loads and then raises rsp by 8, so registers come back in the reverse of the order they went in.

Checkpoint

Check: Push and pop on x86-64

  1. Question 1 of 2 save_two starts with rsp = S and runs push rbx, then push r12. How many bytes below S is rsp now?

    Type a number.

    A hint

    Each push lowers rsp before it stores. By how much, for a 64-bit register?

    Show the answer: Question 1 of 2

    Answer: 16 bytes

    Each push of a 64-bit register lowers rsp by 8 and then stores, so two pushes leave rsp at S - 16, with r12 at S - 16 and rbx at S - 8. The AArch64 run printed the same 16 bytes for its single stp.

  2. Question 2 of 2 save_two pushed rbx and then r12. Which epilogue hands the caller both values back?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: pop r12, then pop rbx

    A pop always takes the value at rsp, the most recent one, so registers come back in the reverse of the order they were pushed: the lesson's listing shows pop r12 (41 5c) before pop rbx (5b).

AArch64 and RISC-V have no push, and move sp in 16-byte steps

AArch64 has no push or pop instruction, and neither has RV64GC, the RISC-V instruction set this lesson’s code is built for. Saving a register there is an ordinary store through sp, plus arithmetic on sp itself, and the two ISAs package that differently.

AArch64 folds both jobs into one instruction. stp x19, x20, [sp, #-16]! is a store pair in its pre-index form: it works out sp - 16, stores x19 there and x20 8 bytes higher, and writes the new address back into sp. That update of the base register is called writeback: The update of the base register by a pre-indexed or post-indexed load or store. AArch64's stp x19, x20, [sp, #-16]! writes sp - 16 back into sp, and ldp x19, x20, [sp], #16 adds 16 to sp after the load., and the ! asks for it. ldp x19, x20, [sp], #16 is the post-index load pair: it loads both registers from sp and then adds 16 to it. The forms come from addressing modes and the pair instructions from loads and stores. The run above shows their effect: inside save_two, after its stp, sp was 16 bytes lower than before the call, and after the return it was back, at +0 bytes.

RISC-V has neither pairs nor writeback. Its save_two moves the stack pointer with addi sp, sp, -16 and stores each register in its own stack slot: One place on the stack that holds one value while a function runs, found at a fixed offset from the stack pointer, such as [rsp + 16] or 8(sp)., sd s1, 0(sp) and sd s2, 8(sp); the restore loads both from the same offsets and ends with addi sp, sp, 16. The three ways side by side:

ISA Save two registers Restore them Stack pointer
x86-64 push rbx, push r12 pop r12, pop rbx 8 per push, 16 in all
AArch64 stp x19, x20, [sp, #-16]! ldp x19, x20, [sp], #16 16 in one instruction
RISC-V addi sp, sp, -16, sd s1, 0(sp), sd s2, 8(sp) ld s1, 0(sp), ld s2, 8(sp), addi sp, sp, 16 16 with one addi

On x86-64 the order of the restores is fixed, because each pop takes whatever is on top. On AArch64 and RISC-V each register comes back from the offset it was stored at, so the two loads could swap places; what has to come last is the instruction that raises sp, since anything may overwrite what lies below sp once it has run. Every version ends with the stack pointer at its entry value, which the +0 bytes line confirms for AArch64. Saves like these at the start of a function and restores at its end belong to its prologue: The instructions at the start of a function that set up its stack frame before the body runs: they save the callee-saved registers the body writes, save the link register when the function makes calls (on x86-64, call has already put the return address on the stack), and reserve its stack space. More in “Callee-saved registers and stack frames” and epilogue: The instructions at the end of a function that undo its prologue: they reload the saved registers (pops come in the reverse order of the pushes), give the stack space back so the stack pointer is where it was on entry, and return. More in “Callee-saved registers and stack frames”, which the lesson on callee-saved registers takes apart.

Why 16 and not 8, as a push would use? AAPCS64 states that whenever memory is accessed through sp, the hardware requires sp to be a multiple of 16, and Arm’s pseudocode for stp and ldp checks the alignment of sp as soon as it reads sp as the base register, before any offset is added. one_push breaks the rule on purpose. It saves one register with str x19, [sp, #-8]!, an 8-byte step: the store itself passes, since sp was aligned when it started, but it leaves sp 8 bytes off a multiple of 16, and the ldr that follows uses that sp as its base:

one_push: one register saved in an 8-byte step

one_push_aarch64.s

// one_push() for AArch64 on Apple platforms: saves ONE register the way x86-64's push would, in an 8-byte step.
// After the store, sp is a multiple of 8 but not of 16, and the load that takes x19 back uses sp as its base.
    .text
    .globl _one_push
    .p2align 2
_one_push:
    str x19, [sp, #-8]!     // sp = sp - 8, then x19 to [sp]: sp is now 8 bytes off a multiple of 16
    mov x0, #1
    ldr x19, [sp], #8       // a load whose base is that sp
    ret

one_push_main.c

/* Calls one_push once. The line below is printed only if one_push returns. */
#include <stdio.h>

long one_push(void);

int main(void) {
    printf("one_push returned %ld\n", one_push());
    return 0;
}
one_push on Apple silicon Python · one_push_session.py
"""Builds one_push for AArch64 and runs it. The program is stopped by a signal, so the shell's own message about it
is dropped and the exit status is printed instead; an exit status above 128 is 128 plus the signal's number."""

import pathlib
import sys

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

session.run("""
# AArch64: one register saved in an 8-byte step, as a push would do it
clang -O1 -o one_push one_push_main.c one_push_aarch64.s
{ ./one_push; } 2>/dev/null; echo "exit=$?"
# the name of signal 138 - 128 = 10
kill -l 10
""")

Output

# AArch64: one register saved in an 8-byte step, as a push would do it
$ clang -O1 -o one_push one_push_main.c one_push_aarch64.s
$ { ./one_push; } 2>/dev/null; echo "exit=$?"
exit=138
# the name of signal 138 - 128 = 10
$ kill -l 10
BUS

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

The driver’s line never appeared. The program ended with exit status 138, which is how the shell reports a process stopped by a signal: A notification the operating system delivers to a process, such as SIGFPE after an integer divide fault; unhandled, it ends the process, and the shell reports status 128 plus the signal number. More in “Division: traps, defined results, remainders”: 138 - 128 = 10, and kill -l 10 names signal 10 BUS, the bus error. On AArch64 a function with one register to save still moves sp by 16 and leaves 8 bytes of the space unused. To RISC-V hardware sp is the ordinary register x2, but its psABI requires sp to be 16-byte aligned at every procedure entry and to stay aligned while the procedure runs, so RISC-V code moves sp in 16-byte steps too. x86-64 hardware has no such rule for a push; the alignment rules of its ABIs concern the stack at and around calls, and they have details of their own.

Key idea

AArch64 and RISC-V save registers with ordinary stores through sp: stp with [sp, #-16]! lowers sp and stores a pair in one instruction and ldp with [sp], #16 undoes it, while RISC-V moves sp with addi and stores each register at its own offset. Both keep sp a multiple of 16, and on AArch64 an access through a misaligned sp faults.

Checkpoint

Check: Saving registers on AArch64 and RISC-V

  1. Question 1 of 2 From sp = S, AArch64 code runs stp x19, x20, [sp, #-16]! and then stp x21, x22, [sp, #-16]!. How many bytes below S is sp now?

    Type a number.

    A hint

    The ! writes the lowered address back into sp each time.

    Show the answer: Question 1 of 2

    Answer: 32 bytes

    Each pre-indexed stp lowers sp by 16 and keeps the result, so after two of them sp is S - 32, with x21 and x22 at the lower pair of slots. The restores run in reverse: the ldp of x21 and x22 first, then the one of x19 and x20.

  2. Question 2 of 2 Why did one_push end with exit status 138 instead of printing its line?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Its ldr used sp as the base register while sp was 8 bytes off a multiple of 16.

    str x19, [sp, #-8]! left sp 8 bytes off a multiple of 16, and the next load through sp faulted. The shell reported the signal as 128 + 10 = 138, and kill -l 10 named it BUS.

A red zone lets a leaf keep data below the stack pointer, on two of these ABIs

So far every save lowered the stack pointer before it stored, because anything that writes to the stack may overwrite the memory below it. Two ABIs carve out an exception. The System V psABI sets aside the 128 bytes just below rsp, and signal and interrupt handlers must leave them as they are. Any function may park short-lived data there, data it will not need once it calls something, and a leaf function: A function that makes no calls of its own. Nothing overwrites its link register while it runs, so it needs no save of x30 or ra, and at -O2 compilers often give it no stack space at all. More in “Calls, returns and the link register” can fit its entire frame in it and skip moving rsp altogether. That area is the red zone: The 128 bytes just below the stack pointer that the System V x86-64 ABI, and Apple on arm64, keep safe from signal and interrupt handlers, so a function may keep temporary data there without moving the stack pointer.. Apple documents the same 128 bytes below sp for arm64: its platforms leave them unchanged during exceptions, and user-mode programs may use them for local variables.

leaf_sum makes the difference visible. Its four temporaries are volatile, so the compiler must store each one to memory and load it back, and it calls nothing:

leaf.c: four values that must live in memory C · leaf.c
/* A leaf function whose four locals must live in memory: volatile makes the compiler store and load each one.
   long long is 64 bits on every target of the session, so each one keeps the same 32 bytes of data. */
long long leaf_sum(long long a, long long b) {
    volatile long long tmp[4];
    tmp[0] = a;
    tmp[1] = b;
    tmp[2] = a + b;
    tmp[3] = a - b;
    return tmp[0] + tmp[1] + tmp[2] + tmp[3];
}

The session prints what clang -O2 makes of it for five ABIs, and for x86-64 Linux a second time with the red zone switched off:

leaf_sum compiled for five ABIs Python · leaf_session.py
"""Prints what clang -O2 makes of the same leaf function for five ABIs, and for x86-64 Linux once more with the red
zone switched off. The grep after each command keeps labels and instructions and drops directives and comments."""

import pathlib
import sys

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

session.run("""
# x86-64 Linux, System V ABI
clang --target=x86_64-linux-gnu -O2 -S -masm=intel -fno-asynchronous-unwind-tables -o - leaf.c | grep -E '^[_[:alpha:]]|^[[:space:]]+[[:lower:]]'
# the same target with the red zone switched off
clang --target=x86_64-linux-gnu -O2 -S -masm=intel -fno-asynchronous-unwind-tables -mno-red-zone -o - leaf.c | grep -E '^[_[:alpha:]]|^[[:space:]]+[[:lower:]]'
# x86-64 Windows, Microsoft x64 ABI
clang --target=x86_64-pc-windows-msvc -O2 -S -masm=intel -fno-asynchronous-unwind-tables -o - leaf.c | grep -E '^[_[:alpha:]]|^[[:space:]]+[[:lower:]]'
# arm64 on Apple platforms
clang --target=arm64-apple-macos -O2 -S -fno-asynchronous-unwind-tables -o - leaf.c | grep -E '^[_[:alpha:]]|^[[:space:]]+[[:lower:]]'
# AArch64 Linux, AAPCS64
clang --target=aarch64-linux-gnu -O2 -S -fno-asynchronous-unwind-tables -o - leaf.c | grep -E '^[_[:alpha:]]|^[[:space:]]+[[:lower:]]'
# RISC-V Linux, LP64D
clang-23 --target=riscv64-linux-gnu -march=rv64gc -mabi=lp64d -O2 -S -fno-asynchronous-unwind-tables -o - leaf.c | grep -E '^[_[:alpha:]]|^[[:space:]]+[[:lower:]]'
""")

Output

# x86-64 Linux, System V ABI
$ clang --target=x86_64-linux-gnu -O2 -S -masm=intel -fno-asynchronous-unwind-tables -o - leaf.c | grep -E '^[_[:alpha:]]|^[[:space:]]+[[:lower:]]'
leaf_sum:                               # @leaf_sum
	mov	qword ptr [rsp - 24], rdi
	mov	qword ptr [rsp - 8], rsi
	lea	rax, [rsi + rdi]
	mov	qword ptr [rsp - 40], rax
	sub	rdi, rsi
	mov	qword ptr [rsp - 16], rdi
	mov	rax, qword ptr [rsp - 24]
	add	rax, qword ptr [rsp - 8]
	add	rax, qword ptr [rsp - 40]
	add	rax, qword ptr [rsp - 16]
	ret
# the same target with the red zone switched off
$ clang --target=x86_64-linux-gnu -O2 -S -masm=intel -fno-asynchronous-unwind-tables -mno-red-zone -o - leaf.c | grep -E '^[_[:alpha:]]|^[[:space:]]+[[:lower:]]'
leaf_sum:                               # @leaf_sum
	sub	rsp, 40
	mov	qword ptr [rsp + 16], rdi
	mov	qword ptr [rsp + 32], rsi
	lea	rax, [rsi + rdi]
	mov	qword ptr [rsp], rax
	sub	rdi, rsi
	mov	qword ptr [rsp + 24], rdi
	mov	rax, qword ptr [rsp + 16]
	add	rax, qword ptr [rsp + 32]
	add	rax, qword ptr [rsp]
	add	rax, qword ptr [rsp + 24]
	add	rsp, 40
	ret
# x86-64 Windows, Microsoft x64 ABI
$ clang --target=x86_64-pc-windows-msvc -O2 -S -masm=intel -fno-asynchronous-unwind-tables -o - leaf.c | grep -E '^[_[:alpha:]]|^[[:space:]]+[[:lower:]]'
leaf_sum:                               # @leaf_sum
	sub	rsp, 40
	mov	qword ptr [rsp + 16], rcx
	mov	qword ptr [rsp + 32], rdx
	lea	rax, [rdx + rcx]
	mov	qword ptr [rsp], rax
	sub	rcx, rdx
	mov	qword ptr [rsp + 24], rcx
	mov	rax, qword ptr [rsp + 16]
	mov	rcx, qword ptr [rsp + 32]
	add	rcx, rax
	mov	rdx, qword ptr [rsp]
	mov	rax, qword ptr [rsp + 24]
	add	rax, rdx
	add	rax, rcx
	add	rsp, 40
	ret
# arm64 on Apple platforms
$ clang --target=arm64-apple-macos -O2 -S -fno-asynchronous-unwind-tables -o - leaf.c | grep -E '^[_[:alpha:]]|^[[:space:]]+[[:lower:]]'
_leaf_sum:                              ; @leaf_sum
	sub	sp, sp, #32
	str	x0, [sp, #24]
	str	x1, [sp, #16]
	add	x8, x1, x0
	str	x8, [sp, #8]
	sub	x8, x0, x1
	str	x8, [sp]
	ldr	x8, [sp, #24]
	ldr	x9, [sp, #16]
	add	x8, x9, x8
	ldr	x9, [sp, #8]
	ldr	x10, [sp], #32
	add	x9, x9, x10
	add	x0, x8, x9
	ret
# AArch64 Linux, AAPCS64
$ clang --target=aarch64-linux-gnu -O2 -S -fno-asynchronous-unwind-tables -o - leaf.c | grep -E '^[_[:alpha:]]|^[[:space:]]+[[:lower:]]'
leaf_sum:                               // @leaf_sum
	sub	sp, sp, #32
	add	x8, x1, x0
	sub	x9, x0, x1
	str	x0, [sp, #24]
	str	x1, [sp, #16]
	str	x8, [sp, #8]
	str	x9, [sp]
	ldr	x8, [sp, #24]
	ldr	x9, [sp, #16]
	ldr	x10, [sp, #8]
	ldr	x11, [sp], #32
	add	x8, x9, x8
	add	x9, x10, x11
	add	x0, x8, x9
	ret
# RISC-V Linux, LP64D
$ clang-23 --target=riscv64-linux-gnu -march=rv64gc -mabi=lp64d -O2 -S -fno-asynchronous-unwind-tables -o - leaf.c | grep -E '^[_[:alpha:]]|^[[:space:]]+[[:lower:]]'
leaf_sum:                               # @leaf_sum
	addi	sp, sp, -32
	sd	a0, 24(sp)
	sd	a1, 16(sp)
	add	a2, a1, a0
	sub	a0, a0, a1
	sd	a2, 8(sp)
	sd	a0, 0(sp)
	ld	a0, 24(sp)
	ld	a1, 16(sp)
	ld	a2, 8(sp)
	ld	a3, 0(sp)
	add	a0, a0, a1
	add	a2, a2, a3
	add	a0, a0, a2
	addi	sp, sp, 32
	ret

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

The x86-64 Linux build never touches rsp. It stores its four values in four 8-byte slots at rsp - 24, rsp - 8, rsp - 40 and rsp - 16, all inside the red zone, adds them up and returns. With -mno-red-zone the same function begins with sub rsp, 40 and ends with add rsp, 40 before ret, and its slots move to rsp + 16, rsp + 32, rsp and rsp + 24: the very same addresses as before, now above the stack pointer instead of below it. The Windows build does the same, because Microsoft x64 has no red zone. Microsoft counts all memory below rsp as volatile, open to being overwritten by the operating system, a debugger or an interrupt handler, and requires rsp to be set before a function reads or writes its stack frame. All six builds:

ABI Red zone leaf_sum at -O2
System V, x86-64 Linux 128 bytes below rsp four stores below rsp, which never moves
System V with -mno-red-zone switched off sub rsp, 40, then the same four slots
Microsoft x64 none sub rsp, 40
Apple’s arm64 128 bytes below sp sub sp, sp, #32
AAPCS64, Arm Linux none sub sp, sp, #32
RISC-V LP64D none addi sp, sp, -32

Keeping data below rsp in code for Windows

A leaf ported from Linux that stores at [rsp - 16] without moving rsp breaks Microsoft’s rule without a word from the assembler: the bytes below rsp are volatile there, so the value can change between the store and the load. Lower rsp first and address the slots from it, as the Windows build of leaf_sum does with sub rsp, 40.

Two rows need a word. AAPCS64 itself has no red zone: no thread may read or write the inactive part of its stack, which is everything below sp. Apple’s arm64 grants the 128 bytes on top of that, yet clang moved sp for this function all the same; a red zone lets code keep data below the stack pointer, and nothing obliges a compiler to. RISC-V’s psABI tells procedures not to rely on data below sp persisting, and its build moved sp by 32.

The zone only holds data that is not needed after a call, because a call writes below the stack pointer too: on x86-64 it pushes its return address at rsp - 8, the top of the zone, and the callee may use the same 128 bytes as its own red zone. That is why the psABI offers the space for temporary data and names leaf functions as the ones that can live in it entirely.

Key idea

A red zone is the 128 bytes below the stack pointer that System V on x86-64 and Apple on arm64 keep safe from signal and interrupt handlers, so a leaf may store there without moving the stack pointer. Microsoft x64, AAPCS64 and RISC-V have none, and code for them lowers the stack pointer before it stores anything.

Checkpoint

Check: Red zones and the ABIs that have one

  1. Question 1 of 2 Which build of leaf_sum stores its four values below the stack pointer without ever moving it?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: x86_64-linux-gnu with the default flags

    Only the System V build used the red zone. Every other build, Apple's included, lowered the stack pointer first and kept its data at and above it.

  2. Question 2 of 2 An x86-64 Linux function stores a value at [rsp - 8] without moving rsp, calls another function, and then reads [rsp - 8]. What does it read?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The return address that its own call pushed there

    The psABI offers the red zone for data that is not needed across calls. A call pushes its return address at rsp - 8, the top of the zone, and the callee may use the same bytes as its own red zone.

Kernel code turns the red zone off, because interrupts push onto its stack

Linux builds its x86-64 kernel with -mno-red-zone: the flag is set in the kernel’s arch/x86/Makefile, beside its other code-generation flags, and the kernel’s Rust code gets the matching -Cno-redzone=y. GCC’s manual describes the flag as turning off the red zone that the x86-64 ABI mandates, the 128 bytes that signal and interrupt handlers do not modify. Why would a kernel give that up? Because of where the processor saves its state when an A request from a device or a timer that makes the processor set aside the code it is running, run a handler and then resume that code where it stopped. arrives, as Intel’s manual describes it.

When an interrupt arrives while user code runs, its handler runs at a more privileged level, and the processor first switches to a stack that the kernel set up for that level; only then does it save the interrupted state. The user’s stack, red zone and all, is never written. Kernel code already runs at the handler’s level, so for an interrupt that arrives while kernel code runs there is no switch: the processor saves the state on the current stack, right where rsp points. In 64-bit mode it first aligns rsp down to a multiple of 16 and then pushes five 8-byte values, the old SS, RSP, RFLAGS, CS and RIP: 40 bytes just below rsp, or 48 when an exception adds an error code. Only interrupts whose descriptor selects a separate stack from the interrupt stack table avoid this. That is delivery through the interrupt descriptor table (IDT), the scheme x86-64 processors have long used. FRED, a newer scheme that Intel’s manual also describes, first lowers rsp at the same privilege level by an amount the kernel configures, a multiple of 64 bytes, and the kernel’s build sets -mno-red-zone all the same. Step through one interrupt, delivered through the IDT, that lands on a kernel leaf’s red-zone data:

Interactive · step by step An interrupt lands on data below rsp in kernel code Step through one interrupt, delivered through the IDT, taken while an x86-64 kernel leaf keeps two values below rsp. R is rsp; slots lower on the page are lower addresses.
An interrupt lands on data below rsp in kernel code, step 7 of 7. Showing rsp (R again), Processor (running the leaf), Read of [rsp - 8] (gets the saved SS), R - 8 (saved SS), R - 16 (saved RSP), R - 24 (saved RFLAGS), R - 32 (saved CS), R - 40 (saved RIP). In focus: rsp.rspR againProcessorrunning the leafRead of [rsp - 8]gets the saved SSR - 8saved SSR - 16saved RSPR - 24saved RFLAGSR - 32saved CSR - 40saved RIP

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

Every step as text
  1. Data below rsp. A kernel leaf written like System V user code stored temp a at [rsp - 8] and temp b at [rsp - 16] without moving rsp. Here rsp is R, a multiple of 16.
  2. An interrupt arrives. A device interrupt arrives while the leaf runs. The leaf is kernel code, so the handler runs at the same privilege level, and without an IST entry the processor stays on the current stack.
  3. SS and RSP pushed. In 64-bit mode an interrupt always pushes the old SS and RSP first, 8 bytes each, just below the 16-byte aligned rsp: at R - 8 and R - 16. temp a and temp b are gone.
  4. RFLAGS, CS and RIP. Three more 8-byte pushes fill R - 24 to R - 40: a 40-byte frame, or 48 bytes for an exception that adds an error code. Anything the handler pushes goes below that.
  5. iretq. The handler ends with iretq, which pops the frame and puts rsp back to R. The frame's words stay in memory, where temp a and temp b used to be.
  6. temp a read back. The leaf loads [rsp - 8] and gets the saved SS instead of temp a. Nothing faults and no signal is sent: the result is simply wrong, and only when an interrupt lands in that window.
  7. The fix. Built with -mno-red-zone, as Linux builds its x86-64 kernel, the leaf first lowers rsp with sub rsp, 16. Its data then sits at and above rsp, and an interrupt frame lands below it.

The failure leaves no trace. Nothing faults, no signal is sent, and the value is wrong only when an interrupt lands in a window a few instructions long, between a store below rsp and the load that reads it back, so it is rare in a test and more frequent under heavy interrupt load. With -mno-red-zone the compiler does what the second leaf_sum build showed: it lowers rsp first, so the function’s data sits at and above rsp and an interrupt frame lands below it. User code keeps its red zone, because an interrupt taken in user code writes its frame on another stack, and the psABI binds signal handlers to leave the 128 bytes alone.

Key idea

An interrupt taken in kernel code is handled at the same privilege level, so with IDT delivery the processor pushes its 40- or 48-byte frame on the current stack, just below rsp, where red-zone data would be. That is why the x86-64 Linux kernel is compiled with -mno-red-zone and its functions lower rsp before they store.

Checkpoint

Check: Why kernel code turns the red zone off

  1. Question 1 of 2 Interrupts arrive while user code runs too. Why may x86-64 Linux user code keep data in its red zone all the same?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: The handler runs at a more privileged level, so the processor switches to the kernel's stack before it saves anything.

    An interrupt taken in user code changes privilege level, and with that change comes a switch to the stack the kernel set up. Signal handlers are bound by the psABI to leave the 128 bytes alone.

  2. Question 2 of 2 For an interrupt delivered through the IDT in 64-bit mode, with no error code, how many bytes does the processor push?

    Type a number.

    A hint

    Count the saved values (SS, RSP, RFLAGS, CS and RIP) and their size.

    Show the answer: Question 2 of 2

    Answer: 40 bytes

    Five values of 8 bytes each: SS, RSP, RFLAGS, CS and RIP make 40 bytes, and an error code adds 8 more for 48.

Practise

Four tasks on copies of this lesson’s files. Build the AArch64 programs and assemble or compile the others with the commands of the sessions above, and for every task write your prediction down before you run anything.

  1. Copy save_two_aarch64.s and let save_two borrow x21 and x22 as well: a second stp straight after the first, the matching ldp just before the first ldp, and values for x21 and x22 in the body, with the result still 42. Before you link it with save_two_main.c, write down both sp lines the driver will print.
  2. Trace the stack pointer through the eleven instructions of save_two_riscv64.s and the nine of save_two_x86_64.s, writing it after each one as an offset from S. Check both traces against the offsets that the stores and loads use in the listings.
  3. In a copy of save_two_x86_64.s, swap the two pops, then assemble and list it. Say which value rbx and r12 each hold when ret runs, and what the caller would see afterwards.
  4. Copy leaf.c, give tmp 20 elements and add all 20 into the result, then compile it for x86_64-linux-gnu with and without -mno-red-zone. The values take 160 bytes and the red zone 128: decide first how far each build must move rsp, then read what clang did.

Practice that adapts to you

Practise this lesson

12 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 12 What does push rax do, in order?

    Choose one answer.

    Show the answer: Question 1 of 12

    Answer: Lowers rsp by 8, then stores rax at the new [rsp]

    push lowers the stack pointer and then stores; pop loads and then raises it. Both move rsp by 8 for a 64-bit register.

  2. Question 2 of 12 From rsp = S, a function runs push rbx, push r12, pop r12, pop rbx. Where is rbx's old value now?

    Choose one answer.

    Show the answer: Question 2 of 12

    Answer: Still at S - 8, below rsp, where the next push or call may overwrite it

    Popping gives a slot up; it does not empty it. The value stays below rsp until a later push, call or interrupt writes over it, which is why nothing may rely on it.

  3. Question 3 of 12 A routine pushes rbx and then r12, but its epilogue is pop rbx, pop r12. Its caller had 1 in rbx and 2 in r12. What does the caller find after the return?

    Choose one answer.

    Show the answer: Question 3 of 12

    Answer: rbx = 2 and r12 = 1

    Order matters only for which register receives which slot. rsp ends where it started, so nothing faults, and the caller silently gets its two values swapped.

  4. Question 4 of 12 Which AArch64 instruction stores x19 and x20 and lowers sp by 16 in one go?

    Choose one answer.

    Show the answer: Question 4 of 12

    Answer: stp x19, x20, [sp, #-16]!

    The ! asks for writeback in the pre-index form. Its partner ldp x19, x20, [sp], #16 loads the pair and then adds 16 to sp.

  5. Question 5 of 12 In save_two_riscv64.s, which restore instruction has to come last?

    Choose one answer.

    Show the answer: Question 5 of 12

    Answer: addi sp, sp, 16

    On AArch64 and RISC-V the loads may come in either order because each uses its own offset. What must come last is the instruction that gives the space back.

  6. Question 6 of 12 A RISC-V function must save s1, s2 and s3, 8 bytes each. By how many bytes does its first addi lower sp?

    Type a number.

    A hint

    The psABI keeps sp a multiple of 16 the whole time.

    Show the answer: Question 6 of 12

    Answer: 32 bytes

    Three registers take 24 bytes, but sp has to stay a multiple of 16, so the function lowers sp by 32 and leaves 8 bytes unused. AArch64 code does the same, with an stp for one pair and a str for the third register.

  7. Question 7 of 12 How many bytes below rsp does the System V psABI reserve as the red zone?

    Type a number.

    A hint

    Apple documents the same size for arm64.

    Show the answer: Question 7 of 12

    Answer: 128 bytes

    The psABI reserves the 128 bytes beyond rsp, which signal and interrupt handlers must not modify. Apple documents 128 bytes below sp on arm64.

  8. Question 8 of 12 Which of these ABIs give a function a red zone? Choose every one.

    Choose every answer that is right.

    Show the answer: Question 8 of 12

    Answer:

    • Apple's arm64
    • System V on x86-64

    Two of the five ABIs in the lesson have a red zone, and both make it 128 bytes. Code for the other three lowers the stack pointer before it stores, as their builds of leaf_sum did.

  9. Question 9 of 12 A System V leaf function needs 160 bytes for its locals. Which plan breaks the ABI?

    Choose one answer.

    Show the answer: Question 9 of 12

    Answer: Store all 160 bytes below rsp without moving it.

    The red zone has a fixed size. A leaf may use any part of those 128 bytes, and whatever does not fit must sit at or above rsp, which means lowering rsp first.

  10. Question 10 of 12 Which compiler flag does Linux set for its x86-64 kernel so that its functions never keep data below rsp?

    Choose one answer.

    Show the answer: Question 10 of 12

    Answer: -mno-red-zone

    With -mno-red-zone the compiler lowers rsp before it stores, as the second build of leaf_sum showed with sub rsp, 40 and the same four slots above rsp.

  11. Question 11 of 12 An interrupt arrives through the IDT while x86-64 kernel code runs, and its IDT entry selects no separate stack. Where does the processor save the interrupted state?

    Choose one answer.

    Show the answer: Question 11 of 12

    Answer: On the current stack, just below rsp, after aligning rsp down to a multiple of 16

    Intel's manual: at the same privilege level the state goes on the current stack, and in 64-bit mode rsp is first aligned to 16 bytes and SS, RSP, RFLAGS, CS and RIP are pushed.

  12. Question 12 of 12 A kernel leaf built without -mno-red-zone keeps a value at [rsp - 32]. rsp is a multiple of 16, and a device interrupt with no error code arrives through the IDT between the store and the load. What does the load read?

    Choose one answer.

    Show the answer: Question 12 of 12

    Answer: The saved CS

    The frame covers the 40 bytes just below an aligned rsp, so any red-zone value in that range is replaced, and only when an interrupt lands in the window, which is why such bugs are rare and hard to reproduce.

Interview questions

What does x86-64’s push do to rsp and to memory, and how do AArch64 and RISC-V save a register without one?

push lowers rsp by 8 and then stores its operand at the new address, so rsp always points at the most recent value, and pop loads the value at rsp into its operand and then raises rsp by 8. Registers therefore come back in the reverse of the order they were pushed. AArch64 and RV64GC RISC-V have no push or pop. AArch64 stores a pair with writeback: stp x19, x20, [sp, #-16]! lowers sp by 16 and stores both registers in one instruction, and ldp x19, x20, [sp], #16 undoes it. RISC-V lowers sp with addi sp, sp, -16, stores each register at its own offset with sd, and reverses that with ld and addi sp, sp, 16. Both keep sp a multiple of 16, and on AArch64 an access through a misaligned sp faults.

The two questions below put the same rules under pressure: a kernel helper whose temporaries change only under load, and a save sequence that has to move from x86-64 to the other two ISAs.

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

  1. Mid to staff

    An x86-64 Linux kernel module is built by a hand-written Makefile that calls gcc with flags copied from a user-space project. Its leaf helper, which sums a few stack temporaries, returns a wrong total about once a day, only under heavy network load. Explain what goes wrong, how to confirm it from the binary, and the fix.

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

Flashcards · 7 cards

Recall it, then check

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

  1. Which way does the stack grow on x86-64, AArch64 and RISC-V, and what does the stack pointer address?

    Towards lower addresses on all three. The stack pointer holds the address of the value stored most recently, so the stack works last in, first out.

  2. What exactly do x86-64 push and pop do to rsp and to memory?

    push lowers rsp by 8 and then stores its operand at the new address; pop loads the value at rsp and then raises rsp by 8. Registers therefore come back in the reverse of the order they were pushed.

  3. AArch64 and RISC-V have no push instruction. How do they save registers on the stack?

    AArch64 uses stp with pre-index writeback, which lowers sp by 16 and stores a pair in one instruction, and ldp with post-index to undo it. RISC-V lowers sp with addi and stores each register at its own offset with sd.

  4. Why did a function that moved sp by only 8 bytes crash on Apple silicon?

    AArch64 and RISC-V keep sp a multiple of 16, and on AArch64 a load or store through a misaligned sp faults. The function's next access through sp ended with a bus error.

  5. What is the red zone, and which ABIs have one?

    The 128 bytes just below the stack pointer that signal and interrupt handlers leave alone, so a leaf may keep data there without moving the stack pointer. System V on x86-64 and Apple on arm64 promise it; Microsoft x64, AAPCS64 and RISC-V do not.

  6. Why is the x86-64 Linux kernel built with -mno-red-zone?

    An interrupt taken in kernel code stays at the same privilege level, so the processor pushes its frame onto the current stack just below rsp, exactly where red-zone data would sit. Kernel functions therefore lower rsp before storing anything.

  7. After a pop, what happens to the bytes that held the value?

    Nothing at once: they stay in memory below the stack pointer until a later push, call or interrupt overwrites them.

Key takeaways

  • All three stacks grow towards lower addresses and the stack pointer addresses the most recent value: x86-64’s push lowers rsp by 8 and then stores, and pop loads and then raises rsp by 8, so registers come back in the reverse of the order they were pushed.
  • AArch64 saves a pair and lowers sp in one stp with [sp, #-16]! and undoes it with ldp and [sp], #16, while RISC-V moves sp with addi and stores each register in its own slot with sd; every version ends with the stack pointer back at its entry value.
  • AArch64 and RISC-V move sp in 16-byte steps, and on Apple silicon one_push, which saved one register in an 8-byte step, died with a bus error at its next load through sp.
  • System V on x86-64 and Apple on arm64 promise a 128-byte red zone below the stack pointer; clang’s x86-64 Linux build of leaf_sum stored there without moving rsp, while every other build, Apple’s included, lowered the stack pointer first.
  • In kernel code an interrupt delivered through the IDT pushes its frame on the current stack, just below rsp, so Linux compiles its x86-64 kernel with -mno-red-zone.

Terms in this lesson

pop
Taking the most recent value off the stack: load it from the address in the stack pointer, then raise the stack pointer past it. The bytes stay in memory, below the stack pointer, until something overwrites them.
push
Putting a value on the stack: lower the stack pointer, then store the value at the new address. x86-64's push instruction does both and moves rsp by 8 for a 64-bit register; AArch64 and RISC-V use a store and a stack-pointer update.
red zone
The 128 bytes just below the stack pointer that the System V x86-64 ABI, and Apple on arm64, keep safe from signal and interrupt handlers, so a function may keep temporary data there without moving the stack pointer.
stack
Memory a program uses last in, first out: the value stored most recently is the first taken back. The stack pointer holds the address of that value, and on x86-64, AArch64 and RISC-V the stack grows towards lower addresses.
stack slot
One place on the stack that holds one value while a function runs, found at a fixed offset from the stack pointer, such as [rsp + 16] or 8(sp).
writeback
The update of the base register by a pre-indexed or post-indexed load or store. AArch64's stp x19, x20, [sp, #-16]! writes sp - 16 back into sp, and ldp x19, x20, [sp], #16 adds 16 to sp after the load.

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.