Commit 923de18
docs(plan): the four general build-infrastructure gaps (#583)
* docs(plan): the four general build-infrastructure gaps
Separates what is general build infrastructure from what belongs to the
heterogeneous domain, by a stated criterion: an item qualifies when at least
two of CMake / Meson / Autotools / Cargo have a counterpart and its reason for
existing names no domain concept. RDC fails that test and is left to docs/20;
the primitive it would reuse already exists.
Four gaps survive, and two of them were smaller than they first looked once
main was measured rather than recalled:
* config.h generation is NOT an engine gap. All four pieces are present
(toolchain_dir/sysroot_dir for the right compiler, a real program to write
the file, include-dir to make the package's TUs see it, rerun-if-changed
for incrementality). What is missing is a shared probe library, which is a
package rather than an engine change.
* package layout does NOT need a new section. `[runtime].artifacts` already
declares a relative path plus a role; the white list is missing exactly one
role -- a data file read by a loader outside this package. Adding a section
would have duplicated an answer another section already gives, which
docs/05 Appendix A refuses.
The two that remain are an `exports` declaration rendered per platform (one
neutral statement, three renderings, the same shape `[runtime]` already
established) and a generic `link-flag` directive, which is the member the
link-lib / link-search / link-script family is missing and the escape hatch a
generated version script needs.
Every criterion is two-sided, and C6 is the one that cannot be verified on a
developer machine: a probe implementation that wrongly reads the host is green
wherever /usr/bin/cc exists, so it has to run in the hermetic container job.
* docs(plan): attribution, the programming-model three-way split, and a falsification target
Revises the design doc. Two of its own judgements were wrong and are corrected
in place rather than appended to, because a design doc whose corrections live
at the bottom is read top-down and gets the retracted version.
* RDC is plugin-side, engine change zero. Rechecked against the attribution
rule this revision adds: it names a vendor (test 1), changes no artifact
property (test 2) and invents no edge kind (test 3). Every primitive it
needs exists -- `action` with the `object` destination is "an external
step's object joins the ordinary link", twice. It therefore does not sit on
the critical path, which changes the staging.
* stdpar is islandable, so calling it mutually exclusive with mcpp's model
was an overclaim. `-stdpar` decomposes into a compile-side island and a
link-side allocator, and the allocator is a whole-image property imposed by
a dependency -- the shape `cxx_runtime` already established. What is lost
is the selling point ("change no source"), not the capability. docs/20's
two-way split should become three-way.
Three gaps added. The first came out of a manifest that reads badly:
[target.'cfg(not(any(accelerator = "cuda", accelerator = "vulkan")))'.build]
`accelerator` is an open vocabulary by design, and `not(any(<enumeration>))`
over an open vocabulary silently changes meaning as the ecosystem grows -- a
fifth backend edits the meaning of every fallback predicate already written.
The general rule is that an open vocabulary needs a "none" that does not
enumerate, and the spelling is already in this manifest: `os = "none"` is bare
metal, so `accelerator = "none"` is no device backend. Preferred over `cpu`,
which would make the axis carry two questions and leave the truth value of
`cfg(accelerator = "cpu")` under `accel = "cuda"` undecided.
The other two are the package-wide barrier a generated header creates
(`source`'s documented semantics: every compile edge of the package waits) and
Fortran, which is recorded as identified and deliberately given no design.
Also: an attribution rule (three ordered tests) so the engine/plugin boundary
is decided rather than argued each time; a note that "island" collides with
branch-island in linker vocabulary and cuts across single-source/separate-
source, with an alignment sentence rather than a rename; and CANN as the
experiment that could falsify the central claim, since all four existing lanes
are in one vendor lineage. Its decisive criterion is `git diff src/` empty.
* docs(plan): the three CANN uncertainties, measured
Two confirmed, one not, and the one that failed is the only real obstacle.
All CANN repos clone anonymously from gitcode.com/cann at branch 8.5.0 under
the CANN Open Software License Agreement 2.0.
CONFIRMED -- hardware-free execution exists, and it is the vendor's own test
path. Ascend C has three run modes, not two: npu (default), sim (NPU
simulation) and cpu (CPU debug), selected by -DCMAKE_ASC_RUN_MODE=. The
simulators ship inside the toolkit per SoC and asc-devkit's own unit tests link
them (pvmodel_ascend910/310p/610, pem_davinci_ascend910B1/310B/610Lite).
The distinction that decides whether a criterion is usable: cpu mode links
tikicpulib and the tikcpp headers, so the same kernel source is compiled by the
HOST compiler and there is no island in that graph at all. Passing in cpu mode
proves the kernel's numerics, not the mechanism under test -- the criterion
would be pointed at the wrong object, which is a shape this repository has paid
for repeatedly. sim mode is recorded as keeping the island by inference, not by
measurement, with the check to run at implementation time stated.
CONFIRMED -- operator registration is JSON and is per-SoC. In ops-math (72 MB,
1451 .cpp under math/) each operator carries op_host/config/<soc>/<op>_binary.json
mapping the operator signature to the device binary's filename, with six SoC
directories. That is a textbook role = "manifest" case and it is target-
conditional, so the slice exercises SPEC-004's target axis as well. The source
layout is also already islanded: op_kernel/ beside op_host/ per operator.
NOT CONFIRMED -- BiSheng is not a standalone artifact. It lives at
${ASCEND_DIR}/compiler/ccec_compiler/bin/bisheng inside the CANN toolkit, whose
download requires accepting a separate licence and appears to need login. No
stable anonymous URL was found and redistribution terms are unverified. This is
the experiment's one real obstacle and it is not a technical one.
Consequently the feasibility gate is corrected: "hardware or a simulator" was
wrong as a hard gate, because the decisive criterion (git diff src/ empty) is
entirely build-time. The only true prerequisite is obtaining BiSheng lawfully.
Incidental finding worth recording: CMake registers Ascend C as a LANGUAGE
(FindASC.cmake, "plugin support ASC language") with the real machinery inside
the toolkit. That is third-party corroboration of the axis in section 9 --
CMake puts it in the engine, mcpp puts it in a package. Same axis, different
attribution, which is exactly what section 7 exists to decide.
* docs(plan): the toolkit pulls anonymously, so the experiment has no blocker
Overturns the previous commit's one negative finding, and corrects the framing
error underneath it.
The framing error first: BiSheng and the simulator were treated as two
acquisition questions. They are one -- ccec_compiler/bin/bisheng and
simulator/<SoC>/lib are both inside the CANN toolkit.
The toolkit's official distribution is a Docker image, not a .run installer
(measured in ops-math/QUICKSTART.md):
swr.cn-south-1.myhuaweicloud.com/ascendhub/cann:8.5.0-910b-ubuntu22.04-py3.10-ops
and it pulls anonymously. Measured:
GET /v2/ 401
GET /swr/auth/v2/registry/auth?...:pull token issued
GET /v2/ascendhub/cann/manifests/<tag> with token 200, arm64 + amd64
That 401 is what produced the wrong answer the first time. A bare 401 reads
identically to "credentials required", but it is also the first step of an
anonymous token handshake -- Docker Hub behaves the same way. Stopping at step
one turns "anonymously available" into "unobtainable". The criterion has to
walk the whole handshake, which is this repository's recurring lesson about a
criterion whose "no" and whose "not measured" produce the same reading.
Compliance lands in the tier the invariant already allows: proprietary vendor
userspace is fetched from the vendor's own published URL and never copied into
an xlings-res release. Pulling the official image from Huawei's own registry is
exactly that, so no redistribution right is needed.
Also records that AscendNPU IR is now open with a Triton path
(Triton IR -> Linalg -> AscendNPU IR), which gives a rule package a second
possible entry height. Not selected -- Ascend C remains the direct one, because
it is the actual form of the 1451 .cpp files in ops-math.
The feasibility gate therefore has no blocking row. One technical question
remains before implementation: whether sim mode still invokes bisheng, since
that decides which object the device criterion is pointed at.
* docs(plan): sim mode does invoke bisheng, measured in vendored cmake
Closes the last open question in section 10. It was recorded as inference; it
is now source.
asc-devkit vendors the toolkit's own ASC_CMake under tools/ascc/cmake/, so the
device compile path is readable without pulling the image:
CMakeDetermineASCCompiler.cmake:47
find_program(CMAKE_ASC_COMPILER NAMES "bisheng" PATHS ".../ccec_compiler/bin/")
CMakeASCInformation.cmake:49
CMAKE_ASC_COMPILE_OBJECT = "<CMAKE_ASC_COMPILER> ... -c -x asc <SOURCE>"
host_config.cmake:69
CCEC_LINKER = <toolkit>/ccec_compiler/bin/ld.lld
The decisive one is negative evidence: every RUN_MODE test in the repository is
STREQUAL "cpu". There is no sim branch anywhere. Build-time therefore
distinguishes only cpu from not-cpu, so sim takes the same path as npu and
bisheng is invoked. The two differ at run time -- which runtime and simulator
libraries load -- not in the build graph. The one extra action on the non-cpu
branch is update_host_stub.py, which generates host-side launch stubs, and a
launch stub exists precisely because there is a real device binary to start.
Worth keeping the reason the question was asked: the opposite design is real
and has good reasons behind it, since instruction-level simulation is orders of
magnitude slower than host code and a host compiler gives gdb and ASAN. CANN
split those concerns instead -- cpu mode IS that design, so sim would duplicate
it unless it executed real device instructions. Three modes rather than two is
itself the answer.
Section 10 now has no open items.
* docs(plan): the validation item gets a criterion and a slot, and that criterion was wrong
Review pass over the whole document. Three findings, one of which is a defect
in a criterion rather than an omission.
OMISSION -- the validation experiment had neither a criterion nor a stage. It
was described at length in section 10 and then appeared in neither the criteria
table nor the staging table, so nothing tied it to the rest of the plan. Added
as C9 and as a staging row with 13.1 explaining where it sits.
DEFECT -- C9's decisive criterion, "git diff src/ is empty", cannot tell two
failures apart:
* the diff carries an Ascend-specific identifier -> the claim is refuted
* the diff is a general capability like exports -> the engine is merely
missing general infrastructure, which is what sections 2-6 exist to add
Both read the same, so running it before the general gaps land would report the
second as the first. Replaced by two levels: the primary criterion is that
test_core_vendor_probes.cpp stays green after the port, which judges by
property rather than by whether anything changed and therefore needs no
precondition; "git diff src/ empty" is kept as a stricter additional statement,
valid only after stages one to three.
That also unblocks running the experiment early: the primary criterion works at
any time, and an early run may surface a fifth general gap, which is cheaper
than discovering it after three stages of engine work.
ORPHAN -- kind = "device" appeared once, in the attribution table, with no
criterion and no stage. It sits in a cross box: engine-side by test 2, but
domain by the section 0.1 admission line, because it talks about accel. Two
rulers, and they cross. Stated explicitly and scoped to docs/20 rather than
left ambiguous.
Also marked the superseded half of revision two in the change record, so a
reader scanning the log does not take an overturned conclusion as current.
* feat: link-flag, and an open vocabulary that can say "empty"
Stage one of the general build-infrastructure design
(.agents/docs/2026-09-07-general-build-infrastructure-gaps-design.md).
mcpp:link-flag (protocol v8). link-lib, link-search and link-script each name
one kind of thing, so a flag the program COMPUTED had no outlet: a version
script whose content depends on which features are on, -Wl,--wrap=malloc for a
runtime that takes over a C-library symbol, -Wl,--exclude-libs,ALL so a
statically absorbed third party does not become part of this package's ABI.
It reaches the consumer, and that CORRECTS the design doc, which first ruled it
private by analogy with include-dir. The analogy is false and the code says so:
linkUsage.ldflags is a copy of buildConfig.ldflags, so a private link flag is
not a policy this engine can express, and [build] ldflags -- the declarative
twin -- already propagates. Making the computed form behave differently would
be the inconsistency rather than the safeguard. The hazard that follows is
stated in the docs rather than hidden: a dependency emitting --version-script
puts it on the consumer's link too, which a dependency writing the same flag in
[build] ldflags has always done.
cfg(accelerator = "none"). A CPU fallback could only be written by enumerating
the backends it is not, and accelerator's vocabulary is open by design -- so
that predicate changes meaning the day a fifth backend exists, and every
fallback already written starts treating a build that named the new backend as
having no accelerator. The spelling reuses os = "none", which this manifest
already means "bare metal" by. Not cpu: that puts a second question on an axis
whose job is "which device compiler, which architecture", and leaves
cfg(accelerator = "cpu") under accel = "cuda" with no self-consistent answer.
Both criteria measure the property rather than its shadow. e2e 620 asserts the
LINKER'S BEHAVIOUR -- the program computes -Wl,--defsym=mcpp_e2e_620=42 and the
artifact prints that symbol's address -- because grepping build.ninja would
pass for a flag written down and never handed to the linker. The unit test
SIMULATES the fifth backend arriving: the enumeration starts lying on the spot
and none does not, which is the entire reason the row exists and is invisible
under a single backend.
* feat: exports -- one neutral statement of the symbol surface, three renderings
Stage two of the general build-infrastructure design.
Both platforms already publish everything: ELF gives symbols default
visibility, and PE gets an auto-generated .def listing every symbol
(mcpp.build.coff_exports, WINDOWS_EXPORT_ALL_SYMBOLS semantics). What was
missing is the other direction.
A runtime with a stable ABI publishes a reviewed set so that what is outside it
stays free to change; a plugin loaded beside its rivals must not collide -- a
Vulkan ICD that exports its internals collides with the loader and with the
other ICDs in the process. This repository has the symptom on file: mcpp's own
duplicate-symbol check on the SYCL example reports 68 _Unwind_* symbols,
because one image holds two C++ runtimes and both export them.
exports takes a file of symbol patterns or an inline list, and the backend
renders it per platform -- version script, -exported_symbols_list, or the .def
that replaces the all-exports one. One statement, three renderings, which is
the shape [runtime] already established and the reason this is a manifest key
rather than three platform-specific flag lists.
IT DOES NOT IMPLY HIDDEN VISIBILITY, and that CORRECTS the design doc, which
said it should. The narrowing is a link-time property on all three formats, so
implying a compile-time one would give a single key two effects -- and the
second effect also changes how this library's own translation units see each
other, which is a separate decision with a separate reason.
-fvisibility=hidden stays available through [build] cxxflags for the code
generation it buys.
The export list is read at manifest load rather than at plan time, so every
later stage sees one representation; origin's parent is the package root and
that holds for the root, a path dependency and a store dependency alike, which
is the "same decision in N places" this would otherwise become.
e2e 621 builds one source twice and requires the two readings to DIFFER.
Asserting only that the public symbol is present passes for a library that
exports everything, which is the state before this change; asserting only that
the internal one is absent cannot distinguish "correctly hidden" from "never
linked at all".
* docs(plan): stages one and two landed, and three judgements they corrected
Records what implementation established, including where it contradicted the
design.
* link-flag reaches the consumer. The doc ruled it private by analogy with
include-dir; the code refutes the analogy. linkUsage.ldflags is a copy of
buildConfig.ldflags and propagateLinkFlags pushes every dependency ldflag
to the consumer, so a private link flag is not a policy the engine can
express. include-dir is private because a compile interface HAS a
declarative public counterpart; link flags do not, so the computed form
must behave like its declarative twin. C4 is rewritten accordingly.
* exports does not imply hidden visibility. The narrowing is link-time on all
three formats, so implying a compile-time effect gives one key two, and the
second changes how the library's own TUs see each other -- a separate
decision. C3 is retired because it asserted exactly that coupling.
* Section 4.3's open question is answered. manifest_emit builds
[[runtime.artifacts]] from doc.legs plus one interface entry and does NOT
carry the author's declared artifacts; role is a free string in the parser
and the white list is an effect of its readers. So stage three is a new
path through the packer -- carry a declared file into the artifact -- not
a new role value. Staging is unchanged; its rationale is.
* docs(examples): a multi-backend artifact, an Ascend one, and the table a rule package belongs in
Two examples, and one correction that applies to every example already here.
multi-backend -- several backends in ONE artifact, chosen at run time. The four
examples beside it are each 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 right 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.
It is also the first real use of cfg(accelerator = "none"). The dispatcher must
be built for cuda, for vulkan, for both, and for a backend that does not exist
yet, and not(any(cuda, vulkan)) needs editing every time the ecosystem grows --
the edit that is forgotten being silent. Its default accel is empty, so a plain
mcpp build needs no payloads at all, which is what lets CI build it and makes
the "none" path genuinely covered rather than described.
cann -- Ascend, behind the same seam. It DOES NOT BUILD, and the README says so
and names the two missing pieces: a rules-ascendc rule package, and an xim
package for the toolkit. It records what was measured instead of guessed: both
BiSheng and the simulator live in that one toolkit; the toolkit image pulls
anonymously; sim mode needs no hardware AND keeps the island, while cpu mode
does not keep it and therefore cannot stand in for a device criterion. The
manifest is written out rather than described so the shape is concrete. CANN's
own operator libraries already split op_kernel/ from op_host/, so the island is
not a shape mcpp imposes on Ascend.
THE CORRECTION. All four existing examples declare mcpp:plugins in
[dependencies], and docs/05 section 2.6.1 names exactly that case as what
[build-dependencies] is for: a package whose library must never reach the target
while its rule is still wanted. The two axes are separate -- host-module = true
says which build-time product is wanted, the section says whether the package
reaches the target, and a rule package answers no on the second. Verified by
moving it and rebuilding: build.mcpp compiles, the rule runs, shaders compile,
and the artifact runs on a real device.
Writing it in [dependencies] still works, which is precisely why the
distinction has to be stated rather than left to a failure to teach.
* Five defects a second backend found, and none of them was findable by reading
The gap list this branch implements came from reading the code and from one
device backend. Writing the second one turned up five more in an afternoon, and
not one of them could have been found by continuing to read: every reading is
either "the build succeeded" or an error message pointing somewhere else.
A DEVICE SOURCE CAN COMPILE NOTHING, AND THE BUILD SUCCEEDS. A device-kind file
is the one source the engine has no rule for: it goes to the package's build
program and comes back as an action, or it is never compiled -- and nothing
checked that it came back. The multi-backend example had no build.mcpp at all,
so both device globs were dropped in silence and the reading was `undefined
reference to opkit_cuda_saxpy` at the link: a message naming a symbol and never
the file that would have defined it. A `kind = "lib"` target does not even get
that, because an archive is not resolved. Now refused, naming the files, and
distinguishing "no build program" from "a program that claimed nothing" because
the fixes differ. The criterion is the action INPUTS, which is also the
condition an action needs anyway: one that compiles a file it does not declare
as an input does not rerun when that file changes.
ONE RULE TOOK EVERY DEVICE SOURCE. `device_sources()` is the package's whole
set and every rule in a build program reads it. Correct for exactly as long as
a build has one rule in it -- a premise never written down. Measured with two:
the CUDA rule compiled `scale.comp` AS CUDA and produced an object, and the
shader rule then failed on the `.cu` with a message about stages. The louder
failure was the harmless one. Fixed in mcpp-plugins 0.2.2 (each rule claims its
extensions); the engine's half is the refusal above, because "no rule claimed
it" is only visible here.
`accelerator` WAS CLASSIFIED AS A RESOLVED LAYER. The five real layers are
answered by dependency resolution, so refusing them in `[xlings]` predicates is
right: payloads are installed before resolution. The accelerator is not one of
them -- it is `--accel`, or `[build] accel`, read before the first package is
looked up. The cost was paid on every build with a device island and paid worst
on the cheapest one: a vendor toolkit could be declared unconditionally or not
at all, so a CPU-only build downloaded gigabytes for a device it was not
compiling for. Split by SCHEDULE rather than by subject; the connected
consequence is that `[target.'cfg(accelerator = "cuda")'.dependencies]` now
applies, there being nothing circular about it.
NAMING A SUBSET OF BACKENDS WAS TREATED AS A MISMATCH. The refusal is right
about architectures -- a file for sm_89 in a build targeting sm_80 is not a
variant -- and was applied across backends, so a package with a CUDA island and
a Vulkan one was refused when built with `--accel vulkan1.2`. A package could
have several device backends only if every build took all of them, which is
exactly what an additive-backend library cannot do. A glob whose backend is not
named is now left out as `--no-accel` leaves it; what keeps that from turning
`accel = "cude12.9"` into a file that is never compiled and never mentioned is
a new check against `[package] accelerators`.
A RULE'S PAYLOAD WAS UNREACHABLE FROM THE CONSUMER. A rule's code runs inside
its consumer's build program, so `xpkg_dir` is asked there while the payload was
declared in the rule's own `[feature-xlings]`. The graph already installed it;
only the answer was missing, because `fillXpkgDirs` read one manifest. The
reading was "the toolkit is not installed" with the toolkit on disk.
Also: an import no dependency provides is refused by name. Left to the compiler
it is `failed to read compiled module` plus a note that imports must be built
first -- true, and naming neither the package nor `host-module = true`. The set
of names that can compile there is closed, so a name outside it is refused with
the candidates that could have provided it.
VERIFICATION. e2e 622 through 626, five files. 622 and 625 were run against the
released 2026.9.6.4 and failed as designed. 626 has a negative leg because two
of its three legs would pass on an engine that refused every device source.
107 unit tests pass; the two cfg tests that stated the old classification now
state the new one.
examples/09-heterogeneous/multi-backend is the example all of this came from,
and it now works: four command lines, three of them on a real RTX 4080.
mcpp run backend: cpu (only backend in this build)
mcpp run --accel "vulkan1.2" backend: vulkan (NVIDIA GeForce RTX 4080)
mcpp run --accel "cuda12.9+{sm_89}" backend: cuda
mcpp run --accel "cuda12.9+{sm_89}, vulkan1.2" backend: cuda
Its Vulkan half was a stub that declined; a stub would have printed `backend:
cpu` for a Vulkan build, which reads as "Vulkan failed here" rather than "this
was never written", so the real implementation is ported from the sibling
example. Its CUDA leg takes the clang route, measured rather than chosen: on the
12.9 line nvcc's own front end refuses the toolkit's non-`noexcept` `cospi`
against the C library's, an older `xim:gcc` payload does not help because the
declarations come from the C library, and the 13.x line raises the driver floor
to r580 -- a requirement on the machine rather than a decision the project makes.
* `.asc` and `.cce` enter the device-source table, and the CANN toolkit turns out to be one download
The Ascend example named an `xim:cann-toolkit` that did not exist and a device
extension the engine did not know. Both are now answerable, and the answer to
the second was simpler than the design doc said.
THE EXTENSION. `.asc` is Ascend C, compiled by `ccec` (BiSheng) from the CANN
toolkit, and `.cce` is the older spelling of the same thing. They belong in the
table for the reason `.sycl` does: the content is ordinary C++ and the
criterion is the compiler. CANN's own operator libraries already split
`op_kernel/` from `op_host/`, and CMake registers ASC as a language of its own
-- the island is a shape Ascend already has, not one mcpp imposes on it.
Adding to this table cannot change a build that works today: device extensions
are absent from the default source glob, and one named in `sources` was until
now a hard error.
THE TOOLKIT. The design doc recorded that the vendor's container image pulls
anonymously, which is true and is not the simplest route -- it stopped one
layer short. Measured today, with no credentials at all: every toolkit from
8.0.RC1 to 8.5.0 is a plain `.run` on Huawei's own OBS, answering 200 to a HEAD
request. 8.5.0 is 1.12 GB for x86_64 and 1.10 GB for aarch64.
It installs without root and without a driver:
./Ascend-cann-toolkit_8.5.0_linux-x86_64.run --install \
--install-path=<dir> --quiet
and 2.9 GB later the two pieces the lane needs are both there:
`x86_64-linux/ccec_compiler/bin/{ccec,bisheng}` runs and reports clang 15.0.5,
and `x86_64-linux/simulator/` carries 38 SoC directories, each with its own
`libpem_davinci.so`. So the whole lane -- compiler and a hardware-free way to
run what it produces -- is one anonymous download.
The installer writes two things outside its install path, `~/Ascend` (8 KB, an
install record) and `~/var/log/ascend_seclog`. Measured: overriding `HOME` for
the duration contains both, and the payload installs identically, so a package
recipe need not be a bad citizen of the user's home directory.
This settles what the design doc listed as the lane's only remaining
prerequisite. Compliance-wise it sits in the first tier of the standing rule --
closed-source and not redistributable, so it is fetched from the vendor's own
URL with no CN mirror, which costs nothing here because the vendor's URL is
already in-country.
Verified after the mcpp-plugins 0.2.2 release: the multi-backend example now
resolves the PUBLISHED `mcpp:plugins@0.2.2` from the index with no path
override and no lock file, and runs -- `backend: cpu` with no accelerator and
`backend: cuda` on a real device.
* The first thing the new refusal caught was this repository's own fixtures
Three e2e fixtures built a `build.mcpp` that read `mcpp::device_sources()`,
printed it, and compiled nothing. They were asserting on the env contract, which
is a real thing to assert on -- and they were modelling a project whose device
files compile to nothing, which is exactly the defect the new refusal exists to
catch. So the refusal caught them, and it was right to.
606, 609 and 613 now declare a `check` action per device source, which is the
edge a real rule package declares, and go on asserting on the variable exactly
as before. The action's input has to be ABSOLUTE: `device_sources()` is
package-root-relative and an action does not run in the package root, which the
first attempt got wrong and `cp: cannot stat 'shaders/s.tesc'` said plainly.
EXAMPLE 05 PINS ITS TOOLCHAIN, and this is a different failure that surfaced in
the same run. `mcpp pack` names its output directory after the ABI tag, and the
consumer beside it names that directory literally -- so the pair only agrees on
a machine whose DEFAULT toolchain is the one that produced the tag. Measured:
a runner whose sandbox had been left with an LLVM default produced
`mathkit-0.1.0-x86_64-linux-gnu-clang22-libcxx22-c++23` while the consumer
named the `gcc16-libstdcxx16` one, and the example that had passed for months
failed without anything in it changing.
An example that hard-codes an ABI tag has to name the toolchain that produces
it. That is not a workaround for CI: a packed artifact IS specific to the ABI
it was built against, which is the entire subject of that example, so leaving
the toolchain implicit was the inconsistency.
* The Ascend example stops being a sketch: the kernel compiles, and the driver is what is missing
It was written as a shape with two named gaps -- a rule package and an xim
package for the toolkit -- and both now exist. What replaced them is a list of
measurements.
On an x86_64 machine with no Ascend hardware and no Ascend driver:
`xim:cann-toolkit` provisions 2.9 GB without root; `build.mcpp` compiles and
imports `mcpp.rules.ascendc`; the kernel compiles under
`bisheng -x asc --cce-aicore-arch=dav-c220`; the resulting object joins the
ORDINARY link, because the rule compiles in mixed mode and gets an x86-64
object carrying the device binary rather than a Da Vinci one the host linker
cannot place; the host half links against ACL and the six-library closure the
rule names. The artifact then does not start, because `libascend_hal.so` is
missing -- the DRIVER, which is the role `libcuda.so.1` plays for CUDA: in ABI
lockstep with the kernel module, not redistributable, and correctly absent
here. `mcpp run --no-accel` builds and runs: `12 24 36 48`, `device: cpu`.
So the example completes everywhere and RUNS only on an Ascend machine, which
is the same statement `examples/09-heterogeneous/cuda` makes about a machine
with no NVIDIA driver. Its skip reason says that instead of what it used to
say.
The payloads are gated on the accelerator, so the CPU leg installs nothing at
all -- which is the first real use of that gating outside the multi-backend
example.
THE SEAM IS A C FUNCTION, and that is measured rather than stylistic: BiSheng's
own launcher for a `__global__` function is C++-MANGLED even when the kernel is
declared `extern "C"`. Calling it directly would make the program depend on
BiSheng and the project's C++ compiler agreeing about mangling -- clang 15 and
whatever the project chose. The `.asc` file exports an `extern "C"` wrapper
instead, and the `<<<...>>>` launch spelling never leaves the translation unit
the device compiler owns.
The README's "what was established about the toolkit" section is replaced by
what the toolkit turned out to BE, including the 38 SoC simulators that make
this lane verifiable without an NPU -- which is the next thing this example
should use, and a separate piece of work with its own contract.
---------
Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com>1 parent 325e5ff commit 923de18
65 files changed
Lines changed: 3944 additions & 76 deletions
File tree
- .agents/docs
- .github/tools
- docs
- zh
- examples
- 05-lib-distribution
- consumer
- producer
- 09-heterogeneous
- cann/app
- include/saxpy
- src
- ascend
- cpu
- kernels
- cuda/app
- hip/app
- multi-backend
- include/opkit
- src
- backends
- cuda
- vulkan
- cpu
- dispatch
- sycl/app
- vulkan/app
- modules
- buildmcpp/src
- manifest/src
- source-kind/src
- versioning/src
- src/build
- tests
- e2e
- unit
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 895 additions & 0 deletions
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
24 | 24 | | |
25 | 25 | | |
26 | 26 | | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
27 | 40 | | |
28 | 41 | | |
29 | 42 | | |
| |||
38 | 51 | | |
39 | 52 | | |
40 | 53 | | |
| 54 | + | |
41 | 55 | | |
42 | 56 | | |
43 | 57 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5 | 5 | | |
6 | 6 | | |
7 | 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 | + | |
8 | 130 | | |
9 | 131 | | |
10 | 132 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
31 | 31 | | |
32 | 32 | | |
33 | 33 | | |
34 | | - | |
| 34 | + | |
35 | 35 | | |
36 | 36 | | |
37 | 37 | | |
38 | 38 | | |
| 39 | + | |
| 40 | + | |
39 | 41 | | |
40 | 42 | | |
41 | 43 | | |
| |||
0 commit comments