Skip to content

Level 5 · Chapter 5.4

x86, ARM and RISC-V side by side

The three instruction sets that run nearly every computer today — where they come from, how their registers, encodings, addressing and calls differ — and the same seven C functions compiled for all three, instruction by instruction and byte by byte.

Three instruction sets run almost every computer in use today. x86-64 runs PCs and most servers. ARM64 runs phones, tablets, Apple's computers and a growing share of servers. RISC-V is the newcomer: an open ISA that anyone can implement, already common in microcontrollers and embedded cores. Tanenbaum's book compares a different trio, from 2013: the 32-bit IA-32, the 32-bit ARMv7 of a phone chip, and the 8-bit AVR of a microcontroller. The ideas carry over; the chips have moved on.

This chapter puts the three current ISAs side by side, then lets you compare them the way that matters most: the same C code, compiled for each.

Three histories

x86 descends in an unbroken line from the 16-bit Intel 8086 of 1978, itself shaped by the 8-bit 8080. The 80386 made it 32-bit in 1985 (IA-32, the architecture the book describes). The 64-bit extension, x86-64, came from AMD in 2003 — a rare case, as the book notes, where Intel had to adopt a competitor's design. Every generation kept the previous one working, which is x86's great strength and the source of its baggage: variable-length instructions from 1 to 15 bytes, prefixes stacked on prefixes, registers with special roles, and modes going back to the 8086. A modern x86 still boots in 16-bit real mode.

ARM started at Acorn in 1985, inspired by the Berkeley RISC research: a 32-bit design with 16 registers, the program counter being one of them. The book calls ARMv8, with 64-bit addresses, "recently published". It was announced in 2011, and its 64-bit mode, AArch64 (ARM64), turned out to be a nearly clean redesign rather than an extension: 31 general-purpose registers, the program counter no longer a general register, most instructions no longer conditional, and a new fixed 32-bit encoding. It has since become the most widely used 64-bit ISA, and ARM designs are licensed: companies like Apple and Qualcomm pay Arm to implement it.

RISC-V began at Berkeley in 2010, as a clean ISA for research and teaching, and its base was ratified in 2019. Its distinguishing feature is not technical but legal: it's an open standard, free to implement without a license. It's also modular: a small base integer ISA (RV32I has about 40 instructions; RV64I adds a dozen for 64-bit words), plus optional standard extensions named by letters — M for multiply and divide, A for atomics, F and D for floating point, C for compressed 16-bit instructions, V for vectors. RV64GC, the usual target for Linux-capable cores, means RV64I plus M, A, F, D (together "G") and C.

The designs compared

x86-64ARM64RISC-V (RV64GC)
PhilosophyCISC, with backward compatibility to 1978RISC, pragmaticRISC, minimal and modular
General-purpose registers1631 + zero register32, x0 always zero
Instruction length1 to 15 bytesalways 4 bytes4 bytes, or 2 with the C extension
Arithmetic on memory operandsyes (add eax, [rbx])no: load/storeno: load/store
Addressingbase + index × scale + displacementbase + offset, scaled register, pre/post-incrementbase + 12-bit offset only
Flagsyes, set by most instructionsyes, set only by instructions that ask (adds, cmp)none: branches compare registers
Conditional selectcmovcccselnone in the base ISA (added by the 2023 Zicond extension)
Return addresspushed on the stack by callin link register x30in ra (x1)
Memory orderingTSOweakweak (RVWMO)
LicensingIntel and AMDlicensed by Armopen

The previous chapters covered many of these rows: instruction formats, addressing modes, memory ordering and calling conventions.

The same code, three ways

The comparison below compiles seven small C functions with clang at -O2, for each ISA, and shows the actual instructions and bytes (disassembled with llvm-objdump):

Three ISAs · Seven functions, three ISAs

Try it: Pick a function below to see the same C code compiled for x86-64, ARM64 and RISC-V, with every instruction's bytes.

int get(const int *a, long i) {
    return a[i];
}
x86-64variable length, 1–15 bytes
  1. 8b 04 b7mov eax, dword ptr [rdi + 4*rsi]
  2. c3ret
2 instructions4 bytes
ARM64fixed 4 bytes
  1. b8617800ldr w0, [x0, x1, lsl #2]
  2. d65f03c0ret
2 instructions8 bytes
RISC-V (RV64GC)4 bytes, or 2 compressed
  1. 058aslli a1, a1, 0x2
  2. 952eadd a0, a0, a1
  3. 4108lw a0, 0x0(a0)
  4. 8082ret
4 instructions8 bytes

All seven functions: x86-64 32 instructions, 83 bytes · ARM64 34 instructions, 136 bytes · RISC-V (RV64GC) 35 instructions, 92 bytes

clang -O2 (LLVM 21 for RISC-V), no unrolling or vectorization, position-dependent code; alignment padding removed. → marks a symbol the linker fills in, so its field shows 0 here.

What the seven functions show:

  • get — reading a[i]. x86 does it in one 3-byte instruction with its scaled index, [rdi + 4*rsi]; ARM64 also in one, with a scaled register offset, [x0, x1, lsl #2]. RISC-V's only addressing mode is base + constant, so it needs a shift, an add and then the load: three instructions instead of one.
  • sum — a loop. All three end up with about ten instructions, but in different styles. x86 uses the scaled index again; ARM64 uses a post-increment load, ldrsw x9, [x0], #4, which loads and advances the pointer in one instruction; RISC-V converts the loop to walk a pointer to an end address, so it can use bne to compare two registers directly, with no flags.
  • max — a choice without a branch on x86 (cmovg) and ARM64 (csel, conditional select). Base RISC-V has no conditional move, so the compiler emits a branch.
  • big — a 64-bit constant. x86 has a 10-byte movabs with the whole constant inside. ARM64 instructions are only 4 bytes long, so it builds the constant 16 bits at a time: mov then three movk (move and keep). RISC-V loads it from a constant pool in memory.
  • bump — counter++ on a global. x86 increments memory directly: inc qword ptr [rip + counter], one instruction that reads, adds and writes. The load/store ISAs need to form the address, load, add and store: four instructions.
  • call — calling another function. x86's call pushes the return address on the stack (the push rax only keeps the stack 16-byte aligned). ARM64's bl and RISC-V's jal put it in a link register instead, which is faster for leaf functions; since call makes a call of its own, it has to save its link register on the stack first.
  • divide — x86's idiv has fixed registers: the dividend must be in edx:eax, hence the mov and the cdq that sign-extends into edx. ARM64 and RISC-V divide any two registers into a third. (As the previous chapter measured, they also differ on division by zero.)

Size and count

Across the seven functions, x86-64 needs 32 instructions in 83 bytes, ARM64 34 instructions in 136 bytes, and RISC-V 35 instructions in 92 bytes. Without the C extension, the same RISC-V code takes 140 bytes: compressed instructions make it about a third smaller, close to x86's density with a far more regular encoding.

Two things stand out. First, the instruction counts are nearly equal. The CISC–RISC gap is much smaller in practice than the folklore suggests: x86 saves an instruction here and there with memory operands and rich addressing, but compilers mostly use it like a load/store machine. And inside the CPU, a modern x86 core translates its instructions into RISC-like micro-operations anyway. Second, fixed-length instructions cost code size but simplify decoding: with every instruction 4 bytes long, a core can decode eight of them in parallel, knowing where each one starts. An x86 core has to work out each instruction's length before it can decode the next, which is why x86 cores cache already-decoded micro-operations.

This is a small sample of seven functions, not a benchmark. On whole programs, the three typically end up within a few tens of percent of each other in code size, and performance depends far more on the microarchitecture than on the ISA.

Why it matters, and why it matters less than it used to

For a long time, the ISA decided who could build a chip: x86 was effectively limited to Intel and AMD, and compatibility with existing binaries kept it dominant on PCs. Two things weakened that. Most software is now compiled from source or runs on a virtual machine, so porting it to a new ISA is a recompilation — as Apple showed, twice, by moving the Mac from PowerPC to x86 and from x86 to ARM, with a binary translator (Rosetta) covering the transition. And the microarchitecture now matters more than the ISA: a well-designed ARM64 core and a well-designed x86-64 core, built on the same process, perform within a small margin of each other.

What still differs is who may build one. x86 is closed, ARM is licensed, and RISC-V is open — which is why it's showing up wherever companies want a custom processor without paying for, or asking permission from, anyone.

Takeaways

  • x86-64 (1978 → AMD64 in 2003) keeps full backward compatibility: variable-length instructions, 16 registers, memory operands, flags.
  • ARM64 (AArch64, 2011) is a clean 64-bit redesign of ARM: fixed 4-byte instructions, 31 registers, a link register, conditional select, licensed to chip makers.
  • RISC-V (2010, ratified 2019) is open and modular: a tiny base ISA plus letter extensions (RV64GC); no flags, one addressing mode, optional 16-bit compressed instructions.
  • Compiled side by side, the three need about the same number of instructions (32, 34, 35 here). x86 is densest (83 bytes), RISC-V with compression close behind (92), ARM64 largest (136).
  • The differences show in the details: scaled addressing, conditional moves, 64-bit constants, memory operands, link registers, fixed division registers.
  • Today the microarchitecture matters more for performance than the ISA; the ISA mostly decides who may build the chip.

In this level

  1. 5.1What the ISA promises: memory model, alignment and ordering
  2. 5.2Instruction formats and encoding
  3. 5.3Addressing modes
  4. 5.4x86, ARM and RISC-V side by side
  5. 5.5Traps, interrupts and exceptions
  6. 5.6Data types the hardware understands
  7. 5.7VLIW, EPIC and the Itanium