You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
examples: the module surface, and one header kept for contrast (#588)
The payload lane and the boundary lane are separate, and each example now
shows one of them.
`vulkan` reaches its SPIR-V through a MODULE. `src/vulkan/saxpy.cpp` opened
with `#include "scale_comp.h"` -- a name no line in the project produced and
no reader could derive without opening the rule -- and now imports
`vulkan_saxpy.shaders`, whose name and whose `scale_comp()` accessor are both
derived from names the project already wrote. The accessor answers with the
address and the byte count together, which is what makes `sizeof` the wrong
question rather than merely an awkward one: the rule may put the words in an
object, and there is then no array to take the size of.
`cuda` and `sycl` GENERATE their `extern "C"` boundary. Each entry point is
marked with `MCPP_EXPORT_C` where it is defined, and `mcpp.tools.island`
writes the header the island's compiler reads and the module the seam
imports. `include/saxpy/saxpy.h` is gone from both. Both halves are handed to
the scan, so a signature that drifted between the island and the CPU
fallback is refused while the boundary is generated -- the one point at which
both texts exist at once, and the only check available at a boundary where C
language linkage does not mangle and the two halves are never in one link.
The island names nothing and the host half writes one `#include`, and that
asymmetry is measured rather than incidental. The island is compiled by a
driver mcpp did not invoke, so its rule takes forced-include flags for that
one command line. The host half is ordinary project C++, and the only
project-wide channel would force the header into every C++ translation unit
including the seam, where declarations ahead of `export module` are
ill-formed.
`hip` keeps its hand-written header, and its README says why. It computes the
same thing through the same seam as `cuda`, so the difference between the two
examples is exactly this one thing, and the hand-written form is what every C
boundary looks like today. Neither is deprecated; the generated form is the
default because the copy it removes is the one that goes wrong quietly.
`multi-backend` reaches its SPIR-V as `opkit.shaders` too, and keeps
`include/opkit/opkit.h` written by hand -- which is the distinction this example
is for. That header is a LIBRARY boundary: consumers outside this tree compile
against it, so it is not an intermediate and generating it would not remove a
copy, it would move one. The shader payload is an intermediate, and it moves.
`examples/10-graphics/offscreen` moves too, and it is the one example CI both
builds AND RUNS on two platforms -- so the surface is exercised there rather
than merely compiled. Two includes become one import, and the two stages are
`offscreen::shaders::triangle_vert()` and `::triangle_frag()`.
`cann` is unchanged. Its boundary has a different shape -- `saxpy_device`
between the seam and a host glue file, `saxpy_launch` between that glue and
the kernel -- and both halves of the first one are compiled by mcpp, so the
forced-include asymmetry above does not apply. Its README already records
that the example does not build yet.
Verified on this host: cuda and sycl build and run on an RTX 4080 with the
generated boundary on the island's own command line, vulkan runs on the same
device with its shader reached as a module, multi-backend runs on lavapipe with
`--accel "vulkan1.2"`, the offscreen triangle renders on lavapipe at centre
pixel `(124, 70, 62, 255)` -- the same pixel its CPU leg produces, which is the
assertion CI makes -- and each CPU leg builds and runs under `--no-accel`.
`docs/20`'s two-lanes section said the seam is written by hand and the shader
lane's equivalent is generated. Half of that still holds: the seam is a design
decision. What sits under it is each signature written a second time, and that
half is now generated too. The section says so, and gains the table that maps a
file name to the call it arrives as -- which is the question a reader of that
document actually has.
Every example under `examples/09-heterogeneous` and `examples/10-graphics`
pins 0.3.0 now, `hip` and `cann` included. Neither uses anything new, and
leaving them behind would be defensible in isolation -- but `hip` exists to
be read beside `cuda`, and a reader comparing the two would find a second
difference the text never explains. The floor sentences move with them.
The six plugin pins in `docs/05` and `docs/20` move too, in both languages.
A version in a document is the line someone pastes, and 0.2.1 and 0.2.4
predate every declaration those sections now describe -- a reader who copied
one would get a build in which the rules never route.
The Windows graphics run step was brought back as a MEASUREMENT and is
withdrawn again, this time with an answer. Four rounds established what it is
not -- a missing `vulkan-1.dll`, an unparseable ICD manifest (a real defect,
fixed in openxlings/xim-pkgindex#781, and not this failure's cause), a failed
`LoadLibrary`, or the driver-files variable's vintage -- and the last round
asked the loader instead of guessing:
INFO: Loader is running with elevated permissions.
Environment variable VK_DRIVER_FILES will be ignored
INFO: Loader is running with elevated permissions.
Environment variable VK_ICD_FILENAMES will be ignored
ERROR | DRIVER: Registry lookup failed to get ICD manifest files.
A GitHub Windows runner runs elevated, and the loader discards every
driver-path environment variable when it is -- it will not let a path a
non-administrator could write inject a driver into an elevated process. It
falls back to the registry, which has no ICD. No package change reaches
that; the mechanism this example uses on Linux and macOS is unavailable to
an elevated process on Windows, and reaching lavapipe there means a registry
entry under `HKEY_LOCAL_MACHINE\SOFTWARE\Khronos\Vulkan\Drivers` or an
unelevated run.
The four eliminations and the loader's own words are recorded beside the
withdrawn step, so the next attempt starts from there rather than from the
first hypothesis. Windows keeps building the Vulkan half and running the CPU
fallback.
Requires mcpp:plugins 0.3.0.
Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com>
Copy file name to clipboardExpand all lines: .agents/docs/2026-09-07-module-first-heterogeneous-surface.md
+49Lines changed: 49 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -846,6 +846,55 @@ and a newer one moves a project to the module surface with nothing declared.
846
846
| Seamless upgrade | A project that writes nothing new keeps its behaviour | Header storage stays the default under `modules = false`; the default surface follows `[language] modules` |
847
847
| Test coverage | Each surface and each storage has a consumer that runs | P7 |
848
848
849
+
### 13.3.1 What the self-review found after the surface was working
850
+
851
+
Four findings, three of them in names the surface generates. Every one was found
852
+
by giving the generator an input nobody had tried, and none of them by reading
853
+
the code -- which is the reason they are recorded together.
854
+
855
+
**The user-facing name came from the wrong question.** The module a consumer
856
+
imports was derived from the leaf of `MCPP_MANIFEST_DIR` -- a directory name --
857
+
because nothing in the build-program contract answered "what package am I
Copy file name to clipboardExpand all lines: docs/01-examples.md
+4-4Lines changed: 4 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -32,10 +32,10 @@ examples.
32
32
| 07 |[`examples/07-project-subos`](../examples/07-project-subos/)| A build program that finds its tools in the environment the project declared |`[xlings] subos`, `[xlings.workspace]`, a build program whose `PATH` is the environment the project named |
33
33
| 08 |[`examples/08-build-rules`](../examples/08-build-rules/)| Two rule packages and a project that uses both |`host-module = true`, `[build-dependencies]`, `mcpp::action` with `role = "check"`|
34
34
| 09 |[`examples/09-heterogeneous`](../examples/09-heterogeneous/)| One computation on a device, in several programming models, with a CPU fallback in each; plus one artifact carrying several backends at once |`accel`, constrained source globs, the seam module, rule packages from `mcpp:plugins`, `cfg(accelerator = …)`|
35
-
| 09a |[`…/cuda`](../examples/09-heterogeneous/cuda/)| A CUDA kernel behind a seam module|`mcpp.rules.cuda`, `mcpp::action` with `role = "object"`, the driver stated as a fact and a floor |
36
-
| 09b |[`…/vulkan`](../examples/09-heterogeneous/vulkan/)| The same computation as a Vulkan compute shader, on a GPU or on the CPU |`mcpp.rules.spirv`, `mcpp::action` with `role = "source"`, generated headers, a software driver as a payload |
37
-
| 09c |[`…/sycl`](../examples/09-heterogeneous/sycl/)| The same computation as a SYCL kernel, compiled by a second compiler |`mcpp.rules.sycl`, the `.sycl` device extension, a chained `mcpp::action` for the device link, `compat:sycl-runtime`|
38
-
| 09d |[`…/hip`](../examples/09-heterogeneous/hip/)| The same computation in HIP, reaching an NVIDIA device|`mcpp.rules.hip`, HIP as a header layer over the CUDA runtime, a two-chunk `accel`|
35
+
| 09a |[`…/cuda`](../examples/09-heterogeneous/cuda/)| A CUDA kernel behind a seam module, with the `extern "C"` boundary GENERATED |`mcpp.rules.cuda`, `mcpp.tools.island`, `mcpp::action` with `role = "object"`, the driver stated as a fact and a floor |
36
+
| 09b |[`…/vulkan`](../examples/09-heterogeneous/vulkan/)| The same computation as a Vulkan compute shader, with the SPIR-V payload reached as a MODULE |`mcpp.rules.spirv`, the module surface, `mcpp::action` with `role = "source"`, a software driver as a payload |
37
+
| 09c |[`…/sycl`](../examples/09-heterogeneous/sycl/)| The same computation as a SYCL kernel, compiled by a second compiler |`mcpp.rules.sycl`, `mcpp.tools.island`, the `.sycl` device extension, a chained `mcpp::action` for the device link, `compat:sycl-runtime`|
38
+
| 09d |[`…/hip`](../examples/09-heterogeneous/hip/)| The same computation in HIP, with the boundary WRITTEN BY HAND — the contrast against 09a|`mcpp.rules.hip`, HIP as a header layer over the CUDA runtime, a two-chunk `accel`|
39
39
| 09e |[`…/multi-backend`](../examples/09-heterogeneous/multi-backend/)| Several backends in ONE artifact, chosen at run time — the library shape, not the program shape |`accel` as a set, `cfg(accelerator = "none")` and its negation, a dispatch chain, a module seam over a C island boundary |
40
40
| 09f |[`…/cann`](../examples/09-heterogeneous/cann/)| An Ascend C kernel behind the same seam. **Does not build yet** — its README names the two missing pieces | the `.asc` device extension, `op_kernel`/`op_host` as an island CANN already has, `accelerator = "none"` for the fallback |
41
41
| 10 |[`examples/10-graphics`](../examples/10-graphics/)| Graphics rather than compute: a rendering pipeline whose result is pixels |`mcpp.rules.spirv` for the vertex and fragment stages, offscreen rendering as the assertable form |
0 commit comments