Reuse typical instantiation DispatchMap for generic instantiations#130530
Open
davidwrighton wants to merge 2 commits into
Open
Reuse typical instantiation DispatchMap for generic instantiations#130530davidwrighton wants to merge 2 commits into
davidwrighton wants to merge 2 commits into
Conversation
When loading a non-typical instantiation of a generic type that undergoes a full MethodTableBuilder run (the __Canon canonical form and value-type instantiations such as List<int>), the interface DispatchMap was rebuilt from scratch via PlaceInterfaceMethods for every instantiation. The encoded DispatchMap is instantiation-independent (it stores type IDs and slot numbers), so it can instead be built once while constructing the type's typical instantiation and reused for all of its non-typical instantiations. In release builds, PlaceInterfaceMethods is skipped for a non-typical instantiation when the typical instantiation's DispatchMap can be reused, and the typical instantiation's encoded map bytes are copied into the new MethodTable's inline DispatchMap. In debug/checked builds the specific instantiation's DispatchMap is still built and asserted to be byte-for-byte identical to the typical instantiation's map, guarding the instantiation-independence invariant. This is safe because PlaceInterfaceMethods only produces DispatchMap interface entries (it does not mutate the vtable; PlaceMethodImpls still always runs), and the two consumers that read the half-built dispatch map after PlaceInterfaceMethods (ValidateInterfaceMethodConstraints and VerifyVirtualMethodsImplemented) are already skipped for non-typical instantiations because fNoSanityChecks is TRUE for them. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3bea685d-50eb-4245-b1ea-4310df00c7d3
bmtInterfaceEntry::CreateSlotTable walked every method of an interface that has virtual static methods, counting the static+virtual ones solely to size the bmtInterfaceSlotImpl array. The subsequent loop already recomputes the exact per-method placement, so the array can simply be over-allocated to the interface's method count, eliminating the extra MethodIterator walk. The reused pDeclMD from the placement loop replaces the redundant it.GetDeclMethodDesc() call. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3bea685d-50eb-4245-b1ea-4310df00c7d3
Contributor
|
Tagging subscribers to this area: @agocke |
Contributor
There was a problem hiding this comment.
Pull request overview
This PR optimizes CoreCLR generic type loading by avoiding redundant work when building interface dispatch maps for non-typical generic instantiations, and by reducing per-interface iteration overhead when sizing virtual-static slot tables.
Changes:
- Reuse the typical instantiation’s encoded
DispatchMapfor eligible non-typical generic instantiations (release), with byte-for-byte validation against a rebuilt map in_DEBUG/Checked builds. - Reduce work in
bmtInterfaceEntry::CreateSlotTableby avoiding an extraMethodIteratorpass when sizing storage for interfaces with virtual static methods. - Add a
DispatchMaphelper to expose the encoded map bytes for reuse/copying.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| src/coreclr/vm/methodtablebuilder.h | Tracks typical instantiation MethodTable on all builds and adds helper declaration for DispatchMap reuse. |
| src/coreclr/vm/methodtablebuilder.cpp | Implements DispatchMap reuse (skip PlaceInterfaceMethods in release when safe) and adjusts interface slot table sizing for virtual static methods. |
| src/coreclr/vm/contractimpl.h | Adds DispatchMap::GetEncodedMapData() to access encoded map bytes for reuse/copying. |
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.
This PR contains two related optimizations to generic type loading / interface slot handling in the CoreCLR type loader.
1. Reuse typical instantiation DispatchMap for generic instantiations
When loading a non-typical instantiation of a generic type that undergoes a full
MethodTableBuilderrun (the__Canoncanonical form and value-type instantiations such asList<int>), the interfaceDispatchMapwas rebuilt from scratch viaPlaceInterfaceMethodsfor every instantiation. The encodedDispatchMapis instantiation-independent (it stores type IDs and slot numbers), so it can instead be built once while constructing the type's typical instantiation and reused for all of its non-typical instantiations.PlaceInterfaceMethodsis skipped for a non-typical instantiation when the typical instantiation'sDispatchMapcan be reused, and the typical instantiation's encoded map bytes are copied into the newMethodTable's inlineDispatchMap.DispatchMapis still built and asserted to be byte-for-byte identical to the typical instantiation's map, guarding the instantiation-independence invariant.This is safe because
PlaceInterfaceMethodsonly producesDispatchMapinterface entries (it does not mutate the vtable;PlaceMethodImplsstill always runs), and the two consumers that read the half-built dispatch map afterPlaceInterfaceMethods(ValidateInterfaceMethodConstraintsandVerifyVirtualMethodsImplemented) are already skipped for non-typical instantiations becausefNoSanityChecksisTRUEfor them.2. Avoid iterating interface methods to size virtual-static slot table
bmtInterfaceEntry::CreateSlotTablewalked every method of an interface that has virtual static methods, counting the static+virtual ones solely to size thebmtInterfaceSlotImplarray. The subsequent loop already recomputes the exact per-method placement, so the array can simply be over-allocated to the interface's method count, eliminating the extraMethodIteratorwalk.Validation
clr.runtimebuilds succeed with 0 warnings/errors.Performance
Measured on an Rx cold-start micro-benchmark that isolates the
System.Reactiveconstruct+initialize type-loading path (interleaved A/B, 60 iterations each, releasecoreclr.dllswapped, baseline = before both changes):Standard deviation was ~1.8 ms, so the ~1.4% improvement is a clear signal on type-loading-bound workloads. On WPF R2R startup (where type loading is a much smaller fraction of total startup) the effect is smaller and within run-to-run noise.