From acaae883a9efaec814a3a5d4ffe86ceaf618ef8a Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Thu, 10 Sep 2026 00:59:53 +0800 Subject: [PATCH] feat(compat.sycl-runtime): the OpenCL adapter the payload ships is served The package declared it did not serve `libOpenCL.so.1`, and that decision was made from a real measurement and was still wrong. WHAT THE MEASUREMENT SAID. Declaring `compat:opencl` made the farm test pass with every member loading -- and pass with `libnvidia-ml.so.1` REMOVED from the farm. `compat:opencl` depends on `compat:opencl-runtime`, whose farm mirrors the host's NVIDIA OpenCL family and therefore carries NVML, so the new dependency was satisfying the need the rest of this change exists to satisfy, and the criterion could no longer fail. WHY THAT REASON EXPIRED. The masking was a property of a criterion that measured the PROCESS. `tests/farm.cpp` was rewritten -- because of that very measurement -- to read each member's DT_NEEDED against the farm alone. Once nothing on the search path can answer for the farm, the only objection left was the size of the surface, and a back end whose adapter the payload ships is not something a runtime adapter should leave permanently unreachable. Measured with the edge declared: the farm test passes, and with NVML removed it still fails naming `libur_adapter_cuda.so.0`. The decision is reversible because the criterion is no longer maskable. `libOpenCL.so.1` stays named in the test with its meaning changed: not "this package does not serve it" but "a declared dependency of this package provides it". mcpp deploys `compat:opencl`'s shared library beside the consumer's executable and `$ORIGIN` finds it there. Everything not on that short list must be in the farm, which is what keeps the self-sufficiency assertion exact without reopening the process to answer for it. --- pkgs/c/compat.sycl-runtime.lua | 45 ++++++++++++---------- tests/examples/sycl-runtime/tests/farm.cpp | 33 ++++++++++------ 2 files changed, 46 insertions(+), 32 deletions(-) diff --git a/pkgs/c/compat.sycl-runtime.lua b/pkgs/c/compat.sycl-runtime.lua index 9f42de1..b1bef00 100644 --- a/pkgs/c/compat.sycl-runtime.lua +++ b/pkgs/c/compat.sycl-runtime.lua @@ -128,29 +128,34 @@ package = { c_standard = "c11", sources = { "mcpp_generated/sycl_runtime_empty.c" }, targets = { ["sycl_runtime"] = { kind = "lib" } }, - -- THE OTHER BACK END THE PAYLOAD SHIPS AN ADAPTER FOR IS DELIBERATELY - -- NOT SERVED HERE, and saying so is the point: it was unstated before, - -- which is the same condition that let the CUDA one break silently. + -- THE OTHER BACK END THE PAYLOAD SHIPS AN ADAPTER FOR. -- - -- `libur_adapter_opencl.so.0` has `libOpenCL.so.1` in its DT_NEEDED and - -- nothing on an mcpp artifact's search path provides it, so the OpenCL - -- back end of a SYCL program does not load. `compat:opencl` builds that - -- loader and would fix it in one line here. + -- `libur_adapter_opencl.so.0` has `libOpenCL.so.1` in its DT_NEEDED, + -- and nothing on an mcpp artifact's search path provided it, so the + -- OpenCL back end of a SYCL program did not load. That is the same + -- defect as the missing driver soname one API over, and it was + -- invisible for the same reason: an adapter that fails to load is + -- reported by nothing. -- - -- MEASURED REASON FOR NOT WRITING THAT LINE. `compat:opencl` depends in - -- turn on `compat:opencl-runtime`, a farm of the HOST's proprietary - -- OpenCL driver family, so the edge would put a machine-specific vendor - -- surface into every SYCL project. It also carries `libnvidia-ml.so.1`, - -- and with the edge declared it satisfied the CUDA adapter's need for - -- NVML -- which made the farm below look correct while it was not. A - -- dependency that hides the defect the package next to it is fixing is - -- the wrong dependency. + -- A DEPENDENCY RATHER THAN A FARM ENTRY. Unlike the driver, this one is + -- not a host file that may not be redistributed: `compat:opencl` builds + -- the Khronos ICD loader from source with the canonical soname, and it + -- reaches the machine's own drivers through `compat:opencl-runtime`. + -- Farming a copy here would be a second answer to a question this index + -- already answers. -- - -- A project that wants OpenCL devices from SYCL writes - -- `[dependencies.compat] opencl = "2026.05.29"` in its own manifest, - -- where the vendor surface is its choice. `tests/farm.cpp` records the - -- adapter as expected-unserved so the omission stays visible. - deps = {}, + -- WHAT WAS BRIEFLY WRITTEN HERE INSTEAD, AND WHY IT WAS WRONG. A draft + -- declared the adapter unserved, on the ground that `compat:opencl` + -- drags a vendor surface in and that its farm -- which carries + -- `libnvidia-ml.so.1` -- made this package's own farm look correct + -- while it was missing NVML. The second half was true of a criterion + -- that measured the PROCESS: `tests/farm.cpp` now reads each member's + -- DT_NEEDED against this farm alone, so nothing else on the search path + -- can hide a gap in it. With that fixed, the remaining objection was + -- only the size of the surface, and a back end the payload ships an + -- adapter for is not something a runtime adapter should leave + -- unreachable. + deps = { ["compat.opencl"] = "2026.05.29" }, runtime = { library_dirs = { "mcpp_generated/sycl_runtime/lib" }, capabilities = { "sycl.runtime" }, diff --git a/tests/examples/sycl-runtime/tests/farm.cpp b/tests/examples/sycl-runtime/tests/farm.cpp index 147d371..29105bc 100644 --- a/tests/examples/sycl-runtime/tests/farm.cpp +++ b/tests/examples/sycl-runtime/tests/farm.cpp @@ -44,12 +44,15 @@ // consumer. The entry is present, so the package did its part; the target // is the machine's answer. // -// * A SONAME this package states it does not serve. Today that is -// `libOpenCL.so.1`: the payload ships an OpenCL adapter, and serving it -// would mean depending on `compat:opencl`, which drags the host's -// proprietary OpenCL driver farm into every SYCL project. The recipe -// records that decision; this list is the half that makes it visible, and -// a project that wants the back end declares `compat:opencl` itself. +// * A SONAME this package gets from a DECLARED DEPENDENCY rather than from +// its own farm. Today that is `libOpenCL.so.1`: `compat:sycl-runtime` +// depends on `compat:opencl`, whose shared library is deployed beside the +// consumer's executable and is found there through `$ORIGIN`. The name is +// listed here rather than resolved through the process, because resolving +// through the process is what let another farm answer for +// `libnvidia-ml.so.1` and hide the gap this test exists to catch. A short +// list that a reader can check is the price of a criterion that cannot be +// masked. // // Which devices exist is deliberately NOT asserted: that is the machine's // answer, not this package's. @@ -83,9 +86,14 @@ bool provided_by_the_artifact(const std::string& soname) { || soname.rfind("ld-linux", 0) == 0; } -// Stated, not served. See the header for the reason and for what a project -// that wants it writes instead. -bool declared_unserved(const std::string& soname) { +// Served by a declared dependency of this package, not by its farm. +// +// `compat:opencl` builds the Khronos ICD loader with the canonical soname and +// mcpp deploys it beside the consumer's executable, where `$ORIGIN` finds it. +// Naming it here keeps the farm's self-sufficiency assertion exact: everything +// NOT on this list must be in the farm, and no other directory on the search +// path can answer for it. +bool served_by_a_declared_dependency(const std::string& soname) { return soname == "libOpenCL.so.1"; } @@ -258,9 +266,10 @@ int main() { name.c_str(), soname.c_str()); continue; } - if (declared_unserved(soname)) { - std::printf("note %s needs %s, which this package states it " - "does not serve\n", name.c_str(), soname.c_str()); + if (served_by_a_declared_dependency(soname)) { + std::printf("note %s needs %s, which a declared dependency of " + "this package provides\n", + name.c_str(), soname.c_str()); continue; } std::printf("FAIL %s needs %s, which the farm does not carry\n",