SPEC-09P.1, edition 1. Published under the Apache License 2.0 with LLVM Exceptions, as in the cint repository. Corrections that change no meaning are listed in the errata. Fixed address: /specifications/spec-09p/1/.
SPEC-09P.1: Toolchain and conformance
SPEC-09P.1, edition 1 of the toolchain and conformance specification, cint v0.2.0, 2026-10-11.
Foreword
This document specifies how the cint toolchain of cint 0.2.0 is built and checked: the intermediate representation that its compiler produces, the C that the compiler emits and the runtime that C calls, what two implementations of cint must agree on, the conformance suite that measures that agreement, the bootstrap of the compiler from a hand-written C program, and the receipts from which a reader reruns a published figure.
Each rule ends with one or more clause identifiers in brackets, such as [SIR-09] or [EMIT-03]. An identifier names one rule across editions: a rule keeps its identifier when its text moves to another section or another document, and an identifier is not reused. Code comments, test files and receipts cite rules by these identifiers. Section numbers belong to this edition only.
Introduction
cint has one specification of meaning, SPEC-04P.2, and several implementations of it. A program compiled to C, the same program run by the interpreter, and a program whose kernels run on a GPU must give the same output bytes, the same fault record and the same count of fuel. A separate implementation written in Python, the reference, runs each program from its source text and gives the expected results. This document says how these implementations are built, what they must agree on, and how that agreement is measured and recorded.
A small program shows what agreement means. It prints a sum of squares and then narrows the sum to a type too small for it.
```cint agree.ci I64 total = 0; for i in 1..=10 { total += i * i; } "{total}\n"; I8 small = total as I8; "{small}\n";
```text
385
fault[E_NARROW]: the value does not fit the destination type
--> agree.ci:6:18
|
6 | I8 small = total as I8;
| ^ as.checked.i64.i8
|
= operation: as.checked.i64.i8
= operand-count: 1
= operand: I64 385
= exact: 385
= limit: I8 127
...
cint run compiled the program to C, built it and ran it. The reference shares no code with the compiler (5.4). It reads the same source and prints the outcome as the text that the conformance suite stores (9.3):
$ python3 -m cint_ref run agree.ci
case agree
source reference
outcome fault
stdout-bytes 4
stdout-sha256 5476dba1f4bd59f1444ca1fd0770c1db808311b772f7685b3ff60ad7956418a8
fuel-consumed 11
fault.code E_NARROW
fault.operation as.checked.i64.i8
fault.operand I64 385
fault.exact 385
fault.limit I8 127
fault.position agree.ci:6:18
fault.revision self
fault.address none
fault.stack-depth 0
The two agree. Both wrote the same 4 bytes, 385 and a line feed, whose SHA-256 digest is the stdout-sha256 line. Both stopped at the conversion at agree.ci:6:18, with the same operation, operand, exact result and limit. Both consumed 11 units of fuel: 1 for the entry and 1 for each of the 10 loop iterations. The line fault.revision self stands for the revision of the program under test, which the reference does not compute (6.5).
How this document is organized
Clauses 1 to 4 state what the document covers, the documents it relies on, its terms and what conformance means. Clause 5 describes the architecture: one front end, one intermediate representation, three executors and the reference. Clause 6 defines that intermediate representation, SIR, and its text form. Clause 7 defines the C that the compiler emits and the runtime contract it relies on. Clause 8 says what two implementations must agree on and how a case that is not compared is reported. Clause 9 describes the conformance suite and how to run one case. Clause 10 describes the bootstrap of the compiler and what its self-hosting fixpoint shows and does not show. Clause 11 describes receipts and how a reader reruns a published figure. Clause 12 lists the constructs that the tools of cint 0.2.0 refuse. Clause 13 names two future directions. Annex A gives the measured figures of the release, and the Bibliography lists the works cited for information.
Every example in this document was run with the tools of cint 0.2.0. A cint block is a complete program, and the text block after it is its output exactly as cint run prints it. A console block is a tool session: a line that begins $ is a command, the lines after it are its output exactly as printed, and a line ... stands for lines left out. Commands that name a file of the source tree, such as tools/cint_check.py, are run from the root of the tree. The others are run from a directory that holds the example's files. In every session the tree's ref/ directory is on PYTHONPATH, so that python3 -m cint_ref runs the reference, and the cint command is on PATH. Digests in the output change when the sources change, so the text says what each digest is, not its value.
1 Scope
- This document specifies, for cint 0.2.0:
- the architecture of the toolchain: its front end, its intermediate representation, its executors and the reference;
- the semantic intermediate representation (SIR), its text form and the revision identity computed from it;
- the C that the compiler emits, and the runtime contract that this C relies on;
- what two implementations must agree on, and how a refusal or a disagreement is reported;
- the conformance suite: case files, the
.expectformat, operation fixtures, required case lists, and how one case is run; - the bootstrap of the compiler and its self-hosting fixpoint; - receipts, and how a reader reruns a published figure; - the constructs that the tools of cint 0.2.0 refuse. - This document does not specify: - the language, which SPEC-04P.2 specifies; - the byte encodings of fault records and program state, which SPEC-01P.2 specifies; - how an executor carries out a kernel dispatch, which SPEC-02P.1 specifies; - the C and Python interfaces through which a host program calls cint code, which SPEC-03P.1 specifies; - the commands of the cint tools, which SPEC-06P.1 specifies.
2 Normative references
The following documents are referred to in the text in such a way that some or all of their content constitutes requirements of this document. For dated references, only the edition cited applies.
- ISO/IEC 9899:2018, Information technology: Programming languages: C (C17). Used for the language of emitted C and for the behaviors that clause 7 avoids.
- D. Crocker, editor and P. Overell. Augmented BNF for Syntax Specifications: ABNF. RFC 5234 (STD 68), January 2008. Used for the grammar of 9.3.
- P. Kyzivat. Case-Sensitive String Support in ABNF. RFC 7405, December 2014. Used for the
%s"..."strings of 9.3. - A. Rundgren, B. Jordan and S. Erdtman. JSON Canonicalization Scheme (JCS). RFC 8785, June 2020. Used for the bytes of a receipt, with the departure in the escaping of strings that 11.2 states.
- T. Bray, editor. The I-JSON Message Format. RFC 7493, March 2015. Used for the integer range of a receipt.
- National Institute of Standards and Technology. Secure Hash Standard (SHS). FIPS PUB 180-4, August 2015. Used for SHA-256.
- F. Yergeau. UTF-8, a transformation format of ISO 10646. RFC 3629 (STD 63), November 2003.
- The Unicode Consortium. The Unicode Standard, Version 17.0.0, 2025. Used for the term Unicode scalar value (8.1).
3 Terms, definitions and symbols
The terms of SPEC-04P.2 clause 3 apply, among them diagnostic, refusal, implementation, the reference, executor, entry, fuel, semantic input and revision. The following terms are added. Other terms are defined where they first appear, in italic type.
3.1 toolchain: the programs of cint 0.2.0 that translate, run and check cint programs: the compiler cintc, the cint command and its subcommands, the interpreter cint-interp, the runtime and the conformance tools under tools/ and conformance/tools/.
3.2 front end: the part of the compiler that reads source text, checks it and produces SIR.
3.3 SIR: the semantic intermediate representation, the form of a checked program that every executor of the toolchain runs (clause 6).
3.4 node: one operation of SIR, such as one checked addition or one call.
3.5 site: a node that can fault, identified by its module and its ordinal within the module, with the source position and the operation identifier that a fault record reports for it.
3.6 emitted C: the C17 source text that the compiler writes for a program.
3.7 runtime: the C code declared in rt/cint_rt.h and rt/cint_rt_host.h and built as the library cint_rt, which emitted C and the interpreter cint-interp call for checked arithmetic, fuel, call depth and fault records (SPEC-03P.1, 3.3). Its interface is the runtime contract of 7.10.
3.8 helper: a function of the runtime that performs one operation of one type and reports its fault, such as cint_add_i64.
3.9 capability: a set of SIR node families, forms and types that an executor carries out with their exact meaning. An executor's capability table lists, for every family, form and type, whether it is supported.
3.10 conformance case: a program, or one operation, together with its expected outcome.
3.11 expectation: the expected outcome of a conformance case, written in the .expect format of 9.3. A frozen expectation is one that is part of the suite and is not edited in place (9.1).
3.12 canonical outcome: what implementations compare for one entry: the output bytes, the kind of outcome, the returned value, the fuel consumed and the fault record, or, for a rejected program, the diagnostic code and position.
3.13 agreement: the relation between two implementations whose canonical outcomes for a program and the same semantic inputs are equal in the sense of 8.1.
3.14 receipt: a canonical JSON file (11.2) that records one run of a tool: its inputs, its outcome and what it observed. A bootstrap, a fixpoint run and a conformance run each write one, and so do the tools of SPEC-06P.1 that take --receipt (SPEC-06P.1 12).
3.15 receipt identity: the SHA-256 digest of the canonical bytes of a receipt's inputs and outcome (11.2). Two runs with the same receipt identity had the same inputs and the same outcome.
3.16 configuration: one choice of C toolchain, optimization level, helper mode (7.3) and sanitizers under which the toolchain and the programs it compiles are built.
3.17 compiler source identity: the SHA-256 digest of the source manifest of the compiler's source files (11.2).
3.18 bootstrap: the building of the compiler from its source text and a C toolchain, with no cint compiler built earlier.
3.19 fixpoint: the state in which the compiler, built from the C that it emitted for its own source, emits that same C again.
4 Conformance
- In this document, a rule is stated in the present tense, as a fact about a conforming implementation or tool, and ends with its clause identifiers (Foreword). "May" states a permission.
- Clauses 6 and 7 apply to the toolchain and to any other implementation that produces SIR or emits C for the runtime of 7.10. Clauses 8 to 11 apply to every implementation that claims agreement with the reference.
- A conformance run is evidence of agreement for the cases it runs, in the configurations it runs. It does not define the language: where an implementation and SPEC-04P.2 differ, SPEC-04P.2 governs, and the difference is a defect of the implementation or an ambiguity in the text.
- Clause 12, Annex A, the examples and the notes are informative.
5 Architecture
The toolchain turns a program's source text into one checked representation and runs that representation in several ways. Each way of running it is held to the same expected results, which a separately written implementation computes from the source text alone.
5.1 Overview
- The figure shows the parts and how outcomes are compared.
source text (.ci)
|
+--------------+---------------+
| |
cintc front end the reference
| (cint_ref, Python,
SIR reads the source itself)
| |
+---------+-----------+ |
| | | |
cpu-c17 cpu-sir-interp gpu-cuda |
emitted cint-interp cpu-c17 plus |
C and runs the SIR PTX for the |
runtime text work-item bodies |
| | | |
+---------+-----------+--------+---------+
|
canonical outcomes, compared (clause 8)
5.2 One front end
The compiler cintc is written in cint and is the only front end of the toolchain.
Semantics
- One front end produces the SIR of a program, and every path that runs or describes the program takes that SIR:
cint run,cint build,cint test,cint emit-c, the interpreter and the debugging toolscint-explainandcint-workbench. Arithmetic is carried out in these places only, each held to the conformance suite: the helpers of the runtime (7.10), the constant evaluation of the front end (held by the constant and run-time category of 9.4), each executor on its capability table, and the reference. The interpreter calls the runtime helper for every arithmetic, conversion and built-in node, the same helper that emitted C calls for that node, so it adds no arithmetic of its own [ARCH-01].
5.3 Executors
An executor runs the SIR of a program. cint 0.2.0 has three: cpu-c17, cpu-sir-interp and gpu-cuda.
Semantics
- The executor
cpu-c17runs a program as C17 that the compiler emits (clause 7), compiled by a C toolchain and linked with the runtime. It runs the work-items of a kernel dispatch one at a time, in canonical order, stages the outputs and publishes them only when the dispatch ends without a fault. cpu-c17is the reference implementation of the language on a CPU and the oracle against which other executors are compared. Where it and SPEC-04P.2 differ, SPEC-04P.2 governs (clause 4, paragraph 3). Every other executor is admitted by comparing its outcomes with those ofcpu-c17and of the reference on the conformance suite [BACK-03].- An executor that writes files is a module of
cintc, compiled into it. The compiler loads no executor code when it runs, because such code would lie outside the compiler source identity [BACK-01]. - The executor
cpu-sir-interpis the interpretercint-interp, a C17 program outsidecintc. It reads the SIR text thatcintcwrites for each module (6.2) and the program filecint-program.sitesthat lists the modules. Before it runs a program it checks the verifier rules it relies on (each temporary defined once and before it is used, the operand types, and a site on every node that can fault) and recomputes the revision identity (6.5); a text that fails these checks is reported with its position and is not run. It needs neithercintcnor a C compiler at run time. It calls the runtime helper for each arithmetic, conversion and built-in node and uses thecint_rtlibrary for contexts, fault records and output. It executes the structured control flow of SIR as written, keeps frames under the depth limit, counts local arrays against the frame-arena capacity in elements, charges fuel only where SIR charges it (6.1), and runs the work-items of a dispatch one at a time in canonical order, publishing outputs only on success [BACK-01a]. - The executor
gpu-cudais thecpu-c17build of a program together with device code for each kernel: PTX text written bycompiler/back_ptx.ciwith the directives.version 6.0and.target sm_52, embedded in the module's C file and loaded through the CUDA driver API. Sequential code, the entry checks of a dispatch, publication, the final fold of each reduction and the description of a fault by re-execution run on the host; only the work-item bodies run on the device. Every kernel of agpu-cudabuild runs on the device. A kernel that needs a capability thegpu-cudatable does not support fails the build withE_UNSUPPORTED(5.5) [BACK-16]. - The Python package of SPEC-03P.1 names an executor by its backend:
cpuforcpu-c17,cudaforgpu-cudaandrefforcpu-sir-interp.cint.loadbuilds and loads a library forcpu, its default or forcudaand raisesNotImplementedErrorforref, sincecpu-sir-interpruns no library;cint.replayruns a recording onref, its default or oncpu. Any other name is refused with an exception [BACK-15].
Notes
- SPEC-02P.1 specifies how each executor carries out a dispatch so that its outcome is the sequential one.
5.4 The reference
The reference cint_ref is a Python program that runs cint source directly. Its results are the expected results of the conformance suite.
Semantics
- The reference is written in Python with the standard library only. It reads
.cisource, parses it with its own parser, and executes it on Python integers, which have no fixed width, with an explicit range check after every operation that can overflow. It shares no code, tables, grammar files or generated files with the compiler or the runtime: it does not import, generate from or read any file ofcintc, of the bootstrap compiler (10.2) or ofrt/cint_rt.h. It enforces the call-depth limit itself, faultingE_DEPTH, so the recursion limit of Python is not reached first [ARCH-02, REF-01]. - The reference shares no code with the compiler, but it is not independent of it in a stronger sense. Both were written from the same specification text, so an ambiguity in the text can reach both. The check on such shared errors is the set of anchor cases (9.2), each derived by hand with its derivation written beside it [REF-02].
- The reference runs the work-items of a kernel dispatch one at a time in canonical order, stages the outputs and publishes them only on success. It reports each fault with the full fault record except the revision, which it does not compute [REF-03].
- Speed is not a requirement of the reference [REF-04].
- Anyone may run the reference directly on a program in its scope, from its command line or inside another Python runtime. A direct run is not a conformance run and writes no receipt. The reference does not define the meaning of a program: this specification does, and the reference is held to it [REF-08].
- The reference runs one
testblock of a module as its entry, in a fresh context, withpython3 -m cint_ref run FILE.ci --test NAME, whereNAMEis the test's name. The outcome of the test body gives the verdict. The reference has no command that runs every test of a module, so a caller names each test. For a test that both run, the outcome agrees with its outcome undercint testas 8.1 requires, so the verdicts agree [REF-07].
5.5 Capabilities and refusal
An executor that cannot carry out a node with its exact meaning refuses the program before it runs. It does not run the program with another meaning.
Semantics
- The executor is chosen per build. An executor that cannot carry out a SIR node with its exact meaning refuses the program at translation with
E_UNSUPPORTED, naming the node, its source position and the missing capability. No executor narrows, wraps or reorders an operation to fit what it supports [ARCH-03]. - Each executor has a capability table: for every SIR node family, form and type, one of
native,lowered:<strategy>orunsupported. The table is part of the compiler source. The table ofgpu-cudais the comment at the head ofcompiler/back_ptx.ci[BACK-04]. - A program that needs an
unsupportedentry of the selected executor fails to build withE_UNSUPPORTED, naming the node, its site and the capability. A device that lacks, when a program is loaded, a capability that translation assumed fails the load withE_UNSUPPORTEDbefore any kernel is prepared. Execution does not fall back to a narrower type [BACK-05]. - A
lowered:<strategy>entry names a lowering, such as carrying out a 64-bit operation in two 32-bit words, that has conformance cases of its own. A lowering is admitted only when those cases pass [BACK-06].
Examples
- The kernel below doubles each element with the saturating
+|. The executorcpu-c17runs it.
```cint sat.ci kernel kn over [i: n] { b[i] = a[i] +| a[i]; }
export I64 run() { I64[4] a; I64[4] b; for j in 0..4 { a[j] = (j + 1) as I64; } k(a, b); return b[3] as I64; } "{run()}\n";
```text
8
The capability table of gpu-cuda lists saturating arithmetic as unsupported. The emitted C carries the refusal inside the block that only a gpu-cuda build compiles:
$ cint emit-c --root . --out out sat.ci
$ grep '#error' out.stage-0/sat.c
#error "gpu-cuda: E_UNSUPPORTED: kernel sat.k needs capability sat, node 35, at 2:17 (SPEC-09 BACK-05)"
A gpu-cuda build compiles this file with the macro CINT_GPU_CUDA defined, so it stops at the #error, which names the kernel, the capability, the SIR node and the position 2:17 of the +|. A cpu-c17 build of the same file does not define the macro and runs the program.
5.6 The compiler
The compiler is a checked cint program. It keeps all its state in tables whose sizes it computes before it starts, and a host program drives it.
Semantics
- The source of the compiler is the import closure of
compiler/main.ci. In cint 0.2.0 it is 14 modules:back_c,back_ptx,check,decl,emit,fold,limits,lowering,main,parse,resolve,scan,sha256andverify, each a filecompiler/<name>.ci. - All compiler state lives in tables of fixed capacity. The capacity of each table is a formula of the sizes of the inputs, capped by a hard limit. An input that exceeds a capacity is reported with the diagnostic C9001, naming the table, the limit and the source position; it does not crash the compiler and is not cut short silently [CINTC-01].
- Blocks nest at most 256 deep, not counting the body of a function or a test, and the
{of a block 257 deep is the diagnostic C9004; the arms of anelse ifchain or aswitchdo not nest (paragraph 4). An expression nests as deep as the parser's stack holds, and beyond that is also C9004. Thecintcommand reads at most 2,048 modules for one program, and stops a program of more with a tool error and exit status 5; 12 states what it does with a source file beyond the limit below. For a module ofnsource bytes, whose number of tokens,tokens, is at mostn + 1, the tables have these capacities and hard limits; each division rounds down [CINTC-02]:
| Table | Capacity | Hard limit |
|---|---|---|
| Source | n bytes |
4,194,304 bytes per module, 67,108,864 bytes per program |
| Modules | the modules of the build | 4,096 per program |
| Syntax nodes | 2 * tokens + 16 |
set by the source limit |
| Symbols | tokens / 2 + 64 |
1,048,576 per module |
| Functions | tokens / 4 + 1 |
65,536 per module |
| Types | 256 + tokens / 8 |
65,536 per module |
| SIR nodes | 4 * tokens + 16 |
set by the source limit |
| Parser stack | 512 + tokens / 32 frames |
131,072 frames |
| Declarations seen across modules | 1, plus n / 4 + n / 32 + 1 for each module |
1,048,576 per program |
| Output | the bytes that measure counts |
268,435,456 bytes per module |
| Retained diagnostics | 100 per module | 100 per module |
- A chain of
else ifarms, and the arms of aswitch, are one node with an ordered list of arms, and every pass of the compiler walks the list iteratively. A chain of 1,000 arms therefore uses the same stack depth as a chain of 2 [CINTC-03]. - The compiler uses no heap allocator and requests no memory during a call. A host program, the bridge (
rt/cint_bridge.candrt/cint_build.c), drives it through four exported calls:plan, which computes the size of every table from the sizes of the inputs;compile, which reads the source of a module into the tables and produces its SIR or its diagnostics;measure, which counts the bytes of each output file of a module; andemit, which writes them. The bridge allocates the tables afterplanand the output buffers aftermeasure[CINTC-05]. measureandemitrun over the same SIR in separate calls. The bridge allocates exactly the number of bytesmeasurecounted, andemitwrites exactly that many; a different count is an internal compiler error [CINTC-04].- The compiler performs no input or output of its own. 7.1 describes how the bridge reads its inputs and commits its outputs.
- A fault inside the compiler, such as an index outside a table, is reported as an internal compiler error with the full fault record, the compiler source identity and the identity of the compiler's inputs [CINTC-10].
Notes
- Because the compiler is a checked program, a fault inside it stops it instead of corrupting memory. A logic error that produces wrong output without a fault, such as lowering
+to+%, is found only by the conformance suite and the fixpoint (10.4).
6 SIR
SIR is the program after it has been checked: every value has its type, every intermediate value has a name, control flow is structured, and every point where the program can fault is marked with its source position. Every executor of the toolchain runs SIR, and cint-explain prints it.
6.1 Design rules
Semantics
- SIR is typed. Every temporary, local, parameter and result has exactly one cint type,
Boolfor comparisons and conditions. There is no implicit conversion: every change of width or signedness is anasnode [SIR-01]. - Every node that produces a value defines a new temporary,
%t0,%t1and so on, exactly once. A variable of the source is a slot, read byload.localand written bystore.local. Nodes are evaluated in the order they are written; nothing in SIR is unordered [SIR-02]. - A checked operation is one node, such as
add.checked.i64, not an addition followed by a test. An executor lowers the node as a unit and cannot separate the check from the operation [SIR-03]. - Every node that can fault, including a fuel charge and a call, is a site: it carries its module, line, column and operation identifier. Sites are numbered from 1 in each module, in node order; the number 0 means no site [SIR-04].
- Control flow is structured: blocks,
if,loop,switch,break,continue,returnand the fault exit. There is no arbitrary jump.&&and||become anifthat evaluates the right operand only when it is needed. A chain ofelse ifarms is oneifnode with an ordered list of arms. The interpreter runs this structure as it is written and lowers nothing itself [SIR-05]. - The front end resolves every default of the language before it writes SIR. A fixed-point multiplication carries its intermediate width and its rounding mode even when the source used the default, and a reduction carries the width of its accumulator. An executor does not supply a default [SIR-06].
- Every fuel charge is explicit in SIR, so every executor charges at the same points. An entry's charge and a loop iteration's charge are
fuel.chargenodes, a call's charge is made by its call node, and a dispatch's charge by itsdispatch.chargenode. The amount of a charge is a constant, or a constant times a product of extents computed with checkedI64arithmetic, whose overflow isE_FUELat the charge's site when a budget is set. The interpreter charges fuel at these nodes and at no other point [SIR-07]. - The opcode of every arithmetic, conversion and reduction node is the operation identifier of SPEC-04P.2 that a fault record reports, in lowercase, with the types written
i64,u8,i128:add.checked.i64,div.checked.i64for the floor quotient/,as.checked.i64.i8. Theoperationfield of a fault record is the opcode of the node that faulted. When an executor carries out a node as a sequence of steps, the record still names the node: amuldivthat faults reportsmuldiv.checked.i64.i64.floor, not the division inside it. Type names in type annotations are those of the source, such as%a: I64[SIR-15, SIR-16].
6.2 The text form
SIR has one text form. The compiler writes it for every module, the interpreter reads it, and cint-explain prints it.
Semantics
- The text form is ASCII with LF line endings and one node per line. Temporaries are numbered from
%t0in each function, in the order they are defined. A node that is a site ends with its ordinal and position,#<ordinal> @<path>:<line>:<column>, where<path>is the module-relative path. A node is indented two spaces for each level of structure that encloses it, up to 16 levels and 32 spaces at any deeper level, so the size of the text stays linear in the number of nodes; braces carry the structure [SIR-09]. - The text of a module is, in order: a line
module <name>with the module's dotted name; thesitesline,sites <path>followed by one item<ordinal>:<line>:<column>:<operation>for each site in node order; onestructline for each struct type of the module, in declaration order; onegloballine for each module-level variable, in declaration order; and the functions, kernels, script body and test blocks of the module. In thesitesline, the operation of a call's site iscall.enter, that of a print statement's site isprint, and that of any other site is the node's own operation identifier. On a node line, a site's annotation begins at the 50th character, or after it when the node text is longer, with at least one space before it. A parameter is written%<name>, a slot$<name>.<k>, a module-level variable@<name>and a structure thatbreakorcontinuenamesL<k>; these numbers count from 0, and site ordinals from 1. Values are decimal, and printed text is written in double quotes with\",\\,\n,\tand\x<hh>for other bytes outside printable ASCII [SIR-09a]. - The main forms are these.
| Form | Meaning |
|---|---|
func @<module>.<name>(%<p>: <T>, ...) -> <T> { |
A function; -> <T> is absent when it has no result, and the line begins export for an exported function. |
kernel @<module>.<name>(%<p>: <T>, ...) { |
A kernel. |
script @<module> { |
The top-level statements of a script, its implicit entry. |
test @<module> <k> { |
Test block k of the module. |
local $<x>.<k>: <T> |
A slot comes into scope. |
%t<N> = const <T> <value> |
A typed literal. |
%t<N> = <operation> %<a>, %<b> |
An arithmetic, bitwise or comparison node, spelled as its operation identifier. A conversion and a unary node take one operand. |
%t<N> = load.local $<x>.<k>, store.local $<x>.<k>, %<v> |
A slot is read or written. |
%t<N> = call @<module>.<name>(<argument>, ...) |
A call, with %t<N> = when the function returns a scalar. |
dispatch @<module>.<kernel>(<argument>, ...), <bits> |
A kernel dispatch. |
fuel.charge <n> |
A charge of n units: an entry's, first in its body or a loop iteration's, after the loop's while. |
if {, arm {, } then %<c> {, else { |
An if chain: each arm holds its condition's nodes and ends with then. |
loop L<k> {, while %<c>, next |
A loop: its condition and while, which leaves the loop when c is false, the body, then next, the target of continue and the update. |
switch L<k> %<v> {, case <items> {, default { |
A switch; items are values and inclusive ranges, such as 1, 3..=5. |
break L<k>, continue L<k>, return, return %<v> |
A jump. |
print.begin, print.bytes "<text>", print.value <T> %<v>, print.end |
One print statement. |
} |
The end of a function or of a structure. |
Examples
- A function and a script that calls it:
```cint add.ci I64 add3(I64 a, I64 b, I64 c) { return a + b + c; } "{add3(1, 2, 3)}\n";
```text
6
cint emit-c writes the SIR text of each module into a file named after the module with the extension .sites, beside its C file (7.1):
$ cint emit-c --root . --out out add.ci
$ cat out.stage-0/add.sites
module add
sites add.ci 1:2:14:add.checked.i64 2:2:18:add.checked.i64 3:4:1:fuel.charge 4:4:3:call.enter 5:4:1:print
func @add.add3(%a: I64, %b: I64, %c: I64) -> I64 {
%t0 = add.checked.i64 %a, %b #1 @add.ci:2:14
%t1 = add.checked.i64 %t0, %c #2 @add.ci:2:18
return %t1
}
script @add {
fuel.charge 1 #3 @add.ci:4:1
%t0 = const I64 1
%t1 = const I64 2
%t2 = const I64 3
%t3 = call @add.add3(%t0, %t1, %t2) #4 @add.ci:4:3
print.begin #5 @add.ci:4:1
print.value I64 %t3
print.bytes "\n"
print.end
}
The two additions of a + b + c are sites 1 and 2, at the two + signs. The script charges 1 unit when its entry begins (site 3). The call charges its unit at the call's site (site 4, call.enter), where a call that would exceed the depth limit also faults. The function add3 has no fuel.charge of its own, because it is entered only by calls.
cint-explainprints the fault sites of a function and its SIR:
$ cint-explain add.ci:add3
module add.ci revision bc1faa5409ed570368f6f2ed815f89623096edcce3fcde135d6a1db89a37a2f2
function add3 add.sites:3 pure
signature (in a: I64, in b: I64, in c: I64) -> I64
fault sites (2): 2 possible, 0 statically excluded
add3#1 add.ci:2:14 add.checked.i64 E_OVERFLOW possible
add3#2 add.ci:2:18 add.checked.i64 E_OVERFLOW possible
...
sir
func @add.add3(%a: I64, %b: I64, %c: I64) -> I64 {
%t0 = add.checked.i64 %a, %b #1 @add.ci:2:14
%t1 = add.checked.i64 %t0, %c #2 @add.ci:2:18
return %t1
}
The first line gives the revision identity of the program (6.5). SPEC-06P.1 describes the other lines.
6.3 Verifier
Constraints
- Before any executor runs, the verifier checks that every temporary is defined exactly once and before it is used; that the types of the operands match the node's signature exactly; that every node that can fault has a site; that every write goes to a view with write permission or carries the run-time
E_ALIAScheck; that kernel bodies meet the restrictions of SPEC-04P.2 6.13; and that the outputs of each dispatch are covered by one alias check over all its writable bindings. A failure is an internal compiler error that names the node, not a warning. The interpreter checks again, when it reads a SIR text, the rules it relies on (5.3) [SIR-10]. - The verifier computes the capabilities that each function and kernel needs. The executor's admission of a program compares this set with its capability table (5.5) [SIR-11].
6.4 Transformations
Constraints
- A transformation of SIR is admissible only if, for every input, it keeps the output bytes, whether a fault occurs, the full fault record (code, operation, operands, exact result, position and address, including the step ordinal), every effect that comes before the fault, and the fuel charged up to any point that can be observed [SIR-12].
6.5 Revision identity
The revision identity is a digest of a program's SIR with the source positions taken out. Fault records, checkpoints and receipts name a program by it.
Semantics
- The hashed encoding of a module is its SIR text (6.2) with the
sitesline removed and, on every node line, the text@<path>:<line>:<column>after the site ordinal removed. The ordinals#<n>stay, and so do the spaces before them; no line or column remains. The revision identity is the SHA-256 digest of these bytes, in order [SIR-17]: - the domaincint-core-1/revision/v1, the profile namecint-core-1and the runtime contract version (7.10), each as a 32-bit little-endian length followed by its ASCII bytes; - the number of modules, as a 32-bit little-endian integer; - for each module, in byte order of its module-relative path: the path, as a 32-bit little-endian length followed by its UTF-8 bytes, then the hashed encoding, as a 64-bit little-endian length followed by its bytes.
The fuel model is not an input of the revision identity. A separate source-map digest is the SHA-256 digest of the domain cint-core-1/source-map/v1, as a 32-bit little-endian length followed by its ASCII bytes; the number of modules, as a 32-bit little-endian integer; and for each module, in the same order, its path, as a 32-bit little-endian length followed by its UTF-8 bytes, its number of sites, as a 32-bit little-endian integer, and for each item of its sites line, in the line's order, the ordinal, line and column, as 32-bit little-endian integers, and the operation, as a 32-bit little-endian length followed by its ASCII bytes. Both are recorded with a program. Fault positions are read through the source map recorded with the revision. The interpreter recomputes the revision identity from the text it reads, so a checkpoint, a recording or a case runs on it only with the revision it names.
Examples
- For
add.ciof 6.2, the hashed encoding of the moduleadd.ciis the text ofadd.siteswithout its second line, with each@add.ci:<line>:<column>removed. Its revision identity is the digest thatcint-explainprinted in 6.2, and the same 32 bytes are the arraycg_revisionof the emittedcint-program.c. A copy of the module with a blank line added before it keeps the same revision, while its sites move to line 3:
$ mkdir moved
$ { echo; cat add.ci; } > moved/add.ci
$ cint-explain --root moved add.ci:add3
module add.ci revision bc1faa5409ed570368f6f2ed815f89623096edcce3fcde135d6a1db89a37a2f2
function add3 add.sites:3 pure
signature (in a: I64, in b: I64, in c: I64) -> I64
fault sites (2): 2 possible, 0 statically excluded
add3#1 add.ci:3:14 add.checked.i64 E_OVERFLOW possible
add3#2 add.ci:3:18 add.checked.i64 E_OVERFLOW possible
...
Notes
- A blank line added to a module, or a function moved down, changes the source map but not the revision identity, so a checkpoint of a context that has not faulted still restores. Reordering two statements, or adding an operation that can fault, changes the site ordinals and so the revision identity.
- A checkpoint of a context that holds a fault keeps the position of its fault, and a restore maps that position back to a site by its line and column. After an edit that moves the faulting line, such a checkpoint is refused even though the revision identity has not changed.
7 Emitted-C contract
The compiler emits C17 that is defined for every input that reaches it: each check comes before the operation it guards, and the C compiler is given nothing it may treat as undefined. The emitted C calls the runtime for checked operations, fuel, call depth and fault records. This clause states what the emitted C and the runtime promise each other.
7.1 The output set
The compiler writes the files of a program as one set, which a reader sees complete or not at all.
Semantics
- The compiler performs no input or output. The bridge reads the files named in the build manifest, in manifest order and not by listing a directory, and passes each module's path relative to the source root, with
/separators. Outputs are committed as one set [CINTC-06]: - All output files are written into a new sibling directory
<out>.stage-<N>, whereNis the first unused decimal integer, created exclusively. Each module's files are written as itsemitcall completes, and the program file after the last module. A diagnostic, a fault or a failed write in any module abandons that directory: the committed set stays as it was, byte for byte, and no new file is visible to a reader. - The bridge writes the file
MANIFESTinto that directory. Its first line iscint-manifest-1, and each following line is<sha256> <bytes> <path>for one output file, in byte order of path. The bridge flushes every file to stable storage. - The commit is one atomic replacement of the single file
<out>/MANIFEST.ref, which holds the name of the new directory and the SHA-256 digest of itsMANIFEST:renameon POSIX systems, andMoveFileExWwithMOVEFILE_REPLACE_EXISTINGandMOVEFILE_WRITE_THROUGHon Windows. These are the only platform calls of the bridge. - Readers trust only the files that the
MANIFESTnamed byMANIFEST.reflists, after checking each file's digest. Directories left by earlier builds are removed after a commit when possible; their presence does not change what a reader sees.
A reader that runs while a build commits sees MANIFEST.ref name either the complete old set or the complete new one. No claim is made for single files, or for durability after a crash or a power loss, which depends on the file system.
2. A module-relative path is one or more segments separated by /. Each segment is made of ASCII letters, digits, _, - and ., and is not empty, . or ... A path has no leading /, no drive letter, no \ and no :. No segment names a Windows device (CON, PRN, AUX, NUL, COM1 to COM9, LPT1 to LPT9, in any case) before its first ., and no segment ends in .. All the paths of one build differ under ASCII case folding. With the final .ci removed, every segment is an identifier, so a dotted module name and its path correspond one to one. Any other path is the diagnostic C3030, naming the path and the rule it breaks. The bridge opens every file relative to the source root and refuses symbolic links and other reparse points [CINTC-12].
Examples
- The build of
add.cifrom 6.2:
$ cint emit-c --root . --out out add.ci
$ ls out out.stage-0
out:
MANIFEST.ref
out.stage-0:
MANIFEST
add.c
add.sites
cint-program.c
cint-program.sites
$ cat out/MANIFEST.ref
out.stage-0 06d38f20f86a9e377eca47c7cd7eebc5c8fd7154ee06e93498ed881a63395424
$ cat out.stage-0/MANIFEST
cint-manifest-1
1a4f0c8f856d921cbfa7f478970c9926c77ce455ee776711033781b195708d34 2358 add.c
d83a256fcde82d46d7b00eaa2de7c6e491dc1ac747e3f4f10244d35b758887f5 634 add.sites
2a7330a85849fd42f62eb7f4643131da96de92dc9f2cec08ffeb2a36285b141b 1318 cint-program.c
ce55c32dc70f577c08198ce999ba084cbbf8e0e291a2a4828d6933b052ca778d 16 cint-program.sites
$ sha256sum out.stage-0/MANIFEST
06d38f20f86a9e377eca47c7cd7eebc5c8fd7154ee06e93498ed881a63395424 out.stage-0/MANIFEST
add.c is the module's C, add.sites its SIR text, cint-program.c the program file that ties the modules together and holds the revision, and cint-program.sites the list of modules that the interpreter reads. The digest in MANIFEST.ref is the SHA-256 digest of MANIFEST, as an external tool computes it.
Notes
- Microsoft does not document the replacement by
MoveFileExWas atomic, so on Windows the property of paragraph 1, that a reader sees the complete old set or the complete new one, rests on a test that stops the bridge after each file and checks what a reader sees.
7.2 Language level and headers
Constraints
- Emitted C is C17: GCC and Clang compile it with
-std=c17, and MSVC with/std:c17. It includes only<stdint.h>,<stddef.h>,<stdbool.h>,<string.h>,<limits.h>andcint_rt.h. It does not include<math.h>or<float.h>, and it contains nofloat,doubleorlong doubletoken. The C sources of the runtime, the bridge, the harness, thecintcommand, the interpreter and the bootstrap compiler contain no floating-point type, literal or header either;python3 tools/cint_lint.pychecks each of these sources for them [EMIT-01, RT-2]. - Emitted C relies only on behavior that C17 defines for every input that reaches it, and on these properties of the C implementation, each checked when
cint_rt.his compiled:CHAR_BITis 8 (a static assertion);int8_ttoint64_tanduint8_ttouint64_texist, which in C17 makes the signed exact-width types two's complement (an#errorotherwise);size_thas at least 32 bits (a static assertion); and pointers have 64 bits, so the runtime supports 64-bit hosts only (a static assertion). External identifiers must be significant to at least 247 characters, which no source check can test; a generated symbol longer than 247 bytes is the diagnostic C9002 (7.7). No string literal of emitted C, after adjacent literals are joined, is longer than 4,095 characters, the minimum that C17 5.2.4.1 requires an implementation to accept: the literal text of a print statement reaches the runtime in pieces of at most 1,024 bytes, each written as adjacent literals of at most 64 bytes, and the PTX of a module's kernels is an array of literals, one per line [EMIT-02]. - Emitted C is ASCII with LF line endings, a final LF, no trailing space, no tab and four-space indentation. Its first line is the comment
/* cint-core-1, cint-rt-3, emitted by cintc */, which names the language profile, the runtime contract and the compiler and nothing else [EMIT-24].
7.3 Checks before operations
In C, a signed overflow, a division by zero and a shift by the width of the type are undefined. Emitted C performs the check that cint requires before the operation, so the undefined case is never evaluated, and it computes the defined result with operations that C defines for all inputs.
Semantics
- No emitted operation is evaluated when its C result would be undefined. Either the check comes first, or the operation is one that C defines for all inputs: unsigned arithmetic, or a checked builtin. Computing a signed result that overflows and testing it afterward is forbidden. Every helper of the runtime computes a defined C result before it reports any fault [EMIT-03, RT-9].
- Emitted C performs a checked operation by calling a helper
bool cint_<op>_<t>(cint_ctx *ctx, cint_site site, ..., T *out), such ascint_add_i64(ctx, site, a, b, &out). A helper writes*outand returnstruewhen the exact result is in range. Otherwise it writes the full fault record into the context, if the context holds none yet, and returnsfalsewithout writing*out. A wrapping helper that cannot fault takes no context; a wrapping left shift takes the context and the site, because it still faultsE_SHIFTfor a bad count. The checked add, subtract and multiply have two helper modes. WithCINT_RT_HELPERS_BUILTIN, they use__builtin_add_overflow,__builtin_sub_overflowand__builtin_mul_overflowon GCC and Clang, and the intrinsics_mul128and_umul128for the multiply on MSVC for x64. WithCINT_RT_HELPERS_PORTABLE, whichCINT_RT_PORTABLE=1also selects, they use range tests inint64_tanduint64_tbefore the operation. Defining both macros is an#error. With neither defined,cint_rt.hselects the builtin mode on GCC and Clang and the portable mode elsewhere. The two modes give identical outcomes, and the conformance suite runs in both [EMIT-04, EMIT-05, RT-8, RT-12]. - A wrapping operation (
+%,-%,*%,<<%) is computed in the unsigned type of the same width, or inuint64_tfor widths below 64, and the result is mapped back to the signed type by a function that C defines. C17 makes the direct conversion of an unsigned value that is out of range to a signed type implementation-defined (6.3.1.3); emitted C does not contain one [EMIT-07]. - An operand of a type narrower than 64 bits is converted explicitly to
int64_toruint64_tbefore arithmetic, and the result is checked against the range of its type, or reduced explicitly, before it is narrowed. This keeps C's promotion tointout of every operation [EMIT-08]. - A division or a remainder checks the divisor for 0 (
E_DIV_ZERO) and, for a signed type, the pair (MIN, -1), before any C/or%is evaluated. For that pair a quotient faultsE_OVERFLOWand a remainder is 0. cint's/and%are floor division; emitted C derives the floor, truncating and Euclidean results from C's truncating quotient and remainder with an explicit adjustment [EMIT-09]. - A shift checks its count against the width of the operand before shifting, and faults
E_SHIFTfor a negative count or a count at or above the width. A left shift is carried out on the unsigned representation after the widening of paragraph 4. A checked<<then shifts the result back by the same count and compares it with the operand: arithmetically, at the operand's width, for a signed type, and logically for an unsigned type. A difference isE_OVERFLOW. An arithmetic right shift of a negative value, which C17 leaves implementation-defined (6.5.7), is emitted as~(~a >> c)[EMIT-10].
| Case | Shift back | Outcome |
|---|---|---|
I64 1 << 62 |
arithmetic: 2^62 >> 62 is 1 |
4611686018427387904 |
I64 1 << 63 |
arithmetic: MIN >> 63 is -1, not 1 |
E_OVERFLOW |
I64 -1 << 63 |
arithmetic: MIN >> 63 is -1 |
-9223372036854775808 |
U64 1 << 63 |
logical: 2^63 >> 63 is 1 |
9223372036854775808 |
I8 64 << 1 |
arithmetic at width 8: -128 >> 1 is -64, not 64 |
E_OVERFLOW |
- A checked conversion
astests the source value against the range of the target before any conversion is evaluated, and the conversion is then exact. The test depends on the signedness of the two types [EMIT-11]:
| Source | Target | Test |
|---|---|---|
| signed | unsigned | a value below 0 faults; otherwise (uint64_t)v is compared with the target's maximum as uint64_t |
| unsigned | signed | v, as uint64_t, is compared with the target's maximum converted to uint64_t; there is no lower test |
| signed | signed | compared in int64_t with the target's minimum and maximum |
| unsigned | unsigned | compared in uint64_t with the target's maximum |
- A wide integer,
I128toI1024, is emitted as a struct that holds an array ofuint64_tlimbs, least significant limb first.__int128and_BitInt(N)do not appear in any interface or stored layout. In the builtin helper mode, the runtime may compute a limb product withunsigned __int128on GCC and Clang, written with__extension__so that-Wpedantic -Werroraccepts it, and with_umul128and_udiv128on MSVC for x64 [EMIT-12].
Examples
- The shifts of the table, on both sides of the limit:
```cint shifts.ci I64 one = 1; I64 neg = -1; U64 u = 1; "{one << 62} {neg << 63} {u << 63}\n"; I8 b = 64; I8 c = b << 1;
```text
4611686018427387904 -9223372036854775808 9223372036854775808
fault[E_OVERFLOW]: the exact result is outside the type's range
--> shifts.ci:6:10
|
6 | I8 c = b << 1;
| ^ shl.checked.i8
|
= operation: shl.checked.i8
= operand-count: 2
= operand: I8 64
= operand: I64 1
= exact: 128
= limit: I8 127
...
- A wrapping multiplication of
U16values. In C,uint16_toperands are promoted tointand 65535 × 65535 overflows a 32-bitint, which is undefined. Emitted C multiplies inuint64_tand reduces the product modulo 2^16:
```cint wrap16.ci U16 a = 65535; U16 p = a *% a; "{p}\n";
```text
1
- Floor division, the remainder of (
MIN, -1), and the quotient of the same pair:
```cint divmin.ci I64 m = I64.min; I64 d = -1; I64 a = -7; I64 two = 2; "{a / two} {a % two} {m % d}\n"; I64 q = m / d;
```text
-4 1 0
fault[E_OVERFLOW]: the exact result is outside the type's range
--> divmin.ci:6:11
|
6 | I64 q = m / d;
| ^ div.checked.i64
|
= operation: div.checked.i64
= operand-count: 2
= operand: I64 -9223372036854775808
= operand: I64 -1
= exact: 9223372036854775808
= limit: I64 9223372036854775807
...
- Checked conversions at the limits of the table of paragraph 7:
```cint narrow.ci I64 a = -128; U64 b = 9223372036854775807; U8 c = 255; "{a as I8} {b as I64}\n"; I8 d = c as I8;
```text
-128 9223372036854775807
fault[E_NARROW]: the value does not fit the destination type
--> narrow.ci:5:10
|
5 | I8 d = c as I8;
| ^ as.checked.u8.i8
|
= operation: as.checked.u8.i8
= operand-count: 1
= operand: U8 255
= exact: 255
= limit: I8 127
...
7.4 Memory
Semantics
- Index and slice checks are computed on integer offsets before any pointer is formed. No emitted expression forms a pointer outside an object or more than one element past its end [EMIT-13].
- The bytes of a buffer are reinterpreted only with
memcpyor throughunsigned char. No pointer is cast between distinct object types, so the aliasing rules of C17 6.5 are neither relied on nor broken [EMIT-14]. - Every scalar local is initialized where it is declared, and every array is zeroed where the language says it is. A wide temporary may be left uninitialized at its declaration when the node that defines it comes before every use on every path, which the emitter reads from the structured control flow of SIR [EMIT-15].
7.5 The shape of the code
Semantics
- Each SIR node becomes one C statement, with two exceptions: an index check that the emitter shows cannot fail becomes none, and a counted loop is emitted twice. A counted loop is a loop whose
I64counter is compared by<with a bound that the loop does not change, is stepped once per iteration by a constant of at least 1, and whose body holds only loads, stores, element and field accesses, arithmetic, conversions, comparisons, built-ins and checks. Before it, one test enters a copy of the loop that charges no fuel per iteration, when the context holds no fault and the fuel left covers every iteration the counter allows; otherwise the loop runs as written. Each exit of the copy first sets the fuel consumed to the count the loop as written would have reached, so the outcome, fault record and fuel are those of the loop as written. No emitted C expression contains more than one operation that can fault or has a side effect, so C's unspecified order of evaluation of operands and arguments never matters [EMIT-16]. - Scalar temporaries are C locals, such as
int64_t t3, not slots in memory managed by the runtime [EMIT-17]. - Faults do not use
setjmp,longjmpor signals. A function emitted for a cint function follows the convention of the runtime's helpers [EMIT-18]: - It returnsbool:trueon success, andfalseafter the runtime has written the fault record. A function that returns a value writes it through a finalT *resultparameter, and only when it returnstrue. - A checked operation calls a helper with acint_site, the pair of the module's index in the program and the site ordinal. When the helper returnsfalse, the function jumps to its single fault exit. - A call of a cint function is preceded bycint_rt_call_enter(ctx, site), which checks the depth limit, charges the call's fuel and records the call site, and is followed on success bycint_rt_call_leave(ctx). Onfalsethe caller jumps to its fault exit without leaving, so the runtime keeps every active call site for thestackfield of the fault record. - An entry point is a generated wrapper. It callscint_rt_entry_begin(ctx, site, fuel, depth), then the function, and returnscint_rt_entry_end(ctx), which isCINT_FAULTwhen the context holds a fault. - Outside kernels, a check that cannot fail is not written. A checked remaindera % cof typeTby a constant power of twocis written(T)((uint64_t)a & ((uint64_t)c - 1u)), which is the floor remainder for anyain two's complement. An index check of an array of constant extentEis not written when the index is a floor remainder by a constantcwith0 < c <= E. A function left with no statement that jumps to its fault exit has no fault exit.
Examples
- The function
add3of 6.2, as the compiler emits it inadd.c. The two additions are sites 1 and 2 of module 0:
static bool ci_3_add_4_add3(cint_ctx *ctx, int64_t v_a, int64_t v_b, int64_t v_c, int64_t *result)
{
int64_t t0 = 0;
int64_t t1 = 0;
(void)ctx;
(void)v_a;
(void)v_b;
(void)v_c;
(void)result;
static const cint_site cs1 = {0u, 1u}; (void)cs1;
static const cint_site cs2 = {0u, 2u}; (void)cs2;
if (!cint_add_i64(ctx, cs1, v_a, v_b, &t0)) goto fault;
if (!cint_add_i64(ctx, cs2, t0, v_c, &t1)) goto fault;
*result = t1;
return true;
fault:
return false;
}
The script calls it between cint_rt_call_enter(ctx, cs4) and cint_rt_call_leave(ctx), and the program's entry, cg_3_add_run, begins with cint_rt_entry_begin(ctx, (cint_site){0u, 3u}, fuel, CINT_DEPTH_FROM_CONFIG), which makes the entry's charge at site 3.
7.6 Independence from the C compiler's options
Semantics
- The observable behavior of emitted C does not depend on the optimization level, on
-fwrapv,-ftrapvor-fno-strict-aliasingor on MSVC's/O1,/O2or/Od, for a program that does not exhaust the C stack. The conformance suite runs at-O0and-O2(/Odand/O2) and requires identical outcomes [EMIT-19]. - With GCC and Clang, the conformance suite also runs under
-fsanitize=undefined,address, UndefinedBehaviorSanitizer and AddressSanitizer, and requires that they report nothing. A sanitized build is a conformance build only [EMIT-20].
Notes
- The sanitized runs are evidence for 7.3 paragraph 1 over the programs run, not a proof for all programs (11.4).
7.7 Deterministic output
Semantics
- Emitted C is a function of these inputs only: the bytes of the declared input files and their module-relative paths, the compiler source identity, the language profile and the executor and its options. It contains no time, date, host name, user name, absolute path, environment value, random value or digest of the compiler's executable. The C toolchain that built the compiler is not an input [EMIT-21].
- Emitting the same program twice, from different working directories, with different environment variables and on different hosts, gives byte-identical C [EMIT-25].
- Every file that an executor writes, PTX text included, follows paragraphs 1 and 2: LF line endings, stable numbering and no time or path [BACK-02].
- Symbol names are built with an encoding that is injective, so two different names never give the same symbol, and self-delimiting, so a symbol splits back into its parts in one way only. A component
sis encoded asE(s): each byte in[A-Za-z0-9]is copied, and every other byte,_included, becomes_xand two uppercase hexadecimal digits. A component is written C(s), which is_<L>_<E(s)>, where<L>is the decimal length ofE(s)in bytes. Every symbol encodes the module-relative pathPwithout.ci, with/separators, never the dotted module name. There are four families:cifor user functions,ci+ C(P) + C(name);cgfor generated internal names,cg+ C(P) followed by a generated name;cxfor the public wrapper of an exported function,cx+ C(P) + C(name); andcmfor the public descriptor of a module,cm+ C(P). The program file also exportscint_program_abi,cint_observer_descandcint_library_desc. A symbol longer than 247 bytes is the diagnostic C9002, naming the symbol. Acxorcmsymbol that contains__is the diagnostic C5051, because C++ reserves such identifiers and the public header is included from C++; so are two exports that map to one public symbol [EMIT-22].
| Module | Name | Symbol |
|---|---|---|
add |
add3 |
ci_3_add_4_add3 |
ab |
x_c_d |
ci_2_ab_11_x_x5Fc_x5Fd |
ab_c |
x_d |
ci_7_ab_x5Fc_6_x_x5Fd |
arith/add |
f |
ci_12_arith_x2Fadd_1_f |
add |
add3, public wrapper |
cx_3_add_4_add3 |
a/b_c |
f, public wrapper |
cx_11_a_x2Fb_x5Fc_1_f |
a_b/c |
f, public wrapper |
cx_11_a_x5Fb_x2Fc_1_f |
- Sites are numbered from 1 in each module, in SIR node order, and 0 means no site. A site is the pair
cint_site { uint32_t module; uint32_t index; }. The C of each module holds one constant site table, whose rowkgives the line, column and operation identifier of sitek, and one module descriptor that holds the module-relative path and the table. A helper that faults receives the site and resolves it into the record's position and operation; emitted code does not format a position itself. No hash-table order or address enters the numbering, so two compilations of one program give identical site tables [EMIT-23, RT-6, RT-7].
Examples
- The same program emitted from two directories, with different time zones, locales and
SOURCE_DATE_EPOCH:
$ mkdir a b
$ cp add.ci a/ && cp add.ci b/
$ (cd a && TZ=UTC LANG=C cint emit-c --root . --out out add.ci)
$ (cd b && TZ=Asia/Tokyo LANG=C.UTF-8 SOURCE_DATE_EPOCH=1 cint emit-c --root . --out out add.ci)
$ diff -r a/out.stage-0 b/out.stage-0 && echo identical
identical
7.8 Warnings
Constraints
- Emitted C,
cint_rt.h, thecint_rtlibrary, the bridge, the harness and the bootstrap compiler compile with no warning under these flags [EMIT-26]:
| Toolchain | Flags |
|---|---|
| GCC | -std=c17 -Wall -Wextra -Wconversion -Wsign-conversion -Wshadow -Wpedantic -Werror |
| Clang | the GCC flags and -Wimplicit-int-conversion |
| MSVC | /std:c17 /W4 /WX |
7.9 Symbol audit
Constraints
- Every generated object is audited at both optimization levels of the conformance suite. The audit fails on an exported symbol other than the
cx_andcm_symbols,cint_library_desc,cint_program_abiandcint_observer_desc; on an external definition that is not exported, other than theci_andcg_symbols of the program's own modules; and on any undefined symbol outside this list [EMIT-31]:
| Class | Symbols | Reason |
|---|---|---|
| Runtime | cint_rt_ and the other cint_ functions of rt/cint_rt.h |
the runtime contract |
| Same build | ci_ and cg_ symbols of another module, or of the program file, of the same build |
imports and site tables |
| C support | memcpy, memset, memmove, memcmp |
emitted by 7.4, and inserted by GCC, Clang and MSVC for large copies and zeroing |
| MSVC x64 and ARM64 | __chkstk, __security_cookie, __security_check_cookie, __GSHandlerCheck, __report_rangecheckfailure, __isa_available |
stack probes for frames over one page; the /GS checks, the MSVC default; the CPU feature level that code built with /O2 reads |
| GCC and Clang | __stack_chk_fail, __stack_chk_guard |
-fstack-protector-strong |
| Apple Clang, macOS | __chkstk_darwin, ___chkstk_darwin, bzero |
the stack probe on AArch64 and x86-64; the zeroing of a large local array at -O0 |
A row for a target applies to that target only. A library build (cint build --lib) also exports the host functions of the runtime, which carry CINT_RT_EXPORT in such a build, and the audit of its export table admits exactly those. The list may shrink when a build removes the stack protector with -fno-stack-protector or /GS-; such a build records the flag in its receipt.
7.10 The runtime contract
The runtime is the header rt/cint_rt.h and the library cint_rt. The header is the normative statement of the contract; this subclause explains it.
Semantics
- The runtime contract version is a string
cint-rt-<N>that names the interface and meaning ofcint_rt.hand thecint_rtlibrary on which emitted code depends: the names and signatures of the helpers, the layout of the fault record and the layout of the site table. It changes when any of these changes incompatibly, and it is an input of the revision identity (6.5). In cint 0.2.0 it iscint-rt-3, and the header defines the interface versionCINT_ABI_VERSIONas0x00030002. A checkpoint binds to the revision identity; the compiler source identity is recorded beside it for identification only, so a new compiler that leaves the SIR and the runtime contract unchanged does not invalidate checkpoints [RCPT-08]. - Every symbol that the runtime exports is in the
cint_namespace. The helpers arestatic inlineand add no undefined symbol to a generated object [RT-3]. - A context,
cint_ctx, is an opaque object that holds one instance of a program's state from its creation to its destruction. Emitted code passes the pointer to helpers and does not read its fields. For each entry it holds the fuel budget and the fuel consumed, the depth limit and the current depth, and the stack of call sites. Across entries it holds module state, registered buffers, arenas and the fault record [RT-4]. - Faults are sticky. The first fault of an entry writes the fault record once. A later helper call on a context that holds a fault returns
falseand changes nothing. Emitted code tests every helper result and leaves through the function's single fault exit. Only the host clears the record, withcint_ctx_clear_fault, which changes no other state [RT-10]. - A call checks the depth limit first, faulting
E_DEPTHwithout a fuel charge, then charges one unit of fuel, then records its call site. A loop iteration charges one unit after its condition holds. A charge that would take the fuel consumed above the budget faultsE_FUELand is not consumed. The entry itself is a call from the host: it charges one unit and adds no position to the stack. A fuel budget ofCINT_FUEL_UNBOUNDED, which is -1, counts fuel and never faultsE_FUEL. The runtime provides frames for a depth limit of up toCINT_RT_MAX_DEPTH, 1,024; an entry with a larger limit faultsE_UNSUPPORTEDbefore it runs. Clause 12 compares this limit with the minimum of SPEC-04P.2 [RT-11]. cint_fault_recordis a C structure with every field of the fault record of SPEC-04P.2 8.1: the code, the operation, up to 8 typed operands, the exact result, the limit, the position, the revision, the address and the stack. An exact result or operand of a wide type takes at most 257 bytes in fault record version 2 and 264 bytes in version 3. Static assertions fix the size and field offsets of the structure. The structure is a convenience; the canonical form of a record is its bytes, whichcint_fault_encodewrites: little-endian, with minimal integer encodings, tagged operands and no padding, as SPEC-01P.2 specifies. Conformance compares these bytes, not the structure [RT-13, RT-14].- Each context holds one atomic flag. Every library function that touches the context sets it on entry by an atomic exchange and clears it on return. A call that finds the flag set returns
CINT_BUSYand changes nothing. The flag detects a second caller; it does not make a context usable from two threads at once [RT-15].
Notes
- The status values of
rt/cint_rt.hareCINT_OK(0),CINT_FAULT(1),CINT_REFUSED(2),CINT_BUSY(3),CINT_FAULTED(4) andCINT_RESOURCE(5). SPEC-03P.1 specifies what each means to a host program.
8 Agreement
Two implementations of cint agree on a program when every observable part of its outcome is the same. This clause says what is compared, what is not, and how a case that is not compared is reported, so that a refusal is never counted as agreement.
8.1 What is compared
Semantics
- A program is in the scope of both the reference and a compiler when the reference runs it and the compiler builds it without a diagnostic whose code begins with
C9. For such a program and the same semantic inputs, the two agree as a conformance case does: the same bytes on standard output; the same kind of outcome; the same returned value; the same fuel consumed; and the same fault record in every field except the revision and the source-map digest, which the reference does not compute. For a program that both reject, they agree when they report the same diagnostic code at the same position. A disagreement is a defect of one of them or an ambiguity in the text; it is not settled by choosing one. The program, reduced to a case, joins the suite. Agreement on one program is a measurement of that program and shows nothing about another [REF-06]. - A position, in a diagnostic, a fault record, SIR text or an
.expectfile, is written<path>:<line>:<column>.<path>is the module-relative path,.ciincluded, with/separators. Lines count from 1. Columns count from 1 in Unicode scalar values, not bytes [DIAG-01]. - Codes have two separate sets of names. A fault code is one of the
E_names of SPEC-04P.2 5.2.3. A diagnostic code isCand four digits, listed in Annex B of SPEC-04P.2. A constant expression whose evaluation meets a fault condition is the diagnostic C6001, which carries the fault code, and C6002 when it runs out of compile-time fuel. A diagnostic that reports a limit of an implementation has a code that begins withC9: C9001 for a capacity or an output size, C9002 for a generated symbol longer than 247 bytes, C9003 for a local array over the frame limit, and C9004 for nesting beyond the implementation's limit [DIAG-02]. - These are not compared: the message text of a diagnostic, the reason given for a refused entry, executables and their digests, timings and any other observation of the host.
Examples
- The
+below is the sixth Unicode scalar value of its line and the seventh byte, becauseétakes two bytes in UTF-8:
```cint column.ci I8 a = 100; "é{a + a}\n";
```text
fault[E_OVERFLOW]: the exact result is outside the type's range
--> column.ci:2:6
...
8.2 Categories of a case
Semantics
- A conformance run reports each case in exactly one category. The category depends only on the case, its frozen expectation and the compiler under test, not on the C toolchain, so it belongs in the outcome part of the receipt [CONF-14]:
- Compared. The implementation produced an outcome and it was compared with the frozen expectation. An expected diagnostic is compared too: the frozen outcome is a compile error, and the compiler reports the same code at the same position.
- Unsupported by this implementation. The frozen outcome is a value, a fault or a compile error with another code or position; the compiler reports a
C9code under a documented limit; and the fileconformance/unsupported.txtlists that case, compiler, code and limit. Forgpu-cuda, the refusalE_UNSUPPORTEDof 5.5 takes the place of theC9code. An unsupported case is a named gap, not agreement. - Held. The file
conformance/held.txtlists the case, which has no frozen expectation. - Outside the subset. The compiler under test accepts only a subset of the language (10.2), the case is outside it and the compiler reports the diagnostic for that.
- Not applicable. An operation fixture whose operation has no operator form in the subset that the compiler accepts, so that no program exists for it.
Any other result is a disagreement: a C9 code that conformance/unsupported.txt does not list, a refusal by a subset compiler of a case inside its subset, or a compile error whose code or position differs from the frozen expectation. A case that the list names as unsupported and that the compiler accepts is also a disagreement, until the list is corrected.
2. Each line of conformance/unsupported.txt is <case> <compiler> <code> <limit> <reason>. For gpu-cuda the compiler is cintc/gpu-cuda, the code is E_UNSUPPORTED, and the limit is BACK-05 capability <name>, where <name> is the capability that the refusal names; a refusal that names another capability is a disagreement. For example, the line control/nest_blocks_200 cint-seed C9004 SEED-15 the seed compiles at most 199 nested blocks lists a case that the seed refuses under its nesting limit (10.2, paragraph 8).
8.3 Disagreement
Semantics
- Any disagreement fails the run. A disagreement between the reference and a hand-derived anchor expectation is a defect of the reference, and it also fails the run until the reference is fixed or the expectation is corrected by an erratum (9.5) [CONF-04].
8.4 The run matrix
Semantics
- A conformance run executes every case on the reference and on every build in this matrix, and requires identical canonical outcomes across all of them [CONF-03]:
| Axis | Values |
|---|---|
| C toolchain | GCC on Linux x86-64; Clang on Linux x86-64 and Apple Clang on macOS AArch64; MSVC on Windows x86-64 |
| Optimization | -O0 and -O2 (/Od and /O2) |
| Helper mode | CINT_RT_HELPERS_BUILTIN and CINT_RT_HELPERS_PORTABLE |
| Compiler | the cintc built by the bootstrap (10.3), and the bootstrap compiler on the cases of its subset (10.2) |
| Executor | cpu-c17 on every case; cpu-sir-interp on every case; gpu-cuda on the cases its capability table supports, the others producing E_UNSUPPORTED at translation |
8.5 Parallel executors
An executor may run the work-items of a dispatch in parallel, but its outcome is the one of running them one at a time in canonical order.
Semantics
- The canonical outcome of a dispatch, fuel included, is the outcome of running its work-items one at a time in canonical order, as
cpu-c17does. The dispatch first chargesN, the number of its work-items. A parallel executor computes the outcome as follows, whereFis the fuel left when the first work-item starts,c(i, s)is the fuel that itemihas charged through its steps, andC(i)is the total charge of itemiwhen it runs to its end or to its first fault other thanE_FUEL[BACK-09]: -k_faultis the least item with a fault other thanE_FUEL, ands_faultthe step of that fault. -k_fuelis the least itemiwith a charge pointssuch thatC(0) + ... + C(i - 1) + c(i, s) > F, ands_fuelis the first suchs. Without a budget there is nok_fuel. - The canonical fault is the lesser of (k_fault,s_fault) and (k_fuel,s_fuel) in work-item, then step, order; the second isE_FUEL. - The fuel consumed by the dispatch isNplus the charges of items 0 tok - 1plus the charges of itemkbefore its faulting step, wherekis the item of the canonical fault; a charge that faultsE_FUELis not consumed. Without a fault it isNplus the sum of allC(i).
Which work-item failed first on the hardware cannot be observed. 2. The outputs of a dispatch that faults are not published [BACK-10]. 3. Running a work-item again to find or describe the canonical fault charges no fuel. The canonical fuel is computed once, by paragraph 1 [BACK-12].
Examples
- A dispatch of 1,000 work-items, each of which charges 10 units in its loop. Item 900 overflows on its second iteration:
$ cat dispatch_fuel.ci
kernel tenfold[n](in I64[n] a, out I64[n] b) over [i: n] {
I64 s = 0;
for j in 0..10 {
s += a[i];
}
b[i] = s;
}
export I64 run() {
I64[1000] a;
I64[1000] b;
a[900] = I64.max;
tenfold(a, b);
return b[0];
}
$ python3 -m cint_ref run dispatch_fuel.ci --entry run --format 4
...
fuel-consumed 10003
fault.code E_OVERFLOW
fault.operation add.checked.i64
...
fault.kernel dispatch_fuel.tenfold
fault.address 0 900 3
...
$ python3 -m cint_ref run dispatch_fuel.ci --entry run --format 4 --fuel 6001
...
fuel-consumed 6001
fault.code E_FUEL
fault.operation fuel.charge
fault.exact none
fault.limit I64 6001
fault.position dispatch_fuel.ci:3:5
...
fault.kernel dispatch_fuel.tenfold
fault.address 0 500 0
...
Without a budget, the entry consumes 1 unit, the dispatch 1,000, items 0 to 899 10 each, and item 900 2 before its overflow: 10,003. With a budget of 6,001, F is 5,000 after the entry's unit and the dispatch's 1,000, items 0 to 499 consume it, and the first charge of item 500 faults E_FUEL. The overflow at item 900 is not reported, because item 500 comes first. The address is the dispatch, the work-item and the step.
9 The conformance suite
The conformance suite, in conformance/, holds the cases on which every implementation must agree, with their expected outcomes. Most expectations come from the reference, and the boundary cases are derived by hand.
9.1 Case files
Semantics
- There are two kinds of case, with one rule of agreement (8.1). An operation fixture is one operation on given operands, in a CIF-1 file under
conformance/integer-machine/, one JSON object per line. A program case is a.cifile and its.expectfile, for whole programs: control flow, views, kernels, diagnostics and fuel. Each operation fixture is also run through generated table programs,conformance/tables/<name>.ciwith the list of calls<name>.cases, so both kinds run on every implementation. A program case begins with a header comment of lines// case: <name>,// clause: <rules>and// entry: <entries>; args: <arguments>[; fuel <n>][; depth <n>]; without anentryline, the case runs its script, else its functionmain, else its only test. A case with several entries has one file<case>.<entry>.expectfor each. The.expectfile is canonical text in the grammar of 9.3 [CONF-01]. - An expectation comes from one of three sources, which its
sourceline states:anchor, derived by hand, with the derivation written beside it, and used for boundary cases and to check the reference itself;reference, written by the reference withconformance/tools/gen_expect.pyand then frozen; orcompile-error, the expected diagnostic code and position. A frozen expectation is not edited in place. It changes in one of three ways [CONF-02]: - A change of meaning (a value, a fault code or content, a fuel count or a diagnostic code or position) comes with a new language profile, which has its own frozen expectations. A change of encoding with the same content comes with a new version of that encoding: a new domain string, a new runtime contract version (7.10) or a new.expectformat (9.3). Frozen files of the old version stay valid and are read in their own format. - Areferenceexpectation found wrong because of a defect of the reference, with the specification text unchanged, is replaced by a recorded erratum, a fileconformance/errata/<id>.txtthat names the case, the old and the new expectation, the clause text that decides it, and who checked it. Errata are part of the digest of the suite. Ananchorexpectation is corrected only by an erratum of the same form, which names two people who checked it. - A change to theclauseline alone, which no comparison reads, regenerates the file withconformance/tools/gen_expect.pyin a change of its own, in which every other line stays identical and after whichgen_expect.py --checkreports that no file differs.
9.2 Operation fixtures and coverage
Semantics
- A CIF-1 line is one JSON object with the keys
id,status,op,argsandexpect, in that order.opis an operation identifier,argsa list of typed values{"t": <type>, "v": <decimal>}, andexpectholds either avalueor afaultwith its code and, where the record has them, its exact result and limit. Every integer is a JSON string of decimal digits, so no JSON reader loses precision. A hand-derived anchor adds a keyderivationthat shows the arithmetic. - The suite holds these fixtures:
anchors.cif1.jsonl, the anchors;exh8/, every operand pair of the 14 binary operator forms (+ - * / % << >> +% -% *% <<% +| -| *|) onI8andU8, 1,835,008 records;bnd64/, theI64boundary matrix of the 16 valuesMIN,MIN + 1, -2^32, -2^31, -2, -1, 0, 1, 2, 2^31 - 1, 2^31, 2^32, 3037000499, 3037000500,MAX - 1andMAXfor each form (3037000499 and 3037000500 bracket the square root of 2^63); andbnd128/tobnd1024/, the boundary matrices of the wide types. - Every implementation that evaluates constants is also run on the constant and run-time category: each boundary-matrix and exhaustive 8-bit expression is evaluated as a constant expression and at run time from variables, and the two must give the same value, or the same fault code, operation and operands, which the constant program reports as the diagnostic C6001. If the front end folded
I8100 +| 100to anything other than 127, the two programs would disagree [CONF-08].
Examples
- The reference checks the anchors against their hand-derived values:
$ python3 -m cint_ref cif1 conformance/integer-machine/anchors.cif1.jsonl
cif1: 300 records, 300 agree, 0 disagree, 0 unsupported, 0 invalid
9.3 The .expect format
The .expect format is the canonical text of an outcome: one field per line, in a fixed order, with every integer in exact decimal.
Syntax
- An
.expectfile is the text that this grammar derives. The notation is ABNF (RFC 5234) with the case-sensitive strings%s"..."of RFC 7405;ALPHA,DIGIT,SP,LFandVCHARare the core rules of RFC 5234. A file whose third line isformat 2,format 3,format 4orformat 5is a file of that format. A file without that line is a format 1 file, stays valid and is compared in format 1. Keys appear in this order:case,clause,format,source,outcome,stdout-bytes,stdout-sha256,return,fuel-consumed, theerror.keys, thefault.keys, thediagnostic.keys,refused.reasonandstate.global[CONF-11].
expect-file = case-line clause-line [ format-line ]
( run-case / compile-case / refused-case )
format-line = %s"format 2" LF ; absent: format 1
case-line = %s"case" SP case-name LF
case-name = segment *( "/" segment )
segment = 1*( ALPHA / DIGIT / "_" / "-" / "." )
clause-line = %s"clause" SP text LF
text = VCHAR *( *SP VCHAR ) ; printable ASCII, no edge space
run-case = %s"source" SP ( %s"anchor" / %s"reference" ) LF
( value-body / fault-body ) *state-line ; state-line: format 2
state-line = %s"state.global" SP module-name SP name SP typed-value LF
value-body = %s"outcome value" LF output-lines [ return-line ] fuel-line
fault-body = %s"outcome fault" LF output-lines fuel-line fault-record
output-lines = %s"stdout-bytes" SP count LF
[ %s"stdout-sha256" SP 64lhex LF ]
return-line = %s"return" SP typed-value LF
fuel-line = %s"fuel-consumed" SP count LF
fault-record = %s"fault.code" SP fault-code LF
%s"fault.operation" SP op-id LF
*( %s"fault.operand" SP typed-value LF )
%s"fault.exact" SP ( int / %s"none" ) LF
%s"fault.limit" SP ( typed-value / %s"none" ) LF
%s"fault.position" SP position LF
%s"fault.revision self" LF
[ %s"fault.source-map self" LF ] ; format 2
%s"fault.address" SP ( %s"none" / address ) LF
%s"fault.stack-depth" SP count LF
*( %s"fault.stack" SP position LF ) ; format 2
compile-case = %s"source compile-error" LF %s"outcome compile-error" LF
%s"diagnostic.code" SP c-code LF
%s"diagnostic.position" SP position LF
[ diagnostic-fault ]
diagnostic-fault = %s"diagnostic.fault.code" SP fault-code LF
%s"diagnostic.fault.operation" SP op-id LF
*( %s"diagnostic.fault.operand" SP typed-value LF )
%s"diagnostic.fault.exact" SP ( int / %s"none" ) LF
%s"diagnostic.fault.limit" SP ( typed-value / %s"none" ) LF
refused-case = %s"source reference" LF %s"outcome refused" LF
%s"refused.reason" SP text LF
typed-value = int-type SP int / %s"Bool" SP ( %s"true" / %s"false" )
/ %s"Z" SP int
int-type = %s"I" ( "8" / "16" / "32" / "64" / "128" / "256" / "512" / "1024" )
/ %s"U" ( "8" / "16" / "32" / "64" )
fault-code = %s"E_" 1*( %x41-5A / "_" )
op-id = 1*( %x61-7A / DIGIT / "_" / "." )
c-code = %s"C" 4DIGIT
position = segment *( "/" segment ) ":" pos-int ":" pos-int
address = count SP count SP count ; dispatch, work-item, step
int = [ "-" ] count
count = "0" / %x31-39 *DIGIT
pos-int = %x31-39 *DIGIT
lhex = DIGIT / %x61-66 ; lowercase hexadecimal digit
module-name = name *( "." name )
name = ( ALPHA / "_" ) *( ALPHA / DIGIT / "_" )
- Format 3 adds the outcome
error, for an entry that returns an error, by incremental alternatives (=/), so the rules above are unchanged:
format-line =/ %s"format 3" LF
run-case =/ %s"source" SP ( %s"anchor" / %s"reference" ) LF
error-body *state-line
error-body = %s"outcome error" LF output-lines fuel-line error-value
error-value = %s"error.set" SP [ module-name "." ] name LF
%s"error.value" SP [ name "." ] name LF
%s"error.tag" SP uint-type SP pos-int LF
uint-type = %s"U" ( "8" / "16" / "32" / "64" )
- Format 4 adds the fault record of a kernel dispatch:
format-line =/ %s"format 4" LF
fault-record =/ %s"fault.code" SP fault-code LF
%s"fault.operation" SP op-id LF
*( %s"fault.operand" SP typed-value LF )
*( %s"fault.descriptor" SP descriptor LF )
%s"fault.exact" SP ( int / %s"none" ) LF
%s"fault.limit" SP ( typed-value / %s"none" ) LF
%s"fault.position" SP position LF
%s"fault.revision self" LF
%s"fault.source-map self" LF
%s"fault.phase" SP phase LF
%s"fault.kernel" SP kernel-name LF
%s"fault.address" SP kernel-address LF
%s"fault.stack-depth" SP count LF
*( %s"fault.stack" SP position LF )
kernel-address = count SP ( count SP count / %s"none none" )
; dispatch, then work-item and step
phase = %s"entry" / %s"work-item" / %s"epilogue"
kernel-name = module-name "." name
descriptor = elem-type SP ( count SP count / %s"none" ) SP count SP int
1*( SP count SP int SP int ) SP ( %s"read" / %s"write" )
elem-type = int-type
- Format 5 adds fixed-point values, the unsigned wide types and fixed-point element types:
format-line =/ %s"format 5" LF
typed-value =/ q-type SP %s"raw" SP int
int-type =/ %s"U" ( "128" / "256" / "512" )
elem-type =/ q-type
q-type = %s"Q" pos-int "." count ; i + f is 8, 16, 32 or 64
Constraints
- These rules hold in addition to the grammar:
- The file is ASCII with an LF after every line, the last included. It contains no CR, no tab and no trailing space.
stdout-sha256is present exactly whenstdout-bytesis not 0, and is the SHA-256 digest of the output bytes.returnis present exactly when the entry returns a value. Avoidfunction, a test block and a script have noreturnline.- There is one
fault.operandline for each operand of the record, in the record's order.fault.exactandfault.limitarenonewhen the record has no such field. fault.revision selfmeans that the record's revision must equal the revision identity of the program under test, andfault.source-map selfthe same for the source-map digest; both are compared only between builds ofcintc.fault.addressgives the dispatch, the work-item as one linear index and the step.fault.stack-depthis the number of positions in the record's stack. A format 2 file writesfault.source-map selfin every fault record and onefault.stackline per position, outermost first; a format 1 file writes neither.diagnostic-faultis present exactly when the code is C6001 or C6002. The message text of the diagnostic is not part of the file.- The outcome
refusedmeans that the host interface refused the entry before it started, for example for a fuel budget below 0. No execution exists, so the file has no output or fuel line. Therefused.reasontext is informative and is not compared. An implementation that cannot decide a case does not produce a frozen expectation for it. - The
clausevalue is free text. A case cites the rules it exercises by their clause identifiers, with;between documents. - Two
.expecttexts agree when they are equal byte for byte, except therefused.reasonline and thefault.revisionandfault.source-mapcomparisons of rule 5. A format 1 file is compared in format 1. - A program with module-level variables writes, in format 2 and later, one
state.globalline for each scalar module-level variable after the outcome lines: modules in the order of the program's module table, the root module first and then each imported module at its first import, breadth first; within a module, in declaration order. - Lines have no length bound in the grammar. A reader accepts a
Zoperand of 1,235 characters and an exact result of 1,253 characters, the largest a record ofcint-core-1can hold. - In format 3,
error.setis the error set of the entry's result type, qualified by its module,error.valuethe name of the value anderror.tagits tag with the set's underlying type. For a test that an error leaves bytry,error.setis the set of that error. Theerroroutcome has noreturnline. - In format 4, a fault raised by a kernel dispatch takes the kernel form of
fault-record, and any other fault the form of format 2.fault.kernelis the kernel's qualified name. A fault in theentryorepiloguephase writes its dispatch number andnone none; awork-itemfault writes the dispatch number, the work-item and the step. A record that carries view descriptors writes onefault.descriptorline for each, after its operands: the element type, the buffer identifier and generation (nonefor arrays of a.ciprogram), the rank, the origin and for each dimension its extent, lower bound and stride, then the permission. - In format 5, a fixed-point value is its type, the word
rawand its storage integer:Q16.16 raw 98304is 1.5. A fixed-pointexactkeeps theintform, as the fault record holds it.U128toU512are the unsigned wide types.
A file of a later format may use every line of the earlier formats, and files of earlier formats are not rewritten. conformance/tools/check_expect_abnf.py checks every .expect file of the suite against this grammar and the rules that a single file can show.
Examples
- The expectation of the case
arith/add_i64_overflowis in format 1:
$ cat conformance/arith/add_i64_overflow.expect
case arith/add_i64_overflow
clause SPEC-01 IM-30, IM-32, IM-106, IM-107
source reference
outcome fault
stdout-bytes 0
fuel-consumed 1
fault.code E_OVERFLOW
fault.operation add.checked.i64
fault.operand I64 9223372036854775807
fault.operand I64 1
fault.exact 9223372036854775808
fault.limit I64 9223372036854775807
fault.position arith/add_i64_overflow.ci:5:15
fault.revision self
fault.address none
fault.stack-depth 0
The case is a test block, which is its entry and charges 1 unit. The fault is in the entry's own body, so the stack is empty. The print after the addition does not run, so no byte was written. The program is lines 2 to 7 of conformance/arith/add_i64_overflow.ci, after its header comment:
test "add overflow at I64 max" {
I64 a = 9223372036854775807;
I64 b = 1;
I64 c = a + b;
"c={c}\n";
}
9.4 Lists of the suite
Semantics
conformance/held.txtlists, one per line, the program cases that have no frozen expectation, each with its reason. A held case is not compared.conformance/unsupported.txtlists the cases that an implementation refuses under a documented limit, in the line format of 8.2.conformance/errata/holds the errata of 9.1.
9.5 Required case lists
Semantics
- A required case list is a file
conformance/required/<name>.txtwith one line<case> <compiler> <category>for each case and compiler, where the category is one of 8.2 and, where the category needs one, the line adds the limit. The compiler column names the executor as well:cintcfor the compiler's C oncpu-c17,cintc/cpu-sir-interpfor its SIR text on the interpreter, andcintc/gpu-cudaforgpu-cuda.python3 tools/cint_check.py --required FILEfails a run when a listed case is absent, a case of the suite is not listed, a case's category differs from the list, a compile error's code or position differs from the frozen expectation, or a case has no.expectfile and is not held [CONF-15].
9.6 Observing compiled programs
Semantics
- A compiled conformance program is observed through
cint-harness, a C17 program. It reads a case list, one call per line,<module> <function> <typed arguments>, and calls each exported function through its observer entry with a fresh context and the case's fuel budget. It writes the canonical outcome (the returned value, or the full fault record from the context, and the fuel consumed) in.expectsyntax. The reference evaluates the same calls. The same harness serves every compiler under test, so all are compared through the same path [CONF-10]. - The interpreter is observed the same way:
cint-interp observetakes the command line ofcint-harness, with a directory of SIR text in place of a program library, and writes the same text.tools/cint_check.py --interpruns it on every compared case [CONF-10a]. - A case list is the text that this grammar derives, with the core rules of 9.3. Line
nis calln, and each call runs in a fresh context [CONF-12]:
case-list = *call-line
call-line = module-name SP name *( SP typed-value ) LF
- A program built for the harness follows the observer interface
cint-observe-1[CONF-13]: - It is one shared library built from the C of all its modules, the program file and thecint_rtlibrary of the same version as the harness. Its exported symbols have C linkage and carryCINT_RT_EXPORT. The harness resolves the program's path before it loads it. - It exports exactly two observer symbols, both defined in the program file:const uint32_t cint_program_abi = CINT_ABI_VERSION;andconst cint_observer cint_observer_desc;. The descriptor names the program (every module's site table, the root module first) and lists one entry for every exported function of every module whose parameters and result are scalars, in module order and then declaration order. The harness does not call a program whosecint_program_abidiffers from its own. - Each entry carries its module index, its name, a type signature of its result and at most 16 parameters (the tags ofI8toI64,U8toU64andBool) and its function, of typecint_status (*)(cint_ctx *ctx, int64_t fuel, int64_t depth, const uint64_t *args, uint64_t *result). An argument is a 64-bit pattern: sign-extended for a signed type, zero-extended for an unsigned type, 0 or 1 forBool. The function callscint_rt_entry_begin(ctx, <its entry site>, fuel, depth), returns that status unless it isCINT_OK, then runs the body and returnscint_rt_entry_end(ctx). It writes*resultonly when there is a result and the status isCINT_OK. - Entry functions arestatic, namedcg+ C(P) +_o<N>, with type signaturescg+ C(P) +_t<N>, numbered in each module in declaration order (7.7). Each module's C file defines its observer tablecg+ C(P) +_obsand its site table with external linkage, not exported, so that the program file can name them.
A change to this interface is a new version, cint-observe-2. The layouts of cint_observer and cint_observer_entry are declared in rt/cint_rt.h, which governs where it and this paragraph differ.
9.7 Further checks of a run
Semantics
- For each conformance program, the compiler is run twice: from two different working directories, with the source tree at two different absolute paths, and with different values of
TZ,LANGandSOURCE_DATE_EPOCH. The two sets of emitted C are compared byte for byte, and a difference fails the run [CONF-05]. - The reference writes the canonical type signature of each export (SPEC-03P.1), and
tools/cint_check.pycompares it, for every export of the suite that has a public wrapper, with the bytes that the compiler under test writes into the program's reflection table. A difference fails the run as an outcome difference does [CONF-16]. - A conformance run on
gpu-cudauses the same frozen expectations. Each compared case runs at two block sizes, 32 and 256 work-items (tools/cint_check.py --backend gpu-cuda --block 32and--block 256), on the device. The boundary matrices ofbnd64/also run as kernels, through the kernel tablesconformance/tables/kernel_<op>_<form>_i64.ci: one dispatch over the rows that do not fault, and one dispatch for each row that faults, with expectations in format 4. A case that the capability table ofgpu-cudadoes not support is unsupported, with its line inconformance/unsupported.txtnaming the capability (8.2) [CONF-17].
9.8 Running one case
Examples
- The reference checks one case against its frozen expectation. It prints nothing and exits with status 0 when they agree, and names the first line that differs otherwise:
$ python3 -m cint_ref check conformance/arith/add_i64_overflow.ci conformance/arith/add_i64_overflow.expect
$ echo $?
0
$ sed 's/^fuel-consumed 1$/fuel-consumed 2/' conformance/arith/add_i64_overflow.expect > wrong.expect
$ python3 -m cint_ref check conformance/arith/add_i64_overflow.ci wrong.expect
wrong.expect: line 6 differs
expected: fuel-consumed 2
actual: fuel-consumed 1
$ echo $?
1
tools/cint_check.pyruns cases through a compiler and compares them with their frozen expectations and with the reference.--onlyselects the cases whose name contains its argument, and--quickleaves out the table programs.--compiler b1 --cint "$BOOT/cint"tests the compiler inside thecintcommand that the bootstrap of 10.3 wrote into the directory$BOOT; without--cint, the tool first runs that bootstrap itself.--leg,--optand--helperschoose the configuration and--no-receiptwrites no receipt (11.3 writes them):
$ python3 tools/cint_check.py --leg gcc --opt 0 --helpers portable --quick --compiler b1 --cint "$BOOT/cint" --only arith/add_i64_overflow --no-receipt
inventory: 1 programs, 1 records, 0 cases classified without compiling; cint_ref against the frozen .expect files: 0 differ (23 ms)
...
runtime symbol audit: 131 defined external symbols, 0 outside cint_
gcc-0-portable-quick: 1 programs compiled, 1 records compared, 0 disagree, 0 sanitizer reports, 0 nondeterministic, 0 symbol audit findings, 0 type signatures compared, 0 type signature disagreements, 8 s; suite 5ccd378e30da7e1917bb03176f68a388d770da998075a10160043635f6e89d79
not compared: held 0, not_applicable 0, outside_subset 0, unsupported 0; disagreements before compiling: 0
...
The first line reports that the reference agrees with the frozen expectation. The runtime's own symbols are audited (7.10). The case was compiled twice (9.7) and compared, and no case fell in another category of 8.2. The digest after suite is the SHA-256 digest of the suite's files, which identifies the version of the suite.
10 Bootstrap
The compiler is written in cint, so building it needs a cint compiler. cint 0.2.0 breaks this circle with a C17 program written by hand, which compiles the compiler's source once. Every later compiler is built from that one. This clause describes that program, how the compiler is built from it, the check that the compiler reproduces itself, and what that check does not show.
10.1 Names
- These names are used in this clause only:
- C0: the C toolchain of a configuration.
- the seed: the bootstrap compiler
cint-seed, built by C0 fromseed/. - the seed's C: the C that the seed emits for the compiler's source. - the first compiler: the compiler that C0 builds from the seed's C, the runtime and the bridge. Thecintcommand contains the same compiler objects as the first compiler. - the first emitted C: the C that the first compiler emits for the compiler's source. - the second compiler: the compiler that C0 builds from the first emitted C. - the second emitted C: the C that the second compiler emits for the compiler's source.
10.2 The seed
Semantics
- The seed compiles the compiler's source into the seed's C, and, for its own conformance, programs of the boot subset of paragraph 7. It is not a compiler for users and is not installed [SEED-01].
- The seed is written by hand in C17. It is not generated from cint, and no generated C file is an input of the bootstrap [SEED-02].
- The seed depends only on the C17 standard library,
cint_rt.h, thecint_rtlibrary and the bridge. Its platform calls are confined to the bridge [SEED-03]. - The seed shares no code with
cintcother than the runtime and the bridge. It translates its parse tree directly into C and builds no SIR [SEED-04]. - The seed performs every run-time check of the boot subset and produces the full fault record, field for field identical to the one
cintcproduces, except the revision, which it reports as absent because it builds no SIR [SEED-05]. - The seed performs no optimization. It does not remove, merge or reorder run-time checks; it folds no expression that is not a constant expression; it does not inline functions, eliminate common subexpressions, hoist loop invariants or unroll loops; it does not choose a representation for a value from the value; and it relies on no extension of the C compiler other than those that
cint_rt.hselects. The C compiler that compiles the seed's C may optimize [SEED-13]. - The boot subset,
cint-boot-1, is the part of the language that the seed accepts: the integer typesI8toI64andU8toU64,Bool, the checked, wrapping and bitwise operators,asandas%, structs, local arrays of constant extent, rank-1 views as parameters, constants, functions,if,while, the C form and the range form offor,switch, andimportandexport. It excludes recursion, fixed point, the wide types, kernels and reductions, saturating operators,defer,tryand error unions, print statements,testblocks, compile-time execution beyond constant expressions and bytes outside ASCII in a source file. A string literal there is a read-only view of its bytes, of typein U8[_], and the typeStris not part of it. The seed refuses a program outside the subset with a diagnostic at the first construct outside it, andcintcaccepts the same program ascint-core-1[BOOT-01, BOOT-02]. - The seed emits C nested at most 200 levels deep, counting each brace level and each open
for (;;). A construct beyond that is C9004, so the seed compiles at most 199 nested blocks, 66 nestedwhileloops or 49 nestedforloops.else ifarms andswitcharms do not count. The limit is the seed's, not the subset's: such a program is inside the subset, and a conformance run reports it as unsupported by the seed [SEED-15]. - The seed holds the magnitude of an integer literal in 64 bits. Where only a literal's size matters, it decides as the reference does. Where the value would have to be reported, it refuses with C9102: a decimal literal of more than 1,233 significant digits, and a checked conversion
asof a literal of 2^64 or more. A conformance run reports such a case as unsupported by the seed [SEED-16].
Notes
- The root of trust that the seed adds is a set of files a reader can read. The C toolchain that compiles it stays trusted, and 10.5 says what checks it.
- Two translators of different structure that agree, such as the seed and
cintc, are stronger evidence than one translator checked against itself.
10.3 Building the compiler
Semantics
- The compiler's own source keeps these design rules: no call cycle; no print statement or other input or output; no module-level variable or statement, since state outside the tables that the bridge allocates would outlive a module; no call of a program function in a constant, a static assertion or an extent; and ASCII bytes only. In cint 0.2.0 the compiler's source is inside the boot subset, so the seed compiles it directly [BOOT-03].
- The compiler's sources, the seed, the runtime, the bridge and the harness use LF line endings, and the bootstrap refuses a CR byte in them [CINTC-07].
python3 tools/cint_bootstrap.py --leg gcc --out "$BOOT"builds the compiler with GCC into the directory$BOOT, which must lie outside the source tree. It builds, in order: the runtime objects; the seed, fromseed/*.c,rt/cint_rt.candrt/cint_bridge.c; the seed's C, by running the seed oncompiler/main.ciincompiler/, one C file for the whole compiler; the first compiler, from the seed's C,compiler/tests/golden_host.c, the runtime and the bridge; and thecintcommand, fromcli/and the same compiler objects. It then writescint, two toolchain files and the receiptbootstrap.jsoninto$BOOT.cint.toolchainis the configuration with whichcint runandcint buildcompile programs,-O2with the builtin helpers, andcint-bootstrap.toolchainthe one with which the first compiler andcintwere built,-O0with the portable helpers.- Digests are computed twice: by the
digestexport ofcompiler/sha256.ciin the first compiler, and by an external SHA-256 implementation. The bootstrap tool computes in the first compiler the digests of the source manifests, of the seed's C, ofcintand of the runtime sources, compares each with Python'shashliband fails on any difference. The receipt is written by the tool, not by the compiler, which performs no input or output and reads no clock [RCPT-05].
Examples
- The receipt of a bootstrap:
$ jq -r '.schema, .identity.profile, .identity.runtime_contract_version, .outcome.source_digest_verification' "$BOOT/bootstrap.json"
cint-bootstrap-receipt-1
cint-core-1
cint-rt-3
pass
$ jq -r '.identity.compiler_sources[].path' "$BOOT/bootstrap.json"
compiler/back_c.ci
compiler/back_ptx.ci
compiler/check.ci
compiler/decl.ci
compiler/emit.ci
compiler/fold.ci
compiler/limits.ci
compiler/lowering.ci
compiler/main.ci
compiler/parse.ci
compiler/resolve.ci
compiler/scan.ci
compiler/sha256.ci
compiler/verify.ci
$ jq -cS '{identity, outcome}' "$BOOT/bootstrap.json" | tr -d '\n' | sha256sum
2b4859c123ecdced8c3bb8ec5e6eee55c1de5fb55b5e53eb014dee1daf9d0cfc -
$ jq -r .identity_sha256 "$BOOT/bootstrap.json"
2b4859c123ecdced8c3bb8ec5e6eee55c1de5fb55b5e53eb014dee1daf9d0cfc
The identity names the 14 source files of the compiler with their digests, the runtime sources and the seed's source identity. The last two commands recompute the receipt identity (11.2) with external tools and show the value the receipt records.
10.4 The fixpoint
Semantics
- The first compiler compiles the compiler's source into the first emitted C, one C file for each module and a
MANIFEST; C0 builds the second compiler from it; and the second compiler compiles the same source into the second emitted C. The first and the second emitted C are byte-identical, file by file, theMANIFESTincluded. The seed's C is not compared with them, because the seed is a separate translator that does not optimize, and its C differs from that ofcintc[FIX-01]. - The fixpoint is checked by comparing bytes and by comparing the SHA-256 digest of each file, and both results are recorded in the receipt. The report of a difference names the first differing file, its byte offset and its line [FIX-02].
- The first and the second compiler emit byte-identical C for every program of the corpus: every
.ciprogram underconformance/,examples/andcompiler/tests/. This extends the fixpoint from one program, the compiler, to all of them [FIX-03]. - The digests of the executables of the seed and of the first and second compilers are recorded as observations, outside every identity. They differ between toolchains and may differ between two builds with one toolchain; for example, an MSVC link without
/Breprowrites a time stamp into the executable. No claim is made that executables are byte-identical [FIX-05]. python3 tools/cint_fixpoint.py --leg gcc --receipt FILEbuilds the seed, the two compilers and the two emitted C in one configuration and checks paragraphs 1 to 3;--optand--helperschoose the configuration,--sanitizeadds the sanitizers of 7.6 and--no-corpuschecks paragraphs 1 and 2 only. The receipt's identity holds the input digests and the digest of the first emitted C, and no path, time or executable digest.
10.5 What the fixpoint does not show
Semantics
- Cross-toolchain bootstrap agreement: the first emitted C is byte-identical whichever toolchain of the run matrix (8.4) is C0, at every optimization level.
python3 tools/cint_fixpoint.py --compare RECEIPT ...checks that a set of fixpoint receipts has one identity. Different C compilers produce different executables of the seed and of the first compiler, so a miscompilation that changed the compiler's emitted C would have to occur identically in every C compiler of the matrix to go undetected. The check shows that the compiler's emitted C does not depend on which toolchain of the matrix built the compiler. It does not show that any distributed executable corresponds to the compiler's source (10.4 paragraph 4); that the shared inputs, the compiler's source, the seed, the runtime and the bridge, are free of defects, since every toolchain compiles the same ones; or that the toolchains share no code or design [FIX-04].
Notes
- The idea of rebuilding a compiler with a diverse second compiler and comparing the result comes from Wheeler's diverse double-compiling. That technique makes a different claim: under its stated assumptions, the equality of the final executable with the distributed one shows that source and executable correspond. The check of paragraph 1 designates no trusted compiler and compares emitted C, so it makes no claim about executables and is not diverse double-compiling.
10.6 Reproducing the bootstrap
Examples
- A reader with a C17 toolchain, Python 3 and the source tree reproduces the bootstrap and the fixpoint with two commands, each on one configuration:
python3 tools/cint_bootstrap.py --leg gcc --out "$BOOT"
python3 tools/cint_fixpoint.py --leg gcc --receipt fixpoint-gcc-0-portable.json
--leg is gcc, clang or apple-clang on Linux and macOS, and msvc on Windows. The fixpoint receipt's identity is then compared with the published one (11.3).
11 Receipts and reproduction
A receipt records what a run read, what it concluded and what it observed of the machine. Its identity covers only the first two, so two machines that ran the same inputs to the same outcome produce the same identity. A published figure is rerun by producing a receipt and comparing its identity.
11.1 Identities
Semantics
| Identity | Inputs | What it determines |
|---|---|---|
| Build identity of emitted text | the sources and their module-relative paths, the compiler source identity, the language profile, the executor and its options | emitted C and PTX, byte for byte; C0 is not an input (7.7) |
| Executable observation | the above and C0 | executables and objects, recorded and not claimed identical (10.4) |
| Revision identity | 6.5 | the meaning of a program, shared by every build of it against one runtime contract version |
| Execution identity | the revision and the semantic inputs | the output bytes, the fault record and the fuel |
An x86-64 executable and a GPU kernel do not share a digest; built against one runtime contract version, they share the revision identity and the canonical outcomes.
11.2 Receipt content
Semantics
- A bootstrap, a fixpoint run and a conformance run each write a receipt. A receipt file is the receipt object followed by one LF, written as the JSON Canonicalization Scheme (RFC 8785) writes it, with keys sorted and no white space outside strings, except in the escaping of strings. That departure is this: a receipt holds ASCII characters only, so a character outside ASCII is written
\uwith four lowercase hexadecimal digits, and a character above U+FFFF as its UTF-16 surrogate pair, where RFC 8785 writes the character itself. The tools of this clause write a receipt with Python'sjson.dumpsand the argumentssort_keys=True,separators=(",", ":")andensure_ascii=True. They refuse a string outside ASCII, and they write U+007F as\u007f, where RFC 8785 and the tools of SPEC-06P.1 12.1 write it as itself. A receipt holds integers only, each within the I-JSON range of plus or minus 2^53 - 1; larger values and all digests are strings, and digests are 64 lowercase hexadecimal characters. Arrays of files are in byte order of path. The keyschemanames the format of the receipt and its version, such ascint-bootstrap-receipt-1orcint-conformance-receipt-2. A new version is written to a new file, and a receipt of an earlier version is not edited or regenerated; it stays valid for the sources it names.identity.encodingsrecords each encoding version that the receipt relies on, such as the.expectformats and the fault record version [RCPT-02]. - A receipt has three parts:
identity, the inputs and the digests of emitted text;outcome, the result of each check; andobservations, the timings, the host, the C toolchain, the digests of executables and the counts of executions. The receipt identity is the SHA-256 digest of the bytes of the object{"identity": ..., "outcome": ...}, written as paragraph 1 states. No field ofidentityoroutcomedepends on the toolchain or the host. The count of cases in the outcome counts distinct cases, not executions [RCPT-03]. - The compiler source identity is the SHA-256 digest of the source manifest of the compiler's source files. A source manifest is the bytes
CISRC001, the number of files as a 32-bit little-endian integer, and then, for each file in byte order of its path relative to the root of the tree, the path's length as a 16-bit little-endian integer, the path in UTF-8, the file's length as a 64-bit little-endian integer, and the 32-byte SHA-256 digest of its contents. In a fixpoint receipt, the digest of the first emitted C (10.1) is the digest of the source manifest of its files, with paths relative to the directory that holds them. The digest of the C that the bootstrap compiler emits is an observation, because that C depends on the version of the bootstrap compiler [RCPT-04].
11.3 Rerunning a published figure
Examples
- A published figure of agreement is a set of receipts. To rerun one, a reader builds
cintwith the bootstrap of 10.3, runstools/cint_check.pywith the same arguments, writes the receipt with--receiptand compares its identity with the published one. Two configurations of the same tree and the same suite have the same identity, because nothing in it depends on the configuration:
$ python3 tools/cint_check.py --leg gcc --opt 0 --helpers portable --quick --compiler b1 --cint "$BOOT/cint" --only arith/add_i64_overflow --receipt rc/receipt-gcc-0-portable.json
...
receipt written
$ python3 tools/cint_check.py --leg gcc --opt 2 --helpers builtin --quick --compiler b1 --cint "$BOOT/cint" --only arith/add_i64_overflow --receipt rc/receipt-gcc-2-builtin.json
...
gcc-2-builtin-quick: 1 programs compiled, 1 records compared, 0 disagree, 0 sanitizer reports, 0 nondeterministic, 0 symbol audit findings, 0 type signatures compared, 0 type signature disagreements, 14 s; suite 5ccd378e30da7e1917bb03176f68a388d770da998075a10160043635f6e89d79
...
receipt written
$ python3 tools/cint_receipts.py identity rc
receipt-gcc-0-portable.json 0dd4dfea0066583c pass
receipt-gcc-2-builtin.json 0dd4dfea0066583c pass
1 receipt identity in 2 receipts
$ python3 tools/cint_receipts.py compare --outcome rc/receipt-gcc-0-portable.json rc/receipt-gcc-2-builtin.json
compare: same outcome
$ jq -c '.identity | {schema, profile, runtime_contract_version, encodings}' rc/receipt-gcc-0-portable.json
{"schema":"cint-conformance-receipt-2","profile":"cint-core-1","runtime_contract_version":"cint-rt-3","encodings":{"expect_formats":[1,2,3,4,5],"fault":"cint-core-1/fault/v2","type_signature":"cint-core-1/type-signature/v1"}}
$ jq -cS '{identity, outcome}' rc/receipt-gcc-0-portable.json | tr -d '\n' | sha256sum
0dd4dfea0066583c8e5dcc9ddc73017defc38ee2a4443760b3231a1e2e11d7af -
$ jq -r .observations.receipt_identity_sha256 rc/receipt-gcc-0-portable.json
0dd4dfea0066583c8e5dcc9ddc73017defc38ee2a4443760b3231a1e2e11d7af
cint_receipts.py identity prints the first 16 hexadecimal digits of each receipt identity and whether the run passed. The last two commands compute the receipt identity with jq and sha256sum, which share no code with the tools of the tree; jq -cS writes the object with sorted keys and no spaces, which for these receipts are the bytes of 11.2, paragraph 1. A reader whose receipt identity equals the published one ran the same inputs to the same outcome.
- A report of a reproduction states the machine, the operating system, the toolchain and its version, the commands run, the resulting receipt identities and any deviation from the published procedure [DISC-07].
11.4 Claims
Semantics
- A published claim about cint is backed by material that a reader can obtain, normally a receipt. No claim of compliance with MISRA C, the SEI CERT C Coding Standard or another standard is made without a documented assessment against named editions and rules. Citations are to published material [DISC-02].
- The statement that emitted C has no undefined behavior is made only in this form [DISC-03]:
| Part | Content |
|---|---|
| Claim | Emitted C, cint_rt.h and the cint_rt library do not evaluate an operation whose C17 behavior is undefined, for programs that cintc accepts. |
| Design rules | 7.3 and 7.4. |
| Evidence | The conformance programs compiled and run under UndefinedBehaviorSanitizer and AddressSanitizer on the toolchains and versions that the receipts record, with no report. These sanitizers do not detect violations of the aliasing rules, unsequenced modifications or every lifetime error, so the evidence covers the programs run, not every accepted program. |
| Assumptions | A correct bootstrap compiler (10.2), cintc, runtime and bridge; the properties of 7.2 paragraph 2; a correct C toolchain for the constructs used (none of the toolchains of the matrix claims full C17 conformance); no foreign code; buffers from the host that meet the contract of SPEC-03P.1. |
| Exclusions | Exhaustion of the C stack; foreign functions; buffers from the host that break the contract. |
- A performance number is published only with its workload, machine, toolchain, flags and method of measurement [DISC-04].
12 Refusals of the cint tools in 0.2.0
This clause is informative. It lists the constructs that the reference runs and that a tool of cint 0.2.0 refuses, by construct, with the tool and the diagnostic, and the limits of the tools that are below the minimums of SPEC-04P.2. A refused program is not run with another meaning (8.2), and on an implementation that runs it, its outcome is the one SPEC-04P.2 specifies. The interpreter runs the SIR text that cint writes, so a construct that cint refuses does not reach it.
- The
cintcommand (cint run,cint build,cint testandcint emit-c) refuses these constructs with the diagnostic C9102, or the one that the row names, at the position shown:
| Construct | Example | Position |
|---|---|---|
Fixed-point types Q<i>.<f> and fraction literals |
Q16.16 a = 1.5;, Q8.8 x = 0.25; |
the type |
as? from a wide type |
I64 r = v as? I64 catch -1; with I128 v |
the operand |
| A result form on a wide type | I128 r = add_result(a, b) catch 0; |
the call |
switch on a wide value |
switch (v) with I256 v |
the scrutinee |
rotl and rotr by a count of a wide type |
rotl(a, n) with U64 a, I128 n |
the call |
muldiv of narrow operands into a wide result type |
muldiv(I128, a, b, c, floor) with I64 operands |
the call |
| An exported function with a parameter or a result of a wide integer type or a fixed-point type | export I64 low(I128 x) |
the parameter's name, or the result type |
| An array declared at the top level of a script | I64[3] a = [1, 2, 3]; |
the array literal, or the declaration when it has none |
| A struct type declared in a script | struct P { I64 x; I64 y; } before top-level statements |
the struct's name |
| An array literal of structs | P[2] ps = [P(1, 2), P(3, 4)]; |
the [ of the literal |
| A view variable over an array of structs | in P[_] v = ps; |
the variable's name |
| Named arguments in a call of a user function | diff(b = 1, a = 10) |
the first name |
A step by in a range for loop |
for i in 0..10 by 3 { ... } |
the step |
| A selected import | import geo.vec.{V2, dot2}; |
the import |
catch on a call of a function of an imported module |
parse.digit(c) catch -1 with import lib.parse; |
the function's name |
A catch expression as the operand of another operator |
(f(1) catch 0) + 1 |
the catch |
The wrapping negation -% |
I8 n = -%m; |
the operator |
len of a string literal |
len("abc") |
the string literal |
The format conversions !bits and !lanes(T) in a print statement |
"{x!bits}\n";, "{w!lanes(U8)}\n"; |
the print statement |
The format kind c |
"{c:c}\n"; |
the value of the hole |
| An array, or a slice of one, as the value of a hole | "{a}\n";, "{a[1..]}\n"; with I32[3] a |
the value of the hole, or the [ of the slice |
| An empty struct type | struct Empty { } |
the struct's name |
| An array literal as an argument of a struct constructor | Vec3([1, 2, 3]) |
the [ of the literal |
| An array literal as the right operand of an assignment, to an array or a slice | a = [3, 4];, a[0..2] = [3, 4]; |
the [ of the literal |
| A module-level array whose extent is the name of a constant or a constant expression | I64[N] table;, I64[N * 2] table; |
the name, or the operator of the expression |
| An array of handles | Handle(I64)[3] hs; |
the type |
| An exported arena | export Arena(I64, 4) store; |
the type |
An array element as the scalar out argument of a kernel |
total(a, r[0]); |
the [ of the element |
A local Arena(T) or Pool(T) initialized from a module-level name |
Arena(I64) s = scratch; |
the local's name |
| Arena storage, or a view variable over it, passed to a view parameter | total(scratch[h]) |
the call |
| A slice of arena storage, or of a view variable over it | scratch[h][2..4], v[2..5] |
the [ of the slice |
| A dispatch of a kernel of an imported module, called after the module's name or alias | dbl.twice(a, b); with import kmod.dbl; |
the kernel's name, with the diagnostic C3005 |
- A program whose entry is a function
main, rather than top-level statements, may declare struct types: the row of a struct declared in a script applies to scripts only. Thecintcommand also reports C9102 for==between two arrays, which the reference rejects with the diagnostic C2013 at the same position, so both reject such a program. It reports C9102 for a fraction literal given to an integer type, as inI64 x = 1.5;, which the reference rejects with the diagnostic C2004 at the same position. In the same way it reports C9102, at the print statement, for the conversion!ratioof an integer, as in"{x!ratio}\n";withI64 x. SPEC-04P.2 7.5.4 makes that conversion C1037, and the reference reports C1037 at the value of the hole. The C3005 of a kernel called after a module's name is not a refusal in the sense of SPEC-04P.2 4, paragraph 4, whose codes begin withC9: thecintcommand rejects that conforming program as it rejects an undefined name. - In one case the direction is reversed, and the reference refuses a run that the
cintcommand carries out. Forclamp(x, lo, hi)withlogreater thanhiat run time,cint runfaultsE_DOMAINwith the operationclamp.checked.<t>, such asclamp.checked.i64, and the operandsx,loandhi, with no exact result and no limit, as SPEC-04P.2 7.2.1 states. The reference reports the outcomerefusedfor that run. - A
gpu-cudabuild refuses a kernel whose body needs a capability that thegpu-cudatable lists as unsupported, withE_UNSUPPORTED, naming the kernel, the capability, the SIR node and the position (5.5). Thecpu-c17build of the same program runs it.
| Capability | Construct in a kernel body | Example |
|---|---|---|
sat |
the saturating operators +\|, -\| and *\| |
b[i] = a[i] +\| a[i]; |
div |
/ and % |
b[i] = a[i] / 2; |
shift |
<< and >>, wrapping or checked |
b[i] = a[i] << 1; |
bits |
&, \|, ^, ~ and ! |
b[i] = a[i] & 3; |
neg |
unary - |
b[i] = -a[i]; |
as.wrap |
the wrapping conversion as% |
(a[i] as% I32) as I64 |
builtin |
every built-in function except div_round |
b[i] = min(a[i], 2); |
switch |
switch |
switch (a[i]) { ... } |
store.narrow |
a store into a view of 8-bit, 16-bit or Bool elements |
out U8[n] b |
array |
a local array | I64[2] t; |
wide |
any value of I128 to I1024 |
in I128[n] a |
- Three limits of the tools of cint 0.2.0 are below the minimums that SPEC-04P.2 5.3 states. A program beyond any of them is stopped, and the reference runs it:
| Quantity | Minimum in SPEC-04P.2 5.3 | Limit of the tools | Beyond the limit |
|---|---|---|---|
| Size of one source file | 16,777,216 bytes | 4,194,304 bytes, in cint run, cint build, cint test and cint emit-c |
For the file it is given, the command stops with a tool error and exit status 5; an imported module of that size is the diagnostic C3009. The reference runs a file of 5,242,880 bytes. |
| Call-depth limit accepted as a semantic input | 4,096 active calls | 1,024 active calls, the constant CINT_RT_MAX_DEPTH of the runtime (7.10) |
An entry whose depth limit is larger faults E_UNSUPPORTED at call.enter before it runs, with the limit 1,024. The reference runs a recursion 4,000 calls deep under a depth limit of 4,096. |
| Size of an imported module, against the size of the root module | 16,777,216 bytes, as for every source file | A bound that grows with the size of the root module, in cint run, cint build, cint test and cint emit-c: 745,090 bytes for a root module of 34 bytes |
The command stops with the diagnostic C9001 at line 0 of the root module, position 0:0, though the imported module is under 4,194,304 bytes; SPEC-04P.2 5.3 places a C9001 that concerns a whole file at line 1. Under a root module of 34 bytes, an imported module of 745,091 bytes is C9001. Under a root module of 341 bytes, an imported module of 4,000,000 bytes runs, and so does a root module of 4,000,000 bytes that holds the same function. The reference runs the program. |
13 Future directions
This clause is informative. It names work that cint 0.2.0 does not include.
- Reproducible executables. The executables of a bootstrap differ between toolchains and may differ between two builds with one toolchain, so receipts record their digests as observations only (10.4). Byte-identical executables, built with options such as
-ffile-prefix-mapfor GCC and Clang and/Breprofor MSVC, let a receipt identify the executable as well as the C it was built from. - Diverse double-compiling. In Wheeler's technique, the compiler's source is compiled by a second compiler that is trusted, and the resulting executable is compared with the distributed one; equality shows that the source and the executable correspond. It needs reproducible executables and a designated trusted compiler, and it extends the agreement of 10.5 from emitted C to executables.
Annex A (informative) Measured agreement
- The figures of this annex are sets of receipts published with cint 0.2.0, and 11.3 shows how a reader reruns them. Every receipt was written from one source tree, whose suite digest is
3a19e408ac4c5822411ec2444a42d6507e97952a914591ccfa09e6375bd1377e. The source package of the release publishes the conformance tables as a separate file,cint-v0.2.0-conformance-tables.tar.gz, so a run of the package reports the suite digest of 11.3 and another receipt identity than the figures below. cpu-c17: 24 configurations pass with one receipt identity,1970cbf4dd7c5ef39f04cc10cbfa293702057fc31e6e64bea30ed2b80996a128. They are GCC and Clang on Linux x86-64 at-O0and-O2in both helper modes, each with and without the sanitizers of 7.6 (16); MSVC on Windows x86-64 at/Odand/O2in both helper modes (4); and Apple Clang on macOS AArch64 at-O0and-O2in both helper modes (4). Each compiles 658 programs and compares 1,868,066 records with no disagreement; 11 cases are held, 67 are not applicable and 47 are unsupported, the cases ofconformance/fixed/(clause 12).cpu-c17andcpu-sir-interptogether: 44 of the 48 configurations of this run pass, with the counts of paragraph 2 and two receipt identities,30ffed5415bcc145a41090b4411c111120393191bca59e31120dd42fae016d89forcpu-c17and4c89cb670ff83b3a9d425d9b5c47b430e99a3cca7b20ba7d99ebad3c1733b35fforcpu-sir-interp. Four configurations with GCC 16.2.1 and Clang 22.1.8 on Linux x86-64 did not complete, so this figure is incomplete: GCC at-O2in portable mode on both executors, the same with the sanitizers oncpu-c17, and Clang at-O0in portable mode oncpu-c17.- Sanitized runs: 23 configurations of GCC and Clang on Linux x86-64 under UndefinedBehaviorSanitizer and AddressSanitizer, 15 on
cpu-c17and 8 oncpu-sir-interp, report no finding. - The fixpoint (10.4 and 10.5) passes on four toolchains, MSVC, GCC 16.2.1, Clang 22.1.8 and Apple Clang, with one receipt identity,
8cd0acfaec5d39075f319f1debda5d136deaec40fb6f538f2e2a7e101eb5a1c1. The fixpoint receipts record the build directories of the machines that made them, so the receipts asset leaves them out; the runtime decoder fuzz receipts are left out for the same reason. - The conformance runs of the executor
gpu-cudado not meet their required case list (9.5) in cint 0.2.0. Thecintcommand refuses fixed-point types on every executor (clause 12), andconformance/unsupported.txtlists the 47 cases ofconformance/fixed/as unsupported forcpu-c17only, so no line lists their refusal ongpu-cuda, and under 8.2 each such refusal is a disagreement. cint 0.2.0 claims no figure of agreement forgpu-cuda.
Bibliography
- David A. Wheeler. Fully Countering Trusting Trust through Diverse Double-Compiling. PhD dissertation, George Mason University, 2009. arXiv:1004.5534. Diverse double-compiling, in 10.5 and 13.
- Free Software Foundation. "Integer Overflow Builtins." Using the GNU Compiler Collection (GCC). https://gcc.gnu.org/onlinedocs/gcc/Integer-Overflow-Builtins.html. The builtins of the helper mode of 7.3, paragraph 2.
- LLVM Project, the Clang Team. "UndefinedBehaviorSanitizer." Clang documentation. https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html. The sanitized conformance runs of 7.6 and 11.4.
- LLVM Project, the Clang Team. "AddressSanitizer." Clang documentation. https://clang.llvm.org/docs/AddressSanitizer.html. The sanitized conformance runs of 7.6 and 11.4.
Annex B (informative) Clause identifiers stated in other editions
A tool message, or an earlier edition, may cite a clause identifier of this document's series whose rule a 0.2.0 edition states elsewhere. Each such identifier is listed here with the edition and section that state it, or, where no 0.2.0 edition states its rule, with the section that describes the topic.
| Identifier | Stated in |
|---|---|
| BACK-07 | SPEC-02P.1 9.1 |
| BACK-08 | SPEC-02P.1 9.2 |
| BACK-13 | SPEC-02P.1 10.6 |
| CINTC-09 | SPEC-06P.1 9.9 |