COMPUTER ORGANIZATION & ARCHITECTURE / 9. ISA — X86-64 & ARM

Instruction Set Architecture — x86-64 & ARM

The contract between hardware and software — what the programmer sees


EXPLANATION

The Instruction Set Architecture (ISA) is the abstract model of a computer that software is written for. It defines everything a programmer (or compiler) needs to know: what instructions exist, what registers are available, how memory is addressed, and what the calling convention is.

The ISA is a contract: Intel can redesign the transistors, change the pipeline, add new microarchitecture features — as long as the ISA behavior is preserved, all existing software continues to work. This is why a program compiled for x86 in 1990 still runs on your 2024 Intel CPU.

x86-64 (AMD64) — the dominant desktop/server ISA:
- CISC (Complex Instruction Set Computer)
- Variable-length instructions: 1 to 15 bytes per instruction
- 16 general-purpose 64-bit registers: RAX, RBX, RCX, RDX, RSI, RDI, RSP, RBP, R8-R15
- Special registers: RIP (instruction pointer), RFLAGS (status flags)
- Memory operands: instructions can directly operate on memory (not just registers)
- Internally: CPU translates CISC instructions into RISC-like micro-ops before execution

Key x86-64 registers and their conventional uses:
- RAX — accumulator, return value from functions
- RBX — base register (callee-saved)
- RCX — counter (loop variable)
- RDX — data (second argument, used in multiply/divide)
- RSI/RDI — source/destination index (string ops), first two function arguments
- RSP — stack pointer (top of stack)
- RBP — base pointer (stack frame base)
- R8-R15 — additional general purpose
- XMM0-XMM15 — 128-bit SIMD registers (SSE/AVX)

ARM (AArch64) — the dominant mobile/embedded ISA, now servers and Macs:
- RISC (Reduced Instruction Set Computer)
- Fixed-length instructions: always 4 bytes (32-bit)
- 31 general-purpose 64-bit registers: X0-X30 (W0-W30 for 32-bit view)
- X0-X7: function arguments and return values
- X29: frame pointer, X30: link register (return address), SP: stack pointer
- Load-Store architecture: ONLY LOAD and STORE can access memory. All arithmetic is register-to-register.
- Condition codes on every instruction (ARM can conditionally execute most instructions)
- Much simpler to pipeline than x86 — one reason Apple Silicon is so fast

Calling Conventions (ABI — Application Binary Interface):
How functions pass arguments and return values:
- x86-64 System V (Linux/Mac): RDI, RSI, RDX, RCX, R8, R9 for first 6 args; stack for more; RAX for return
- x86-64 Windows: RCX, RDX, R8, R9 for first 4 args; stack for more
- ARM64: X0-X7 for first 8 args; X0 for return

Stack conventions: RSP/SP must be 16-byte aligned before a CALL instruction. CALL pushes return address. RET pops and jumps to it.

RISC-V — the open source ISA gaining momentum:
- Royalty-free, open standard
- Base instruction set (RV32I/RV64I): only ~47 instructions
- Modular extensions: M (multiply), A (atomic), F (float), D (double), C (compressed 16-bit)
- Growing in embedded, academia, and now data center chips

DIAGRAM

x86-64 REGISTER FILE:
  63      31  15  8 7   0
  │──RAX────│──EAX──│AH│AL│   General purpose
  │──RBX────│──EBX──│BH│BL│   (backward compat:
  │──RCX────│──ECX──│CH│CL│    can access 8/16/32/64
  │──RDX────│──EDX──│DH│DL│    bit views)
  │──RSI────│──ESI──│──SI──│
  │──RDI────│──EDI──│──DI──│
  │──RSP────│──ESP──│──SP──│   Stack pointer
  │──RBP────│──EBP──│──BP──│   Frame pointer
  │──R8─────│──R8D──│─R8W──│
  ...       ...
  │──R15────│──R15D─│─R15W─│
  │──RIP────│       ← Instruction Pointer

  FUNCTION CALL (x86-64 System V):
  C code:          long add(long a, long b) { return a + b; }
  Assembly:        mov rax, rdi    ; return value = first arg
                   add rax, rsi    ; add second arg
                   ret             ; return (pops RIP from stack)

  Caller:          mov rdi, 5     ; first arg = 5
                   mov rsi, 3     ; second arg = 3
                   call add       ; push RIP, jump to add
                   ; result now in RAX = 8

  ARM vs x86 (same operation):
  x86: add rax, [rbx + 8]    ← memory operand directly in ALU op
  ARM: ldr x1, [x0, #8]      ← LOAD first
       add x0, x0, x1        ← then ALU op on registers only

CODE

PYTHON
1import struct
2import ctypes
3import sys
4
5# ── Inspect x86-64 instructions (disassembly) ─────────
6# pip install capstone
7try:
8 from capstone import Cs, CS_ARCH_X86, CS_MODE_64
9
10 # Compile and disassemble a simple function
11 # This is actual x86-64 machine code for: return a + b
12 machine_code = bytes([
13 0x48, 0x89, 0xF8, # mov rax, rdi (rax = first arg)
14 0x48, 0x01, 0xF0, # add rax, rsi (rax += second arg)
15 0xC3, # ret
16 ])
17
18 md = Cs(CS_ARCH_X86, CS_MODE_64)
19 print("x86-64 machine code for 'return a + b':")
20 for instr in md.disasm(machine_code, 0x1000):
21 print(f" {instr.address:#06x}: {instr.bytes.hex():12s} {instr.mnemonic} {instr.op_str}")
22except ImportError:
23 print("(Install capstone for disassembly: pip install capstone)")
24
25# ── Calling convention demo in Python (ctypes) ────────
26print("
27Calling Convention demo via ctypes:")
28
29# Python → C function call follows the ABI
30libc = ctypes.CDLL(None)
31libc.printf.argtypes = [ctypes.c_char_p]
32libc.printf.restype = ctypes.c_int
33
34# Internally: Python puts format string ptr in RDI, calls printf
35# printf puts return value (chars written) in RAX
36result = libc.printf(b" printf called via ctypes! chars written: %d
37", 42)
38
39# ── Endianness (ISA-defined byte order) ───────────────
40print("
41Endianness (byte storage order):")
42val = 0x12345678
43b = struct.pack("<I", val) # little-endian (x86-64, ARM in LE mode)
44print(f" Value: {val:#010x}")
45print(f" Little-endian bytes: {' '.join(f'{byte:02x}' for byte in b)}")
46print(f" LSB ({val & 0xFF:#04x}) stored at LOWEST address")
47
48b_be = struct.pack(">I", val) # big-endian (network byte order)
49print(f" Big-endian bytes: {' '.join(f'{byte:02x}' for byte in b_be)}")
50print(f" MSB ({(val>>24) & 0xFF:#04x}) stored at LOWEST address")
51
52# ── Integer representation edge cases ─────────────────
53print("
54ISA integer representation quirks:")
55import ctypes
56
57# Signed overflow (undefined behavior in C, but let's see what x86 does)
58max_int32 = 2**31 - 1
59print(f" Max int32: {max_int32} = {max_int32:#010x}")
60print(f" Max + 1: {max_int32 + 1} (Python int arbitrary precision)")
61overflow = ctypes.c_int32(max_int32 + 1).value
62print(f" Max + 1 in 32-bit: {overflow} (wraps to {overflow}!)")
63
64# Two's complement
65print(f"
66 Two's complement -1 in 32-bit: {ctypes.c_uint32(-1).value:#010x}")
67print(f" Two's complement -1 in 8-bit: {ctypes.c_uint8(-1).value:#04x}")
68
69# ── SIMD: process multiple values at once ──────────────
70print("
71SIMD (Single Instruction Multiple Data):")
72import array
73
74# Simulate what SSE/AVX does: 4 additions in one instruction
75a = array.array('f', [1.0, 2.0, 3.0, 4.0])
76b = array.array('f', [5.0, 6.0, 7.0, 8.0])
77# Real SIMD: one ADDPS instruction adds all 4 pairs simultaneously
78result = array.array('f', [x + y for x, y in zip(a, b)])
79print(f" SIMD ADD (4 floats at once): {list(a)} + {list(b)} = {list(result)}")
80print(f" AVX-512 can do 16 floats at once used in ML inference!")
← PREV8. Cache Memory