Skip to content

fix: cl.exe consumption verified, the flag-axis rule written down, and the lib-root decision finished - #454

Merged
Sunrisepeak merged 3 commits into
mainfrom
fix/cl-exe-consumption
Aug 18, 2026
Merged

fix: cl.exe consumption verified, the flag-axis rule written down, and the lib-root decision finished#454
Sunrisepeak merged 3 commits into
mainfrom
fix/cl-exe-consumption

Conversation

@Sunrisepeak

Copy link
Copy Markdown
Member

Implements the plan in
.agents/docs/2026-08-18-open-items-analysis-and-axis-discipline.md. The four
items 2026.8.18.2 left open turned out to be three instances of one mistake and
one deliberate break.

cl.exe consumption: already implemented, never verified, and the docs said ❌

The dialect-neutral [target.<pred>.runtime] mechanism shipped last round. The
limits table in docs/12 still said ❌ see below while the prose beside it
described 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;
  • e2e 262, whose consumer pins msvc@system. That is the criterion: if the
    neutral 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. The
    test also asserts the leg's -L is absent from the generated graph, because
    "it linked" could otherwise mean cl was merely tolerant.

⚠️ A finding that runs against last round

Last round fixed three flags that were keyed on the dialect and belonged to the
target. It is tempting to call if (isMsvcDialect) return PeMsvc a fourth. It
is not
, and the difference is now written down in docs/08 §7.5:

axis the question examples
target format what image is produced -fPIC
target ABI which linker consumes this --out-implib / /IMPLIB:, /DEF:
dialect which program mcpp invokes -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.cppm warned that an .ixx project's lib root was missing when it
    was right there;
  • prepare.cppm handed a host-module dependency a path to a file that does not
    exist.

Control-verified against the released 2026.8.18.2 binary on the same fixture:

warning: src/mathkit.cppm: lib target without conventional lib root
         'src/mathkit.cppm' (create the file or set [lib].path)

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

  • 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, in every
    consumer's build, with nothing to fix.
  • The data-symbol dllimport limit is reproducible instead of prose (e2e
    258). 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

docs/05 gains the rule the previous round's hard error follows from — every
entry sources matches must produce an object that gets linked
— so it reads as
a consequence rather than a new prohibition. The 简体中文 page was missing even
the sources = [] note; both are there now.

Notes for review

  • Local: 239 passed, 1 failed, 15 skipped; the failure is
    22_doctor_cache_publish, which the previous RELEASE binary fails identically
    on this machine.
  • Built with both gcc and clang (a name attached to two modules is a clang-only
    error).
  • e2e 262 and the extended 258 are # requires: msvc — Windows CI is the only
    place they run, and the merge criterion is that they are seen to RUN there.

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.
@Sunrisepeak
Sunrisepeak merged commit f0de3ef into main Aug 18, 2026
19 checks passed
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.

2 participants