Skip to content

Commit 716cce1

Browse files
committed
docs(plan): section 14 — what is open after the release, and each one's fix
Section 7 listed what was open before the work; this lists what is open after it, with a mechanism for each rather than a restatement of the symptom. Two entries were found by measurement while assembling the comparison in section 10. 14.1 is the one that produces a wrong artifact rather than a failure: under `storage::object`, editing the shader does not reach the binary (bytes=1480 before and after an edit that takes header storage from 1480 to 1776). The generated `.S` is handed to the engine as an ordinary source, so its compile edge does not know the payload, and the `.S`'s own text does not change when the payload does. A depfile does NOT close it, and that was measured: neither gcc nor clang names an `.incbin`'d file in its dependency output. The fix is a `role = "object"` action that declares both inputs. 14.3 carries the exact registry entry read out of the loader's source -- key, value name, REG_DWORD 0, and the `known_drivers` bypass that lets a software rasteriser be listed -- with three routes to it and a recommendation, because the choice is `xim:mesa-lavapipe`'s to make. 14.4 corrects its own withdrawal note: the guessed mechanism is false (feature sources fold at prepare.cppm ~7138, `units()` runs at ~7616), so the cause is unestablished and the next person should not trust the guess. 14.5 records a constraint true of every descriptor, found while sizing it: a platform table's `targets` APPENDS. `mcpp xpkg parse` on today's compat.vulkan reports ["vulkan", "vulkan"].
1 parent 076b6cc commit 716cce1

1 file changed

Lines changed: 186 additions & 0 deletions

File tree

.agents/docs/2026-09-07-module-first-heterogeneous-surface.md

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -907,3 +907,189 @@ rather than half-fixed.
907907
(`xlings subos use N --sandbox --cmd ...`) with the CN mirror configured,
908908
because a sandbox is the only thing that tests what was published rather than
909909
what is in the working tree.
910+
911+
## 14. What is open after the release, and what each one's fix is
912+
913+
Section 7 listed what was open before the work. This lists what is open after
914+
it. Two entries were found by measurement while assembling the comparison in
915+
section 10, and the first of them produces a wrong artifact rather than a
916+
failure.
917+
918+
### 14.1 Object storage does not rebuild when its payload changes
919+
920+
Measured on `tests/spirv-object-storage` against the released 2026.9.7.1, by
921+
editing the shader so its compiled output must differ and reading the byte count
922+
the program prints:
923+
924+
| storage | before the edit | after |
925+
|---|---|---|
926+
| `header` (the default) | `bytes=1480` | `bytes=1776` |
927+
| `object` | `bytes=1480` | **`bytes=1480`** |
928+
929+
The build succeeds and the artifact is the old one. This is not the `#include`
930+
case in 14.2 -- it is the DIRECT source, which the shader action does declare.
931+
932+
**Mechanism.** `rules/spirv.cppm` hands the generated `.S` to `mcpp::generated`,
933+
so its compile edge knows the `.S` and nothing else. The `.S` names the payload
934+
in `.incbin` but its own text does not change when the payload does, so
935+
`write_if_different` leaves the file alone, its mtime does not move, and no edge
936+
is dirty. The shader itself recompiles; the object that carries its bytes does
937+
not.
938+
939+
**A depfile does not close this, and that was measured rather than assumed:**
940+
941+
```
942+
gcc: p.o: p.S /usr/include/stdc-predef.h
943+
clang: p.o: p.S
944+
```
945+
946+
Neither assembler names an `.incbin`'d file in its dependency output. The
947+
channel added in this release is the right one for 14.2 and the wrong one here.
948+
949+
**The fix is to stop handing the `.S` to the engine as an ordinary source.**
950+
Object storage should emit a `role = "object"` `mcpp::action` that runs the
951+
assembler itself and declares BOTH inputs -- the `.S` and the payload -- which
952+
is the shape `mcpp.rules.cuda` already uses for a `.cu`. The dependency then
953+
exists in the graph rather than in a comment.
954+
955+
`storage::sidecar` copies the payload beside the artifact and has not been
956+
checked for the same shape. It should be, in the same change.
957+
958+
### 14.2 No rule passes a depfile
959+
960+
`mcpp::action::depfile` shipped in 2026.9.7.1 and `grep depfile rules/` in
961+
`mcpp:plugins` returns nothing across all six rules; each declares
962+
`a.input(source)` alone. A shader or kernel that `#include`s another file does
963+
not rebuild when that file changes, which is the same behaviour xmake has and
964+
the one thing CMake's `add_custom_command(DEPFILE)` gets right.
965+
966+
Every compiler involved already emits one: `glslangValidator --depfile`,
967+
`glslc -MD -MF`, `slangc -depfile`, and `-MD -MF` for the clang-family drivers
968+
behind cuda, hip, sycl and ascendc. The work is one flag and one
969+
`a.depfile(...)` per rule, plus a fixture whose criterion is that editing an
970+
included file rebuilds -- the criterion has to be the artifact's content, not
971+
the build's exit code, because the defect is a green build over stale bytes.
972+
973+
14.1 and 14.2 belong in one release. They are the same class of defect and the
974+
same fixture shape answers both.
975+
976+
### 14.3 A device on Windows, and who decides it
977+
978+
The cause is settled and it is not a packaging defect. Under `VK_LOADER_DEBUG=all`
979+
on a GitHub runner:
980+
981+
```
982+
INFO: Loader is running with elevated permissions.
983+
Environment variable VK_DRIVER_FILES will be ignored
984+
INFO: Loader is running with elevated permissions.
985+
Environment variable VK_ICD_FILENAMES will be ignored
986+
DRIVER: Found no registry files in HKEY_LOCAL_MACHINE\SOFTWARE\Khronos\Vulkan\Drivers
987+
ERROR | DRIVER: Registry lookup failed to get ICD manifest files.
988+
```
989+
990+
A process running elevated is not permitted to be told where its drivers are,
991+
because a path a non-administrator can write would then decide what code an
992+
elevated process loads. The loader falls back to the registry, which on a
993+
machine with no GPU is empty.
994+
995+
**The registry entry, exactly.** From `loader/loader_windows.c` and
996+
`vk_loader_platform.h` of the loader `compat:vulkan` builds:
997+
998+
- key `HKEY_LOCAL_MACHINE\SOFTWARE\Khronos\Vulkan\Drivers`
999+
- value NAME: the absolute path of the ICD manifest, `...\lvp_icd.x86_64.json`
1000+
- value DATA: `REG_DWORD` `0` -- the loader accepts an entry only when
1001+
`value_size == sizeof(value) && value == 0`
1002+
- a driver whose file name is not in the loader's `known_drivers` table skips
1003+
the DXGI adapter check and is "assumed to be active", which is what lets a
1004+
software rasteriser be listed at all
1005+
1006+
**Three ways to get there, and they are not equivalent.**
1007+
1008+
1. `xim:mesa-lavapipe`'s `config()` writes the entry on Windows. It has the
1009+
information and the install already runs with the necessary rights on a
1010+
runner. It is also a MACHINE-WIDE mutation performed by a package install:
1011+
every Vulkan application on that machine would then see lavapipe. That is a
1012+
different promise from the one the package makes today, whose own comment
1013+
says it "places the payload and stops; naming the ICD is the consumer's".
1014+
2. The consumer writes it -- this repository's CI step, or a project's own
1015+
`build.mcpp`. Scoped to whoever wants it, and it keeps the package's promise
1016+
intact; the cost is that every consumer repeats it, which is the shape the
1017+
rule-package work exists to remove.
1018+
3. Run the program unelevated. Correct on a developer machine and awkward on a
1019+
runner that is elevated by construction.
1020+
1021+
**Recommendation: 2 for CI now, and 1 only behind an explicit opt-in.** A
1022+
package that registers a driver system-wide as a side effect of being installed
1023+
is a surprise, and the surprise lands on software that has nothing to do with
1024+
mcpp. The decision is `xim:mesa-lavapipe`'s to make, not this repository's.
1025+
1026+
Whichever is chosen, the CI step withdrawn from `ci-windows.yml` carries the
1027+
four eliminations and the loader's own output beside it, so the next attempt
1028+
starts from here.
1029+
1030+
### 14.4 A rule feature that is on by default does not imply `host-module`
1031+
1032+
`host-module` is inferred from the features a consumer REQUESTS. A rule package
1033+
whose rule sits in its own `[features] default` is activated without being
1034+
named, so the rule modules are collected -- that reads the resolved set -- while
1035+
the inference reads the requested one. Measured with a probe package declaring
1036+
`default = ["rules-probe"]`: mcpp synthesises the build program and then refuses
1037+
with
1038+
1039+
```
1040+
error: build.mcpp imports 'probe.rules.probe', and no dependency provides it
1041+
as a host module.
1042+
declared without `host-module = true`: probe.rules
1043+
```
1044+
1045+
**The fix that was written and withdrawn.** Extending the inference to the
1046+
dependency's own defaults made the outcome worse: the refusal gained
1047+
`importable here: rules`, which says the dependency is wired up while the
1048+
feature's own module still is not.
1049+
1050+
**The mechanism is not what the first reading said, and that is worth
1051+
recording.** The withdrawal note guessed that a default feature's sources are
1052+
folded into the set `units()` enumerates too late. That is false: the fold
1053+
happens at `prepare.cppm` line ~7138 and `units()` runs at ~7616. So the cause
1054+
is elsewhere and is NOT established. Anyone picking this up should start by
1055+
finding it rather than by trusting the guess.
1056+
1057+
`mcpp:plugins` declares `default = []`, so nothing published reaches this, and
1058+
the current behaviour is a refusal that names both the module and the key to
1059+
add. It is a correctness-of-diagnosis item, not a correctness-of-artifact one.
1060+
1061+
### 14.5 A hermetic `vulkan-1.dll` on Windows
1062+
1063+
Measured feasible and not currently needed. The Khronos loader in
1064+
`compat:vulkan` cross-builds into a working DLL from the source the index
1065+
already carries: 265 exports matching upstream's `vulkan-1.def` name for name
1066+
and all `vk*`-prefixed, `DllMain` present, importing only ADVAPI32, CFGMGR32,
1067+
KERNEL32 and msvcrt. The descriptor's note argues a Windows loader must be a
1068+
DLL, not that it cannot be built.
1069+
1070+
**One constraint, found while sizing this and true of every descriptor.** A
1071+
platform table's `targets` APPENDS rather than replaces. `mcpp xpkg parse` on
1072+
today's `pkgs/c/compat.vulkan.lua` reports
1073+
1074+
```
1075+
targets: ["vulkan", "vulkan"]
1076+
```
1077+
1078+
-- the base `{ ["vulkan"] = { kind = "lib" } }` and the linux
1079+
`{ ["vulkan"] = { kind = "shared", soname = ... } }` are two targets sharing a
1080+
name, silently. So a Windows entry naming a target `vulkan-1` would produce a
1081+
THIRD target rather than a replacement. The change has to either move the base
1082+
`targets` into each platform table, so each platform declares exactly one, or
1083+
keep the name `vulkan` and reach `vulkan-1.dll` through `soname`.
1084+
1085+
That a duplicate target name is accepted without a word is worth a look on its
1086+
own, independently of this entry.
1087+
1088+
### 14.6 Not open: the Slang version pin
1089+
1090+
Recorded because it was on the plan and is resolved by fact rather than by work.
1091+
`xim:slang` publishes `2026.14.1` and nothing newer, `latest` points at it, and
1092+
`mcpp:plugins` declares `>=2026.14.1`. The floor is already at the index's
1093+
ceiling. Raising it means publishing a newer slang payload to `xim-pkgindex`
1094+
first -- four platforms and a GitCode mirror -- which is a packaging task, not a
1095+
pin edit.

0 commit comments

Comments
 (0)