[EnC] Switch to lazy MethodDesc creation for methods/fields added to existing generic instantiations
- Dominant language
- C#
- Stars
- 18.3k
- Forks
- 5.6k
- PR merge metrics
- PR metrics pending
Description
## Background
When EnC adds a new method or field to a generic type, `EEClass::AddMethod` (and `EEClass::AddField`) eagerly walk every loaded assembly's `m_AvailableParamTypes` hash to find each existing instantiation of the modified type and attach the new `MethodDesc`/`FieldDesc` to it. This eager walk has two problems:
1. **Pre-existing race**. Even with `m_AvailableTypesLock` held throughout the iteration, another thread can begin building a new instantiation, read the `EEClass` method list before the EnC apply mutates it, and publish the resulting `MethodTable` after the EnC walk completes. That instantiation is silently missing the new method/field. Holding the lock longer narrows the window but does not close it.
2. **Lock-level / reentrancy**. `EEClass::AddMethod` for a generic method routes through `InstantiatedMethodDesc::SetupGenericMethodDefinition`, which itself takes `CrstAvailableParamTypes` to publish the new method's `TypeVarTypeDesc` into `Module.m_GenericParamToDescMap`. If the inner acquire targets a different loader module's lock instance, the lock-level ordering check fires in checked builds and would deadlock in release.
## Proposed fix
Switch from eager attachment to lazy attachment, matching how the type loader handles other generic-instantiation lookups today.
1. Drop the per-instantiation walk in `EEClass::AddMethod` (`vm/class.cpp` lines ~692-755) and `EEClass::AddField` (`vm/class.cpp` lines ~380-422). The new `MethodDesc`/`FieldDesc` is only added to the canonical/generic `EEClass`.
2. Lazily create the per-instantiation `MethodDesc` in `MethodTable::GetParallelMethodDescForEnC` (`vm/methodtable.cpp` line ~7878). When the iterator-based search returns no match on the instantiation MT but the canonical MT has a matching EnC-added MD, create the per-instantiation MD on demand via `EEClass::AddMethodDesc` and append it to the chain. Same treatment for fields via the analogous lookup path.
3. Resolve the contract mismatch. `GetParallelMethodDescForEnC` is currently declared `NOTHROW; GC_NOTRIGGER; MODE_ANY`. `EEClass::AddMethodDesc` is `THROWS; GC_NOTRIGGER; MODE_COOPERATIVE` and allocates from `LoaderHeap`. Two options: relax the contracts on `GetParallelMethodDesc` and audit the ~16 call sites, or introduce a separate `EnsureParallelMethodDesc` API that callers invoke from safe contexts before the cheap lookup.
4. Add concurrency control to prevent two threads from racing to lazily create the same per-instantiation MD.
5. Apply symmetrically to fields so the EnC `AddField` path follows the same model.
Reference: #127755
Contributor guide
Assessment
This issue has not been assessed yet.