Skip to content

feat(compat): the driver farm enumerates the sentinel instead of naming a file - #375

Merged
Sunrisepeak merged 1 commit into
mainfrom
feat/driver-farm-enumerates-the-sentinel
Sep 9, 2026
Merged

feat(compat): the driver farm enumerates the sentinel instead of naming a file#375
Sunrisepeak merged 1 commit into
mainfrom
feat/driver-farm-enumerates-the-sentinel

Conversation

@Sunrisepeak

Copy link
Copy Markdown
Member

What

compat.sycl-runtime and compat.cuda-driver build their driver farm by enumerating the sentinel's directory instead of linking one hand-written libcuda.so.1, and pin xim:libcuda-host-link@0.0.2 -- the version at which that package answers for a set of sonames (openxlings/xim-pkgindex#796).

New version keys 2026.09.10 for both. compat.cuda-runtime is untouched: its header states it receives no new versions.

Why

libur_adapter_cuda.so.0 has libnvidia-ml.so.1 in DT_NEEDED beside libcuda.so.1. The farm did not carry it, an mcpp artifact's private loader consults no host directory, so the adapter did not load, the CUDA back end disappeared, and the program terminated with exit code 134 and no exception text: mcpp#596.

Every directory this farm draws from was enumerated except the driver's, and the hand-written half is the half that was wrong. Which driver libraries exist is the sentinel's question; a farm that reads its directory cannot disagree with it.

The criterion, which was green for the whole life of the defect

tests/examples/sycl-runtime/tests/farm.cpp asserted three hand-written sonames against a farm of twenty-five members. The two broken members were not among the three.

Enumerating the members and dlopening each is still not enough, and this was measured rather than reasoned: dlopen measures the process, whose search path holds more than this package put there. With an OpenCL dependency briefly declared, compat:opencl-runtime's farm supplied libnvidia-ml.so.1, so removing NVML from this farm changed nothing and the test passed on a farm that was missing it.

The test now reads each member's DT_NEEDED directly and resolves it against the farm alone, separating three states:

state meaning verdict
resolved in the farm, or one of the three the artifact already loaded nothing to say silent
present as an entry but dangling the machine has no driver; the sentinel's self-heal shape note
absent packaging gap fail

Both denominators are printed (members, walked), because a farm that failed to build enumerates nothing and every per-member assertion then passes.

Measured, all three legs on an RTX 4080 host:

repaired farm, driver present   PASSED   members: 26, walked: 26
NVML removed from the farm      FAILED   libur_adapter_cuda.so.0 needs libnvidia-ml.so.1, which the farm does not carry
driver links made dangling      PASSED   members: 26, walked: 24, 2 noted

libOpenCL.so.1

compat.sycl-runtime now states that it does not serve it. The payload ships an OpenCL adapter, compat:opencl would satisfy it in one line, and that line was written and then removed: compat:opencl depends in turn on compat:opencl-runtime, so the edge puts the host's proprietary OpenCL driver farm into every SYCL project -- and it is what masked the defect above. A project that wants the back end declares compat:opencl in its own manifest.

The omission was unstated before, which is the same condition that let the CUDA one break silently.

…ng a file

`compat.sycl-runtime` and `compat.cuda-driver` built their driver farm from
one hand-written name, `libcuda.so.1`, while enumerating every other
directory they draw from. The hand-written half is the half that was wrong:
`libur_adapter_cuda.so.0` has `libnvidia-ml.so.1` in DT_NEEDED as well, the
farm did not carry it, the adapter did not load, the CUDA back end
disappeared, and the program terminated with exit code 134 and no exception
text (mcpp#596).

Which driver libraries exist is the sentinel's question. A farm that reads
its directory cannot disagree with it; a farm that names a file already had.
Both recipes now link every versioned soname the sentinel publishes and pin
`xim:libcuda-host-link@0.0.2`, the version at which it answers for a set.

Old version keys are kept. What the new keys buy is a reinstall rather than
a different recipe: there is one install() per file and neither reads
pkginfo.version(), so an old pin installed today builds the current farm.

`compat.sycl-runtime` also states, for the first time, that it does not
serve `libOpenCL.so.1`. The payload ships an OpenCL adapter and
`compat:opencl` would satisfy it in one line, but that package depends in
turn on the host's proprietary OpenCL driver farm -- a machine-specific
vendor surface in every SYCL project -- and it supplies `libnvidia-ml.so.1`,
which made the farm above look correct while it was not. A project that
wants the back end declares `compat:opencl` itself.

The test is rewritten from three hand-written dlopens to a DT_NEEDED closure
walk over every farm member. Three names against twenty-six members is why
this criterion was green for the whole life of the defect. dlopen of every
member is not enough either: it measures the process, whose search path
holds more than this package put there, and measurement showed another farm
supplying the missing soname. The walk reads the farm alone and separates
three states -- resolved, present but dangling (a machine with no driver),
absent (a packaging gap).

Measured, all three legs:

  repaired farm, driver present    PASSED   26 members, 26 walked
  NVML removed from the farm       FAILED   names the member and the soname
  driver links made dangling       PASSED   24 of 26 walked, 2 noted
@Sunrisepeak
Sunrisepeak merged commit dc0f9bd into main Sep 9, 2026
14 checks passed
Sunrisepeak added a commit to openxlings/xim-pkgindex that referenced this pull request Sep 9, 2026
…recorded beside the list (#798)

`compat.cuda-driver` records a measurement that decided libnvidia-ptxjitcompiler
is unnecessary, and mcpplibs/mcpp-index#375 says that result belongs beside the
list that decides the set rather than beside one consumer of it. That sentence
was true about where it should live and false about where it was: this is the
half that moves it.

A farm that mirrors this directory inherits both the additions and the
omissions, so an omission whose reason lives in one consumer is an omission the
next consumer re-litigates.

Co-authored-by: sunrisepeak <x.d2learn.org@gmail.com>
Sunrisepeak pushed a commit to mcpp-community/mcpp that referenced this pull request Sep 9, 2026
…er process (2026.9.10.1)

A SYCL project built cleanly and then terminated with exit code 134 and no
exception text (#596). The trigger was in the ecosystem -- an adapter's farm
was missing one driver library -- but the reasons it presented as a silent
abort are two independent gaps in mcpp, and both are repaired here.

THE SURFACE NOTHING WALKED. `resolve_runtime_closure` is seeded with the
artifact and follows DT_NEEDED. A library a package publishes through
`runtime.library_dirs` exists precisely because something will dlopen it, so
no link edge names it and it is outside that closure BY CONSTRUCTION. Measured:
a farm of twenty-five libraries, two of which could not load at all, while the
build reported nothing -- because nothing had asked.

`inspect_dlopen_surface` reads each such library's own DT_NEEDED and resolves
it against the search path the artifact actually carries, separating three
states: resolved, present as a dangling link (the machine has no driver), and
absent (a packaging gap). Only the third is reported, and it is a warning: a
dangling link is the documented shape of a host driver that is not installed,
and failing there would turn a supported configuration into a failed build.
`runtime.dlopen_surface` in resolution.json carries the findings and both
denominators -- a surface that failed to build enumerates nothing, and "no
findings" must not read like "nothing was examined".

TWO UNWINDERS IN ONE PROCESS. A lane whose device compiler is configured
against libstdc++ puts libstdc++ on the link line while the artifact links
libc++ statically. `hide_static_cxx_runtime` skipped executables on the
premise that "ld exports only what a loaded object references, and mcpp passes
no -rdynamic" -- a correct premise with a wrong conclusion, because a loaded
libstdc++ DOES reference them. Measured: 89 exported symbols, 68 of them also
defined by libstdc++ or libgcc_s.

Ten of libgcc's eighteen `_Unwind_*` entry points came from the artifact and
eight stayed in libgcc_s, including the accessors libstdc++'s personality
routine calls. It read an LLVM libunwind context through libgcc's accessors,
recovered a meaningless IP, found no landing pad, and `__cxa_call_terminate`
ran past a handler three frames up; the verbose terminate handler's rethrow
then terminated as well, which is why nothing was printed.

Such a link now takes `--unwindlib=libgcc` and hides the static archives'
symbols. libgcc_s is in the process either way -- libstdc++ needs it -- so
this names a library rather than adding one, and the C++ runtime stays
embedded. A link with no second runtime on it is byte-for-byte unchanged.

Measured on one machine, same source, both legs:

  before   exit 134, no output
  after    `sycl: no usable device: ...` / `device unavailable`, exit 1
  device   `12 24 36 48` in both

`-Wl,--exclude-libs` alone was written here as a prediction from the mechanism
and then measured: exit 139 instead of 134. Hiding the exports makes libstdc++
bind to libgcc while the artifact's own libc++abi still calls its statically
linked libunwind, so the mismatch reproduces in the other direction. The
reason is recorded in the design record so the flag is not proposed again.

The duplicate-symbol warning now states that consequence when the conflicting
set includes the unwinder family, instead of describing it as one more copy
that is never called.

One documented claim was refuted along the way: the SYCL example states that a
missing device image is the one failure its island cannot turn into a return
code. Measured by compiling it for sm_90 and running on an sm_89 device -- it
was not outside the catches; no catch worked. The example's comment and README
are corrected.

Design record: .agents/docs/2026-09-09-dlopen-surface-and-two-unwinders.md
Ecosystem halves: openxlings/xim-pkgindex#796, mcpplibs/mcpp-index#375
Sunrisepeak pushed a commit to mcpp-community/mcpp that referenced this pull request Sep 9, 2026
…er process (2026.9.10.1)

A SYCL project built cleanly and then terminated with exit code 134 and no
exception text (#596). The trigger was in the ecosystem -- an adapter's farm
was missing one driver library -- but the reasons it presented as a silent
abort are two independent gaps in mcpp, and both are repaired here.

THE SURFACE NOTHING WALKED. `resolve_runtime_closure` is seeded with the
artifact and follows DT_NEEDED. A library a package publishes through
`runtime.library_dirs` exists precisely because something will dlopen it, so
no link edge names it and it is outside that closure BY CONSTRUCTION. Measured:
a farm of twenty-five libraries, two of which could not load at all, while the
build reported nothing -- because nothing had asked.

`inspect_dlopen_surface` reads each such library's own DT_NEEDED and resolves
it against the search path the artifact actually carries, separating three
states: resolved, present as a dangling link (the machine has no driver), and
absent (a packaging gap). Only the third is reported, and it is a warning: a
dangling link is the documented shape of a host driver that is not installed,
and failing there would turn a supported configuration into a failed build.
`runtime.dlopen_surface` in resolution.json carries the findings and both
denominators -- a surface that failed to build enumerates nothing, and "no
findings" must not read like "nothing was examined".

TWO UNWINDERS IN ONE PROCESS. A lane whose device compiler is configured
against libstdc++ puts libstdc++ on the link line while the artifact links
libc++ statically. `hide_static_cxx_runtime` skipped executables on the
premise that "ld exports only what a loaded object references, and mcpp passes
no -rdynamic" -- a correct premise with a wrong conclusion, because a loaded
libstdc++ DOES reference them. Measured: 89 exported symbols, 68 of them also
defined by libstdc++ or libgcc_s.

Ten of libgcc's eighteen `_Unwind_*` entry points came from the artifact and
eight stayed in libgcc_s, including the accessors libstdc++'s personality
routine calls. It read an LLVM libunwind context through libgcc's accessors,
recovered a meaningless IP, found no landing pad, and `__cxa_call_terminate`
ran past a handler three frames up; the verbose terminate handler's rethrow
then terminated as well, which is why nothing was printed.

Such a link now takes `--unwindlib=libgcc` and hides the static archives'
symbols. libgcc_s is in the process either way -- libstdc++ needs it -- so
this names a library rather than adding one, and the C++ runtime stays
embedded. A link with no second runtime on it is byte-for-byte unchanged.

Measured on one machine, same source, both legs:

  before   exit 134, no output
  after    `sycl: no usable device: ...` / `device unavailable`, exit 1
  device   `12 24 36 48` in both

`-Wl,--exclude-libs` alone was written here as a prediction from the mechanism
and then measured: exit 139 instead of 134. Hiding the exports makes libstdc++
bind to libgcc while the artifact's own libc++abi still calls its statically
linked libunwind, so the mismatch reproduces in the other direction. The
reason is recorded in the design record so the flag is not proposed again.

The duplicate-symbol warning now states that consequence when the conflicting
set includes the unwinder family, instead of describing it as one more copy
that is never called.

One documented claim was refuted along the way: the SYCL example states that a
missing device image is the one failure its island cannot turn into a return
code. Measured by compiling it for sm_90 and running on an sm_89 device -- it
was not outside the catches; no catch worked. The example's comment and README
are corrected.

Design record: .agents/docs/2026-09-09-dlopen-surface-and-two-unwinders.md
Ecosystem halves: openxlings/xim-pkgindex#796, mcpplibs/mcpp-index#375
Sunrisepeak pushed a commit to mcpp-community/mcpp that referenced this pull request Sep 9, 2026
…er process (2026.9.10.1)

A SYCL project built cleanly and then terminated with exit code 134 and no
exception text (#596). The trigger was in the ecosystem -- an adapter's farm
was missing one driver library -- but the reasons it presented as a silent
abort are two independent gaps in mcpp, and both are repaired here.

THE SURFACE NOTHING WALKED. `resolve_runtime_closure` is seeded with the
artifact and follows DT_NEEDED. A library a package publishes through
`runtime.library_dirs` exists precisely because something will dlopen it, so
no link edge names it and it is outside that closure BY CONSTRUCTION. Measured:
a farm of twenty-five libraries, two of which could not load at all, while the
build reported nothing -- because nothing had asked.

`inspect_dlopen_surface` reads each such library's own DT_NEEDED and resolves
it against the search path the artifact actually carries, separating three
states: resolved, present as a dangling link (the machine has no driver), and
absent (a packaging gap). Only the third is reported, and it is a warning: a
dangling link is the documented shape of a host driver that is not installed,
and failing there would turn a supported configuration into a failed build.
`runtime.dlopen_surface` in resolution.json carries the findings and both
denominators -- a surface that failed to build enumerates nothing, and "no
findings" must not read like "nothing was examined".

TWO UNWINDERS IN ONE PROCESS. A lane whose device compiler is configured
against libstdc++ puts libstdc++ on the link line while the artifact links
libc++ statically. `hide_static_cxx_runtime` skipped executables on the
premise that "ld exports only what a loaded object references, and mcpp passes
no -rdynamic" -- a correct premise with a wrong conclusion, because a loaded
libstdc++ DOES reference them. Measured: 89 exported symbols, 68 of them also
defined by libstdc++ or libgcc_s.

Ten of libgcc's eighteen `_Unwind_*` entry points came from the artifact and
eight stayed in libgcc_s, including the accessors libstdc++'s personality
routine calls. It read an LLVM libunwind context through libgcc's accessors,
recovered a meaningless IP, found no landing pad, and `__cxa_call_terminate`
ran past a handler three frames up; the verbose terminate handler's rethrow
then terminated as well, which is why nothing was printed.

Such a link now takes `--unwindlib=libgcc` and hides the static archives'
symbols. libgcc_s is in the process either way -- libstdc++ needs it -- so
this names a library rather than adding one, and the C++ runtime stays
embedded. A link with no second runtime on it is byte-for-byte unchanged.

Measured on one machine, same source, both legs:

  before   exit 134, no output
  after    `sycl: no usable device: ...` / `device unavailable`, exit 1
  device   `12 24 36 48` in both

`-Wl,--exclude-libs` alone was written here as a prediction from the mechanism
and then measured: exit 139 instead of 134. Hiding the exports makes libstdc++
bind to libgcc while the artifact's own libc++abi still calls its statically
linked libunwind, so the mismatch reproduces in the other direction. The
reason is recorded in the design record so the flag is not proposed again.

The duplicate-symbol warning now states that consequence when the conflicting
set includes the unwinder family, instead of describing it as one more copy
that is never called.

One documented claim was refuted along the way: the SYCL example states that a
missing device image is the one failure its island cannot turn into a return
code. Measured by compiling it for sm_90 and running on an sm_89 device -- it
was not outside the catches; no catch worked. The example's comment and README
are corrected.

Design record: .agents/docs/2026-09-09-dlopen-surface-and-two-unwinders.md
Ecosystem halves: openxlings/xim-pkgindex#796, mcpplibs/mcpp-index#375
Sunrisepeak pushed a commit to mcpp-community/mcpp that referenced this pull request Sep 9, 2026
…er process (2026.9.10.1)

A SYCL project built cleanly and then terminated with exit code 134 and no
exception text (#596). The trigger was in the ecosystem -- an adapter's farm
was missing one driver library -- but the reasons it presented as a silent
abort are two independent gaps in mcpp, and both are repaired here.

THE SURFACE NOTHING WALKED. `resolve_runtime_closure` is seeded with the
artifact and follows DT_NEEDED. A library a package publishes through
`runtime.library_dirs` exists precisely because something will dlopen it, so
no link edge names it and it is outside that closure BY CONSTRUCTION. Measured:
a farm of twenty-five libraries, two of which could not load at all, while the
build reported nothing -- because nothing had asked.

`inspect_dlopen_surface` reads each such library's own DT_NEEDED and resolves
it against the search path the artifact actually carries, separating three
states: resolved, present as a dangling link (the machine has no driver), and
absent (a packaging gap). Only the third is reported, and it is a warning: a
dangling link is the documented shape of a host driver that is not installed,
and failing there would turn a supported configuration into a failed build.
`runtime.dlopen_surface` in resolution.json carries the findings and both
denominators -- a surface that failed to build enumerates nothing, and "no
findings" must not read like "nothing was examined".

WHAT "THE SEARCH PATH THE ARTIFACT ACTUALLY CARRIES" TURNED OUT TO MEAN. Three
corrections, all of them false positives, none visible until a project with a
shared dependency was measured:

  * `$ORIGIN` leads every artifact's DT_RPATH and a shared dependency is
    deployed BESIDE the executable. `runtime_search_dirs` cannot carry that --
    `$ORIGIN` is a property of each artifact, not of the plan -- so the
    artifacts' own directories are added in `check_dlopen_surface`.
  * A plan that produces no program has no surface to judge. The adapter
    package is `kind = "lib"`; reporting a consumer's surface against an
    archive's non-existent search path named a library the consumer resolves.
  * A SONAME is not a filename. mcpp links `bin/libopencl.so` whose SONAME is
    `libOpenCL.so.1`, and the alias appears later; `mcpp test` calls the check
    twice and only the second call saw it. The SONAMEs this build produces are
    read from the objects and passed in.

TWO UNWINDERS IN ONE PROCESS. A lane whose device compiler is configured
against libstdc++ puts libstdc++ on the link line while the artifact links
libc++ statically. `hide_static_cxx_runtime` skipped executables on the
premise that "ld exports only what a loaded object references, and mcpp passes
no -rdynamic" -- a correct premise with a wrong conclusion, because a loaded
libstdc++ DOES reference them. Measured: 89 exported symbols, 68 of them also
defined by libstdc++ or libgcc_s.

Ten of libgcc's eighteen `_Unwind_*` entry points came from the artifact and
eight stayed in libgcc_s, including the accessors libstdc++'s personality
routine calls. It read an LLVM libunwind context through libgcc's accessors,
recovered a meaningless IP, found no landing pad, and `__cxa_call_terminate`
ran past a handler three frames up; the verbose terminate handler's rethrow
then terminated as well, which is why nothing was printed.

Such a link now takes `--unwindlib=libgcc` and hides the static archives'
symbols. libgcc_s is in the process either way -- libstdc++ needs it -- so
this names a library rather than adding one, and the C++ runtime stays
embedded. A link with no second runtime on it is byte-for-byte unchanged.

Measured on one machine, same source, both situations:

  before   exit 134, no output
  after    `sycl: no usable device: ...` / `device unavailable`, exit 1
  device   `12 24 36 48` in both

`-Wl,--exclude-libs` alone was written here as a prediction from the mechanism
and then measured: exit 139 instead of 134. Hiding the exports makes libstdc++
bind to libgcc while the artifact's own libc++abi still calls its statically
linked libunwind, so the mismatch reproduces in the other direction. The
reason is recorded in the design record so the flag is not proposed again.

The duplicate-symbol warning now states that consequence when the conflicting
set includes the unwinder family, instead of describing it as one more copy
that is never called.

One documented claim was refuted along the way: the SYCL example states that a
missing device image is the one failure its island cannot turn into a return
code. Measured by compiling it for sm_90 and running on an sm_89 device -- it
was not outside the catches; no catch worked. The example's comment and README
are corrected.

Design record: .agents/docs/2026-09-09-dlopen-surface-and-two-unwinders.md
Ecosystem halves: openxlings/xim-pkgindex#796 #798, mcpplibs/mcpp-index#375
#377 #378
Sunrisepeak added a commit to mcpp-community/mcpp that referenced this pull request Sep 9, 2026
…er process (2026.9.10.1) (#598)

* fix(runtime, build): the dlopen surface is walked, and one unwinder per process (2026.9.10.1)

A SYCL project built cleanly and then terminated with exit code 134 and no
exception text (#596). The trigger was in the ecosystem -- an adapter's farm
was missing one driver library -- but the reasons it presented as a silent
abort are two independent gaps in mcpp, and both are repaired here.

THE SURFACE NOTHING WALKED. `resolve_runtime_closure` is seeded with the
artifact and follows DT_NEEDED. A library a package publishes through
`runtime.library_dirs` exists precisely because something will dlopen it, so
no link edge names it and it is outside that closure BY CONSTRUCTION. Measured:
a farm of twenty-five libraries, two of which could not load at all, while the
build reported nothing -- because nothing had asked.

`inspect_dlopen_surface` reads each such library's own DT_NEEDED and resolves
it against the search path the artifact actually carries, separating three
states: resolved, present as a dangling link (the machine has no driver), and
absent (a packaging gap). Only the third is reported, and it is a warning: a
dangling link is the documented shape of a host driver that is not installed,
and failing there would turn a supported configuration into a failed build.
`runtime.dlopen_surface` in resolution.json carries the findings and both
denominators -- a surface that failed to build enumerates nothing, and "no
findings" must not read like "nothing was examined".

WHAT "THE SEARCH PATH THE ARTIFACT ACTUALLY CARRIES" TURNED OUT TO MEAN. Three
corrections, all of them false positives, none visible until a project with a
shared dependency was measured:

  * `$ORIGIN` leads every artifact's DT_RPATH and a shared dependency is
    deployed BESIDE the executable. `runtime_search_dirs` cannot carry that --
    `$ORIGIN` is a property of each artifact, not of the plan -- so the
    artifacts' own directories are added in `check_dlopen_surface`.
  * A plan that produces no program has no surface to judge. The adapter
    package is `kind = "lib"`; reporting a consumer's surface against an
    archive's non-existent search path named a library the consumer resolves.
  * A SONAME is not a filename. mcpp links `bin/libopencl.so` whose SONAME is
    `libOpenCL.so.1`, and the alias appears later; `mcpp test` calls the check
    twice and only the second call saw it. The SONAMEs this build produces are
    read from the objects and passed in.

TWO UNWINDERS IN ONE PROCESS. A lane whose device compiler is configured
against libstdc++ puts libstdc++ on the link line while the artifact links
libc++ statically. `hide_static_cxx_runtime` skipped executables on the
premise that "ld exports only what a loaded object references, and mcpp passes
no -rdynamic" -- a correct premise with a wrong conclusion, because a loaded
libstdc++ DOES reference them. Measured: 89 exported symbols, 68 of them also
defined by libstdc++ or libgcc_s.

Ten of libgcc's eighteen `_Unwind_*` entry points came from the artifact and
eight stayed in libgcc_s, including the accessors libstdc++'s personality
routine calls. It read an LLVM libunwind context through libgcc's accessors,
recovered a meaningless IP, found no landing pad, and `__cxa_call_terminate`
ran past a handler three frames up; the verbose terminate handler's rethrow
then terminated as well, which is why nothing was printed.

Such a link now takes `--unwindlib=libgcc` and hides the static archives'
symbols. libgcc_s is in the process either way -- libstdc++ needs it -- so
this names a library rather than adding one, and the C++ runtime stays
embedded. A link with no second runtime on it is byte-for-byte unchanged.

Measured on one machine, same source, both situations:

  before   exit 134, no output
  after    `sycl: no usable device: ...` / `device unavailable`, exit 1
  device   `12 24 36 48` in both

`-Wl,--exclude-libs` alone was written here as a prediction from the mechanism
and then measured: exit 139 instead of 134. Hiding the exports makes libstdc++
bind to libgcc while the artifact's own libc++abi still calls its statically
linked libunwind, so the mismatch reproduces in the other direction. The
reason is recorded in the design record so the flag is not proposed again.

The duplicate-symbol warning now states that consequence when the conflicting
set includes the unwinder family, instead of describing it as one more copy
that is never called.

One documented claim was refuted along the way: the SYCL example states that a
missing device image is the one failure its island cannot turn into a return
code. Measured by compiling it for sm_90 and running on an sm_89 device -- it
was not outside the catches; no catch worked. The example's comment and README
are corrected.

Design record: .agents/docs/2026-09-09-dlopen-surface-and-two-unwinders.md
Ecosystem halves: openxlings/xim-pkgindex#796 #798, mcpplibs/mcpp-index#375
#377 #378

* fix(plan, symbol_provision): a build program's objects stay in this package's images, and vague linkage is not a second provider

Two defects that serving the SYCL runtime's OpenCL adapter made active. Both
were latent, neither is caused by #596, and both were found by the checks this
branch sharpened rather than by reading.

A BUILD PROGRAM'S OBJECTS REACHED A DEPENDENCY'S IMAGE. `role = "object"` with
no named target attaches to "every linked image", and that read as "every link
unit in this plan" -- which includes the shared library a dependency
contributes. Measured: `compat:opencl`'s ICD loader, a C library, came out of
the link carrying `saxpy_device` and thirty-seven `sycl::` instantiations, 193
dynamic symbols where its own API is 154, and the process held two copies of
the device island. `LinkUnit::dependencyOwned` now separates the images this
package produces from the ones a dependency contributes. After: 0 sycl symbols,
154 exports, all of them its own `cl*` entry points.

Latent until a SYCL project first had a shared dependency, which is what
declaring `compat:opencl` did.

VAGUE LINKAGE IS NOT A SECOND PROVIDER. `DynamicSymbol` recorded the symbol's
TYPE and not its BINDING, so template instantiations, inline functions and
vtables -- which the C++ ABI emits into every image and expects the loader to
unify -- were counted as a second provider. `hide_static_cxx_runtime` had
already written that rule in a comment; nothing enforced it one layer up. The
binding is recorded now, weak definitions are counted rather than reported, and
the count is printed for the reason every denominator in this area is printed.

Both were invisible while the same artifact still had 68 real findings on top
of them.

One test moved from positional to designated initialisation: adding a field to
`Conflict` bound the provider list to a bool -- a string literal converts to
one, so it compiled and the list silently became empty.

* ci: an apt list this job does not use must not fail its setup

Two cross-build jobs died in setup with

    E: Failed to fetch https://dl.google.com/linux/chrome-stable/deb/.../Packages.gz  Hash Sum mismatch
    E: Some index files failed to download.

before a single byte was compiled. The runner image carries third-party apt
lists that these jobs never install from, and `apt-get update` fails the whole
run when any one of them is transiently inconsistent -- so a red that says
nothing about the change under test.

The lists a job has no use for are removed before the update. What remains is
Ubuntu's own, which is what `qemu-user-static`, `wine` and `build-essential`
come from.

Left alone: the two container-based jobs, whose images carry no third-party
lists, and the `|| { ... }` fallback beside the wine dpkg path, which already
continues past a failed update.

* ci: remove the unused apt sources by content, not by filename

The first attempt named `google-chrome.list`, and `apt-get update` failed on
the same URL: on ubuntu-24.04 the runner writes deb822 `.sources` files, so
the filename was a guess and the guess was wrong.

Selected by what the file CONTAINS now. That is the same correction
`libs/hostlib.lua` records for library directories: a layout you assume is a
layout you are wrong about on some machine.

---------

Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com>
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.

1 participant