O0 and debugging

Preservation of source computations, constant expressions, storage, and debug correspondence.

Wave Foundation

Preservation model

O0 preserves the original typed IR's computations, variables, and control flow for debugging. Unused results and structurally unreachable blocks remain present. Verification diagnoses invalid IR without deleting blocks or simplifying operations.

Required machine transformations take place in a separate lower IR. The mapping to original IDs must survive those transformations. Preserving typed IR does not require every IR operation to correspond to exactly one machine instruction.

Transformations excluded at O0

Transformation O0 behavior
Inlining Keep the call and function boundary
Tail-call transformation Keep the ordinary call/return structure
Dead-code elimination Retain unused computations and unreachable blocks
Runtime constant folding Keep the original operation
Common-subexpression elimination Keep distinct computations
Local-variable storage reuse Keep separate local storage
Frame-pointer omission Retain the frame pointer
Automatic string merging Keep distinct string objects

For example, a runtime addition of two constant operands remains an addition even if the result is unused. A branch with a constant condition retains its original control-flow structure.

Compile-time constants

A compile-time constant declaration retains both its typed initializer expression and its evaluated result. This is different from folding an ordinary runtime instruction.

For a declaration whose initializer is 1 + 2, the stored information includes the addition expression and the result 3. This example describes the expression, not source-language declaration syntax. The result can be used to construct static data without turning the initializer into runtime arithmetic.

Unused and unreachable declarations remain identifiable. Names, declaration IDs, and references must continue to distinguish shadowed declarations. Verification rejects invalid references, dependency cycles, invalid types, and a cached result that disagrees with its expression.

Unreachable source statements

Statements following return, break, or continue remain represented in disconnected blocks. Their presence must not change the preceding terminator or create a new executable path. Invalid expressions in unreachable statements still produce diagnostics.

Retaining such statements is useful for inspecting the original program structure. It does not cause them to execute after the terminator.

Source and stack information

The debug interface uses DWARF 5 for function records, source lines, basic local variables, and call-frame information. Function and local-variable information must remain associated with original IR identities through lowering.

The AMD64 profile retains frame pointers and does not use the red zone. Call-frame information supports stack inspection; it does not imply support for exception unwinding. Traps terminate execution without guaranteeing destructors or unwinding.

DWARF emission and native execution availability are listed in the toolchain overview. O1 and higher optimization behavior is outside this O0 reference.