Skip to content

Codegen Patterns Od

riyu edited this page Nov 4, 2026 · 1 revision

Codegen Patterns: /Od and expression shape

These observations apply to the noted compiler modes. /Od preserves many source-level choices, so temporary values, argument order, and even empty branches can be visible in the output.

Call result passed directly

Symptom: Before a call, a value is copied from one register to another (mov ecx,[slot]; mov edx,ecx). Likely source: a call result is passed inline as an argument, instead of first being assigned to a named local.

consume(reader.read());

A named local often produces a direct reload for the argument:

int value = reader.read();
consume(value);

Float computed before object expression

Symptom: A float argument is spilled, then loaded immediately before the call (movss xmm0,[slot]; movaps xmm1,xmm0). Likely source: the float rvalue was written inline in the call rather than saved in a named local.

object->set(static_cast<float>(compute(other())));

Float comparison operand order

Symptom: The executable loads A, compares against B (comiss xmm0,[B]), then branches with jae. Likely source: an expression shaped like B > A may produce that operand order under MSVC 19.16; syntactically similar comparisons can reverse loads and select different conditions.

float result = (b > a) ? b : a;

Try source spellings against the actual compiler rather than inferring solely from the branch mnemonic.

By-value argument copies

Symptom: A 12-byte structure copy uses rep movsb, sometimes with the source pointer still in rax. Likely source: the callee takes a small structure by value; if the copy comes straight from the result register, the call may be nested directly in the argument list.

consume(makePoint());

A named temporary changes stack-slot placement and may change the emitted sequence:

auto point = makePoint();
consume(point);

Offset-zero member passed as an object

Symptom: The code loads an object pointer, moves it through a register, then calls a helper; a base-class view would pass this directly. Likely source: the helper operates on the object's first member, not on the object itself.

struct View { Buffer first; };
helper(&reinterpret_cast<View*>(object)->first);

Empty switch cases

Symptom: A switch dispatch is followed by extra jmp end instructions before the first real case body. Likely source: empty case N: break; labels before the first real case body. In the observed older compiler, the dispatch can omit comparisons for cases targeting the end while retaining their body jumps.

switch (code) {
case 1: break;
case 2: break;
case 3: handle(); break;
}

Implicit conversions and temporary lifetimes

Symptom: Extra copies, argument slots, or cleanup calls appear despite a seemingly simple expression. Likely source: an implicit conversion, a by-value parameter, or a full-expression temporary. Preserve the original expression shape and scope when testing a match; replacing it with a named local can change both copy elision and lifetime.

consume(makeValue()); // Preserve the direct rvalue form when evidence supports it.

Clone this wiki locally