None of `gcc/patches/default.nix` was applied here. Take them, with the same
conditions the monolithic set uses:
- `no-sys-dirs`, with its RISC-V companion, keeping `/usr/local/include`,
`/lib` and `/usr/lib` off the search paths. GCC drops those itself for a
cross compiler with no sysroot, which is most of what this set builds, so
the gap only shows up natively -- but it does: a `gcc` built without this
reports `/lib/` and `/usr/lib/` in `-print-search-dirs`.
- `mangle-NIX_STORE-in-__FILE__`, keeping store hashes out of `__FILE__` and
so `-dev` outputs out of runtime closures. `cc-wrapper` sets
`useMacroPrefixMap = !isGNU` and this set's compiler is `isGNU`, so the
wrapper deliberately leaves this to the compiler and nothing was doing it.
- `ppc-musl`, unconditional there and here. `no-sys-dirs` does not subsume
it: `rs6000/sysv4.h` builds its own `INCLUDE_DEFAULTS` for musl and tests
`LOCAL_INCLUDE_DIR` before the `#undef` in `cppdefault.cc` is reached.
- `gfortran-driving`, on `langFortran`. Keeps `-l` and its argument together
in the `Driving:` line, which is what libtool parses out of `gfortran -v`.
- `c++tools-dont-check-enable-default-pie`, below 16. `--enable-default-pie`
is a target option but `c++tools` is built for the host and should follow
`--enable-host-pie`; we pass the former and do build `c++tools`. 16 dropped
the check upstream.
- the two Cygwin patches, on `isCygwin`. Cygwin's `abort` arrives through
`windows.h` and collides with the `tsystem.h` macro on the `inhibit_libc`
path, which this set's bootstrap goes through by design; and `unix` should
be defined with `builtin_define_std`.
Three go to `libgcc` rather than `gcc`: `cfi_startproc-reorder-label`, which
moves a directive in the aarch64 LSE helpers that clang 18 and later refuse
to assemble; and the two small Darwin ones, where `darwin-detection` is
paired by version -- 14's variant for 16, 15's for 15 -- so it goes through
`getVersionFile` rather than being picked once.
Iain Sandoe's branch is the one that could not simply be copied: it spans the
monorepo, so each package takes the files it builds, the way
`system-libbacktrace.patch` is already split here. `gcc` gets `gcc/`,
`fixincludes/` and the top-level `configure`; `libgcc` gets `libgcc/`;
`libsanitizer` gets its one `configure.tgt` case. That is 69 of the 72 files;
the rest are `libitm` and `libgcobol`, which this set does not package. From
Homebrew rather than GitHub's compare API, as the monolithic set does. The 16
diff is against 16.1.0 and applies to 16.2.0 unchanged.
Two of our own patches are held back on Darwin, because his branch already
carries the same change and they no longer apply on top of it:
- `cfi_startproc-reorder-label`, which the monolithic set also excludes.
`STARTFN` is replaced by an `ENTRY` macro that already writes the label
ahead of `.cfi_startproc`, so there is nothing left to reorder.
- `fix-collect2-paths`, which is not excluded there because the monolithic
set does not have it. His `collect2.cc` has `is_cross_compiler`, the extra
`post_ld_pass` argument and the unguarded `target_machine` already; all
ours would add is deleting the loop his version leaves behind.
Copied rather than shared with `../patches`, so that this set does not reach
into the monolithic one's directory.
Not taken: `libssp-noshared-musl32`, which assumes the libc ships
`libssp_nonshared.a` while this set builds its own `libssp`.
No Darwin builds tested yet, as I don't have one.
Assisted-by: Claude Code (Claude Opus 5)
None of `gcc/patches/default.nix` is applied in this set, and it was not
recorded anywhere whether that is deliberate. Go through all of them and say
so, with the reason, so that the next person does not have to derive it
again.
Four are real gaps rather than things this set does not need: `no-sys-dirs`
for native builds, `mangle-NIX_STORE-in-__FILE__` because `cc-wrapper` leaves
`__FILE__` to the compiler for GNU, the `cfi_startproc` reordering on aarch64
-- which belongs to `libgcc` here, not `gcc` -- and `ppc-musl`, which
`no-sys-dirs` does not subsume because `rs6000/sysv4.h` tests
`LOCAL_INCLUDE_DIR` before the `#undef` in `cppdefault.cc` is reached.
One is left open on purpose: `libssp-noshared-musl32` assumes the libc ships
`libssp_nonshared.a`, and this set builds its own `libssp`, which is worth
settling against a real musl x86_32 target rather than on paper.
No patch is added here; this is only the reasoning.
Assisted-by: Claude Code (Claude Opus 5)
`gomp_get_uid_for_device` allows ten digits for the `%d`, but `device_num`
is a plain `int`, whose widest rendering is `-2147483648` -- eleven. With
the eight-character `OMP_DEV_` prefix that is twenty bytes plus the NUL into
a nineteen-byte buffer. GCC 16 started noticing, and `libgomp` builds with
`-Werror`:
target.c:5956:31: error: 'snprintf' output may be truncated before the
last format character [-Werror=format-truncation=]
A real off-by-one rather than a false positive, so fix the size instead of
silencing the warning. My first guess was that fortify was to blame, since
the diagnostic named `__builtin___snprintf_chk`; it is not -- turning
hardening off only changes the name in the message.
Co-authored-by: Ben Siraphob <bensiraphob@gmail.com>
Assisted-by: Claude Code (Claude Opus 5)
Both are new in 16 and both come from `libatomic` no longer being buildable
on its own without help.
`configure.ac` now refuses to run with an empty `CFLAGS`: it appends
`-fno-link-libatomic` and needs `AC_PROG_CC`'s conftests to see it, so it
will not let `AC_PROG_CC` supply the default instead.
configure: error: CFLAGS must be set.
The top level passes them down in a monorepo build; here each library is
configured on its own, so pass what `AC_PROG_CC` would have chosen anyway.
`Makefile.am` then grew an `all-local` hook copying `libatomic.la` into
`../../gcc/`, so a compiler built in the same object tree can link
`-latomic`. There is no such tree here -- the compiler is a finished package
and gets the library through its wrapper -- so the rule just fails:
install: cannot create regular file '/build/build/../../gcc/libatomic.so.1.2.0'
Drop the hook rather than the rule, to keep the difference from upstream
small.
Neither is caught by checking that patches apply: both are build failures on
a tree that patched cleanly.
Co-authored-by: Ben Siraphob <bensiraphob@gmail.com>
Assisted-by: Claude Code (Claude Opus 5)
Same version and hash the monolithic set already packages, exposed as
`gccNGPackages_16`. The default stays at `default-gcc-version`, so nothing
switches over yet.
Every patch each component applies on 16 was checked by running `patch(1)`
over a pristine 16.2.0 tree, in list order, against the files `fetchpatch`
actually produces -- `git apply` is not a substitute, as it accepts context
that `patch` rejects.
Assisted-by: Claude Code (Claude Opus 5)
Of the patches that are not upstream, three need per-version handling on 16.
The version-constraint mechanism in `patches.nix` already picks the newest
matching entry, so everything else keeps resolving to `../15`.
`gcc/fix-collect2-paths.diff`: 16 spells one context line with the C++
`const_cast` operator rather than the `CONST_CAST2` macro, and excludes one
more linker -- `wild` -- from the target-prefixed names. That exclusion is
carried over into the loop the patch relocates, so the rebase preserves
upstream behaviour rather than quietly reverting it.
`libgfortran/force-regular-dirs.patch`: 16 grew a second coarray library,
`libcaf_shmem`, sharing the `cafexeclib` prefix this patch removes, so that
one has to move to `lib_LTLIBRARIES` as well.
`driver-search-PATH-ourselves.patch`: the `CONST_CAST` rewrite exists because
GCC 15 spells that cast with the macro while the posted patch, being against
trunk, uses the operator. 16 uses the operator too, so there the rewrite
turns a matching context line into a non-matching one. Take the patch as
posted above 16; the two differ in one line, so they need separate hashes.
Assisted-by: Claude Code (Claude Opus 5)
Most of the patches here are cherry-picks of upstream commits, and eight of
them are in the `releases/gcc-16` branch. Applying them to 16 would simply
fail, so gate them on the version and split each list in two: the block that
is only for GCC 15, then the block that applies to both.
In `libatomic` that accounts for the whole list. Its second patch is the
hand-edit of the generated `aclocal.m4` that `fetchpatch` cannot carry for
the first, and 16 already includes `../config/gthr.m4` there.
The remainder of the driver series is trunk-only, so it stays unconditional,
as do the ones not upstream anywhere yet.
Assisted-by: Claude Code (Claude Opus 5)
`libgfortran.spec`, which the driver reads out of the `libgfortran` directory
the wrapper already names, links `-lquadmath` unconditionally -- and does so
even though `libgfortran` is configured `--disable-libquadmath`, which
governs building the library rather than the reference to it.
Nothing put that library anywhere the driver would look, so linking any
Fortran failed. Not new in 16; 15 has the same spec.
Co-authored-by: Ben Siraphob <bensiraphob@gmail.com>
Assisted-by: Claude Code (Claude Opus 5)
`printf/gmp-impl.h` includes `longlong.h`, and the `Makefile` looks for it in
`../include`, but the source subset never copied that directory:
gmp-impl.h:71:10: fatal error: longlong.h: No such file or directory
`libiberty`, `libgomp` and `libstdcxx` all copy `include` already; do the
same. Not new in 16 -- 15 fails the same way, so nothing had built this.
Assisted-by: Claude Code (Claude Opus 5)
MinGW's libc offers only the `win32` threading model, so that is what `libgcc`
has been getting there. The monolithic `gcc` has long picked `mcf` instead,
via `threadsCross`; do the same here.
Rather than that attribute's `{ model, package }` pair, the threading library
declares its own model, with the same `passthru.threadModel` a libc uses.
`libgcc` gains a `threads` argument which takes precedence over the libc, and
which is `null` everywhere the libc's own threading is what we want.
`libstdcxx` needs no change at all: it already reads `libgcc.threadModel`.
The compilers from the threaded `libgcc` onwards carry the library, and have
to name its directories and pass `-lmcfgthread` themselves: a gcc *configured*
for a model links it through its own specs, and this set configures the
compiler independently of the runtimes. The comments at each site give the
details, including why the `nixSupport` fragment is conditional as a whole.
Nothing new is needed to stay out of the bootstrap cycle: the threading
library is built by `windows.crossThreadsStdenv`, which on these platforms is
the stage that then builds the threaded libgcc, and being plain C it needs no
threading model of its own.
Assisted-by: Claude Code (Claude Opus 5)
`libgomp` is OpenMP on top of pthreads, and building it anywhere else is
not a silent mistake: its own configure hard errors with
configure: error: Pthreads are required to build libgomp
which currently makes `stdenv.cc` unbuildable for those targets, since the
wrapper refers to `libgomp` for its `-B` and `-I` flags.
So gate it on the threading model rather than on the platform. Windows is
what prompted this, but the platform is the wrong question: Cygwin does
have pthreads, and a MinGW toolchain built against `winpthreads` would too.
Assisted-by: Claude Code (Claude Opus 5)
libstdc++'s rule for `gthr-default.h` names the thread header it wants
by its path relative to the tree root. For the POSIX model that is
`libgcc/gthr-posix.h`, which the trimmed source already carried, so this
went unnoticed; other models live under `libgcc/config/<cpu>/`, and the
build stops with
No rule to make target '.../libgcc/config/i386/gthr-win32.h'
Copy those in with their directory structure intact.
Assisted-by: Claude Code (Claude Opus 5)
`force-regular-dirs.patch` and `system-libbacktrace.patch` both rewrite
the same region of `libgfortran/Makefile.am`, and applied in that order
the second one fails:
patching file libgfortran/Makefile.am
Hunk #1 FAILED at 48.
`force-regular-dirs.patch` turns `toolexeclib_LTLIBRARIES` into
`lib_LTLIBRARIES`, and that line is *context* for the hunk that swaps
`../libbacktrace/libbacktrace.la` for `$(LIBBACKTRACE_LIB)` just below
it. So the hunk can never apply afterwards.
Apply `system-libbacktrace.patch` first and rebase ours onto the result,
rather than the other way around: that one is the version posted to
gcc-patches and is best kept as posted, while this one is ours to move.
The rebased patch makes exactly the same edits -- only context lines and
offsets change.
This was latent rather than new: nothing rebuilt `libgfortran` from
source until the `gcc` changes on this branch forced it to.
Assisted-by: Claude Code (Claude Opus 5)
Fallout from f39d68cbf9, which dropped
`--with-sysroot`. Two parts of configure scripts reach the branch we
want only when a sysroot is set or `host != target`, and a native build
has neither, so both went the other way. That commit was verified on
three cross targets, which is why this went unnoticed.
- `gcc`: `--disable-fixincludes`. No host platform headers are exposed to
gcc, whatever the crossness, so there is nothing for it to fix. Left on it
falls back to `/usr/include` and stops the build when that is missing.
- `libgcc`: `inhibit_libc` stated outright rather than left to be
inferred. `gcc/configure` derives it from `host != target` alone, so the
native pre-libc stage came out `false` and compiled every file against a
libc that is not there yet.
See the comments for further details.
Note that both flags are passed regardless of crossness (`host !=
target` for GCC, `build != host` for libgcc). This eases
maintainability.
The trickiest part of this is that while it fixed native and fixed the
other crosses, it broke Cygwin! The solution is thankfully a tiny patch
that is already upstream.
Assisted-by: Claude Code (Claude Opus 5)
TEMP
The monolithic `gcc` excludes Cygwin from its `enableShared`; do the same
here and explain why.
Also clean up that comment in general.
Assisted-by: Claude Code (Claude Opus 5)
Cygwin's libc is partly C++ -- so it needs both C++ headers and a
`libstdc++` to link against before it exists itself.
(The monolithic `gcc` says the same thing with `langCC =
stdenv.targetPlatform.isCygwin`, and its pre-libc compiler ships exactly
this: freestanding headers plus static `libstdc++.a` and `libsupc++.a`,
and no shared library. That is possible before a libc exists because a
static archive is compiled and `ar`'d, never linked.)
We add this new package to do the equivalent with the split GCC NG
package set, and it is used in the definition `gccWithLibgcc` in the
Cygwin case (the old one becomes `gccWithLibgccNoCxx`) to enable this
bootstrapping.
Assisted-by: Claude Code (Claude Opus 5)
This has to be done for all libcs that are used with GCC NG. See the
comments for further details.
Also update the comments on other libcs, which had gotten redundant.
Assisted-by: Claude Code (Claude Opus 5)
- `gccNGPackages.libbacktrace`: Remove, in favor of the `libbacktrace`
package in `pkgs/by-name`
- `stdenvNoCxx`: New, a compiler with a libc but no C++ standard library
- `libbacktrace`: Build with `stdenvNoCxx`, so `libstdc++` can link it
without a cycle
- `gccNGPackages.gcc`, `gccNGPackages.libgfortran`,
`gccNGPackages.libstdcxx`: Take `libbacktrace` as a build input and pass
`--with-system-libbacktrace`
That option is not upstream; it comes from a patch posted to gcc-patches[^1],
which we fetch from the archive rather than vendor.
One posting covers every component that links libbacktrace, so each
package filters it down to the files its own `src` carries, and leaves
out the generated files because we regenerate those locally anyway.
`gcc` and `libgfortran` were already using this `libbacktrace`, but by
faking up the sibling directory an in-tree build would have had --
archive, libtool archive and headers -- for the relative paths to resolve
against. Passing the flag lets that go.
Note that this means our libstdc++ is getting `std::stacktrace` support
for the first time; we were unwittingly excluding the vendored support.
Nothing failed, which is why it went unnoticed:
`--enable-libstdcxx-backtrace` defaults to `auto`, and `auto` means yes
wherever the library is hosted, so the feature was asked for. But the
probe that picks the object format runs `libbacktrace/filetype.awk`,
which was not among the files we copied, so it got nothing back, warned
"could not determine output file type", and quietly settled on
`enable_libstdcxx_backtrace=no`.
Note also that with this approach, a downstream `libbacktrace`
*cannot* be built by the regular `stdenv` with `useGccNG = true`,
because its symbols would conflict. We'll see whether that is acceptable
or not. If it isn't, we can still avoid vendoring, but we will need to
replicate the symbol renaming that GCC would have done externally.
[^1]: https://inbox.sourceware.org/gcc-patches/20260814013206.3818461-1-git@JohnEricson.me/
Assisted-by: Claude Code (Claude Opus 5)
The crt-building hack in `postPatch` does two things, both of which assume ELF:
- it forces `libgcc.a` to depend on `crti.o`/`crtn.o`. Those are an ELF
convention. PE/COFF targets have no such files, so the rule makes the
build assemble the generic ELF `config/i386/crti.S` with a PE
assembler, which fails with `junk at end of line`.
- it blanks `SHLIB_LC`, which for ELF is `-lc` and stands in the way of
linking `libgcc_s.so` before libc exists. On Cygwin and MinGW
`SHLIB_LC` is instead the list of system import libraries the DLL
genuinely needs, so blanking it drops real dependencies.
So apply it only on ELF.
That leaves the pre-libc stage on PE/COFF with no way to link a shared
`libgcc`: every symbol in a DLL has to resolve at link time, and the
libc's import library does not exist yet. Build only `libgcc.a` there --
which is all that stage is used for -- and let the shared library be
built as usual once the real libc is present. Which case applies is
read from `stdenv.cc.libc`, as everything else in this package is, with
headers-only stand-ins marking themselves via `passthru.headersOnly`.
ELF targets build the same thing as before but do not keep the same
derivation: `--enable-shared` is now stated outright where configure was
previously left to default to it. `libgcc` for `x86_64-unknown-netbsd`
gains that one flag and nothing else.
Assisted-by: Claude Code (Claude Opus 5)
We don't really want to share Nix logic with the monolithic GCC build,
as GCC NG is supposed to be unconstrained to go in different directions.
Inline `libgcc-buildstuff.nix` and read `hostPlatform` throughout.
Assisted-by: Claude Code (Claude Opus 5)
The chain is the part of this package set that cannot be read off any one
file: four compilers, each a step further along, and two `libgcc`s either
side of the libc. Until now it was only recoverable by reading the
`overrideCC` call sites in order and reconstructing the argument.
Write it down once, as a table, next to why the cycle needs breaking at
all -- a libc's sources call into libgcc, and a libgcc that can use the
libc's threads needs the libc. Also record the two things that are easy
to get wrong and hard to notice: that the pre-libc stage is written down
in exactly one place (`binutilsNoLibc` carrying `preLibcHeaders` as its
`libc`), and that a libgcc built with `inhibit_libc` still compiles,
links and installs, just with pieces silently missing.
The per-wrapper comments shrink to what is local to each -- which stage it
is and what keeps the libc out -- since the chain-wide narrative now has
somewhere better to live.
Assisted-by: Claude Code (Claude Opus 5)
`--enable-clocale=gnu` was passed for every target. That selects
`config/locale/gnu`, the glibc locale model, whose
`ctype_members.cc` reads `__ctype_b` -- a glibc symbol. On any other
libc the build fails on the type the table is not:
ctype_members.cc:50:51: error: cannot convert 'const short unsigned int*'
to 'const std::ctype_base::mask*' {aka 'const unsigned int*'} in assignment
libstdc++ works this out for itself from the host triple, as it does for
the OS layer next door -- `configure.host:262` maps `linux-musl*` to
`os/generic` -- and the monolithic build passes no `--enable-clocale` at
all, leaving that to configure. Do the same here.
Verified by building the full cross toolchain, `stdenv.cc`, for
`aarch64-unknown-linux-musl`, which builds libstdc++ and previously
failed on exactly this.
Assisted-by: Claude Code (Claude Opus 5)
Two things a monolithic build does implicitly that the split set had
nobody to do.
Linking `libstdc++.so` with `g++` fails, because that driver implies
`-lstdc++` and the library being linked is the one that would provide it:
ld: cannot find -lstdc++: No such file or directory
`libstdc++-v3/src/Makefile.am` names the remedy where it defines
`CXXLINK`: use `gcc` as the C++ compilation driver, which the top level
arranges via `RAW_CXX_FOR_TARGET` -- `xgcc`, not `xg++`, plus
`-shared-libgcc` and `-nostdinc++`. Configured standalone there is no top
level to arrange it, so arrange it here. The C driver still compiles
`.cc` as C++ by extension; what it drops is the implicit `-lstdc++`.
`-shared-libgcc` restores the linkage `g++` would have chosen, and
`-nostdinc++` keeps already-installed C++ headers out of the build whose
purpose is to produce them.
`src/Makefile` also computes `LTLDFLAGS` by shelling out to
`$(top_srcdir)/../libtool-ldflags`, reaching out of `libstdc++-v3` into
the GCC top level. Our trimmed source tree never copied it, so the shell
reported `No such file or directory`, `LTLDFLAGS` came out empty, and
`LDFLAGS` was quietly dropped from every library link. Same failure mode
as the `gthr-default.h` bug: a file that exists only because a monolithic
tree has it, missing without anything treating its absence as an error.
Assisted-by: Claude Code (Claude Opus 5)
Two halves of how the compiler finds libstdc++.
The library was not on the link path. `wrapCCWith` was given
`libcxx = libstdcxx`, which tells cc-wrapper where the C++ headers live
but does not put the library where a GNU compiler looks, so every C++
link failed with `cannot find -lstdc++`. Add `-B${libstdcxx}/lib`, as
libgcc, libssp, libatomic and libgomp already are.
The headers had the opposite problem: found too eagerly, by the wrong
language. libstdc++ deliberately ships headers named after C headers --
`math.h`, `stdlib.h`, `complex.h`, `stdckdint.h`, `stdbit.h`,
`stdatomic.h`, `tgmath.h`, `fenv.h` -- whose purpose is to shadow the C
ones when compiling C++. Installed into `$dev/include` they shadowed
unconditionally, since `libcxx` is a propagated target-target dep of
cc-wrapper and the setup hook puts that directory on the **C** include
path of everything built with the compiler. Under C, libstdc++'s
`<stdckdint.h>` is an empty shell, so its functions vanish:
bash: braces.c: implicit declaration of function 'ckd_sub'
coreutils: randperm.c: implicit declaration of function 'stdc_bit_width'
Both build fine with monolithic GCC, whose libstdc++ headers live in
`include/c++/$ver` and are added by the g++ driver alone. Install them
into a sibling `include-cxx`, which keeps them off C's include path while
staying findable for C++; no version or target subdirectory is needed,
since the store path already separates one libstdc++ from another.
Assisted-by: Claude Code (Claude Opus 5)
One question in three places: which part of the split set carries the
target libc, and what happens where there is none yet.
The compiler was configured with
`--with-sysroot=${lib.getDev targetPackages.libc}`, so every libc change
moved `gcc-unwrapped` -- the most expensive thing here to rebuild, and
exactly the coupling this split exists to remove. cc-wrapper already
supplies the target libc, the same way the LLVM set leaves `clang`
carrying none. Measured with a behaviour-free libc change: before,
`gcc-unwrapped` moves, hours; after, `gcc-unwrapped` and `libgcc` are
byte-identical and only `libstdcxx` and the wrapper move, minutes.
libgcc is where the headers are wanted, and it wants them twice. First,
`gcc/configure` sets `inhibit_libc=true` when host != target and
`$target_header_dir/stdio.h` is absent, which makes `tsystem.h` skip
`<unistd.h>` and friends. That is fine for the generic sources and fatal
for the target-specific ones that need libc declarations, which fail as
`implicit declaration of function 'profil'` and the like.
`target_header_dir` comes from `--with-sysroot`, not `--with-headers`, so
point the sysroot pair at whichever libc the compiler carries -- read
off `stdenv.cc.libc`, which in the pre-libc build is `preLibcHeaders`,
the header-only package nixpkgs already keeps for this stage (NetBSD,
MinGW, Cygwin), and nothing at all elsewhere. Being headers-only, a libc
change still leaves the pre-libc libgcc byte-identical.
Reading it off the compiler rather than naming `preLibcHeaders` also
fixes glibc and musl, which matched no branch of `preLibcHeaders`, got no
sysroot at all, and so built their real libgcc with `inhibit_libc` set.
That is silent -- it compiles, links and installs cleanly, and
`_eprintf.o` simply comes out empty:
aarch64-unknown-linux-gnu libgcc.a
before 413014 bytes, `__eprintf` absent
after 415276 bytes, `__eprintf` present
The real libc is already an input to that build through `stdenv.cc`, so
naming it here adds no coupling. Dropping the flag instead was the other
option; NetBSD shows the cost, where all 334 libgcc compiles gain
`-Dinhibit_libc` and the same symbol disappears there too.
Second, `limits.h`. gcc's chains to the target libc's with
`#include_next`, and that is the right header to ship: it names no libc,
so it stays correct for whatever is on the include path later. Where the
compiler has a libc -- headers-only or real -- the chain resolves, and
must: those same sources want `PATH_MAX` from it. Where it does not, the
chain has nowhere
to land and even configure's `AC_PROG_CPP` probe fails, since it includes
`<limits.h>` precisely because that "exists even on freestanding
compilers", after which it falls back to `/lib/cpp` and reports that:
checking how to run the C preprocessor... /lib/cpp
configure: error: C preprocessor "/lib/cpp" fails sanity check
Only then, put gcc's own `glimits.h` earlier on the include path for this
build. That is the self-contained variant, the same file gcc installs
when configured against no libc. It is build-local: the compiler keeps
shipping the chained header.
Fixing the compiler instead is wrong, and glibc catches it at once --
`bits/stdlib.h:98: #error "Assumed value of MB_LEN_MAX wrong"` while
building libgomp. `limits.h` is the only header libgcc wanted on glibc
and musl, which is why this is one build-local include path rather than a
header package per libc.
Verified by building `stdenv.cc` for `aarch64-unknown-linux-gnu` and
`aarch64-unknown-linux-musl`, neither of which worked before;
`x86_64-unknown-netbsd`, whose bootstrap compiler carries
`preLibcHeaders`, uses those headers and carries no freestanding include
path.
Assisted-by: Claude Code (Claude Opus 5)
The libc bootstrap had every stage except the last. `gccNoLibgcc` builds
libgcc, `gccWithLibgcc` builds the libc with it — and then nothing goes
back to rebuild libgcc now that the libc exists. So the only libgcc
anyone ever got was the one from before there was a libc, which is
necessarily single-threaded: `gthr-posix.h` includes `<pthread.h>`
unconditionally, and at that point there is no libc to provide it.
`libstdcxx` above it assumed `posix` regardless, so the two disagreed by
construction — the unwinder's registry locking compiled away to nothing
underneath a `libstdc++` handing out `std::thread`.
Add the last stage the way the LLVM package set does with
`compiler-rt-no-libc` and `compiler-rt-libc`: instantiate the same
package twice. `libgcc-no-libc` is the existing one and now feeds only
the three bootstrap compilers. `libgcc-libc` is built after the libc, by
a new `gccWithLibcAndBasicLibgcc` — real libc, bootstrap libgcc — and is
what `libgcc` resolves to for any platform that has a libc.
Nothing is passed to the package to say which of the two it is. The
distinction is the compiler it is handed, exactly as in the LLVM set:
`gccNoLibgcc` is wrapped with `binutilsNoLibc`, whose `libc` is
`preLibcHeaders` -- the header-only stand-in, or nothing at all on
platforms without one -- and `wrapCCWith` defaults a wrapper's `libc` to
its bintools'. So the package reads `stdenv.cc.libc` and needs no
bootstrap flag of its own.
Which threading model is available is a property of the libc, so the libc
declares it as `passthru.threadModel` and `libgcc` reads it from there.
That is what makes the pre-libc build single-threaded without being told
to be: a headers-only package declares no `threadModel`, a real libc
does.
`libstdcxx` in turn takes both the model and the generated
`gthr-default.h` from `libgcc`, replacing a `$CXX -v` probe that asked
the compiler — which in this package set is configured separately from
libgcc and so answers for the wrong component. Platforms that declare
nothing keep the single-threaded model they already had.
The second libgcc is also the first one that can be shared. Previously
only the static library was built, and the compiler was configured
`--disable-shared` for every target, which is compiled into the driver's
specs, so it named bare `-lgcc` and never `-lgcc_s`:
$ x86_64-unknown-netbsd-g++ -### eh.cc
collect2 ... -lstdc++ -lm -lgcc -lc -lgcc
Both halves have to move together. Build the shared library without
changing the specs and the unwinder leaves `libgcc.a` while the specs
still name it:
libgcc.a _Unwind_RaiseException: absent
libgcc_eh.a _Unwind_RaiseException: present
libgcc_s.so.1 _Unwind_RaiseException: present
so every throwing C++ program fails to link and `rustc` fails on
`-lgcc_s` outright. Either standard arm would do, `-lgcc_s -lgcc` shared
or `-lgcc -lgcc_eh` static; the halves disagreeing is what breaks.
`--disable-shared` was passed to libgcc with the comment "Do not have
dynamic linker without libc". That does not hold: the monolithic build
ships `libgcc_s` even from its *nolibc* cross stage, and the result needs
nothing at run time --
$ readelf -d .../nolibc-gcc-15.3.0-libgcc/.../libgcc_s.so.1
0x...0e (SONAME) Library soname: [libgcc_s.so.1]
(no NEEDED entries at all)
What genuinely blocks it is libgcc's own makefile: `SHLIB_LC` defaults to
`-lc`, so the `libgcc_s.so` rule links against a libc that does not exist
yet and fails with `cannot find -lc`. The monolithic build clobbers that
variable -- see `common/libgcc-buildstuff.nix` -- so reuse that helper
rather than reinventing it. On the compiler side the flag derives from
`hasSharedLibraries`, as the monolithic build does, so a target genuinely
without shared libraries keeps the old behaviour; it is spelled
`enableTargetShared` there because it describes the target's libgcc
rather than anything about the compiler being built.
Verified on `x86_64-unknown-netbsd`, where `libgcc_s.so.1` and the
`GROUP ( libgcc_s.so.1 -lgcc )` script now appear in the output, and by
building `stdenv.cc` for `aarch64-unknown-linux-gnu` and
`aarch64-unknown-linux-musl`.
Assisted-by: Claude Code (Claude Opus 5)
The package set offered two compilers either side of a gap:
- `gccNoLibgcc` — no libgcc, no libc. Builds libgcc.
- `gccWithLibc` — libgcc *and* libc. Builds libssp and everything above.
Nothing in between, and the missing stage is the one a libc is compiled
with. A libc's own sources call into libgcc — 128-bit and soft-float
helpers, `__stack_chk`-adjacent bits, unwinder support — so
`gccNoLibgcc` cannot compile it. `gccWithLibc` obviously cannot either:
it is defined in terms of the libc that does not exist yet. Any platform
bringing up a new libc therefore has no compiler to express the step
with, and the bootstrap simply cannot be written down.
Add `gccWithLibgcc`: libgcc present, libc absent. `binutilsNoLibc` is
what enforces the second half — it supplies the header-only
`preLibcHeaders` rather than a real libc, so the derivation refers to no
libc at all and the dependency cycle stays broken. Verified on a cross
target: the wrapper's `orig-libc` is the headers package, and a
translation unit needing `__udivti3` compiles and links against libgcc.
This is additive; no existing derivation changes.
Assisted-by: Claude Code (Claude Opus 5)
A release tarball carries generated files that a git tree does not, and
two of those bit us.
`MD5SUMS` is generated when a tarball is rolled. Each sub-source
extractor asserted `[[ -f MD5SUMS ]]` before copying it, so every one of
them failed outright the moment `monorepoSrc` was a git tree:
unpacking source archive /nix/store/...-source
source root is source
<exit 1>
Copy it only when it is there.
The generated sources are the other half: a checkout lacks
`gengtype-lex.cc` and friends, which a tarball ships pre-built, so they
have to be regenerated. Take flex and bison as native inputs when
`fromVCS` says the source is a checkout.
Tarball builds are unaffected either way.
Assisted-by: Claude Code (Claude Opus 5)
GCC decides at configure time what its assembler and linker can do and
writes the answers into the compiler. A probe it cannot run is not an
error -- it silently records "no". We passed `--with-as` but put nothing
on `PATH`, so the probes had no tools to ask, and **31 capability macros**
came out wrong in the installed `auto-host.h`: `HAVE_GAS_HIDDEN`,
`HAVE_LD_PIE`, `HAVE_LD_RELRO_SUPPORT`, `HAVE_LD_NOW_SUPPORT`,
`HAVE_LTO_PLUGIN`, `HAVE_COMDAT_GROUP`, `HAVE_GAS_CFI_DIRECTIVE`,
`HAVE_AS_LEB128`, `HAVE_LD_EH_FRAME_HDR` and more. nixpkgs' hardening
flags were quietly inert, LTO unavailable, C++ without COMDAT.
`HAVE_GAS_HIDDEN` is how it surfaced: without it `-fvisibility=hidden`
is not rejected but becomes a no-op that merely warns, so every symbol
stays preemptible while GCC still emits direct `R_X86_64_PC32`
references, which the linker then rejects:
libc_pic.a(stats.pico): relocation R_X86_64_PC32 against symbol
`opt_stats_interval_opts' can not be used when making a shared object
Found on NetBSD, where it stops `libc.so` linking at all. Targets
matching configure's hardcoded `*-*-solaris2*` case escape it, which is
what made it look target-specific.
Put the unwrapped bintools on `PATH` via `depsBuildTarget` -- they run on
the build machine and act on target artifacts -- so the probes ask the
real tools. Unwrapped keeps the earlier decoupling: `as` and `ld` proper
carry no target-libc reference, unlike the bintools wrapper.
Drop `--with-as` rather than adding `--with-ld`. Those bake
`DEFAULT_ASSEMBLER`/`DEFAULT_LINKER` as absolute store paths, so the
driver runs exactly those binaries instead of deferring to the wrapped
ones. Finding them at use time is what the three `find_a_program`
patches are for: `PATH` was left to `execvp`, which matches `NAME` alone,
and `PATH` is exactly where we expose a cross toolchain under prefixed
names. They come from the posting to `gcc-patches`; the second needs a
`postFetch` tweak, being against trunk where one cast is spelled with the
C++ operator rather than `CONST_CAST`.
All three configurations were built and compared:
- `--with-as` only (before): 31 macros wrong, `libc.so` unlinkable
- `--with-as` + `--with-ld`: capabilities recovered, but ten probes
still fail, because probes need the rest of binutils on `PATH`, not
one absolute path
- `PATH` only (this): all 31 recovered, `DEFAULT_ASSEMBLER` and
`DEFAULT_LINKER` undefined, `-print-prog-name=as`/`ld` return bare
names, so the wrapper stays in charge
Assisted-by: Claude Code (Claude Opus 5)
`libgcc` runs `gcc/configure` itself, to generate the makefile fragments
the split build has nobody else to produce. That configure identifies the
target assembler and linker from `AS_FOR_TARGET`/`LD_FOR_TARGET` and
probes them for capabilities.
`preConfigure` set those *after* assigning `AS=$AS_FOR_BUILD` and
`LD=$LD_FOR_BUILD`, deriving them with `$(basename $AS)`. The build
assignments stomp on the variables the target ones are read from, so it
took the basename of the *build* tools -- plain `as` and `ld` -- and
looked for them inside the *target* wrappers, which install
machine-prefixed names only. Neither path existed. Nothing about that is
target-specific; it catches every cross target.
`gcc/configure` does not treat it as an error: a probe it cannot run
records "no", so every `gcc_cv_as_*`/`gcc_cv_ld_*` capability came back
"no". The silently fatal one is `HAVE_LD_EH_FRAME_HDR`, which
`unwind-dw2-fde-dip.c` gates `USE_PT_GNU_EH_FRAME` on, leaving the
unwinder with only the `__register_frame` registry, which nothing
populates for normally linked objects. libgcc and libstdc++ still build,
link and install cleanly, and every C++ `throw` finds no FDE and calls
`std::terminate`.
Snapshot the target tool names before the `*_FOR_BUILD` assignments
clobber them. `CPP` is not always exported, so fall back to the
machine-prefixed name the wrappers install.
Assisted-by: Claude Code (Claude Opus 5)