feat(cgc): inspect D3D12 adapters and the MLIR patterns their drivers… - #1454
Merged
Merged
Conversation
… declare Two questions get asked on every new machine and after every driver update: which D3D12 adapters are here, and what does this driver's MLIR-program implementation claim it can match. Both are answered through public Windows APIs with ctypes, so nothing has to be built and no extra package installed. winml cgc adapters # which adapters are here winml cgc patterns [-a ADAPTER] [-v] # what a driver declares winml cgc patterns -a ADAPTER --dump [--overwrite] # keep it on disk winml cgc patterns [-a ADAPTER] --open [FILE] # look at a dump Listing adapters needs nothing beyond Windows. Anything touching MLIR patterns needs an Agility SDK D3D12Core.dll of SDK 720 or newer, named by --d3d12-dir or $WINML_D3D12_DIR/$D3D12_DIR, or dropped in bin/ beside the venv or the install; the inbox runtime does not serve the preview feature. Which core answered opens every run on the redist: line, because the same driver reports different things through the 720 and 721 exchange shapes. When none can be found, the MLIR column reads ? rather than no -- the question could not be asked -- and the failure names every place that was searched. With no -a, `patterns` asks every adapter and prints one line each; a driver that claims the exchange and then refuses it costs only its own line, on stderr, and the run exits 1 at the end. A -a number is an index when one exists and otherwise a description substring, so `-a 5060` finds the RTX 5060. A dump is filed under patterns/<adapter-slug>/<driver-version>/ and holds patterns.mlir, metadata.txt (the six keys dxcgc-dump-driver-patterns writes, plus which redist and ABI produced it and how the stored payload differs from the one received) and patterns.json: one record per pattern -- index, name, kind, benefit, rule count, source file, kernel, line count and its own text -- plus a summary, per-kind and per-source grouping and a format tag, so nothing downstream has to parse MLIR. Text payloads are normalised once, where the exchange returns them: the trailing NUL is dropped and every line break becomes LF, so the counts, patterns.mlir and patterns.json describe the same bytes. Bytecode is left alone. Counting treats a pattern as one cgc_pattern.pattern declaration and a rule as one flat match alternative after any_of expansion, because comparing declaration counts alone across drivers can read a loss where the matching surface grew. Comments are blanked rather than deleted so offsets stay valid, the displayed text keeps its comments, and a brace inside a comment cannot unbalance a slice. --open shows a dump in the CGC Pattern Atlas, a self-contained page that ships inside the package and is resolved with importlib.resources, so it works from a wheel, an editable install and a checkout alike. A dump is embedded into a copy of the page named after it, with every "<" escaped so a pattern's own text cannot close the script element. --open FILE shows a dump taken earlier and asks no driver anything; anything that is not a patterns.json is a usage error. Verified on an RTX 5060 (driver 32.0.16.3004), an AMD 880M and WARP against the SDK 720 and 721 redists in DXCGC-Drop10.0-x64 and against Drop11: 51 patterns / 63 rules on the 720 path, byte-identical dumps across refactors, and the documented exit codes on every failure path. docs/commands/cgc.md covers both commands, every flag and scenario, the dump layout and patterns.json. TODO(tests) markers record the gaps left untested. Claude-Session: https://claude.ai/code/session_01AUbPTmiBrAd6G1MFudgbkn
Use consistent module-qualified ctypes imports and keep hardware-test adapter access in the successful enumeration path. Preserve existing hardware skips, runtime behavior, and lambdas without suppressing either CodeQL alert.
Zac (tezheng)
marked this pull request as ready for review
September 23, 2026 09:23
Zac (tezheng)
added a commit
to tezheng/winml-cli
that referenced
this pull request
Sep 24, 2026
Pattern parsing: - Quoted text is no longer read as structure. A "}" inside a string ended a pattern early and changed its kind; a quoted declaration followed by a real brace became a phantom record; a quoted any_of multiplied the rule count. The payload is now read through two views built in one pass (_blank_views): values, with only comments blanked, because drivers name a pattern's kind and kernel inside strings; and structure, with string contents blanked too, which alone decides where declarations, braces and any_of blocks are. One brace walker, _block, replaces _balanced and _any_of_branches. - The rule count stays the cross product of a pattern's any_of groups: the dialect allows any_of only at the top level of a pattern (cgc_pattern_ops.td, AnyOfOp: HasParent<PatternOp>). The docs now say so. Dumps: - A dump is no longer lost to a reader that stops early. report_patterns collects its whole answer and prints it only once the dump is on disk, so `--dump | head -1` with --overwrite leaves a complete dump. What is printed, and its order, is unchanged. - A dump that cannot be written -- a read-only or locked destination, a full disk -- is reported as "could not write the dump", exit 1, not a traceback. --open: - The seeded atlas page is named from the dump's resolved path plus a hash of it, so two captures under different roots no longer share a page, and one dump opened by two spellings of its path reuses one. - A path the OS refuses to stat is a usage error (exit 2); an unreadable file says "could not be read", a malformed one "is not JSON"; a UTF-8 BOM is accepted. --dump with an --open filename is a usage error, and the docs say so instead of claiming the new dump is shown. Adapters: - list_adapters releases everything it acquired when any call after the factory fails, including CreateAdapterList and Sort, and tries the GENERIC_ML then CORE_COMPUTE list in one loop. Cleanup, no change to output: one-caller helpers (is_mlir_bytecode, clear_dump, write_patterns_json, redist_line, adapter_type, adapter_attrs, make_version_number) are inlined; CGC_MAX_IR_VERSION is stored packed; vcall builds its prototype from the vtable slot index; --d3d12-dir is declared once; hashlib and webbrowser load only for --open. Tests cover each of the above, drive helpers through their callers, and are clean under pyright strict and default mode on cgc.py and test_cgc.py; mypy and ruff are clean. Checked on an RTX 5060: the listing and the sweep print as before, and a dump -- also through `| head -1` -- is byte-identical, 51 patterns / 63 rules. Claude-Session: https://claude.ai/code/session_01Vx4R8Vb5rTZruzRTWJzWtR
Zac (tezheng)
added a commit
to tezheng/winml-cli
that referenced
this pull request
Sep 24, 2026
Pattern parsing: - Quoted text is no longer read as structure. A "}" inside a string ended a pattern early and changed its kind; a quoted declaration followed by a real brace became a phantom record; a quoted any_of multiplied the rule count. The payload is now read through two views built in one pass (_blank_views): values, with only comments blanked, because drivers name a pattern's kind and kernel inside strings; and structure, with string contents blanked too, which alone decides where declarations, braces and any_of blocks are. One brace walker, _block, replaces _balanced and _any_of_branches. - The rule count stays the cross product of a pattern's any_of groups: the dialect allows any_of only at the top level of a pattern (cgc_pattern_ops.td, AnyOfOp: HasParent<PatternOp>). The docs now say so. Dumps: - A dump is no longer lost to a reader that stops early. report_patterns collects its whole answer and prints it only once the dump is written, or has failed, so `--dump | head -1` with --overwrite leaves a complete dump. What is printed, and its order, is unchanged. - A write that fails part-way through a dump -- a read-only destination, a locked file, a full disk -- is reported as "could not write the dump", exit 1, not a traceback, and the driver's answer is still printed. --open: - The seeded atlas page is named from the dump's resolved path plus a hash of it, so two captures under different roots no longer share a page, and one dump opened by two spellings of its path reuses one. - A path the OS refuses to stat is a usage error (exit 2); an unreadable file says "could not be read", a malformed one "is not JSON"; a UTF-8 BOM is accepted. --dump with an --open filename is a usage error, and the docs (prose, flags table and exit codes) say so instead of claiming the new dump is shown. Adapters: - list_adapters releases everything it acquired when anything after the factory raises, Ctrl-C included, never releases a list twice, and tries the GENERIC_ML then CORE_COMPUTE list in one loop. Cleanup, no change to output: one-caller helpers (is_mlir_bytecode, clear_dump, write_patterns_json, redist_line, adapter_type, adapter_attrs, make_version_number) are inlined; CGC_MAX_IR_VERSION is stored packed; vcall builds its prototype from the vtable slot index; --d3d12-dir is declared once; hashlib and webbrowser load only for --open. Tests cover the parsing, dump and --open changes and drive helpers through their callers; list_adapters needs DXCore and has no unit test. pyright (strict and default), mypy and ruff are clean on cgc.py and test_cgc.py. Checked on an RTX 5060 before the final error-path fixes, which leave the success path as it was: the listing and the sweep print as before, and a dump -- also through `| head -1` -- is byte-identical, 51 patterns / 63 rules. Claude-Session: https://claude.ai/code/session_01Vx4R8Vb5rTZruzRTWJzWtR
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
… declare
Two questions get asked on every new machine and after every driver update: which D3D12 adapters are here, and what does this driver's MLIR-program implementation claim it can match. Both are answered through public Windows APIs with ctypes, so nothing has to be built and no extra package installed.
winml cgc adapters # which adapters are here
winml cgc patterns [-a ADAPTER] [-v] # what a driver declares
winml cgc patterns -a ADAPTER --dump [--overwrite] # keep it on disk
winml cgc patterns [-a ADAPTER] --open [FILE] # look at a dump
Listing adapters needs nothing beyond Windows. Anything touching MLIR patterns needs an Agility SDK D3D12Core.dll of SDK 720 or newer, named by --d3d12-dir or $WINML_D3D12_DIR/$D3D12_DIR, or dropped in bin/ beside the venv or the install; the inbox runtime does not serve the preview feature. Which core answered opens every run on the redist: line, because the same driver reports different things through the 720 and 721 exchange shapes. When none can be found, the MLIR column reads ? rather than no -- the question could not be asked -- and the failure names every place that was searched.
With no -a,
patternsasks every adapter and prints one line each; a driver that claims the exchange and then refuses it costs only its own line, on stderr, and the run exits 1 at the end. A -a number is an index when one exists and otherwise a description substring, so-a 5060finds the RTX 5060.A dump is filed under patterns/// and holds patterns.mlir, metadata.txt (the six keys dxcgc-dump-driver-patterns writes, plus which redist and ABI produced it and how the stored payload differs from the one received) and patterns.json: one record per pattern -- index, name, kind, benefit, rule count, source file, kernel, line count and its own text -- plus a summary, per-kind and per-source grouping and a format tag, so nothing downstream has to parse MLIR. Text payloads are normalised once, where the exchange returns them: the trailing NUL is dropped and every line break becomes LF, so the counts, patterns.mlir and patterns.json describe the same bytes. Bytecode is left alone.
Counting treats a pattern as one cgc_pattern.pattern declaration and a rule as one flat match alternative after any_of expansion, because comparing declaration counts alone across drivers can read a loss where the matching surface grew. Comments are blanked rather than deleted so offsets stay valid, the displayed text keeps its comments, and a brace inside a comment cannot unbalance a slice.
--open shows a dump in the CGC Pattern Atlas, a self-contained page that ships inside the package and is resolved with importlib.resources, so it works from a wheel, an editable install and a checkout alike. A dump is embedded into a copy of the page named after it, with every "<" escaped so a pattern's own text cannot close the script element. --open FILE shows a dump taken earlier and asks no driver anything; anything that is not a patterns.json is a usage error.
Verified on an RTX 5060 (driver 32.0.16.3004), an AMD 880M and WARP against the SDK 720 and 721 redists in DXCGC-Drop10.0-x64 and against Drop11: 51 patterns / 63 rules on the 720 path, byte-identical dumps across refactors, and the documented exit codes on every failure path. docs/commands/cgc.md covers both commands, every flag and scenario, the dump layout and patterns.json. TODO(tests) markers record the gaps left untested.