Repository navigation
Three findings left open by #641/#642: static dependencies of a shared package (F1), the payload's Mach-O private libc++ (F2), the std module initialiser in every image (F3) #646
Description
Activity
Implemented in 2026.9.16.1 (#650). The triage record is
.agents/docs/2026-09-16-646-649-four-issues-by-home.mdand the plan with the ledger and its readings is.agents/docs/2026-09-16-646-649-implementation-plan.md.What was measured, and where it differs from the report. The 882 interposed symbols are not the std module initialiser. On ELF the default role contracts gave the program
self-containedand the plan-built shared librarytoolchain-coupled, so one process carried two C++ runtimes; on the llvm toolchain the same shape aborts withstd::bad_cast(exit 134) before the report's symptom is reached, and on gcc it runs with roughly 900 interposed libstdc++ symbols. The duplicate report itself was also wrong in the other direction:STB_GNU_UNIQUEis vague linkage, so a definition both images take from one build object, and the one the toolchain's own runtime provides for the std module initialiser, were reported as conflicts although the loader unifies them by design.What changed.
- Every C++ image a plan loads into one process now shares one C++ runtime. A program or test over a plan-built C++ shared library takes that library's contract; a program that states
self-containedover such a library is refused withprogram-cxx-runtime-split, which names the library and the two contracts. (e2e 700) - Symbol provision reports real duplicates only: a definition that both images take from one build object, and the toolchain's std module initialiser, are no longer findings. (e2e 701)
- A static package reached from several shared images is refused where the link or the load cannot succeed (Mach-O, PE, the Android
approw) with thelinkage = "shared"remedy; on the remaining ELF rows it builds as before and reportsbuild/static-placement. (e2e 702, 307) - On Mach-O the private libc++ default is unchanged, and a build whose images would not share one C++ runtime identity now says so (
build/cxx-runtime-identity).
What was not changed, and why. The Mach-O
-load_hiddendefault stays: a plan's images each carry a private libc++, which is what makes a packed application independent of the host's runtime. The engine now states the consequence rather than silently accepting it.Verification. A SubOS sandbox with CN mirrors for both xlings and mcpp runs the scenarios against the published engine, plugins and index:
version=2026.9.16.1 fails=0, seventeen assertions, of which this issue's arethe program runs on one C++ runtimeandlibfw.so loads on its own (RTLD_NOW): x is inside it. The same script against 2026.9.15.2 readsfails=10, so each assertion measures the change rather than the setup.- Every C++ image a plan loads into one process now shares one C++ runtime. A program or test over a plan-built C++ shared library takes that library's contract; a program that states
- added a commit that references this issue
on Sep 16, 2026 Closing. Every item of this issue was delivered in 2026.9.16.1 (#650) — with
mcpp:plugins0.12.0 for the plugin-side members — and the close-out record merged as #653.The verification is the part worth keeping: a SubOS sandbox with CN mirrors for both xlings and mcpp ran the scenarios against the published engine, plugins and index and read
version=2026.9.16.1 fails=0. The same script against 2026.9.15.2 readsfails=10, so each assertion measures this change rather than the setup it runs in. Two follow-ups also landed and are not open work: #651 (two spellings of one version constraint are one source, found by the review before the release) and the sandbox reading itself.Records:
.agents/docs/2026-09-16-646-649-four-issues-by-home.md(triage) and.agents/docs/2026-09-16-646-649-implementation-plan.md(plan, ledger, readings).Where the delivery contradicts the report, the comment above says so — those corrections are the reason to read it rather than this line.
The work on #641 and #642 (#644, released as 2026.9.15.2; record merged as #645) found three problems that were recorded and deliberately left out of scope. This issue keeps them visible until each has its own record and decision. The sources are:
.agents/docs/2026-09-15-641-642-link-forms-standards-and-paths.md: ledger rows F1 and F2, §1 item 6, §3.3.3, §3.3.5 and §6 (reading M3b).agents/docs/2026-09-15-641-642-implementation-plan.md: §1.9 (F3) and §9.5 (what remains open).agents/docs/2026-09-15-641-642-probes.sh: probe M3bF1. A shared package over a static package: the static dependency's objects are not linked into the shared library
Status: defect, measured on ELF. A design decision is needed.
A dependency's shared library is linked from its own package's objects only. When that package depends on a static package, the static package's objects go into the program and not into the shared library. This is the same assembly that lost the C++ runtime in #641 item 5. For the runtime, the refusal and
cxx_runtime = { shared = "self-contained" }now cover the case. Every other static dependency of a shared package is still affected.Reading (M3b, Linux x86_64).
xis a static C package providingx_answer.fwis akind = "shared"package that calls it, and the root program depends onfw.The build succeeds and the program runs only because ELF lets
libfw.sobind to the executable's copy. The same manifest cannot work on the other formats (read, not measured):-undefined errorby default, so the dylib link fails.lib<fw>.sobeforelib<app>.so, and bionic binds immediately, so the ELF binding fails at load time.Question to decide. Where does a static package reachable through a shared image get linked?
linkage_formexists to decide.The C++ runtime is the member of this class that every C++ image reaches, and it was decided through the runtime contract rather than through placement (§3.3.4, D1). The general case has no decision yet.
Suggested criterion. The M3b manifest builds with
x_answerdefined inside the shared image, or is refused before compiling with a message naming the remedy. The check should cover ELF and at least one of Mach-O or PE, since ELF alone hides the defect.F2. The payload's Mach-O shared default gives each dylib a private libc++, which probably breaks catching std exceptions by type across images
Status: probable defect, inferred and not measured. It affects builds that work today, so it needs a measurement before any change.
default_contract(Role::SharedLibrary, Format::MachO)(src/build/distribution.cppm:214-251) returnsSelfContained. As a result, every dylib embeds the payload'slibc++.athrough-Wl,-load_hidden,<archive>(PR #117). The comment there describes this behaviour as correct and unchanged.The type information of an exception class whose key function is in libc++'s sources is a strong definition with default visibility.
std::runtime_erroris one such class. libc++ therefore marks the type information unique and compares it by address.-load_hiddenthen gives each image its own unique copy. libc++'s own notes (include/typeinfo,NonUniqueARMRTTIBit) say that across linked image boundaries such types "are thus considered different types". That includes arm64 Apple. By this reading, astd::runtime_errorthrown inside a dylib is not caught bycatch (const std::runtime_error&)in the program.What has been measured. The same property was measured for the graph's runtime (
llvm.libcxxundercxx_runtime = { shared = "self-contained" }):run exit=0 output: fw-3 | runtime_error NOT matched by type | fw_error caught by typellvm.libcxx's CI on the Link forms, standard levels and path lengths: #641 and #642 (2026.9.15.2) #644 branch:runtime_error not matched by its classThe payload's origin, which is the default for every Mach-O shared library today, has not been measured. The record also did not look at whether the PE default (a private runtime per DLL) behaves the same way.
Suggested criterion. Run the M5 probe on macos-15 against the payload's runtime with no
cxx_runtimekey. The program should catch astd::runtime_errorthrown in the dylib by its class, or the default should change. Because the change affects working builds, the decision belongs in its own record. The measurement can use a temporary pull request, as the #644 macOS readings did.F3. Every C++ image links the std module's initialiser, so
symbol_provisionreports it as provided twiceStatus: false report, present before #641/#642.
The std module object (
std.o, andstd.compat.owhen used) is linked into every C++ image. A program that importsstdand uses a C++ shared library that also importsstdtherefore defines the module initialisers in both images. Thebuild/symbol-provisioncheck (src/build/ninja_backend.cppm:3283, advisory) reports this as symbols provided twice.cxx_runtime = { shared = "self-contained" }), it reports two:_ZGIW3stdand_ZGIW3stdW6compat.The remaining two symbols are expected given the current assembly. Either the std module object should have one owner in a graph that contains shared images, or the check should recognise module initialisers that were linked privately on purpose. Until one of these is done, the diagnostic tells the user about a problem that is not a defect in their project. That makes real findings from the same check harder to trust.
Suggested criterion. For a program over a C++ shared library where both import
std, the build reports nobuild/symbol-provisionfinding under both the graph's runtime and the payload's runtime. A real duplicate provision in the same build is still reported.