Skip to content

Restructure compiler flags: separate compile and final-link profiles#1

Merged
Sam Gammon (sgammon) merged 6 commits intomainfrom
claude/improve-compiler-flags-2sKV8
May 5, 2026
Merged

Restructure compiler flags: separate compile and final-link profiles#1
Sam Gammon (sgammon) merged 6 commits intomainfrom
claude/improve-compiler-flags-2sKV8

Conversation

@sgammon
Copy link
Copy Markdown
Member

Summary

This PR restructures the clang/lld compiler flag organization to distinguish between compile-time flags (applied to all translation units and intermediate dependency builds) and final-link-only flags (applied only at the consuming project's final executable/library link step). This separation prevents overly aggressive optimizations (like --gc-sections) from breaking intermediate builds while still enabling full optimization at the final link.

Key Changes

  • Introduced binary profiles (*-bin.txt files): New profile layer for final-link-only flags that would break intermediate dependency builds (autoconf conftests, BoringSSL test binaries, etc.)

    • linux-bin.txt: ELF-wide final-link flags (--gc-sections, -z defs, --compress-debug-sections=zstd, --fatal-warnings)
    • darwin-bin.txt: Mach-O-wide final-link flags (-dead_strip, -dead_strip_dylibs)
    • Per-arch placeholders: linux-amd64-bin.txt, linux-arm64-bin.txt, darwin-amd64-bin.txt, darwin-arm64-bin.txt
  • Expanded Linux compile flags (linux.txt):

    • Added -fno-semantic-interposition for intra-DSO call optimization
    • Added -fno-plt with -Wl,-z,now for PLT-less call lowering
    • Added _FORTIFY_SOURCE=2 hardening (musl-compatible level 2)
    • Added _LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST for libc++ bounds checking
    • Added linker flags: -Wl,-z,pack-relative-relocs, -Wl,--as-needed, -Wl,--icf=safe, -Wl,--build-id=sha1, -Wl,-O2
    • Removed warning flags (moved to base.txt for cross-platform consistency)
  • New Darwin compile flags (darwin.txt):

    • Added -fuse-ld=lld to use mainline LLVM linker instead of ld64
    • Added _FORTIFY_SOURCE=2 and _LIBCPP_HARDENING_MODE_FAST
    • Added Mach-O linker flags: -bind_at_load, -fixup_chains, -no_data_in_code_info
  • Enhanced base.txt:

    • Added -fzero-call-used-regs=used-gpr for ROP/JOP gadget surface reduction
    • Added -fasynchronous-unwind-tables for profiler/crash-collector support
    • Moved -fstack-clash-protection out (now Linux-only in linux.txt)
    • Consolidated warning flags with clang-compatible set (removed GCC-only diagnostics)
    • Updated comments to clarify clang 22.x requirement
  • Expanded architecture-specific flags:

    • linux-amd64.txt: Added x86-64-v3 ISA baseline with crypto extensions (AES, PCLMUL, SHA, VAES, GFNI, RDRAND), -mtune=znver3, and -fcf-protection=full (Intel CET)
    • linux-arm64.txt: Added ARMv8.2-A baseline with crypto/CRC/dotprod, -mbranch-protection=standard (PAC+BTI), -Wl,-z,force-bti, -Wl,-z,pac-plt
    • darwin-amd64.txt: Added x86-64-v3 baseline with conservative crypto extensions (no SHA/VAES/GFNI)
    • darwin-arm64.txt: Added -mcpu=apple-m1 targeting and -mbranch-protection=standard
  • Added CLI tools:

    • cli/cflags.sh: Bash script to concatenate and output flags for a given (os

https://claude.ai/code/session_01CGzQMHWJw5FZ8weeEtqKWJ

Drop GCC-only diagnostics that clang silently no-ops or rejects under
upstream -Werror (Wlogical-op, Wduplicated-cond/branches, Wtrampolines,
Wtraditional-conversion, Wformat-signedness, Wstringop-overflow=N,
Wformat-{overflow,truncation}=N, Warith-conversion, Whardened) and move
the clang-compatible warning set into base.txt so it applies on darwin
too. Remove the duplicate -fstack-clash-protection from base — it's
linux-only per the comment and Apple's clang rejects it.

Add modern hardening across the matrix: -fzero-call-used-regs=used-gpr
and -fasynchronous-unwind-tables in base; -D_FORTIFY_SOURCE=3 plus
-D_GLIBCXX_ASSERTIONS, -fno-semantic-interposition, -fno-plt and a
broader lld bundle (--icf=safe, --gc-sections, --as-needed,
-z pack-relative-relocs, --build-id=sha1, -O2) on linux;
-D_FORTIFY_SOURCE=2, -D_LIBCPP_HARDENING_MODE_FAST and Mach-O
-dead_strip / -bind_at_load / -fixup_chains on darwin.

Set the previously-missing arch baselines: -march=x86-64-v3 on
{linux,darwin}-amd64; -march=armv8.2-a+crypto+crc+dotprod with
-mbranch-protection=standard and -z force-bti / -z pac-plt on
linux-arm64; -mcpu=apple-m1 with -mbranch-protection=standard on
darwin-arm64. Enable -fcf-protection=full (Intel CET) on linux-amd64
only — macOS userspace doesn't back IBT/SHSTK.
Address the musl/libstdc++/dependency-build constraints surfaced in
review:

- Drop -Wconversion (too noisy across Netty JNI glue and BoringSSL
  intrinsics; re-enable per-target if needed).
- Drop -Wl,--gc-sections from linux.txt and -Wl,-dead_strip from
  darwin.txt — these flags are applied to dependency builds whose
  intermediate links (conftests, test executables) need sections we'd
  otherwise strip. -ffunction-sections/-fdata-sections still emit the
  per-symbol granularity, and the consuming project picks up DCE at
  its own final link step. Documented as such in the README.
- Replace -D_FORTIFY_SOURCE=3 with =2 on linux.txt: Linux/amd64 ships
  against musl, which tops out at level 2 — level 3 needs the glibc
  __builtin_dynamic_object_size chk variants.
- Drop -D_GLIBCXX_ASSERTIONS (we don't ship libstdc++ on Linux);
  add -D_LIBCPP_HARDENING_MODE_FAST instead, since linux-amd64 ships
  a vendored libc++ that honors it.
- Layer the cryptographic ISA extensions onto -march=x86-64-v3 to
  mirror our Rust profile: linux-amd64 gets +aes+pclmul+sha+vaes+gfni
  +rdrand and -mtune=znver3 (effective floor: Ice Lake / Zen 3+);
  darwin-amd64 keeps a conservative +aes+pclmul+rdrand subset that
  every Intel Mac has.

Document toolchain + libc + ISA floors in the README and the consumer-
final-link responsibility for section stripping.
Split the "applies to every translation unit + every link in the
dependency-build pipeline" set from the "applies only at the consuming
project's final executable / shared-library / dylib link" set.
Anything that strips Mach-O / ELF sections, rejects undefined symbols,
compresses debug info, or otherwise assumes "no further link will
follow" now lives in a *-bin sibling and is applied only at the
consumer's leaf link step.

Each leaf profile (linux-amd64, linux-arm64, darwin-amd64,
darwin-arm64) gets a *-bin counterpart that rolls up on top of the
existing chain (base → os → os-arch → os-arch-bin):

- linux-amd64-bin / linux-arm64-bin:
    -Wl,--gc-sections                    DCE consumer of -ffunction/-fdata-sections
    -Wl,-z,defs                          reject undefined symbols at final link
    -Wl,--compress-debug-sections=zstd   ~40-60% smaller .debug_* (lld 18+)
    -Wl,--fatal-warnings                 promote linker warnings only at the leaf

- darwin-amd64-bin / darwin-arm64-bin:
    -Wl,-dead_strip                      Mach-O analogue of --gc-sections
    -Wl,-dead_strip_dylibs               Mach-O analogue of --as-needed

README documents the compile-vs-binary profile split, lists the new
profiles in their own table, and updates the IMPORTANT callout to
point consumers at the *-bin profile instead of asking them to add
the flag manually.
Mirror the compile-side rollup on the binary-link side: introduce
linux-bin.txt and darwin-bin.txt to hold OS-wide final-link flags, and
reduce each linux-$arch-bin.txt / darwin-$arch-bin.txt to a placeholder
for arch-specific leaf-link flags (currently none).

The full rollup for a binary profile is now:

  base.txt → $os.txt → $os-$arch.txt → $os-bin.txt → $os-$arch-bin.txt

linux-bin.txt now owns:
  -Wl,--gc-sections
  -Wl,-z,defs
  -Wl,--compress-debug-sections=zstd
  -Wl,--fatal-warnings

darwin-bin.txt now owns:
  -Wl,-dead_strip
  -Wl,-dead_strip_dylibs

The four leaf $os-$arch-bin.txt files remain as documented placeholders
so the script can read them unconditionally and so we have a clear home
for any future arch-specific final-link flag (ISA-targeted linker
assertion, BTI/PAC tightening beyond what linux-arm64.txt enables, …).

README updated: drop the implication that the leaf-bin files carried
the strip/defs/compress flags, list linux-bin and darwin-bin as
their own rows, and document the new five-stage rollup chain.
Add two equivalent CLIs (Bash + Bun/TypeScript) that print the
concatenated, comment-stripped flag list for a given (os, arch) pair
as a single whitespace-separated line, suitable for shell command
substitution into CFLAGS / LDFLAGS.

Surface (identical between the two):

  cflags[.sh|.ts] [<os> <arch>] [--bin]

  <os>:   linux | darwin    (defaults to host)
  <arch>: amd64 | arm64     (defaults to host)
  --bin:  switch to the *-bin rollup (final-link flags)

Verified that both implementations emit byte-identical output across
all eight (os × arch × {compile, --bin}) combinations.

Both scripts have shebangs and execute permissions, so the canonical
invocations work as expected:

  export CFLAGS="$(./cli/cflags.sh)"
  export LDFLAGS="$(bun run ./cli/cflags.ts --bin)"

README documents the CLI, the surface, and the example export
patterns.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR reorganizes the compiler/linker flag profiles to separate “compile-time” flags (safe for all intermediate dependency builds) from “final-link-only” flags (safe only when producing the consuming project’s final binary), and adds a small CLI to emit the resolved flag set for a target.

Changes:

  • Added new *-bin.txt “binary profiles” to hold final-link-only flags like section GC / dead-strip, undefined-symbol rejection, debug-section compression, and fatal linker warnings.
  • Expanded Linux/Darwin compile profiles with additional hardening, relocation, and tuning flags; moved warning configuration to base.txt for cross-platform consistency.
  • Introduced CLI helpers (cli/cflags.sh and cli/cflags.ts) to concatenate and comment-strip the selected profile chain (compile vs --bin).

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
README.md Documents the new compile vs binary profile model and CLI usage.
base.txt Adds cross-platform hardening/warning flags and clarifies toolchain assumptions.
linux.txt Adds Linux/ELF compile/link hardening flags and moves warnings out.
linux-bin.txt Introduces Linux final-link-only flags (gc-sections, defs, debug compression, fatal warnings).
linux-amd64.txt Adds amd64 ISA baseline/tuning and CET hardening.
linux-arm64.txt Adds arm64 ISA baseline and BTI/PAC-related flags.
linux-amd64-bin.txt Placeholder for amd64-specific final-link-only flags.
linux-arm64-bin.txt Placeholder for arm64-specific final-link-only flags.
darwin.txt Introduces macOS compile/link flags, including -fuse-ld=lld and Mach-O hardening.
darwin-bin.txt Introduces macOS final-link-only flags (dead-strip, dead_strip_dylibs).
darwin-amd64.txt Adds Intel macOS ISA baseline/feature selection.
darwin-arm64.txt Adds Apple Silicon targeting and branch protection flags.
darwin-amd64-bin.txt Placeholder for Intel-macOS-specific final-link-only flags.
darwin-arm64-bin.txt Placeholder for Apple-Silicon-specific final-link-only flags.
cli/cflags.sh Bash script to emit the resolved flag list for a target, optionally including --bin.
cli/cflags.ts Bun/TS script to emit the resolved flag list for a target, optionally including --bin.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread base.txt Outdated
Comment thread linux.txt Outdated
Comment thread linux-amd64.txt Outdated
Comment thread linux-arm64.txt Outdated
Comment thread README.md Outdated
Comment thread cli/cflags.ts Outdated
Comment thread darwin-amd64.txt Outdated
Comment thread darwin-arm64.txt Outdated
Eight inline review comments, all valid; six stale-doc and one factual
fix in the file headers / README, plus one real bug in cli/cflags.ts.

Stale-doc fixes (header comments referenced files/profiles that this
repo never had):

- base.txt        — drop the openbsd/freebsd/windows OS list and the
                    amd64.txt/arm64.txt/riscv64.txt arch layer; replace
                    with the actual file inventory and the documented
                    compile / binary rollup chains.
- linux.txt       — header listed "linux / linux-aarch64 / linux-riscv64"
                    profiles. Only linux-amd64 and linux-arm64 exist.
- linux-amd64.txt — drop "and amd64.txt" from "appended after …".
- linux-arm64.txt — drop "and arm64.txt".
- darwin-amd64.txt — drop "and amd64.txt".
- darwin-arm64.txt — drop "and arm64.txt".

Factual fix:

- README.md "Toolchain & deployment floor" claimed the Darwin/amd64
  x86-64-v3 baseline covered "all Intel Macs (Ivy Bridge+)". Ivy Bridge
  is x86-64-v2 (no AVX2/BMI2/FMA), so v3 binaries do not run there.
  Restate as "Haswell+ (2013); covers every Intel Mac supported by
  macOS 12+" — every macOS 12 Intel host is Broadwell or newer, well
  above the v3 floor. darwin-amd64.txt body comment also clarified to
  stop implying Ivy Bridge is in scope.

Real bug:

- cli/cflags.ts usage() printed only the first paragraph of the header.
  After stripping the `// ` marker, blank-comment separator lines
  (`//` followed by nothing) become empty strings, and the previous
  `split(/\n\s*\n/)[0]` picked the first paragraph and stopped.
  Replaced with a loop that emits every consecutive `//` line after
  the shebang. Verified `--help` now prints the full Usage and
  Recognized-values block, and that the eight (os × arch × mode)
  outputs still match byte-for-byte between cflags.sh and cflags.ts.
@sgammon Sam Gammon (sgammon) marked this pull request as ready for review May 5, 2026 23:10
@sgammon Sam Gammon (sgammon) merged commit 36b2f8a into main May 5, 2026
Sam Gammon (sgammon) pushed a commit that referenced this pull request May 6, 2026
CI:
- New tests/probe.c — minimal C TU exercising libc calls, function-
  pointer dispatch, a small loop, and stack-allocated string formatting.
  Designed to stay warning-clean under the live -Wall -Wextra -Wpedantic
  -Wformat=2 -Wstrict-prototypes -Wshadow -Wundef set so any codegen
  regression breaks CI early.
- New .github/workflows/ci.yml — matrix probe on three GitHub-hosted
  runners (ubuntu-24.04 / ubuntu-24.04-arm / macos-14). For each
  target it installs clang-22 + lld-22 (apt.llvm.org on Linux,
  Homebrew on macOS), asserts cli/cflags.sh and cli/cflags.ts emit
  byte-identical output for the compile and binary profiles, then
  compiles tests/probe.c with the compile profile (-c) and
  compile+links+runs it with the binary profile.
- README CI section documents coverage and notes that darwin-amd64
  is exercised locally pre-tag because GitHub no longer offers an
  Intel-Mac runner.

Copilot review on PR #2 (3 valid comments addressed; 1 false positive
skipped):

- darwin.txt + linux.txt + README.md: the parked libc++ flag was
  written as the shorthand -D_LIBCPP_HARDENING_MODE_FAST in three
  comment blocks, but the real flag in labs.disabled.txt is
  -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST. Re-enabling
  via the shorthand is a silent no-op (libc++ checks the value of
  the macro, not its mere definition). All three call sites now use
  the exact spelling, and the README's parked-flag list is reformatted
  as a bullet list with -U_FORTIFY_SOURCE included for completeness.
- linux.txt:45 (false positive): Copilot flagged -fno-plt as a
  newly-introduced undocumented flag. -fno-plt was added in PR #1
  (commit 6d0e14c) and is not in this PR's diff; Copilot compared
  the PR description's "Added" section against the file content
  rather than against the actual diff. Skipped.
Sam Gammon (sgammon) pushed a commit that referenced this pull request May 6, 2026
CI:
- New tests/probe.c — minimal C TU exercising libc calls, function-
  pointer dispatch, a small loop, and stack-allocated string formatting.
  Designed to stay warning-clean under the live -Wall -Wextra -Wpedantic
  -Wformat=2 -Wstrict-prototypes -Wshadow -Wundef set so any codegen
  regression breaks CI early.
- New .github/workflows/ci.yml — matrix probe on three GitHub-hosted
  runners (ubuntu-24.04 / ubuntu-24.04-arm / macos-14). For each
  target it installs clang-22 + lld-22 (apt.llvm.org on Linux,
  Homebrew on macOS), asserts cli/cflags.sh and cli/cflags.ts emit
  byte-identical output for the compile and binary profiles, then
  compiles tests/probe.c with the compile profile (-c) and
  compile+links+runs it with the binary profile.
- README CI section documents coverage and notes that darwin-amd64
  is exercised locally pre-tag because GitHub no longer offers an
  Intel-Mac runner.

Copilot review on PR #2 (3 valid comments addressed; 1 false positive
skipped):

- darwin.txt + linux.txt + README.md: the parked libc++ flag was
  written as the shorthand -D_LIBCPP_HARDENING_MODE_FAST in three
  comment blocks, but the real flag in labs.disabled.txt is
  -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST. Re-enabling
  via the shorthand is a silent no-op (libc++ checks the value of
  the macro, not its mere definition). All three call sites now use
  the exact spelling, and the README's parked-flag list is reformatted
  as a bullet list with -U_FORTIFY_SOURCE included for completeness.
- linux.txt:45 (false positive): Copilot flagged -fno-plt as a
  newly-introduced undocumented flag. -fno-plt was added in PR #1
  (commit 6d0e14c) and is not in this PR's diff; Copilot compared
  the PR description's "Added" section against the file content
  rather than against the actual diff. Skipped.
Sam Gammon (sgammon) added a commit that referenced this pull request May 6, 2026
* chore: phase out hardening flags hurting startup; add three perf wins

We're prioritising startup time over defence-in-depth for now. Six
hardening flags move to a new tracked, non-consumed parking-lot file
labs.disabled.txt; three pure perf/size wins land in the live profiles.

Parked in labs.disabled.txt (each with its own re-enable criteria):

  base.txt:
    -fstack-protector-strong               (canary prologue/epilogue)
    -fzero-call-used-regs=used-gpr         (per-return reg-clearing)

  linux.txt:
    -U_FORTIFY_SOURCE
    -D_FORTIFY_SOURCE=2                    (per-call libc bounds checks)
    -D_LIBCPP_HARDENING_MODE_FAST          (per-call libc++ bounds checks)

  darwin.txt:
    -U_FORTIFY_SOURCE
    -D_FORTIFY_SOURCE=2
    -D_LIBCPP_HARDENING_MODE_FAST

  linux-amd64.txt:
    -fcf-protection=full                   (ENDBR64 on every indirect target)

labs.disabled.txt is not loaded by the rollup or by cli/cflags.{sh,ts}
— the resolver only matches a closed set of {base, $os, $os-$arch,
$os-bin, $os-$arch-bin}.txt. Verified with a smoke test.

Kept on:
  -fasynchronous-unwind-tables             (better profiling > table size)
  -fbasic-block-sections=all (linux-amd64) (Propeller is on the roadmap;
                                            keeping the per-BB sections
                                            avoids a rebuild-the-world
                                            once we wire in the symbol-
                                            ordering file)

Added (small, free startup wins):
  base.txt:
    -fmerge-all-constants                  (dedupe equal string/numeric
                                            constant pool entries → smaller
                                            binary, fewer relocs at load;
                                            mild ISO violation we don't
                                            depend on)

  linux.txt:
    -Wl,--hash-style=gnu                   (drop legacy sysv-hash; ~2x
                                            faster dyn-symbol lookup at
                                            load + smaller .dynsym)
    -Wl,-O3 (replaces -Wl,-O2)             (more aggressive linker-side
                                            string/section merging; pure
                                            link-time cost)

README features matrix updated; new "Phasing in" subsection lists the
parked flags and points at labs.disabled.txt. cli/cflags.{sh,ts}
output verified byte-identical across all eight (os × arch × mode)
combinations after the change.

* ci: add probe + cross-platform clang-22 workflow; address Copilot review

CI:
- New tests/probe.c — minimal C TU exercising libc calls, function-
  pointer dispatch, a small loop, and stack-allocated string formatting.
  Designed to stay warning-clean under the live -Wall -Wextra -Wpedantic
  -Wformat=2 -Wstrict-prototypes -Wshadow -Wundef set so any codegen
  regression breaks CI early.
- New .github/workflows/ci.yml — matrix probe on three GitHub-hosted
  runners (ubuntu-24.04 / ubuntu-24.04-arm / macos-14). For each
  target it installs clang-22 + lld-22 (apt.llvm.org on Linux,
  Homebrew on macOS), asserts cli/cflags.sh and cli/cflags.ts emit
  byte-identical output for the compile and binary profiles, then
  compiles tests/probe.c with the compile profile (-c) and
  compile+links+runs it with the binary profile.
- README CI section documents coverage and notes that darwin-amd64
  is exercised locally pre-tag because GitHub no longer offers an
  Intel-Mac runner.

Copilot review on PR #2 (3 valid comments addressed; 1 false positive
skipped):

- darwin.txt + linux.txt + README.md: the parked libc++ flag was
  written as the shorthand -D_LIBCPP_HARDENING_MODE_FAST in three
  comment blocks, but the real flag in labs.disabled.txt is
  -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST. Re-enabling
  via the shorthand is a silent no-op (libc++ checks the value of
  the macro, not its mere definition). All three call sites now use
  the exact spelling, and the README's parked-flag list is reformatted
  as a bullet list with -U_FORTIFY_SOURCE included for completeness.
- linux.txt:45 (false positive): Copilot flagged -fno-plt as a
  newly-introduced undocumented flag. -fno-plt was added in PR #1
  (commit 6d0e14c) and is not in this PR's diff; Copilot compared
  the PR description's "Added" section against the file content
  rather than against the actual diff. Skipped.

* ci: harden toolchain install for arm64 runners

The first run of ci.yml passed on linux-amd64 but failed in ~30s on
linux-arm64 and darwin-arm64, both during the toolchain-install step.
I don't have direct access to the GHA logs from this session, so this
commit makes the install path more robust along the most likely
failure modes:

- Linux: replace the hand-rolled gpg + apt source setup with
  apt.llvm.org's llvm.sh, which is the canonical installer and
  handles distro/codename detection across noble/noble-arm64.
  Drop --update-alternatives in favour of explicit symlinks in
  /usr/local/bin (which precedes /usr/bin in PATH on ubuntu-24.04),
  so `clang` and `ld.lld` resolve to the v22 binaries even if the
  base image already has an unversioned `clang` from a different
  source.

- Darwin: keep the Homebrew install but capture brew --prefix llvm
  into a CC env var passed via $GITHUB_ENV so subsequent steps
  invoke that clang explicitly instead of relying on PATH order.
  Also prepend $(brew --prefix llvm)/bin so -fuse-ld=lld finds
  ld64.lld.

- New "Toolchain sanity" step prints `which clang`, `clang --version`,
  and `ld.lld` / `ld64.lld --version` so any future failure surfaces
  the actual install state in the log.

Compile / link steps now invoke "$CC" explicitly rather than relying
on whichever `clang` is first in PATH.

* ci+arm64: drop strict BTI/PAC link flags; harden darwin install assertions

Two unrelated CI failures were chasing different root causes:

linux-arm64
  ld.lld errored hard at link time:
    ld.lld: error: /lib/aarch64-linux-gnu/Scrt1.o: -z force-bti:
      file does not have GNU_PROPERTY_AARCH64_FEATURE_1_BTI property
  …and the same for crti.o / crtn.o under -z pac-plt. lld treats
  these two -z flags as strict — every input object must carry the
  matching GNU property note or the link aborts. Stock Ubuntu noble
  glibc CRT objects ship without those notes, so the link can never
  succeed on a vanilla distro toolchain.

  Drop -Wl,-z,force-bti and -Wl,-z,pac-plt from linux-arm64.txt.
  Keep -mbranch-protection=standard so our own .o files still emit
  the PAC/BTI sequences and per-object property notes — when the
  rest of the link surface (CRT, libc) catches up, we can re-enable
  the linker-side enforcement. Comment in linux-arm64.txt records
  the rationale and the verbatim error message so the next person
  who sees it knows what changed and what would need to be true to
  flip it back on.

darwin-arm64
  clang: error: invalid linker name in argument '-fuse-ld=lld'
  This message is Apple-clang's signature for "I don't know what
  'lld' is" — meaning Apple's /usr/bin/clang got picked up instead
  of the brew-installed mainline clang. Two defensive changes to
  ci.yml so the next failure surfaces the actual install state:

  - Install step now hard-asserts that $(brew --prefix llvm)/bin/clang
    AND $(brew --prefix llvm)/bin/ld64.lld both exist. If brew shipped
    llvm without ld64.lld (rare but possible on some images), the step
    fails immediately with the missing path printed instead of letting
    the failure cascade into a confusing clang error two steps later.
  - Toolchain sanity step now invokes "$CC" instead of bare `clang`,
    explicitly rejects an Apple-clang $CC, and uses
    `--print-prog-name=ld.lld` / `--print-prog-name=ld64.lld` to show
    the lld path the driver would actually pick under -fuse-ld=lld.

* ci(darwin): install brew lld separately; symlink ld64.lld next to clang

Homebrew's `llvm` formula on macOS does not ship lld — confirmed by
listing /opt/homebrew/opt/llvm/bin on macos-14 (clang, MLIR tools,
FileCheck, ~150 binaries, zero lld variants). On Mac, `lld` is a
standalone Homebrew formula; the asymmetry vs Linux (where
apt.llvm.org bundles them in the llvm-toolchain-N package set) is
why my "install lld with the llvm formula" assumption broke darwin
CI even though linux CI worked.

Fix: `brew install llvm lld zstd` (add lld to the install list),
then symlink $(brew --prefix lld)/bin/ld64.lld into
$(brew --prefix llvm)/bin/ld64.lld so clang's `-fuse-ld=lld` driver
finds it in its own bin dir (clang searches there before falling
through to PATH). Hard-assert both binaries exist before continuing
so the failure mode is "MISSING: <path>" not the cryptic
"invalid linker name in argument '-fuse-ld=lld'" that surfaces two
steps later when Apple's clang inadvertently gets picked up.

* ci(darwin): tighten Apple-clang detector; was false-positive on triple

The toolchain-sanity guard that's supposed to reject Apple's clang
fired a false positive on macos-14: \$CC was correctly set to
/opt/homebrew/opt/llvm/bin/clang (Homebrew clang 22.1.4), but the
naive `grep -qiE 'apple|xcode'` matched on the target triple line:

    Target: arm64-apple-darwin23.6.0

…which every clang on macOS prints, Homebrew or otherwise. Restrict
the match to the *version banner* (first line) and require the exact
"Apple clang" string Apple's binary uses there. Homebrew's banner is
"Homebrew clang version 22.1.4"; Xcode/Apple's is "Apple clang
version 15.x.y …". The discriminator is the leading word, not the
case-insensitive presence of "apple" anywhere in the multi-line
output.

Net: lld install succeeded, brew clang is the one running, and CI
should now get past the sanity step.
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.

3 participants