-
Notifications
You must be signed in to change notification settings - Fork 14
Expand file tree
/
Copy pathmcpp.toml
More file actions
160 lines (147 loc) · 8.08 KB
/
Copy pathmcpp.toml
File metadata and controls
160 lines (147 loc) · 8.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
[package]
name = "opkit-multi-backend"
namespace = "example"
version = "0.1.0"
description = "One operator, several device backends in one artifact, chosen at run time"
accelerators = ["cuda", "vulkan"]
[language]
standard = "c++23"
modules = true
import_std = true
# WHAT THIS EXAMPLE SHOWS THAT THE FOUR BESIDE IT DO NOT.
#
# Each of cuda/, hip/, sycl/ and vulkan/ is ONE seam: a device file and a CPU
# file define the same symbol and are never in one link, so exactly one exists
# and the choice is made at BUILD time. That is the right shape for a program.
#
# A library cannot make that choice. It is compiled once and consumed by people
# whose machines differ, so its backends are ADDITIVE -- several land in one
# artifact and the choice moves to RUN time. This is that shape.
#
# `accel` is deliberately absent, so a plain `mcpp build` produces the CPU-only
# variant and needs no payloads at all. The device backends are opt-in:
#
# mcpp build --accel "vulkan1.2"
# mcpp build --accel "cuda12.9+{sm_89}"
# mcpp build --accel "cuda12.9+{sm_89}, vulkan1.2" # both, one artifact
# clang, because the CUDA rule follows the project's toolchain to pick its
# route and the clang route is the one this line supports. It costs the
# CPU-only build an LLVM payload it would not otherwise need, which is the
# honest price of having the device leg work on the driver a developer already
# has.
[toolchain]
default = "llvm@22.1.8"
# BOTH rules, in one build program. `host-module = true` compiles their module
# interfaces for the build program to import; `[build-dependencies]` keeps the
# package out of the target, which is the case docs/05 section 2.6.1 exists for.
# The rules are declared unconditionally because `build.mcpp` imports them
# unconditionally -- each returns immediately when its own backend is absent.
[build-dependencies.mcpp]
plugins = { version = "0.5.2", features = ["rules-cuda", "rules-spirv"], host-module = true }
# ── the payloads, gated on the device they are for ──────────────────────────
#
# `cfg(accelerator = ...)` in an `[xlings]` table is what keeps `mcpp build`
# free: a CPU-only build of this project installs neither the CUDA toolkit nor
# the shader compiler, because neither predicate holds. Unconditional pins --
# the only spelling available before mcpp 2026.9.6.5 -- would have made the
# cheapest build the most expensive one, and that is the build CI runs.
#
# `accelerator` is admitted here and the five resolved layer keys (`c-abi`,
# `compiler`, ...) are not, because it is an INPUT to the build rather than an
# answer from the dependency graph: `--accel` is read before the first package
# is resolved, while a C library is chosen by the resolution a payload would
# have to precede.
# The 12.9 line and the CLANG route, which is the combination this repository
# verifies everywhere: examples/09-heterogeneous/cuda and every mcpp-plugins
# fixture use it, and it runs on any driver from r525 onward.
#
# The nvcc route on the same line does not work: the 12.9 headers redeclare the
# C23 `cospi`/`sinpi`/`rsqrt` for the host without `noexcept` while the C
# library declares them with it, and nvcc's front end refuses the pair. Moving
# to 13.x fixes that and raises the driver floor to r580, which is a machine
# requirement rather than a project decision. The clang route never includes
# that header and imposes no such floor.
# ONE ENTRY, AND IT IS THE ONLY OVERRIDE IN THIS REPOSITORY'S EXAMPLES.
#
# `mcpp.rules.cuda` declares the whole toolkit -- nvcc, cudart, cuRAND, CCCL and
# the driver sentinel -- so the four beside this one are gone and this project
# would build with no `[xlings.workspace]` at all. The line is kept to show the
# escape hatch working: the declaration nearer the artifact wins, one version is
# installed either way, and a pin that failed a floor the rule stated would be
# refused naming both sides.
#
# Which CUDA line matters here is what makes it a plausible override rather than
# a contrived one: a runtime must not be newer than the driver it will meet, so
# a project whose machines are older or newer than the rule's default is exactly
# the project that should say so.
[target.'cfg(accelerator = "cuda")'.xlings.workspace]
"xim:cuda-nvcc" = "12.9.86"
# The driver's userspace library, reached through an index package rather than
# the host: mcpp's private loader does not consult /usr/lib, so a statically
# linked CUDA runtime cannot otherwise dlopen the driver. It is the one CUDA
# component that cannot be an ordinary payload -- the licence forbids
# redistributing it and it is in ABI lockstep with the kernel module.
[target.'cfg(accelerator = "cuda")'.dependencies.compat]
cuda-driver = "2026.09.05"
# The shader compiler is NOT here: `mcpp.rules.spirv` declares `xim:glslang` for
# itself. What stays is a DEVICE -- a Vulkan driver that is always present
# because it is the CPU, which is what makes the Vulkan leg runnable on a
# machine with no GPU, and what every CI runner in this ecosystem is. A rule
# declares what it needs to COMPILE; a project declares what it needs to RUN.
[target.'cfg(accelerator = "vulkan")'.xlings.workspace]
"xim:mesa-lavapipe" = "26.2.1"
# The Khronos loader, built by the index rather than taken from the host, and
# the adapter that makes the host's own ICDs reachable from a binary running
# under mcpp's private loader. Neither is a driver: a driver has to match the
# kernel module on the machine it runs on, which is why the software one above
# is a payload and the hardware ones are the host's.
[target.'cfg(accelerator = "vulkan")'.dependencies.compat]
vulkan = "1.4.357.0"
vulkan-runtime = "2026.09.10"
[build]
# The device sources carry the accel they are for. A CONSTRAINED glob gates
# itself -- it is offered to the build program only when this build's `accel`
# accepts it -- so these need no `cfg` block, and the engine never offers a
# `.cu` or a `.comp` to the C++ compiler.
sources = [
"src/*.cppm",
"src/main.cpp",
"src/cpu/*.cpp",
{ glob = "src/backends/cuda/*.cu", accel = "cuda12.9+{sm_89}" },
{ glob = "src/backends/vulkan/*.comp", accel = "vulkan1.2" },
]
include_dirs = ["include"]
# ── the backends, additive ──────────────────────────────────────────────
#
# Each block activates when its backend is named, and several may activate at
# once -- `accelerator` is a SET, not a choice. `defines` is what tells the
# dispatcher which backends exist, so the registry's list and the sources
# actually compiled cannot drift apart.
# What each block carries is the HOST half of a backend plus the define that
# admits it to the dispatcher. The device half is gated by its constrained glob
# above, and the two must agree -- which is why the define lives here, beside
# the host file that implements the entry point the dispatcher will call.
[target.'cfg(accelerator = "cuda")'.build]
defines = ["OPKIT_HAVE_CUDA=1"]
# Linked statically, and only when a device build asks for it. NO ABSOLUTE
# PATHS: the rule package puts the payload's library directory on the link line
# from `mcpp::xpkg_dir`, so this names libraries only.
ldflags = ["-lcudart_static", "-lrt", "-lpthread", "-ldl"]
[target.'cfg(accelerator = "vulkan")'.build]
sources = ["src/backends/vulkan/*.cpp"]
defines = ["OPKIT_HAVE_VULKAN=1"]
# ── the dispatcher, and why neither predicate enumerates ─────────────────
#
# `not(accelerator = "none")` means "this build named at least one backend".
# Spelled `not(any(accelerator = "cuda", accelerator = "vulkan"))` it would
# have to be edited every time the ecosystem gains a backend -- and
# `accelerator` is open by design, so a third one is a package rather than an
# engine change. The edit that is forgotten is silent: the CPU-only dispatcher
# and the registry would both compile, or neither would.
[target.'cfg(not(accelerator = "none"))'.build]
sources = ["src/dispatch/registry.cpp"]
[target.'cfg(accelerator = "none")'.build]
sources = ["src/dispatch/cpu_only.cpp"]
[targets.opkit-multi-backend]
kind = "bin"
main = "src/main.cpp"