Reading Assembly

Part 4 · Where the fictions break

The listing is a dependency graph

Fiction two, dismantled. Instructions do not execute in the order they are written, and once you stop reading them as a script the whole subject reorganises.

40 minThreshold conceptrefutation textPOEnotional machine
What this lesson is trying to break (3)
  • Instructions execute in the order listed.
  • Reordering source lines changes what the CPU does.
  • The CPU executes one instruction at a time.
Liminality warning

This is a threshold concept: troublesome, transformative, and irreversible. You should expect to be genuinely stuck here, and being stuck is the mechanism rather than a sign that you are failing. Budget several sessions. Do not move on because you are bored — move on when you can explain it out loud without notes.

Unlearn thiscarried over from every mental model of a computer you have ever had
You think

The CPU reads an instruction, does it, moves to the next. Assembly is a sequence of steps and the machine performs them in that sequence. This is what every explanation of a computer has told you since you started.

Wrong here

A modern core decodes many instructions at once, works out which depend on which, and executes each one as soon as its inputs are ready — which may be long before earlier instructions have finished. It also executes instructions past branches whose outcome is not yet known, and throws the work away if it guessed wrong.

Instead

Read a listing as a dependency graph, not a script. The vertical order is the order the compiler wrote things down; the edges — which instruction needs which result — are what determines time. Two adjacent instructions with no edge between them effectively happen at once.

Why the illusion is airtight

If execution is reordered, why has this never broken any program you have written?

Because retirement is in order. Execution happens whenever convenient; results become architecturally visible in strict program order. A correct single-threaded program cannot observe the difference — only timing reveals it.

That is the contract from 0.2 doing its job: behave as if sequential, perform as fast as possible. Which is why fiction 2 is perfectly safe for reasoning about correctness and completely useless for reasoning about speed.

(The exception is multiple threads, where another core can observe the reordering. That is what memory ordering and fences exist for, and it is why Ordering::SeqCst versus Relaxed is a real decision rather than paranoia.)

Reading the edges

Take two sequences with identical instruction counts.

; A — one chain
        add     eax, edi
        add     eax, esi
        add     eax, edx
        add     eax, ecx
; B — two chains
        add     eax, edi
        add     ebx, esi
        add     eax, edx
        add     ebx, ecx

Four adds each. In A every instruction needs the previous result: a chain of length 4, so it takes four add-latencies end to end.

In B there are two independent chains of length 2. The core can run one from each chain simultaneously, so it finishes in roughly two add-latencies — about twice as fast, with the same instruction count and the same arithmetic.

Nothing about the text tells you this. The edges do.

Predict firstcommit in writing, then look
// A
long chain(long *p) {
    long acc = 0;
    for (int i = 0; i < 1024; i++) acc = acc * 3 + p[i];
    return acc;
}

// B
long indep(long *p) {
    long a = 0, b = 0;
    for (int i = 0; i < 1024; i += 2) { a += p[i]; b += p[i+1]; }
    return a + b;
}

Both touch 1024 elements. Both do roughly one arithmetic operation per element.

How to read for the critical path

A practical procedure for a hot loop:

  1. Find the loop body — the backward jump.
  2. For each instruction, ask which earlier result it needs. Draw the edges.
  3. Find the longest path through one iteration, and check whether it connects to the next iteration. If it does, that is your loop-carried dependency and your floor.
  4. Count the resource users — loads, stores, multiplies. If you need three loads per iteration and the core has two load ports, you are port-limited regardless of dependencies.
  5. Only then think about instruction count.

Step 3 is where the money is. And you do not have to do it by hand: llvm-mca does exactly this analysis and Compiler Explorer will show it in a pane beside your code. It reports the critical path, port pressure, and an estimated throughput.

With the caveat from Setup: llvm-mca models the core and knows nothing about caches or branch prediction. It will confidently analyse a loop whose real cost is entirely memory stalls. Use it for arithmetic-bound code and distrust it elsewhere.

Explain it to yourselfwrite it — thinking it does not count

Explain to a colleague why these two facts are both true and not in conflict:

  1. Adding more instructions to a loop can make it faster.
  2. The CPU still executes your program exactly as written.

Two or three sentences. You need the words dependency and retire.

What you should now be able to say

  • Instructions execute when their inputs are ready, not in listed order; they retire in order, which preserves the illusion.
  • A listing is a dependency graph; the edges determine time, the order does not.
  • Independent work overlaps and is nearly free; dependent work serialises.
  • A loop-carried dependency defeats out-of-order execution and vectorisation, and no flag fixes it.
  • The floor is the critical path plus resource limits (ports), not the instruction count.
  • llvm-mca computes this for you, and ignores memory.

Next: 4.2 — Latency versus throughput.

In the wildgo read the real thing now, while it is fresh
  • llvm-mca in Compiler ExplorerAdd an "llvm-mca" pane to any Godbolt session. It shows you the dependency graph and port usage that this lesson describes, for your actual code. It models the core and ignores caches — know that before believing its numbers.
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.