By default a DLL exports every global symbol, minus those from the
standard libraries, startup objects, and (here) any archive under the
sysroot, which ld filters by the symbol's archive of origin, and minus
weak symbols, which is what COMDAT template instantiations are. LTO
defeated all of that: a DLL using std::string exported 704 symbols
instead of 2, with every one of them pinned against dead-code
elimination.
The exports came from the placeholder bfds that stand in for claimed
IR objects. They stay in the input list after LTO, still listing every
definition the plugin reported as a strong global, and they have no
archive to be excluded by. Skip them in the export loop; the definitions
that survive LTO are in the output objects the plugin added, and only
those count.
That alone would still export what the compiler keeps global, since ld
tells the plugin that in a DLL link every symbol may be visible from
outside. Add an emulation hook consulted during symbol resolution, which
the PE emulations answer with the same auto-export decision, using the
IR object's original archive member and treating weak or COMDAT
definitions as unexportable. A library symbol is then reported as not
visible, so the compiler localizes and prunes it as it would in an
executable, and its export name is remembered so that auto-export also
rejects the ones that survive as globals because regular objects
reference them. Symbols listed in a .def file remain exported, and
dllexport attributes are unaffected since the compiler honors them
regardless of resolution.
Verified with a native x64 build of ld on both the x64 and -m32
multilib: export-all LTO DLLs export exactly what non-LTO ones do and
come out half the size, dllexport and .def exports are kept, the DLLs
work when called, and executables are unchanged.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
LTO bytecode packs a REAL_VALUE_TYPE's significand as SIGSZ words of
HOST_BITS_PER_LONG bits, which is the host's own layout: three 64-bit
words on the Linux build host, five 32-bit words on Windows. The
runtime libraries' bytecode is produced by the Linux-hosted cross
compiler and consumed by the Windows-hosted compiler, so every
floating-point constant in it arrived garbled: FLT_MAX read as zero,
libgfortran printed every real as Infinity, and std::from_chars
returned 0 for any floating input. Integers are unaffected because
they stream as HOST_WIDE_INT, which is 64 bits on both hosts.
Stream the significand's top 128 bits as four 32-bit words instead,
most significant first, which covers every target format (binary128
needs 113), and the 16-byte decimal128 encoding in memory order for
decimal values. Both hosts have at least 128 significand bits. The
chunk arithmetic was checked to round-trip between the two layouts.
This changes the bytecode format relative to upstream, so objects
compiled by another GCC 16.2 build would misread constants here; the
toolchain is self-contained and the version check never covered host
differences anyway. Applied before both compilers are built.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The linker deduplicates COMDAT sections by key. For a real COFF
section the key is its COMDAT symbol name, which on i386 PE carries the
user label prefix (__ZTISt9bad_alloc). For a symbol from an LTO IR
object, ld creates a placeholder section named after the COMDAT group
the compiler reported, which is the group name without any prefix
(_ZTISt9bad_alloc). The two never match, so whenever a symbol is
defined both by a regular object and by LTO bytecode, ld reports a
multiple definition instead of keeping one copy. That is every -flto
link on i686 once libstdc++ carries bytecode, since libsupc++ and the
CRT are regular objects that define the same typeinfo and template
instantiations, and it also breaks linking a plain object into an -flto
program. x86_64 PE has no prefix and is unaffected.
Strip the target's symbol leading char from keys derived from real
objects so that they match the compiler's group names. Verified with
native x86_64 and i686 builds of ld against both kits: -flto links,
mixed plain and -flto links, and ordinary links all behave, and x86_64
is unchanged.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Parallel LTRANS (-flto=N, -flto=auto, or a detected jobserver) hands
the partitions to make through a generated Makefile, and lto-wrapper
finds make only through $MAKE or PATH. When the toolchain is invoked by
path without its directory on PATH, which the self-locating driver
otherwise supports, that search fails and lto-wrapper silently falls
back to serial compilation, with a warning suggesting -flto=auto that
the user may already be passing.
After $MAKE and PATH, also try make in the driver's own directory,
known from COLLECT_GCC, and run that one. Together with make locating
its shell beside itself, an -flto=auto link now works in parallel with
nothing on PATH; verified by pointing $MAKE at the kit's make from an
empty PATH, which exercises the same invocation.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
On Windows, make finds its shell by searching PATH for sh.exe and falls
back to cmd.exe, whose quoting rules break ordinary recipes. When make
is invoked by path without w64devkit's bin/ on PATH, that search fails
even though sh.exe sits right next to make.exe, and it does not help to
set SHELL in the environment, which make ignores. Check make's own
directory first, so that make works on its own like the rest of the
toolchain. This is what GCC's lto-wrapper needs for parallel LTO in a
PATH-free invocation.
Verified with a native build: invoked with an empty PATH, the patched
make runs a recipe through the adjacent sh.exe, and falls back to
cmd.exe as before when sh.exe is removed.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Two parts of libstdc++ cannot safely be link-time optimized, even
though the rest of the library is built as fat LTO objects.
libsupc++ is the C++ runtime proper. Compiler-generated code refers to
some of its entry points in ways the LTO symbol resolution never sees:
a must-not-throw landing pad calls __cxa_call_terminate, but that call
is only materialized late, in pass_lower_resx, after whole-program
analysis has already dropped the apparently unreferenced definition
from eh_call.o. An -flto link of a program using <thread> or <format>
then fails with "defined in discarded section". Compile all of
libsupc++ without LTO, as libgcc is.
ios_init.cc defines cout, cin, and the other standard streams with
deliberately unrelated types (character arrays sized and aligned like
the stream classes) to control their construction. That is a textbook
ODR violation that the compiler can only see across translation units
with LTO, and -Wodr then reports every one of them on every -flto link
that uses iostreams. Keep that one file plain as well.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
With full plugin support in the linker, the driver passes the LTO
plugin on every link, and the plugin claims any input containing LTO
bytecode. Since libstdc++, libgfortran, and libquadmath are now fat
LTO objects, that recompiled the library members of every C++ program
whether or not the user asked for LTO: links took 4x longer for tiny
programs and 30-45x for typical ones, lto-wrapper nagged about serial
compilation, and every LTO edge case reached every user.
Only pass the plugin when -flto, -flto=N, or -fuse-linker-plugin is on
the link line. Ordinary links use the libraries' regular code exactly
as before, and -flto links get the full benefit, including dead-code
elimination inside the libraries. The one behavior change is that a
slim LTO object linked without -flto now fails with "plugin needed to
handle lto object", which is what the GCC manual documents anyway.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The driver copies argv[0] verbatim into COLLECT_GCC, which lto-wrapper
uses as the compiler for the LTRANS stage. For parallel LTO (-flto=N,
-flto=auto, or a detected jobserver) lto-wrapper writes that path
unquoted into a generated Makefile and runs make, whose shell then
consumes the backslashes of a Windows path, leaving "C:Users...g++.exe"
and a failed link. Since the driver now always replaces argv[0] with
the GetModuleFileNameA result, every -flto=N link was affected. Convert
the path to forward slashes, which GCC accepts everywhere.
The i686-dumpmachine patch is refreshed for the shifted hunk.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Disable the dual ABI, and patch libstdc++ to keep the new SSO string
rather than the old gcc4-compatible COW string that configure imposes
on single-ABI builds. The dual ABI exists for link compatibility with
binaries built against GCC 4-era libstdc++, which a from-scratch
static-only toolchain does not need, and it costs every program dead
weight at archive-member granularity: both ABIs' locale facets and
string instantiations, plus the facet shims to bridge them.
The library nearly supports this configuration already. Beyond the
configure default, the patch defines the new-ABI ios_base::failure in
a C++11 translation unit, and extends the instantiate-once guards on
ABI-agnostic locale instantiations to cover single-ABI builds.
The Transactional Memory TS clones in cow-stdexcept.cc are written
against COW string internals (_Rep, _M_dataplus) and so can only be
compiled when std::basic_string is the COW string. This is invisible
on x86_64, where _GLIBCXX_USE_WEAK_REF is false and the section is
skipped entirely, but breaks the i686 build, where it is true. Compile
the clones only when the string ABI matches. Their absence is legal by
design: they are weakly referenced, and -fgnu-tm code falls back to
serial-irrevocable execution. (-fgnu-tm with libstdc++ headers is
broken in GCC 16.2 regardless, in both dual-ABI and single-ABI builds:
new_allocator.h trips over __builtin_operator_new selection.)
Now that libstdc++ is built as fat LTO objects, this also removes most
of the library's deliberate ODR violations from what an -flto link can
see: the punned __cow_string and __sso_string redefinitions and the
old-ABI compile of the standard exception classes no longer exist, so
-Wodr stops reporting them on every link.
A trivial C++ program shrinks from 104K to 73K, iostream hello world
from 1010K to 656K, and libstdc++.a from 8.1M to 6.0M. Exceptions,
iostreams with error codes, locales, and std::filesystem verified
working.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
When a function's mangled name changed in some ABI version, GCC also
emits its old name as a weak alias so that objects from older compilers
still link. By default it does so for changes since ABI 18 (GCC 13),
and -Wabi=N quietly implies -fabi-compat-version=N, which libstdc++
passes (-Wabi=19) and so ends up with a different set of aliases than
user code. That asymmetry breaks LTO on PE: the aliases share a COMDAT
group with their target, ld cannot reconcile a group with two members
in one IR object and one in another, and any C++20 constrained
function explicitly instantiated in libstdc++ and implicitly in an
-flto user object fails with a multiple definition error.
This toolchain is static and self-contained, so there are no older
objects to stay compatible with. Emit no compatibility aliases unless
-fabi-compat-version is given explicitly, and leave -Wabi= as a pure
diagnostic. This goes in the bootstrap queue because the cross
compiler is what compiles libstdc++.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Build GCC with LTO support and compile the three runtime libraries
that benefit most as fat LTO objects. They link as before without
-flto, while an -flto link can inline into them and, more importantly,
drop their unused functions, which --gc-sections cannot do on x86_64
PE because .pdata roots every function. libstdc++'s largest members
(locale, format, and stream instantiations) are only partly used by
any one program, so this is where the size win concentrates.
libstdc++ is the only C++ target library, so CXXFLAGS_FOR_TARGET
reaches it alone. libgfortran and libquadmath share CFLAGS_FOR_TARGET
with libgcc, libgomp, and libatomic, which stay plain (libgcc feeds
the -r tricks in this repository; the others synchronize threads and
must not have their translation-unit boundaries dissolved), so those
two are rebuilt after the main build with their own flags. The LTO
plugin is also installed in lib/bfd-plugins so that plain ar, nm, and
ranlib handle slim LTO objects without gcc-ar.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Since Binutils 2.41 (PR30343) the PE linker creates an early undefined
reference to _pei386_runtime_relocator whenever the LTO plugin is
loaded, which an LTO-enabled GCC does on every link. With -r that
reference leaks into the output as a stray undefined symbol, breaking
later -nostdlib links of the result. Skip it for relocatable output,
where the final link makes its own reference.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Without it GNU patch reads the final context line as incomplete, warns
"patch unexpectedly ends in middle of line", drops that line, and
reports fuzz 1 on the hunk. The hunk now applies with a plain offset.
This is the residual issue after 9d32fc5 fixed the file's line endings;
the leftover fuzz was the newline, not CRLF.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
"crossgcc" names a property both GCC builds share: /x-gcc is a plain
cross build and /gcc is a Canadian cross, so "cross" does not tell them
apart, and it points at the bootstrap compiler, which is the one queue
it does not apply to.
What separates the queues is reach. Both push onto the same tree, which
is never popped: src/gcc goes on before the bootstrap compiler and is
still applied when the shipped compiler configures, so it reaches both.
The second queue goes on top afterward and reaches only the compiler
that ships. Name it for that.
As a bonus the directories now sort in push order; crossgcc sorted
before gcc while being applied after it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Per the upstream release notes, the only documented change is that Swift
policy CMP0215's NEW behavior now also requires CMP0157 and CMP0195 to
be set to NEW. force-ninja.patch still applies.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Move each component's patches from flat, prefixed file names into a
per-component directory with a quilt series file, dropping the number
prefixes: application order is now stated by series rather than
enforced by lexicographic glob expansion. Outside the build, a queue
is managed by our own quilt against an unpacked tarball from source/
with QUILT_PATCHES=/path/to/src/<component>. The shipped kit carries
the same layout under $W64DEVKIT_HOME/src, consumable by the bundled
quilt.exe the same way.
Inside the build, apply queues with Debian's original quilt rather
than a native build of src/quilt.cpp, whose amalgamation is
Windows-only. This keeps battle-tested GNU patch as the engine, and
makes every image build a conformance check: series semantics mean
whatever the reference implementation says, and a queue our clone
manages must be accepted by original quilt. The .pc state is removed
after each push because source trees are never popped, and a stale
.pc from one queue blocks pushing a second queue onto the same tree,
which happens twice: gcc then crossgcc, mingw then gendef.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
NSIS unconditionally builds ANSI stubs and plugin variants alongside
Unicode whenever the target CPU is x86, with no option to disable
them. ANSI installers are legacy compatibility w64devkit does not
need: makensis defaults to Unicode everywhere, and "Target x86-ansi"
fails cleanly with "adequate stub not found". This keeps the x86 kit
and the multilib kit's secondary x86 set Unicode-only, matching the
shape of the amd64 set.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
makensis is retargetable: the architecture of the installers it emits
is decided by which precompiled stub it attaches, stubs and plugins
are stored under target-suffixed names, and a script selects one with
"Target x86-unicode" (or CPU x86). Its default remains amd64, chosen
by the bitness of makensis itself. Add a second stub-and-plugin pass
so the multilib kit can emit 32-bit installers, which remain the
traditional choice even for 64-bit payloads since they run on every
Windows and can fail politely on 32-bit hosts.
The NSIS build derives each tool from XGCC_W32_PREFIX and never adds
-m32 on its own, so present the multilib compiler through an i686
interface: wrapper scripts that forward with -m32, --32, and
--target=pe-i386, mirroring the aliases the kit itself ships. The
x86-ansi extras come along unconditionally, matching the standalone
x86 kit's layout.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CMAKE_C_FLAGS and CMAKE_CXX_FLAGS are real CMake variable names, and
profile values sit in the environment of every RUN, including the other
cmake invocations that do not pass these flags explicitly. CMake does
not read them from the environment today, but it honors a growing list
of CMAKE_* environment variables, so real names are a latent hazard.
Rename them and document the naming discipline alongside the profiles,
which also covers why CRT_LIB32/CRT_LIB64 avoid their obvious names.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Use the selected VARIANT directly for multilib-only control flow rather
than maintaining a separate EXTRA_32 boolean. Redeclare the global build
argument in the selected cross stage so each conditional can state the
actual profile requirement explicitly.
Represent GCC multilib and mingw-w64 CRT selections as enable or disable
state instead of storing complete configure options. Form the GCC and CRT
arguments at their call sites, which keeps punctuation out of the profile
data and makes the three configurations easier to compare.
Remove the standalone x86 WINNT_FLAGS override. The
mingw-default-win32-winnt patch already substitutes Windows XP for every
__i386__ compilation, so passing the same header default through configure
was redundant. Keep the separate Windows 7 CMake flags because CMake and
its bundled curl cannot build against the XP target.
Rename the remaining values that intentionally contain complete command
arguments to GCC_ARCH_FLAG, GCC_MANIFEST_FLAG, and ZSTD_THREAD_FLAG. The
component scope and representation are now visible in each name.
Verify the resulting x64, x86, and multilib Dockerfiles with build checks
and complete cross-toolchain builds. Confirm that only the multilib profile
executes the secondary 32-bit runtime and alias blocks and that each GCC and
CRT configure command receives the expected enable or disable options.
Treat the multilib profile as a first-class CI and release artifact
alongside the standalone x64 and x86 profiles.
Add a build-multilib job that selects VARIANT=multilib, uses an
independent GitHub Actions cache scope, extracts the generated
self-extracting archive, and uploads it with a multilib-specific name.
Run it after the x64 job so the workflow retains the existing ordering
while allowing the x86 and multilib builds to proceed independently.
Make tagged releases wait for the multilib build as well. Build the
signed target with VARIANT=multilib, pass the same trusted-signing
credentials used by the other profiles, and pack the result as
w64devkit-multilib-<version>.7z.exe. The existing release glob then
publishes the signed multilib archive with the x64, x86, and source
artifacts.
Replace the loosely coordinated collection of x86 build arguments with
a single VARIANT selector. Define x64, x86, and multilib configuration
stages, then select the requested stage as the common cross-compiler
base. This keeps each supported configuration closed and prevents
callers from combining incompatible architecture settings.
Make x64 the default standalone profile. It targets
x86_64-w64-mingw32, disables GCC multilib support, builds only the
64-bit CRT, and omits the secondary 32-bit libraries and aliases.
Keep the standalone x86 behavior in its own profile. It targets
i686-w64-mingw32, retains the Pentium 4 default, enables only the
32-bit CRT, preserves the Windows XP header default and manifest
behavior, selects the 32-bit BusyBox and NSIS configurations, disables
zstd threading, and raises _WIN32_WINNT only while building CMake.
CMake and its bundled curl require at least Windows Vista APIs, so the
C and C++ flags continue to target Windows 7 without changing the
XP-oriented default of the installed toolchain.
Define multilib as an x86_64 compiler with GCC multilib enabled, both
CRT library sets installed, and the existing i686 forwarding aliases.
Use a dedicated EXTRA_32 setting for the secondary lib32 libraries,
winpthreads builds, thread support object, and aliases instead of
inferring that work from the spelling of a GCC configure option.
Expose independent CRT_LIB32 and CRT_LIB64 settings so each profile
selects exactly the runtime libraries it needs.
Pass the profile's CMake C and C++ flags directly rather than building
an optional argument list through shell positional parameters. Keep all
other profile-specific values next to the selected base stage, including
GCC architecture and manifest flags, BusyBox configuration, zstd
options, and NSIS target architecture.
Simplify multibuild.sh to pass only VARIANT to Docker. Add -m for the
multilib flavor, make -a build all three profiles, and remove the old
argument-file and Dockerfile-patch fallback paths. Update unsigned and
signed CI builds to select x64 or x86 directly, remove the obsolete
variant-x86.args bundle, and document the new build interface.
Verify all three profiles with docker build --check and complete cross
toolchain builds. Smoke-test x64 and x86 compilation, plus both the
64-bit compiler and i686 alias in the multilib profile. Also build the
x64 and x86 CMake targets to exercise both the empty and Windows 7
CMake flag configurations.
The variant patch no longer exists, so both x86 builds read
src/variant-x86.args into a step output, which build-push-action
accepts verbatim as its newline-delimited build-args input. The args
file remains the single definition of the variant.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Hoist every variant-sensitive value into an ARG at the top of the cross
stage, with defaults producing the x64 kit, and gate the multilib-only
sidecar builds on MULTILIB. The x86 variant becomes a set of NAME=value
overrides in src/variant-x86.args, which multibuild.sh turns into
--build-arg options. A plain "docker build" continues to work untouched,
downloads stay shared between variants, and the patch mechanism remains
available as a fallback for a future variant needing structural changes.
One hazard of this approach: ARGs leak into the environment of every
subsequent RUN in the stage, and configure scripts read arbitrary
environment variables. mingw-w64-crt decides whether to build the Win64
runtime by testing whether a shell variable named LIB64 is merely set,
so an ARG of that name forced lib64 on past an explicit --disable-lib64.
Hence CRT_LIB64. Keep ARG names clear of names configure might consult.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Keep x86_64 as the default output while enabling the GCC and mingw-w64
32-bit multilib under lib32. Build 32-bit support archives, CRT
libraries, and winpthreads for both the bootstrap and final sysroots so
each compiler stage has a complete i686 runtime.
Use pentium4 as the 32-bit architecture baseline. Add a GCC specs rule
that defaults _WIN32_WINNT to 0x0501 only under -m32 and only when the
caller has not supplied a value, preserving the existing x64 default and
explicit overrides.
Retain the x86_64-w64-mingw32-prefixed tools and add a separate build
step for an i686-w64-mingw32 interface. Compiler drivers inject -m32.
Target-sensitive binutils select their 32-bit modes explicitly: as uses
--32, ld uses -m i386pe, dlltool uses -m i386 with --as-flags=--32, and
windres uses --target=pe-i386. Input-driven inspection and archive tools
forward without a target override.
These forwarding executables provide both prefixed interfaces without
duplicating a second binutils installation. They deliberately remain
selectors for the underlying x86_64-configured multilib compiler rather
than spoofing its configured target identity.
Direct, Autoconf, CMake, and Libtool probes using the i686-prefixed
interface all produced PE-i386 output.
BuildKit keys ADD --checksum on the checksum and the URL basename, not
on the host, so re-pointing a download at another mirror is a cache hit
and does not even resolve the host. Swapping mirrors during an ftp.gnu.org
outage no longer costs a re-download of everything, which is the trap
that made the recent debian:trixie-slim digest bump so expensive.
Stages that fetched several tarballs in one curl now get one ADD each,
so a mirror change re-fetches a single file rather than all of dl-cross.
Names come from ADD's dest where the URL basename differs, replacing
curl --remote-header-name. This matters beyond cosmetics: every stage's
/dl/*.* is copied into /source/, so these names ship in the source
distribution. GitHub archive refs are the four affected downloads.
ADD cannot run a shell, so expat's tag gains an EXPAT_TAG arg rather
than deriving it from EXPAT_VERSION with tr.
Drops curl from base. BuildKit verifies TLS with its own trust store,
so the downloads are now authenticated rather than passing --insecure.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The build context was the whole working tree, including source/ (227MB
of release tarballs), release artifacts, and .git. BuildKit only sends
files matched by a COPY, so little of this was transferred, but it was
still walked and hashed on every build, and a COPY with a broad pattern
would silently pull in the lot.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The archives are identical except for .frp_describe, which is gitignored
and absent from GitHub archives. Without it the Makefile finds neither a
git checkout nor a source directory named after the tag, and the version
degrades from 1.38.0.git-6075-g169694ebd to 1.38.0.git. Recreate the
file after extraction.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The driver trusted argv[0], which requires a PATH search when it lacks
a directory component, as with w64devkit command aliases. Instead ask
Win32 for the true module path. Also search the driver's own directory
for subprograms, because as and ld live in bin/ rather than a tooldir.
Invoked by absolute path, the toolchain no longer needs its bin/ in
PATH, and remains relocatable.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Windows path rewriting is now case-insensitive and Unicode-aware, and
ignore_headers_in_manifest matches case-insensitively on Windows.
- Detects C23 #embed directives and assembler .incbin directives,
preventing false cache hits.
- Removed the cpp_extension (CCACHE_EXTENSION) option; preprocessor
output is now processed in memory instead of via temporary files.
- Fixed a size overflow when allocating very large files on Windows.
Both Windows console output patches were regenerated against 4.14.
Ccache captures compiler diagnostics and writes them to the terminal
itself. Its Unix-style isatty and TERM checks do not reliably describe
Win32 console capabilities, and its byte-oriented output path cannot
preserve Unicode outside the active code page.
The color bug can be reproduced in a legacy console without virtual
terminal processing:
printf 'int main(void) { return undeclared; }\n' > error.c
TERM=cygwin ccache gcc -c error.c
The diagnostic contains literal escape sequences such as ?[01m?[K
instead of colored text.
The Unicode bug can be reproduced with a non-ASCII identifier:
printf 'float π(void) { return 3; }\nvoid f(void) { float unused = π(); }\n' > unicode.c
ccache gcc -Wall -Wextra -c unicode.c
ccache gcc -Wall -Wextra -c unicode.c
The source line emitted by ccache renders π as mojibake on both captured
and cached diagnostic output.
Add two ccache patches. The first detects native console handles with
GetConsoleMode, enables virtual terminal processing when available, and
otherwise translates supported SGR and erase-line sequences with Win32
console APIs. Redirected output remains unchanged.
The second decodes valid UTF-8 and writes non-ASCII text to native
consoles with WriteConsoleW. ASCII, invalid UTF-8, and redirected output
retain the existing byte-oriented path.
Apply both patches when building ccache.
Fixesskeeto/w64devkit#413.
Defining __trap_terminate_handler in vterminate.cc places it in the
same archive member as the verbose terminate handler, so the default
terminate handler dragged __cxa_demangle, __mingw_sprintf, and gdtoa
into every program that can throw, despite --disable-libstdcxx-verbose.
Define it in eh_term_handler.cc instead, alongside the initializer that
references it, so vterminate.o stays out of default links. A trivial
C++ program shrinks from 190K to 104K.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This ignores the output of multibuild.sh and a copy of the source/
directory extracted from the "source" Docker target, which I typically
keep on hand.
Per the upstream release notes: "This version made no changes to
documented features or interfaces. Some implementation updates were
made to support ecosystem changes and/or fix regressions."
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>