Repository navigation
Releases: tanakh/cinrs
Release list
v0.3.0
0.3.0 — 2026-10-13
This release adds a second way to use cinrs: ccinrs, a C compiler with
GCC's command line, in a fifth crate versioned with the other four. Each C
file becomes Rust that rustc compiles into an ordinary object, and rustc
links the program, so make CC=ccinrs — or a configure script or CMake told
that ccinrs is the C compiler — builds a C project with no C compiler
involved, and with Rust's run-time checks on. Twenty-seven open-source
programs were built that way from their release tarballs, from bzip2 and
SQLite to git, nginx, Redis, OpenSSL, FFmpeg and CPython, and their own test
suites run; see doc/ccinrs-real-programs.md.
What they needed is most of the rest of this release, and the macros have it
too: setjmp and longjmp, GCC's vector extensions, weak symbols, transparent
unions, thread-local objects shared between units, inline functions that
provide no external definition, volatile accesses that are volatile, and
many smaller fixes.
Upgrading from 0.2
- An enumeration with no negative enumerator is
unsigned int, as in GCC
and Clang, so the Rust alias of such anenumtype is ac_uintwhere it
was ac_int; the enumerators stayc_int. Rust code that passes an
enumerator where the type is expected, or compares a value of the type with
one, needs anas. See Changed. - On macOS an
__asm__label is the assembler's symbol, as in GCC and
Clang, so it spells Mach-O's leading underscore itself:__asm__("strlen")
now names the symbolstrlen, and the C library's is
__asm__("_strlen"), which__USER_LABEL_PREFIX__spells portably. Other
targets are unchanged. See Fixed. __DATE__and__TIME__are the moment of translation, not
placeholders, so a block that uses them expands differently from one build
to the next;SOURCE_DATE_EPOCHfixes the moment for a reproducible build.- For direct users of
cinrs-core:Optionsand the IR'sProgram,
Function,Object,RecordDef,Field,PointerTypeandArrayType
have new public fields, so a struct literal of one needs them;Options::new
followed by field updates does not. See Added.
Conformance and speed, as measured for this release
- c-testsuite: 215 of the 218 cases
c99!is eligible for are correct
(98.6 %), 218 of 220 underc23!and every GNU dialect, as in 0.2.0;
ccinrsruns the suite through its command line to the same 218 of 220.
Seedoc/c-testsuite.md. - GCC's C torture tests: 1,698 of the 1,769 run are correct (96.0 %)
undergnu11!, up from 1,643, and 1,693 (95.7 %) undergnu89!, up from
1,636; most of the gain is cases that use GCC's vector extensions or
setjmp.
Seedoc/gcc-torture.md. - Clang's C conformance tests: 169 of the 203 revisions run are correct
(83.3 %), up from 167, and 582 of the 620expected-errorlines land on
the right line, up from 557. - Not one case in any of the three is tagged
[bug]. - 48 whole C programs built as
gcc -O2,clang -O2and acinrsblock:
mediancinrs/gcc -O2ratio 1.00×, 42 of 48 within 10 % ofgcc -O2
or faster, every program's output identical across the three builds
(doc/benchmarks.md, measured on Rust 1.99.0).
spectral-norm-sse2is still the one row slower than both compilers
(1.12×), the price of signed arithmetic that wraps.dhrystonetakes 0.83×
GCC's time but twice Clang's: a localchar[31]is now 16-byte aligned as
the psABI asks (see Changed), which in Rust makes the object 32
bytes, and thestrcmpof it that Clang folds — and cinrs folded before —
is a call again, as in GCC. ccinrson real programs: 27 open-source programs built from their
release tarballs with their own build systems, their own test suites run
against GCC's build (doc/ccinrs-real-programs.md);
SQLite, Lua, git, Redis, libuv, zstd, jq and OpenSSL were built and tested
again with this release, with the same results. With the run-time checks
on, most take 1.0 to 1.5 times GCC's time, and about GCC's own without
them; interpreters pay most (CPython 1.70× with the checks, 1.30× without).
Building takes longer than with GCC: git 2.2× GCC's time atmake -j2
(114.6 s against 53.1 s), libuv 2.1×, SQLite 1.2×.
Added
ccinrs, a C compiler with GCC's command line, in a fifth crate
versioned with the other four; seedoc/ccinrs.md, and
doc/ccinrs-real-programs.mdfor the 27
programs it has built. Each C file is translated by cinrs and compiled by
rustcinto an ordinary object, andrustclinks the program, somake CC=ccinrs— or CMake told thatccinrsis the C compiler — builds a C
project with no C compiler involved.cargo install ccinrsinstalls it,
and what it needs to build a program is whatcargo buildneeds.- GCC's defaults:
gnu17, GCC 14.2's predefined macros, the platform's
headers before the bundled ones, every non-staticdefinition a symbol.
Rust's run-time checks are on unless-fno-cinrs-checkssays otherwise,
and a panic names the C file and line: the Rust is printed on the C's
lines. At-O1and above LLVM's inline thresholds are raised, so that
the checks do not change which functions are inlined; without the checks
they still make CPython's interpreter about 3 % faster. -cobjects carry therustcthat made them, and a link with another
refuses them in one sentence;-Swrites the Rust;-Eprints the
preprocessed C with GCC's line markers, and-dMthe macros;
-fsyntax-onlychecks and writes nothing;-M,-MM,-MD,-MMD,
-MF,-MT,-MQand-MPwrite Makefile rules under GCC's names;
-includeand standard input (-) are read as GCC reads them.--target=for another machine, with nothing to install but the Rust
target:wasm32-wasip1andwasm32-wasip2; musl, linked byrust-lld
itself into a static program on a machine with no C compiler at all;
andwasm32-unknown-unknown, as a module that exports the C's functions
and amallocfamily and imports what the C leaves undefined.-march=
and-m<feature>asrustc's target CPU and features, with the feature
macros to match.-fltocompiles each file to an rlib and has the link optimise them as
one program, cinrs's runtime and Rust's standard library included.-sharedlinks an ELF shared library that exports the C symbols, those
of an archive between-Wl,--whole-archiveand-Wl,--no-whole-archive
included, as libtool links a convenience library into one, and those of
an object another compiler or an assembler made, read off its symbol
table;
-static,-rdynamic,--version,-dumpversion,-dumpmachine, and
the-print-*questions libtool asks,-Xlinkerand-l:file. GCC's
warning options are taken without a word; an unknown one is a warning,
and an error under-Werror, which is what aconfigureprobe looks
for, and which makes every warning an error,#warningincluded. A
version script is found however it is given, libtool's
-Wl,--version-script -Wl,fileincluded, and a wildcard in itsglobal:
list stands for the C symbols, not for Rust's standard library linked in
beside them.-Etakes a header.-fuse-ld=chooses a
program's linker (GNU ld forbfd, whererustc's own is LLD), and
-pie,-no-pieand the hardening options distributions pass are taken.
/dev/nullis a C file like any other, and a file for the linker that
does not exist is an error even when nothing is linked, as in GCC 15._Complexworks: cinrs-rt's source is compiled with the user'srustc
the first time a program needs it and kept in a cache directory.scripts/check-ccinrs.shbuilds lz4, cJSON, cmark, brotli, zlib and
expat with their own build systems — Makefiles, CMake, zlib'sconfigure,
autoconf and libtool — and runs their tests, and runs c-testsuite through
the command line (218 of 220).-vends with GCC'sgcc version …line,
which is what aconfigurescript reads to decide that a compiler takes
GCC's options.
- GCC's defaults:
setjmpandlongjmp, as a Rust unwind:longjmpisresume_unwind
to the activation thejmp_bufnames, and a function that callssetjmp
runs its control-flow graph insidecatch_unwind, with its locals hoisted
outside — so they keep their latest values — re-entering at thesetjmp
the jump came back to.setjmp,_setjmp,sigsetjmp(signal mask
included),__builtin_setjmpand theirlongjmps, from the bundled
<setjmp.h>(now a real header) or the platform's, andlongjmp's address.
Asetjmpwhere C17 7.13.1.1p4 allows it, plusr = setjmp(buf),
int r = setjmp(buf),if ((r = setjmp(buf)) == 0)and, in the condition
of anifor a loop, an operand of&&or||(OpenSSL'sif (!r || !_setjmp(env))), and anywhere else a located error. libpng's default build, TurboJPEG, xz's unit tests and Lua
now build and pass their suites withccinrs, and six gcc-torture cases
pass. Seedoc/limitations.mdfor
what works and what does not.- A
longjmpthat cannot be done stops the program with a message of
cinrs's own before anything is unwound: out of a signal handler that
interrupted the program's own code, onto a buffer whosesetjmphas
returned or ran on another thread or stack, or through a frame without
unwind tables. It walks the stack first, which is part of its cost —
...
- A
v0.2.0
0.2.0 — 2026-10-02
Rust 1.99 made c_variadic stable, and this release is built on it: a
variadic function definition is translated on every supported compiler, and
1.99 is the minimum. The rest is what real C asked for — the x86 SIMD
intrinsics up to AVX-512, GCC's inline assembly, a relooper that gives
goto-heavy code its loops back, and cinrs presenting itself as GCC 14.2 —
measured against SQLite, BLAKE3 and xxHash in the repository and ten more
programs outside it.
Upgrading from 0.1
- Rust 1.99 or later is required.
<unistd.h>,<fcntl.h>,<strings.h>and<sys/types.h>are no longer
bundled. A unit that includes one needs#pragma cinrs system_include(or
CINRS_SYSTEM_INCLUDE=1), which brings the platform's complete headers; see
Changed.__GNUC__is 14, up from Clang's 4.2.1, so code that tests the
compiler's version takes GCC 14's branches;__CINRS__is what says it is
cinrs.- For direct users of
cinrs-core:C_VARIADIC_SUPPORTEDand
Options::c_variadicare gone, since every supported compiler has the
feature.
Conformance and speed, as measured for this release
- c-testsuite: 215 of the 218 cases
c99!is eligible for are correct
(98.6 %), 218 of 220 underc23!and every GNU dialect — up from 214 and
217, because00140, which defines a variadic function, passes on every
supported compiler now. Seedoc/c-testsuite.md. - GCC's C torture tests: 1,643 of the 1,769 run are correct (92.9 %) under
gnu11!, up from 1,515, and 1,636 (92.5 %) undergnu89!. Sixty cases that
were refused on their inline assembly now run,execute/bitfld-5with them,
the four_FloatNissignalingcases with the keywords, and the sixty-one
that needed onlyc_variadicwith the new minimum Rust;execute/pr117432,
which calls C23's one-argumentva_start, passes undergnu23!and is the
conforming refusal it is undergnu11!andgnu89!. See
doc/gcc-torture.md. - Clang's C conformance tests: 167 of the 203 revisions run are correct
(82.3 %), and 557 of the 620expected-errorlines land on the right line,
as in 0.1.0. - Not one case in any of the three is tagged
[bug]. - 48 whole C programs, up from 39, built as
gcc -O2,clang -O2and a
cinrsblock: mediancinrs/gcc -O2ratio 1.01×, 42 of 48 within 10 %
ofgcc -O2or faster, every program's output identical across the three
builds (doc/benchmarks.md, measured on Rust 1.98.1).
The one row slower than both compilers,spectral-norm-sse2, is the price of
signed arithmetic that wraps, which is a decision rather than a gap; see
doc/translation.md. - Real programs: SQLite 3.53.4, BLAKE3 1.8.7 and xxHash 0.8.4 build, run and
pass their own checks underscripts/check-*.sh, and CRoaring, lz4, cJSON,
libdeflate, stb_image, chibicc, cmark, brotli, Kissat and Wren were compiled
unedited outside the repository; each has its gcc and clang timings in
doc/real-programs.md.
Added
-
The x86 SIMD intrinsics.
#include <immintrin.h>and write Intel's
intrinsics the way real C does:__m128,__m128i,__m128d,__m256,
__m256iand__m256dare types, and 881 functions — SSE, SSE2, SSE3, SSSE3,
SSE4.1, SSE4.2, AVX, AVX2, FMA, AES, PCLMUL, SHA, and the BMI1, BMI2, POPCNT
and LZCNT scalar ones — are declared. There is no vector language and no new
syntax: the mapping is by name. A call to_mm_add_ps(a, b)becomes
::core::arch::x86_64::_mm_add_ps(a, b), which works becausecore::archwas
generated from the same Intel data Intel's own headers are. The bundled
headers' prototypes were read out ofcore::arch's source by a maintainer
tool that writes both the header and the table driving the mapping
(crates/cinrs-core/tests/x86_intrinsics.rs), so a declaration and the
function it resolves to cannot drift apart — and a test that needs no
toolchain checks the two committed files against each other on every run.
<xmmintrin.h>,<emmintrin.h>,<pmmintrin.h>,<tmmintrin.h>,
<smmintrin.h>,<nmmintrin.h>,<wmmintrin.h>,<avxintrin.h>,
<avx2intrin.h>,<x86intrin.h>and<popcntintrin.h>are bundled too, in
the layering GCC and Clang give them, so code written for an older compiler
finds its instruction set under the header name it expects. The macros come
with them:_MM_SHUFFLE,_MM_SHUFFLE2,_MM_TRANSPOSE4_PS,_MM_HINT_*,
_MM_FROUND_*,_CMP_*,_SIDD_*and the rest, with the values GCC's
headers give them.The vector types are ordinary objects of a known size and alignment —
sixteen or thirty-two bytes, aligned to themselves — so they work as locals,
parameters, return values,statics, array elements,structmembers and the
members of theunion { __m128i v; int32_t i[4]; }that most code reaches a
lane through. An immediate operand is aconstgeneric incore::arch,
so the argument is folded and written into a turbofish
(_mm_slli_epi32::<{ 3i32 }>(v)); a non-constant is a diagnostic naming the
intrinsic, which is what Intel's own compilers say too. Taking the address
of an intrinsic works — GCC's arestatic inlinefunctions, so real code does
— through a privateextern "C"shim per intrinsic; an intrinsic with an
immediate operand has no address, and that is a diagnostic. Nothing is
exported: those prototypes add not one item to the unit'sexternblock
or to its glob re-export, so ause core::arch::x86_64::*in the surrounding
Rust cannot clash with them.See SIMD intrinsics for the coverage per
instruction set and the intrinsics whosecore::archsignature C cannot
spell;tests/simd.rsruns every family, with the expected values
computed in scalar C in the same block and cross-checked againstgcc -O2.
What stays refused, now with a diagnostic that names the intrinsic to write
instead: the GNU vector extensions,
__attribute__((vector_size))and__builtin_ia32_*. What is not here:
MMX and__m64, whichcore::archdropped —<mmintrin.h>is an#error
that names the SSE2 form of each intrinsic. -
AVX-512, and what arrived with it. The same mapping now covers
AVX-512F, BW, CD, DQ and VL, the VBMI, VBMI2, VNNI, BITALG, VPOPCNTDQ, IFMA,
BF16 and FP16 extensions, GFNI, VAES and VPCLMULQDQ at every width, AVX-VNNI
with its INT8 and INT16 forms, AVX-IFMA, F16C, SHA-512, SM3 and SM4: 6,075
intrinsics in all, 5,194 more than the SSE-to-AVX2 set above. The new types
are__m512,__m512iand__m512d(sixty-four bytes, aligned to
sixty-four), the bfloat16 vectors__m128bh/__m256bh/__m512bhand the
half-precision__m128h/__m256h/__m512h; the masks__mmask8to
__mmask64are plain unsigned integers, as in GCC, and the_MM_*_ENUM
immediates areint, with_MM_CMPINT_*,_MM_MANT_*and_MM_PERM_*—
in GCC's lower-case spellings (_MM_MANT_SIGN_src) as well ascore::arch's.
<immintrin.h>pulls in thirty-five new headers under GCC's names, from
<avx512fintrin.h>and<avx512vlintrin.h>to<sm4intrin.h>, and each one
included on its own works too. A function that passes or returns a 512-bit
vector by value needs__attribute__((target("avx512f"))), as a 256-bit one
needsavx; rustc refuses it otherwise (tests/ui/simd_abi_512.rs).target
and__builtin_cpu_supportsknow every one of the new names — the fourteen
avx512*,gfni,vaes,vpclmulqdq,avxvnni,avxvnniint8,
avxvnniint16,avxifma,avxneconvert,sha512,sm3,sm4,kland
widekl, spelled as GCC 15 spells them — and a feature list such as
gfni,avx512bw,avx512vlis read out as the instruction sets it names in a
diagnostic. A memory operand takes any object pointer: a pointer parameter
isvoid *orconst void *exactly where GCC 15.2's headers declare it so
— 335 of them,_mm_prefetchand the SSE2_mm_loadu_si16family included
— and the generated call casts every pointer argument tocore::arch's
type, so_mm512_loadu_si512(p)takes anint *without a cast, as in GCC.
Still out, becausecore::archkeeps them unstable: AVX512-VP2INTERSECT,
and the fifty FP16 and BF16 intrinsics that take or return a scalarf16or
bf16. -
GCC's vector operators, subscripts and brace initialisers on the Intel
vector types. In GCC__m128dis a vector of two doubles, and real code
writesa * b + c,v * 2.0,1.0 / v,-v,v += w,v[1]and
(__m128d){x, y}on it. Each is now lowered to the intrinsic that does the
same, a call of the function the header declares, so thetarget,
pointer and[[cinrs::safe]]rules apply unchanged:+ - * /and& | ^
on the float and double vectors at every width,+ -and the bitwise
operators on the integer vectors as GCC's 64-bit lanes, unary-as a
sign-bit flip, the comparisons on the 128- and 256-bit float and double
vectors typed as the same-size integer vector, a scalar on either side
converted to the lane type and broadcast, and the compound assignments.
v[i]is a lane of the lane type — an lvalue when the vector is an object
— with a constant index out of range an error;{a, b}lists the lanes in
memory order (_mm_setr_pd), the missing ones zero. Refused, each naming
the intrinsic to write: integer*,/,%, shifts and comparisons, the
512-bit comparisons, every operator on the bfloat16 and half-precision
vectors, designators in a vector's braces,~on a float vector, the
reversedi[v], and lanes for a vector with static storage duration, which
would be a call...