Repository navigation
Conversation
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
The change set is large and spans conformance-sensitive lexer, preprocessor, literal, ABI, and codegen behavior whose correctness warrants final human review despite no confirmed blocking defect.
Review effort: Balanced
Findings: None
What changed in this PR
"cc Updates" is a broad set of changes to the cc crate (the from-scratch C17 compiler) in posixutils-rs. It tightens lexer/preprocessor conformance, fixes handling of constructs left unfinished at end-of-file, improves universal-character-name (UCN) literal diagnostics, and adds substantial cross-target test coverage. The changes move several utility areas closer to the "feature complete"/"test coverage" lifecycle stages while keeping behavior aligned with gcc/clang where POSIX/C17 is silent.
Changes:
- Lexer/preprocessor conformance: remove the non-C
..(DotDot) operator and lex...via explicit two-dot lookahead; mark the stream-end token as beginning a line so unterminated directives/_Pragmaat EOF no longer loop unbounded; extenducn_is_forbiddento reject scalars aboveU+10FFFF. - Literal handling: add an
IncompleteUcnescape variant so a\u/\Uwith too few hex digits is diagnosed, decodes as its letter, and consumes only its digits; report an empty character constant per C17 6.4.4.4p1. - Tests/docs: new preprocessor suites (
end_of_file,target_predefines,pp_diagnostics) plus literal/lexer/preprocess unit tests; remove a closed TODO conformance item.
| File | Description |
|---|---|
| cc/token/lexer.rs | Removes DotDot, lexes ... via lookahead, forbids UCNs > U+10FFFF, marks stream-end token as line-starting |
| cc/token/literal.rs | Adds IncompleteUcn escape variant + diagnostic and empty-char-constant error; renders the letter in all byte/utf16/wide encodings |
| cc/token/test_lexer.rs | Drops test_dotdot_operator, updates two-char-operator test, adds stream-end and dot-punctuator tests |
| cc/token/test_literal.rs | Adds tests for empty char constant, UCNs beyond the code space, and incomplete-UCN digit retention |
| cc/token/test_preprocess.rs | Adds tests for EOF survival, malformed _Pragma, stringify/paste spacing, #if ?:, PIC macros, and #line |
| cc/tests/preprocessor/mod.rs | Registers new end_of_file, pp_diagnostics, and target_predefines test modules |
| cc/tests/preprocessor/end_of_file.rs | New suite covering translation units ending mid-construct |
| cc/tests/preprocessor/target_predefines.rs | New cross-target predefined-macro tests (Darwin/FreeBSD/Linux, unsupported OS) |
| cc/TODO.md | Removes the now-resolved restrict-on-non-pointer conformance item |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…lues are diagnosed, not crashes
- The end of the stream now begins a line. A last line closed by a
backslash-newline let `#define X \`, `#include "x" \` and friends read the
end-of-stream marker as an operand, and the parser, never seeing the end
of the file, looped allocating without bound.
- `_Pragma` peeks before it takes each operand token and reports gcc's
"_Pragma takes a parenthesized string literal" when the operand is
malformed or cut off, instead of silently swallowing the next token
(the end of the stream included).
- C17 6.7.2.1p3: a member of incomplete or function type is an error and is
dropped, so `struct A { struct A a; }` no longer builds a type that
contains itself (a Rust stack overflow in initializer lowering).
- C17 6.7.2.1p13: a tagged struct/union specifier in a member list declares
its tag, not an anonymous member ("declaration does not declare
anything", as gcc), which closes `struct A { struct A; }` the same way.
- Reading, writing or read-modify-writing an object of incomplete type is
"invalid use of incomplete type" (C17 6.3.2.1p2), checked where the
linearizer reads and assigns objects; a forward-declared enum's
zero-width value used to reach the IR validator as an ICE.
- C17 6.9.1p7 / 6.5.2.2p4: a definition's parameter of incomplete type, and
a call through a prototype naming one, are errors with gcc's wording.
- Tests: `compile_bounded` runs c17 under a deadline so a regressed hang
fails instead of hanging the suite.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ve diagnostics Wrong output: - `#__VA_ARGS__` keeps the spacing of the commas between variadic arguments: argument collection now keeps the real separator tokens, so `H(a , b)` stringifies as "a , b" (was "a, b"), and `-E` of a `__VA_ARGS__` expansion keeps them too. - A `##` result is spaced as its left operand was, not as the invocation: `S(a[b##n])` gave "a[ b1]", which broke <sys/sdt.h> probe operands. - `#if c ? x : y` evaluates only the arm taken (C17 6.5.15p4), so `1 ? 2 : (1/0)` is accepted, and the result has both arms' common type (`1 ? -1 : 0u` is unsigned). - `..` is no longer a token: C17 has no such punctuator, so `.` ## `.` is now gcc's invalid-paste error instead of silently making one. Diagnostics gcc gives: - extra tokens after #ifdef/#ifndef/#undef/#include warn; after #line it is a warning (was an error) and the directive still applies. - `#define A+1` warns "ISO C99 requires whitespace after the macro name". - `__VA_ARGS__` anywhere but a C99 variadic macro's body warns. - `#include stdio.h` is an error instead of a guessed "stdio.h". - redefining a predefine or -D macro to something else warns (feature-test macros excepted, since c17 predefines those where gcc does not); `#undef` of a C17 6.10.8 name warns; `#undef defined` is an error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ected - C17 6.7.2.1p13: an anonymous struct/union member counts as a named member before a flexible array member (<linux/bpf.h>, all of tools/lib/bpf); only an unnamed bit-field does not, as in gcc. - An unnamed bit-field may begin a declarator list, `unsigned char :1, :1, x:1;` (<linux/ioam6.h>): the separate first-position branch that then demanded `;` is gone, and the declarator loop handles both positions. - A stray `;` in a member list is skipped, as gcc does (<linux/nfc.h>). - A `_Static_assert` message is a string literal after phase 6, so adjacent literals concatenate (`BUILD_BUG_ON_ZERO`'s `#e " is true"`), with any encoding prefix. - `&&` and `||` fold without evaluating a right operand the left decides (C17 6.5.13p4, 6.5.14p4, 6.6p3): `1 || 1/0` is a constant, in static initializers, array bounds, `case` labels and `_Static_assert`. - The bundled <stdint.h> hands a hosted glibc build to glibc's own, as gcc's does: <sys/eventfd.h>, <sys/inotify.h>, <sys/signalfd.h> and <sys/fanotify.h> include only <stdint.h> and expect <sys/cdefs.h> from it. A sweep of every /usr/include header gcc compiles alone now finds none that c17 rejects. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…integers as double
- C17 6.4.2.2p1: `__func__` (and gcc's `__FUNCTION__`/`__PRETTY_FUNCTION__`)
is a `static const char[]`, typed from the enclosing function's name. It
was `char *`: `sizeof __func__` was 8, `_Generic` chose `char *`, and a
write through it was accepted. It holds the name as written, not an asm
label's (`int f(void) __asm__("g")` has `__func__` "f", as gcc); it is an
address constant in a static initializer; outside a function gcc's
warning and an empty name.
- C17 7.3.1p4: `_Complex_I` is `float _Complex`; the `double _Complex` one
widened every float complex expression it touched.
- C17 7.25p3 in <tgmath.h>: an integer argument counts as `double` when
choosing among several arguments, so `pow(i, f)` is `pow`, not `powf`
(which rounded i); a real argument to a complex-only macro (`cimag`,
`conj`, ...) picks its own precision's function.
- `_Complex` with a typedef name, `typeof`, a tag, `_Bool` or `void` is an
error instead of being silently dropped (`ty _Complex z` was a double).
- `mode(byte)` and `mode(unwind_word)` select 1- and 8-byte integers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Flags that stopped c17 before it compiled anything: - `-pedantic`, `-pedantic-errors`: accepted (the pedantic mode c17 has). - `-ansi`: gcc's `-std=c90`, reported as any older revision is. - `-x LANG` / `-xLANG` for c, cpp-output, assembler, assembler-with-cpp and none, applying to every later operand; another language is "language not recognized". Operands are classified through one `Args::lang_of`, so `.S`-style preprocessing and `.i` handling follow `-x` too. - `-g0`..`-g3`, `-ggdb[N]`, `-gdwarf[-N]`: last one wins (`-g -g0` is no debug info); layout-only `-g` options (`-gsplit-dwarf`, `-gz`, ...) are ignored quietly, any other `-g...` with a warning. - `-m` flags are judged once the target is known: choosing or tuning for a CPU (`-march=`, `-mtune=`, `-mcpu=`) and the target's own baseline (`-m64`, `-msse2`, `-mfpmath=sse`, `-mabi=lp64`, ...) change nothing c17 emits and are accepted; an ISA extension or ABI change is still refused. `__PIC__`/`__pic__` and `__PIE__`/`__pie__` (value 2) are now defined from the same position-independence decision that drives code generation, for C and `.S` alike; Mach-O always has `__PIC__`, as with clang. Hand-written asm that tests them took the absolute-address path in PIC code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…fused - FreeBSD: `wint_t` is `int` (its `__ct_rune_t`), so `__WINT_TYPE__`, the bundled <stdint.h> limits and the system's typedef agree; `__FreeBSD_kernel__` (GNU/kFreeBSD's macro) and `__BSD_VISIBLE` (which <sys/cdefs.h> computes from the program's feature-test macros) are no longer predefined. - Darwin: `__APPLE_CC__` is clang's 6000; the `__DARWIN__` and `__MACH_O__` that no Darwin compiler defines are gone. - `--target` with an OS c17 does not support (windows, mingw, none, netbsd, ...) is "unsupported target" instead of a silent Linux with `__linux__` and `__ELF__` defined. A bare architecture still means Linux. - The feature-test macros c17 predefines on Linux stay, now recorded in DECISIONS.md with what they cost (a visible GNU namespace, glibc's C2X `strtol`/`scanf` bindings); the comment claiming gcc does the same is corrected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… honoured
Linker flags:
- `-Wl,a,b` was split at its commas and every piece handed to the host
driver bare, after all objects and libraries: `-Wl,--whole-archive` became
a driver option `cc` rejects, `-Wl,-soname,x` two unrelated words, and
nothing kept its place relative to the archive it governs. `-Wl,...` and
`-Xlinker x` now travel as written, and `linkargs::scan` keeps every
link-step flag (`-pthread`, `-rdynamic` too) in its position on the link
line, as it already did for operands, `-L`, `-l` and `-R`.
Visibility:
- `-fvisibility=` was accepted and ignored, so a shared object built with
`-fvisibility=hidden` exported every symbol, and calls between its own
functions bound through the PLT to the executable's symbols of the same
name -- CPython's test_peg_generator extension ran the interpreter's
parser instead of its own. Every definition with external linkage that
names no visibility now gets the flag's (`Module::apply_default_visibility`);
an unknown value is an error, as in gcc.
- An object's definition now inherits the attributes of every declaration of
it, before or after, as gcc does: CPython's PyAPI_DATA puts
`visibility("default")` only on the `extern` declaration, so its objects
were hidden and extension modules could not find `PyFloat_Type`.
CPython 3.12.9 at -O2: run=41,758 Result: SUCCESS.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The error and warning counters were process globals. The compiler runs on one thread of its own, but a unit test's translation unit runs on one of the test harness's, so a count shared with every concurrent test made "did this report an error" unanswerable: a parser test comparing the count before and after saw other tests' errors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rced c17 checked a redeclaration's type only, and only in its own scope. Linkage is now tracked (C17 6.2.2) per symbol and, for identifiers with linkage, across the whole translation unit (parse/linkage.rs): - 6.7p3: a second declaration of a no-linkage identifier in one scope is "redefinition of 'x'" -- it used to bind nothing, silently -- including a body declaration repeating a parameter; mixing a no-linkage declaration with an `extern` one in a scope is gcc's error in either order. - 6.2.2p7: `static` after a non-static declaration, and a non-static object after a static one, are errors; `extern` and a function declaration still take the visible linkage. - 6.2.7p2: block-scope `extern` declarations are compared with the file's and with each other's (`int x; ... extern long x;`). - 6.9p3, p5: a second definition of an object or function is an error (it reached the assembler as a duplicate symbol), except beside a GNU inline-only (`extern inline` under gnu_inline) body. - Tags (6.7.2.3): redefining a struct/union/enum in one scope, and naming a tag as the wrong kind (`struct S; union S *p;`), are errors. - Storage classes: `_Thread_local` on a function or typedef, or alone at block scope (it was silently an automatic variable); `auto` at file scope; `register` at file scope without an asm register name (gcc's global register variables stay); `static` on a block-scope function. - `for` loop declarations may not declare a typedef or a tag (6.8.5p3). - A variably modified typedef may not be redefined (6.7p3). - A function may not return an array or a function (6.7.6.3p1), and `restrict` qualifies only a pointer to an object type (6.7.3p2), including through a typedef and on a parameter -- the TODO.md entry is closed. - gcc's warnings for `inline`/`_Noreturn` on an object, `inline main`, and an untagged struct/union declaring nothing. - A file-scope array defined without an extent (6.9.2p2) is judged at the end of the unit: a later declaration in any scope completes it, every declarator takes the final type (so storage is the full size), and one still incomplete there is one element with gcc's "assumed to have one element" warning, once. An extent from a block-scope `extern` after the definition is gcc's "completed incompatibly" error. The new check found a real duplicate in codegen_float16_mega, renamed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each of these compiled; several into wrong code. - Designators (C17 6.7.9p7): `.y` naming no member was dropped with its value. Every designator chain is now resolved against the object it initializes, following nested lists through designators and by position (until brace elision makes the position unclear), for declarations and compound literals alike; a field designator on a non-aggregate and an index on a non-array are reported too. - `s->a` on a structure (6.5.2.3p2) loaded through the structure's bytes; it is now gcc's "invalid type argument of '->'". An array still decays. - Casts (6.5.4p2-4): to a structure, from a structure or union, and between a pointer and a floating type, with gcc's wording. A cast to the operand's own structure type, qualifiers aside, is gcc's extension and is accepted. - Assigning a structure or union with a `const` member at any depth (6.3.2.1p1). - Bit-fields: `&`, `sizeof`, `_Alignof` and `offsetof` of one; an `_Atomic` bit-field; a `_Bool` bit-field wider than 1. - `_Generic` associations of function or incomplete type; `_Alignof` of an incomplete type (gcc's `void` extension stays; a VLA type is complete); `_Atomic(T)` of a qualified or atomic `T`; `_Alignas` weakening a member's alignment, or past the 2^28 ceiling. - `static`/qualifiers only in the outermost dimension of an array parameter, whose name may be parenthesized (`int (a)[static 3]`); a duplicate member reached through an anonymous struct/union; an unnamed `void` among other parameters. - Literals: `''`; a UCN past U+10FFFF; `u8` next to a wide literal, in either order; a string longer than its array (gcc's warning; filling it exactly is C). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- C17 6.7.6.2p4: `[*]` is allowed only in a prototype's parameter list. In
a function definition the parameters are in the body's scope, and
`void g(int n, int a[*]) {}` compiled; it is now gcc's error. A `[*]` in
a nested prototype (a parameter that points to a function) stays legal.
Each parameter list records its own `[*]`, so a function returning a
function pointer is judged by its own parameters, not the return type's.
- A struct, union or enum first declared inside a parameter list has
prototype scope, so no later declaration can name the same type; gcc
warns, and c17 now does too, for tagged and anonymous ones.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…odifiers); #line maps positions Builtins: - `__builtin_fabs` and `__builtin_copysign` in their `_FloatN` and `q` spellings (`f32`, `f64`, `f128`, `q`), and `__builtin_infq`, `huge_valq`, `nanq`, `nansq`; on x86-64 the binary128 ones call libm's `fabsf128`/`copysignf128` (the backend has no XMM sign-bit form for it, and crashed). `__builtin_sqrtf128` and `__builtin_fmaf128` call libm. The f128 and q forms exist only where `_Float128` does. - `__builtin_FILE()`, `__builtin_LINE()`, `__builtin_FUNCTION()`. - `__builtin_expect_with_probability`, with gcc's check of the probability. - `__builtin_dynamic_object_size`, answered as `__builtin_object_size`. - x86-64 `__builtin_cpu_init`, `__builtin_cpu_supports`, `__builtin_cpu_is` (parse/cpu_builtin.rs), reading libgcc's `__cpu_model` and `__cpu_features2`; the tables hold every name gcc 13 accepts, with the encodings read off its own lowering. - x86-64 asm operand modifiers `%z` (size suffix), `%p`, and `%x`/`%t`/`%g` (a vector register's XMM/YMM/ZMM name). - All registered for `__has_builtin`; BUILTIN.md updated. `#line`: it renumbered only `__LINE__`/`__FILE__`. It now establishes the same mapping as a `# N "file"` linemarker, per physical stream (so a file's mapping survives an `#include` in it): diagnostics read `renamed.c:77`, as gcc's do, and so do the position builtins and the debug line table. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…imals compile correctly; K&R, void and incomplete-type constraints enforced
Wrong code and an internal error fixed:
- A braced scalar initializer for a member of an automatic struct
(`struct S s = {{1}, 2}`) reached the linearizer as a list and
crashed. The initializer walker now strips scalar braces everywhere,
warning on extra levels and excess elements as gcc does.
- A qualifier on an array typedef (`typedef int A[3]; const A x;`) was
dropped; it now qualifies the elements (C17 6.7.3p10).
- A decimal constant above LLONG_MAX wrapped to a negative long long;
it is now __int128 with gcc's warning.
- An identifier-list definition after a prototype read its parameters
at the wrong type; argument count and promoted types are now checked
against the prototype.
- A typedef name of a struct in a member list was taken as an anonymous
member and changed the layout; it declares nothing, as in gcc.
Constraints now diagnosed:
- K&R declaration lists that name an unlisted, repeated or void
parameter. An undeclared one warns "defaults to 'int'".
- Casting a void expression, or passing one as an argument
(__builtin_va_arg_pack excepted).
- Arithmetic, ++/-- and subscripting on a pointer to an incomplete
struct, union or enum.
- An incomplete return type in a definition or at a call.
- A function definition through a typedef.
- Casts between complex and pointer types.
- `void f(const void)`.
- An incomplete \u/\U universal character name.
- Excess elements in union and nested initializers, and nested scalar
initializers of the wrong type.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cc 7.5 C23's interchange and extended floating types (TS 18661-3) were aliases of float/double, or absent. They are now distinct types, as in gcc: - A FloatClass (standard / interchange / extended) on Type is part of the interning key and of compatibility, so `_Float32` is not `float` in _Generic or __builtin_types_compatible_p. The kind stays the format's, so layout, ABI and code generation are unchanged. - Usual arithmetic conversions follow gcc for two names of one format: interchange over standard over extended (`_Float32 + float` is `_Float32`, `_Float32x + double` is `double`). - `_Float32` is not promoted through `...`. - `long _Float64` is now an error; the specifier tally used to record it as `double`. - `_Float32x`/`_Float64x` keywords and `f32x`/`f64x` literal suffixes. `_Float64x` is long double's format and does not exist on Apple aarch64. - f32x/f64x forms of the inf/huge_val/nan/nans/fabs/copysign builtins. The f32/f64 forms and library prototypes now use the _FloatN types. - __FLT32_*, __FLT64_*, __FLT32X_* and __FLT64X_* predefines. __LDBL_*, __FLT128_* and these families are all generated from one per-format FormatLimits table. - float.h exposes the FLTN_* families only under __STDC_WANT_IEC_60559_TYPES_EXT__, as gcc does. - tgmath.h dispatches _FloatN arguments to glibc's sqrtf32 etc. where declared, and otherwise to the same-format standard function. With that, __GNUC__ moves from 6.5 to 7.5. glibc now takes its native-_FloatN paths: __MATH_TG with `_Float32:` beside `float:`, and no typedefs or builtin macros from bits/floatn.h. 8.0 would need __builtin_tgmath and the nonstring attribute. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t, lowered lane by lane
vector_size types were storage only: every use as a value was an
error. They are now values, with gcc's rules (measured against gcc 13):
- Arithmetic and bitwise operators, shifts, unary - + ~, ++ and --,
comparisons and ?:.
- A comparison yields a signed integer mask vector (-1/0). The mask is
gcc's "opaque" type: it also assigns to any vector with integer lanes
of the same shape.
- A scalar operand is spread across the lanes when the lane type holds
it exactly. Otherwise c17 reports gcc's truncation or "cannot convert
value to a vector" errors.
- Casts reinterpret bits between same-size vectors and integers.
- Vectors no longer decay to pointers. A vector is qualified as a whole.
- Diagnostics spell a vector type `__vector(N) T`.
Lowering: a vector travels by address, as a complex value does. Each
operation reads its operands' lanes, computes each lane with the scalar
opcode and stores it into a frame temporary. Assignment, initializers
(locals, members, array elements, unions), ternaries and subscripts of
rvalues all take the address path, so nothing below the linearizer sees
a vector. Output matches gcc at -O0/-O1/-O2 on x86-64 and on aarch64
under qemu.
Also fixed:
- A vector_size among the declaration specifiers now applies to the
type they name: every declarator gets it, and a function returns the
vector. It used to reach only the first declarator, or the function
type itself.
- A vector value initializing a vector member or array element is no
longer read as brace elision. The parser's walker now uses the shared
elision predicate instead of its own copy.
Still rejected, with a message saying so: passing or returning a vector
(needs the vector ABI), plus __builtin_shuffle and
__builtin_convertvector.
Torture: 45 vector tests leave the skip list and pass. The harness
fixes:
- `dg-do ... { target <list> }` is now honoured, so other targets' tests
are skipped. aarch64 was building x86 asm tests, and x86 was building
aarch64 ones.
- New named aarch64 skip for pr27528, which needs non-PIC code; gcc
rejects it under PIE too.
Both baselines are re-recorded. The header sweep, CPython (run=41,758
SUCCESS) and sparse are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…assified; x86 vector alignment matches gcc
The calling conventions now carry vector_size values. At each call
boundary a vector travels as a "carrier", a type whose own convention is
gcc's for the vector (new Abi::vector_carrier):
- SysV and AAPCS64:
- 16 bytes as binary128: one XMM (SSE+SSEUP) or one Q register.
- 8 bytes as double: one XMM, or D.
- Integer-lane vectors of 4 bytes or fewer as the unsigned integer of
that size.
- Vectors over 16 bytes as a same-size, same-alignment struct (interned
with the vector type): memory or by reference, returned through the
hidden pointer.
- SysV: a one-lane `float` or `double` vector as a struct holding it --
MEMORY class, as gcc classes it and any struct holding one.
- Darwin, whose compiler is clang: an integer vector of 4 bytes or fewer
is passed as above but returned in V0, one lane in its low bits and
several widened to fill D0 (Abi::vector_return_carrier,
vector_return_widened).
- Win64: 8 bytes or fewer in a general register; 16 by reference and
returned in XMM0, as __int128.
- Every classifier delegates a vector to its carrier, so va_arg agrees
with the caller.
- The linearizer converts between a vector's address and its carrier for
arguments, parameters, returns, call results and va_arg (including the
Microsoft va_list). Neither backend changed for this.
- Parameters of vector type are no longer adjusted to pointers.
- Only a one-lane `float` vector on aarch64 stays refused: gcc passes it
like no type c17 has.
Structs holding vectors:
- SysV: a vector member's eightbytes are classified by its carrier;
struct { v4si } is one SSE+SSEUP register.
- AAPCS64: Homogeneous Short-Vector Aggregates use new
HfaBase::ShortVector64/128, homogeneous only with each other.
Layout fix: on x86-64 gcc aligns a vector to its own size (up to 2^28)
in structs and when passing it, while _Alignof answers at most 16. c17
had taken the _Alignof answer for the layout, so struct { char c; v8si v; }
placed v at 16 where gcc places it at 32. The layout now matches, and
_Alignof still answers 16 (TypeTable::alignof_value).
Also: `mode` written with `vector_size` among the specifiers now sets the
element width before the vector is built. It used to replace the vector.
Verified: every gcc/c17 pairing as caller and callee matches gcc at
-O0/-O2 on x86-64 and on aarch64 under qemu, covering register and
stacked vectors, varargs, structs of vectors and ms_abi (new
codegen/vector_abi tests). 40 more torture tests leave the vector skip
list; six remain (__builtin_shuffle/shufflevector/convertvector, and one
4-byte float vector). Both baselines are re-recorded. The header sweep,
CPython (SUCCESS) and sparse are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ector; __builtin_ilogb
The three gcc builtins over GNU vectors, with gcc's constraints in gcc's
words (new parse/vector_builtin.rs), lowered lane by lane:
- __builtin_shuffle(a[, b], mask) (ExprKind::VectorShuffle with a mask):
- Each lane is loaded at the run-time index, taken modulo the lanes
the operands hold.
- For two operands, a select chooses between a and b.
- __builtin_shufflevector(a, b, i...) (constant indices, -1 for any
lane):
- The operands may differ in length.
- The result has one lane per index, a power of two of them.
- __builtin_convertvector(v, T) (ExprKind::ConvertVector): each lane
converted as a cast does.
Also __builtin_ilogb / ilogbf / ilogbl, which pr108892 needed.
Output matches gcc at -O0 and -O2 on x86-64 and on aarch64 under qemu.
Five more torture tests leave the vector skip list; the one left is a
4-byte float vector passed by value. Both baselines are re-recorded.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…3..-msse4.2; vector asm operands; narrow-lane compare fix
Intrinsic headers, all written in C over GNU vectors and c17's vector
builtins. There is no __builtin_ia32_*/aarch64_*, and no text from
gcc's or clang's headers.
- x86-64:
- mmintrin.h, xmmintrin.h, emmintrin.h, pmmintrin.h, tmmintrin.h,
smmintrin.h, nmmintrin.h and popcntintrin.h. These are every name
gcc 13 defines there, including PCMPxSTRx in every _SIDD_ mode and
CRC-32C.
- immintrin.h stops at SSE4.2.
- Also x86intrin.h (__rdtsc and bit helpers) and mm_malloc.h.
- xmmintrin.h brings emmintrin.h, as gcc's does (mesa relies on it).
- aarch64: arm_neon.h, 3164 intrinsics across all 12 element types and
the x2/x3/x4 structs. The estimate instructions are bit-exact.
- Registered per architecture in builtin_headers.rs, so on another
target the name is not found, as with gcc.
- Checked differentially against gcc's headers on the hardware (x86-64)
and under qemu (aarch64): byte-identical over about 2M result lines at
-O0 and -O2. Five self-checking programs with gcc-derived expected
values are now tests (codegen/simd/).
Driver:
- -msse3, -mssse3, -msse4.1, -msse4.2/-msse4, -mpopcnt, their -mno-
forms and -march= (every 64-bit CPU gcc 13 names, and native) are
accepted. They define __SSE3__ .. __SSE4_2__ and __POPCNT__ as gcc does
(target::X86Isa): the last -march= gives the base, only the last of -mX
and -mno-X counts, enabling X brings what it needs and disabling it
drops what needs it, and SSE4.2 brings POPCNT unless an option named
POPCNT.
- Every intrinsic is available whatever the flags; only the macros
follow them.
Compiler bugs the header work found, fixed:
- Vector asm register operands:
- An "x"/"v" (x86) or "w" (aarch64) operand of vector type was passed
as its address in a general register and read back 8 bytes wide.
Operands now travel as the vector's carrier.
- Both register allocators class those pseudos as floating.
- The 16-byte moves on both sides are full XMM/Q moves.
- An unmodified aarch64 vector operand prints as vN, as gcc does;
%d0 and the other modifiers still give the width forms.
- An 8- or 16-bit lane compare on x86-64 now extends both operands
itself, as its signedness says. Inlining made one operand the constant
-128, kept zero-extended in its slot, so `v < -128` was true in every
lane (_mm_cvtps_pi8 at -O2).
Real-world check: ggml's quantization and SIMD dot products (llama.cpp
ggml-cpu x86 quants), built by c17, give output identical to gcc's at
the SSE2, SSE3, SSSE3 and SSE4.2 levels. mesa's util/ SSE users compile
wherever gcc's do.
Gates: fmt, clippy, release and debug suites, torture on both targets,
header sweep, CPython (SUCCESS) and sparse are all unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two vectors whose integer lanes differ in signedness compared at the left operand's lane type, so `a < b` with a signed and b unsigned compared signed while `b > a` compared unsigned. gcc compares unsigned whenever either side's lanes are unsigned; arithmetic still computes at the left operand's lane type, which is the result's. Tests: IR unit test (SetB/SetA, not SetLt/SetGt, and signed divide kept); runtime test of comparisons, divide, shift and result types, values from gcc. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… continuation
The initializer rule of C17 6.7.9p17-20 -- which subobject each element
lands in -- had partial copies that disagreed, giving wrong code and
false errors:
- A string literal never started brace elision, so `"abc"` was given a
whole `struct { char s[4]; ... }` member and the next value was checked
against the wrong member (`{"abc", 2.5, 0}` refused). At static storage
`{L"ab", 3, L"c", 4}` was refused as not constant.
- Elision counted a slot's scalar fields, but a string fills a whole char
array and a braced list a whole subaggregate: `{"abc", 7, {"de", 8}}`
for two `struct In` members put everything in the first (wrong values
at run time).
- The parser's walk never tracked elision in arrays: `struct Pt
g[2][2] = {1, 2, {.y = 5}}` checked `{.y = 5}` against `struct Pt[2]`.
- Positional elements after a designator chain restarted at the outer
list: `{.a.x = 1, 2, 3}` gave `1 0 2 3` (gcc: `1 2 3 0`), and
`[1][0] = 5, 6` lost the 6. A continuation into an anonymous member
given a braced list was zeroed.
Now `ObjectWalk` (parse/ast.rs) is the only statement of the rule. The
parser spells each chain continuation out once as explicit designators
(`Designator::Member(i)` names an anonymous member by position), so the
parser's checks, array sizing and the linearizer read landing places
instead of tracking the cursor at depth. Brace elision
(`brace_elision_span`/`designated_span`) and excess counting
(`initializer_list_end`, now also with designators present) use the same
walk; a string elides unless its slot is an integer-element array.
`count_scalar_fields` is deleted. A GNU range continues in its last
element only, as gcc does.
Tests: parse unit tests for the spans, the walk and the spelled chains;
c99/brace_elision.rs runtime cases (static and automatic, -O0/-O2,
aarch64); constraint_sweep accept/warn cases, including excess elements
after a chain.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No description provided.