C, Ocean Edition I

Annex K. Deterministic Translation

Annex
K
ISO C99 mapping
new
Status
Normative

K.0 The Requirement

Translating the same inputs with the same implementation shall produce byte-for-byte identical artifacts.

This is a conformance requirement, stated normatively in clause 4.10 and specified in detail here.

Everywhere else in this collection, determinism means that a program's behavior is determined by its source. This annex extends that idea one step earlier, to the translation itself. A specification that fixes evaluation order, removes indeterminate values, and defines overflow has taken the position that a programmer should be able to predict what their program does. Leaving the compiler free to emit different bytes on Tuesday than it emitted on Monday would abandon that position at the last moment.

The obligation is on the implementation, not on the program. No source change is required to obtain it, there is no flag that turns it on, and a program cannot opt out of it.

The obligation is also narrower than it first appears. It fixes the output of translation, not the conduct of it. An implementation remains free to use the host it runs on as hard as it can, and clause K.2.3 says so in terms. What it may not do is let the host it happened to run on show up in the result.


K.0.1 Why This Is a Requirement Rather Than a Recommendation

The verification argument for reproducible builds is well known, and it is not the main reason this annex exists. The main reason is that without it, a programmer cannot measure their own program.

Consider the ordinary case. A programmer benchmarks a function, changes it, and benchmarks it again. If the implementation is free to make different optimization decisions between the two builds, the difference in the measurement contains two contributions that cannot be separated: the effect of the change, and the effect of the compiler having chosen differently this time. Rebuilding without changing anything can move a benchmark by a noticeable amount, which means the programmer is measuring the compiler's mood alongside their own work.

This failure is common in practice, and it is usually traced to something small. An inlining heuristic consults a container whose iteration order depends on allocation addresses. A pass gives up on a large function after a time budget that expires at a different point on a loaded machine. A partitioning step for parallel code generation splits differently depending on how many threads were available. None of these changes what the program computes, and every one of them changes how fast it runs.

The consequence is that performance work becomes guesswork. A programmer who cannot reproduce a build cannot attribute a regression, cannot bisect for the commit that caused it, and cannot tell a real improvement from noise without running the experiment enough times to average out an effect that should never have been there.

Under this annex the situation is different. Identical inputs produce identical bytes, identical bytes have identical performance, and any difference a benchmark reports is therefore attributable to something the programmer actually changed. That is the property that makes optimization work possible at all, and it is why clause 4.10 states this as a requirement rather than leaving it to the quality of an implementation.

The same reasoning appears elsewhere in this collection. Clause 4.7 forbids an implementation from deriving facts that make a program's behavior depend on the optimizer's reasoning. This annex forbids an implementation from letting a program's performance depend on the optimizer's circumstances. Both say that the machine should answer to the program.


K.0.2 Scope of the Obligation

This annex binds the implementation, not the program, and it binds it only where an internal choice reaches an artifact. Clause 1.2 leaves compiler architecture unspecified, and that remains true: an implementation may use any data structures, any passes, and any intermediate representation it likes, provided that two translations of the same inputs emit the same bytes.

Where this annex names an internal technique, as clause K.9 does, it names the effect that is prohibited rather than the design that is required. Clause K.13 collects the techniques that implementations have actually needed, and those are recommendations.


K.1 Terms

artifact

Any file the implementation produces from a translation or a link. This includes object files, static libraries and archives, shared libraries, executable images, debug information whether embedded or separate, dependency files, listing files, and any auxiliary output.

translation input closure

The complete set of inputs that determine an artifact. Clause K.3 defines it exhaustively.

reproducible

An artifact is reproducible when repeating the translation with an identical input closure yields an identical sequence of bytes.

build configuration

The portion of the input closure that is not source text: the options selected, the target ABI, the edition and profile, the search paths as they resolve, and the declared timestamp of clause K.5.


K.2 The Guarantee

Given two translations that agree on every element of the translation input closure of clause K.3, a conforming implementation shall produce artifacts that compare equal byte for byte.

The guarantee holds across all of the following, and an implementation shall not claim it while excepting any of them:

Note. The last item rules out a common failure: an optimizer that gives up on a function after a time budget or a memory budget expires produces different code on a loaded machine than on an idle one. Budgets shall be counted in units the implementation controls, rather than in seconds or in bytes of host memory available. See clause K.9.2.

The qualification on that item matters, and clause K.2.1 states it generally. An implementation is always free to fail. What it may not do is succeed twice with different results.


K.2.1 Translation May Fail

This annex constrains what an implementation emits, never whether it succeeds in emitting anything.

An implementation may refuse to translate for any reason it documents, including exhaustion of a budget under clause K.9.2, exhaustion of a host resource, or an internal limit under Annex B clause B.0. A translation that fails produces no artifact, and an obligation about the bytes of an artifact does not apply where there is no artifact.

The requirement is therefore one-sided:

succeeds twice   the artifacts shall be identical
fails twice      no obligation on the diagnostics beyond clause K.11
succeeds, fails  permitted

The last line is the one that resolves the apparent conflict between reproducibility and reliable building. A translation that runs out of memory on a small machine and completes on a large one is conforming. A translation that completes on both and emits different bytes is not.

Recommended practice. A build that can fail on one machine and succeed on another is unpleasant even though it conforms, so an implementation should make its internal limits depend on the input rather than on the machine wherever it can. A limit expressed in nodes fails identically everywhere, which turns a machine-dependent failure into a reported one. Clause K.9.2 gives the rule for optimization budgets, and the same reasoning applies to any other limit an implementation imposes.

Resource exhaustion. Running out of memory, disk, or file descriptors is a failure of the host rather than a property of the program, and it shall be reported as a diagnostic. An implementation shall not respond to resource pressure by emitting a smaller, cheaper, or differently optimized artifact, because that is precisely the substitution this annex exists to prevent.


K.2.2 Implementations That Emit No Artifact

An interpreter and a just-in-time compiler produce no persistent artifact, so clauses K.3 through K.12 have nothing to bind.

Such an implementation is subject to the rest of this specification in full, and remains free to adapt its translation to observed execution, to available memory, and to elapsed time, because the results of doing so are not artifacts that anyone will later attempt to reproduce.

Where such an implementation does write a persistent artifact, such as a cached compilation, this annex applies to that artifact.


K.2.3 Best Effort on the Host

Effort is best-effort. Results are not.

An implementation should use the host it runs on as effectively as it can. Nothing in this annex asks an implementation to be slow, to be single-threaded, to ignore available memory, or to pretend it is running on a machine other than the one it was given. The requirement governs the bytes that come out, and it says nothing about how hard the implementation works to produce them.

The separation is this:

Adapts to the host Determined by the input
how long translation takes the artifact
how much memory is used the diagnostics produced
how many threads or processes are used whether a budget is exhausted
the internal order in which work is scheduled the response to exhausting one
what is cached and what is recomputed whether translation succeeds on a given host, subject to clause K.2.1
which internal algorithm is selected
which host instructions the implementation itself uses

An implementation is encouraged to:

Every one of these is permitted precisely because none of them can reach an artifact. A lexer that hashes identifiers with a host vector instruction produces the same token stream as one that does not, and it is free to be faster.

Best effort at completing.

An implementation should prefer completing a translation to failing one. Clause K.2.1 permits failure, and permission is not encouragement.

Where an implementation can finish by requesting more memory, by spilling to disk, by falling back to a slower algorithm that needs less space, or by giving up an optimization it was attempting, it should do so rather than report that the host was insufficient. Failure belongs at the end of that sequence rather than at the start of it.

Budget defaults.

A budget under clause K.9.2 exists to bound pathological input, not to ration ordinary work. Defaults should be set high enough that programs written by people do not reach them, so that a translation failing for want of budget is a rare event that indicates something unusual about the program.

An implementation that finds ordinary code exhausting its budgets has set them too low, and the correct response is to raise them rather than to measure them in seconds.

Deterministic diagnostics.

Diagnostics written to a file are artifacts under clause K.10 and are bound by this annex. Diagnostics written to a terminal are not, and an implementation should nevertheless produce them in a determined order with determined content, because programmers compare them between builds and because a diagnostic that appears only sometimes is worse than one that never appears at all.


K.3 The Translation Input Closure

An artifact shall be a function of exactly the following, and of nothing else.

K.3.1 What is in the closure

  1. The bytes of every source file and header the translation reads, including every file reached by inclusion and every file reached by import.
  2. The identity of the implementation, meaning its version and the version of every component that participates in translation, including plugins and code generation back ends.
  3. The selected target ABI, data model, and target features.
  4. The edition and any profile selected under clause 4.6.
  5. Every option supplied to the implementation, after the implementation's own normalization of them.
  6. Every macro defined on the command line, and its replacement list.
  7. The resolved path of every file the translation read, subject to the mapping of clause K.6.
  8. The declared translation timestamp of clause K.5, where the translation uses it.
  9. The contents of any profile data, link-time optimization index, or other translation-directing input, where such an input is supplied.
  10. The contents of every input artifact, for a link or an archive operation.

K.3.2 What shall not affect an artifact

An artifact shall not depend on any of the following. This list is a prohibition, not a set of things an implementation should be careful about.

Difference: ISO C99 says nothing about any of this, and permits an implementation to embed the wall clock in its output through __DATE__ and __TIME__ while requiring nothing about the rest.


K.4 Prohibited Constructs in Artifacts

An artifact shall not contain a value derived from a source in clause K.3.2. In particular, an implementation shall not embed:

Padding in artifacts. Every byte of every artifact shall be determined. Where a container format requires padding or reserved space, the implementation shall write a fixed value, and shall document it. Writing whatever happened to be in a buffer is the most common cause of a build that differs in three bytes for no visible reason.

Build identifiers. Where an artifact format carries a build identifier, the implementation shall compute it as a function of the artifact contents, such as a cryptographic digest, rather than generating it randomly. A content-derived identifier serves the same purpose and is reproducible.


K.5 Time

__DATE__ and __TIME__ cannot report the wall clock and also satisfy clause K.2. This clause resolves that directly.

K.5.1 The declared translation timestamp

Every translation has a declared translation timestamp, which is part of the build configuration and therefore part of the input closure.

An implementation shall determine it as follows, taking the first that applies:

  1. A timestamp supplied explicitly through an option the implementation documents.
  2. The value of the SOURCE_DATE_EPOCH environment variable, when it is present and holds a decimal count of seconds since the epoch of 1970-01-01T00:00:00Z. A value that is present but malformed is a translation error rather than a value to be ignored.
  3. The implementation's documented default.

The default shall be a fixed value, and shall not be the current time. An implementation should default to the epoch itself, so that a translation performed with no configuration is reproducible without the programmer having to know that this clause exists.

Adopted from settled practice. SOURCE_DATE_EPOCH is not specified by any C standard and is honored by a large part of the software distribution world. It passes the test of clause 1.4.1: it removes a category, since a build timestamp stops being an unpredictable input and becomes a declared one, and it adds no syntax. See clause 2.4.

K.5.2 __DATE__ and __TIME__

__DATE__ and __TIME__ expand to the declared translation timestamp, rendered in UTC, in the formats clause 6.10.8.1 gives.

They therefore remain available, remain the same spelling, and remain useful for recording when a release was built. What changes is that the recorded value is an input the build declares rather than a reading the compiler takes.

Difference: ISO C99 defines these as the date and time of translation, which is inherently unreproducible. The construct is retained and its source is redefined, which is a smaller change than removing it and serves the same programs.

Diagnostics. An implementation should diagnose a use of __DATE__ or __TIME__ when the declared timestamp came from the implementation's default rather than from an explicit configuration, since the program is recording a value the build did not choose.

K.5.3 Time available to the implementation

An implementation may read the clock for progress reporting, for diagnostics, and for its own logs. It shall not allow a value so obtained to reach an artifact, and shall not allow one to influence a translation decision.


K.6 Paths

Absolute paths are the second common cause of irreproducibility, because they carry the working directory and the user's home directory into debug information and into __FILE__.

K.6.1 Path mapping

An implementation shall provide a documented mechanism that maps a path prefix to a replacement prefix, applied to every path an artifact records, including those in __FILE__, in debug information, and in diagnostics where the implementation writes them to a file.

K.6.2 Recorded paths

A path recorded in an artifact shall be one of:

An implementation shall not record a path that depends on the working directory unless the working directory is a declared part of the build configuration.

K.6.3 __FILE__

__FILE__ expands to a string literal naming the current source file, as recorded under clause K.6.2. Its value shall not depend on the working directory unless that directory was declared.

An implementation should make __FILE__ relative to the root of the translation by default, because that is both reproducible and the value a programmer reading a diagnostic wants.

K.6.4 Path separators

A path recorded in an artifact shall use one separator convention, which the implementation shall document, regardless of the host convention. A build on a host using backslashes and a build on a host using forward slashes shall record the same bytes.


K.7 Host Independence

Translating for a given target shall produce identical artifacts regardless of the host the implementation runs on.

This is the requirement that makes independent verification possible. A guarantee that only holds on one machine cannot be checked by anyone who does not have that machine.

An implementation shall therefore:

Note. The last two items are why clause K.3.2 forbids locale dependence. A compiler that lowercases an identifier with a host function will produce different output under a Turkish locale, which is a defect that has occurred in practice more than once.


K.8 Parallelism and Incrementality

K.8.1 Parallel translation

Translation may be performed in parallel, and an implementation should parallelize it as far as the host allows, under clause K.2.3. The result shall not depend on the degree of parallelism or on the order in which parallel work completes.

Where results are collected from parallel work, the implementation shall order them by a key derived from the input rather than by completion order.

K.8.2 Incremental translation and caching

An incremental rebuild, a cached result, and a clean build shall produce identical artifacts for the same input closure.

A cache shall be keyed on the complete input closure of clause K.3.1. A cache that omits an element of the closure will eventually return a result that a clean build would not have produced, and an implementation that ships such a cache does not conform.

K.8.3 Distributed translation

Where translation is distributed across machines, clause K.7 applies to every participating machine, and the result shall not reveal which machine performed which part.


K.9 Determinism of Optimization

Optimization is where nondeterminism most often enters, because optimizers use hash tables, priority queues, work lists, and budgets. It is also where nondeterminism does the most damage, for the reasons clause K.0.1 gives.

K.9.0 Optimization decisions

Every optimization decision shall be a function of the translation input closure.

This is stronger than requiring that the emitted bytes match, and it is stated separately because an implementation can satisfy the letter of clause K.2 while still deciding differently in ways that eventually escape.

The decisions bound by this clause include which calls are inlined, which loops are unrolled, which loops are vectorized and by what factor, which functions are cloned or specialized, how registers are allocated, how instructions are scheduled, how basic blocks are laid out and aligned, which functions are placed where in a section, and which transformations are attempted at all.

A heuristic driving any of these shall take its inputs from:

A heuristic shall not take an input from anything clause K.3.2 excludes. In particular it shall not consult a host address, the iteration order of an unordered container, elapsed time, available memory, system load, the number of threads, the order in which parallel work completed, or a random value.

Two translations of the same function with the same closure shall reach the same decisions, whatever else is being translated alongside it and whatever the machine is doing at the time.

Note. The practical test is simple to apply. If a decision would come out differently on a busy machine, on a machine with more cores, or on a second run on the same machine, the heuristic is consulting something it should not.

K.9.1 Ordering

An implementation shall not allow iteration order over an unordered container to affect an artifact.

Where an order is needed, it shall be derived from the program: from source order, from declaration order, from an assigned sequence number, or from a name compared by the implementation's own comparison. It shall not be derived from a hash of a host pointer, from an allocation address, or from insertion order that itself depended on an address.

K.9.2 Budgets

An implementation may bound any analysis or transformation. Bounding work is necessary, and this annex does not ask an implementation to run an expensive pass to completion on a pathological input.

The unit. A budget shall be counted in units the implementation controls, such as nodes visited, instructions considered, iterations performed, or bytes allocated from an arena the implementation manages. It shall not be counted in elapsed time, in host memory remaining, in system load, or in any other measurement of the machine the implementation happens to be running on.

The response. When a budget is exhausted, the implementation shall do one of the following, and which one it does shall itself be determined by the input:

An implementation shall not keep whatever partial work it managed to complete before the budget ran out, because the amount completed is what makes the result vary.

Adjusting a budget. A budget is part of the build configuration, and therefore part of the input closure under clause K.3.1 item 5. An implementation should let a programmer raise a budget explicitly. A build that fails on a large function is then fixed by declaring a larger budget, which is recorded and reproduces everywhere, rather than by moving the build to a faster machine, which is not.

Design note. A wall-clock budget appears to buy reliability and does not. A pass that stops after ten seconds performs different work on a loaded machine than on an idle one, so both builds succeed and the two programs differ. The failure is silent, it is unattributable, and it is discovered by a user rather than by the build. A work budget converts that into a consistent outcome: a function that is too complex is too complex on every machine, and the implementation says so.

The reliability argument is real, and clause K.2.1 is where it is answered. An implementation may always decline to produce an artifact. It may never produce a different one.

K.9.3 Translation-time arithmetic

Constant folding and any other translation-time evaluation shall use the target's arithmetic, computed by the implementation, and shall not use the host's.

A floating expression folded at translation time shall produce the same value on every host, and shall agree with what the same expression would produce at execution time on the target, subject to the rounding rule of clause 6.6.3.

K.9.4 Profile-directed optimization

Where an implementation consumes profile data, that data is part of the input closure under clause K.3.1 item 9, and the same profile shall produce the same artifact.

An implementation shall not consume a profile that it also generates during the same translation in a way that makes the result depend on execution timing.


K.10 Artifacts

The requirement applies to every artifact, not only to the final executable. A program that reproduces only at the end is not reproducible, because the intermediate results cannot be checked or cached correctly.

Artifact Requirement
Object file reproducible, including symbol order, section order, and relocation order
Static library or archive reproducible, including member order; member timestamps, owner, group, and mode shall be fixed values
Shared library reproducible, including the symbol table and any build identifier
Executable image reproducible, including layout and any embedded identifier
Debug information reproducible, whether embedded or in a separate file
Dependency file reproducible, including the order in which dependencies are listed
Listing, map, and diagnostic files written by the implementation reproducible

Symbol and section ordering. Symbols, sections, and relocations shall appear in an order determined by the program, and an implementation shall document the rule it uses.

Linkage names. The linkage name of a namespace member is fixed by clause 6.9.6.4 and is therefore a function of the source, not of the implementation. Two implementations translating the same source produce the same external names, which is a stronger property than this annex requires and is what makes separate compilation across implementations possible.

Linking. Where the implementation performs the link, this annex binds the linked artifact. Where the implementation invokes a linker it does not supply, it shall document that fact and shall name the linker as part of the input closure. An implementation should supply or require a linker that provides the same guarantee, since a reproducible object file followed by an unreproducible link leaves the program unverifiable.


K.11 Tooling Obligations

A promise that cannot be checked is not a promise. An implementation shall provide the means to check this one.

K.11.1 Recording the input closure

An implementation shall be able to emit, for any artifact it produces, a record of the translation input closure sufficient to repeat the translation.

The record shall identify the implementation and its version, the target ABI and data model, the normalized options, the command-line macro definitions, the declared translation timestamp, the path mapping in effect, every environment variable the implementation treated as configuration, and a digest of every source file and header read.

The record shall be reproducible under this annex, so that two builds produce identical records.

K.11.2 Verification mode

An implementation shall provide a documented mode that performs a translation twice and reports whether the artifacts are identical.

The two translations shall differ in every respect this annex says must not matter. An implementation should vary the working directory, the number of threads, the environment, the locale, the system time, and the temporary directory between the two runs, since a verification that repeats the build identically checks almost nothing.

Where the artifacts differ, the mode shall report the offset of the first difference and should identify the region of the artifact containing it.

K.11.3 Artifact digest

An implementation shall be able to report a digest of any artifact it produces, computed by a documented algorithm, so that a third party can compare a rebuild against a published value without possessing the original artifact.

K.11.4 Reporting a violation

Where an implementation detects that it has produced a nonreproducible artifact, it shall report a diagnostic and shall identify the artifact. Silently producing output that violates this annex is not conforming.


K.12 Permitted Exceptions

This annex admits exceptions narrowly, and every one of them shall be documented.

An implementation may produce a nonreproducible artifact only when all of the following hold:

An implementation shall not claim an exception for an internal cause. A hash table whose order varies, a buffer whose padding is uninitialized, and an optimizer whose budget is measured in seconds are defects rather than exceptions.

An implementation shall not offer a mode that disables this annex while still claiming Ocean Edition I conformance, under clause 4.8.1.


K.13 Recommended Practice

The following are not requirements. They are the measures that implementations achieving this in practice have found necessary.

Data structures. Replace hash containers keyed on pointers with containers keyed on an assigned index, or sort before iterating. This single change resolves the majority of reproducibility defects in a compiler.

Sorting. Use a stable sort, or make the comparison a total order over the keys actually present. Clause 7.2.18 notes that qsort is not required to be stable, and an implementation of this specification is subject to its own advice.

Buffers. Zero every buffer that becomes part of an artifact before writing structured data into it. Clause 6.7.9.2 gives every object in an Ocean Edition I program that property already, which is a convenient argument for writing the compiler in the language it compiles.

Uninitialized reads. Run the implementation under a memory checker. A read of uninitialized storage inside a compiler is the most common source of a build that differs once in several hundred runs, and it is nearly impossible to find by rebuilding.

Testing. Include reproducibility in the test suite rather than checking it before a release. Verify each artifact kind, and vary the conditions listed in clause K.11.2. A reproducibility guarantee regresses quietly, and it regresses in the commit that adds a hash table.

Bootstrapping. An implementation that can translate itself should verify that translating itself twice produces identical output, and that an implementation built from source reproduces the published binary. That check exercises the whole system on a large input and is the strongest single test of this annex available.


K.14 Reproducibility and Stability

Two properties are easy to confuse, and only one of them is required.

Reproducibility is what this annex requires. The same translation input closure produces the same artifact. Rebuilding changes nothing.

Stability is the property that a small change to the input produces a small change to the artifact. It is not required, and it cannot be guaranteed in general.

K.14.1 Why stability cannot be guaranteed

Adding a function to a translation unit may shift the placement of every function after it. New placement changes instruction cache behavior, branch predictor aliasing, and page boundaries, and any of those can move a measurement on code that was not edited. Changing a function's size may push a loop across an alignment boundary. Adding a call may change what a caller's register allocator has to spill.

None of this is an implementation being careless. It is what happens when code occupies storage, and no compiler can promise otherwise while still laying its output out sensibly.

K.14.2 What this means for measurement

The distinction is worth keeping straight, because the two failures look identical in a benchmark and have different remedies.

Observation Cause Remedy
The same source, rebuilt, benchmarks differently irreproducibility prohibited by clause K.2; report it as a defect
Edited source benchmarks differently, including in code not edited instability inherent; measure the whole program rather than the edited function alone

Clause K.11.3 makes the first case decidable rather than a matter of suspicion. A benchmark harness can record the artifact digest alongside each result, and two results carrying the same digest were produced by identical code. Any difference between them came from the measurement, from the machine, or from the harness, and none of it came from the compiler. That check costs almost nothing and removes an entire category of doubt from performance work.

K.14.3 Recommended practice

An implementation cannot deliver stability, and it can avoid making the situation worse than it needs to be.

Keep decisions local. A decision about a function should depend on that function, on its callees, and on its callers. It should not depend on how many unrelated functions the translation unit contains, on their names, or on the order in which they were declared, beyond the extent that placement requires.

Do not key a heuristic on a whole-module digest. A threshold derived from a hash of the entire translation unit makes every decision in the unit sensitive to every edit anywhere in it. The result is reproducible and needlessly unstable.

Derive alignment from local properties. Where a loop or a function is aligned, base the decision on that loop or function rather than on its absolute offset, so that an edit elsewhere does not silently unalign it.

Document the coupling that remains. An implementation should describe which kinds of source change can perturb code that was not edited, so that a programmer reading an unexpected benchmark result knows where to look.

Report layout changes. An implementation should provide a way to determine that the placement of a function changed between two builds even though its code did not. A benchmark harness can then distinguish a real regression from a relocation, which is otherwise among the most frustrating results to investigate.