Skip to content

Registers and Integer Operations

A register is a fixed-width storage location, not a variable with a permanent C type. Predict the effects of three operations, then check actual results.

Before you start

Complete the first program. You can assemble and run code, but do not yet need nested functions or stack frames. Work in a fresh practice directory on the Pi.

Outcome: explain a 32-bit destination write, construct a large constant and evaluate a small unsigned expression.

The ARM64 register model

AArch64 has x0–x30 general-purpose registers. w0–w30 name their low 32-bit views, not a separate bank.

64-bit vs 32-bit access

Operation Effect
mov x0, #-1 All 64 bits become 1
mov w0, #0x1234 Upper 32 bits of x0 become zero
mov w0, #-1 x0 becomes 0x00000000ffffffff
sxtw x0, w0 Sign-extend the low 32 bits

Do not write a pointer through a w destination in this 64-bit course. Storing w0 to memory, by contrast, writes four bytes; it does not clear bits of the source register.

sp is the stack pointer; xzr/wzr reads as zero and discards writes. They are not interchangeable. The PC is the current instruction address, inspected later as $pc in GDB. SIMD/FP registers are a separate bank introduced in Part 4.

Operations and interpretation

Instruction Meaning
add, sub Fixed-width addition and subtraction
mul Keep the low-width product
and, orr, eor Combine bits
lsl, lsr Left shift, logical right shift
asr Sign-filling right shift
udiv, sdiv Unsigned or signed division

The bits alone do not choose signedness. Width, instruction and the program's contract do. Overflow wraps at the instruction width in the additions used here; this is not an overflow detector. Immediate operands have encoding limits.

An arbitrary mov x0, #0x123456789abcdef0 cannot be assumed to assemble. Use explicit movz (initialize and zero other chunks) and movk (replace a chunk). A shift of 16 moves a chunk by 16 bits, not bytes.

Build a checked register lab

For now use only this minimal function contract: integer arguments arrive in x0, x1, x2; a result leaves in x0. Each leaf function returns through ret. The full preservation rules wait until the functions lesson.

Save both files in the same directory:

// file: registers.S
.text
.global width_demo
.type width_demo, %function
width_demo:
    mov x0, #-1
    mov w0, #0x1234
    ret
.size width_demo, .-width_demo

.global constant_demo
.type constant_demo, %function
constant_demo:
    movz x0, #0xdef0
    movk x0, #0x9abc, lsl #16
    movk x0, #0x5678, lsl #32
    movk x0, #0x1234, lsl #48
    ret
.size constant_demo, .-constant_demo

.global scaled_sum
.type scaled_sum, %function
scaled_sum:
    add x0, x0, x1
    mul x0, x0, x2
    ret
.size scaled_sum, .-scaled_sum
.section .note.GNU-stack,"",%progbits
/* file: registers.c */
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
extern uint64_t width_demo(void);
extern uint64_t constant_demo(void);
extern uint64_t scaled_sum(uint64_t, uint64_t, uint64_t);
_Static_assert(sizeof(void *) == 8, "Use a 64-bit userspace");
int main(void)
{
    uint64_t width = width_demo();
    uint64_t constant = constant_demo();
    uint64_t sum = scaled_sum(10, 20, 3);
    if (width != UINT64_C(0x1234) ||
        constant != UINT64_C(0x123456789abcdef0) || sum != 90) {
        fputs("Register lab check failed\n", stderr);
        return 1;
    }
    printf("width: 0x%016" PRIx64 "\nconstant: 0x%016" PRIx64
           "\nscaled_sum: %" PRIu64 "\n", width, constant, sum);
    return 0;
}
1
2
3
gcc -std=c11 -Wall -Wextra -Wpedantic -Werror -g -O0 registers.c registers.S -o registers
./registers
objdump -d registers | sed -n '/<width_demo>:/,+5p'
1
2
3
width: 0x0000000000001234
constant: 0x123456789abcdef0
scaled_sum: 90

A successful exit means all three checks passed, not just that printing worked. Disassembly may display aliases differently. If a constant fails, inspect each 16-bit chunk before changing the expected value.

Practice

In a copy, change mov w0, #0x1234 to mov w0, #-1. The driver must fail because its contract is unchanged. Restore the source afterwards. Do not delete the assertion to make a changed implementation look correct.

Check your understanding

Predict the full x0 value after the changed write. Why is it not a 64-bit negative one?

Compare your reasoning

A w0 write zeroes the upper half, leaving 0x00000000ffffffff. A 64-bit all-ones result requires a 64-bit operation or explicit sign extension.

Continue the course

Previous: Tools and Your First AArch64 Program · Course roadmap · Next: Flags, Branches and Loops

Donate