Skip to content

Share interop discovery member reservations across a module - #2641

Open
Sergio Pedri (Sergio0694) wants to merge 6 commits into
staging/3.0from
user/sergiopedri/interop-discovery-limits
Open

Sergio Pedri (Sergio0694) wants to merge 6 commits into
staging/3.0from
user/sergiopedri/interop-discovery-limits

Conversation

@Sergio0694

@Sergio0694 Sergio Pedri (Sergio0694) commented Nov 3, 2026 •

Copy link
Copy Markdown
Member

Summary

Share the active member scan's reservation across a whole module (instead of a lexical family) in the interop generator's generic discovery, so that large test projects no longer hit CSWINRTINTEROPGEN0106 while the existing recursion guards remain in place.

Motivation

Replaying the Store test-project debug repros (Desktop, BusinessLogic, Purchase) produced CSWINRTINTEROPGEN0106 warnings (Desktop: 2, BusinessLogic: 2, Purchase: 0). Debug logs showed the 1024 reservation budget being consumed by generic helper expansion spread across many unrelated types in the same module (mocking libraries such as Moq). The existing lexical-family rule from #2619 only applied to a type and its nested definitions, so it did not cover these transitions even though they stay within a single, finite module at an unchanged closed substitution.

With this change, ordinary member transitions at an unchanged, fully closed substitution within the same resolved module share the active scan's reservation. This is an accounting rule only: members are still scanned and considered for marshalling. Changing arguments or crossing into another module still costs a reservation, and the visited-key deduplication, FIFO ordering, depth limit (32), signature complexity limit (256) and the 1024 reservation budget are all unchanged, so genuine recursion is still bounded. The 1024 limit is not increased.

Shared transitions are not charged, so they get their own allowance of 4096 per enumeration. Once it is exhausted, further candidates are charged against the normal 1024 budget, so a pathologically wide module still reaches CSWINRTINTEROPGEN0106. The highest shared count measured on the Store repros was about 1600.

Validation on the three repros: all 0106 warnings disappear and the generated WinRT.Interop.dll is byte-identical to the baseline (re-verified with the cap). End-to-end benchmarking over 264 accepted runs (baseline, this change, an increased-limit variant, and a baseline-copy control) showed no measurable performance impact beyond run-to-run noise, with memory within ±2% in 17 of 18 cells.

Changes

  • src/WinRT.Interop.Generator/Discovery/TypeSignatureDiscovery.MemberScanContext.cs: remove GetLexicalFamily and the family identity; MemberScanContext now carries the declaring ModuleDefinition and compares module identity when deciding whether a transition can share the reservation. Sharing consumes a separate allowance (MaxSharedReservations, 4096) and falls back to normal charging once it is exhausted.
  • src/WinRT.Interop.Generator/Discovery/TypeSignatureDiscovery.Members.cs and src/WinRT.Interop.Generator/Discovery/TypeSignatureDiscovery.Worklist.cs: create the member scan context from, and pass to CanShareMemberReservation, the declaring module instead of the declaring type, and update the related comments.
  • src/WinRT.Interop.Generator/Discovery/TypeSignatureDiscovery.cs: add MaxSharedReservations and its counter, and update the MaxTransitiveTypes doc comment.
  • src/Tests/ProjectionWriterTest/Test_InteropGenericDiscovery.MemberReservations.cs (renamed from ...LexicalFamilies.cs): tests now cover sharing across unrelated types within a module, rejection across modules, changed-argument limits, open-context behavior and the shared allowance bound.
  • src/Tests/ProjectionWriterTest/Test_InteropGenericDiscovery.StaticDispatchLimits.cs: update the expected Node count (1022 to 1023), since Required<string> now shares the instance default's reservation.
  • .github/skills/interop-generator/SKILL.md: document the module-scoped sharing policy and the shared allowance.

Replace the lexical-family rule for sharing the active member scan reservation with a module-scoped rule. Ordinary member transitions at an unchanged, fully closed substitution within the same resolved module no longer consume additional reservations, which avoids exhausting the 1024 reservation budget when generic helpers in the same module (e.g. mocking libraries) expand across unrelated types. Members are still scanned, and visited-key deduplication, depth, complexity and the reservation budget are unchanged.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Rename the lexical family tests to member reservation tests, cover sharing across unrelated types within a module and rejection across modules, and update the static dispatch limit expectation to reflect the shared reservation.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Update the interop generator skill to describe the module-scoped reservation sharing policy.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

Cap the transitions that share an active module scan's reservation at 4096 per enumeration. Once exhausted, further candidates are charged against the normal transitive budget, so a pathologically wide module still reaches CSWINRTINTEROPGEN0106.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working CsWinRT 3.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant