Skip to content

Releases: tanakh/cinrs

v0.3.0

Choose a tag to compare

@tanakh tanakh released this 13 Oct 07:30

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 an enum type is a c_uint where it
    was a c_int; the enumerators stay c_int. Rust code that passes an
    enumerator where the type is expected, or compares a value of the type with
    one, needs an as. 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 symbol strlen, 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_EPOCH fixes the moment for a reproducible build.
  • For direct users of cinrs-core: Options and the IR's Program,
    Function, Object, RecordDef, Field, PointerType and ArrayType
    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 under c23! and every GNU dialect, as in 0.2.0;
    ccinrs runs the suite through its command line to the same 218 of 220.
    See doc/c-testsuite.md.
  • GCC's C torture tests: 1,698 of the 1,769 run are correct (96.0 %)
    under gnu11!, up from 1,643, and 1,693 (95.7 %) under gnu89!, up from
    1,636; most of the gain is cases that use GCC's vector extensions or
    setjmp.
    See doc/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 620 expected-error lines 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 -O2 and a cinrs block:
    median cinrs/gcc -O2 ratio 1.00×, 42 of 48 within 10 % of gcc -O2
    or faster, every program's output identical across the three builds
    (doc/benchmarks.md, measured on Rust 1.99.0).
    spectral-norm-sse2 is still the one row slower than both compilers
    (1.12×), the price of signed arithmetic that wraps. dhrystone takes 0.83×
    GCC's time but twice Clang's: a local char[31] is now 16-byte aligned as
    the psABI asks (see Changed), which in Rust makes the object 32
    bytes, and the strcmp of it that Clang folds — and cinrs folded before —
    is a call again, as in GCC.
  • ccinrs on 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 at make -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; see doc/ccinrs.md, and
    doc/ccinrs-real-programs.md for the 27
    programs it has built. Each C file is translated by cinrs and compiled by
    rustc into an ordinary object, and rustc links the program, so make CC=ccinrs — or CMake told that ccinrs is the C compiler — builds a C
    project with no C compiler involved. cargo install ccinrs installs it,
    and what it needs to build a program is what cargo build needs.
    • GCC's defaults: gnu17, GCC 14.2's predefined macros, the platform's
      headers before the bundled ones, every non-static definition a symbol.
      Rust's run-time checks are on unless -fno-cinrs-checks says otherwise,
      and a panic names the C file and line: the Rust is printed on the C's
      lines. At -O1 and 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.
    • -c objects carry the rustc that made them, and a link with another
      refuses them in one sentence; -S writes the Rust; -E prints the
      preprocessed C with GCC's line markers, and -dM the macros;
      -fsyntax-only checks and writes nothing; -M, -MM, -MD, -MMD,
      -MF, -MT, -MQ and -MP write Makefile rules under GCC's names;
      -include and standard input (-) are read as GCC reads them.
    • --target= for another machine, with nothing to install but the Rust
      target: wasm32-wasip1 and wasm32-wasip2; musl, linked by rust-lld
      itself into a static program on a machine with no C compiler at all;
      and wasm32-unknown-unknown, as a module that exports the C's functions
      and a malloc family and imports what the C leaves undefined. -march=
      and -m<feature> as rustc's target CPU and features, with the feature
      macros to match.
    • -flto compiles each file to an rlib and has the link optimise them as
      one program, cinrs's runtime and Rust's standard library included.
    • -shared links an ELF shared library that exports the C symbols, those
      of an archive between -Wl,--whole-archive and -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, -Xlinker and -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 a configure probe looks
      for, and which makes every warning an error, #warning included. A
      version script is found however it is given, libtool's
      -Wl,--version-script -Wl,file included, and a wildcard in its global:
      list stands for the C symbols, not for Rust's standard library linked in
      beside them. -E takes a header. -fuse-ld= chooses a
      program's linker (GNU ld for bfd, where rustc's own is LLD), and
      -pie, -no-pie and the hardening options distributions pass are taken.
      /dev/null is 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.
    • _Complex works: cinrs-rt's source is compiled with the user's rustc
      the first time a program needs it and kept in a cache directory.
    • scripts/check-ccinrs.sh builds lz4, cJSON, cmark, brotli, zlib and
      expat with their own build systems — Makefiles, CMake, zlib's configure,
      autoconf and libtool — and runs their tests, and runs c-testsuite through
      the command line (218 of 220). -v ends with GCC's gcc version … line,
      which is what a configure script reads to decide that a compiler takes
      GCC's options.
  • setjmp and longjmp, as a Rust unwind: longjmp is resume_unwind
    to the activation the jmp_buf names, and a function that calls setjmp
    runs its control-flow graph inside catch_unwind, with its locals hoisted
    outside — so they keep their latest values — re-entering at the setjmp
    the jump came back to. setjmp, _setjmp, sigsetjmp (signal mask
    included), __builtin_setjmp and their longjmps, from the bundled
    <setjmp.h> (now a real header) or the platform's, and longjmp's address.
    A setjmp where C17 7.13.1.1p4 allows it, plus r = setjmp(buf),
    int r = setjmp(buf), if ((r = setjmp(buf)) == 0) and, in the condition
    of an if or a loop, an operand of && or || (OpenSSL's if (!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 with ccinrs, and six gcc-torture cases
    pass. See doc/limitations.md for
    what works and what does not.
    • A longjmp that 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 whose setjmp has
      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 —
      ...
Read more

v0.2.0

Choose a tag to compare

@tanakh tanakh released this 02 Oct 03:46

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_SUPPORTED and
    Options::c_variadic are 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 under c23! and every GNU dialect — up from 214 and
    217, because 00140, which defines a variadic function, passes on every
    supported compiler now. See doc/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 %) under gnu89!. Sixty cases that
    were refused on their inline assembly now run, execute/bitfld-5 with them,
    the four _FloatN issignaling cases with the keywords, and the sixty-one
    that needed only c_variadic with the new minimum Rust; execute/pr117432,
    which calls C23's one-argument va_start, passes under gnu23! and is the
    conforming refusal it is under gnu11! and gnu89!. See
    doc/gcc-torture.md.
  • Clang's C conformance tests: 167 of the 203 revisions run are correct
    (82.3 %)
    , and 557 of the 620 expected-error lines 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 -O2 and a
    cinrs block: median cinrs/gcc -O2 ratio 1.01×, 42 of 48 within 10 %
    of gcc -O2 or 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 under scripts/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,
    __m256i and __m256d are 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 because core::arch was
    generated from the same Intel data Intel's own headers are. The bundled
    headers' prototypes were read out of core::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, struct members and the
    members of the union { __m128i v; int32_t i[4]; } that most code reaches a
    lane through. An immediate operand is a const generic in core::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 are static inline functions, so real code does
    — through a private extern "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's extern block
    or to its glob re-export, so a use core::arch::x86_64::* in the surrounding
    Rust cannot clash with them.

    See SIMD intrinsics for the coverage per
    instruction set and the intrinsics whose core::arch signature C cannot
    spell; tests/simd.rs runs every family, with the expected values
    computed in scalar C in the same block and cross-checked against gcc -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, which core::arch dropped — <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, __m512i and __m512d (sixty-four bytes, aligned to
    sixty-four), the bfloat16 vectors __m128bh/__m256bh/__m512bh and the
    half-precision __m128h/__m256h/__m512h; the masks __mmask8 to
    __mmask64 are plain unsigned integers, as in GCC, and the _MM_*_ENUM
    immediates are int, with _MM_CMPINT_*, _MM_MANT_* and _MM_PERM_* —
    in GCC's lower-case spellings (_MM_MANT_SIGN_src) as well as core::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
    needs avx; rustc refuses it otherwise (tests/ui/simd_abi_512.rs). target
    and __builtin_cpu_supports know every one of the new names — the fourteen
    avx512*, gfni, vaes, vpclmulqdq, avxvnni, avxvnniint8,
    avxvnniint16, avxifma, avxneconvert, sha512, sm3, sm4, kl and
    widekl, spelled as GCC 15 spells them — and a feature list such as
    gfni,avx512bw,avx512vl is read out as the instruction sets it names in a
    diagnostic. A memory operand takes any object pointer: a pointer parameter
    is void * or const void * exactly where GCC 15.2's headers declare it so
    — 335 of them, _mm_prefetch and the SSE2 _mm_loadu_si16 family included
    — and the generated call casts every pointer argument to core::arch's
    type, so _mm512_loadu_si512(p) takes an int * without a cast, as in GCC.
    Still out, because core::arch keeps them unstable: AVX512-VP2INTERSECT,
    and the fifty FP16 and BF16 intrinsics that take or return a scalar f16 or
    bf16.

  • GCC's vector operators, subscripts and brace initialisers on the Intel
    vector types.
    In GCC __m128d is a vector of two doubles, and real code
    writes a * 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 the target,
    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
    reversed i[v], and lanes for a vector with static storage duration, which
    would be a call...

Read more

v0.1.0

Choose a tag to compare

@tanakh tanakh released this 22 Sep 03:41