You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
T8 (#718 W9): the CRT model is a property of the MSVC ABI, not of cl.exe
Every MSVC-ABI row now receives the same CRT model: cl spells it /MT or
/MD, clang++ targeting *-windows-msvc spells it -fms-runtime-lib=static
or =dll. One helper (msvc_abi_crt_word, dialect.cppm) decides the word
for the translation units, the std/std.compat BMIs and the link command
alike, closing #649 E10 (a compile-only flag never reached the clang
driver's own link-time choice of -defaultlib:).
toolchain-coupled (the dynamic CRT, with the toolset's own
vcruntime140.dll/msvcp140.dll staged beside the artifact) is now the
default for every role on this ABI (dist::msvc_abi_default_contract,
ContractStatement::msvcAbiDefault). A toolset with no VC\Redist\MSVC
directory defaults to host-coupled silently and refuses an explicit
toolchain-coupled, naming the missing directory (prepare/plan.cpp). A
free-form CRT word in cxxflags/dialect_cxxflags is checked against the
resolved model: agreeing is a warning, contradicting is a refusal
(dialect::check_crt_word, wired in prepare/scan.cpp).
The toolset's redistributable directory is carried as its own
Toolchain field (msvcRedistDir), populated for cl from vc_redist_dir
and for the LLVM row from its sysroot's tools directory
(vc_redist_dir_for_tools_dir) rather than from linkRuntimeDirs, which
holds LLVM's own runtime directories on that row. The staging gate and
the mcpp run/test search path both read this field, gated on the MSVC
ABI rather than on which compiler is in use. mcpp pack carries the
staged DLLs by default; an explicit --mode system now resolves a
defaulted (never-declared) toolchain-coupled contract to host-coupled
instead of refusing.
e2e 703 is inverted to the new default; e2e 814 covers the LLVM row's
import table, staged DLL, clean-PATH run, self-contained round trip,
BMI switching (A, B, A) and pack modes (Windows-only, unverified here).
Unit tests cover the CRT-word derivation, the free-form-word check, the
MSVC-ABI default/redistributable resolution and a compute_flags-level
property test across both rows; the link-line half of that test is
gated on mcpp.platform.is_windows, since link_shape resolves
LinkShape::PeLld only when current_link_host() is Windows and a
Linux-built mcpp cannot reach that branch regardless of the plan's
target triple.
docs/20, docs/zh/20 and SPEC-006 record the new default and the
upgrade; docs/04 needed no change (it only points at docs/20).
131/131 unit tests pass on Linux (gcc 16.1.0 and llvm 22.1.8 rows);
docs structure/style checks pass.
Copy file name to clipboardExpand all lines: docs/20-toolchains.md
+72-39Lines changed: 72 additions & 39 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -499,7 +499,10 @@ It is a **compatibility floor declaration**, not a payload binding like
499
499
`glibc@2.39` on Linux — `ucrtbase.dll` is a Windows component and mcpp neither
500
500
ships nor substitutes it.
501
501
502
-
**CRT model.**`/MD` (host-coupled) by default; `/MT` when either
502
+
**CRT model.**`/MD` by default, with the toolset's own redistributable staged
503
+
beside the artifact (`toolchain-coupled` — see [On the MSVC
504
+
runtime](#on-the-msvc-runtime) below for the full model, which applies to `cl`
505
+
and to clang++ on this ABI alike); `/MT` when either
503
506
504
507
```toml
505
508
[target.x86_64-windows-msvc]
@@ -1131,7 +1134,7 @@ loaded *into* a process that already has a C++ runtime.
1131
1134
|---|---|---|
1132
1135
| ELF (Linux, …) |`toolchain-coupled`| ELF has one global symbol namespace and the first definition loaded wins. A `.so` that statically embedded libstdc++ **exports** it, and the executable linking that library binds *its*`std::` references there — its own `self-contained` contract silently becomes a no-op, and its C++ runtime is whichever build of that library happens to load. |
1133
1136
| Mach-O |`self-contained`| the mechanism there is already `-load_hidden`, i.e. hidden visibility, so dyld never unifies those symbols; and toolchain-coupled is not available on macOS at all (see the note below). |
1134
-
| PE (Windows) |`self-contained`| PE has no global symbol namespace — imports resolve per-DLL by name, so a DLL's private runtime cannot be picked up by anything else. |
1137
+
| PE, GNU ABI (MinGW) |`self-contained`| PE has no global symbol namespace — imports resolve per-DLL by name, so a DLL's private runtime cannot be picked up by anything else. The MSVC ABI's own default is a separate rule — see [On the MSVC runtime](#on-the-msvc-runtime) below. |
1135
1138
1136
1139
Setting `shared = "self-contained"` on ELF is supported and does exactly what it
1137
1140
says: the library embeds the runtime. mcpp additionally passes
@@ -1214,28 +1217,75 @@ artifact than the manifest asked for.
1214
1217
1215
1218
### On the MSVC runtime
1216
1219
1217
-
The CRT model is the mechanism here, and it is a **whole-project** switch: cl
1218
-
bakes `_MSVC_MT`/`_MSVC_MD` into the one `std` module a project builds, so a
1219
-
per-role contract that disagrees with the project's cannot be honoured and is
1220
-
reported rather than ignored.
1221
-
1222
-
| value | meaning on MSVC |
1223
-
|---|---|
1224
-
|`self-contained`|`/MT` — the static CRT. `linkage = "static"` selects the same thing from the libc axis. |
1225
-
|`host-coupled` (default under `/MD`) | the target provides `vcruntime140.dll` / `msvcp140.dll` — i.e. Visual Studio or the redistributable is installed there. |
1226
-
|`toolchain-coupled`| the toolset's **own** copy of those DLLs travels with the artifact. |
1227
-
1228
-
`toolchain-coupled` is worth spelling out, because the obvious reading is
1229
-
wrong. `ucrtbase.dll`*is* a Windows component (since Windows 10) and mcpp
1230
-
never ships it. `vcruntime140.dll` and `msvcp140.dll` are **not**: every MSVC
1231
-
toolset carries them under `VC\Redist\MSVC\<version>\<arch>\`, exactly the
1232
-
way a gcc payload carries `libstdc++.so`. Under this contract mcpp stages them
1233
-
beside the artifact — which is what makes a default `/MD` build runnable on a
1234
-
machine that has only the pinned toolset and no Visual Studio at all.
1220
+
The CRT model is a property of the **target ABI**, not of the compiler: `cl`
1221
+
and clang++ targeting `*-windows-msvc` (the `llvm` row) receive the *same*
1222
+
model, each spelling it for its own driver. It is also a **whole-project**
1223
+
switch: `cl` bakes `_MSVC_MT`/`_MSVC_MD` into the one `std` module a project
1224
+
builds, so a per-role contract that disagrees with the project's cannot be
1225
+
honoured and is reported rather than ignored.
1226
+
1227
+
| value | meaning on the MSVC ABI |`cl` spelling | clang++ spelling |
1228
+
|---|---|---|---|
1229
+
|`self-contained` (or `linkage = "static"`) | the static CRT |`/MT`|`-fms-runtime-lib=static`|
1230
+
|`toolchain-coupled` (**default**) | the dynamic CRT, with the toolset's own copy of `vcruntime140.dll`/`msvcp140.dll` staged beside the artifact |`/MD`|`-fms-runtime-lib=dll`|
1231
+
|`host-coupled`| the dynamic CRT, with nothing staged — the target provides those DLLs itself (Visual Studio, or the redistributable installer) |`/MD`|`-fms-runtime-lib=dll`|
1232
+
1233
+
**`toolchain-coupled` is the default**, whatever `cxx_runtime` says, for every
1234
+
role. This is worth spelling out, because the obvious reading of "portable by
1235
+
default" is wrong here: `ucrtbase.dll`*is* a Windows component (since Windows
1236
+
10) and mcpp never ships it, but `vcruntime140.dll` and `msvcp140.dll` are
1237
+
**not** — every MSVC toolset carries them under
1238
+
`VC\Redist\MSVC\<version>\<arch>\`, exactly the way a gcc payload carries
1239
+
`libstdc++.so`. Under this contract mcpp stages them beside the artifact
1240
+
(on the `mcpp build` output directory) and puts the same directory on the
1241
+
`mcpp run`/`mcpp test` search path — which is what makes the default build
1242
+
runnable on a machine that has only the pinned toolset and no Visual Studio
1243
+
at all.
1244
+
1245
+
A resolved toolset that carries no `VC\Redist\MSVC` directory (measured on
1246
+
some `msvc@system` installs) cannot deliver `toolchain-coupled`. The
1247
+
undeclared default then resolves to `host-coupled` instead, silently — this is
1248
+
a property of the row, stated once here, not a warning on every build of it.
1249
+
An **explicit**`cxx_runtime = "toolchain-coupled"` on such a row is refused,
1250
+
naming the missing directory: an explicit statement a toolset cannot meet is
1251
+
an error, never a silent downgrade.
1235
1252
1236
1253
The debug CRT (`vcruntime140d.dll` and friends, under `debug_nonredist\`) is
1237
-
never staged: it may not be redistributed.
1238
-
1254
+
never staged: it may not be redistributed, and mcpp's `dev` profile does not
1255
+
select it — it states debug information, not a different CRT. That axis stays
1256
+
deferred until a consumer needs it.
1257
+
1258
+
Combining `toolchain-coupled` or `host-coupled` with `/MT` (`linkage =
1259
+
"static"`, or `self-contained`) is a contradiction rather than a missing
1260
+
feature — a static CRT leaves no DLL to couple to — so it is reported and
1261
+
resolved to `self-contained`. `mcpp pack` enforces the other half: a mode
1262
+
that bundles nothing (`--mode static`) together with an *explicit*
1263
+
`toolchain-coupled` cannot deliver it and refuses; `--mode system` on a
1264
+
project that never stated a contract resolves the default to `host-coupled`
1265
+
instead, since an explicit mode outranks a default.
1266
+
1267
+
**A free-form CRT word is always a second statement.** Every MSVC-ABI build
1268
+
now states its own CRT, so a literal `/MT`, `/MD`, `/MTd`, `/MDd` or
1269
+
`-fms-runtime-lib=*` (either dash) in `[build] cxxflags` or `dialect_cxxflags`
1270
+
can never be the only voice. One that **agrees** with the resolved model is
1271
+
warned as redundant, naming the key (`cxx_runtime` or `linkage`) to write
1272
+
instead; one that **contradicts** it is refused, naming the word, the key it
1273
+
was found in, and the value it corresponds to. The engine never lets the
1274
+
last word on the command line decide silently.
1275
+
1276
+
> **Upgrading to 2026.9.28.1?**`cl`-row projects are unchanged apart from
1277
+
> gaining the staged DLLs beside their programs. **LLVM-row programs move
1278
+
> from the static to the dynamic CRT**: before this release clang++ on the
1279
+
> MSVC ABI received no model at all and linked `libcmt` regardless of
1280
+
> `cxx_runtime`; now it receives the same model `cl` does, defaulting to
1281
+
> `toolchain-coupled`. A project that links a prebuilt `/MT` library on this
1282
+
> row now fails to link (`LNK2038`, a CRT mismatch) and should state
1283
+
> `cxx_runtime = "self-contained"` to restore the static CRT it had before.
1284
+
> Two manifests that built before this release are refused after it: a
1285
+
> free-form CRT word that contradicts the resolved model, and an explicit
1286
+
> `toolchain-coupled` on a row whose toolset ships no redistributable — see
1287
+
> above for both.
1288
+
>
1239
1289
> **Upgrading from 2026.8.15 or earlier?** This key used to be **inert** on the
1240
1290
> MSVC ABI — it reported `not implemented for the MSVC runtime yet` and every
1241
1291
> value fell back to `/MD`. Since 2026.8.16 it is honoured, so a manifest that
@@ -1244,23 +1294,6 @@ never staged: it may not be redistributed.
1244
1294
> model, and the switch is silent because the value was always valid. A project
1245
1295
> that set it while the key did nothing should re-confirm the intended value.
1246
1296
1247
-
Combining it with `/MT` is a contradiction rather than a missing feature — a
1248
-
static CRT leaves no DLL to couple to — so it is reported and resolved to
1249
-
`self-contained`. `mcpp pack` enforces the other half: a mode that bundles
0 commit comments