ISAs: RISC vs CISC, x86 / ARM / RISC-V
An ISA is the contract between software and silicon — understanding RISC vs CISC, x86/ARM/RISC-V trade-offs, and why the kernel must speak every dialect is the foundation of everything low-level.
Overview
Every program you have ever run — from a web browser to the Linux kernel itself — is ultimately a sequence of instructions executed by a physical processor. But instructions are not a physical thing; they are an abstraction, a language defined in a document called an Instruction Set Architecture (ISA). The ISA is the most important interface in all of computing: it is the contract that lets software written today run on hardware built ten years from now, and on chips made by a dozen different companies.
This module is about understanding that contract deeply. We will explore what an ISA actually specifies (registers, memory model, encodings, privilege levels), the two great philosophical camps of ISA design — RISC and CISC — and the three ISAs that dominate the real world: x86-64 (your laptop/desktop), AArch64 (your phone, Apple Silicon, AWS Graviton), and RISC-V (the open-source newcomer taking over embedded, academic, and increasingly server silicon). We will be precise about the difference between an ISA (the specification) and a microarchitecture (the physical implementation).
By the end, you will understand why the Linux kernel has an arch/ directory with separate subdirectories for each ISA, why a context switch looks different on x86 vs ARM, and why the choice of ISA shapes everything from syscall conventions to interrupt handling. This is not trivia — it is the bedrock on which all kernel programming stands.
If computer science has a periodic table, the ISA is it: a structured, principled description of the fundamental elements that everything else is built from. Once you truly internalize the ISA concept, the kernel source code stops being mysterious and starts being inevitable.
The Linux kernel is not a single program — it is a family of programs, one per supported ISA. Every time you look at kernel source under arch/x86/, arch/arm64/, or arch/riscv/, you are reading code that exists because ISAs differ. The entry path for a system call, the layout of the thread stack frame, the way the kernel saves and restores registers during a context switch, the mechanism for memory-mapped I/O, the handling of hardware interrupts — all of these are ISA-specific. You cannot read or write that code without knowing what the ISA guarantees and what it does not.
Beyond reading existing code, ISA knowledge is essential for kernel contribution. Submitting a patch to the scheduler, the memory management subsystem, or a device driver requires understanding whether your change is portable across architectures or ISA-specific. Bugs that appear only on ARM but not x86 — or only on little-endian but not big-endian systems — are a constant reality in kernel development. The maintainers expect you to know the difference. Understanding the ISA as a contract also teaches you to think about what the hardware guarantees vs. what is merely customary, which is the exact mental model you need to reason about memory ordering, atomics, and lock-free algorithms — topics that run through the heart of kernel concurrency code.
ISA Layers: From C Source to Silicon
The ISA is the boundary between what software can observe and what hardware is free to implement however it likes. The compiler targets the ISA; the CPU designer targets performance within its constraints.
The concepts
01What an ISA Actually Is
An Instruction Set Architecture is a formal specification that defines the complete interface between software and hardware. Think of it as a constitution: the hardware must implement every guarantee in the document, and software may rely on any guarantee the document makes.
An ISA specifies at minimum:
- Registers — How many, how wide, what each is allowed to hold (integers, floats, SIMD vectors), and which are caller-saved vs callee-saved by convention.
- Instructions — Every operation the processor can perform: arithmetic, logic, memory load/store, branches, system calls, etc.
- Instruction encodings — The exact bit pattern for every instruction. This is what the assembler produces and what the CPU's decoder reads.
- Memory model — What ordering guarantees are made when multiple cores read and write shared memory. This is subtler and more important than most people realize.
- Privilege levels — How the CPU distinguishes kernel code from user code (x86 calls these "rings"; ARM calls them "Exception Levels").
- Exception and interrupt model — What happens when a fault occurs, how the CPU saves state, where it transfers control.
- System registers and control — Special registers that configure caches, MMU, floating-point behavior, etc.
The ISA does not specify:
- How many pipeline stages the processor has
- Whether it executes instructions in order or out-of-order
- The size of caches
- Clock speed
Those belong to the microarchitecture — a concept we will return to.
A beautiful fact: the x86 ISA has been continuously extended since 1978. A binary compiled in 1985 for an 8086 still runs correctly on a 2024 Intel Core i9. The ISA is the contract that makes this immortality possible.
# The assembler converts ISA mnemonics to binary encodings.
# Both of these are valid x86-64 for 'move the value 1 into register eax':
# mov eax, 1
# Its encoding is: B8 01 00 00 00 (5 bytes)
# You can verify with objdump:
# Write a tiny program:
printf '\xb8\x01\x00\x00\x00\xc3' > /tmp/tiny.bin
# B8 = MOV EAX, imm32; 01 00 00 00 = 1; C3 = RET
objdump -D -b binary -m i386:x86-64 /tmp/tiny.bin
02Microarchitecture vs Architecture: The Same ISA, Different Brains
This is the single most important distinction in CPU design, and it is frequently confused.
Architecture (the ISA) is the specification: what the programmer observes and can rely on. It is abstract, independent of transistors.
Microarchitecture is the implementation: the actual circuit design, the pipeline, the branch predictor, the caches, the execution units. It is entirely internal — software cannot directly observe it (with a few exceptions like performance counters and timing side channels).
Consider the x86-64 ISA. It is implemented by:
- Intel Core (Alder Lake, Raptor Lake, ...) — deeply out-of-order, wide superscalar, large caches
- AMD Zen 4 — similar out-of-order execution, different cache topology, different branch predictor
- VIA Nano — same ISA, much simpler microarchitecture, lower performance
All three chips run the exact same binary. They look identical to the OS kernel. But Intel vs AMD vs VIA differ radically in how they achieve the same observable behavior.
The analogy is a legal contract. The ISA is the contract text. The microarchitecture is how each company's lawyers and engineers choose to fulfill its obligations. You can build a fast, expensive law firm or a slow, cheap one — but you must honor the contract.
This matters for the kernel because:
- The kernel is written against the ISA, not any specific microarchitecture.
- Performance tuning (e.g., cache line size, memory ordering barriers) sometimes needs to be microarchitecture-aware. Linux uses CPU feature flags (
/proc/cpuinfo, CPUID instruction) to detect the specific microarchitecture at runtime. - Security vulnerabilities like Spectre and Meltdown are microarchitecture bugs — the ISA contract was not violated, but the microarchitectural implementation created observable side effects that the ISA spec never intended to permit.
// The CPUID instruction is an ISA instruction that
// reports microarchitecture-level information at runtime.
// Linux reads it during boot to detect CPU features.
#include <stdio.h>
void cpuid(int leaf, unsigned *eax, unsigned *ebx,
unsigned *ecx, unsigned *edx) {
__asm__ volatile ("cpuid"
: "=a"(*eax), "=b"(*ebx), "=c"(*ecx), "=d"(*edx)
: "a"(leaf) : );
}
int main(void) {
unsigned a, b, c, d;
cpuid(0, &a, &b, &c, &d);
// EBX:EDX:ECX contain vendor string (e.g. "GenuineIntel")
char vendor[13];
*(unsigned*)&vendor[0] = b;
*(unsigned*)&vendor[4] = d;
*(unsigned*)&vendor[8] = c;
vendor[12] = '\0';
printf("CPU vendor: %s\n", vendor);
printf("Max CPUID leaf: %u\n", a);
return 0;
}
03RISC Philosophy: Simple Instructions, Compiler Does the Work
RISC stands for Reduced Instruction Set Computer. The name is slightly misleading — it is not about having fewer instructions total, but about each instruction being simple and uniform.
The RISC philosophy, pioneered by David Patterson (Berkeley, RISC project) and John Hennessy (Stanford, MIPS project) in the early 1980s, rests on a key empirical observation: most programs spend the vast majority of their time executing a tiny subset of simple operations. Complex instructions that hardware architects labored to include were rarely used and made the entire pipeline slower.
RISC design principles:
- Fixed-width instructions — Every instruction is the same size (typically 32 bits). The decoder always knows exactly where the next instruction starts. This makes pipelining straightforward.
- Load/store architecture — Only dedicated LOAD and STORE instructions access memory. All arithmetic operates on registers. You cannot, say, add a memory location directly to a register in one instruction.
- Large, uniform register file — Typically 16–32 general-purpose registers of equal capability.
- Simple addressing modes — Memory access is base + offset, period. No complex calculated addresses in a single instruction.
- Operations complete in one cycle (ideally) — Each instruction does one well-defined thing.
The implicit bargain: the hardware is simpler and faster, but the compiler must do more work. Where a CISC programmer writes one complex instruction, a RISC compiler emits two or three simple ones. This was controversial in the 1980s but turned out to be the right trade-off because:
- Simple hardware runs faster per cycle
- Out-of-order execution engines work much better with uniform instructions
- Compilers got very good at managing registers
ARMv8 (AArch64) and RISC-V are the dominant RISC ISAs today.
// RISC-V assembly: compute c = a + b where a is in memory
// Load/store architecture: memory access and arithmetic are separate
.section .text
.global compute
compute:
# a0 = pointer to 'a', a1 = pointer to 'b'
lw t0, 0(a0) # LOAD: t0 = memory[a0]
lw t1, 0(a1) # LOAD: t1 = memory[a1]
add a0, t0, t1 # ADD: a0 = t0 + t1 (register-to-register only)
ret # return (a0 holds return value)
# Notice: 3 instructions. Memory access and ALU are strictly separate.
04CISC Philosophy: Complex Instructions, Hardware Does the Work
CISC stands for Complex Instruction Set Computer. The x86 family is the canonical CISC architecture, and it dominates personal computers, laptops, and servers.
CISC emerged in the 1970s when memory was expensive and slow. Encoding a complex operation in a single instruction meant smaller programs and fewer memory fetches. The Intel 8086 (1978) inherited and extended this tradition. By the time RISC challenges arose, x86 had an enormous installed base and backward-compatibility requirements that prevented radical simplification.
CISC characteristics:
- Variable-length instructions — x86-64 instructions range from 1 to 15 bytes. The decoder must figure out where each instruction ends before it can decode the next. This is genuinely hard to pipeline.
- Memory-to-memory operations — You can add a value in memory directly to a register, or even add two memory locations.
- Many addressing modes —
[rbx + rcx*4 + 32]is a single valid x86 memory operand: base + index*scale + displacement. - Fewer architectural registers — Classic x86 had only 8 general-purpose registers. x86-64 extended this to 16, still fewer than ARM's 31.
- Special-purpose registers — Segment registers, control registers, debug registers all have specific roles.
The CISC dirty secret: modern Intel and AMD processors don't actually execute CISC instructions. Internally, the processor's front-end decoder translates variable-length x86 instructions into fixed-width micro-operations (micro-ops or µops) before they enter the out-of-order execution engine. In other words, modern x86 chips have a RISC core with a CISC translator bolted onto the front.
This means the philosophical war between RISC and CISC is largely settled: both camps converged on similar microarchitectures. The real legacy of CISC is backward compatibility — the same ISA encoding that worked in 1985 must still work today.
// x86-64 assembly: same c = a + b, CISC style
// Notice: we CAN operate directly on memory
.intel_syntax noprefix
.section .text
.global compute_cisc
compute_cisc:
// rdi = pointer to 'a', rsi = pointer to 'b'
mov eax, DWORD PTR [rdi] // load a into eax (still a load...)
add eax, DWORD PTR [rsi] // ADD directly from memory to register!
ret
// x86 allows the ADD to reference memory directly.
// RISC would require a separate LOAD instruction first.
// The encoder packs this into a compact binary form.
05x86-64: The Dominant Desktop and Server ISA
x86-64 (also called AMD64, x64, or EM64T) is the 64-bit extension of the original 8086 ISA, introduced by AMD in 2003 and quickly adopted by Intel. It is the ISA of virtually every laptop, desktop, and x86 server.
Key features relevant to the kernel:
- 16 general-purpose registers:
rax, rbx, rcx, rdx, rsi, rdi, rsp, rbp, r8–r15. Therspregister is the hardware stack pointer;rbpis conventionally the frame pointer. - Instruction pointer:
rip— the CPU always knows where it is. - Flags register (RFLAGS): Records results of arithmetic (zero, carry, overflow, sign). Conditional branches test these flags.
- Privilege rings: Ring 0 (kernel), Ring 1 & 2 (unused by Linux), Ring 3 (user space). The current privilege level (CPL) is stored in the CS segment register's low 2 bits.
- System calls: The
syscallinstruction (64-bit ABI) savesripandrflags, switches to kernel stack, jumps to the address in theLSTARMSR. The kernel entry point in Linux isentry_SYSCALL_64inarch/x86/entry/entry_64.S. - Segmentation (legacy): x86 has a segmentation system inherited from the 8086 era. In 64-bit mode, most segment registers are forced to base 0, making segmentation effectively flat. The kernel uses
gsandfsfor per-CPU and per-thread data. - SIMD extensions: SSE, SSE2, AVX, AVX-512 add wide vector registers. The kernel must save/restore these on context switches (lazily, using the
fxsave/xsaveinstructions). - Page table format: 4-level (48-bit virtual address space) or 5-level (57-bit) page tables. Each level is a 512-entry array of 8-byte entries.
The x86-64 ABI (System V AMD64 ABI) specifies that function arguments are passed in rdi, rsi, rdx, rcx, r8, r9, with the return value in rax. The kernel system call ABI is slightly different: rax holds the syscall number.
// Minimal x86-64 Linux syscall: write "hi\n" to stdout
// Demonstrates ISA-level system call mechanism
.section .data
msg: .ascii "hi\n"
.section .text
.global _start
_start:
mov rax, 1 // syscall number: sys_write
mov rdi, 1 // fd: stdout
lea rsi, [rip+msg] // buf: address of msg
mov rdx, 3 // count: 3 bytes
syscall // transfers to kernel Ring 0
mov rax, 60 // syscall number: sys_exit
xor rdi, rdi // status: 0
syscall
// assemble: as -o t.o t.s && ld -o t t.o && ./t
06AArch64: The ARM 64-bit ISA Powering Mobile, Apple Silicon, and Cloud
AArch64 is the 64-bit execution state of the ARMv8 and later architectures. It is the ISA of every modern smartphone (iOS and Android), Apple Silicon (M1/M2/M3/M4), AWS Graviton, Ampere Altra, and countless embedded systems.
ARM (now Arm Holdings) licenses its ISA to chip designers who build their own microarchitectures — this is why Apple's M4 and a Cortex-A53 in a $5 microcontroller both run AArch64 binaries, despite radically different performance.
Key AArch64 features:
- 31 general-purpose registers:
x0–x30(64-bit) orw0–w30(32-bit views of the same registers).x30is the link register (return address). The stack pointerspand program counterpcare separate, not in the general register file. - Clean fixed-width encoding: All instructions are exactly 32 bits. The decoder is trivially simple.
- Condition codes: Unlike x86 where flags are set by most arithmetic operations, AArch64 only sets flags when you use flag-setting variants of instructions (e.g.,
adds,subs,cmp). - Exception Levels (EL): EL0 = user, EL1 = OS kernel, EL2 = hypervisor, EL3 = secure monitor (TrustZone). Linux runs at EL1; virtual machines run at EL1 under a hypervisor at EL2.
- System calls: The
svc #0instruction (Supervisor Call) triggers an exception, saving state inELR_EL1(return address) andSPSR_EL1(saved processor state), then jumping to the exception vector table. In Linux this lands atel0_syncinarch/arm64/kernel/entry.S. - Load/store exclusive:
ldxr/stxrpairs implement atomic operations without a lock prefix (unlike x86'slockprefix). This is howatomic_cmpxchgis implemented on ARM. - A64 calling convention: Arguments in
x0–x7, return inx0. Callee must savex19–x28, x29 (fp), x30 (lr).
// AArch64 Linux syscall: write "hi\n" to stdout
// Compare structure to the x86-64 version above
.section .data
msg: .ascii "hi\n"
.section .text
.global _start
_start:
mov x8, #64 // syscall number: sys_write (ARM Linux)
mov x0, #1 // fd: stdout
adr x1, msg // buf: address of msg
mov x2, #3 // count: 3 bytes
svc #0 // Supervisor Call -> EL1 (kernel)
mov x8, #93 // syscall number: sys_exit
mov x0, #0 // status: 0
svc #0
// Note: syscall number in x8 (not rax like x86).
// This is ISA-defined ABI difference — the kernel
// entry code in arch/arm64/ handles it accordingly.
07RISC-V: The Open-Source ISA
RISC-V (pronounced "risk five") is a free and open ISA designed at UC Berkeley starting in 2010. Unlike x86 (proprietary to Intel/AMD) and ARM (licensed from Arm Holdings for significant fees), RISC-V is governed by the non-profit RISC-V International and anyone can implement it without royalties or license fees.
This is revolutionary. It means a student can design a RISC-V CPU core, tape it out at a semiconductor fab, and sell chips with no licensing cost. It is why RISC-V is exploding in academia, embedded systems, and increasingly in server and AI accelerator silicon (SiFive, Esperanto, StarFive, Alibaba T-Head).
RISC-V design principles:
- Modular base + extensions: The base integer ISA (RV32I or RV64I) is deliberately minimal — 47 instructions. Extensions are named letters:
M(multiply/divide),A(atomics),F(single-precision float),D(double-precision float),C(compressed 16-bit instructions),V(vector). A typical Linux-capable chip implements RV64GC (G = IMAFD, C = compressed). This modularity means a RISC-V for a tiny microcontroller and one for a server can share the same base specification. - 32 registers:
x0–x31.x0is hardwired to zero — reads always return 0, writes are ignored. This elegant trick eliminates a specialCLRinstruction and simplifies many encodings. By convention,x1= return address (ra),x2= stack pointer (sp). - Clean 32-bit encoding with 16-bit compressed: Base instructions are 32 bits. The
Cextension adds 16-bit compressed versions of common instructions for code density. - Privilege levels: Machine mode (M, bare metal), Supervisor mode (S, OS kernel), User mode (U). Linux runs in S mode. A firmware layer (OpenSBI) runs in M mode.
- System calls: The
ecallinstruction triggers an environment call exception. On Linux/RISC-V,a7holds the syscall number;a0–a5hold arguments. The kernel entry is inarch/riscv/kernel/entry.S. - No branch delay slots: Unlike MIPS (another classic RISC), RISC-V has no branch delay slots, removing a major source of compiler complexity.
RISC-V is the best ISA to learn assembly on because it is clean, minimal, and has no legacy warts.
// RISC-V (RV64) Linux syscall: write "hi\n" to stdout
// The cleanest of the three ISAs to read
.section .data
msg: .ascii "hi\n"
.section .text
.global _start
_start:
li a7, 64 // syscall number: sys_write
li a0, 1 // fd: stdout
la a1, msg // buf: address of msg
li a2, 3 // count: 3 bytes
ecall // Environment Call -> Supervisor mode
li a7, 93 // syscall number: sys_exit
li a0, 0 // status: 0
ecall
// RISC-V uses a7 for syscall number, a0-a5 for args.
// Identical structure to AArch64 but different registers.
// The x0 register is always zero — 'li a0, 0' often
// compiles to 'mv a0, x0' (move zero register).
08The ISA as a Contract: Memory Models and Privilege
The ISA is not just a list of instructions — it is a formal contract with two especially subtle clauses that matter enormously to kernel programmers: the memory model and the privilege model.
Memory Model
In a single-core world, the order in which a program observes memory operations matches the program order. In a multi-core world, this assumption breaks catastrophically.
Modern processors reorder memory operations for performance (store buffers, load-store queues, non-coherent caches). The ISA's memory model specifies exactly what reorderings are permitted and what software must do to prevent them.
- x86-64 Total Store Order (TSO): Relatively strong. Loads are never reordered with other loads; stores are never reordered with other stores; loads may pass pending stores but only to different addresses. This means x86 needs very few explicit barriers.
- AArch64 Weak Ordering: Much weaker. Almost any reordering is permitted unless you insert explicit barrier instructions:
dmb(Data Memory Barrier),dsb(Data Synchronization Barrier),isb(Instruction Synchronization Barrier). ARM's weak memory model makes it faster (the hardware does less synchronization automatically) but forces the kernel and compiler to be explicit about ordering. - RISC-V RVWMO (RISC-V Weak Memory Ordering): Similar to ARM's model. Uses the
fenceinstruction to impose ordering constraints.
This is why the Linux kernel has functions like smp_rmb(), smp_wmb(), smp_mb() (read/write/full memory barriers) that compile to different instructions on different ISAs — or even to nothing on x86 where TSO makes some barriers free.
Privilege Model
The ISA defines how the CPU enforces the kernel/user distinction:
- x86: CPL in CS register, changed by
syscall/sysretand interrupt gates - AArch64: Exception Level, changed by
svc(EL0→EL1) anderet(EL1→EL0) - RISC-V: Mode in
mstatus/sstatusCSRs, changed byecallandsret
The kernel's entire security model depends on these ISA-guaranteed privilege transitions working correctly.
// Linux kernel memory barrier usage (from include/asm-generic/barrier.h)
// The ISA's memory model determines what these expand to.
// On x86-64 (TSO): smp_rmb() and smp_wmb() are often just
// compiler barriers (no CPU instruction needed), while smp_mb()
// needs a LOCK prefix or MFENCE.
// On ARM64 (weak): all three need explicit DMB instructions:
// smp_rmb() -> dmb ishld (inner-shareable load barrier)
// smp_wmb() -> dmb ishst (inner-shareable store barrier)
// smp_mb() -> dmb ish (full inner-shareable barrier)
// Example: lock-free ring buffer producer (simplified)
void ring_produce(struct ring *r, int val) {
r->data[r->head] = val; // write data
smp_wmb(); // BARRIER: ensure data visible before head
r->head++; // advance head (consumer reads this)
// Without smp_wmb(), a weakly-ordered CPU (ARM/RISC-V)
// could advance head BEFORE writing data -> consumer reads garbage
}
09Why the Kernel Has an arch/ Directory
Open the Linux kernel source and you will find arch/ containing subdirectories: x86/, arm64/, riscv/, mips/, powerpc/, s390/, and a dozen more. Each is a distinct implementation of the same kernel services, tailored to one ISA's contract.
The key ISA-specific pieces every architecture must provide:
-
Entry/exit to kernel mode (
arch/*/kernel/entry.S): When a syscall or interrupt fires, the CPU jumps to an ISA-specific entry point. This code must save all user registers (the ISA defines exactly what "all registers" means) to the kernel stack in astruct pt_regs. On x86-64, this is 168 bytes; on AArch64 it is different. The layout is ISA-defined. -
Context switching (
arch/*/kernel/process.c,switch_to()): When the scheduler switches from task A to task B, it must save A's register state and restore B's. The ISA determines what state must be saved: integer registers, floating-point/SIMD state (saved lazily on many architectures), segment registers (x86 only), control registers. -
Page table format (
arch/*/mm/): x86 uses 4-level page tables with specific entry formats; AArch64 uses a similar but differently laid-out structure; RISC-V uses Sv39 (3-level), Sv48 (4-level), or Sv57 (5-level). The MMU hardware that walks these tables is microarchitecture-specific but the format is ISA-defined. -
Boot and CPU initialization (
arch/*/boot/,arch/*/kernel/cpu.c): Each ISA has its own boot protocol (multiboot, EFI, device tree, SBI) and CPU feature discovery mechanism (CPUID on x86, MIDR/ID registers on ARM, CSRs on RISC-V). -
Signal delivery: When the kernel delivers a signal to a user process, it must push a signal frame onto the user stack — a saved copy of the register state so the signal handler can return correctly. The exact layout of this frame is ISA-specific and is part of the user ABI.
Portable kernel code lives in kernel/, mm/, fs/, etc. and calls into ISA-specific code through a thin abstraction layer. When you contribute a kernel patch, you need to know whether your change is portable or needs per-arch implementations.
// From arch/x86/include/asm/ptrace.h (simplified)
// This struct captures the exact register state at a
// syscall/interrupt boundary. Layout is ISA-mandated.
struct pt_regs {
unsigned long r15, r14, r13, r12;
unsigned long rbp, rbx;
unsigned long r11, r10, r9, r8;
unsigned long rax, rcx, rdx, rsi, rdi;
unsigned long orig_rax; // syscall number
unsigned long rip; // instruction pointer
unsigned long cs; // code segment
unsigned long eflags; // processor flags
unsigned long rsp; // user stack pointer
unsigned long ss; // stack segment
};
// Compare to arch/arm64/include/asm/ptrace.h which has
// u64 regs[31], sp, pc, pstate — completely different layout,
// same kernel-level concept.
10Comparing the Three ISAs: A Practical Summary
Having studied each ISA in depth, it is useful to compare them across the dimensions that matter most for kernel programming and systems work.
Register count and naming
- x86-64: 16 GPRs, historically named (
rax,rbx, ...) with legacy 8/16/32-bit sub-registers (al,ax,eax). The naming is a historical accident. - AArch64: 31 GPRs (
x0–x30), plus dedicatedspandpc. Clean numbering, dual 32/64-bit views (w0–w30). - RISC-V: 32 GPRs (
x0–x31),x0hardwired zero. Also clean numbering; ABI aliases (ra,sp,gp,a0–a7,t0–t6,s0–s11) are conventions, not hardware.
Instruction width
- x86-64: Variable, 1–15 bytes. Compact but hard to decode.
- AArch64: Fixed 32 bits. Simple decoder.
- RISC-V: 32 bits base; 16 bits with C extension. Best of both worlds.
System call convention
| Syscall instruction | Number reg | Arg regs | Return reg | |
|---|---|---|---|---|
| x86-64 | syscall | rax | rdi,rsi,rdx,r10,r8,r9 | rax |
| AArch64 | svc #0 | x8 | x0–x5 | x0 |
| RISC-V | ecall | a7 | a0–a5 | a0 |
Memory model strength
- x86-64: TSO (strong) — most barriers are free
- AArch64: Weak — explicit barriers required
- RISC-V: Weak (RVWMO) — explicit
fencerequired
Openness
- x86-64: Proprietary (Intel + AMD cross-license)
- AArch64: Licensed from Arm Holdings (expensive)
- RISC-V: Fully open, royalty-free
Best for learning assembly: RISC-V, because it is clean, minimal, and has no legacy complexity. Use Godbolt Compiler Explorer (godbolt.org) to instantly see how C compiles to all three.
// See the same C function compiled to all three ISAs
// on Godbolt: https://godbolt.org
// Try this function with -O2 optimization:
long sum(long *arr, int n) {
long s = 0;
for (int i = 0; i < n; i++)
s += arr[i];
return s;
}
// x86-64 will use tight register ops, possibly SIMD auto-vectorization
// AArch64 will show the clean fixed-width encoding
// RISC-V will show the load/store separation most explicitly
// Try: gcc -O2 -march=rv64gc for RISC-V target
Key insights
- ✦The ISA is a formal contract between software and hardware — the microarchitecture can be anything as long as it honors that contract, which is why a 1985 x86 binary still runs on a 2024 CPU.
- ✦Modern x86 chips secretly have a RISC core: the front-end decoder translates variable-length CISC instructions into fixed-width micro-ops before execution. The RISC vs CISC war ended in a draw by convergence.
- ✦The Linux kernel's arch/ directory exists entirely because ISAs differ: register counts, privilege mechanisms, syscall conventions, memory models, and page table formats are all ISA-defined, and the kernel must implement all of them.
- ✦A weak memory model (ARM, RISC-V) is not a bug — it is a deliberate trade-off that allows hardware to reorder memory operations for performance. The programmer's job is to insert barriers where ordering actually matters.
- ✦RISC-V's x0 register being hardwired to zero is a beautiful example of ISA elegance: it eliminates the need for a CLR instruction, simplifies immediate encoding, and costs exactly zero hardware area.
- ✦The syscall mechanism is the most important ISA feature for the kernel: it is the only hardware-enforced gate between user and kernel mode, and its exact behavior (which registers are saved, where execution jumps, what state is preserved) is completely ISA-specific.
- ✦Spectre and Meltdown are microarchitecture bugs, not ISA bugs — the ISA contract was never violated, but the microarchitectural side effects created information leaks the ISA spec never intended to permit.
Common pitfalls
- ⚠Confusing ISA with microarchitecture: saying 'x86 is slow' or 'ARM is power-efficient' conflates the contract (ISA) with a specific implementation. Apple's M4 is ARM and is faster than many x86 chips.
- ⚠Assuming x86 memory ordering everywhere: code written and tested only on x86 (TSO) will appear correct but can fail on ARM or RISC-V systems due to missing memory barriers. Always use smp_rmb()/smp_wmb()/smp_mb() in kernel code.
- ⚠Treating assembly registers as interchangeable: every register has ABI-defined roles (caller-saved vs callee-saved, argument vs scratch). Violating these corrupts the call stack in ways that are hard to debug.
- ⚠Forgetting that x86-64 syscalls use r10, not rcx for the 4th argument: the syscall instruction clobbers rcx (saving the return address there), so the 4th argument is passed in r10 instead. The glibc syscall() wrapper handles this, but raw assembly must not.
- ⚠Assuming ISA = ABI: the ISA defines what the hardware supports; the ABI (Application Binary Interface) defines how software should use it. The Linux kernel has a separate kernel ABI (UAPI) that must remain stable even as the internal ISA usage evolves.
- ⚠Ignoring endianness: x86-64 is always little-endian; AArch64 is configurable but Linux uses little-endian; RISC-V is little-endian by default. Code that casts between integer types and byte arrays must account for this.
Practice
- 01easy
Use Godbolt Compiler Explorer (godbolt.org) to compile the same simple loop (sum an array) targeting x86-64, AArch64, and RISC-V (rv64gc) with -O2. Count the number of instructions in each. Identify the load instruction, the add instruction, and the branch instruction in each output. Write a paragraph comparing the instruction density and readability.
- 02medium
Write a minimal Linux program in raw x86-64 assembly (no libc, no C) that reads a single byte from stdin and writes it back to stdout, then exits. Use only the syscall instruction and the System V AMD64 syscall ABI. Assemble with 'as' and link with 'ld'.
- 03hard
Demonstrate x86 vs ARM memory ordering in C. Write a program with two threads: thread 1 writes data then sets a flag; thread 2 spins on the flag then reads data. Run it 10 million iterations with and without an smp_wmb()-equivalent (use __sync_synchronize() or C11 atomic_thread_fence). Use QEMU user-mode to run an AArch64 cross-compiled binary on your x86 machine and observe whether the behavior differs.
- 04hard
Read the Linux kernel source for the x86-64 and AArch64 syscall entry points. Find 'arch/x86/entry/entry_64.S' and 'arch/arm64/kernel/entry.S' in the kernel source (browse at elixir.bootlin.com). Identify: (1) where the register state is saved to the kernel stack, (2) how the syscall number is extracted, (3) where the syscall table is indexed. Write a comparison of how the two architectures differ in their entry paths.
- 05medium
Implement a RISC-V instruction decoder in Python. Given a 32-bit RISC-V RV64I instruction as a hex integer, decode and print: the opcode, instruction format (R/I/S/B/U/J), and register fields. Test it on at least 5 instructions. Use the RISC-V specification (riscv.org/technical/specifications/) as your reference.
Resources
The primary RISC-V specification — clean, well-written, and free. Read Chapters 1-5 for the base integer ISA. This is the gold standard for how an ISA spec should be written.
The definitive AArch64 reference. Requires free registration. Start with the Programmer's Model chapter; the full manual is 10,000+ pages but the relevant sections are well-indexed.
The authoritative x86-64 reference. Volume 1 (Basic Architecture) and Volume 2 (Instruction Set Reference) are the most useful. Dense but complete.
The book that defines the field. Patterson and Hennessy invented RISC — reading their textbook is hearing the story from the source.
Indispensable tool: paste C/C++ and instantly see the assembly for x86-64, AArch64, RISC-V, and 50+ other targets. Essential for building ISA intuition.
Cross-referenced Linux kernel source with search. Essential for reading arch/ code — click any symbol to find its definition across all architectures.
The original argument for RISC-V as an open standard. Motivates why ISA openness matters and gives a clear historical context for RISC vs CISC.
Paul McKenney's deep dive into memory ordering as it applies to the Linux kernel. The definitive resource for understanding smp_mb() and friends.
The best hands-on RISC-V assembly book for beginners — clean examples, no legacy baggage.
Concise and authoritative. Perfect companion to the formal specification — explains the why behind every design choice.
Checkpoints — you can now…
- ✓You can explain in one paragraph what an ISA specifies and what it deliberately leaves unspecified, giving a concrete example of each.
- ✓You can describe the difference between the x86-64 CISC approach and RISC-V RISC approach using the same C operation as an example, showing the resulting assembly for each.
- ✓You can write a minimal Linux system call in raw assembly for at least one of x86-64, AArch64, or RISC-V, identifying which register holds the syscall number and why that differs across ISAs.
- ✓You can explain why Linux's arch/ directory exists by naming at least three ISA-specific kernel subsystems and what ISA contract property each one depends on.
- ✓You can explain TSO vs weak memory ordering with a concrete producer-consumer example and state which barrier instruction is needed on ARM but not x86.
- ✓You can use Godbolt to compile a simple C function to all three ISAs and read the output well enough to identify load, store, branch, and arithmetic instructions.
- ✓You can distinguish a microarchitecture bug (like Spectre) from an ISA contract violation, and explain why that distinction matters for software mitigations.