summaryrefslogtreecommitdiff
path: root/src/vm/virtualcallstub.cpp
diff options
context:
space:
mode:
authorKoundinya Veluri <kouvel@users.noreply.github.com>2019-01-12 02:02:10 (GMT)
committerGitHub <noreply@github.com>2019-01-12 02:02:10 (GMT)
commit37b9d85941c39cfdce2a2ea877388ab1ab630c68 (patch)
tree004009a0f73f752ecd338a7460473e861609db21 /src/vm/virtualcallstub.cpp
parent834f8d9bd3ee5f0095c91e334ed4565a1a740fee (diff)
downloadcoreclr-37b9d85941c39cfdce2a2ea877388ab1ab630c68.zip
coreclr-37b9d85941c39cfdce2a2ea877388ab1ab630c68.tar.gz
coreclr-37b9d85941c39cfdce2a2ea877388ab1ab630c68.tar.bz2
Patch vtable slots and similar when tiering is enabled (#21292)
Patch vtable slots and similar when tiering is enabled For a method eligible for code versioning and vtable slot backpatch: - It does not have a precode (`HasPrecode()` returns false) - It does not have a stable entry point (`HasStableEntryPoint()` returns false) - A call to the method may be: - An indirect call through the `MethodTable`'s backpatchable vtable slot - A direct call to a backpatchable `FuncPtrStub`, perhaps through a `JumpStub` - For interface methods, an indirect call through the virtual stub dispatch (VSD) indirection cell to a backpatchable `DispatchStub` or a `ResolveStub` that refers to a backpatchable `ResolveCacheEntry` - The purpose is that typical calls to the method have no additional overhead when code versioning is enabled Recording and backpatching slots: - In order for all vtable slots for the method to be backpatchable: - A vtable slot initially points to the `MethodDesc`'s temporary entry point, even when the method is inherited by a derived type (the slot's value is not copied from the parent) - The temporary entry point always points to the prestub and is never backpatched, in order to be able to discover new vtable slots through which the method may be called - The prestub, as part of `DoBackpatch()`, records any slots that are transitioned from the temporary entry point to the method's at-the-time current, non-prestub entry point - Any further changes to the method's entry point cause recorded slots to be backpatched in `BackpatchEntryPointSlots()` - In order for the `FuncPtrStub` to be backpatchable: - After the `FuncPtrStub` is created and exposed, it is patched to point to the method's at-the-time current entry point if necessary - Any further changes to the method's entry point cause the `FuncPtrStub` to be backpatched in `BackpatchEntryPointSlots()` - In order for VSD entities to be backpatchable: - A `DispatchStub`'s entry point target is aligned and recorded for backpatching in `BackpatchEntryPointSlots()` - The `DispatchStub` was modified on x86 and x64 such that the entry point target is aligned to a pointer to make it backpatchable - A `ResolveCacheEntry`'s entry point target is recorded for backpatching in `BackpatchEntryPointSlots()` Slot lifetime and management of recorded slots: - A slot is recorded in the `LoaderAllocator` in which the slot is allocated, see `RecordAndBackpatchEntryPointSlot()` - An inherited slot that has a shorter lifetime than the `MethodDesc`, when recorded, needs to be accessible by the `MethodDesc` for backpatching, so the dependent `LoaderAllocator` with the slot to backpatch is also recorded in the `MethodDesc`'s `LoaderAllocator`, see `MethodDescBackpatchInfo::AddDependentLoaderAllocator_Locked()` - At the end of a `LoaderAllocator`'s lifetime, the `LoaderAllocator` is unregistered from dependency `LoaderAllocators`, see `MethodDescBackpatchInfoTracker::ClearDependencyMethodDescEntryPointSlots()` - When a `MethodDesc`'s entry point changes, backpatching also includes iterating over recorded dependent `LoaderAllocators` to backpatch the relevant slots recorded there, see `BackpatchEntryPointSlots()` Synchronization between entry point changes and backpatching slots - A global lock is used to ensure that all recorded backpatchable slots corresponding to a `MethodDesc` point to the same entry point, see `DoBackpatch()` and `BackpatchEntryPointSlots()` for examples Due to startup time perf issues: - `IsEligibleForTieredCompilation()` is called more frequently with this change and in hotter paths. I chose to use a `MethodDesc` flag to store that information for fast retreival. The flag is initialized by `DetermineAndSetIsEligibleForTieredCompilation()`. - Initially, I experimented with allowing a method versionable with vtable slot backpatch to have a precode, and allocated a new precode that would also be the stable entry point when a direct call is necessary. That also allows recording a new slot to be optional - in the event of an OOM, the slot may just point to the stable entry point. There are a large number of such methods and the allocations were slowing down startup perf. So, I had to eliminate precodes for methods versionable with vtable slot backpatch and that in turn means that recording slots is necessary for versionability.
Diffstat (limited to 'src/vm/virtualcallstub.cpp')
-rw-r--r--src/vm/virtualcallstub.cpp45
1 files changed, 41 insertions, 4 deletions
diff --git a/src/vm/virtualcallstub.cpp b/src/vm/virtualcallstub.cpp
index ef11c6e..875ee1c 100644
--- a/src/vm/virtualcallstub.cpp
+++ b/src/vm/virtualcallstub.cpp
@@ -489,7 +489,7 @@ void VirtualCallStubManager::Init(BaseDomain *pDomain, LoaderAllocator *pLoaderA
// Record the parent domain
parentDomain = pDomain;
- isCollectible = !!pLoaderAllocator->IsCollectible();
+ m_loaderAllocator = pLoaderAllocator;
//
// Init critical sections
@@ -628,7 +628,7 @@ void VirtualCallStubManager::Init(BaseDomain *pDomain, LoaderAllocator *pLoaderA
BYTE * initReservedMem = NULL;
- if (!isCollectible)
+ if (!m_loaderAllocator->IsCollectible())
{
DWORD dwTotalReserveMemSizeCalc = indcell_heap_reserve_size +
cache_entry_heap_reserve_size +
@@ -833,7 +833,7 @@ void VirtualCallStubManager::Uninit()
{
WRAPPER_NO_CONTRACT;
- if (isCollectible)
+ if (m_loaderAllocator->IsCollectible())
{
parentDomain->GetCollectibleVSDRanges()->RemoveRanges(this);
}
@@ -891,7 +891,7 @@ VirtualCallStubManager::~VirtualCallStubManager()
// This was the block reserved by Init for the heaps.
// For the collectible case, the VSD logic does not allocate the memory.
- if (m_initialReservedMemForHeaps && !isCollectible)
+ if (m_initialReservedMemForHeaps && !m_loaderAllocator->IsCollectible())
ClrVirtualFree (m_initialReservedMemForHeaps, 0, MEM_RELEASE);
// Free critical section
@@ -2630,6 +2630,12 @@ VirtualCallStubManager::TraceResolver(
slot = pItfMD->GetMethodTable()->FindDispatchSlot(pItfMD->GetSlot(), TRUE /* throwOnConflict */);
}
+ // The dispatch slot's target may change due to code versioning shortly after it was retrieved above for the trace. This
+ // will result in the debugger getting some version of the code or the prestub, but not necessarily the exact code pointer
+ // that winds up getting executed. The debugger has code that handles this ambiguity by placing a breakpoint at the start of
+ // all native code versions, even if they aren't the one that was reported by this trace, see
+ // DebuggerController::PatchTrace() under case TRACE_MANAGED. This alleviates the StubManager from having to prevent the
+ // race that occurs here.
return (StubManager::TraceStub(slot.GetTarget(), trace));
}
@@ -2791,6 +2797,16 @@ DispatchHolder *VirtualCallStubManager::GenerateDispatchStub(PCODE ad
#endif
);
+#ifdef FEATURE_CODE_VERSIONING
+ MethodDesc *pMD = MethodTable::GetMethodDescForSlotAddress(addrOfCode);
+ if (pMD->IsVersionableWithVtableSlotBackpatch())
+ {
+ EntryPointSlots::SlotType slotType;
+ TADDR slot = holder->stub()->implTargetSlot(&slotType);
+ pMD->RecordAndBackpatchEntryPointSlot(m_loaderAllocator, slot, slotType);
+ }
+#endif
+
ClrFlushInstructionCache(holder->stub(), holder->stub()->size());
AddToCollectibleVSDRangeList(holder);
@@ -2837,6 +2853,16 @@ DispatchHolder *VirtualCallStubManager::GenerateDispatchStubLong(PCODE
(size_t)pMTExpected,
DispatchStub::e_TYPE_LONG);
+#ifdef FEATURE_CODE_VERSIONING
+ MethodDesc *pMD = MethodTable::GetMethodDescForSlotAddress(addrOfCode);
+ if (pMD->IsVersionableWithVtableSlotBackpatch())
+ {
+ EntryPointSlots::SlotType slotType;
+ TADDR slot = holder->stub()->implTargetSlot(&slotType);
+ pMD->RecordAndBackpatchEntryPointSlot(m_loaderAllocator, slot, slotType);
+ }
+#endif
+
ClrFlushInstructionCache(holder->stub(), holder->stub()->size());
AddToCollectibleVSDRangeList(holder);
@@ -3005,6 +3031,17 @@ ResolveCacheElem *VirtualCallStubManager::GenerateResolveCacheElem(void *addrOfC
e->pNext = NULL;
+#ifdef FEATURE_CODE_VERSIONING
+ MethodDesc *pMD = MethodTable::GetMethodDescForSlotAddress((PCODE)addrOfCode);
+ if (pMD->IsVersionableWithVtableSlotBackpatch())
+ {
+ pMD->RecordAndBackpatchEntryPointSlot(
+ m_loaderAllocator,
+ (TADDR)&e->target,
+ EntryPointSlots::SlotType_Normal);
+ }
+#endif
+
//incr our counters
stats.cache_entry_counter++;
stats.cache_entry_space += sizeof(ResolveCacheElem);