Skip to content

Codegen Patterns O2

riyu edited this page Nov 4, 2026 · 1 revision

Codegen Patterns: /O2

Optimization makes control-flow shape and source ordering visible in less intuitive ways. A small rewrite can affect register pressure and code layout well before the statement that changed.

Shared failure label and prologue saves

Symptom: The executable pushes all callee-saved registers in the prologue, while a candidate delays some pushes until after early-return checks. Likely source: the original funnels failures through a shared label and exit rather than using separate returns.

if (!first) goto fail;
if (!second) goto fail;
return 1;
fail:
return 0;

Loop body uses nested branches

Symptom: Loop-head alignment nops or register choices differ, and the candidate uses early continue statements. Likely source: the original wraps work in nested if/else blocks, possibly with an empty branch, changing the control-flow graph and cleanup joins.

for (auto item : items) {
    if (item.matches()) {
        process(item);
    } else {
        recordMiss(item);
    }
}

Store order around a loop

Symptom: The executable uses inc [member] inside the loop, while the candidate keeps the counter in a register and stores it afterward. Tail instructions may also appear in another order. Likely source: source statement ordering after the loop can change scheduling and register allocation.

while (more()) { ++count; }
kind = nextKind;
++column;
++length;

Try plausible statement orders while preserving behavior.

Unguarded indexed write and range check

Symptom: cmp index,bound; jae range_check; ... mov byte [buffer+index],0 appears before an indexed byte store. Likely source: a plain indexed write without an explicit bounds guard; compiler security checks add the range check.

buffer[length] = '\0';

A branchy clamp may also result when an unsigned size is widened into another integer type through an explicit if/else assignment:

unsigned size = source.size();
std::size_t length;
if (size < limit) length = size;
else length = limit - 1;

Adjacent byte clears

Symptom: The executable writes an immediate zero word while a candidate writes a zero register. Likely source: two adjacent byte assignments; the compiler can merge them late into a word store.

bytes[offset] = 0;
bytes[offset + 1] = 0;

Field reloaded at each use

Symptom: The executable reloads a member pointer after each store or call, while a candidate caches it in a register. Likely source: the original expression reads the field anew at each use, perhaps through a helper or lambda.

auto current = [&] { return object->member; };
current()->write();
current()->finish();

Clone this wiki locally