Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,12 @@ env:
# it is the release that honours the `provision = "on-request"` entries this
# collection now declares, so a consumer that names its own tool downloads
# nothing. An older engine refuses the manifest at the package floor.
MCPP_VERSION: 2026.10.1.3
#
# 2026.10.26.1 IS WHAT 0.20.0'S SYCL LANE NEEDS (mcpp#841): a graph-supplied
# libstdc++ under clang (`gcc.libstdcxx`) and `MCPP_CXX_ABI_ROOT`. The package
# floor stays 2026.10.1.3: the rule reads the variable from the environment,
# so a gcc SYCL build still works on the older engine.
MCPP_VERSION: 2026.10.26.1
# THE RELEASE EVERY JOB FETCHES, WHICH IS NOT ALWAYS THE ONE UNDER TEST.
#
# `MCPP_VERSION` states the engine this collection needs -- the package floor
Expand All @@ -78,7 +83,7 @@ env:
#
# It moves to `MCPP_VERSION` once that release exists, so the ordinary path
# runs the engine this collection states.
MCPP_BOOTSTRAP: 2026.10.1.3
MCPP_BOOTSTRAP: 2026.10.26.1
# AN ENGINE BUILT FROM SOURCE, WHEN A DISPATCH NAMES ONE.
#
# Empty on every push and pull request, so the steps run the release above.
Expand All @@ -97,7 +102,7 @@ env:
# and says so, which the lane counts as a failure. Raising either is a
# one-line change here.
MCPPLS_VERSION: 0.0.15
MCPP_EDITOR_VERSION: 2026.10.11.1
MCPP_EDITOR_VERSION: 2026.10.26.1
# PINNED, AND WITHOUT IT THE CACHE BELOW CACHED NOTHING.
#
# A released mcpp is self-contained: with no `MCPP_HOME`, `mcpp self env`
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ module name the member declares, and configures it there.

```toml
[build-dependencies.mcpp]
plugins = { version = "0.19.1", features = ["rules-spirv"], host-module = true }
plugins = { version = "0.20.0", features = ["rules-spirv"], host-module = true }
```

```cpp
Expand Down
2 changes: 1 addition & 1 deletion docs/plugin-development.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ mcpp-index refuses such a package.

```toml
[build-dependencies.mcpp]
plugins = { version = "0.19.1", features = ["plugins-core"], host-module = true, reexport = true }
plugins = { version = "0.20.0", features = ["plugins-core"], host-module = true, reexport = true }
```

`reexport = true` is needed when the plugin's consumers import L2 modules in
Expand Down
4 changes: 3 additions & 1 deletion docs/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ Module `mcpp.rules.swift`; engine floor: 2026.9.8.1.

## `rules-sycl`

Module `mcpp.rules.sycl`; engine floor: 2026.10.1.3 (0.19.0, mcpp#755; 2026.9.6.6 before).
Module `mcpp.rules.sycl`; engine floor: 2026.10.1.3 (0.19.0, mcpp#755; 2026.9.6.6 before); an llvm build needs 2026.10.26.1 (0.20.0).

**One C++ runtime, libstdc++ (0.20.0).** `libsycl.so` is compiled against libstdc++ and throws `sycl::exception` on it, and a typed `catch` does not match across two C++ runtimes in one process (mcpp#838). The program's own C++ library is therefore libstdc++: a gcc toolchain, or llvm with `gcc.libstdcxx` in the graph (`[target.'cfg(accelerator = "sycl")'.dependencies] gcc.libstdcxx = "16.1.0.1"`). On Linux the rule refuses any other C++ library before a unit is compiled, naming both remedies, and `compat:sycl-runtime` states the same requirement. The rule names no C++ library on the link line (0.19 added `-l:libstdc++.so.6` beside an llvm build's libc++); `--gcc-install-dir` follows the build's GCC: the graph's supplier (`MCPP_CXX_ABI_ROOT`), else the gcc toolchain's payload, else the `xim:gcc` the rule declares.

**Needs and behaviour.** `[build] accel = "sycl"` or `"sycl, cuda12.9+{sm_89}"`, a constrained glob for `*.sycl`, and `compat:sycl-runtime` so the artifact can reach `libsycl.so.9` at run time. Its own engine need is `.sycl` in the device-source table, 2026.9.6.1
2 changes: 1 addition & 1 deletion mcpp.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "plugins"
namespace = "mcpp"
version = "0.19.1"
version = "0.20.0"
# The first mcpp release that states where each tool and payload comes from
# (protocol 15, mcpp#755): `xpkg_source`, `xpkg_program`, `xpkg_request`,
# `mcpp::decision`, `mcpp::toolchain`, `mcpp::phase`, and the
Expand Down
79 changes: 52 additions & 27 deletions rules/sycl.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -40,30 +40,25 @@
// measured: without it, clang finds the host's CUDA
// installation and says nothing.
//
// WHY THE LINK CARRIES `-l:libstdc++.so.6` AND NOT `-lstdc++`
// ONE C++ RUNTIME, AND IT IS libstdc++ (0.20.0; mcpp 2026.10.26.1+).
//
// The SYCL unit and `libsycl.so` are compiled against libstdc++; mcpp's own
// clang toolchain uses libc++. Both may be in one process because the seam is
// `extern "C"` and no C++ type crosses it -- but the libstdc++ half must
// actually be linked. `-lstdc++` does not do that: clang's driver treats it
// as a selector for the C++ standard library and REWRITES it to `-lc++`
// under `-stdlib=libc++`, so the flag disappears from the link line with no
// diagnostic and `std::cerr` comes back undefined from inside a SYCL header.
// `-l:<soname>` names a file and is not rewritten.
// `libsycl.so` is compiled against libstdc++ and throws `sycl::exception` on
// it, and the device units catch that type. Two Itanium C++ runtimes in one
// process share the ABI names -- `__cxa_throw`, the personality routine, the
// type information of `std::exception` -- so a typed `catch` across them does
// not match (mcpp-community/mcpp#838: only `catch (...)` caught what the
// runtime threw). Until 0.19 this rule put `-l:libstdc++.so.6` beside the
// libc++ an llvm build links and called the seam safe; it was not.
//
// AND THE BUILD SAYS SO, WHICH IS CORRECT AND EXPECTED.
//
// Two C++ runtimes in one image means two sets of unwinder symbols, and mcpp's
// duplicate-symbol check reports it:
//
// warning: 68 symbols in this image are also provided by a library it loads
// _Unwind_DeleteException() ... also provided by libgcc_s.so.1
//
// That warning is accurate and is the reason the seam discipline is not
// optional here: a SYCL exception must be caught inside the device translation
// unit and turned into a return code, because the runtime that threw it is not
// the one the caller would unwind with. The fixture in tests/sycl-consumer
// does exactly that, and its comment says why.
// So the build's own C++ library is libstdc++ (mcpp SPEC-002 §7): with a gcc
// toolchain, or with llvm and `gcc.libstdcxx` in the graph. The rule reads the
// resolved library (`mcpp::cxx_stdlib()`) and refuses anything else before a
// unit is compiled, naming both remedies; `compat.sycl-runtime` states the same
// requirement, so the engine refuses it at planning as well. The C++ library
// is the engine's to link, so this rule names none, and `--gcc-install-dir`
// points the device compiler at the same GCC the program uses: the graph's
// supplier (`MCPP_CXX_ABI_ROOT`), else the gcc toolchain's own payload,
// else the `xim:gcc` this rule declares.
module;
#include <cstdlib>
#include <cstdio>
Expand Down Expand Up @@ -228,6 +223,25 @@ inline std::string gcc_install_dir(const std::string& gcc_root) {
return {};
}

// The GCC whose libstdc++ this build uses: the graph's supplier, whose `cxx`
// links the payload's include/c++/<version> (so the GCC root is three levels
// above it); else the gcc toolchain's own payload; else the `xim:gcc` this rule
// declares.
inline std::string cxx_gcc_root() {
std::error_code ec;
// Read from the environment rather than through `mcpp::cxx_abi_root()`,
// which an engine before 2026.10.26.1 does not have: with a gcc toolchain
// this rule still compiles and works there.
const char* abiRoot = std::getenv("MCPP_CXX_ABI_ROOT");
if (const std::string root = abiRoot ? abiRoot : ""; !root.empty()) {
const auto cxx = std::filesystem::canonical(std::filesystem::path(root) / "cxx", ec);
if (!ec) return cxx.parent_path().parent_path().parent_path().string();
}
if (std::string_view(mcpp::compiler()) == "gcc")
if (const std::string dir = mcpp::toolchain_dir(); !dir.empty()) return dir;
return payload("gcc");
}

// `dpcpp --version` states the release and the intel/llvm revision it was
// built from. Stated as a fact so a build log answers "which SYCL compiler"
// without anyone reproducing the build.
Expand Down Expand Up @@ -352,14 +366,29 @@ inline std::vector<edge> plan(std::span<const std::string> sources, options opt
const auto dpcppTool = mcpp::plugins::tool::resolve(dpcppSpec, opt.compiler);
// Asked for: the engine installs `xim:dpcpp` and runs this program again.
if (dpcppTool.pending()) return out;
// One C++ runtime per process (file header). On Windows both halves use
// MSVC's, so there is nothing to choose.
if constexpr (!kWindows) {
if (std::string_view(mcpp::cxx_stdlib()) != "libstdc++") {
std::cerr << std::format("mcpp.rules.sycl: SYCL needs this build's C++ library to be libstdc++, and it is {}.\n"
" libsycl.so throws sycl::exception on libstdc++, and a typed catch does not match\n"
" across two C++ runtimes in one process.\n"
" help: add a libstdc++ supplier beside the llvm toolchain:\n"
" [target.'cfg(accelerator = \"sycl\")'.dependencies]\n"
" gcc.libstdcxx = \"16.1.0.1\"\n"
" help: or build with a gcc toolchain: [toolchain] default = \"[email protected]\"",
*mcpp::cxx_stdlib() ? mcpp::cxx_stdlib() : "unknown") << '\n';
return out;
}
}
// TWO VALUES, NOT ONE: the compiler to run, and the root its libraries sit
// under. They were one string while this rule took the payload directory and
// appended `/bin/clang++`; a resolver answers the program, and `-L` still
// needs the root (a build of the SYCL consumer failed with
// `unable to find library -lsycl` when the root was the program's path).
const std::string dpcpp = dpcppTool.program;
const std::string dpcppRoot = dpcppTool.root;
const std::string gcc = payload("gcc");
const std::string gcc = cxx_gcc_root();
const std::string cuda = tg.cuda_archs.empty() ? std::string{} : payload("cuda-nvcc");
// THE C LIBRARY IS THE SAME QUESTION AS THE C++ ONE, ONE LAYER DOWN.
//
Expand Down Expand Up @@ -474,10 +503,6 @@ inline std::vector<edge> plan(std::span<const std::string> sources, options opt
// rule resolved the payload, so the rule names where its libraries are.
mcpp::link_search((dpcppRoot + "/lib").c_str());
mcpp::link_lib("sycl");
// See the file header for why this is not `-lstdc++`. The reason is a
// Linux one: two C++ runtimes cannot share a process, and on Windows there
// is one -- MSVC's, which both this payload and mcpp's own compiler use.
if constexpr (!kWindows) mcpp::link_lib(":libstdc++.so.6");

// SPIR-V IS NOT A DEVICE, AND A BUILD THAT NAMES NO DEVICE SHOULD BE TOLD.
//
Expand Down
2 changes: 1 addition & 1 deletion src/plugins.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export namespace mcpp::plugins {
//
// One package, one version: the number lives in mcpp.toml, and the CI step
// `the collection states its own version` compares the two.
inline constexpr std::string_view version = "0.19.1";
inline constexpr std::string_view version = "0.20.0";

} // namespace mcpp::plugins

Expand Down
12 changes: 10 additions & 2 deletions tests/sycl-consumer/mcpp.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,25 @@ default = "[email protected]"
[build-dependencies.mcpp]
plugins = { path = "../..", features = ["rules-sycl"], host-module = true }

# The SYCL runtime, on the artifact's runtime search path. mcpp's private
# The SYCL runtime, on the artifact's runtime search path, for a SYCL build. mcpp's private
# loader does not consult /usr/lib, so `libsycl.so.9` -- which the rule
# satisfied at LINK time from the payload -- would not be found at run time.
#
# This is the ONLY compat entry a SYCL project needs, and that is the point of
# where the boundary was drawn: the runtime's CUDA back end dlopens the
# driver, so the adapter carries that hop. A project that writes SYCL does not
# have to know CUDA is underneath it.
[dependencies.compat]
[target.'cfg(accelerator = "sycl")'.dependencies.compat]
sycl-runtime = "2026.09.07"

# THE C++ LIBRARY OF A SYCL BUILD IS libstdc++ (0.20.0; mcpp 2026.10.26.1+).
# `libsycl.so` throws `sycl::exception` on libstdc++, and a typed catch does not
# match across two C++ runtimes in one process (mcpp-community/mcpp#838), so
# the compiler stays llvm and the C++ library comes from the graph. Both edges
# are under the accelerator: the CPU variant (`--no-accel`) keeps llvm's libc++.
[target.'cfg(accelerator = "sycl")'.dependencies.gcc]
libstdcxx = "16.1.0.1"

# NO [xlings.workspace]. The rule declares the payloads it drives, under the
# feature that selects it and the accelerator it serves, so this project names
# the rule and nothing else. Needs mcpp 2026.9.6.6; before it, a payload a
Expand Down
23 changes: 11 additions & 12 deletions tests/sycl-consumer/src/kernels/saxpy.sycl
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,18 @@
// NOT.
//
// tests/cuda-consumer and tests/hip-consumer say in their own comments that
// their islands touch no standard library, because an island that pulls one in
// links a second C++ runtime into a program whose own came from mcpp's
// toolchain. A SYCL island cannot make that promise: SYCL IS a C++ library,
// `sycl::queue` and `sycl::buffer` are class templates over standard types,
// and `libsycl.so` is itself compiled against libstdc++.
// their islands touch no standard library. A SYCL island cannot make that
// promise: SYCL IS a C++ library, `sycl::queue` and `sycl::buffer` are class
// templates over standard types, and `libsycl.so` is itself compiled against
// libstdc++ and throws `sycl::exception` on it.
//
// So the second runtime is present here by construction, and what makes that
// safe is the seam rather than an absence. Nothing below crosses
// `saxpy_device`'s signature: the parameters are pointers and a count, the
// return is an int, and no object allocated by one runtime is freed by the
// other. `mcpp.rules.sycl` links the libstdc++ half explicitly for the same
// reason -- it is a consequence of the compiler this rule chose, not something
// the manifest should have to know.
// So the whole program uses libstdc++ (0.20.0): the manifest supplies it to the
// llvm build through `gcc.libstdcxx`, and `mcpp.rules.sycl` points the device
// compiler at the same GCC. One C++ runtime in the process is what lets a
// `catch (sycl::exception const&)` match what the runtime threw; until 0.19
// the program linked libc++ beside it, and only `catch (...)` matched
// (mcpp-community/mcpp#838). The seam stays `extern "C"` for the reason the
// siblings give: it is the one place a backend can be exchanged.
//
// WHAT THIS ISLAND CAN AND CANNOT TURN INTO A RETURN CODE.
//
Expand Down
Loading