summaryrefslogtreecommitdiff
path: root/src/vm/i386
diff options
context:
space:
mode:
authorFadi Hanna <fadim@microsoft.com>2018-11-13 12:44:49 -0800
committerGitHub <noreply@github.com>2018-11-13 12:44:49 -0800
commitd2cd0df600cfa697d30cf7ef5a7cf1c3c33b9959 (patch)
tree5780e415551b7aa0d43cc18d04c977c014a4d3a3 /src/vm/i386
parentd8bfe11f24598e6e909e6e49aea8fba3925c91b7 (diff)
downloadcoreclr-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.hpp113
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];