Skip to content

[Shift-Left] Auto-detect many typedefs and manual remaps by walking the AST before PInvokeGenerator runs#2240

Merged
jevansaks merged 16 commits into
mainfrom
user/jevansa/win32metadata-scraper
Apr 20, 2026
Merged

[Shift-Left] Auto-detect many typedefs and manual remaps by walking the AST before PInvokeGenerator runs#2240
jevansaks merged 16 commits into
mainfrom
user/jevansa/win32metadata-scraper

Conversation

@jevansaks

@jevansaks jevansaks commented Mar 31, 2026

Copy link
Copy Markdown
Member

Summary

This PR introduces automatic discovery of typedef→tag remaps and function pointer fixups by walking the Clang AST before PInvokeGenerator runs. This eliminates 12,176 manual --remap entries from scraper.settings.rsp and 152 manual entries from functionPointerFixups.json, replacing them with entries that are auto-discovered at scrape time.

The net winmd output is identical — the same types, the same metadata, the same binary. What changes is how the toolchain arrives at the correct configuration: instead of a 12,700-line hand-maintained remap list, the scraper discovers most remaps from the headers themselves.

Motivation

The manual remap list in scraper.settings.rsp was a growing maintenance burden:

  • Every new Windows SDK release could introduce new typedef→tag pairs requiring manual additions
  • Mistakes were hard to detect — a missing remap silently produces a less-useful type name in the winmd
  • The list had grown to 12,718 entries with no automated way to validate completeness

How it works

New tool: Win32MetadataScraper — a .NET 8 console app that hosts ClangSharp's PInvokeGenerator as a library. For each partition:

  1. Parses the translation unit using ClangSharp's CXIndex
  2. RemapDiscovery walks the AST to find:
    • Typedef→tag remaps: typedef struct _FOO { ... } FOO;--remap _FOO=FOO
    • Function pointer fixups: typedef void (*PFOO_CALLBACK)(...) where FOO_CALLBACK is a bare function typedef → --exclude PFOO_CALLBACK + --reducePointerLevel PFOO_CALLBACK
  3. Applies heuristic filters (disambiguation for 1:N tag→typedef, suffix-based selection, case-insensitive matching)
  4. Merges auto-discovered remaps with manual --remap entries (manual always wins on conflict)
  5. Passes the merged config to PInvokeGenerator.GenerateBindings on the same TranslationUnit — single parse, no re-parsing

ScrapeHeaders MSBuild task collects the per-partition discoveries and:

  • Writes scraper.autoRemaps.generated.rsp (fed back to the scraper for cross-partition consistency)
  • Writes emitter.autoFnPtr.{arch}.generated.rsp (consumed by the emitter for --reducePointerLevel)
  • Deduplicates discovered entries against manual fixup RSPs to avoid conflicts
  • Validates cross-partition remap consistency (detects when a tag is remapped in one partition but not another)

What remained manual (and why)

Category Count Reason
--remap entries 542 Non-typedef remaps (e.g., long=int, semantic overrides, COM-specific), remaps for types in C++ namespaces, or cases where the auto-heuristic can't disambiguate
functionPointerFixups.json entries 32 Irregular naming patterns (e.g., DRVCALLBACK→LPDRVCALLBACK), alreadyPointer entries, callback types without a P-prefixed pointer typedef, or functions not declared via standard typedef patterns

Partition header adjustments

Seven partitions needed additional #include directives so that typedef declarations are visible during AST walking. Without these, the auto-discovery can't find the typedef→tag pair because the typedef is declared in a header not included by that partition. These are additive-only changes to main.cpp files.

CI pipeline fix

The auto-generated emitter.autoFnPtr.generated.rsp was originally written to a non-arch-specific path. In the CI pipeline (3 parallel scrape jobs for x64/x86/arm64), the artifact merge would overwrite the file — and the x86 version was missing x64/arm64-only entries (e.g., PGET_RUNTIME_FUNCTION_CALLBACK for RtlInstallFunctionTableCallback). Fixed by making the filename arch-specific and using conditional includes in sdk.targets.

Other changes

  • ChangesSinceLastRelease.txt: Empty! (no net winmd changes)
  • Test suite: New Win32MetadataScraperTests project with 41 tests covering RemapDiscovery heuristics, RSP file parsing, and header snippet parsing
  • DoTests.ps1: Updated to run the new scraper test suite
  • BuildSdk.nuspec: Includes Win32MetadataScraper in the SDK package
  • Copilot instructions: Added .github/copilot-instructions.md for repo-specific context
  • Planning docs: docs/copilot/plans/ contains research and implementation plan documents

Verification

Check Result
Winmd binary Identical output (24,328,704 bytes)
ChangesSinceLastRelease.txt Empty (no net API changes)
MetadataUtils.Tests 17/17 ✅
Windows.Win32.Tests 13/13 ✅
Win32MetadataScraperTests 41/41 ✅
ClangSharpSourceToWinmdTests 6/6 ✅
Auto-discovered remaps ~12,176 (replacing manual entries)
Auto-discovered fn-ptr fixups 152 reducePointerLevel entries

Files changed (32 files, +4,033 / −14,104)

Area Files Summary
New tool sources/Win32MetadataScraper/ (3 files) Program.cs (scraper host), RemapDiscovery.cs (AST walking + heuristics), .csproj
New tests tests/Win32MetadataScraperTests/ (4 files) 41 tests for discovery heuristics, RSP parsing, header snippet parsing
Build orchestration ScrapeHeaders.cs, TaskUtils.cs, sdk.targets Auto-remap collection, deduplication, cross-partition consistency checks, arch-specific RSP generation
Configuration scraper.settings.rsp, functionPointerFixups.json −12,176 manual remaps, −152 manual fn-ptr fixups (replaced by auto-discovery)
Partitions 7 main.cpp files Additional #include directives for typedef visibility
Packaging BuildSdk.nuspec, BuildTools.proj, version.json Include new tool in SDK, version bump to 71.0
CI/infra DoTests.ps1, .config/dotnet-tools.json, Directory.Build.props Run new tests, tool manifest, build config
Docs docs/copilot/plans/ (4 files), .github/copilot-instructions.md Planning documents, repo instructions

jevansaks and others added 3 commits March 31, 2026 10:35
Hosts ClangSharp.PInvokeGenerator v17.0.1 as a library (NuGet package
references). Parses the translation unit, walks the AST to discover
typedef-tag remappings, merges them with manual --remap entries, then
runs PInvokeGenerator.GenerateBindings on the same TranslationUnit in
a single parse pass.

- Win32MetadataScraper: new console app tool
- remapExclusions.rsp: exclusion list for unsafe auto-remaps
- ScrapeHeaders.cs: launches scraper, collects .remaps, writes auto RSP
- sdk.targets: includes auto-remaps RSP in @(ScraperRsp)
- Graceful fallback to stock ClangSharpPInvokeGenerator if scraper not found

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… ptr fixups

- Rewrite AST walker to collect ALL typedefs per tag, disambiguate
  multi-typedef tags automatically (no exclusion list needed)
- Add UnwrapType helper for ElaboratedType/AttributedType/ParenType
  sugar types that were hiding typedef-tag and fn-ptr relationships
- Auto-discover function pointer typedef pairs from AST: detect
  FunctionProtoType typedefs and their PointerType aliases, generate
  --remap, --exclude, and --reducePointerLevel entries automatically
- Remove 12,197 of 12,705 manual --remap entries from scraper.settings.rsp
  (508 genuinely manual entries remain: semantic primitives, pointer
  remaps, nested qualifieds, uppercase renames)
- Remove 160 of 184 entries from functionPointerFixups.json
  (24 remain: 6 alreadyPointer, 13 name-only, 5 edge cases)
- Delete remapExclusions.rsp (no longer needed)
- Zero conflicts: auto-discovery never picks wrong name
- Winmd output identical to baseline (24,328,704 bytes)
- All 36 tests pass

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Extract WalkTranslationUnit, ResolveTagRemaps, FilterTagRemaps,
ResolveFunctionPointerFixups, and UnwrapType from Program.cs into a
public RemapDiscovery class that can be tested independently.

Add Win32MetadataScraperTests project with 22 xUnit tests covering:
- Tag-typedef discovery: _Uppercase, _lowercase, tag prefix, lowercase
  tag, trailing underscore, enum typedef patterns
- Multi-typedef disambiguation: stripped prefix match, ambiguous skip,
  manual hint resolution
- Semantic override protection: manual remap takes priority
- Built-in type filtering: _GUID excluded
- Identity remap filtering: same tag/typedef skipped
- Function pointer discovery: two-step LP/P/PFN patterns, direct fn
  ptr, calling convention (AttributedType unwrap), non-standard naming
  skip, typedef alias pattern, ambiguous pointer targets skip
- UnwrapType: ElaboratedType and AttributedType sugar peeling

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jevansaks jevansaks changed the title Add Win32MetadataScraper: single-pass auto-remap discovery Shift-Left Metadata: Auto-detect many typedefs and manual remaps by walking the AST before PInvokeGenerator runs Apr 1, 2026
@jevansaks jevansaks changed the title Shift-Left Metadata: Auto-detect many typedefs and manual remaps by walking the AST before PInvokeGenerator runs [Shift-Left] Auto-detect many typedefs and manual remaps by walking the AST before PInvokeGenerator runs Apr 1, 2026
@jevansaks
jevansaks force-pushed the user/jevansa/win32metadata-scraper branch 3 times, most recently from b5692ed to 2a5e7cc Compare April 2, 2026 00:15
… direction, simplified consistency check

Heuristic fixes:
- Add case-insensitive match to multi-typedef disambiguator (fixes in6_addr→IN6_ADDR
  when competing with IPv6Addr typedef)
- Fix fn ptr alreadyPointer direction: when proto has P/LP prefix and alias doesn't,
  remap alias→proto (e.g., EXCEPTION_ROUTINE→PEXCEPTION_ROUTINE, not reverse)
- Filter suffix-adding remaps (e.g., GLUnurbs→GLUnurbsObj) since tag is canonical
- Skip C++ namespace-qualified tags (Gdiplus::Status, ABI::*)

Cross-partition consistency:
- Simplified CheckCrossPartitionRemapConsistency: scans declared type names in
  generated .cs files and checks against discovered remap tags. No regex needed.
- Warning-only (PInvokeGenerator handles these internally)
- Added 7 partition #include fixes for cross-partition typedef visibility
- 5 irreducible manual remaps for types where AST walker can't discover the typedef
  in certain partition configurations

Tests: 30 scraper tests (7 new), all 60 pass. Winmd 24,328,704 bytes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jevansaks
jevansaks force-pushed the user/jevansa/win32metadata-scraper branch from 2a5e7cc to f5e51c6 Compare April 2, 2026 03:56
jevansaks and others added 10 commits April 2, 2026 09:25
- Remove unused FilterRemaps() method from Program.cs (duplicated RemapDiscovery.FilterTagRemaps)
- Tighten HasPointerPrefix to require uppercase after single P prefix
- Change CheckCrossPartitionRemapConsistency to void (always returned true)
- Fix trailing space in LSA_GET_EXTENDED_CALL_FLAGS in functionPointerFixups.json
- Add plan docs from win32metadata repo (auto-type-remappings, shift-left, annotations)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Restore key manual remaps, tighten auto-discovery heuristics, and capture the remaining rename-drift findings.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The auto-generated emitter.autoFnPtr.generated.rsp was written to a
non-arch-specific path. When the three CI scrape jobs (x64, x86, arm64)
upload artifacts and the build-test job merges them, only one arch's
version survives. The x86 version is missing entries for x64/arm64-only
APIs (e.g. PGET_RUNTIME_FUNCTION_CALLBACK for RtlInstallFunctionTableCallback),
causing the NoInvalidPointersToDelegates test to fail.

Fix: include the ScanArch in the rsp filename and use conditional includes
in sdk.targets - the specific arch file for local builds, a wildcard glob
for CI merged-artifact builds (SkipScraping=true).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…r types

Three improvements to RemapDiscovery.ResolveFunctionPointerFixups:

1. Split FnProtoToPointerTypedefs into pointer-adding aliases (typedef FOO *PFOO)
   and same-level aliases (typedef FOO BAR) via new FnSameLevelAliases field.
   This fixes LSA_AP_CALL_PACKAGE which had 1 pointer alias + 1 same-level alias
   incorrectly counted as 2 aliases (ambiguous).

2. Filter configuredExcludes from alias candidates before the ambiguity check.
   This fixes DRVCALLBACK which has LPDRVCALLBACK + PDRVCALLBACK, but PDRVCALLBACK
   is already excluded in scraper.settings.rsp.

3. Handle already-pointer typedefs with pointer-adding aliases (new pass 2).
   Types like ACMDRIVERPROC (already *) with LPACMDRIVERPROC (adds another *)
   now auto-generate remap + exclude + reducePointerLevel.

Also removes 5 entries from functionPointerFixups.json (now auto-discovered):
- FN_CUSTOM_HELP (stale, not in SDK 10.0.26100.0)
- DRVCALLBACK, LSA_AP_CALL_PACKAGE, ACMDRIVERPROC, INSTALLUI_HANDLER_RECORD

Auto-discovered reducePointerLevel entries: 152 -> 156.
Manual entries: 32 -> 27.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jevansaks
jevansaks marked this pull request as ready for review April 17, 2026 21:31
@jevansaks
jevansaks requested review from a team and vineeththomasalex as code owners April 17, 2026 21:31
["_LARGE_INTEGER"] = "long"
};

var remaps = HeaderSnippetParser.ParseAndResolveTagRemaps(@"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this trying to do? Looks like this isn't legal - https://godbolt.org/z/TEzKEKrWG

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The headers have _LARGE_INTEGER and the existing scraper.settings.rsp remaps _LARGE_INTEGER to "long" in the metadata. "It was like that when I got here", so I'm not questioning why it was done that way.

The new AST walker logic would cause _LARGE_INTEGER usage in the ASTs to show up in the public metadata as "LARGE_INTEGER", but that would be a breaking change.

So we keep the manual _LARGE_INTEGER=long mapping in the .rsp file and then this test validates that .rsp mapping overrides any AST discovered mappings.

@jevansaks jevansaks Apr 18, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, to clarify, it's not trying to inject a typedef to long in the .h to be parsed, but rather this is telling the tool that scrapes the AST that LARGE_INTEGER should be output into the winmd as "long".

jevansaks and others added 2 commits April 17, 2026 16:00
Documents all 27 remaining entries grouped by category, explains why
each can't be auto-discovered yet, and provides concrete steps for
a future agent to address them.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Rewrites the remaining-function-pointer-fixups plan to align with the
goal of eliminating functionPointerFixups.json entirely. For each
category: try a simple heuristic first, fall back to w32m: annotations
in headers for cases the heuristic can't handle.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jevansaks
jevansaks merged commit 6abc147 into main Apr 20, 2026
5 checks passed
@jevansaks
jevansaks deleted the user/jevansa/win32metadata-scraper branch April 20, 2026 22:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants