fix: cl.exe consumption verified, the flag-axis rule written down, and the lib-root decision finished - #454
Merged
Merged
Conversation
Analysis of what 2026.8.18.2 left open, plus the code the analysis is based on. The unifying finding: a decision keyed on the wrong axis. Last round fixed three flags that way (`-fPIC`, `--out-implib`, `/DEF:`), and it is tempting to call the link-intent flavour a fourth — `if (isMsvcDialect) return PeMsvc`. It is NOT, and the difference is worth writing down: those three reach the LINKER, so the target ABI decides; `-L` / `/LIBPATH:` reach whatever mcpp INVOKES, so the dialect does. Clang targeting the MSVC ABI is the case that separates the two questions — it takes `-L` while producing MSVC-ABI objects. Pinned in a unit test that names it. On cl.exe: the mechanism shipped last round and the docs' limits table still said ❌ while the prose beside it described the solution. Fixed, and now verified from both ends — a portable unit test over the renderer, and an e2e whose consumer pins msvc@system, because a clang consumer would pass whether or not the neutral form was used and therefore proves nothing. And a third instance of the lib-root family that survived last round: `prepare.cppm` resolved a host-module dependency's lib root without probing, and `validate.cppm` warned that an `.ixx` project's lib root was missing when it was right there. Control-verified: the released 2026.8.18.2 binary emits `warning: src/mathkit.cppm: lib target without conventional lib root` on a project whose interface is `src/mathkit.ixx`; with the fix it does not. Fixing the path I was testing was not the same as fixing the decision.
…the lib-root decision finished `cl.exe` consumption was already implemented; what was missing was verification and a docs table that had not followed the prose. Both ends now: a portable unit test over the renderer, and an e2e whose consumer pins `msvc@system`, because a clang consumer passes whether or not the neutral form is used and therefore proves nothing. The e2e also asserts the leg's `-L` is absent from the generated graph — "it linked" could otherwise mean cl was merely tolerant.⚠️ And a finding that runs against the previous round: `-L` vs `/LIBPATH:` is a DIALECT question, not a target-ABI one, because those flags go to whatever mcpp invokes rather than to the linker. Clang targeting the MSVC ABI separates all three axes — GNU dialect, MSVC-ABI objects, PE image — and asking it the wrong one fails differently each time. docs/08 §7.5 is that table, in both languages. The lib-root decision was only half fixed last round. `validate.cppm` warned that an `.ixx` project's lib root was missing when it was right there, and `prepare.cppm` handed a host-module dependency a path to a file that does not exist. Control-verified: the released 2026.8.18.2 binary emits `warning: src/mathkit.cppm: lib target without conventional lib root` on a project whose interface is `src/mathkit.ixx`. e2e 263 pins one assertion per CALL SITE, with a negative control — without it the test would also pass against a validator that simply stopped checking. Two more things the work turned up: * a host-module dependency was warned about a dead `module_extensions` entry. Its source globs are emptied on purpose — that is what keeps a build rule out of the consumer's binary — so every declared extension looks dead, and the rule's author sees a warning about their own correct manifest with nothing to fix. * the data-symbol `dllimport` limit is now reproducible rather than prose. The assertion is the DIFFERENCE between the two spellings, not an error text: which form the failure takes depends on the toolset, and pinning one would make the test a hostage to it. If reading exported data without `dllimport` ever starts working, the test says so and names the documentation as the thing to fix. docs/05 gains the rule the hard error follows from — every entry `sources` matches must produce an object that gets linked — and the 简体中文 page, which was missing even the `sources = []` note, gets both.
…om its suffix
macOS CI, on the test written two commits earlier:
clang++: error: no such file or directory: …/.build-mcpp/rulepkg.pcm
clang++: error: no input files
`clang++ --precompile rulepkg.ixx -o rulepkg.pcm` EXITED 0 AND WROTE NOTHING.
Clang's driver does not recognise `.ixx`, so it treated the interface as a linker
input, warned that it was unused, and succeeded — and the failure surfaced one
step later naming an OUTPUT rather than the input that was never read.
Every other module compile in mcpp already states the language explicitly
(`BmiTraits::moduleInterfaceLangFlag` — `/interface /TP`, `-x c++-module`,
`-x c++`). The host-module path was the one place still letting the driver guess,
and it is the fourth place this round where an extension had to stop being a
proxy for a decision.
Two changes, both small: the language flag goes in positionally before the input
for the clang and GCC branches (MSVC already passed `/interface` + `/TP`), and
the precompile now checks that a BMI actually appeared. A step that can succeed
without producing its output must say so where the input is still in scope,
otherwise the next step blames a missing file.
Reproduced locally under `MCPP_TOOLCHAIN=llvm@22.1.8` — which is what makes this
a fix rather than a guess at what macOS meant — and 189/193, the existing
host-module tests, still pass on both toolchains.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements the plan in
.agents/docs/2026-08-18-open-items-analysis-and-axis-discipline.md. The fouritems 2026.8.18.2 left open turned out to be three instances of one mistake and
one deliberate break.
cl.execonsumption: already implemented, never verified, and the docs said ❌The dialect-neutral
[target.<pred>.runtime]mechanism shipped last round. Thelimits table in docs/12 still said
❌ see belowwhile the prose beside itdescribed the solution — which is precisely what the project's own docs style
rule about matching claims to evidence exists to prevent.
Verified from both ends now:
test_link_intent_spelling.cpp— portable, runs on all three platforms;msvc@system. That is the criterion: if theneutral form were ignored and the ldflags applied, a clang consumer would still
link, so only
cl— which stops at the first-L— can prove anything. Thetest also asserts the leg's
-Lis absent from the generated graph, because"it linked" could otherwise mean cl was merely tolerant.
Last round fixed three flags that were keyed on the dialect and belonged to the
target. It is tempting to call
if (isMsvcDialect) return PeMsvca fourth. Itis not, and the difference is now written down in docs/08 §7.5:
-fPIC--out-implib//IMPLIB:,/DEF:-L//LIBPATH:Clang targeting the MSVC ABI separates all three — GNU dialect, MSVC-ABI objects,
PE image — and asking the wrong one fails differently each time. The table exists
so the next person adding a flag does not rediscover this one platform at a time.
The lib-root decision was only half fixed
Last round fixed the resolver the packer uses and left two callers behind:
validate.cppmwarned that an.ixxproject's lib root was missing when itwas right there;
prepare.cppmhanded a host-module dependency a path to a file that does notexist.
Control-verified against the released 2026.8.18.2 binary on the same fixture:
e2e 263 pins one assertion per CALL SITE, with a negative control — a project
with genuinely no lib root must still be warned about, or the test would pass
against a validator that simply stopped checking.
Two more the work turned up
module_extensionsentry.Its source globs are emptied on purpose — that is what keeps a build rule out
of the consumer's binary — so every declared extension looks dead, and the
rule's author sees a warning about their own correct manifest, in every
consumer's build, with nothing to fix.
dllimportlimit is reproducible instead of prose (e2e258). The assertion is the DIFFERENCE between the two spellings, not an error
text: which form the failure takes depends on the toolset, and pinning one
would make the test a hostage to it. If reading exported data without
dllimportever starts working, the test says so and names the documentationas the thing to fix.
Docs
docs/05 gains the rule the previous round's hard error follows from — every
entry
sourcesmatches must produce an object that gets linked — so it reads asa consequence rather than a new prohibition. The 简体中文 page was missing even
the
sources = []note; both are there now.Notes for review
22_doctor_cache_publish, which the previous RELEASE binary fails identicallyon this machine.
error).
# requires: msvc— Windows CI is the onlyplace they run, and the merge criterion is that they are seen to RUN there.