Repository navigation
Conversation
The harness skipped about 250 tests by name across twenty lists (post-C17, GNU-only, nested functions, __label__, vector ABI, 64-bit frames, gcc-internal builtins, "gcc fails too", ...), and skipped every test whose dg-options carry a -m flag. Both are gone: -m flags are forwarded like any other option, and only the suite's own directives (.x files, dg-skip-if, dg-do target selectors) keep a test from running. TEMPLATE_NOT_ASSEMBLED stays -- it stops two tests at -S, as gcc's harness does, rather than skipping them. All four sub-suites, default levels: x86-64 7381 pass / 7702 (95.8%), 283 CFAIL, 19 RFAIL, 19 suite skips aarch64 7372 pass / 7702 (95.7%), 285 CFAIL, 17 RFAIL, 28 suite skips No regressions; the baselines gain 45 (x86-64) and 43 (aarch64) entries that the lists had hidden: __label__, vectors across calls, va-arg-7/8, the *printf-chk tests and the link-at--O0 group. DECISIONS.md's "Torture tests skipped by decision" section and TODO.md's reference to NEEDS_64BIT_FRAMES described the deleted lists. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One SKIP_BY_NAME table (`<sub-suite>/<name> <target> <reason>`) replaces the deleted per-category lists, holding only what is out of scope or what gcc rejects too: nested functions (including the non-local-goto __label__ tests), VLA struct members, post-C17 features, -fgimple, another target's assembly, 64-bit frames, __builtin_apply, #cpu assertions, -mavx, -finstrument-functions, and gcc-rejected tests (dll, pr117432, pr123864 under gcc 14; pr27528 on aarch64 only). Two tests on the old lists that pass are not in it. Old "gcc fails too" claims for bcp-1 and eeprof-1 were stale: gcc 13 passes both. x86-64 7380 pass, 105 fail, 217 skipped; aarch64 7371 pass, 105 fail. uabs-2@-O0 (C2y) leaves the baselines. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Under -fgnu89-inline (or gnu_inline), gcc accepts an `extern inline` body followed by a `static` definition of the same name -- the static one being the function -- where c17 reported "static declaration of 'bar' follows non-static declaration" (gcc.c-torture compile/20021120-1, -2). Not caused by d095fa1; the bug predates it. LinkedName tracks gnu_extern_inline: set by an `extern inline` declaration under GNU semantics, cleared by a plain `inline` declaration or a real definition. While it is set, a static declaration takes the name over (internal linkage, its own definedness) instead of drawing 6.2.2p7's error; the 6.2.7p2 type check (now check_linked_type) still runs. An inline-only body needs external linkage, so `extern inline` after a `static` declaration is an ordinary emitted static function. Matched against gcc 13 on 25 orders in -fgnu89-inline, gnu_inline and C99 modes. Also fixed on the way: `static int f(void); int f(void) {..}` emitted a global f; FunctionDef.is_static now comes from the function's linkage (C17 6.2.2p4/p5), giving a local symbol as gcc does. Torture: compile/20021120-1/-2 pass on both targets. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A filtered run that picked only tests the baseline does not hold yet -- checking a fix for tests that never passed -- died with "FATAL: the baseline and this run have no test in common" although every test passed. The fatal check exists to catch a changed tag format, which an unfiltered run still does; a filtered run now notes that nothing can regress and reports the newly passing tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
C17 6.9.2p1 (and 6.9.2p4's `extern int i3 = 3;`): c17 skipped every
extern declarator, so `extern int y = 5; int *p = &y;` left y undefined
at link, and `static int x; extern int x = 7;` read 0. An initialized
extern declarator now takes the definition path, with internal linkage
when a prior static declaration gave it (6.2.2p4).
Two defects found on the way: a tentative definition after an
initialized one pushed a second global (`int y = 5; int y;` failed to
assemble -- define_global now folds it in), and
`struct T; extern struct T t = {0};` compiled silently (now gcc's
storage-size error).
Diagnostics use gcc 13's wording: file scope warns "'y' initialized and
declared 'extern'", block scope errors "'z' has both 'extern' and
initializer"; a second definition is "redefinition of 'y'".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
c17 rejected constructs gcc accepts with a warning or only flags under
-pedantic. diag::Pedantic now owns both kinds of gcc pedwarn: pedwarn
(silent, warning under -pedantic, error under -pedantic-errors) and the
new pedwarn_default (warning, error under -pedantic-errors); -w and
system headers keep either hidden, as in gcc.
- A conditional with one void arm: pedwarn "ISO C forbids conditional
expr with only one void side"; the result is still void, so using it
as a value stays an error (torture pr26725, pr46309, 20000211-1).
- An external inline definition using, or declaring a modifiable, static:
pedwarn_default in gcc's words ("'x' is static but used in inline
function 'f' which is not static"), off the -fpermissive list
(torture pr38857).
- Swept against gcc 13 (default / -pedantic / -pedantic-errors), keeping
errors gcc 14 makes errors (implicit int, implicit function
declaration, return mismatch): a member list missing its last ';', an
out-of-range escape, an integer constant too large (C and #if), #line
out of range, `int;` and `static;`-style empty declarations, an
unnamed struct declaring nothing, a NUL between tokens, a label at the
end of a compound statement, an enumerator outside int.
- #line expanded its operand as #if does, so `#line abc` became line 0;
it is now ordinary text and an error, as in gcc.
Torture: 8 more passes per target, no regressions.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Owner's decision: match gcc. gcc 13 -O2 deletes printf(""),
fprintf(fp, ""), fputs("", fp), printf("%s", ""), fprintf(fp, "%s", ""),
vprintf("", ap), vfprintf(fp, "", ap), their _chk and _unlocked forms,
and a choice between empty strings, when the result is unused; C17
7.21.2p4 has such a write orient the stream, which c17 kept. The stdio
fold (ir/libcall_fold/stdio.rs) now discards them (writes_nothing,
Folded::Discard), still evaluating arguments with side effects; a used
result and puts("") keep the call. c17 still folds stdio only from -O1
up, so -O0 keeps every call (gcc folds at -O0 too).
Torture: builtins/printf, fprintf, fputs and execute/printf-chk-1,
fprintf-chk-1, vprintf-chk-1, vfprintf-chk-1 now pass at -O2 on both
targets.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gcc's lightweight non-local goto, which every gcc.c-torture builtins/*-chk
test reaches through chk.h. The existing Setjmp/Longjmp opcodes gain a
mode, JmpKind::{Library, Builtin}, so all returns-twice handling applies
unchanged.
- setjmp stores the frame pointer, the resume label (internal to the
instruction's code, so no pass can remove it) and the stack pointer
at words 0, 1, 2 -- gcc's non-CET layout on x86-64 and aarch64 --
returns 0, and returns 1 at the resume label after re-latching an
over-aligned frame's base register. longjmp loads all three before
touching the frame pointer, restores fp and sp and jumps.
- Register allocation: the setjmp clobbers every allocatable register
but its result; the function saves every callee-saved register in its
prologue, as gcc does; values live across a setjmp keep their slots
to the function's end.
- Locals of a function with a builtin setjmp stay in memory (gcc models
an edge from every call to the receiver: pr60003), and such a
function is never inlined, always_inline included.
- Prototypes as gcc's; a longjmp value other than constant 1 is gcc's
error.
Torture: +33 per target (built-in-setjmp, pr60003, pr64242, pr84521,
complex-6, 20011029-1, pr89280, pr82337, and the *-chk tests at -O0);
the *-chk tests still fail at -O2 on __builtin___*_chk object-size
folding, which c17 does not do.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Debug builds panicked on define_global's "tentative definition type mismatch" assert for shapes 1de82ff made reachable -- `int u; extern int u = 3;`, `static int x; extern int x = 7;` and the like: the parser copied the storage class onto the declarator's type at every level, so `static int`, `extern int` and `int` were three TypeIds for one object. Release builds were unaffected (the assert compiles out), which is how it got past 1de82ff's release-only validation. bind_declarator now gives an object of static storage duration without_decl_specifiers(typ) as its type (qualifiers kept), and complete_tentative_arrays writes back the same. define_global takes the TypeTable, debug-asserts the type carries no specifiers, checks a tentative definition against its completion by compatibility rather than identity, and records the definition's type. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
C17 6.2.7p3-4: after a later declaration the identifier has the composite type. c17 replaced a symbol's type only when an unsized array sat at its top level, so `int (*q)[]; int (*q)[3];` kept int (*)[] and `sizeof *q` was 0 (gcc: 12); a block-scope repeat of an extern was dropped; a function definition never composed with its declaration. The existing TypeTable::composite_type now serves all three paths: composite_with_prior_declaration composes any compatible pair of declarations with linkage (incompatible ones are left for check_redeclaration to report), declare_in composes a reused symbol's type, and define_function gives the function symbol the composite while the body is checked against the definition's own type. A block-scope `int h();` under a file-scope prototype now has the prototype, as in gcc. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
__builtin_issignaling is a new FpTest::IsSignaling: the operand is never converted (a conversion quiets), stored at its own type and tested as integer bits -- gcc's test for binary16/32/64, binary128 with the low word folded into the high one, and for x87 also the pseudo-infinity / pseudo-NaN / unnormal encodings gcc and glibc count as signalling. No floating compare, no x87 load of a float or double. A constant operand is an integer constant expression (constexpr.rs). Non-floating arguments get gcc's error. -fsignaling-nans / -fno-signaling-nans are accepted, last wins, and the positive form defines __SUPPORT_SNAN__ as gcc does. c17's float folds already refuse non-finite or signalling operands, so none quiets an sNaN gcc would keep under the flag. Torture: the issignaling tests pass on both targets (14 runs); the bfloat16 test (no __bf16) and the _Float128x one (no target has it, gcc included) join SKIP_BY_NAME. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A static initializer may now be the difference of two label addresses of
one function, plus or minus an integer constant, stored to an integer of
1, 2, 4 or 8 bytes. glibc's vfprintf builds its jump tables this way and
dispatches with `goto *(&&base + table[i])`. Before this change c17 said
"non-constant pointer expression in global initializer".
- linearize_label_diff.rs folds the expression into a sum of labels with
coefficients and a byte addend. It follows +, -, unary -, casts to a
pointer or to an integer at least as wide as the object, and integer
constants, using C pointer scaling (a void/char difference counts
bytes). What is left must be one label minus another. That becomes the
new Initializer::LabelDiff { end, start, addend }; `&&a - &&a` folds
to its constant, as gcc does. Every label is taken, so it is checked
to exist and its block stays alive. The function is marked
saves_label_in_static, so it is never inlined or cloned, matching gcc.
- The backend emits it as `.byte/.short/.long/.quad end - start +/- k`.
Directive::LongDifference is now SymDifference { size, end, start,
addend } and DWARF uses it too. Labels use the target's private
spelling, `.L` on ELF and `L` on Mach-O. Mach-O `L` labels are not
atoms, so llvm-mc resolves the difference for arm64 and x86_64. It
needs no load-time relocation, so a const table goes to .rodata.
- gcc's diagnostics are matched. A _Bool, floating or __int128 object is
"initializer element is not computable at load time". A pointer object
stays a non-constant initializer. A label of another function is
"used but not defined".
- When a static function that saves a label in a static object is
removed as dead, the static objects that name its labels are removed
with it, and so are statics that name those objects. Before, they
stayed and named labels nothing defined. That broke the assembly for
`&&a` tables already, and for the new difference tables too.
gcc 13 gets the sign of the addend wrong in `(&&b + 4) - &&a` (it emits
`.Lb-.La-4`). c17 emits the correct value, and the e2e test checks it
against the runtime difference.
Torture: compile/labels-3 and execute/pr70460 now pass at -O0 and -O2 on
x86-64 and aarch64 (+4 per target baseline).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Builtins gcc made for its own lowering that code also calls:
- __builtin_stack_save / __builtin_stack_restore with gcc's prototypes,
lowered to the existing StackSave/StackRestore opcodes; locals are
frame-pointer addressed and epilogues reset sp from fp, so a program's
restore cannot break VLA bookkeeping.
- __builtin_clear_padding: ir/padding.rs records which bits of each byte
belong to a member and zeroes the rest inline -- whole padding runs as
a memset, a byte shared with a bit-field by load/and/store, a VLA
pointee by a loop. gcc 13's rules: union padding is padding in every
member, x87 long double's 6 bytes, an array argument clears its first
element only; gcc's errors for non-pointer, incomplete, const and
flexible-array pointees.
- __builtin_cexpi{,f,l} as cexp{,f,l}(+0 + xi) -- bit-identical to
cexp(I*x) -- and __builtin_cpow{,f,l} as the library cpow.
Torture: compile/20071117-1, pr98087, pr110266, pr54428 pass on both
targets; x86-64 has no compile failures left.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On x86-64 an unnamed non-zero-width bit-field raised the alignment of
its struct or union and reserved its type's access window, so `struct {
char a; int :5; char b; }` was 4/4 where gcc's is 3/1 -- a different
ABI for any such struct crossing to gcc-built code. The System V psABI
says unnamed bit-fields' types do not affect the aggregate's alignment.
One rule, TypeTable::unnamed_bitfields_align_aggregate(): only aarch64
other than Apple (AAPCS64) lets them; elsewhere an unnamed field is
placed by the unit rule but adds no alignment and spans only the bytes
its bits touch, in structs and unions. Apple arm64 follows clang
(DarwinAArch64TargetInfo: UseZeroLengthBitfieldAlignment = false), which
also changes a zero-width field there: `{ char; int :0; char; }` is now
5/1 (from clang's source; no Apple run here).
Also fixed: attributes after an unnamed field's width failed to parse;
a typedef's raised alignment did not place a bit-field; a written
alignment on a zero-width field was ignored; an unnamed field wider
than its carrier was rejected.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A struct or union declared with a byte order other than the target's stores its scalars byte-reversed. The order is a type property: TypeModifiers::REVERSE_ORDER goes on the member types gcc counts as scalars (integers, enums, floating and complex types, bit-fields, and elements of arrays of them), never on pointers, vectors or nested aggregates. Compatibility ignores it; lvalue conversion and typeof drop it. Linearizer::emit rewrites every Load/Store at a reversed type into a native access plus an explicit Bswap16/32/64 (floats and 16-byte scalars via a frame temporary), so no later pass sees the attribute and load forwarding/memloc stay correct. Bit-fields keep their layout and are numbered from the most significant bit, as gcc does; static images hold the reversed bytes; complex halves are reversed individually. Parser: the attribute in all three specifier positions, gcc's variant type on a reference (typedef struct S __attribute__((...)) T), and #pragma/_Pragma scalar_storage_order big-endian|little-endian|default, carried as a LayoutPragma marker beside #pragma pack and reproduced by -E. Diagnostics in gcc's wording: "cannot take address of scalar with reverse storage order", the array-address warning, bad/missing argument errors, "attribute ignored" off a specifier, the pragma warnings, and "sorry, unimplemented: reverse storage order for XFmode". Torture: execute/20230630-2 and -4 now pass on host and aarch64. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Three defects behind bcp-1, which gcc passes at every level:
- A string literal's start ("hi", &"hi"[0], "hi"+0, L"wide", through
representation-keeping casts) answered 0; gcc answers 1. The parser's
constant_p helper now applies gcc's order: constant -> 1, string
literal start -> 1, pointer/array/struct/union/function type or -O0 ->
0, else deferred to sccp.
- The pre-inlining sccp round answered an unproved ConstantP of a
parameter with a final 0, so inlined copies (bad3(1), bad6(1)) could
never become 1. sccp::run_before_inlining leaves it open; ConstantP of
a float constant now answers 1 too.
- A deferred operand had to pass is_pure_expr, the speculation question,
which rejects division, FP-raising operations and dereferences -- but
ConstantP's operand never runs. is_pure_expr takes an Evaluation mode
(Speculative / Discarded); Discarded still refuses writes, calls and
volatile reads.
New fold, as gcc does: a one-byte load at a constant offset into a
string literal or const char array folds to the byte (constglobal.rs,
via KnownGlobals' ConstBytes), so "hi"[0] is a constant once optimized.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
emit_ret loaded a stack-resident __int128 into X0/X1 with one ldp, and for every other location moved the low half to X0 and zeroed X1. A constant operand is an immediate, so `return (i128)(~(u128)0 >> 1);` returned 2^64 - 1 on aarch64 at -O0. Materialize both halves of an immediate. Test: codegen_int128_return_of_a_constant_keeps_its_high_half (failed on aarch64 at -O0 before the fix). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… value C17 6.3.1.4p1 leaves the conversion undefined. gcc's constant folder saturates (maximum above the range, minimum or 0 below it, 0 for any NaN) in every context, at -O0 too; c17 refused, so the run-time instruction ran and x86-64 gave INT_MIN. gcc.c-torture execute/20031003-1 failed on the host for this. One rule, FloatVal::to_integer_saturating, now serves every folder: - constexpr: enumerators, case labels, _Static_assert, bit-field widths, _Alignas and __builtin_constant_p take the saturated value. A new ConstScope::ArrayBound keeps refusing it, so the array is a VLA as in gcc. - linearizer: a floating constant cast or implicitly converted to an integer type in code folds at every level (fold_float_to_integer). Implicit conversions that saturate warn as gcc does; -Wno-overflow silences. - constfold::eval_fcvt (instcombine): FCvtS/FCvtU of a constant saturates. eval_fcvtf now also folds an infinity or quiet NaN, which raise nothing, so `float x = -INFINITY; (short)x` folds from -O1. Run-time conversions stay hardware instructions. Known gap: a 128-bit conversion becomes a libgcc call before the optimizer runs, so a constant reaching one through a variable is still converted at run time. Tests updated from "not folded" to gcc's answer: instcombine an_out_of_range_conversion_is_left_alone (now ..._saturates) and test_parser test_out_of_range_float_cast_is_not_an_integer_constant (now split into a saturating test and an array-bound VLA test). New: e2e float_to_int_saturation (gcc-validated on x86-64 and aarch64 at -O0/-O1/-O2), constfold/float/linearizer/diagnostic unit tests. DECISIONS.md and BUILTIN.md record the choice. Torture baseline gains execute/20031003-1 at -O0 and -O2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A floating GNU vector of four bytes or fewer was refused at every call
boundary on aarch64 ("c17 does not pass or return this vector type on
this target"), and v2hf on x86-64 as well. gcc.c-torture ieee/fp-cmp-cond-1
failed on aarch64 because of it.
Each platform compiler has its own rule, probed with gcc 13 -O2 -S and,
for Darwin, clang's i32 coercion lowered by llc -mtriple=arm64-apple-macos:
- gcc AAPCS64: on the stack in an eight-byte slot whatever registers are
left; every later general-register argument goes to the stack too (NGRN
becomes 8) while the V registers stay available. Returned in W0
(zero-extended at two bytes). Like no C type, so the carrier is a struct
of its own (TypeTable::vector_stack_carrier) classed by a new
ArgClass::Stacked, handled by param_layout, the caller's register
assignment, the prologue tally and va_arg. The callee reads the
parameter by address, so an inlined body reads through the address the
call passes.
- clang on Darwin: passed as i32 (unsigned int carrier, four bytes on the
stack), returned in V0: S0, or H0 for v1hf.
- gcc System V: v2hf is SSE class, the low four bytes of XMM0 both ways
(float carrier), also as a struct member; one-lane shapes stay in memory.
gcc's own va_arg reads such a vector from the general save area while its
caller stacks it; c17's va_arg follows gcc's caller.
Tests: interop c17/gcc in every pairing on the host and on aarch64 under
qemu (arguments, returns, mixed with ints and floats, more than eight
vector arguments, struct members, inlined static callees), a variadic
aarch64 test (c17 both, gcc caller), asm tests for aarch64 Linux, aarch64
Darwin and x86-64, ABI and linearizer unit tests. The refusal tests now use
a three-lane vector, the one shape still without a carrier.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Debian builds with _FORTIFY_SOURCE, so glibc's headers turn memcpy, strcpy, sprintf and friends into __builtin___*_chk calls with __builtin_object_size. gcc decides them at compile time; c17 called the _chk functions and, since __builtin_object_size of a wrapper's parameter folded to (size_t)-1 at parse time, checked nothing. The 15 gcc.c-torture builtins/*-chk tests failed at -O2. - __builtin_object_size is deferred at -O1+ (ExprKind::ObjectSize -> Opcode::ObjectSize), as ConstantP is: ir/objsize.rs answers a placeholder once its object is known through copies, addresses, displacements, selects, phis and constant allocas (types 0/1 the largest arm, 2/3 the smallest); once the optimizer converges the rest answer unknown and the passes run to a fixed point again. -O0 answers unknown at once, as gcc. constexpr's deferred hook now serves both builtins. - One _chk rule (ir/libcall_fold/fortify.rs, a 14-row table): an unknown size, a constant count or the longest of several that fits, a string plus terminator that fits, an empty append, or a printf format with no directive / "%s" of a known string becomes the plain call or block op; a provable overflow keeps the check, which aborts at run time. Not matched (DECISIONS.md): type 1/3 through IR has no subobject info, no alloc_size, no -Wstringop-overflow warning. Torture: every non-skipped test passes on both targets (x86-64 7481, aarch64 7472). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
C17 6.8.5p3 lets the declaration in a `for` declare only objects with
auto or register storage. c17 already refused a typedef, a tag and the
static/extern/thread-local storage classes there, but accepted a
function declarator -- `for (int g(void);;)`, also through parentheses,
a typedef or an old-style list -- and an enumeration constant introduced
by the specifiers, `for (enum { A } e;;)`.
Both now get gcc 13's error, "declaration of non-variable 'g' in 'for'
loop initial declaration". The typedef case shares the same reporter.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gcc 13 does not fold `1 << -1` or `1 >> -1`: it warns that the count is
negative and then refuses the expression wherever C requires a constant
-- "initializer element is not constant" for a static initializer at
file or block scope, and likewise for an enumerator, a `case` label,
`_Static_assert` and a file-scope array size. c17 folded the shift with
the hardware's count mask and accepted all of them. A count past the
width (`1 << 40`) and a negative value shifted (`-1 << 1`) remain
constants, as in gcc, and an unevaluated operand (`0 ? 1 << -1 : 2`)
is still fine.
The constant walk now declines a negative count, and the diagnostics
take gcc's words:
- a non-constant static initializer leads with "initializer element is
not constant", keeping c17's note of which part is to blame;
- an enumerator: "enumerator value for 'E' is not an integer constant",
at the enumerator, instead of "enum value must be constant";
- `_Static_assert`: "expression in static assertion is not constant";
- a `case` label blames the program ("not an integer constant
expression") rather than the compiler's folder.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An array type recorded its extent as Option<usize>, so `int[]` and `int[n]` were both None and interned to one TypeId. Nothing holding only a type could say whether sizeof of it was a run-time value or a constraint violation, and each consumer guessed: check_sizeof_expr_operand looked only at a bare identifier and a flag on its symbol, so `int (*q)[]; sizeof *q` (and sizeof through __typeof__, or of a flexible array member) compiled to 0 where gcc 13 says "invalid application of 'sizeof' to incomplete type 'int[]'"; check_pointer_steps skipped every unsized pointee, so `q++`, `q + 1` and `q[1]` stepped by zero. The type now carries types::ArrayExtent -- Known(n), Variable or Unknown -- in place of array_size, and every consumer matches on it: - the declarator builds the extent it parsed directly (its private Extent enum, which already had the three cases, is gone); - is_incomplete_array() answers "unknown size", and type_name_is_incomplete, the sizeof/_Alignof/va_arg/_Generic checks, flexible array members, tentative definitions, "array size missing" and linkage completion ask it; type_name_is_incomplete loses its `extents` argument, and with it the sizeof expression check its identifier-only special case; - variable_array_levels() replaces unsized_array_levels for what counts size expressions (record_vm_extents, sizeof_type_is_runtime, vm_extent_count, parameter VM dims, clear_padding, the constant walk); - composite_type takes a known size, then a variable one (C17 6.2.7p3); compatibility still only rejects two different known sizes; - arithmetic on a pointer to an array of unknown size is refused with gcc's "invalid use of array with unspecified bounds"; a pointer to a VLA still steps. A VLA is still spelled `[]` in diagnostics: the type does not hold its size expression. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gcc.dg/sso-1.c: gcc 13 will not statically initialize any member of a scalar_storage_order struct or union in the reverse of the target's order with an address constant -- an object's or function's address, a string literal, an address plus an offset -- whether the member is a pointer (which gcc stores natively), an array of pointers, or an integer holding `(long)&i`. It says "initializer element is not constant". c17 accepted the pointer cases outright, and for an integer member said "not computable at load time" at line 0, having no position for a global. A struct of pointers alone kept no trace of its order, since the order lived only on the member types gcc reverses. CompositeType now records `reverse_order`, set by the attribute, the pragma and a typedef variant in the other order (a variant differing only in this is now a type of its own). The static struct/union initializer refuses a member holding an address when its aggregate is reversed -- unless the member is a struct or union, or an array of them, which keeps its own order -- and ast_init_to_ir refuses one for a reversed scalar at the expression's position. Automatic objects, null and integer-valued pointers, and nested native-order structs are still accepted, as gcc accepts them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
f99cc2f's named_sizes_skip_weak_and_unbounded_objects set Type::array_size = None, a field 51b8cf4 replaced with ArrayExtent (the two were developed in parallel); it now interns Type::array_of(char, ArrayExtent::Unknown). Both objsize test fixtures also name their target instead of the host. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…a carrier
c17 accepted `short __attribute__((vector_size(6)))`, which gcc rejects at the
declaration. c17 then refused the type at every call boundary ("c17 does not
pass or return this vector type on this target"). Now the declaration is
rejected with gcc's wording, along with the related shapes gcc rejects:
- "number of vector components N not a power of two" (3 shorts, 3 doubles,
384 ints, ...), on a typedef, an object or a member
- "zero vector size" for vector_size(0)
- "'vector_size' attribute argument value '-16' is negative"
- "vector size not an integral multiple of component size"
- "invalid vector type for attribute 'vector_size'" for a complex or _Bool
lane, which c17 used to accept
Each remaining vector size is a register width or travels as an aggregate,
so Abi::vector_carrier and vector_return_carrier now return a TypeId instead
of an Option. The refusal (check_not_vector_value, at four parser sites)
is deleted, along with uncarried_vector_class, the linearizer's
`unwrap_or(vec)` fallback and System V's `None => Memory` eightbyte arm.
Making the refusal unreachable surfaced a second hole. Carriers were keyed by
the vector's written alignment, so `typedef v8si w __attribute__((aligned(64)))`
had no carrier and was refused. Vectors declared with `vector_size(32),
aligned(64)` or `vector_size(4), aligned(16)` were accepted, but on x86-64 they
started on their own boundary on the stack. Probing gcc 13 (-O1 -S) shows it
passes a vector as its main variant: a raised or lowered alignment moves no
argument (aligned(64) v8si after six longs and an int lands at offset 32, as a
plain v8si does). Carriers are now keyed by the natural alignment, and
vector_of interns them for the natural shape.
Tests: gcc-wording diagnostics for every shape on all three declaration
forms, a parser unit test for lane counts, ABI unit asserts that a written
alignment keeps the natural carrier, and a new interop test
(vector_abi_aligned_interop_host/_aarch64) of nine aligned shapes as
arguments and returns in every c17/gcc pairing. Before this change it failed
on x86-64 (a8si, t8si, w1sf, a1sf) and on aarch64 (w1sf). The two refusal
tests that used a 6-byte vector now check that a one-lane float vector and
both realigned shapes pass on aarch64 Linux, aarch64 Darwin and x86-64.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
clang on Darwin coerces every vector of four bytes or fewer to i32 (AArch64ABIInfo::coerceIllegalVector). c17 did this only for floating vectors. It passed v2qi, v1hi and v1qi as the unsigned integer of their own size. In a register that makes no difference. On the stack Darwin packs an argument at its own size, so once the general registers ran out these took 1 or 2 bytes instead of 4, and every stacked argument after them was misplaced. Evidence: clang's IR for the call, `declare void @take(i32 x 11)`, lowered by `llc -mtriple=arm64-apple-macos`, stores the stacked arguments at [sp], [sp, #4] and [sp, #8], and the callee reads them back at the same offsets. c17 stored them at [sp], [sp, #2] and [sp, #4]. Linux is unchanged: gcc gives each one an eight-byte slot. Aapcs64Abi::vector_carrier now answers `unsigned int` on Darwin for every vector of four bytes or fewer. Two consumers had relied on the carrier being no wider than the vector: - vector_to_carrier loaded the carrier's full width from the vector's address, reading past a one- or two-byte vector. It now loads the vector's own bytes and zero-extends them. This also covers the existing Darwin v1hf. - An asm register operand took the ABI carrier as its type, so an "=r" output stored four bytes into a v1qi. It is now the unsigned integer of the vector's own size for every vector of four bytes or fewer. That also replaces the struct carriers that a v1sf operand used to get on x86-64 and aarch64 Linux. Tests: an asm test for aarch64 Darwin and Linux with eleven such arguments, pinning the caller's stores and the callee's loads at 4-byte and 8-byte strides (it fails without the fix: [sp, #8] is missing); a linearizer unit test for the narrow load and the asm output store; an ABI unit assert of the carrier per OS; and an interop test with thirteen arguments in every pairing on the host and on aarch64 under qemu. On Apple arm64 the host pairing is against clang. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Under __attribute__((ms_abi)) gcc passes a one-lane floating vector (v1sf, v1df, v1hf, v1tf) by reference to a copy, whatever its size. c17 passed it by value in a general register, as the integer of its size. Every shape of sixteen bytes or fewer was probed with gcc 13 -O2 -S, as argument and as return value: - v1sf, v1df, v1hf: the callee reads the argument through the pointer in its position (`cvttss2sil (%rdx)`, `pinsrw $0, (%rdx)`). A stacked one is a pointer in the stack slot (`movq 40(%rsp), %rax; cvttss2sil (%rax)`), and the caller passes the address of a copy. They come back in RAX. - v1tf (and v1xf): by reference, and returned through the hidden pointer in RCX, not in XMM0, where v2df, v4si and v1ti come back. - v2hf, v4hf, v2sf and every integer vector of eight bytes or fewer (v1qi, v2qi, v4qi, v1hi, v2hi, v4hi, v1si, v2si, v1di): by value in the integer position, returned in RAX. c17 already did this. - A struct holding a v1sf follows the aggregate rule, by value. c17 already did this. Win64Abi::vector_carrier gives a one-lane floating vector the struct that wraps it (TypeTable::vector_wrapper_carrier), which classify_param sends by reference (new TypeTable::is_vector_wrapper_carrier). The new vector_return_carrier returns it as the unsigned integer of its size, or, at sixteen bytes, as the wrapper through the hidden pointer. Tests: an interop test against gcc-compiled ms_abi code (codegen_ms_abi_small_vectors_interoperate_with_gcc) covers all sixteen shapes as arguments, including by-reference ones past the fourth position and a callee writing its copy, and as returns, in every pairing at -O0 and -O2. A one-unit run at -O1..-O3 exercises inlining. Before this change the gcc caller / c17 callee pairing failed at the first check. ABI unit asserts cover the class of each one-lane width and of v2hf and v1si. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rge warning
Fixes from review of the torture-tail series:
- An _Atomic member of a reverse-storage-order struct was read and
written in native byte order. gcc rejects it as an operand anywhere,
even unevaluated ("cannot take address of scalar with reverse storage
order"), except as the whole operand of sizeof/_Alignof/typeof/a
_Generic controlling expression; the parser now holds each such
access and reports what no such operator claims. An element of an
_Atomic array member stays a plain swapped scalar, as in gcc.
- scalar_storage_order written on a reference to an existing tag made a
fresh incompatible type at every use, so `x = y` failed. As in gcc it
is now ignored with a warning on variables, parameters, members,
returns and casts, makes a variant (with gcc's warning) in a type
name, and a distinct variant per typedef declarator.
- __builtin_constant_p of a GNU vector answered 0 at once; it now defers
and asks each lane (Linearizer::vector_constant_p), 1 for a vector of
constants from -O1 as in gcc.
- A decimal constant too large for any type also drew "so large that it
is unsigned" about its truncated value; gcc gives only "too large".
Checked, nothing lost: ba90091's FoldCtx.bytes come from the same
ConstBytes::build at the same point in the pipeline.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gcc's __builtin_setjmp buffer layout on x86-64 depends on
-fcf-protection. Under return protection (=return, =full, and the bare
flag) it is [0] rbp, [8] resume, [16] shadow-stack pointer (rdsspq into
a zeroed register), [24] rsp. Its longjmp then unwinds the shadow stack
with an incsspq loop that pops 255 entries at a time, and does nothing
when rdsspq reads 0. Without return protection (none, branch, check)
the layout is [0] rbp, [8] resume, [16] rsp. Under branch protection
(=branch, =full) the resume label starts with endbr64. Measured with
gcc 13.3 at -O0 and -O2.
c17 accepted the flag with an "unrecognized option, ignored" warning
and always used the three-word layout. Mixing it with gcc-built code
under -fcf-protection=full failed. A gcc longjmp read c17's rsp as a
shadow-stack pointer and hit incsspq (SIGILL). A c17 longjmp loaded
gcc's shadow-stack word, 0, as rsp.
The driver now folds -fcf-protection[=level] and -fno-cf-protection
into one option, and the last one wins. The bare flag means full. An
unknown level is an error in gcc's words. With no flag the setting is
none, which matches Debian's gcc (Ubuntu's defaults to full).
target::CfProtection carries {branch, ret} to CodeGenBase. Only the
x86-64 setjmp/longjmp read it. This adds no endbr64 at function entry,
no .note.gnu.property and no __CET__. aarch64 is untouched. The
library setjmp/longjmp needs no change because glibc saves and unwinds
the SSP inside its own jmp_buf.
Tests: c17/gcc interop in every pairing at -O0/-O2 under
-fcf-protection=none and =full. A second interop test turns the shadow
stack on in-process with arch_prctl(ARCH_SHSTK_ENABLE) and longjmps 601
frames deep, which exercises the incsspq loop for real. Both tests
failed before this change. Also added: asm tests for both layouts and
for the endbr64 landing pad on an explicit x86_64 triple, a driver unit
test for last-flag-wins, and a driver integration test for accepted
spellings and the bad-level error.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
The change spans compiler-wide behavior (preprocessor, lexer, diagnostics, ABI, and multi-architecture codegen) with subtle gcc-conformance semantics that warrant final human review.
Review effort: Balanced
Findings: None
What changed in this PR
This PR is a broad batch of correctness and conformance updates to the cc C compiler crate, aligning several behaviors with gcc. It refines preprocessor and lexer handling (#line out-of-range numbers now warn-and-wrap instead of erroring, inter-token NUL bytes are treated as whitespace with a single warning per run, and #pragma scalar_storage_order survives -E), reworks over-wide integer and character-escape handling onto a two-kind pedantic-warning model, and expands test coverage (gnu89 extern inline→static takeover, -fcf-protection flag acceptance, interop/object test helpers gaining an extra-flags variant). A documentation note in TODO.md is also updated.
Changes:
- Preprocessor/lexer conformance:
#linenumbers beyond the 32-bit counter warn by default and wrap (pedantic-only past C17's limit); NUL between tokens is whitespace;scalar_storage_orderpragma is carried through as a layout marker. - Diagnostics: over-wide integer constants and out-of-range numeric escapes now use
pedwarn_default(default warning, error under-pedantic-errors); newinteger_digits_value/report_too_large_integerhelpers keep the low 64 bits. - Tests/helpers: added coverage for storage-order pragmas,
#linewrapping, gnu89 inline/static interaction,-fcf-protection; test helpers gain*_withvariants threading extra compiler flags.
| File | Description |
|---|---|
| cc/token/preprocess_directive.rs | #line operand rewrite with line_number_operand (warn-and-wrap); scalar_storage_order pragma emission; #line operands now macro-expanded as text. |
| cc/token/literal.rs | New integer_digits_value/report_too_large_integer; over-range escapes switched to pedwarn_default. |
| cc/token/lexer.rs | skip_whitespace treats NUL as whitespace, warning once per run. |
| cc/token/test_preprocess.rs | Test for scalar_storage_order pragma becoming a layout marker. |
| cc/token/test_literal.rs | Test for integer_digits_value low-64-bit truncation semantics. |
| cc/token/test_lexer.rs | Test that NUL between tokens is whitespace. |
| cc/tests/preprocessor/directives.rs | Test that the storage-order pragma survives -E. |
| cc/tests/preprocessor/conformance.rs | Replaces the #line out-of-range error test with a warn-and-wrap test. |
| cc/tests/driver/mod.rs | gnu89 extern inline→static takeover cases and symbol-binding checks. |
| cc/tests/driver/gcc_flags.rs | Test that -fcf-protection levels are accepted and bogus levels error. |
| cc/tests/common/mod.rs | Adds c17_object_with/host_link_and_run_with/interop_host_with extra-flag variants. |
| cc/tests/codegen/mod.rs | Registers new bitfield_layout, float_to_int_saturation, label_diff test modules. |
| cc/TODO.md | Updates the 64-bit-frame torture note to drop the removed skip list. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…eld ABI
Four test expectations were written for Linux targets and failed on the
macOS arm64 CI; c17's code was right in each case.
- c89 zero-width bit-fields: Apple arm64 follows the x86-64 rule (an unnamed
bit-field does not affect aggregate alignment), not AAPCS64. All 40
size/alignment values c17 gives for Darwin were checked against clang's
record layout via libclang.
- __builtin_clear_padding of struct { char; long double; }: long double is
double on Apple, so the struct is 16 bytes with padding at 1..7.
- (long double) of a double sNaN is no conversion where long double is
double, so it raises nothing; that check is skipped there.
- float_to_int_saturation: Apple has no _Float128; those rows use long
double instead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
float_suffix decided long double's rtlib mode from the architecture alone, answering "tf" on every non-x86-64 target. Apple arm64's long double is binary64, so (long double) <-> __int128 called __fixtfti, __fixunstfti, __floattitf and __floatuntitf, which Apple's runtime does not provide: the link failed on macOS (codegen::float_to_int_saturation). The suffix now follows TypeTable::fp_format, the one place that knows each target's long double format, so Darwin gets __fixdfti and friends while aarch64 Linux keeps tf and x86-64 keeps xf. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No description provided.