Reading Assembly

Part 4 · Where the fictions break

µops: the ISA is an interface

Fiction four, and the last one. What executes is not what you read — which settles the CISC-versus-RISC argument you may still hear people having.

35 minrefutation textPOE
What this lesson is trying to break (3)
  • The CPU executes x86 instructions.
  • CISC is slower than RISC because the instructions are complicated.
  • Two instructions always cost more than one.
Unlearn thiscarried over from the reasonable belief that the CPU runs the instructions you can see
You think

Assembly is the machine’s native language. The CPU fetches an x86 instruction and the hardware performs it. That is what “machine code” means.

Wrong here

No modern x86 core executes x86 instructions. A decoder translates each one into one or more internal operations called micro-ops, and the µops are what get renamed, scheduled, executed and retired. The instruction you read is an input to a translator.

Instead

The ISA is an interface — a stable public API over a private, changing implementation. Reading assembly tells you what was requested through that API. It does not tell you what the hardware did, and instruction count is not even the right unit.

The counting breaks in both directions

One instruction can be several µops.

add rax, [rbx] is one instruction and at least two µops: a load, then an add. div is many. A single instruction can occupy the machine for dozens of cycles.

Several instructions can be one µop.

This is macro-op fusion, and it is less well known. The decoder recognises certain adjacent pairs — most importantly a compare followed by the branch that reads its flags — and fuses them into a single µop.

        cmp     eax, edx
        jne     .L2

Two instructions in the listing. One operation in the machine.

Which means the loop condition at the bottom of every loop you have read in this course is cheaper than it looks, and it explains why compilers place cmp immediately before its branch rather than scheduling other work between them: separating them would break the fusion.

Predict firstcommit in writing, then look
; A — x86, one instruction
        add     eax, [rbx]

; B — ARM64, two instructions
        ldr     w1, [x2]
        add     w0, w0, w1

Same computation. A is one instruction, B is two. Which is faster?

What this settles

The CISC-versus-RISC argument, if you still hear it, is largely over, and the reason is this lesson: x86 chips are RISC-like machines with a CISC front end. The execution core is a pile of simple operations being scheduled; the complexity lives in a decoder.

So the honest 2026 summary, useful if the topic comes up:

  • The execution cores of high-end x86 and ARM64 designs are more alike than different: wide, out-of-order, speculative, with similar cache structures.
  • The real remaining differences are decoder cost and power (variable versus fixed length), the memory model — ARM64’s is weaker, which matters for lock-free code — and vector width, where AVX-512 is 512-bit and NEON is 128.
  • “CISC is slow” has not been true for about twenty-five years.

Where to get real numbers

Since fiction 4 means you cannot count instructions, and fiction 3 means you cannot count cycles from a table either:

  • llvm-mca — µop counts, port pressure, critical path for a block. Models the core, ignores memory.
  • uops.info — measured µop counts, latency, throughput and ports per instruction per microarchitecture.
  • A profiler — the only one that measures your program on your data. Which is 6.2.
Explain it to yourselfwrite it — thinking it does not count

Someone claims their ARM64 build is more efficient because the same function compiles to fewer instructions than the x86-64 build.

Write the correction. You need two separate points: one about what an instruction is across the two ISAs, and one about what would actually have to be measured.

Part 4 checkpoint

All four fictions are now broken. Out loud:

  1. Why does out-of-order execution not break your program? (4.1)
  2. What sets the floor on a loop’s speed? (4.1)
  3. Latency versus throughput — when does each bind? (4.2)
  4. Why is division uniquely bad? (4.2)
  5. What does a predicted branch cost? A mispredicted one? (4.3)
  6. Why can sorting an array speed up a loop? (4.3)
  7. Why does pointer chasing lose so badly to array scanning? (4.4)
  8. What does the CPU actually execute? (4.5)

If those are fluent, you have the thing this course exists to teach: you can read a listing and say sensible things about what it will cost.

Next: 5.1 — Vector registers.

Scheduled for recall3 items added to your review queue

These are not shown to you now on purpose. They will surface in Review tomorrow, mixed in among items from other parts of the course — not grouped by topic. Sorting out which idea applies is the part that transfers.