Skip to content

cc Updates - #726

Merged
jgarzik merged 16 commits into
mainfrom
updates
Oct 6, 2026
Merged

jgarzik merged 16 commits into
mainfrom
updates

Conversation

@jgarzik

@jgarzik jgarzik commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

No description provided.

jgarzik and others added 15 commits October 6, 2026 17:55
c17 accepted the -Werror family and ignored it, so a configure probe that
relies on a warning failing the compile got the wrong answer.

- diag: a Werror switch folded in command-line order (-Werror / -Wno-error
  for every warning; -Werror=X / -Wno-error=X give group X its own verdict,
  and -Werror=X also enables X). Every warning goes through one emit path,
  give_warning: a shown warning that -Werror covers becomes an error tagged
  [-Werror] or [-Werror=<group>], and the unit ends with gcc's "all/some
  warnings being treated as errors" line. -w and system headers are never
  promoted. The named-group sites call group_warning(group, ..), so the group
  is known at emit time. -Werror=pedantic enables -Wpedantic.
- -Wno-X is now order-aware: a later -WX or -Werror=X turns X back on.
- System headers: a header found in a system directory (or beside a system
  header that included it) and the bundled headers are system streams, as in
  gcc; before, only `# N "f" 1 3` linemarkers made one, so libc's warnings
  were shown and -Werror would have failed on them.
- Driver: the unrecognized -f/-g option warnings honour -w and -Werror
  wherever they appear; under -Werror the three cases gcc refuses outright
  (unknown option, -o with -c or -E and several files) are errors. Other
  driver warnings stay warnings, as gcc's do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rs refuses them

40 plain warning sites in cc/token and cc/parse are constraint violations
gcc 13 warns about by default and makes errors under -pedantic-errors.
They now go through diag::pedwarn_default: the same warning text by
default, an error under -pedantic-errors.

Preprocessor: missing terminating quote, #if shift overflow, __VA_OPT__ and
__VA_ARGS__ outside a variadic macro, extra tokens after a directive, no
whitespace after a macro name, macro redefinition.
Parser: declaration does not declare anything, excess initializer elements,
initializer-string too long, inline/_Noreturn on a parameter or variable,
inline main, conditional and comparison pointer mismatches, __func__ at
file scope, a constant so large it is unsigned, K&R parameter defaulting to
int, empty attribute declarations, misplaced fallthrough, the non-fatal
assignment/argument/return conversions, and -fpermissive's implicit int and
implicit function declaration.

Each was swept against gcc 13 with and without -pedantic-errors; sites
where gcc keeps a plain warning are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rnal error

loc_to_gp_operand printed a thread-local as Local Exec (MemAddr::TlsLocalExec),
the inline-asm raw moves to and from a location as %fs:sym@TPOFF, and
loc_to_asm_string / loc_to_asm_operand as a plain sym(%rip), whatever the TLS
model. None is reached: under the static ELF models register operands arrive
loaded and memory operands go through global_mem, and under the call-based
models ir::tls removes thread-local operands. Each now reports through
report_tls_operand, the internal error check_tls_reached_only_by_address
already gave, factored out so both share one implementation.
MemAddr::TlsLocalExec stays: the load, store and global_mem paths build it
when Local Exec is the right model.

Unit tests show the guard firing at all five sites; an e2e test runs inline
asm on local and extern thread-locals of every constraint class at -O0, -O2,
-fPIE and -fPIC, and an asm test checks FreeBSD shared objects use Initial
Exec for them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An operand whose suffix names no language (an object under another name,
a GNU ld linker script, a version script) was warned about as an
"unrecognized file type" and dropped from the link. It is now a linker
input in its command-line place, exactly like a .o, with no warning.

Suffixes gcc gives another front end are refused with an error naming
the language ("c17 does not compile C++", Objective-C, Objective-C++,
Fortran, Go, D, Ada, Modula-2) instead of reaching ld. A .h is
preprocessed under -E/-M/-MM and otherwise refused ("c17 does not write
precompiled headers"). .sx is preprocessed assembly, as .S.

When nothing is linked, every linker input (objects included) gets gcc's
"linker input file unused because linking not done" warning, which, as
in gcc, -w does not silence and -Werror does not promote. -M and -MM now
count as not linking: with an object operand they used to run the link.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
c17 accepted every -W<name> in silence, so configure probes
(AX_CHECK_COMPILE_FLAG, meson has_argument, cmake check_c_compiler_flag)
concluded it supports any warning flag.

- New cc/warn_options.rs: one table classifying warning names. 12 are
  c17's own groups/switches (attributes, overflow, pedantic, error,
  c17-dialect, ...); 187 are gcc C names accepted with no effect, drawn
  from Debian's dpkg-buildflags, the autoconf-archive warning macros,
  meson's warning levels, the c17 tests, the torture suite's dg-options
  and the projects c17 builds, each verified against gcc 13 (except
  -Wcompare-distinct-pointer-types, gcc 14's, which the torture suite
  passes). 21 stems take a value (-Wformat=N, -Wlarger-than=SIZE,
  -Wnormalized=WORD, ...), checked with gcc's range/size-unit/choice text.
- Driver: an unknown -W<name> is "unrecognized command-line option", and
  an unknown -Werror=X / -Wno-error=X is "'-Werror=X': no option '-WX'"
  (reported only when the driver-level checks pass), exit 1 before any
  compile. -Wl,, -Wa, and -Wp, are untouched; bare -W stays -Wextra.
- -Wno-<unknown> is accepted silently; if a translation unit shows any
  diagnostic, each gets gcc's "may have been intended to silence earlier
  diagnostics" note, last first, ahead of the -Werror closing line.
  diag::report_promoted_warnings becomes diag::finish_unit, which gives both.
- test_compile's apply_flags refuses an unknown name the same way.

-Wpedantic-errors is refused, as gcc refuses it: -pedantic-errors is folded
in among the -W options internally but is not a -W name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ves; warn on #if overflow, as gcc does

- `\x` with no hex digit is an error where the literal is decoded (string,
  character constant, #if, asm), via a new Escaped::EmptyHex; the lexer no
  longer warns, so -E and skipped groups are silent like gcc.
- A comment reaching EOF is the error "unterminated comment" at its `/*`.
- A literal missing its quote keeps gcc's default pedwarn (now also at
  EOF) and is marked Spelling::Unterminated: -E and `#` spell it without an
  invented quote, and compile_tokens refuses one that reaches the parser
  with "missing terminating X character".
- `#foo`, `#!`, `#"x"` are "invalid preprocessing directive #..." errors and
  `# 12abc` is '"12abc" after # is not a positive integer', except in
  skipped groups and assembly.
- #if signed +, -, *, /, unary - and lossy left shifts give gcc's default
  pedwarn "integer overflow in preprocessor expression"; shifts take gcc's
  values (negative count reverses, >= 64 shifts everything out) and only
  warn when signed bits are lost.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
-fpic, -fPIC, -fpie, -fPIE and -fno-pic/-fno-PIC/-fno-pie/-fno-PIE are
one option to gcc: the last one given wins, and every -fno- spelling
means position-dependent code, PIE included. c17 kept three independent
booleans: -fno-pic, -fno-PIC and -fno-PIE were "unrecognized option,
ignored", -fPIC stuck once seen, and -fPIC -fno-pie still defined
__PIC__.

PositionIndependence is now an enum (Absolute, Pic(level), Pie(level))
that the driver folds from the family in command-line order, defaulting
to a large-model PIE on Linux as Debian's gcc does. It drives the
macros and code generation:

- __PIC__/__pic__ and __PIE__/__pie__ are 1 for -fpic/-fpie and 2 for
  -fPIC/-fPIE, matching gcc 13; any -fno- form leaves them undefined
  (Mach-O still defines __PIC__ 2, as clang does).
- -pie, -no-pie, -static-pie and -shared are link options again and no
  longer change the macros or the code.
- -fno-pic generates the position-dependent code -fno-pie always did
  (direct RIP-relative access on x86-64, no GOT for globals), which
  links into both -no-pie and -pie executables. A shared object still
  gets shared-object code whatever the family says.
- The link is a PIE as the last of -pie/-no-pie says, or else as the
  code was compiled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
-ftls-model=global-dynamic|local-dynamic|initial-exec|local-exec was
"unrecognized option, ignored". gcc treats it as a floor: each access
takes the stronger of that model and the one the code can prove. So an
executable keeps Local Exec for its own thread-locals whatever is
asked, initial-exec takes -fPIC code off the dynamic call, and
local-exec is Local Exec for every access, a thread-local defined
elsewhere included. The last one given wins; an unknown name is gcc's
"unknown TLS model" error with its list of valid arguments, and an
empty one is "missing argument to '-ftls-model='".

The codegen's shared_mode flag becomes a TlsPolicy (shared code plus
the floor), which Target::tls_access and the backends' Initial Exec
test both read, so ir::tls and the backends still agree on the model.
c17 has no Local Dynamic sequence; local-dynamic gets the descriptor
call, a valid Global Dynamic access, as gcc's aarch64 code does.
Mach-O keeps its single TLV access, as clang does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cc's -Woverflow

`char z = 300;` was emitted as `.byte 300`, and every narrow static
initializer -- file scope and block-scope static, scalar, member, array
element, bit-field, enumerated object -- carried the unconverted value
to the assembler, which truncated it with a warning of its own.

One function, convert_integer_constant, now converts an integer constant
to its target type (modulo 2^N, as gcc defines C17 6.3.1.3p3) for the
static initializer and gives gcc's -Woverflow when the conversion
changes the value in the cases gcc reports: "overflow in conversion",
"unsigned conversion" and "conversion from '...' to '...' changes value
from 'A' to 'B'", naming a bit-field as gcc does (`signed char:3`). The
same check runs on every implicit conversion in code: initialization,
assignment (to a bit-field too), `return` and a prototyped argument.
The floating saturation warning shares the wording and now names types
with gcc's spelling (`short int`, `long unsigned int`).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ake an overflowed array size variable

`int x = 2147483647 + 1;` compiled silently. gcc 13 says "integer
overflow in expression of type 'int' results in '-2147483648'"
[-Woverflow] at the operator, for `+`, `-`, `*`, `/` and `%` on signed
constants and "integer overflow in expression '-2147483648' of type
'int' results in '-2147483648'" for negating the minimum -- once, at the
operation that overflowed, and not in an operand that is never
evaluated: of sizeof, _Alignof or typeof, the controlling expression of
_Generic, or an arm a constant `?:`, `&&` or `||` condition discards.

The constant walk now notes a signed operation that overflows
(eval_noting_overflow), and the parser reports it where the operation is
built. An array size whose computation overflows is no integer constant
expression to gcc, so it is now a VLA here too: refused at file scope.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cc does

`int x __attribute__((mode(XF)));` was accepted and the declaration
silently became a `long double`. gcc 13 refuses every mismatch, and so
does c17 now, in gcc's words and order:

  unknown machine mode 'FOO'        no mode of the target (`XF` on aarch64)
  unable to emulate 'BLK'           condition-code, block, one-bit,
                                    fixed-point, over-wide integer modes
  invalid pointer mode 'SI'         a pointer at other than its width
  cannot use mode 'SF' for enumerated types
  mode 'SF' applied to inappropriate type
                                    a mode of another class: floating on
                                    an integer type, integer on a floating
                                    one, anything on _Bool or an aggregate

`mode` with no argument is "wrong number of arguments specified for
'mode' attribute", one that is not a name is ignored with gcc's warning,
and only a `__` on both sides is stripped, so `_SI_` names no mode. A
pointer declared at the pointer width (`int *p __attribute__((mode(DI)))`)
now stays a pointer instead of becoming a `long`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n as gcc does

`static struct S { int a; };`, `const struct S {...};`, `_Alignas(8)
struct S {...};`, `_Thread_local`/`__thread`, `extern`, `typedef`,
`volatile` and `_Atomic` on a tag declaration with no declarator were
silent: check_declares_something returned as soon as it saw a struct,
union or enum type, before looking at any specifier. `register` or
`auto` at file scope, `inline`, `_Noreturn` and `restrict` there were
accepted too.

The check now follows gcc 13: an error for `inline`, `_Noreturn`,
file-scope `auto`/`register` and `restrict` ("invalid use of
'restrict'"); for a reference to a tag already declared, the pedwarn
"empty declaration with storage class specifier|type qualifier|'_Alignas'
does not redeclare tag"; otherwise the warning "useless storage class
specifier", "useless '_Thread_local'" (or '__thread', as spelled),
"useless type qualifier" or "useless '_Alignas'" in empty declaration.
A typedef name for a struct type is a type name, not a tag specifier:
`static T;` is "useless type name in empty declaration", and an untagged
struct reached through a typedef no longer says "unnamed struct/union".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
318b2fe refuses XF and XC as unknown modes on aarch64, as gcc does, but
diagnostics_implemented_modes_are_silent compiles for the host and listed
them unconditionally, so it failed on an aarch64 host (linux-aarch64 CI).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Plain -Werror made every -f option c17 did not know an error, including
ones gcc accepts and real builds pass: -fstack-clash-protection (Debian
amd64 default flags), -fdiagnostics-color=always (meson's default),
-flto=auto and -ffat-lto-objects (Ubuntu's defaults).

cc/f_options.rs classifies every -f name c17 accepts, sourced from distro
build flags, cmake and meson defaults, the build files of several real
projects, c17's own tests and the torture suite's dg-options, each checked
against gcc 13:
- implemented: what the driver parses;
- accepted, silent: options whose effect c17 already has or that change
  nothing observable (optimisation passes, diagnostics presentation, LTO,
  CFI always emitted, frame pointer always kept, no TBAA, no signed-overflow
  folding, -fno-common), each with its reason;
- unsupported: gcc would do something observable that c17 does not
  (-fstack-protector*, -fexceptions, -ftrapv, -fpack-struct, -fopenmp,
  -fsanitize=*, ...): "c17: warning: '-fX' is not supported; ignored", in
  the group c17-unsupported-option, which -w and -Wno- silence and which
  plain -Werror does not promote (-Werror=c17-unsupported-option does);
- unknown: gcc's "unrecognized command-line option" error, as gcc gives it.
Bad values get gcc's wording.

-fstack-clash-protection is warned per function, at its line, only where
gcc would emit probes (a frame step of 4 KiB or more on x86-64, 63 KiB on
aarch64, or alloca/VLA); every other function already meets it.
-fstack-protector* used to be ignored silently though c17 emits no canary.
-fuse-ld= is passed to the link.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… revisions

-std=gnu99 or -std=c11 printed "'-std=X' ignored; c17 compiles C17 only",
which gcc does not, so a configure probe under AC_LANG_WERROR concluded the
flag failed. A C99 or C11 program is a C17 program, so those spellings
(c9x, c99, c1x, c11, the gnu* forms, iso9899:199x/1999/2011) now pass
without comment, like the C17 ones. C90 (c89, c90, gnu89, gnu90,
iso9899:1990/199409, -ansi) keeps the warning: C17 refuses its implicit int
and implicit declarations. A revision after C17 (c2x, c23, c2y, gnu2x,
gnu23, gnu2y, iso9899:2024) stays an error, so a probe for C23 support is
told no. StdRequest::{C17, Older} becomes {Compiled, Ignored}.

NONPOSIX.md said __STDC_VERSION__ was 201112L; it is 201710L.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jgarzik
jgarzik requested a balanced review from Copilot October 6, 2026 20:22
@jgarzik jgarzik self-assigned this Oct 6, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

The change set spans compiler overflow semantics, PIC/TLS codegen policy, and diagnostic behavior across the front end and backends, which warrants final human review despite the extensive passing tests.

Review effort: Balanced
Findings: None

What changed in this PR

This PR advances the cc crate (the in-tree C17 compiler) toward closer gcc‑13 behavioral parity across its front-end diagnostics and some codegen policy. It reworks preprocessor #if integer arithmetic to report "integer overflow in preprocessor expression" exactly where gcc's default pedwarn does (add/sub/mul/div, unary negation, and bit-losing signed left shifts) while giving out-of-range shifts gcc's actual values. It also refactors position-independence handling into a PositionIndependence/PicLevel enum, adds gcc-style type spellings for conversion diagnostics, and tightens the lexer's treatment of unterminated literals/comments and empty \x escapes (deferring the error to the literal decoder).

Changes:

  • Preprocessor #if now computes signed results exactly in a 128-bit carrier and flags intmax_t overflow via a new default pedwarn helper; shifts use a dedicated shift() matching gcc's values.
  • New TypeTable::gcc_type_name/gcc_bitfield_type_name produce gcc's full integer spellings (e.g. short unsigned int, signed char:3) for conversion warnings.
  • Documentation (NONPOSIX.md) updated for the -fpic family, -ftls-model=, -std=/C17 dialect, and -f* option classification; extensive new tests for directives, overflow, shifts, unterminated literals, and system-header diagnostics.
File Description
NONPOSIX.md Documents -fpic family, -std=/C17 dialect, and -Wc17-unsupported-option option classification; consistent with f_options.rs.
cc/​types.rs Adds gcc_type_name/gcc_bitfield_type_name for gcc-style integer/bit-field spellings in diagnostics.
cc/​token/​preprocess.rs Adds checked/shift/overflow helpers and rewires #if add/sub/mul/div/shift/neg to gcc-matching overflow pedwarns and shift values.
cc/​token/​test_preprocess.rs New tests for invalid directives, linemarkers, #if overflow, shift values, and system-header stream classification; PIC-macro test updated to the new enum.
cc/​token/​test_literal.rs New test that empty \x escapes error at decode time in strings/char/#if.
cc/​token/​test_lexer.rs Updated comments/tests: unterminated comment is an error at its open; empty \x left to the decoder; unterminated literals marked and spelled as written.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

…arwin option on macOS

Every c17 executable link on macOS led with -no-pie, and Apple's clang
answers it with "argument unused during compilation: '-no-pie'" -- a Mach-O
executable on arm64 is always position independent and the driver takes
neither -pie nor -no-pie. It had done so since link_mode_flag was added; the
first test to require an empty stderr from a macOS link
(linker_input_unknown_suffix_is_linked) caught it. link_mode_flag now
answers Option, None for any macOS executable; -shared is passed
everywhere, and a -static, -static-pie, -pie or -no-pie the user wrote still
reaches the driver among the other linker flags. Linux and FreeBSD links are
unchanged.

linker_options_are_untouched passed -Wl,-z,now, which Apple ld rejects (it
has no -z); on macOS it now uses -Wl,-dead_strip.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jgarzik
jgarzik merged commit 8d3b41f into main Oct 6, 2026
20 checks passed
@jgarzik
jgarzik deleted the updates branch October 6, 2026 21:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants