Repository navigation
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 —
about 1.2 µs and 130 ns a frame, against GCC's tens of nanoseconds. - A
cleanupattribute does not run when alongjmp(or a Rust panic)
leaves its scope, as in GCC. - Refused on WebAssembly, under
#pragma cinrs no_stdand with
panic = "abort".
- A
- A thread-local object shared between units: one with external linkage
that a unit gives a symbol (#pragma cinrs export, every file of
ccinrs) exports an accessor,name.cinrs_tls, returning the calling
thread's copy, andextern _Thread_local(extern __thread) in another
unit reaches the object through it. Both were refused; Redis's
__thread sds thread_reusable_qband jemalloc'sconfigureTLS probe need
them. An object a C compiler made cannot share one. ccinrs: along doublefunction of the program's own — declared in
its own files rather than in the platform's headers — is called with
cinrs'slong double, thedoubleits definition, compiled byccinrs
too, takes; Redis'sld2stringandstring2ldwere refused as if they were
the C library's.Options::own_declarations_are_cinrsis the switch; a
c99!block keeps refusing, since it may be calling a C library a C
compiler built through that library's own header.- Uninitialised local arrays, on request:
ccinrs -ftrivial-auto-var-init=uninitialized(GCC's option;zerois the default
andpatternis taken aszerowith a warning) and#pragma cinrs auto_var_init uninitialized. A local array declared without an initialiser
is then aMaybeUninit, reached through a raw pointer, instead of being
cleared on every entry to its block, and the arena behind variable length
arrays andallocastops clearing; reading an element nothing wrote is
undefined behaviour, as in C. Scalars, structures and unions stay
zero-filled. libuv'suv__io_pollin miniature goes from 1.04 s to 0.006 s
(gcc 0.009 s). Seedoc/translation.md. #pragma cinrs unwind, andextern "C-unwind"for a unit that calls
setjmporlongjmp: every function, declaration and function pointer
type of the unit, which alongjmpcan then pass through. Other blocks
stayextern "C". Seedoc/pragmas.md.ccinrs's-fno-cinrs-unwind(and-fcinrs-unwind, the default):
ccinrsmakes every functionextern "C-unwind", which costs 0.3 % of the
instructions SQLite's speedtest1 runs (under 0.1 % without the run-time
checks); the option makes them
extern "C"and refusessetjmpandlongjmpwith a located error naming
it.- A
long doublethrough a literalprintforscanfformat.
printf("%.2Lf", x)andsscanf(s, "%Le", &x)used to be refused where the
platform'slong doubleis wider than cinrs's (x87's, on x86-64 Linux and
macOS). When the format is a string literal that names everylong double
argument withL, theLs becomel—%lfis%ftoprintfand a
double *toscanf— and the platform reads thedoubleit is given. Any
other way of passing one is refused as before. - More of GCC's assembly. An rbx clobber — zstd's
cpuidwith"ebx"—
keeps rbx in a scratch register around the template; a clobber may be GCC's
register number, as mbedtls's"0", "1";"m","=m"and"+m"pass the
lvalue's address, named in the template as(%reg)or not at all, as in
mbedtls's bignum loops and its zeroize barrier;%cand%Pprint an
immediate without its$, as xz's range decoder needs; and basic asm at
file scope,__asm__("…");, isglobal_asm!. __builtin_frame_address(0)and__builtin_stack_address(), as an address
in the current function's frame, which is what QuickJS's stack-overflow
check compares; another level is refused.__builtin_ia32_pause(), GCC's own name for_mm_pause, which curl's spin
lock calls under GCC:core::hint::spin_loop().- RDRAND and RDSEED:
_rdrand16_step…_rdseed64_stepin<immintrin.h>,
mapped ontocore::archlike every other intrinsic, the output pointer
passed as the&mutit takes there. libsodium'sconfigureprobes for
them. __attribute__((transparent_union)): a parameter of such a union takes an
argument of any member's type, or a null pointer constant, and is passed as
the first member, which is GCC's ABI. glibc'sbind,acceptand
getsocknameunder_GNU_SOURCEtake astruct sockaddr_in *orNULL,
as libuv calls them. A union whose members could not all be passed that way
keeps GCC's warning and is an ordinary union. A function type with such a
parameter is compatible with one that has a member's type there, so libuv
passesgetsocknameas anint (*)(int, struct sockaddr *, socklen_t *).__attribute__((weak))on a declaration is a weak reference, as in GCC:
zstd'sif (ZSTD_trace_compress_begin != NULL)is a real test at every
optimisation level, a program that does not define the hook links, and one
that does — in another object, under-fltoor in a shared library — calls
it. It used to be ignored, and every program without the hook failed to
link. WebAssembly and Windows refuse the declaration instead.- A weak definition —
__attribute__((weak))on a function or an object the
unit defines, or on any declaration of one, as zstd's CLI defines the hooks
its library header declares weak — is a real one inccinrson ELF without
-flto: the body is a private item and the C symbol a weak alias of it,
made withglobal_asm!(nmsaysW), every use in the file goes through
the symbol, and another file's strong definition overrides it — jemalloc's
tests override its weakmalloc_conf— as does a program the weak symbol
of a shared library. Elsewhere — ac99!block,-flto, macOS, Windows,
WebAssembly — it is an ordinary definition with a warning, where it used to
be refused; a second definition is then a duplicate symbol rather than an
override.Options::weak_definitionsis the switch. - GCC's vector extensions on vector types of the program's own:
typedef int v4si __attribute__((vector_size(16)));,vector_sizeon an
object, a member or through pointers and arrays, and theV4SI-style
machine modes. A vector is a#[repr(C, align(N))]struct over its
elements —core::simdis unstable — with GCC's size and alignment (capped
at 16 bytes, 32 under-mavxand 64 under-mavx512f; analigned
typedefreplaces it, which is what glibc's<link.h>needs), and every
operator is written out over the elements: the arithmetic, bitwise and shift
operators with GCC's scalar broadcast, comparisons giving -1 and 0,v[i]
as an lvalue,++,--and the compound assignments, brace initialisers
(static ones too), casts that keep the bytes,__builtin_convertvectorand
__builtin_shuffle.__m128iand the other Intel types convert to the GCC
vector GCC defines them as, and an array of them may leave its braces out
(__m128i m[4] = { 0U }, HACL*'s Blake2). Redis'scrccombine.h,
CPython's<link.h>, and 43 gcc-torture cases that were refused now build.
A vector passed by value to a function the unit does not define, or through
..., is refused: GCC passes one in a vector register, and the struct does
not go there. __attribute__((ms_struct))lays a record's bit-fields out by
Microsoft's rules, as GCC's-mms-bitfieldsdoes, andgcc_structby the
System V ones. It used to be ignored, which moved fields without a word —
1,560 of CPython'stest_ctypesfailures.#pragma ms_struct onis
refused, naming the attribute.max_align_tin the bundled<stddef.h>(C11), with the platform's size
and alignment — 32 and 16 on x86-64 Linux — rather than one built on
cinrs's eight-bytelong double. autoconf'sAC_CHECK_ALIGNOF(max_align_t)
found none, and CPython fell back to an alignment of 8.- A directive among a function-like macro's arguments is processed, as GCC
does: CPython'sperf_jit_trampoline.cwrites an#ifdefbetween the
parentheses of a macro call, which was "expected expression, found '#'". __attribute__((common))on a tentative definition is a weak definition
whereccinrsmakes those, so that the copies every unit including the
header defines merge into one, as common symbols do: Redis's
redismodule.hdeclares its API's pointers so, andredis-serverfailed
to link with "duplicate symbol".cinrs-core'sFileTranslationsays
whether a unit makes a real weak definition (weak_definitions).- Predefined macros GCC has:
__PIC__and__pic__(2) whereverrustc
compiles position-independent code,__PIE__and__pie__(2) where it
links position-independent executables,__FXSR__with the rest of the
x86-64 baseline, and in the GNU dialects onlylinux,unixandi386. cinrs-core, for a command-line driver:translate_fileand
translate_source, which hand back the Rust, the diagnostics, the headers
read and the symbols defined;preprocess_fileandpreprocess_source,
the preprocessor alone, with the macros defined at the end;
Options::export(every unit under#pragma cinrs export),
Options::macros(-Dand-U),Options::includes(-include) and
Options::target_features(-m<feature>);GCC_VERSION.cinrs-rtcan be compiled without Cargo:num-complexis an optional
(default) feature, without which the crate has a#[repr(C)] Complexof its
own, andSOURCES(featuresources) is its source as text.
Changed
__DATE__and__TIME__are the moment of translation, as C17
6.10.8.1 asks and GCC has them, where they were the placeholders"??? ?? ????"and"??:??:??"— which CPython'ssys.versionparser refuses.
SOURCE_DATE_EPOCH, when it is set, is that moment in UTC, so a
reproducible build still gives the same output twice; GCC's error, at the
first use, when it is not a number of seconds up to the end of 9999.
Otherwise it is now, in the local zone —TZor/etc/localtime, read by
cinrs itself (TZif and POSIX rules), and UTC where neither says anything.
__TIMESTAMP__is the modification time of the file being read, as in
GCC, and the moment of translation for ac99!block's own text. In a
block, the moment is the expansion: a crate that is not rebuilt keeps the
date it was built with.Options::translation_timefixes the moment, and
cinrs_core::clockis the calendar and the zone.ccinrs -std=c89…-std=c17take what GCC's do: the constraint
violations GCC only warns about — a stray;at file scope, an enumerator
outsideint(glibc's<sys/epoll.h>),sizeof (void), a cast to a union
type and the rest of the leniency table indoc/gnu-extensions.md— and
#embedbefore C23, with__has_embeddefined. CPython builds with
-std=c11and FFmpeg with-std=c17. What makes the dialect ISO stays:
asmandtypeofare not keywords,__STRICT_ANSI__is defined and
trigraphs are read.-pedantic-errors(or-Werror=pedantic) gives the
strict entry points' errors back, with a note that says so;-pedantic
alone changes nothing.Options::gnu_lenienciesis the switch, off by
default, soc11!and the other strict macros are unchanged.- An array variable of 16 bytes or more is 16-byte aligned on x86-64
(System V), as the psABI says (3.1.2) and GCC and Clang do: automatic,
staticand file-scope ones, and variable length arrays, through the
wrapper an_Alignasobject is generated in. SIMD code relies on it —
_mm_load_ps(table)is an aligned load, and faulted on a cinrs array four
bytes off. The type and__alignof__are unchanged. In ac99!block a
file-scope array with external linkage keeps its plain Rust type, and
alignment, unlessOptions::abi_align_public_arraysis set, asccinrs
sets it. ccinrs -fltoon x86-64 Linux is the linker's link-time optimisation:
the objects are LLVM bitcode andrust-lldoptimises them (ThinLTO,-C linker-plugin-lto), as GCC's linker plugin does, where it wasrustc's
-C lto. The linker sees every object, so objects and archives compiled
without-fltolink beside the optimised ones, and a file that makes a
weak orcommondefinition is compiled without it — said under-vonly
— instead of losing the definition's weakness: Redis's default build, with
its-flto, linksredis-server(122 of its 125 files optimised).
Elsewhererustc's LTO is as it was.-fuse-ldnaming another linker is
ignored, with a warning, at a link that has-fltobitcode in it.- A function whose jumps the relooper structures is no longer made the
whole-function state machine for a nesting it does not have. The depth
check counted a labelled block for every shape in a run, where the output
has one only for a shape something breaks forwards to, and its limit is 400
such blocks rather than 200. Tcl's bytecode interpreter (TEBCresume, 230
deep where 563 were counted) is relooped: Tcl'sexprbenchmark runs in
0.98 s rather than 2.77 s (gcc: 0.67 s), andtclExecute.ccompiles in
2.6 s rather than 30.6 s. - A program or library
ccinrslinks stops at a panic — a run-time check
that failed — with Rust's message andabort, through a hook its link
installs. Now that every function isextern "C-unwind", the panic would
otherwise unwind looking for a handler and end with "fatal runtime error:
failed to initiate panic"; before, it ended with a backtrace of "panic in a
function that cannot unwind". - A source file — a header, an
include_c99!file — no longer has to be
UTF-8. A byte that is not part of a UTF-8 sequence is passed through a
narrow string literal as the byte it was, read as Latin-1 in a wide one and
ignored in a comment, as GCC does; anywhere else it is "unexpected byte 0xE9
in program (the file is not UTF-8)". Such a file used to be "cannot read". - A macro redefined with a different replacement list is a warning, and the
later definition stands, as in GCC and Clang; a redefinition in a system
header — glibc's<assert.h>after a program's ownassert— says nothing.
It used to be an error, which stopped zstd. - An enumeration with no negative enumerator is
unsigned int, the type
GCC and Clang make it compatible with, and its Rust alias is ac_uint; the
enumerators stayc_int. It wasint, which was wrong wherever the two
differ:e - 1 < 0, a division, a conversion tolong, and — silently — a
bit-field of atypedefof an anonymousenum, which read 4 back from
QuickJS's 3-bitclosure_typeas -4. - The arena behind variable length arrays and
allocakeeps its chunks per
thread when a call returns, for the next call to take, instead of freeing
them: a function thatallocas on every call — QuickJS's interpreter — no
longer pays for an allocation, a zeroed chunk and a free each time (a
microbenchmark of it went from 0.85 s to 0.28 s; gcc takes 0.11 s). A unit
under#pragma cinrs no_stdfrees them as before. __builtin_ctzon x86-64 counts its 32-bit operand in a 64-bit register
with bit 32 set — the same answer for every value — so thattzcntwrites
the register it reads. LLVM's generic x86-64 code wrote whichever register
was free and so waited for that register's previous value, a false
dependency GCC breaks with anxor; libwebp's lossless entropy loop, whose
ctzlanded in the register of the previous iteration's table lookup, ran
at 2.7× GCC's time and now runs at 1.05× (0.200 s → 0.075 s; gcc 0.071 s).- A subscript that cannot be outside an array object is an element of the
array,table[op as usize], rather than pointer arithmetic: a constant, a
value converted from a narrower type (anunsigned charinto 256
elements), what an assignment to a variable stores, or a mask, into a named
array or a member or element of one. With debug assertions,offsetchecks
that the address calculation does not wrap, which cannot fail inside an
object and which LLVM cannot fold for astatic, whose address it does not
know; LLVM drops the element's bounds check, which the subscript's range
proves. An interpreter'sgoto *table[opcode]is the case: CPython's eval
loop is 6 % smaller and runs 2.6 % fewer instructions on a recursive
benchmark, QuickJS's 6 % fewer on a numeric one, in about the same time.
An array reached through a pointer, a thread-local one included, and a
safe function keep the pointer arithmetic. ccinrsgenerates Rust only for what an object needs: the
definitions the linker sees — every symbol of the unit, what a section or a
constructor list puts in the object — what they call, take the address of
or name, through a static's initialiser and anasmtemplate too, and the
declarations and types those name. Astatic inlinefunction a header
defines and nothing calls, and a function a header only declares, are not
generated at all, where before they were generated and the printed Rust
pruned.Options::reachable_onlyasks cinrs-core for this, and
cinrs_core::reachis the walk. git'sabspath.ccompiles in 0.30 s
instead of 0.40 s; git'smake -j2takes 122 s instead of 153 s, and
libuv'scmake --build -j256 s instead of 64 s, with every object of both
defining and referencing the same C symbols as before.- The preprocessor's hide sets — which macros a token may no longer expand —
are shared rather than rebuilt for every token of every expansion: a
third of all the allocations translating a file made were copies of them.
ccinrs's own time on git'sabspath.canddiff.cand libuv's
src/unix/core.cfalls by 7 %, 6 % and 10 %, for the same Rust.
Fixed
volatilewas ignored: avolatileobject was read and written like
any other, so LLVM merged, dropped and hoisted its accesses — a loop
waiting for avolatile sig_atomic_tflag a signal handler sets spun for
ever at-O2, and two stores in a row were one. Every read and write of
avolatilelvalue is now aread_volatileorwrite_volatileof its
address: an object declaredvolatile(through atypedeftoo, a local
included), anything through a pointer tovolatile, a member of a
volatilerecord or one declaredvolatile, and an element of a
volatilearray. A compound assignment and++/--are one volatile
read and one volatile write, whose value is the value stored; a bit-field
is one volatile read and one volatile write of the bytes it lives in; a
whole structure is copied in or out with one of each; a packed member goes
through a packed wrapper.&v,sizeof vand an array's decay are
unchanged.volatile int *andint *are no longer compatible types.
Seedoc/translation.md.- On WebAssembly and Windows,
extern int x __attribute__((weak));
followed by the unit's own definition ofxis that definition, with the
warning, as on every other target; it was refused as a weak reference
before the definition was seen. Only a weak reference nothing in the unit
defines is refused there. (A function's definitions were already known
in advance.) - An AArch64 Apple target defines
__arm64__,__arm64and
__ARM64_ARCH_8__besides__aarch64__, as Apple's Clang does: the
macOS SDK's<sys/cdefs.h>stopped at "Unsupported architecture" without
the first. Linux's AArch64 gets none of them, as with GCC. Reported by
tasuren in #1. - An inline definition provides no external definition where the unit
makes C symbols (ccinrs,#pragma cinrs export), as C99 6.7.4p7 and
GNU89 say: a header'sinlinefunction that two files include is no
longer defined twice ("duplicate symbol", "symbol … is already
defined"). C99's rule — every file-scope declaration saysinlineand
none saysextern— in C99 and later; GNU89's — anextern inline
definition is the inline-only one, a plaininlineone an external
definition — inc89!andgnu89!, under the newccinrs -fgnu89-inline(and-fno-gnu89-inline, which__GNUC_GNU_INLINE__
follows), and for any function a declaration of which says
__attribute__((gnu_inline)): glibc's__extern_inline, and macOS's
__header_always_inlinein<sys/select.h>. The body is a private item
the unit's own calls use, and the function's address is the external
symbol, the same in every unit.Options::gnu89_inlineand
Function::inline_onlyare new. Reported by tasuren in #1. - A static named like a local of the variable-length-array arena —
size,
first,count,other— beside a variable length array oralloca
compiles; the arena's code is in a module of its own now, where a binding
of the same name does not shadow it (E0530). - A link with
-fltothat also takes an object or an archiveccinrs
compiled without it links: the other machine code calls Rust's standard
library by name, andrustc's LTO kept only what the-fltocrates used —
"undefined symbol: core::panicking::panic" when Redis linkedredis-cli
with itsdeps/archives. See the-fltoentry under Changed. - A wide string literal as an arm of
?:that needs aconstpointer —
CPython'sreturn sep ? sep + 1 : L"";— compiles; the block holding the
literal'sstaticwas read as a statement before itsas. - A conditional with an integer constant condition is the address constant
its chosen arm is, in a static initialiser: CPython's_Py_LATIN1_CHRin
Argument Clinic's keyword tables. So is one whose arms are string literals
and that is converted to the object'sconst char *, nested or not:
OpenSSL'ssizeof(long) == 4 ? "four" : sizeof(long) == 8 ? "eight" : "". ##may make a preprocessing number that is not a valid constant, as C17
6.4.8 allows: FFmpeg'sAV_VERSION_DOT(a, b, c) a ##.## b ##.## cgoes
through63.1.on its way to63.1.100, and its32 ## pis pasted on into
a function name. "pasting '63.1' and '.' does not give a valid token" was
28 of FFmpeg's failing files. A number that reaches the parser that way is
still the error it would have been written.- A
staticinteger as wide as a pointer —uintptr_t,intptr_t,long,
an_Atomicone — may be initialised with an address converted to it, which
GCC takes as a relocated constant: FFmpeg'sstatic atomic_uintptr_t av_log_callback = (uintptr_t)av_log_default_callback;. The item holds the
address as a pointer and every use reads it as the integer. As a member of
a table too — git's{ .defval = (intptr_t)"all" }, nginx's
(uintptr_t)ngx_ssl_get_protocol— where the item is aMaybeUninitof the
table with the addresses written into it. - A brace-initialised local structure or union has zero padding, as GCC
and Clang give it (C11 6.7.9p10, p21): the bytes are zeroed and only the
members that are not zero stored, where a struct literal moved into place
left the stack's bytes in the padding. git'sref-filter.ccompares two
{ 0 }structs withmemcmp;git for-each-refdied on a broken ref, and
three of git's tests passed or failed by luck. Compound literals and
definitions hoisted out of their block likewise, andq = p;of a padded
type copies the bytes. - A record both
packedandaligned(N)compiles, laid out as GCC lays it
out — members packed, alignment N, size a multiple of N — where it was "a
record cannot be both packed and given a stricter alignment": the kernel's
<linux/bpf.h>has one, which nginx's configure includes. The Rust item
stays packed and gets its size from a field; an enclosing record pads to
N, and an object of the type is placed at N by an alignment wrapper. __attribute__((noreturn))on a variable of function pointer type is
taken and dropped, as GCC gives it to the pointed-to type, where it was
"'_Noreturn' is only allowed on a function" (git'sNORETURN_PTRin
usage.c). The keyword is still a function's alone.- A strict block below
c23!, which refuses#embed, no longer defines
__has_embedeither, and__has_embed(…)answers "not found" there, so
that a program which asks first — FFmpeg's checkasm,#ifdef __has_embed
— takes its other road rather than the refusal. A GNU dialect takes both,
as before, and so doesccinrsunder every-std=(see Changed). - Under
ccinrs, a diagnostic that says how to choose another standard or
dialect names the command line — "(this file is compiled with
-std=c17)", "GCC accepts this with a warning, and so does ccinrs without
-pedantic-errors or with -std=gnu17" — rather than thegnu17!macro;
Options::front_endsays which front end the C came through. - A static pointing into an
externarray of unknown size at an offset —
FFmpeg'sff_ac3_enc_options + 2— compiles: pointer arithmetic in a
static's initialiser iswrapping_offset, where rustc's constant
evaluation refused anoffsetpast the zero bytes it knew of the array
(E0080). - A file-scope
staticnamedbits,raworvaluebeside a record with
bit-fields compiles: the accessors' own locals and the setter's parameter
are__cinrs_names now, wherelet bitswas "let bindings cannot shadow
statics" (E0530, FFmpeg'sfastaudio.c). A C local, parameter or hoisted
local of such a name was already renamed apart. - An array with static storage duration whose list decides its length may
name itself in that list —static const struct node tree[] = { { 1, tree }, … };, FFmpeg'smatroskadec.c— as its scope begins after its
declarator (C17 6.2.1p7). It was "use of undeclared identifier". __attribute__((aligned(1)))on atypedefof a record lowers its
alignment, as GCC does for a scalar's: a read, a write or a member access
through a pointer to thetypedefis an unaligned one. OpenSSL's
aes_block_tin its AES-IGE code; it was refused.- A diagnostic names an untagged record or enumeration "anonymous struct",
"anonymous union" or "anonymous enum", not by the Rust itemcinrsmade
for it. static T x[64];followed bystatic T x[] = { … };defines one object of
64 elements, as C11 6.9.2 and 6.2.7 make it, rather than "redefinition of
'x'" (CPython'spyexpat.c).__m128i m[4] = { 0U };leaves the inner braces out, as GCC allows for a
vector: the scalar is the first lane of the first element (HACL*'s Blake2
in CPython).s.a = s.b = 1on two bit-fields of one record compiles; the setter's
borrow of the record overlapped the inner assignment's (E0499, in libpng's
pngfix).__extension__is followed by a cast expression, as in GCC's grammar:
Lua's(__extension__ (lua_CFunction)(p))was a call of a type.- A function declared through a
typedefof a function type —typedef int handler(int); static handler f, g;— is a function. It was read as an
object of an incomplete type and refused; expat declares its parser's state
handlers that way. - A null pointer that is only tested is a constant:
bool b = true | false | !nullptr;, autoconf's C23 probe, andint i = (void *)0 == 0;are static
initialisers, as in GCC. They were "not a compile-time constant expression". - The name of a function defined without a prototype (
char *e(p, i) char **p; int i; { … }) is a value of its unprototyped type, which converts to a
prototyped pointer and fills a static table. Passing one where a
char *(*)(char **, int)was wanted maderustcrefuse the translation,
so every autoconf 2.70+configurefound no C89, C99 or C11 compiler. - A block-scope
staticpointer to a variable length array,static int (*q)[m] = &B;, is allowed, as C allows it, and a variable length array type
is compatible with an array of any length, soint (*p)[m] = &B;with an
int B[100]needs no cast. autoconf's variable-length-array probe writes
both, andconfigureconcluded there were no VLAs. __builtin_expect's second argument is evaluated, so__builtin_expect(x, z++)incrementsz; it was checked and dropped (GCC'sexecute/pr85156).- An attribute may follow a parameter's whole declarator —
char *argv[] __attribute__((unused))— where it was a syntax error (execute/stkalign). - An inline-assembly immediate is printed in its operand's width,
sign-extended, as GCC and Clang print it: xz's"n"(UINT32_C(31) - UINT32_C(2048))under%cis the displacement-2017, which the assembler
takes, and not4294965279, which it refused. - A function pointer with static storage duration may hold an object's
address or an integer, as libwebp'sstatic volatile VP8CPUInfo last = (VP8CPUInfo)&last;sentinel andSIG_IGNdo: the item is the data pointer,
read and written as the function pointer C declared.rustc's constant
evaluation refused the translation of every libwebp DSP file. Inside an
array or a structure such a constant is refused with the reason. - A pointer with static storage duration initialised with a negative integer
— glibc'sSIG_ERR,((__sighandler_t) -1), and(void *) -1— is all
ones, as in GCC. It was written-1 as c_ulong, whichrustcrefused
(E0600); curl's test servers keepSIG_ERRin astatic. - A statement may begin with
__extension__and still be an expression:
curl'stypecheck-gcc.hwraps everycurl_easy_setoptin
__extension__({ … }), which was read as a declaration with no type. - Explicit asm labels on Darwin name the assembler symbol directly, without
LLVM adding an extra leading underscore. Labels produced by Apple's
system-header macros could otherwise reference__fputsinstead of
_fputsand fail to link. Reported in #2 and fixed in #4 by tasuren.
Toolchain and platforms
Rust 1.99 or later, tested on 1.99.0; CI builds on stable only.
The macros: as in 0.2.0, developed and fully tested on x86-64 Linux, with the
examples and the portable tests built, linked and run in CI on macOS (arm64)
and Windows (x86-64, MSVC); other targets are compile-checked.
ccinrs: developed and tested on x86-64 Linux, where every program in
doc/ccinrs-real-programs.md was built, and
for wasm32-wasip1, wasm32-wasip2, wasm32-unknown-unknown and musl
(aarch64 musl under qemu) through --target. Its shared libraries, weak
definitions and version scripts are ELF's. macOS and Windows hosts are not
tried yet.