refactor(dispatch): Generate and validate the dispatch surface from a single declaration - #372
refactor(dispatch): Generate and validate the dispatch surface from a single declaration#372ahuber21 wants to merge 23 commits into
Conversation
|
@copilot new or modified files should have |
c2d57c2 to
fe66e35
Compare
|
Tick the box to add this pull request to the merge queue (same as
|
The set of distance kernels compiled ahead of time -- extents x ISA levels
-- was written out by hand in every place that needed it: three extern
template blocks, two per-arch translation units, the `supported_dim_list`
array, and 48 near-identical `SPEC struct` lines in the instantiation
macros. Adding an extent meant editing all of them and hoping none was
missed. One had been: `euclidean.h` was missing d=160 for AVX2 (fixed in
the preceding commit), which silently made consumers instantiate that
kernel locally at their own -march.
Declare the surface once, in `cmake/dispatch-surface.cmake`:
set(SVS_SUPPORTED_DIMS 64 96 100 128 160 200 512 768)
set(SVS_ISA_LEVELS
"AVX2|haswell|avx2"
"AVX512|cascadelake|avx512"
)
`cmake/generate-dispatch-surface.cmake` validates it and writes
`include/svs/core/distance/dispatch_surface.h`, which exports
`SVS_FOR_EACH_SUPPORTED_DIM(M)`, `SVS_FOR_EACH_DISPATCH_TARGET(M)` and
`SVS_SUPPORTED_DIM_COUNT`. Everything that used to spell the list out now
loops over one of those. 108 hand-written instantiation lines become 0.
Type pairs stay in C++, in `multi-arch/x86/preprocessor.h`. A pair exists
because an implementation exists for it -- sometimes a hand-written one --
so the list belongs beside those implementations, not in the build system.
`svs::Dynamic` is appended automatically and cannot be listed: it is what
serves every dimensionality without a fixed-extent kernel, and the library
is incorrect without it.
The generated header is committed as well as generated. The build always
compiles against the build-tree copy, placed ahead of the source include
directory, and installs it over the committed one; the committed copy is
refreshed only when the declaration is the default, so overriding the
surface for a one-off build cannot rewrite the tree. Committing it keeps a
bare `-I include` compile working without CMake -- which the downstream
repository relies on, since it compiles `multi-arch/x86/{avx2,avx512}.cpp`
by path with its own CMake.
No behaviour change: the static library exports the same 864 symbols with
the same sizes, and the two arch objects are symbol-identical before and
after, both here and in the downstream build. `[distance]` passes
(134402115 assertions, 13 test cases).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every comment this branch adds now says what the code cannot say for itself and stops there. The block comments that restated the surrounding code, or spent five lines on a hazard that takes two, are gone; the hazards themselves stay, each naming its failure mode. Comment-only. The non-comment diff against the previous tip is empty.
A kernel that is missing its `extern template` declaration does not
produce an error. The consumer instantiates it locally instead, from the
generic primary template -- and in a baseline consumer translation unit
the vectorized partial specializations are not even visible, since they
are guarded on SVS_AVX2 / SVS_AVX512_F. So the consumer silently gets a
scalar loop where the library has a vectorized kernel, compiled at
whatever -march the consumer happens to use. That is the bug that shipped
for L2 at d=160 with AVX2.
Nothing could catch it, because nothing referenced the whole surface at
once. This adds a consumer that does: tests/multi-arch/x86/link_probe.cpp
names every kernel the surface declares -- every (extent, ISA level) pair,
every element-type pair, all three distances -- and nothing else. It is
compiled at -march=x86-64, like an arbitrary consumer of the headers, and
two tests are run against it:
dispatch_surface_probe calls every kernel whose ISA level this host
satisfies, so a kernel compiled beyond what
its level guarantees faults here
dispatch_surface_linkage reads the object's symbol table and requires
the kernels it references to be exactly the
kernels the library defines
The linkage check is host-independent and covers the whole surface
everywhere; the run covers only what the host can reach.
On the default surface the two sets match exactly at 864 kernels, and on
the reduced surface used by the non-default-surface CI job, at 288. All
three failure modes were confirmed to fire: dropping the L2 extern block
reports 288 kernels instantiated by the probe itself, and checking against
an archive missing the AVX-512 translation unit reports its 432 kernels as
declared but never instantiated.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
"9 extents (8 fixed + svs::Dynamic) x 2 ISA levels" says nothing about which extents, which levels, or what instruction budget each level compiles at, so reading the log gave no way to tell a correct surface from a plausible one. Also name the AVX_AVAILABILITY enumerators that are not in the surface, since that is the question the old count invited and could not answer: NONE is dispatched to but has no translation unit, so every consumer instantiates its kernels itself, at the consumer's own -march. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Four checks, each closing a failure mode the link probe cannot see. dispatch_surface_declaration derives what the library must contain from the three hand-written sources -- the extent list and ISA levels, the type-pair lists, and the AVX_AVAILABILITY enumerator order -- and never reads the generated header. The linkage check compares the archive against a probe built from that header, so a generator that dropped an extent would drop it from both and still agree; this one has nowhere to hide. It also checks the entry-point consumer, whose kernels must all come from the archive: one it defines itself is an extern declaration that is missing. dispatch_instructions_<level>, one test per ISA level, disassembles the level's object file and holds it to a budget table keyed by -march. A level guarantees only what its runtime predicate tests, so an instruction outside that budget faults on a host the dispatcher routes there -- and no symbol-table check can see it. dispatch_surface_execution is the only check that observes a kernel run rather than exist: a specialization lost behind an `#if` still links and still counts. It breaks on every level's kernel for one extent and confirms the run enters the level this host satisfies. Weaker levels are covered by hosts that satisfy only those. dispatch_entry_probe reaches the kernels through the entry points rather than by naming the Impl classes, which is what makes the consumer half of the declaration check meaningful. nm, objdump and gdb are each optional: a missing tool skips its tests rather than failing the build. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Answer the review on the declaration's maintenance story: cmake/dispatch-surface.cmake now states what to edit for an extent, a level, a type pair or an instruction budget, and why AVX_AVAILABILITY::NONE has no row. Move the four checker scripts to cmake/dispatch-checks/ with a README, and make the preprocessor.h type-pair comment stand without the refactoring for context. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…e time
The declaration added in the preceding commit is only worth something if it
is checked rather than trusted, and if the knob that overrides it is
actually turned by something other than a person debugging.
Move the validation out of `generate-dispatch-surface.cmake` into
`validate-dispatch-surface.cmake`, which touches no build-system state and
so runs in script mode:
cmake -DSVS_DISPATCH_SURFACE_FILE=<file> -DSVS_X86_SRC_DIR=<dir> \
-P cmake/validate-dispatch-surface.cmake
`tests/cmake/dispatch-surface/` holds two declarations that must be accepted
and twelve that must be rejected, each carrying the substring its rejection
has to mention. `.github/scripts/check_dispatch_surface.sh` runs the lot --
fifteen cases, counting the default declaration -- in a fraction of a
second, needing no compiler and no build tree. It is a pre-commit hook and a
CI job.
Script mode has no `cmake_minimum_required`, so CMP0007 and CMP0057 default
to OLD there. Both matter: without CMP0007 an empty `|`-field disappears
when the entry is split, and without CMP0057 `IN_LIST` is not an operator.
Set both, scoped with cmake_policy PUSH/POP.
The new `Dispatch Surface` workflow adds what the script cannot check:
- a configure with the default declaration must leave the committed
`dispatch_surface.h` untouched. This catches a declaration changed
without a reconfigure, and a generated header edited by hand.
- a full build and test run against `valid-reduced.cmake`, which shares no
fixed extent with the default declaration -- so a build that quietly
fell back to the committed header would fail to compile rather than pass
by accident. That build's archive holds 288 kernels at extents 32, 384
and svs::Dynamic, against 864 at the default nine.
- that same overridden build must leave the committed header alone.
Correctness does not depend on which extents have a fixed-extent kernel: an
extent without one is served by the svs::Dynamic kernel. `ctest -LE long`
against the reduced surface passes 153 of 154, the one failure being
`Testing Binary Reader Iterator`, which fails identically on the unmodified
default-surface build.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…x512
The AVX512 level's translation unit was compiled at -march=cascadelake, which
enables AVX512-VNNI. That level promises AVX-512 F/BW/DQ about the host and
nothing more, so every kernel in that object file was compiled with permission
to use instructions a Skylake-SP does not have. The VNNI kernels that existed
guarded themselves with a runtime check, but the guard only covered the calls
that were written by hand; the compiler was free to emit vpdpwssd anywhere in
the TU on its own initiative.
Adding AVX_AVAILABILITY::AVX512_VNNI as a fourth level moves the check to the
one place a level is chosen -- the entry point -- and lets each TU be compiled
at exactly what its level promises. The int8/int8 and uint8/uint8 kernels move
to the new level; every other pair promotes to float before doing arithmetic,
where VNNI has nothing to offer, so those pairs have no kernel at this level.
That is what keeps a fourth level from costing a fourth of everything: 54 new
instantiations rather than 432.
Two consequences worth naming:
- The pairs that move need an AVX512-level kernel to fall back to, and it has
to live outside `#if SVS_AVX512_VNNI`. Inside, it would be absent from the
AVX512 TU -- which is now compiled where that macro is 0 -- and silently
replaced by the generic template. This is why the two halves of the change
cannot land separately.
- The entry points must not dispatch to a level that has no kernel for the
pair in hand, for the same reason. `svs::distance::has_vnni_kernel` answers
that, generated from the same list the kernels are, and it is `if constexpr`
so it compiles away for the pairs that do not move.
The generated header now also defines SVS_ISA_LEVEL_<enumerator> per level, so
a surface that leaves a level out is visible to the code that dispatches on it.
Dropping the VNNI level degrades correctly -- those pairs stay on AVX512, and
the probe reports 864 kernels instead of 918. Dropping AVX2 or AVX512 is an
`#error` instead, because the entry points reach those two for every type pair.
ISA levels are not configuration the way the extent list is: a level exists
because kernels, a TU and a CPUID check exist for it.
The dispatch checks pick the change up on their own, which is what they were
written for. dispatch_instructions_avx512 now judges avx512.cpp.o at
skylake-avx512 and so forbids VNNI there, and it fails on the old object file;
the cascadelake row gains `vnni` as a requirement, because a VNNI level whose
object file has no VNNI in it is 54 instantiations of dead weight. Three
mechanical follow-ons: the per-level object libraries are named after the level
rather than the -march, since two levels now share neither; the execution check
breaks on int8/int8 rather than float/float, as a float-promoting pair has no
kernel at the top level and would route one lower; and the VNNI predicate joins
the other two in tests/multi-arch/x86/host_levels.h.
Verified on the default surface: 918 kernels declared, instantiated and
reachable with none instantiated by the consumer; all 456 vpdpwssd encodings in
vnni.cpp.o, zero in avx512.cpp.o and avx2.cpp.o, where before all 456 sat in
the AVX512 level's object file; the AVX2 object identical in symbol names and
sizes to before; the new L2Impl<128,int8,int8,AVX512> vectorized 16-wide float,
not scalar. `[distance]` passes with the same 134402115 assertions as before,
all eight dispatch tests pass, and ctest is otherwise unchanged. Also verified
with SVS_NO_AVX512=YES (both TUs compile generic, zero AVX-512 encodings, all
918 still linked) and with the reduced surface (306 kernels).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
dd2db8d to
5fafe1d
Compare
There was a problem hiding this comment.
Pull request overview
Centralizes x86 distance-kernel extents and ISA levels into one generated dispatch declaration.
Changes:
- Generates kernel declarations, instantiations, and supported dimensions from one CMake surface.
- Adds linkage, instruction, declaration, and runtime dispatch checks.
- Installs the generated header and documents maintenance workflows.
Reviewed changes
Copilot reviewed 24 out of 24 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
CMakeLists.txt |
Installs the generated surface header. |
cmake/AGENTS.md |
Documents dispatch ownership. |
cmake/dispatch-surface.cmake |
Declares extents and ISA levels. |
cmake/generate-dispatch-surface.cmake |
Validates and generates the surface. |
cmake/multi-arch.cmake |
Builds ISA objects from the declaration. |
cmake/templates/dispatch_surface.h.in |
Defines the generated-header template. |
cmake/dispatch-checks/README.md |
Documents dispatch checks. |
cmake/dispatch-checks/check-dispatch-declaration.cmake |
Validates declared kernel counts. |
cmake/dispatch-checks/check-dispatch-execution.cmake |
Verifies runtime routing. |
cmake/dispatch-checks/check-dispatch-instructions.cmake |
Inspects ISA instruction budgets. |
cmake/dispatch-checks/check-dispatch-linkage.cmake |
Verifies kernel linkage. |
include/svs/core/distance/cosine.h |
Generates cosine extern templates. |
include/svs/core/distance/dispatch_surface.h |
Commits the default generated surface. |
include/svs/core/distance/distance_core.h |
Generates supported dimensions. |
include/svs/core/distance/euclidean.h |
Generates L2 extern templates. |
include/svs/core/distance/inner_product.h |
Generates IP extern templates. |
include/svs/multi-arch/x86/avx2.cpp |
Generates AVX2 instantiations. |
include/svs/multi-arch/x86/avx512.cpp |
Generates AVX512 instantiations. |
include/svs/multi-arch/x86/preprocessor.h |
Defines reusable instantiation macros. |
tests/CMakeLists.txt |
Enables multi-architecture tests. |
tests/multi-arch/CMakeLists.txt |
Registers dispatch probes and checks. |
tests/multi-arch/x86/entry_probe.cpp |
Exercises public dispatch entry points. |
tests/multi-arch/x86/host_levels.h |
Models host ISA predicates. |
tests/multi-arch/x86/link_probe.cpp |
References every declared kernel. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| "x86-64||ymm zmm mask vnni" | ||
| "haswell|ymm|zmm mask vnni" | ||
| "skylake-avx512|zmm|vnni" | ||
| "cascadelake|zmm|" |
There was a problem hiding this comment.
Will be fixed in 375 -- and all PRs will be merged together as a stack.
rfsaliev
left a comment
There was a problem hiding this comment.
The change is pretty big with high review/maintenance cost.
It seems like the huge AI generated change to be reviewed by AI.
Scripts should be simplified and well structured.
| @@ -0,0 +1,72 @@ | |||
| /* | |||
There was a problem hiding this comment.
As I understand, this file is autogenerated from dispatch_surface.h.in.
Why do we need to track it in repository?
Suggesting to remove the file from repository but generate it in binary directory during config/build.
There was a problem hiding this comment.
I added a default version for reference. If someone researched the code on GH only, or on a fresh checkout without prior compilation there are no missing files.
| @@ -0,0 +1,66 @@ | |||
| <!-- | |||
There was a problem hiding this comment.
Seems like files in this directory intended for tests.
IMHO it makes sense to move them to /test
| @@ -0,0 +1,250 @@ | |||
| # Copyright 2026 Intel Corporation | |||
There was a problem hiding this comment.
Seems like the cmake code here is pretty complicated.
Writing it in form of straightforward script leads high maintenance costs.
Please, modify the code to make it more structured, e.g. split to functions.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
…heir helpers The checkers are test drivers, so they belong under tests/multi-arch/ with the probes they run. Extract three divergent copies of symbol-reading logic into a shared lib.cmake, eliminating duplication of svs_nm_symbols, svs_require, svs_require_files, and svs_report_first. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Route the hand-rolled ISA level and TU spec splits through centralized parsers that validate field counts, so malformed rows now fail where they are written instead of as empty variables later. The validator now shares the parser with the other two users, and its malformed-row fixture was updated to the shared message. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two levels cannot share one -march instruction budget because the compiler then emits instructions from the weaker level at that budget, giving hosts routed to the weaker level instructions its runtime predicate does not guarantee. The instruction checker cannot catch this because it looks the budget up by -march and would check the weaker level against the (stronger) budget row, which forbids nothing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A CMake function captures the policy stack where it is defined, so the parser's behaviour depended on which caller included it first. Under CMP0007 OLD an empty field vanishes when the spec is split, which turns a row with an empty middle field into a malformed-row error and silently invalidates the premise of the invalid-level-empty-field fixture. Setting the policy in the parser's own file makes the two error paths stable regardless of the caller. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every other test under tests/ is a Catch2 case, so a reader finding no Catch2 macros here has no way to tell whether that is deliberate. It is for the probes, whose tests are the link step and a gdb session, and provisional for the four cmake -P checkers. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The generator emits dispatch_surface.manifest.cmake with the extent list and the ISA level enumerators, and the declaration check reads that instead of including the declaration itself. The check now depends on the surface's contents rather than on that file's format, location, or what else it happens to set. The AVX_AVAILABILITY read from distance_core.h stays: it is what derives the mangled enum digit rather than assuming it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
44dd9a6 to
882b761
Compare
|
Thanks — this is a useful review and most of it is going in. One piece of context that changes several items: #372 is the middle of a stack, and #373/#375 already resolve some of what you raised. To make that reviewable rather than confusing, I've folded #373 and #375 into this PR, so what you see now is the whole dispatch-surface change. The diff is correspondingly larger; #373 and #375 are closed as merged here. Taken2 — shared checker helpers. Done. 7 — checker location. Done. 4 — one ISA-level parser. Done. On the count: there are six 5 — manifest instead of header re-parsing. Partly done, and I'd rather be precise about which part. The generator now emits The Not taken, with reasons1 — port the checkers to Python. Declining, on your own escape clause ("if a Python dependency is unacceptable"). SVS's C++ build and test suite have no Python dependency today: no I do agree with the diagnosis, though — emulating dicts via 3 — the hardcoded mangling. I tried this and measured it, and it doesn't work. The plan was to match That isn't worth the trade: it swaps a dependency on the Itanium mangling, which is a published ABI, for a dependency on the demangler's own rendering, which is a libstdc++ implementation detail and less stable. Worse, the first cut of it deleted the code that reads the enum out of So the mangling stays, and I'd rather say that than claim the item is closed. What did improve is that it is now concentrated in one file with the enum read from the source of truth, instead of also being spread across a surface re-parse. 3, second half — "consider whether both checkers are needed." This one I think is wrong. The linkage check compares the probe's references against the archive's definitions, but both are generated from the same header — so a generator that drops an extent drops it from both, and they still agree. The declaration check derives its expectation from three hand-written sources ( 4, second half — parallel lists instead of the 6 — split 7 — 7 — committed generated header. Also already addressed in #373 (now part of this PR). 7 — comment volume in Found while doing thisNot one of your points, but worth flagging since it's in the same file. The validator rejected a duplicate ISA level and a duplicate TU infix but not a duplicate |
The targets were dispatch_surface_probe and dispatch_entry_probe while the sources are x86/link_probe.cpp and x86/entry_probe.cpp, so neither binary could be found from the name of the file that produced it. The ctest names keep the dispatch_ prefix -- dispatch_link_probe, dispatch_entry_probe -- because `ctest -R dispatch` is how CI and the docs select this group, and renaming the tests to match the binaries would drop two of the eight out of that filter. The object libraries follow from the target name via svs_add_dispatch_probe, so the symbol-table checks pick up link_probe_objects and entry_probe_objects without further change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Pull request overview
Copilot reviewed 46 out of 46 changed files in this pull request and generated 4 comments.
Suppressed comments (3)
tests/multi-arch/dispatch-checks/check-dispatch-declaration.cmake:1
- The error message references
${SVS_SURFACE_FILE}, but this script is driven bySVS_MANIFESTand never definesSVS_SURFACE_FILE, so the message will be missing the file context. Use${SVS_MANIFEST}(or reword to “declared in the manifest”) so failures point to the correct input.
tests/multi-arch/CMakeLists.txt:1 - This
else()triggers when eithersvs_nmorsvs_gdbis missing, but the status message only mentions gdb. Make the message reflect the actual condition (e.g., mention missingnmand/orgdb) so skipped-test diagnostics are accurate.
tests/multi-arch/CMakeLists.txt:1 - This
else()triggers when eithersvs_nmorsvs_gdbis missing, but the status message only mentions gdb. Make the message reflect the actual condition (e.g., mention missingnmand/orgdb) so skipped-test diagnostics are accurate.
The probes printed only their accumulated distance sum, which says nothing about how many kernels ran: a macro list that expanded to fewer calls than the surface declares still produced a plausible float. The count makes that visible, and it cross-checks against a figure derived from a different source -- the declaration checker independently computes 918 kernels, which is what link_probe now reports. entry_probe reports 432 rather than 918 because each entry point picks one ISA level at runtime, so it reaches nine extents by sixteen type pairs by three distances, not the whole surface. Its count comes from a named constant next to entry_one, since that function's body is what fixes the calls per expansion. The comments explaining that printing defeats dead-code elimination are gone from both files: the printed message now says it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The flag was only reached through the execution check's script, so a ctest run never showed the three lines it parses and a probe that stopped honouring the flag surfaced as a missing level rather than as itself. The mode returns before the kernel sweep, so this is a second invocation rather than an argument on the existing one, which keeps the kernel count visible too.
…est" This reverts 669358e. The flag is reached again only through the execution check's script, so a ctest run shows the kernel counts but not the three lines that check parses.
|
By the way, here is the verbose output what the new tests are doing. I agree the So what's left are the changes to existing headers and implementations. Those are very small. It's mostly just removing the now redundant instantiations and some updated dispatching logic because of the VNNI change. So, honestly, I wouldn't be too concerned about "large AI generated PR". |
The comment justified naming the object target after the ISA level by asserting that more than one level can share an instruction budget. That stopped being true when validate-dispatch-surface.cmake began rejecting duplicate -march values as a hard error, so the stated reason no longer holds. Record the constraint that does apply: the target name is unique only because duplicate infixes are rejected too. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
SVS_SURFACE_FILE was never set anywhere in the tree, so the diagnostic rendered as "ISA level 'X' is declared in but is not an AVX_AVAILABILITY enumerator" -- the file context the message exists to give was always blank. The script is driven by SVS_MANIFEST. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The guard requires both tools, but the skip message named only gdb, so a host missing nm reported a cause that was not the one that fired. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The set of distance kernels compiled ahead of time is declared once, in
cmake/dispatch-surface.cmake, and the generated header, the per--marchtranslationunits and the ctest checks all derive from it. Configure-time validation rejects a
malformed declaration, and VNNI becomes its own ISA level so that each level's
-marchbudget matches what it promises.
Gotchas:
touched the same files, so redoing that work here rather than folding them in would have
meant resolving the same conflicts twice. Both are closed as merged here. The diff is
large for that reason, not because the change grew.
cmake/dispatch-checks/is nowtests/multi-arch/dispatch-checks/. They are test drivers; only generation stays undercmake/.add_test, not Catch2.link_probe.cppexists to fail to linkwhen a declared kernel is uninstantiated, and
entry_probe.cppgets driven under gdb —neither works from inside the single
testsbinary. Porting the fourcmake -Pcheckersto Catch2 is a follow-up.
include/svs/core/distance/dispatch_surface.his generated but committed, so a bare-I includecompile works without cmake. CI fails if it goes stale, and asserts that abuild with an overridden surface leaves it alone.
-march. Previously accepted, and itshipped a fault: the weaker level got compiled with instructions its runtime predicate does
not guarantee, and the instruction checker missed it because it looks the budget up by
-march. Not a review point; found while probing whether these checks can still fail.declaration checker as redundant, and parallel lists for the ISA table. Reasons are in
the review thread.