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
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 M3b
F1. 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). x is a static C package providing x_answer. fw is a kind = "shared" package that calls it, and the root program depends on fw.
M3b exit=0
M3b libfw.so undefined x_answer=1 program exports x_answer=1
M3b run exit=0
The build succeeds and the program runs only because ELF lets libfw.so bind to the executable's copy. The same manifest cannot work on the other formats (read, not measured):
- Mach-O: a dylib links with
-undefined errorby default, so the dylib link fails. - PE: every import is resolved when the DLL is linked, so the DLL link fails.
- Android: the Java host loads
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?
- A static package reachable only through a shared image belongs in that image.
- A static package reachable from both the image and the program is an instance of the "one library, one provider, one form" conflict that
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_answer defined 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) returns SelfContained. As a result, every dylib embeds the payload's libc++.a through -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_error is one such class. libc++ therefore marks the type information unique and compares it by address. -load_hidden then 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, a std::runtime_error thrown inside a dylib is not caught by catch (const std::runtime_error&) in the program.
What has been measured. The same property was measured for the graph's runtime (llvm.libcxx under cxx_runtime = { shared = "self-contained" }):
- Linux x86_64 (M5):
run exit=0 output: fw-3 | runtime_error NOT matched by type | fw_error caught by type - macOS arm64, through
llvm.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 class
The 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_runtime key. The program should catch a std::runtime_error thrown 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_provision reports it as provided twice
Status: false report, present before #641/#642.
The std module object (std.o, and std.compat.o when used) is linked into every C++ image. A program that imports std and uses a C++ shared library that also imports std therefore defines the module initialisers in both images. The build/symbol-provision check (src/build/ninja_backend.cppm:3283, advisory) reports this as symbols provided twice.
- Under gcc on 2026.9.15.1, the same shape reported 882 symbols.
- Under the private runtime copy added by M3 (
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 no build/symbol-provision finding under both the graph's runtime and the payload's runtime. A real duplicate provision in the same build is still reported.
Activity
Mach-O: the measured runtime identity split is reported; the stream-i…
One C++ runtime per process, the graph and pack facts, bounded offlin…
Sunrisepeak commentedon Sep 16, 2026
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.
self-containedover such a library is refused withprogram-cxx-runtime-split, which names the library and the two contracts. (e2e 700)approw) with thelinkage = "shared"remedy; on the remaining ELF rows it builds as before and reportsbuild/static-placement. (e2e 702, 307)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.#646 to #649 close: the review before the release, the sandbox readin…
speak-agent commentedon Sep 20, 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.