diff options
author | Fadi Hanna <fadim@microsoft.com> | 2018-11-13 12:44:49 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-11-13 12:44:49 -0800 |
commit | d2cd0df600cfa697d30cf7ef5a7cf1c3c33b9959 (patch) | |
tree | 5780e415551b7aa0d43cc18d04c977c014a4d3a3 /src/vm/i386 | |
parent | d8bfe11f24598e6e909e6e49aea8fba3925c91b7 (diff) | |
download | coreclr-d2cd0df600cfa697d30cf7ef5a7cf1c3c33b9959.tar.gz coreclr-d2cd0df600cfa697d30cf7ef5a7cf1c3c33b9959.tar.bz2 coreclr-d2cd0df600cfa697d30cf7ef5a7cf1c3c33b9959.zip |
Optimize vtable calls (#20696)
* Implementation of R2R vtable call thunks. These thunks will fetch the target code pointer from the vtable of the input thisPtr, and jump to that address.
This is especially helpful with generics, since we can avoid a generic dictionary lookup cost for a simple vtable call.
Overall, these thunks cause the CPU to have less branch mispredictions, and give a small performance boost to vtable calls.
These stubs are under VirtualCallStubManager so that the managed debugger can handle stepping through them.
Diffstat (limited to 'src/vm/i386')
-rw-r--r-- | src/vm/i386/virtualcallstubcpu.hpp | 113 |
1 files changed, 110 insertions, 3 deletions
diff --git a/src/vm/i386/virtualcallstubcpu.hpp b/src/vm/i386/virtualcallstubcpu.hpp index 67737a2d72..3bdae8c3ec 100644 --- a/src/vm/i386/virtualcallstubcpu.hpp +++ b/src/vm/i386/virtualcallstubcpu.hpp @@ -57,9 +57,9 @@ get quickly changed to point to another kind of stub. */ struct LookupStub { - inline PCODE entryPoint() { LIMITED_METHOD_CONTRACT; return (PCODE)&_entryPoint[0]; } - inline size_t token() { LIMITED_METHOD_CONTRACT; return _token; } - inline size_t size() { LIMITED_METHOD_CONTRACT; return sizeof(LookupStub); } + inline PCODE entryPoint() { LIMITED_METHOD_CONTRACT; return (PCODE)&_entryPoint[0]; } + inline size_t token() { LIMITED_METHOD_CONTRACT; return _token; } + inline size_t size() { LIMITED_METHOD_CONTRACT; return sizeof(LookupStub); } private: friend struct LookupHolder; @@ -357,6 +357,66 @@ private: BYTE pad[(sizeof(void*)-((sizeof(ResolveStub))%sizeof(void*))+offsetof(ResolveStub,_token))%sizeof(void*)]; //fill out DWORD //#endif }; + +/*VTableCallStub************************************************************************************** +These are jump stubs that perform a vtable-base virtual call. These stubs assume that an object is placed +in the first argument register (this pointer). From there, the stub extracts the MethodTable pointer, followed by the +vtable pointer, and finally jumps to the target method at a given slot in the vtable. +*/ +struct VTableCallStub +{ + friend struct VTableCallHolder; + + inline size_t size() + { + LIMITED_METHOD_CONTRACT; + + BYTE* pStubCode = (BYTE *)this; + + size_t cbSize = 2; // First mov instruction + cbSize += (pStubCode[cbSize + 1] == 0x80 ? 6 : 3); // Either 8B 80 or 8B 40: mov eax,[eax+offset] + cbSize += (pStubCode[cbSize + 1] == 0xa0 ? 6 : 3); // Either FF A0 or FF 60: jmp dword ptr [eax+slot] + cbSize += 4; // Slot value (data storage, not a real instruction) + + return cbSize; + } + + inline PCODE entryPoint() const { LIMITED_METHOD_CONTRACT; return (PCODE)&_entryPoint[0]; } + + inline size_t token() + { + LIMITED_METHOD_CONTRACT; + DWORD slot = *(DWORD*)(reinterpret_cast<BYTE*>(this) + size() - 4); + return DispatchToken::CreateDispatchToken(slot).To_SIZE_T(); + } + +private: + BYTE _entryPoint[0]; // Dynamically sized stub. See Initialize() for more details. +}; + +/* VTableCallHolders are the containers for VTableCallStubs, they provide for any alignment of +stubs as necessary. */ +struct VTableCallHolder +{ + void Initialize(unsigned slot); + + VTableCallStub* stub() { LIMITED_METHOD_CONTRACT; return reinterpret_cast<VTableCallStub *>(this); } + + static size_t GetHolderSize(unsigned slot) + { + STATIC_CONTRACT_WRAPPER; + unsigned offsetOfIndirection = MethodTable::GetVtableOffset() + MethodTable::GetIndexOfVtableIndirection(slot) * TARGET_POINTER_SIZE; + unsigned offsetAfterIndirection = MethodTable::GetIndexAfterVtableIndirection(slot) * TARGET_POINTER_SIZE; + return 2 + (offsetOfIndirection >= 0x80 ? 6 : 3) + (offsetAfterIndirection >= 0x80 ? 6 : 3) + 4; + } + + static VTableCallHolder* VTableCallHolder::FromVTableCallEntry(PCODE entry) { LIMITED_METHOD_CONTRACT; return (VTableCallHolder*)entry; } + +private: + // VTableCallStub follows here. It is dynamically sized on allocation because it could + // use short/long instruction sizes for the mov/jmp, depending on the slot value. +}; + #include <poppack.h> @@ -895,6 +955,49 @@ ResolveHolder* ResolveHolder::FromResolveEntry(PCODE resolveEntry) return resolveHolder; } +void VTableCallHolder::Initialize(unsigned slot) +{ + unsigned offsetOfIndirection = MethodTable::GetVtableOffset() + MethodTable::GetIndexOfVtableIndirection(slot) * TARGET_POINTER_SIZE; + unsigned offsetAfterIndirection = MethodTable::GetIndexAfterVtableIndirection(slot) * TARGET_POINTER_SIZE; + _ASSERTE(MethodTable::VTableIndir_t::isRelative == false /* TODO: NYI */); + + VTableCallStub* pStub = stub(); + BYTE* p = (BYTE*)pStub->entryPoint(); + + // mov eax,[ecx] : eax = MethodTable pointer + *(UINT16*)p = 0x018b; p += 2; + + // mov eax,[eax+vtable offset] : eax = vtable pointer + if (offsetOfIndirection >= 0x80) + { + *(UINT16*)p = 0x808b; p += 2; + *(UINT32*)p = offsetOfIndirection; p += 4; + } + else + { + *(UINT16*)p = 0x408b; p += 2; + *p++ = (BYTE)offsetOfIndirection; + } + + // jmp dword ptr [eax+slot] + if (offsetAfterIndirection >= 0x80) + { + *(UINT16*)p = 0xa0ff; p += 2; + *(UINT32*)p = offsetAfterIndirection; p += 4; + } + else + { + *(UINT16*)p = 0x60ff; p += 2; + *p++ = (BYTE)offsetAfterIndirection; + } + + // Store the slot value here for convenience. Not a real instruction (unreachable anyways) + *(UINT32*)p = slot; p += 4; + + _ASSERT(p == (BYTE*)stub()->entryPoint() + VTableCallHolder::GetHolderSize(slot)); + _ASSERT(stub()->size() == VTableCallHolder::GetHolderSize(slot)); +} + #endif // DACCESS_COMPILE VirtualCallStubManager::StubKind VirtualCallStubManager::predictStubKind(PCODE stubStartAddress) @@ -932,6 +1035,10 @@ VirtualCallStubManager::StubKind VirtualCallStubManager::predictStubKind(PCODE s { stubKind = SK_RESOLVE; } + else if (firstWord == 0x018b) + { + stubKind = SK_VTABLECALL; + } else { BYTE firstByte = ((BYTE*) stubStartAddress)[0]; |