|
| 1 | +# Hermetic toolchain link model — one-shot cross-repo fix for issue #195 |
| 2 | + |
| 3 | +Fixes [mcpp-community/mcpp#195] (llvm toolchain link fails with |
| 4 | +`ld.lld: cannot open Scrt1.o/crti.o/crtn.o` on Linux) at the architecture |
| 5 | +level: one coordinated change set across **mcpp**, **xim-pkgindex**, |
| 6 | +**xlings-res** and (optionally) **xlings**, shipped as mcpp **0.0.83**. |
| 7 | + |
| 8 | +Companion analysis precedents: PR #62 (payload-first sysroot, removed the |
| 9 | +subos override), PR #119 (single deployment-target resolver on macOS — the |
| 10 | +same "one resolver, many consumers" cure applied here to the Linux C-library |
| 11 | +axis), PR #124 (macOS floor + static libc++ default). |
| 12 | + |
| 13 | +--- |
| 14 | + |
| 15 | +## 1. Problem statement (what #195 actually is) |
| 16 | + |
| 17 | +`mcpp` links Clang-with-cfg toolchains with `--no-default-config` and |
| 18 | +re-provides all paths itself. On the payload path (glibc xpkg present — the |
| 19 | +normal result of installing llvm, whose deps declare `xim:glibc`), the |
| 20 | +compile side is complete (`-isystem` payload headers) but the **link side |
| 21 | +provides only `-L`/`-rpath`/`--dynamic-linker` and no `-B`** |
| 22 | +(`src/build/flags.cppm`, `payload_ld`). The Clang driver locates CRT startup |
| 23 | +objects (`Scrt1.o`, `crti.o`, `crtn.o`) through `-B` prefixes and |
| 24 | +sysroot-derived paths — **never through `-L`** — so it passes the bare file |
| 25 | +names to `lld`, which fails. |
| 26 | + |
| 27 | +Why it was invisible until now: on hosts that have a system C toolchain |
| 28 | +installed (dev machines, CI runners with `libc6-dev`), the driver silently |
| 29 | +falls back to the **host's** `/lib/x86_64-linux-gnu/Scrt1.o`. The link |
| 30 | +"succeeds" by mixing host CRT + payload glibc + payload loader. On hosts |
| 31 | +without one (the reporter's fresh WSL2 Ubuntu), the bug surfaces |
| 32 | +immediately. Verified both ways with `clang++ -###`: without `-B` the CRT |
| 33 | +resolves to host paths (or bare names); with `-B<glibc-payload-lib>` all |
| 34 | +three CRT objects resolve inside the payload and the binary links and runs. |
| 35 | + |
| 36 | +## 2. Root architecture defects (why a one-line `-B` is not enough) |
| 37 | + |
| 38 | +The knowledge "how to compile/link against the payload glibc" currently has |
| 39 | +**five divergent implementations across two repos** and no owner: |
| 40 | + |
| 41 | +| # | Copy | Behavior today | |
| 42 | +|---|------|----------------| |
| 43 | +| 1 | `mcpp src/build/flags.cppm` (main build) | bypasses cfg (`--no-default-config`), compile side complete, **link side missing `-B`** ← #195 | |
| 44 | +| 2 | `mcpp src/toolchain/stdmod.cppm` (std module precompile) | independent re-implementation of the compile side | |
| 45 | +| 3 | `mcpp src/build/build_program.cppm` `host_base_flags` (build.mcpp host) | **trusts** the cfg for Clang | |
| 46 | +| 4 | `mcpp src/toolchain/post_install.cppm` `fixup_clang_cfg` | **rewrites** the cfg (deletes `--sysroot`, comment promises "mcpp provides sysroot via payload paths" — copy #1 never delivered it on the link side) | |
| 47 | +| 5 | `xim-pkgindex pkgs/l/llvm.lua` `__install_linux_cfg` | generates the cfg **from the install-time environment** (subos present → `--sysroot=<subos>`; absent → warn *"clang will use system sysroot"* — host fallback by design) | |
| 48 | + |
| 49 | +Consequences found while tracing #195: |
| 50 | + |
| 51 | +- **D1 — cfg trust is undefined.** flags bypasses it, `probe_sysroot` |
| 52 | + step 2 (`fallback/probe_sysroot.cppm parse_clang_cfg_sysroot`) *mines* it |
| 53 | + for `--sysroot`, `build_program` trusts it, `fixup_clang_cfg` rewrites it |
| 54 | + and deletes the very line probe mines (so probe's cfg path is dead on any |
| 55 | + fixed-up install). Four attitudes toward one file. |
| 56 | +- **D2 — cfg is a non-reproducible artifact.** Same xpkg version produces |
| 57 | + different cfgs depending on whether a subos existed at install time, and |
| 58 | + on *which mcpp code path installed it* (see D3). The #195 reporter's cfg |
| 59 | + carried `--sysroot=<subos>`; a fixed-up install carries no `--sysroot` and |
| 60 | + a payload loader line. |
| 61 | +- **D3 — post-install fixups are not part of the pipeline.** Explicit |
| 62 | + `mcpp toolchain install` runs gcc + llvm fixups |
| 63 | + (`src/toolchain/lifecycle.cppm`); the default-toolchain auto-install runs |
| 64 | + only the gcc fixup (`src/build/prepare.cppm`); the **manifest |
| 65 | + `[toolchain]` auto-install path runs no fixup at all** — the exact path a |
| 66 | + `mcpp.toml` llvm user takes. The gcc side already had this bug once |
| 67 | + ("stdlib.h not found", fixed by sharing the fixup with one auto-install |
| 68 | + path); llvm re-created it on another path. Structural, not incidental. |
| 69 | +- **D4 — hermeticity is a promise, not an assertion.** Nothing at build, |
| 70 | + test or packaging time verifies that resolved CRT/libc/loader paths lie |
| 71 | + inside the sandbox. Host fallback makes CI green a false signal. |
| 72 | +- **D5 — `ld-linux-x86-64.so.2` is hardcoded ~10× in mcpp** |
| 73 | + (`flags.cppm`, `pack.cppm` ×3, `lifecycle.cppm` ×3, `post_install.cppm` |
| 74 | + ×5) **and again in xim-pkgindex** (`llvm.lua`, `gcc.lua`, |
| 75 | + `gcc-specs-config.lua`, …), while `glibc.lua` already declares |
| 76 | + `exports.runtime = { loader, abi }` *precisely so consumers don't |
| 77 | + hardcode this* — and no consumer reads it. aarch64-glibc is silently |
| 78 | + broken across the whole chain. |
| 79 | +- **D6 — test blind spot.** llvm e2e (36–41, 47) pin `llvm@20.1.7` (zero |
| 80 | + coverage of 22.x) and run on hosts whose system CRT masks the entire |
| 81 | + category via D4. |
| 82 | +- **D7 — packaging has no admission gate.** Precedent: a slim-repacked |
| 83 | + llvm asset shipped without a runtime library it needed, caught only by |
| 84 | + users. Same class as #195: install-time artifacts are never smoke-linked. |
| 85 | + |
| 86 | +## 3. Design |
| 87 | + |
| 88 | +### 3.1 `ToolchainLinkModel` — one resolver, all consumers (mcpp) |
| 89 | + |
| 90 | +New module `src/toolchain/linkmodel.cppm`: |
| 91 | + |
| 92 | +```cpp |
| 93 | +export namespace mcpp::toolchain { |
| 94 | + |
| 95 | +enum class CLibMode { PayloadFirst, Sysroot, HostSDK, SelfContained /*musl*/ }; |
| 96 | + |
| 97 | +struct ToolchainLinkModel { |
| 98 | + CLibMode mode; |
| 99 | + std::filesystem::path crtDir; // -B: where Scrt1.o/crti.o/crtn.o live |
| 100 | + std::vector<std::filesystem::path> libDirs; // -L + -Wl,-rpath |
| 101 | + std::filesystem::path loader; // -Wl,--dynamic-linker (empty ⇒ omit) |
| 102 | + std::vector<std::filesystem::path> systemIncludes; // compile-side -isystem |
| 103 | + // canonical flag renderers so every consumer emits IDENTICAL strings: |
| 104 | + std::string compile_flags(bool clang) const; // -isystem… (+ --sysroot when mode==Sysroot) |
| 105 | + std::string link_flags() const; // -B… -L… -rpath… --dynamic-linker=… |
| 106 | +}; |
| 107 | + |
| 108 | +ToolchainLinkModel resolve_link_model(const Toolchain& tc); |
| 109 | + |
| 110 | +} // namespace |
| 111 | +``` |
| 112 | +
|
| 113 | +Resolution (Linux, glibc world; musl/macOS/windows return their existing |
| 114 | +behavior unchanged under `SelfContained`/`HostSDK`): |
| 115 | +
|
| 116 | +1. `payloadPaths` present → `PayloadFirst`: `crtDir = libDirs[0] = |
| 117 | + glibcLib`, `systemIncludes = {glibcInclude, linuxInclude}`, |
| 118 | + `loader` per §3.2. |
| 119 | +2. else usable `tc.sysroot` → `Sysroot`: `--sysroot` on both sides (GCC |
| 120 | + include-fixed requirement preserved). |
| 121 | +3. else → empty model (current behavior; the hermeticity check in §3.5 |
| 122 | + turns silent host fallback into a diagnosed warning). |
| 123 | +
|
| 124 | +Consumers — all four divergent copies converge on the model: |
| 125 | +
|
| 126 | +- `flags.cppm`: `compile_toolchain_flags`/`link_toolchain_flags`/`payload_ld` |
| 127 | + are computed **from the model**. This is where the `-B` lands (fixing |
| 128 | + #195), as a model field, not a patch. |
| 129 | +- `stdmod.cppm`: `sysroot_flag` assembly replaced by |
| 130 | + `model.compile_flags()`. (Identical strings also keep the |
| 131 | + `std-module.json` `std_build_commands` cache key honest.) |
| 132 | +- `build_program.cppm host_base_flags`: Clang branch stops trusting the cfg; |
| 133 | + uses the model like everything else. (GCC branch already matches the |
| 134 | + model's Sysroot/`-B` shape.) |
| 135 | +- `post_install.cppm fixup_clang_cfg`: regenerates the cfg **from the |
| 136 | + model** (see §3.3) instead of line-patching xlings' output. |
| 137 | +
|
| 138 | +### 3.2 Loader/ABI from data, not hardcodes (mcpp + pkgindex) |
| 139 | +
|
| 140 | +`resolve_loader(glibcLib, targetTriple)` in `linkmodel.cppm`, in priority |
| 141 | +order: |
| 142 | +
|
| 143 | +1. **Declared metadata** — if the payload carries persisted exports |
| 144 | + (`<payload>/.xpkg-exports.json`, written by xlings at install time once |
| 145 | + the optional xlings change in §4.4 lands), use `runtime.loader`. |
| 146 | +2. **Triple map** — `x86_64 → ld-linux-x86-64.so.2`, |
| 147 | + `aarch64 → ld-linux-aarch64.so.1`, `riscv64 → ld-linux-riscv64-lp64d.so.1`, |
| 148 | + musl triples → `ld-musl-<arch>.so.1`. |
| 149 | +3. **Glob fallback** — first `ld-*.so*` in `glibcLib`. |
| 150 | +
|
| 151 | +All ~10 mcpp hardcode sites (`flags.cppm:367`, `pack.cppm`, |
| 152 | +`lifecycle.cppm`, `post_install.cppm`) switch to this resolver. This |
| 153 | +resolves the aarch64-glibc loader gap as a by-product — do **not** fix |
| 154 | +`flags.cppm:367` as an isolated issue; it is one instance of D5. |
| 155 | +
|
| 156 | +### 3.3 cfg role: humans only; deterministic content (mcpp + pkgindex) |
| 157 | +
|
| 158 | +- **mcpp never reads the cfg again.** Build flags: always |
| 159 | + `--no-default-config` + full model (unchanged direction, now complete). |
| 160 | + `parse_clang_cfg_sysroot` is removed from the probe chain (it is dead on |
| 161 | + fixed-up installs anyway) and demoted to a verbose diagnostic log. |
| 162 | +- **The cfg exists so a human running `clang++` directly gets a working, |
| 163 | + hermetic compiler.** It is generated deterministically from the model: |
| 164 | + `-B<glibcLib>`, `-L<glibcLib>`, `--dynamic-linker`, rpath, libc++ |
| 165 | + `-isystem`s — no `--sysroot=<subos>`, no dependence on install-time |
| 166 | + environment. |
| 167 | +- **`llvm.lua __install_linux_cfg` (xim-pkgindex)** is rewritten to the same |
| 168 | + deterministic recipe: resolve the sibling `xim:glibc` payload (guaranteed |
| 169 | + by the package's own `deps`), read `exports.runtime.loader` from |
| 170 | + `glibc.lua` (finally consuming the mechanism built for this), and |
| 171 | + **fail the install** if the payload is missing — delete the |
| 172 | + `warn + "clang will use system sysroot"` host-fallback branch. |
| 173 | + Also: name the versioned cfg from the actual major |
| 174 | + (`clang-<major>.cfg`), not the hardcoded `clang-20.cfg`. |
| 175 | +- Compat: an old mcpp (≤0.0.82) against the new cfg behaves no worse than |
| 176 | + against today's fixed-up cfg (both lack `--sysroot`); the #195 bug in old |
| 177 | + binaries is fixed only by upgrading mcpp, which is expected. |
| 178 | +
|
| 179 | +### 3.4 Fixups become a pipeline stage (mcpp) |
| 180 | +
|
| 181 | +New single entry `toolchain::ensure_post_install_fixup(cfg, payload, pkg)`: |
| 182 | +
|
| 183 | +- Dispatches by package kind (gcc → patchelf + specs; llvm → patchelf(lib) |
| 184 | + + cfg regeneration from the model; musl → none). |
| 185 | +- **Idempotent via a content-fingerprinted marker** |
| 186 | + (`<payload>/.mcpp-fixup.json`: schema version + fixup-input hash — glibc |
| 187 | + lib dir, loader path, mcpp fixup code version). A marker whose inputs |
| 188 | + drifted re-runs the fixup (lesson from the `.mcpp_ok` blind-spot class: |
| 189 | + markers must witness content, not just "a process once exited 0"). |
| 190 | +- Called from **one** place: the payload-resolution seam all three current |
| 191 | + call sites share (after `resolve_xpkg_path` for toolchain packages), so |
| 192 | + explicit install, default auto-install and manifest `[toolchain]` |
| 193 | + auto-install can never diverge again. The three existing scattered calls |
| 194 | + are deleted. |
| 195 | +- Ownership guard (inherited/symlinked payloads are not patched) moves into |
| 196 | + the entry point and now covers llvm too (today only gcc has it). |
| 197 | +
|
| 198 | +### 3.5 Hermeticity: from promise to assertion (mcpp) |
| 199 | +
|
| 200 | +- **Build-time check** (Linux glibc/musl toolchains): once per toolchain |
| 201 | + fingerprint (cached next to the BMI cache, not per link), run the driver |
| 202 | + with `-### … -o /dev/null` on a trivial input, extract CRT + loader |
| 203 | + paths, and assert every one lies under an allowed prefix (the toolchain |
| 204 | + payload root, the glibc payload root, the sandbox registry). Violation ⇒ |
| 205 | + hard error naming the leaked host path, with escape hatch |
| 206 | + `[build] allow_host_libs = true` (and `MCPP_ALLOW_HOST_LIBS=1`) for users |
| 207 | + who genuinely want host linking. |
| 208 | +- This converts D4's silent contamination into a first-class diagnostic and |
| 209 | + is the regression fence for the whole category, not just #195. |
| 210 | +
|
| 211 | +### 3.6 Tests & CI (mcpp) |
| 212 | +
|
| 213 | +1. **Unit**: `linkmodel` resolution against fabricated payload trees |
| 214 | + (payload-first / sysroot / none; x86_64 + aarch64 loader; glob fallback). |
| 215 | +2. **e2e `86_llvm_hermetic_link.sh`**: build an `import std` project with |
| 216 | + the llvm toolchain, re-run the link command with `-###`, assert |
| 217 | + `Scrt1.o/crti.o/crtn.o` + `--dynamic-linker` all resolve inside the |
| 218 | + sandbox (`$MCPP_HOME`/registry) — catches both "link fails" and "links |
| 219 | + against the host" regressions on any machine. |
| 220 | +3. **Parametrize llvm e2e**: 36–41/47 read `MCPP_E2E_LLVM_VERSION` |
| 221 | + (default: newest installed payload) instead of the pinned `20.1.7`. |
| 222 | +4. **Hermetic CI job**: a container job on a minimal image with **no host |
| 223 | + toolchain** (e.g. `debian:stable-slim` without gcc/libc6-dev) that |
| 224 | + installs the llvm toolchain and runs the llvm e2e subset + test 86. This |
| 225 | + is the only environment class that reproduces #195 faithfully; standard |
| 226 | + runners cannot. |
| 227 | +
|
| 228 | +### 3.7 Packaging admission gate (xlings-res) |
| 229 | +
|
| 230 | +The llvm slim-repack workflow gains a release gate: in the same minimal |
| 231 | +container, install the candidate asset + `glibc` + `linux-headers`, then |
| 232 | +compile, **link and run** an `import std` hello and run the `-###` prefix |
| 233 | +assertion. Assets that cannot produce a self-contained binary are not |
| 234 | +published (would have caught both the missing-runtime-library precedent and |
| 235 | +the #195 environment class at the source). |
| 236 | +
|
| 237 | +## 4. Work breakdown — PRs, order, versions |
| 238 | +
|
| 239 | +All mcpp-side work ships in **one PR** (commit-staged for reviewability); |
| 240 | +the other repos land small coordinated PRs. Nothing here changes package |
| 241 | +asset contents, so no xpkg version bumps are needed — only mcpp releases. |
| 242 | +
|
| 243 | +### 4.1 PR-1 — mcpp `feat(toolchain): hermetic link model` → **v0.0.83** |
| 244 | +
|
| 245 | +Commit plan inside the single PR: |
| 246 | +
|
| 247 | +1. `feat(toolchain): linkmodel module + loader resolver` (new code + unit |
| 248 | + tests; no behavior change yet). |
| 249 | +2. `fix(build): link CRT discovery via linkmodel (-B payload glibc)` — |
| 250 | + flags/stdmod/build_program/post_install converge on the model; deletes |
| 251 | + the four copies. **This commit alone closes #195.** |
| 252 | +3. `refactor(toolchain): single post-install fixup pipeline` (marker, |
| 253 | + one call site, ownership guard for llvm). |
| 254 | +4. `refactor(probe): drop clang-cfg sysroot mining` (diagnostic only). |
| 255 | +5. `feat(build): hermeticity assertion + allow_host_libs escape hatch`. |
| 256 | +6. `refactor: loader hardcodes → linkmodel (pack/lifecycle/post_install)`. |
| 257 | +7. `test(e2e): 86_llvm_hermetic_link + llvm version parametrization`. |
| 258 | +8. `ci: hermetic no-host-toolchain container job`. |
| 259 | +9. `chore: 0.0.83 + changelog`. |
| 260 | +
|
| 261 | +Merge gate: full e2e matrix (linux/macos/windows self-host) green **plus** |
| 262 | +the new hermetic container job green. |
| 263 | +
|
| 264 | +### 4.2 PR-2 — xim-pkgindex `fix(llvm): deterministic hermetic cfg` |
| 265 | +
|
| 266 | +- `pkgs/l/llvm.lua`: rewrite `__install_linux_cfg` per §3.3 (payload-based, |
| 267 | + reads `glibc.lua exports.runtime`, fail-fast, versioned cfg name). |
| 268 | +- `pkgs/g/glibc.lua`: `exports.runtime` stays the single source of truth; |
| 269 | + add per-arch loader entries when aarch64 assets land (tracked, not |
| 270 | + blocking). |
| 271 | +- Independent of PR-1 (mcpp bypasses the cfg either way); land after PR-1 |
| 272 | + so the hermetic CI job exists to validate it end-to-end via a fresh |
| 273 | + install in mcpp's smoke path. |
| 274 | +
|
| 275 | +### 4.3 PR-3 — xlings-res `ci(llvm): hermetic admission smoke` (§3.7) |
| 276 | +
|
| 277 | +Workflow-only change in the llvm repack repo; no asset changes. |
| 278 | +
|
| 279 | +### 4.4 PR-4 (optional, non-blocking) — xlings `feat(xim): persist xpkg exports` |
| 280 | +
|
| 281 | +Write declared `exports` (e.g. `runtime.loader`/`abi`) to |
| 282 | +`<install_dir>/.xpkg-exports.json` at install time so consumers read |
| 283 | +declared metadata instead of conventions. mcpp's resolver treats this as |
| 284 | +priority 1 (§3.2) but works without it (triple map + glob), so this PR can |
| 285 | +trail without blocking the release train. Ships in the next regular xlings |
| 286 | +patch release if taken. |
| 287 | +
|
| 288 | +### 4.5 Release train (0.0.83) |
| 289 | +
|
| 290 | +Follows the automated ecosystem-publish pipeline established for 0.0.82: |
| 291 | +
|
| 292 | +1. Merge PR-1 → tag `v0.0.83` → release workflow builds + publishes |
| 293 | + assets on GitHub. |
| 294 | +2. Pipeline mirrors the release into `xlings-res/mcpp` (**both** GitHub and |
| 295 | + GitCode remotes — CN mirror must not lag, or CN-side installs fetch a |
| 296 | + stale binary). |
| 297 | +3. Pipeline opens the **xim-pkgindex bump PR** (mcpp 0.0.82 → 0.0.83); |
| 298 | + PR-2 (llvm.lua) merges alongside or immediately after it (same repo, |
| 299 | + separate PRs — keep the bump PR mechanical). |
| 300 | +4. Post-merge verification on a clean environment: `xlings install mcpp` |
| 301 | + resolves 0.0.83; then the #195 reproduction — |
| 302 | + `mcpp new hello` + `[toolchain] linux = "llvm@22.1.8"` + `mcpp run` — |
| 303 | + succeeds in the no-host-toolchain container. |
| 304 | +5. Bump the workspace bootstrap pin to 0.0.83 |
| 305 | + (`ci: workspace mcpp bootstrap pin -> 0.0.83`) only after step 4 passes. |
| 306 | +
|
| 307 | +## 5. Compatibility & migration |
| 308 | +
|
| 309 | +- **Existing installs**: first build with 0.0.83 hits the fixup pipeline's |
| 310 | + marker check (no marker → fixup runs), regenerating the cfg from the |
| 311 | + model and patching anything the old scattered paths missed. No user |
| 312 | + action; no reinstall. |
| 313 | +- **BMI caches**: the flag strings change (new `-B`, canonical model |
| 314 | + rendering) ⇒ `std-module.json` metadata mismatch ⇒ std module rebuilds |
| 315 | + once per fingerprint. Expected, self-healing. |
| 316 | +- **Hermeticity check rollout risk**: environments that intentionally link |
| 317 | + host libs (system OpenGL passthrough etc. use dep `[runtime]` |
| 318 | + library_dirs, which stay allowed — the assertion covers only CRT/loader |
| 319 | + resolution). If an unforeseen legitimate layout trips it, the escape |
| 320 | + hatch keeps users unblocked while the allowlist is extended. |
| 321 | +- **Windows/macOS**: untouched code paths (`HostSDK`/PE deploy model); |
| 322 | + e2e must show zero diffs there. |
| 323 | +
|
| 324 | +## 6. Acceptance criteria |
| 325 | +
|
| 326 | +1. #195 reproduction passes in the hermetic container (fresh env, no host |
| 327 | + toolchain, llvm 22.1.8 via manifest `[toolchain]`, `mcpp run` prints). |
| 328 | +2. `-###` assertion: CRT + loader resolve inside the sandbox on **every** |
| 329 | + Linux llvm e2e, on hosts both with and without a system toolchain. |
| 330 | +3. `grep -rn "ld-linux-x86-64" src/` returns only `linkmodel.cppm`'s triple |
| 331 | + map (mcpp) / only `glibc.lua exports` (pkgindex). |
| 332 | +4. One call site for post-install fixups; all three install paths produce |
| 333 | + byte-identical cfg for the same payload. |
| 334 | +5. Full existing e2e baseline unchanged on macOS/Windows/musl. |
| 335 | +6. Release train completed through the bootstrap-pin bump with the CN |
| 336 | + mirror verified. |
| 337 | +
|
| 338 | +## 7. Explicitly out of scope (tracked separately) |
| 339 | +
|
| 340 | +- aarch64 glibc-world enablement beyond the loader resolver (payload assets |
| 341 | + for aarch64 glibc/llvm don't exist yet; the resolver removes the code |
| 342 | + blocker). |
| 343 | +- Identity-first package resolution (`probe_payload_paths` picking the |
| 344 | + first glibc version dir without identity checks) — same family as the |
| 345 | + existing identity-first refactor plan; the linkmodel takes the payload it |
| 346 | + is handed and does not widen the guessing. |
| 347 | +- gcc-specs / musl worlds beyond keeping their current behavior green. |
0 commit comments