Dynamic generic dictionary expansion feature (#32270)
authorFadi Hanna <fadim@microsoft.com>
Tue, 25 Feb 2020 01:57:39 +0000 (17:57 -0800)
committerGitHub <noreply@github.com>
Tue, 25 Feb 2020 01:57:39 +0000 (17:57 -0800)
* Dynamic dictionary expansion feature

1) Separate dictionary layout expansion from dictionary expansions on types and methods
2) Dictionary expansions on types and methods done on-demand (if needed), after size checks by codegen (uses new JIT feature)
3) Update the R2R dictionary access stubs to perform size checks (and major cleanup there)
4) Add unit test
5) Update documentation
6) Update JIT interface guid

28 files changed:
docs/design/coreclr/botr/shared-generics.md [new file with mode: 0644]
src/coreclr/src/debug/daccess/nidump.cpp
src/coreclr/src/inc/corinfo.h
src/coreclr/src/jit/importer.cpp
src/coreclr/src/tools/Common/JitInterface/CorInfoTypes.cs
src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs
src/coreclr/src/tools/crossgen2/jitinterface/jitwrapper.cpp
src/coreclr/src/vm/amd64/cgenamd64.cpp
src/coreclr/src/vm/appdomain.cpp
src/coreclr/src/vm/arm/stubs.cpp
src/coreclr/src/vm/arm64/stubs.cpp
src/coreclr/src/vm/ceeload.cpp
src/coreclr/src/vm/ceeload.h
src/coreclr/src/vm/clsload.cpp
src/coreclr/src/vm/genericdict.cpp
src/coreclr/src/vm/genericdict.h
src/coreclr/src/vm/generics.cpp
src/coreclr/src/vm/genmeth.cpp
src/coreclr/src/vm/i386/cgenx86.cpp
src/coreclr/src/vm/jithelpers.cpp
src/coreclr/src/vm/jitinterface.cpp
src/coreclr/src/vm/method.cpp
src/coreclr/src/vm/method.hpp
src/coreclr/src/vm/methodtable.inl
src/coreclr/src/vm/methodtablebuilder.cpp
src/coreclr/src/vm/prestub.cpp
src/coreclr/tests/src/Loader/classloader/DictionaryExpansion/DictionaryExpansion.cs [new file with mode: 0644]
src/coreclr/tests/src/Loader/classloader/DictionaryExpansion/DictionaryExpansion.csproj [new file with mode: 0644]

diff --git a/docs/design/coreclr/botr/shared-generics.md b/docs/design/coreclr/botr/shared-generics.md
new file mode 100644 (file)
index 0000000..046a39a
--- /dev/null
@@ -0,0 +1,191 @@
+Shared Generics Design
+===
+
+Author: Fadi Hanna - 2019
+
+# Introduction
+
+Shared generics is a runtime+JIT feature aimed at reducing the amount of code the runtime generates for generic methods of various instantiations (supports methods on generic types and generic methods). The idea is that for certain instantiations, the generated code will almost be identical with the exception of a few instructions, so in order to reduce the memory footprint, and the amount of time we spend jitting these generic methods, the runtime will generate a single special canonical version of the code, which can be used by all compatible instantiations of the method.
+
+### Canonical Codegen and Generic Dictionaries
+
+Consider the following C# code sample:
+
+``` c#
+string Func<T>()
+{
+    return typeof(List<T>).ToString();
+}
+```
+
+Without shared generics, the code for instantiations like `Func<object>` or `Func<string>` would look identical except for one single instruction: the one that loads the correct TypeHandle of type `List<T>`:
+``` asm
+    mov rcx, type handle of List<string> or List<object>
+    call ToString()
+    ret
+```
+
+With shared generics, the canonical code will not have any hard-coded versions of the type handle of List<T>, but instead looks up the exact type handle either through a call to a runtime helper API, or by loading it up from the *generic dictionary* of the instantiation of Func<T> that is executing. The code would look more like the following:
+``` asm
+    mov rcx, generic context                                                // MethodDesc of Func<string> or Func<object>
+    mov rcx, [rcx + offset of InstantiatedMethodDesc::m_pPerInstInfo]       // This is the generic dictionary
+    mov rcx, [rcx + dictionary slot containing type handle of List<T>]
+    call ToString()
+    ret
+```
+
+The generic context in this example is the InstantiatedMethodDesc of `Func<object>` or `Func<string>`. The generic dictionary is a data structure used by shared generic code to fetch instantiation-specific information. It is basically an array where the entries are instantiation-specific type handles, method handles, field handles, method entry points, etc... The "PerInstInfo" fields on MethodTable and InstantiatedMethodDesc structures point at the generic dictionary structure for a generic type and method respectively.
+
+In this example, the generic dictionary for Func<object> will contain a slot with the type handle for type List<object>, and the generic dictionary for Func<string> will contain a slot with the type handle for type List<string>.
+
+This feature is currently only supported for instantiations over reference types because they all have the same size/properties/layout/etc... For instantiations over primitive types or value types, the runtime will generate separate code bodies for each instantiation.
+
+
+# Layouts and Algorithms
+
+### Dictionaries Pointers on Types and Methods
+
+The dictionary used by any given generic method is pointed at by the `m_pPerInstInfo` field on the `InstantiatedMethodDesc` structure of that method. It's a direct pointer to the contents of the generic dictionary data.
+
+On generic types, there's an extra level of indirection: the `m_pPerInstInfo` field on the `MethodTable` structure is a pointer to a table of dictionaries, and each entry in that table is a pointer to the actual generic dictionary data. This is because types have inheritance, and derived generic types inherit the dictionaries of their base types. 
+
+Here's an example:
+```c#
+class BaseClass<T> { }
+
+class DerivedClass<U> : BaseClass<U> { }
+
+class AnotherDerivedClass : DerivedClass<string> { }
+```
+
+The MethodTables of each of these types will look like the following:
+
+| **BaseClass[T]'s MethodTable** |
+|--------------------------|
+| ...      |
+| `m_PerInstInfo`: points at dictionary table below     |
+| ...      |
+| `dictionaryTable[0]`: points at dictionary data below      |
+| `BaseClass's dictionary data here`  |
+
+| **DerivedClass[U]'s MethodTable ** |
+|--------------------------|
+| ...      |
+| `m_PerInstInfo`: points at dictionary table below     |
+| ...      |
+| `dictionaryTable[0]`: points at dictionary data of `BaseClass`      |
+| `dictionaryTable[1]`: points at dictionary data below      |
+| `DerivedClass's dictionary data here`  |
+
+| **AnotherDerivedClass's MethodTable** |
+|--------------------------|
+| ...      |
+| `m_PerInstInfo`: points at dictionary table below     |
+| ...      |
+| `dictionaryTable[0]`: points at dictionary data of `BaseClass`      |
+| `dictionaryTable[1]`: points at dictionary data of `DerivedClass`      |
+
+Note that `AnotherDerivedClass` doesn't have a dictionary of its own given that it is not a generic type, but inherits the dictionary pointers of its base types.
+
+### Dictionary Slots
+
+As described earlier, a generic dictionary is an array of multiple slots containing instantiation-specific information. When a dictionary is initially allocated for a certain generic type or method, all of its slots are initialized to NULL, and are lazily populated on demand as code executes (see: `Dictionary::PopulateEntry(...)`).
+
+The first N slots in an instantiation of N arguments are always going to be the type handles of the instantiation type arguments (this is kind of an optimization as well). The slots that follow contain instantiation-based information.
+
+For instance, here is an example of the contents of the generic dictionary for our `Func<string>` example:
+
+| `Func<string>'s dicionary` |
+|--------------------------|
+| slot[0]: TypeHandle(`string`)      |
+| slot[1]: Total dictionary size  |
+| slot[2]: TypeHandle(`List<string>`)  |
+| slot[3]: NULL (not used)  |
+| slot[4]: NULL (not used)  |
+
+*Note: the size slot is never used by generic code, and is part of the dynamic dictionary expansion feature. More on that below.*
+
+When this dictionary is first allocated, only slot[0] is initialized because it contains the instantiation type arguments (and of course the size slot is also initialized with the dictionary expansion feature), but the rest of the slots (example slot[2]) are NULL, and get lazily populated with values if we ever hit a code path that attempts to use them.
+
+When loading information from a slot that is still NULL, the generic code will call one of these runtime helper functions to populate the dictionary slot with a value:
+- `JIT_GenericHandleClass`: Used to lookup a value in a generic type dictionary. This helper is used by all instance methods on generic types.
+- `JIT_GenericHandleMethod`: Used to lookup a value in a generic method dictionary. This helper used by all generic methods, or non-generic static methods on generic types.
+
+When generating shared generic code, the JIT knows which slots to use for the various lookups, and the kind of information contained in each slot using the help of the `DictionaryLayout` implementation ([genericdict.cpp](https://github.com/dotnet/runtime/blob/master/src/coreclr/src/vm/genericdict.cpp)).
+
+### Dictionary Layouts
+
+The `DictionaryLayout` structure is what tells the JIT which slot to use when performing a dictionary lookup. This `DictionaryLayout` structure has a couple of important properties:
+- It is shared across all compatible instantiations of a certain type of method. In other words, a dictionary layout is associated with the canonical instantiation of a type or a method. For instance, in our example above, `Func<object>` and `Func<string>` are compatible instantiations, each with their own **separate dictionaries**, however they all share the **same dictionary layout**, which is associated with the canonical instantiation `Func<__Canon>`.
+- The dictionaries of generic types or methods have the same number of slots as their dictionary layouts. Note: historically before the introduction of the dynamic dictionary expansion feature, the generic dictionaries could be smaller than their layouts, meaning that for certain lookups, we had to use invoke some runtime helper APIs (slow path).
+
+When a generic type or method is first created, its dictionary layout contains 'unassigned' slots. Assignments happen as part of code generation, whenever the JIT needs to emit a dictionary lookup sequence. This assignment happens during the calls to the `DictionaryLayout::FindToken(...)` APIs. Once a slot has been assigned, it becomes associated with a certain signature, which describes the kind of value that will go in every instantiated dictionary at that slot index.
+
+Given an input signature, slot assignment is performed with the following algorithm:
+
+```
+Begin with slot = 0
+Foreach entry in dictionary layout
+    If entry.signature != NULL
+        If entry.signature == inputSignature
+            return slot
+        EndIf
+    Else
+        entry.signature = inputSignature
+        return slot
+    EndIf
+    slot++
+EndForeach
+```
+
+So what happens when the above algorithm runs, but no existing slot with the same signature is found, and we're out of 'unassigned' slots? This is where the dynamic dictionary expansion kicks in to resize the layout by adding more slots to it, and resizing all dictionaries associated with this layout.
+
+# Dynamic Dictionary Expansion
+
+### History
+
+Before the dynamic dictionary expansion feature, dictionary layouts were organized into buckets (a linked list of fixed-size `DictionaryLayout` structures). The size of the initial layout bucket was always fixed to some number which was computed based on some heuristics for generic types, and always fixed to 4 slots for generic methods. The generic types and methods also had fixed-size generic dictionaries which could be used for lookups (also known as "fast lookup slots").
+
+When a bucket gets filled with entries, we would just allocate a new `DictionaryLayout` bucket, and add it to the list. The problem however is that we couldn't resize the generic dictionaries of types or methods, because they are already allocated with a fixed size, and the JIT does not support generating instructions that could indirect into a linked-list of dictionaries. Given that limitation, we could only lookup a generic dictionary for a fixed number of values (the ones associated with the entries of the first `DictionaryLayout` bucket), and were forced to go through a slower runtime helper for additional lookups.
+
+This was acceptable, until we introduced the [ReadyToRun](https://github.com/dotnet/coreclr/blob/master/Documentation/botr/readytorun-overview.md) and the Tiered Compilation technologies. Slots were getting assigned quickly when used by ReadyToRun code, and when the runtime decided re-jitted certain methods for better performance, it could not in some cases find any remaining "fast lookup slots", and was forced to generate code that goes through the slower runtime helpers. This ended up hurting performance in some scenarios, and a decision was made to not use the fast lookup slots for ReadyToRun code, and instead keep them reserved for re-jitted code. This decision however hurt the ReadyToRun performance, but it was a necessary compromise since we cared more about re-jitted code throughput over R2R throughput.
+
+For this reason, the dynamic dictionary expansion feature was introduced.
+
+### Description and Algorithms
+
+The feature is simple in concept: change dictionary layouts from a linked list of buckets into dynamically expandable arrays instead. Sounds simple, but great care had to be taken when implementing it, because:
+- We can't just resize `DictionaryLayout` structures alone. If the size of the layout is larger than the size of the actual generic dictionary, this would cause the JIT to generate indirection instructions that do not match the size of the dictionary data, leading to access violations.
+- We can't just resize generic dictionaries on types and methods:
+    - For types, the generic dictionary is part of the `MethodTable` structure, which can't be reallocated (already in use by managed code)
+    - For methods, the generic dictionary is not part of the `MethodDesc` structure, but can still be in use by some generic code.
+    - We can't have multiple MethodTables or MethodDescs for the same type or method anyways, so reallocations are not an option.
+- We can't just resize the generic dictionary for a single instantiation. For instance, in our example above, let's say we wanted to expand the dictionary for `Func<string>`. The resizing of the layout would have an impact on the shared canonical code that the JIT generates for `Func<__Canon>`. If we only resized the dictionary of `Func<string>`, the shared generic code would work for that instantiation only, but when we attempt to use it with another instantiation like `Func<object>`, the jitted instructions would no longer match the size of the dictionary structure, and would cause access violations.
+- The runtime is multi-threaded, which adds to the complexity.
+
+The current implementation expands the dictionary layout and the actual dictionaries separately to keep things simple:
+
+ - Dictionary layouts are expanded when we are out of empty slots. See implementations of `DictionaryLayout::FindToken()` in [genericdict.cpp](https://github.com/dotnet/runtime/blob/master/src/coreclr/src/vm/genericdict.cpp).
+ - Instantiated type and method dictionaries are expanded lazily on demand whenever any code is attempting to read the value of a slot beyond the size of the dictionary of that type or method. This is done through a call to the helper functions mentioned previously (`JIT_GenericHandleClass` and `JIT_GenericHandleMethod`).
+
+The dictionary access codegen is equivalent to the following (both in JITted code and ReadyToRun code):
+``` c++
+void* pMethodDesc = <some value>;                         // Input MethodDesc for the instantiated generic method
+int requiredOffset = <some value>;                 // Offset we need to access
+
+void* pDictionary = pMethodDesc->m_pPerInstInfo;
+
+// Note how we check for the dictionary size first before indirecting at 'requiredOffset'
+if (pDictionary[sizeOffset] <= requiredOffset || pDictionary[requiredOffset] == NULL)
+    pResult = JIT_GenericHandleMethod(pMethodDesc, <signature>);
+else
+    pResult = pDictionary[requiredOffset];
+```
+
+This size check is **not** done unconditionally every time we need to read a value from the dictionary, otherwise this would cause a noticeable performance regression. When a dictionary layout is first allocated, we keep track of the initial number of slots that were allocated, and **only** perform the size checks if we are attempting to read the value of a slot beyond those initial number of slots.
+
+Dictionaries on types and methods are expanded by the `Dictionary::GetTypeDictionaryWithSizeCheck()` and `Dictionary::GetMethodDictionaryWithSizeCheck()` helper functions in [genericdict.cpp](https://github.com/dotnet/runtime/blob/master/src/coreclr/src/vm/genericdict.cpp).
+
+One thing to note regarding types is that they can inherit dictionary pointers from their base types. This means that if we resize the generic dictionary on any given generic type, we will need to propagate the new dictionary pointer to all of its derived types. This propagation is also done in a lazy way whenever the code calls into the `JIT_GenericHandleWorker` helper function with a derived type MethodTable pointer. In that helper, if we find that the dictionary pointer on the base type has been updated, we copy it to the derived type.
+
+Old dictionaries are not deallocated after resizing, but once a new dictionary gets published on a MethodTable or MethodDesc, any subsequent dictionary lookup by generic code will make use of that newly allocated dictionary. Deallocating old dictionaries would be extremely complicated, especially in a multi-threaded environment, and won't give any useful benefit.
index 0b23633..8d2f216 100644 (file)
@@ -6957,7 +6957,7 @@ NativeImageDumper::DumpMethodTable( PTR_MethodTable mt, const char * name,
                                    //if there is a layout, use it to compute
                                    //the size, otherwise there is just the one
                                    //entry.
-                                   DictionaryLayout::GetFirstDictionaryBucketSize(mt->GetNumGenericArgs(), layout),
+                                   DictionaryLayout::GetDictionarySizeFromLayout(mt->GetNumGenericArgs(), layout),
                                    METHODTABLES );
 
             DisplayStartArrayWithOffset( m_pEntries, NULL, Dictionary,
@@ -7983,7 +7983,7 @@ void NativeImageDumper::DumpMethodDesc( PTR_MethodDesc md, PTR_Module module )
             {
                 PTR_DictionaryLayout layout(wrapped->IsSharedByGenericMethodInstantiations()
                                             ? dac_cast<TADDR>(wrapped->GetDictLayoutRaw()) : NULL );
-                dictSize = DictionaryLayout::GetFirstDictionaryBucketSize(imd->GetNumGenericMethodArgs(),
+                dictSize = DictionaryLayout::GetDictionarySizeFromLayout(imd->GetNumGenericMethodArgs(), 
                                                                           layout);
             }
         }
index 21640c7..3d20908 100644 (file)
@@ -217,11 +217,11 @@ TODO: Talk about initializing strutures before use
 #endif
 #endif
 
-SELECTANY const GUID JITEEVersionIdentifier = { /* 9C412381-94A6-4F35-B2B6-60AFB2495B72 */
-    0x9c412381,
-    0x94a6,
-    0x4f35,
-    { 0xb2, 0xb6, 0x60, 0xaf, 0xb2, 0x49, 0x5b, 0x72 }
+SELECTANY const GUID JITEEVersionIdentifier = { /* b2e40020-6125-41e4-a0fc-821127ec192a */
+    0xb2e40020,
+    0x6125,
+    0x41e4,
+    {0xa0, 0xfc, 0x82, 0x11, 0x27, 0xec, 0x19, 0x2a}
 };
 
 //////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -1275,6 +1275,7 @@ struct CORINFO_LOOKUP_KIND
 //
 #define CORINFO_MAXINDIRECTIONS 4
 #define CORINFO_USEHELPER ((WORD) 0xffff)
+#define CORINFO_NO_SIZE_CHECK ((WORD) 0xffff)
 
 struct CORINFO_RUNTIME_LOOKUP
 {
@@ -1297,6 +1298,7 @@ struct CORINFO_RUNTIME_LOOKUP
     // If set, test the lowest bit and dereference if set (see code:FixupPointer)
     bool                    testForFixup;
 
+    WORD                    sizeOffset;
     SIZE_T                  offsets[CORINFO_MAXINDIRECTIONS];
 
     // If set, first offset is indirect.
index c7c9d4d..6d1079e 100644 (file)
@@ -2082,14 +2082,12 @@ GenTree* Compiler::impRuntimeLookupToTree(CORINFO_RESOLVED_TOKEN* pResolvedToken
 
         if (pRuntimeLookup->offsets[i] != 0)
         {
-// The last indirection could be subject to a size check (dynamic dictionary expansion feature)
-#if 0  // Uncomment that block when you add sizeOffset field to pRuntimeLookup.
-            if (i == pRuntimeLookup->indirections - 1 && pRuntimeLookup->sizeOffset != 0xFFFF)
+            // The last indirection could be subject to a size check (dynamic dictionary expansion)
+            if (i == pRuntimeLookup->indirections - 1 && pRuntimeLookup->sizeOffset != CORINFO_NO_SIZE_CHECK)
             {
                 lastIndOfTree = impCloneExpr(slotPtrTree, &slotPtrTree, NO_CLASS_HANDLE, (unsigned)CHECK_SPILL_ALL,
                                              nullptr DEBUGARG("impRuntimeLookup indirectOffset"));
             }
-#endif // 0
 
             slotPtrTree =
                 gtNewOperNode(GT_ADD, TYP_I_IMPL, slotPtrTree, gtNewIconNode(pRuntimeLookup->offsets[i], TYP_I_IMPL));
@@ -2160,9 +2158,10 @@ GenTree* Compiler::impRuntimeLookupToTree(CORINFO_RESOLVED_TOKEN* pResolvedToken
 
     GenTree* result = nullptr;
 
-#if 0  // Uncomment that block when you add sizeOffset field to pRuntimeLookup.
-    if (pRuntimeLookup->sizeOffset != 0xFFFF) // dynamic dictionary expansion feature
+    if (pRuntimeLookup->sizeOffset != CORINFO_NO_SIZE_CHECK)
     {
+        // Dynamic dictionary expansion support
+
         assert((lastIndOfTree != nullptr) && (pRuntimeLookup->indirections > 0));
 
         // sizeValue = dictionary[pRuntimeLookup->sizeOffset]
@@ -2186,7 +2185,6 @@ GenTree* Compiler::impRuntimeLookupToTree(CORINFO_RESOLVED_TOKEN* pResolvedToken
         addExpRuntimeLookupCandidate(helperCall);
     }
     else
-#endif // 0
     {
         GenTreeColon* colonNullCheck = new (this, GT_COLON) GenTreeColon(TYP_I_IMPL, handleForResult, helperCall);
         result                       = gtNewQmarkNode(TYP_I_IMPL, nullCheck, colonNullCheck);
index 1b3034c..e5a47be 100644 (file)
@@ -15,6 +15,7 @@ namespace Internal.JitInterface
         // This accounts for up to 2 indirections to get at a dictionary followed by a possible spill slot
         public const uint MAXINDIRECTIONS = 4;
         public const ushort USEHELPER = 0xffff;
+        public const ushort CORINFO_NO_SIZE_CHECK = 0xffff;
     }
 
     public struct CORINFO_METHOD_STRUCT_
@@ -248,6 +249,7 @@ namespace Internal.JitInterface
         public byte _testForFixup;
         public bool testForFixup { get { return _testForFixup != 0; } set { _testForFixup = value ? (byte)1 : (byte)0; } }
 
+        public ushort sizeOffset;
         public IntPtr offset0;
         public IntPtr offset1;
         public IntPtr offset2;
index 37f3818..42f853a 100644 (file)
@@ -1742,6 +1742,7 @@ namespace Internal.JitInterface
 
             // Unless we decide otherwise, just do the lookup via a helper function
             pResult.indirections = CORINFO.USEHELPER;
+            pResult.sizeOffset = CORINFO.CORINFO_NO_SIZE_CHECK;
 
             MethodDesc contextMethod = methodFromContext(pResolvedToken.tokenContext);
             TypeDesc contextType = typeFromContext(pResolvedToken.tokenContext);
index 26731de..8a4b1f9 100644 (file)
@@ -27,11 +27,11 @@ private:
     uint64_t corJitFlags;
 };
 
-static const GUID JITEEVersionIdentifier = { /* 9C412381-94A6-4F35-B2B6-60AFB2495B72 */
-    0x9c412381,
-    0x94a6,
-    0x4f35,
-    { 0xb2, 0xb6, 0x60, 0xaf, 0xb2, 0x49, 0x5b, 0x72 }
+static const GUID JITEEVersionIdentifier = { /* b2e40020-6125-41e4-a0fc-821127ec192a */
+    0xb2e40020,
+    0x6125,
+    0x41e4,
+    {0xa0, 0xfc, 0x82, 0x11, 0x27, 0xec, 0x19, 0x2a}
 };
 
 class Jit
index a1ca359..35d0072 100644 (file)
@@ -1145,6 +1145,8 @@ PCODE DynamicHelpers::CreateDictionaryLookupHelper(LoaderAllocator * pAllocator,
 {
     STANDARD_VM_CONTRACT;
 
+    _ASSERTE(!MethodTable::IsPerInstInfoRelative());
+
     PCODE helperAddress = (pLookup->helper == CORINFO_HELP_RUNTIMEHANDLE_METHOD ?
         GetEEFuncEntryPoint(JIT_GenericHandleMethodWithSlotAndModule) :
         GetEEFuncEntryPoint(JIT_GenericHandleClassWithSlotAndModule));
@@ -1154,6 +1156,8 @@ PCODE DynamicHelpers::CreateDictionaryLookupHelper(LoaderAllocator * pAllocator,
     pArgs->signature = pLookup->signature;
     pArgs->module = (CORINFO_MODULE_HANDLE)pModule;
 
+    WORD slotOffset = (WORD)(dictionaryIndexAndSlot & 0xFFFF) * sizeof(Dictionary*);
+
     // It's available only via the run-time helper function
     if (pLookup->indirections == CORINFO_USEHELPER)
     {
@@ -1172,59 +1176,79 @@ PCODE DynamicHelpers::CreateDictionaryLookupHelper(LoaderAllocator * pAllocator,
         for (WORD i = 0; i < pLookup->indirections; i++)
             indirectionsSize += (pLookup->offsets[i] >= 0x80 ? 7 : 4);
 
-        int codeSize = indirectionsSize + (pLookup->testForNull ? 30 : 4);
+        int codeSize = indirectionsSize + (pLookup->testForNull ? 21 : 1) + (pLookup->sizeOffset != CORINFO_NO_SIZE_CHECK ? 13 : 0);
 
         BEGIN_DYNAMIC_HELPER_EMIT(codeSize);
 
-        if (pLookup->testForNull)
-        {
-            // rcx/rdi contains the generic context parameter. Save a copy of it in the rax register
-#ifdef UNIX_AMD64_ABI
-            *(UINT32*)p = 0x00f88948; p += 3;   // mov rax,rdi
-#else
-            *(UINT32*)p = 0x00c88948; p += 3;   // mov rax,rcx
-#endif
-        }
+        BYTE* pJLECall = NULL;
 
         for (WORD i = 0; i < pLookup->indirections; i++)
         {
-#ifdef UNIX_AMD64_ABI
-            // mov rdi,qword ptr [rdi+offset]
-            if (pLookup->offsets[i] >= 0x80)
+            if (i == pLookup->indirections - 1 && pLookup->sizeOffset != CORINFO_NO_SIZE_CHECK)
             {
-                *(UINT32*)p = 0x00bf8b48; p += 3;
-                *(UINT32*)p = (UINT32)pLookup->offsets[i]; p += 4;
+                _ASSERTE(pLookup->testForNull && i > 0);
+
+                // cmp qword ptr[rax + sizeOffset],slotOffset
+                *(UINT32*)p = 0x00b88148; p += 3;
+                *(UINT32*)p = (UINT32)pLookup->sizeOffset; p += 4;
+                *(UINT32*)p = (UINT32)slotOffset; p += 4;
+
+                // jle 'HELPER CALL'
+                *p++ = 0x7e;
+                pJLECall = p++;     // Offset filled later
             }
-            else
+
+            if (i == 0)
             {
-                *(UINT32*)p = 0x007f8b48; p += 3;
-                *p++ = (BYTE)pLookup->offsets[i];
-            }
+                // Move from rcx|rdi if it's the first indirection, otherwise from rax
+#ifdef UNIX_AMD64_ABI
+                // mov rax,qword ptr [rdi+offset]
+                if (pLookup->offsets[i] >= 0x80)
+                {
+                    *(UINT32*)p = 0x00878b48; p += 3;
+                    *(UINT32*)p = (UINT32)pLookup->offsets[i]; p += 4;
+                }
+                else
+                {
+                    *(UINT32*)p = 0x00478b48; p += 3;
+                    *p++ = (BYTE)pLookup->offsets[i];
+                }
 #else
-            // mov rcx,qword ptr [rcx+offset]
-            if (pLookup->offsets[i] >= 0x80)
-            {
-                *(UINT32*)p = 0x00898b48; p += 3;
-                *(UINT32*)p = (UINT32)pLookup->offsets[i]; p += 4;
+                // mov rax,qword ptr [rcx+offset]
+                if (pLookup->offsets[i] >= 0x80)
+                {
+                    *(UINT32*)p = 0x00818b48; p += 3;
+                    *(UINT32*)p = (UINT32)pLookup->offsets[i]; p += 4;
+                }
+                else
+                {
+                    *(UINT32*)p = 0x00418b48; p += 3;
+                    *p++ = (BYTE)pLookup->offsets[i];
+                }
+#endif
             }
             else
             {
-                *(UINT32*)p = 0x00498b48; p += 3;
-                *p++ = (BYTE)pLookup->offsets[i];
+                // mov rax,qword ptr [rax+offset]
+                if (pLookup->offsets[i] >= 0x80)
+                {
+                    *(UINT32*)p = 0x00808b48; p += 3;
+                    *(UINT32*)p = (UINT32)pLookup->offsets[i]; p += 4;
+                }
+                else
+                {
+                    *(UINT32*)p = 0x00408b48; p += 3;
+                    *p++ = (BYTE)pLookup->offsets[i];
+                }
             }
-#endif
         }
 
         // No null test required
         if (!pLookup->testForNull)
         {
-            // No fixups needed for R2R
+            _ASSERTE(pLookup->sizeOffset == CORINFO_NO_SIZE_CHECK);
 
-#ifdef UNIX_AMD64_ABI
-            *(UINT32*)p = 0x00f88948; p += 3;       // mov rax,rdi
-#else
-            *(UINT32*)p = 0x00c88948; p += 3;       // mov rax,rcx
-#endif
+            // No fixups needed for R2R
             *p++ = 0xC3;    // ret
         }
         else
@@ -1233,32 +1257,21 @@ PCODE DynamicHelpers::CreateDictionaryLookupHelper(LoaderAllocator * pAllocator,
 
             _ASSERTE(pLookup->indirections != 0);
 
-#ifdef UNIX_AMD64_ABI
-            *(UINT32*)p = 0x00ff8548; p += 3;       // test rdi,rdi
-#else
-            *(UINT32*)p = 0x00c98548; p += 3;       // test rcx,rcx
-#endif
+            *(UINT32*)p = 0x00c08548; p += 3;       // test rax,rax
 
-            // je 'HELPER_CALL' (a jump of 4 bytes)
-            *(UINT16*)p = 0x0474; p += 2;
+            // je 'HELPER_CALL' (a jump of 1 byte)
+            *(UINT16*)p = 0x0174; p += 2;
 
-#ifdef UNIX_AMD64_ABI
-            *(UINT32*)p = 0x00f88948; p += 3;       // mov rax,rdi
-#else
-            *(UINT32*)p = 0x00c88948; p += 3;       // mov rax,rcx
-#endif
             *p++ = 0xC3;    // ret
 
             // 'HELPER_CALL'
             {
-                // Put the generic context back into rcx (was previously saved in rax)
-#ifdef UNIX_AMD64_ABI
-                *(UINT32*)p = 0x00c78948; p += 3;   // mov rdi,rax
-#else
-                *(UINT32*)p = 0x00c18948; p += 3;   // mov rcx,rax
-#endif
+                if (pJLECall != NULL)
+                    *pJLECall = (BYTE)(p - pJLECall - 1);
+
+                // rcx|rdi already contains the generic context parameter
 
-                // mov rdx,pArgs
+                // mov rdx|rsi,pArgs
                 // jmp helperAddress
                 EmitHelperWithArg(p, pAllocator, (TADDR)pArgs, helperAddress);
             }
index 44aa02d..0eb122f 100644 (file)
@@ -2004,6 +2004,10 @@ void SystemDomain::LoadBaseSystemClasses()
     g_pDelegateClass = MscorlibBinder::GetClass(CLASS__DELEGATE);
     g_pMulticastDelegateClass = MscorlibBinder::GetClass(CLASS__MULTICAST_DELEGATE);
 
+#ifndef CROSSGEN_COMPILE
+    CrossLoaderAllocatorHashSetup::EnsureTypesLoaded();
+#endif
+
     // used by IsImplicitInterfaceOfSZArray
     MscorlibBinder::GetClass(CLASS__IENUMERABLEGENERIC);
     MscorlibBinder::GetClass(CLASS__ICOLLECTIONGENERIC);
@@ -2020,10 +2024,6 @@ void SystemDomain::LoadBaseSystemClasses()
 #endif // FEATURE_UTF8STRING
 
 #ifndef CROSSGEN_COMPILE
-    CrossLoaderAllocatorHashSetup::EnsureTypesLoaded();
-#endif
-
-#ifndef CROSSGEN_COMPILE
     ECall::PopulateManagedStringConstructors();
 #endif // CROSSGEN_COMPILE
 
index ca6eeae..25feee8 100644 (file)
@@ -2769,6 +2769,8 @@ PCODE DynamicHelpers::CreateDictionaryLookupHelper(LoaderAllocator * pAllocator,
 {
     STANDARD_VM_CONTRACT;
 
+    _ASSERTE(!MethodTable::IsPerInstInfoRelative());
+
     PCODE helperAddress = (pLookup->helper == CORINFO_HELP_RUNTIMEHANDLE_METHOD ?
         GetEEFuncEntryPoint(JIT_GenericHandleMethodWithSlotAndModule) :
         GetEEFuncEntryPoint(JIT_GenericHandleClassWithSlotAndModule));
@@ -2778,6 +2780,8 @@ PCODE DynamicHelpers::CreateDictionaryLookupHelper(LoaderAllocator * pAllocator,
     pArgs->signature = pLookup->signature;
     pArgs->module = (CORINFO_MODULE_HANDLE)pModule;
 
+    WORD slotOffset = (WORD)(dictionaryIndexAndSlot & 0xFFFF) * sizeof(Dictionary*);
+
     // It's available only via the run-time helper function,
 
     if (pLookup->indirections == CORINFO_USEHELPER)
@@ -2791,17 +2795,14 @@ PCODE DynamicHelpers::CreateDictionaryLookupHelper(LoaderAllocator * pAllocator,
     else
     {
         int indirectionsSize = 0;
+        if (pLookup->sizeOffset != CORINFO_NO_SIZE_CHECK)
+        {
+            indirectionsSize += (pLookup->sizeOffset >= 0xFFF ? 10 : 4);
+            indirectionsSize += 12;
+        }
         for (WORD i = 0; i < pLookup->indirections; i++)
         {
-            if ((i == 0 && pLookup->indirectFirstOffset) || (i == 1 && pLookup->indirectSecondOffset))
-            {
-                indirectionsSize += (pLookup->offsets[i] >= 0xFFF ? 10 : 2);
-                indirectionsSize += 4;
-            }
-            else
-            {
-                indirectionsSize += (pLookup->offsets[i] >= 0xFFF ? 10 : 4);
-            }
+            indirectionsSize += (pLookup->offsets[i] >= 0xFFF ? 10 : 4);
         }
 
         int codeSize = indirectionsSize + (pLookup->testForNull ? 26 : 2);
@@ -2815,76 +2816,80 @@ PCODE DynamicHelpers::CreateDictionaryLookupHelper(LoaderAllocator * pAllocator,
             p += 2;
         }
 
+        BYTE* pBLECall = NULL;
+
         for (WORD i = 0; i < pLookup->indirections; i++)
         {
-            if ((i == 0 && pLookup->indirectFirstOffset) || (i == 1 && pLookup->indirectSecondOffset))
+            if (i == pLookup->indirections - 1 && pLookup->sizeOffset != CORINFO_NO_SIZE_CHECK)
             {
-                if (pLookup->offsets[i] >= 0xFF)
+                _ASSERTE(pLookup->testForNull && i > 0);
+
+                if (pLookup->sizeOffset >= 0xFFF)
                 {
                     // mov r2, offset
-                    MovRegImm(p, 2, pLookup->offsets[i]);
-                    p += 8;
-
-                    // add r0, r2
-                    *(WORD *)p = 0x4410;
-                    p += 2;
+                    MovRegImm(p, 2, pLookup->sizeOffset); p += 8;
+                    // ldr r1, [r0, r2]
+                    *(WORD*)p = 0x5881; p += 2;
                 }
                 else
                 {
-                    // add r0, <offset>
-                   *(WORD *)p = (WORD)((WORD)0x3000 | (WORD)((0x00FF) & pLookup->offsets[i]));
-                   p += 2;
+                    // ldr r1, [r0 + offset]
+                    *(WORD*)p = 0xF8D0; p += 2;
+                    *(WORD*)p = (WORD)(0xFFF & pLookup->sizeOffset) | 0x1000; p += 2;
                 }
 
-                // r0 is pointer + offset[0]
-                // ldr r2, [r0]
-                *(WORD *)p = 0x6802;
-                p += 2;
+                // mov r2, slotOffset
+                MovRegImm(p, 2, slotOffset); p += 8;
+
+                // cmp r1,r2
+                *(WORD*)p = 0x4291; p += 2;
 
-                // r2 is offset1
-                // add r0, r2
-                *(WORD *)p = 0x4410;
+                // ble 'CALL HELPER'
+                pBLECall = p;       // Offset filled later
+                *(WORD*)p = 0xdd00; p += 2;
+            }
+            if (pLookup->offsets[i] >= 0xFFF)
+            {
+                // mov r2, offset
+                MovRegImm(p, 2, pLookup->offsets[i]);
+                p += 8;
+
+                // ldr r0, [r0, r2]
+                *(WORD *)p = 0x5880;
                 p += 2;
             }
             else
             {
-                if (pLookup->offsets[i] >= 0xFFF)
-                {
-                    // mov r2, offset
-                    MovRegImm(p, 2, pLookup->offsets[i]);
-                    p += 8;
-
-                    // ldr r0, [r0, r2]
-                    *(WORD *)p = 0x5880;
-                    p += 2;
-                }
-                else
-                {
-                    // ldr r0, [r0 + offset]
-                    *(WORD *)p = 0xF8D0;
-                    p += 2;
-                    *(WORD *)p = (WORD)(0xFFF & pLookup->offsets[i]);
-                    p += 2;
-                }
+                // ldr r0, [r0 + offset]
+                *(WORD *)p = 0xF8D0;
+                p += 2;
+                *(WORD *)p = (WORD)(0xFFF & pLookup->offsets[i]);
+                p += 2;
             }
         }
 
         // No null test required
         if (!pLookup->testForNull)
         {
+            _ASSERTE(pLookup->sizeOffset == CORINFO_NO_SIZE_CHECK);
+
             // mov pc, lr
             *(WORD *)p = 0x46F7;
             p += 2;
         }
         else
         {
-            // cbz r0, nullvaluelabel
+            // cbz r0, 'CALL HELPER'
             *(WORD *)p = 0xB100;
             p += 2;
             // mov pc, lr
             *(WORD *)p = 0x46F7;
             p += 2;
-            // nullvaluelabel:
+
+            // CALL HELPER:
+            if (pBLECall != NULL)
+                *(WORD*)pBLECall |= (((BYTE)(p - pBLECall) - 4) >> 1);
+
             // mov r0, r3
             *(WORD *)p = 0x4618;
             p += 2;
index 0810e0a..9987da8 100644 (file)
@@ -2109,6 +2109,8 @@ PCODE DynamicHelpers::CreateDictionaryLookupHelper(LoaderAllocator * pAllocator,
 {
     STANDARD_VM_CONTRACT;
 
+    _ASSERTE(!MethodTable::IsPerInstInfoRelative());
+
     PCODE helperAddress = (pLookup->helper == CORINFO_HELP_RUNTIMEHANDLE_METHOD ?
         GetEEFuncEntryPoint(JIT_GenericHandleMethodWithSlotAndModule) :
         GetEEFuncEntryPoint(JIT_GenericHandleClassWithSlotAndModule));
@@ -2118,6 +2120,8 @@ PCODE DynamicHelpers::CreateDictionaryLookupHelper(LoaderAllocator * pAllocator,
     pArgs->signature = pLookup->signature;
     pArgs->module = (CORINFO_MODULE_HANDLE)pModule;
 
+    WORD slotOffset = (WORD)(dictionaryIndexAndSlot & 0xFFFF) * sizeof(Dictionary*);
+
     // It's available only via the run-time helper function
     if (pLookup->indirections == CORINFO_USEHELPER)
     {
@@ -2135,7 +2139,15 @@ PCODE DynamicHelpers::CreateDictionaryLookupHelper(LoaderAllocator * pAllocator,
     {
         int indirectionsCodeSize = 0;
         int indirectionsDataSize = 0;
-        for (WORD i = 0; i < pLookup->indirections; i++) {
+        if (pLookup->sizeOffset != CORINFO_NO_SIZE_CHECK)
+        {
+            indirectionsCodeSize += (pLookup->sizeOffset > 32760 ? 8 : 4);
+            indirectionsDataSize += (pLookup->sizeOffset > 32760 ? 4 : 0);
+            indirectionsCodeSize += 12;
+        }
+
+        for (WORD i = 0; i < pLookup->indirections; i++)
+        {
             indirectionsCodeSize += (pLookup->offsets[i] > 32760 ? 8 : 4); // if( > 32760) (8 code bytes) else 4 bytes for instruction with offset encoded in instruction
             indirectionsDataSize += (pLookup->offsets[i] > 32760 ? 4 : 0); // 4 bytes for storing indirection offset values
         }
@@ -2143,8 +2155,7 @@ PCODE DynamicHelpers::CreateDictionaryLookupHelper(LoaderAllocator * pAllocator,
         int codeSize = indirectionsCodeSize;
         if(pLookup->testForNull)
         {
-            codeSize += 4; // mov
-            codeSize += 12; // cbz-ret-mov
+            codeSize += 16; // mov-cbz-ret-mov
             //padding for 8-byte align (required by EmitHelperWithArg)
             if((codeSize & 0x7) == 0)
                 codeSize += 4;
@@ -2159,17 +2170,53 @@ PCODE DynamicHelpers::CreateDictionaryLookupHelper(LoaderAllocator * pAllocator,
 
         BEGIN_DYNAMIC_HELPER_EMIT(codeSize);
 
-        if (pLookup->testForNull)
+        if (pLookup->testForNull || pLookup->sizeOffset != CORINFO_NO_SIZE_CHECK)
         {
             // mov x9, x0
             *(DWORD*)p = 0x91000009;
             p += 4;
         }
 
+        BYTE* pBLECall = NULL;
+
         // moving offset value wrt PC. Currently points to first indirection offset data.
         uint dataOffset = codeSize - indirectionsDataSize - (pLookup->testForNull ? 4 : 0);
         for (WORD i = 0; i < pLookup->indirections; i++)
         {
+            if (i == pLookup->indirections - 1 && pLookup->sizeOffset != CORINFO_NO_SIZE_CHECK)
+            {
+                _ASSERTE(pLookup->testForNull && i > 0);
+
+                if (pLookup->sizeOffset > 32760)
+                {
+                    // ldr w10, [PC, #dataOffset]
+                    *(DWORD*)p = 0x1800000a | ((dataOffset >> 2) << 5); p += 4;
+                    // ldr x11, [x0, x10]
+                    *(DWORD*)p = 0xf86a680b; p += 4;
+
+                    // move to next indirection offset data
+                    dataOffset = dataOffset - 8 + 4; // subtract 8 as we have moved PC by 8 and add 4 as next data is at 4 bytes from previous data
+                }
+                else
+                {
+                    // ldr x11, [x0, #(pLookup->sizeOffset)]
+                    *(DWORD*)p = 0xf940000b | (((UINT32)pLookup->sizeOffset >> 3) << 10); p += 4;
+                    dataOffset -= 4; // subtract 4 as we have moved PC by 4
+                }
+
+                // mov x10,slotOffset
+                *(DWORD*)p = 0xd280000a | ((UINT32)slotOffset << 5); p += 4;
+                dataOffset -= 4;
+
+                // cmp x9,x10
+                *(DWORD*)p = 0xeb0a017f; p += 4;
+                dataOffset -= 4;
+
+                // ble 'CALL HELPER'
+                pBLECall = p;       // Offset filled later
+                *(DWORD*)p = 0x5400000d; p += 4;
+                dataOffset -= 4;
+            }
             if(pLookup->offsets[i] > 32760)
             {
                 // ldr w10, [PC, #dataOffset]
@@ -2197,19 +2244,25 @@ PCODE DynamicHelpers::CreateDictionaryLookupHelper(LoaderAllocator * pAllocator,
         // No null test required
         if (!pLookup->testForNull)
         {
+            _ASSERTE(pLookup->sizeOffset == CORINFO_NO_SIZE_CHECK);
+
             // ret lr
             *(DWORD*)p = 0xd65f03c0;
             p += 4;
         }
         else
         {
-            // cbz x0, nullvaluelabel
+            // cbz x0, 'CALL HELPER'
             *(DWORD*)p = 0xb4000040;
             p += 4;
             // ret lr
             *(DWORD*)p = 0xd65f03c0;
             p += 4;
-            // nullvaluelabel:
+
+            // CALL HELPER:
+            if(pBLECall != NULL)
+                *(DWORD*)pBLECall |= (((UINT32)(p - pBLECall) >> 2) << 5);
+
             // mov x0, x9
             *(DWORD*)p = 0x91000120;
             p += 4;
@@ -2222,9 +2275,13 @@ PCODE DynamicHelpers::CreateDictionaryLookupHelper(LoaderAllocator * pAllocator,
         // datalabel:
         for (WORD i = 0; i < pLookup->indirections; i++)
         {
-            if(pLookup->offsets[i] > 32760)
+            if (i == pLookup->indirections - 1 && pLookup->sizeOffset != CORINFO_NO_SIZE_CHECK && pLookup->sizeOffset > 32760)
+            {
+                *(UINT32*)p = (UINT32)pLookup->sizeOffset;
+                p += 4;
+            }
+            if (pLookup->offsets[i] > 32760)
             {
-                _ASSERTE((pLookup->offsets[i] & 0xffffffff00000000) == 0);
                 *(UINT32*)p = (UINT32)pLookup->offsets[i];
                 p += 4;
             }
index 3ff65d6..149d44a 100644 (file)
@@ -556,6 +556,7 @@ void Module::Initialize(AllocMemTracker *pamTracker, LPCWSTR szName)
     m_FixupCrst.Init(CrstModuleFixup, (CrstFlags)(CRST_HOST_BREAKABLE|CRST_REENTRANCY));
     m_InstMethodHashTableCrst.Init(CrstInstMethodHashTable, CRST_REENTRANCY);
     m_ISymUnmanagedReaderCrst.Init(CrstISymUnmanagedReader, CRST_DEBUGGER_THREAD);
+    m_DictionaryCrst.Init(CrstDomainLocalBlock);
 
     if (!m_file->HasNativeImage())
     {
@@ -688,7 +689,6 @@ void Module::Initialize(AllocMemTracker *pamTracker, LPCWSTR szName)
 #endif // defined (PROFILING_SUPPORTED) &&!defined(DACCESS_COMPILE) && !defined(CROSSGEN_COMPILE)
 
     LOG((LF_CLASSLOADER, LL_INFO10, "Loaded pModule: \"%ws\".\n", GetDebugName()));
-
 }
 
 #endif // DACCESS_COMPILE
index f4a0b05..86b3fe3 100644 (file)
@@ -3234,6 +3234,9 @@ public:
 
     void SetNativeMetadataAssemblyRefInCache(DWORD rid, PTR_Assembly pAssembly);
 #endif // !defined(DACCESS_COMPILE)
+
+    // For protecting dictionary layout slot expansions
+    CrstExplicitInit        m_DictionaryCrst;
 };
 
 //
index 7c1bac2..c83da26 100644 (file)
@@ -3268,14 +3268,13 @@ TypeHandle ClassLoader::CreateTypeHandleForTypeKey(TypeKey* pKey, AllocMemTracke
         if (IsCanonicalGenericInstantiation(pKey->GetInstantiation()))
         {
             typeHnd = CreateTypeHandleForTypeDefThrowing(pKey->GetModule(),
-                                                            pKey->GetTypeToken(),
-                                                            pKey->GetInstantiation(),
-                                                            pamTracker);
+                                                         pKey->GetTypeToken(),
+                                                         pKey->GetInstantiation(),
+                                                         pamTracker);
         }
         else
         {
-            typeHnd = CreateTypeHandleForNonCanonicalGenericInstantiation(pKey,
-                                                                                        pamTracker);
+            typeHnd = CreateTypeHandleForNonCanonicalGenericInstantiation(pKey, pamTracker);
         }
 #if defined(_DEBUG) && !defined(CROSSGEN_COMPILE)
         if (Nullable::IsNullableType(typeHnd))
index e573988..82016e1 100644 (file)
 //---------------------------------------------------------------------------------------
 //
 //static
-DictionaryLayout *
-DictionaryLayout::Allocate(
-    WORD              numSlots,
-    LoaderAllocator * pAllocator,
-    AllocMemTracker * pamTracker)
+DictionaryLayout* DictionaryLayout::Allocate(WORD              numSlots,
+                                             LoaderAllocator * pAllocator,
+                                             AllocMemTracker * pamTracker)
 {
     CONTRACT(DictionaryLayout*)
     {
@@ -63,34 +61,35 @@ DictionaryLayout::Allocate(
 
     DictionaryLayout * pD = (DictionaryLayout *)(void *)ptr;
 
-    // When bucket spills we'll allocate another layout structure
-    pD->m_pNext = NULL;
-
     // This is the number of slots excluding the type parameters
     pD->m_numSlots = numSlots;
+    pD->m_numInitialSlots = numSlots;
 
     RETURN pD;
-} // DictionaryLayout::Allocate
+}
 
 #endif //!DACCESS_COMPILE
 
 //---------------------------------------------------------------------------------------
 //
-// Count the number of bytes that are required by the first bucket in a dictionary with the specified layout
-//
+// Count the number of bytes that are required in a dictionary with the specified layout
+// 
 //static
-DWORD
-DictionaryLayout::GetFirstDictionaryBucketSize(
-    DWORD                numGenericArgs,
+DWORD 
+DictionaryLayout::GetDictionarySizeFromLayout(
+    DWORD                numGenericArgs, 
     PTR_DictionaryLayout pDictLayout)
 {
     LIMITED_METHOD_DAC_CONTRACT;
     PRECONDITION(numGenericArgs > 0);
     PRECONDITION(CheckPointer(pDictLayout, NULL_OK));
 
-    DWORD bytes = numGenericArgs * sizeof(TypeHandle);
+    DWORD bytes = numGenericArgs * sizeof(TypeHandle);          // Slots for instantiation arguments
     if (pDictLayout != NULL)
-        bytes += pDictLayout->m_numSlots * sizeof(void*);
+    {
+        bytes += sizeof(ULONG_PTR*);                            // Slot for dictionary size
+        bytes += pDictLayout->m_numSlots * sizeof(void*);       // Slots for dictionary slots based on a dictionary layout
+    }
 
     return bytes;
 }
@@ -109,194 +108,344 @@ DictionaryLayout::GetFirstDictionaryBucketSize(
 // Optimize the case of a token being !i (for class dictionaries) or !!i (for method dictionaries)
 //
 /* static */
-BOOL
-DictionaryLayout::FindTokenWorker(LoaderAllocator *                 pAllocator,
-                                  DWORD                             numGenericArgs,
-                                  DictionaryLayout *                pDictLayout,
-                                  CORINFO_RUNTIME_LOOKUP *          pResult,
-                                  SigBuilder *                      pSigBuilder,
-                                  BYTE *                            pSig,
-                                  DWORD                             cbSig,
-                                  int                               nFirstOffset,
-                                  DictionaryEntrySignatureSource    signatureSource,
-                                  WORD *                            pSlotOut)
+BOOL DictionaryLayout::FindTokenWorker(LoaderAllocator*                 pAllocator,
+                                       DWORD                            numGenericArgs,
+                                       DictionaryLayout*                pDictLayout,
+                                       SigBuilder*                      pSigBuilder,
+                                       BYTE*                            pSig,
+                                       DWORD                            cbSig,
+                                       int                              nFirstOffset,
+                                       DictionaryEntrySignatureSource   signatureSource,
+                                       CORINFO_RUNTIME_LOOKUP*          pResult,
+                                       WORD*                            pSlotOut,
+                                       DWORD                            scanFromSlot /* = 0 */,
+                                       BOOL                             useEmptySlotIfFound /* = FALSE */)
+
 {
     CONTRACTL
     {
         STANDARD_VM_CHECK;
         PRECONDITION(numGenericArgs > 0);
+        PRECONDITION(scanFromSlot >= 0 && scanFromSlot <= pDictLayout->m_numSlots);
         PRECONDITION(CheckPointer(pDictLayout));
-        PRECONDITION(CheckPointer(pSlotOut));
+        PRECONDITION(CheckPointer(pResult) && CheckPointer(pSlotOut));
         PRECONDITION(CheckPointer(pSig));
         PRECONDITION((pSigBuilder == NULL && cbSig == -1) || (CheckPointer(pSigBuilder) && cbSig > 0));
     }
     CONTRACTL_END
 
-#ifndef FEATURE_NATIVE_IMAGE_GENERATION
-    // If the tiered compilation is on, save the fast dictionary slots for the hot Tier1 code
-    if (g_pConfig->TieredCompilation() && signatureSource == FromReadyToRunImage)
-    {
-        pResult->signature = pSig;
-        return FALSE;
-    }
-#endif
-
-    BOOL isFirstBucket = TRUE;
+    // First slots contain the type parameters
+    _ASSERTE(FitsIn<WORD>(numGenericArgs + 1 + scanFromSlot));
+    WORD slot = static_cast<WORD>(numGenericArgs + 1 + scanFromSlot);
 
-    // First bucket also contains type parameters
-    _ASSERTE(FitsIn<WORD>(numGenericArgs));
-    WORD slot = static_cast<WORD>(numGenericArgs);
-    for (;;)
+#if _DEBUG
+    if (scanFromSlot > 0)
     {
-        for (DWORD iSlot = 0; iSlot < pDictLayout->m_numSlots; iSlot++)
+        _ASSERT(useEmptySlotIfFound);
+
+        for (DWORD iSlot = 0; iSlot < scanFromSlot; iSlot++)
         {
-        RetryMatch:
-            BYTE * pCandidate = (BYTE *)pDictLayout->m_slots[iSlot].m_signature;
-            if (pCandidate != NULL)
+            // Verify that no entry before scanFromSlot matches the entry we're searching for
+            BYTE* pCandidate = (BYTE*)pDictLayout->m_slots[iSlot].m_signature;
+            if (pSigBuilder != NULL)
             {
-                bool signaturesMatch = false;
-
-                if (pSigBuilder != NULL)
-                {
-                    // JIT case: compare signatures by comparing the bytes in them. We exclude
-                    // any ReadyToRun signatures from the JIT case.
-
-                    if (pDictLayout->m_slots[iSlot].m_signatureSource != FromReadyToRunImage)
-                    {
-                        // Compare the signatures. We do not need to worry about the size of pCandidate.
-                        // As long as we are comparing one byte at a time we are guaranteed to not overrun.
-                        DWORD j;
-                        for (j = 0; j < cbSig; j++)
-                        {
-                            if (pCandidate[j] != pSig[j])
-                                break;
-                        }
-                        signaturesMatch = (j == cbSig);
-                    }
-                }
-                else
+                if (pDictLayout->m_slots[iSlot].m_signatureSource != FromReadyToRunImage)
                 {
-                    // ReadyToRun case: compare signatures by comparing their pointer values
-                    signaturesMatch = (pCandidate == pSig);
-                }
-
-                // We've found it
-                if (signaturesMatch)
-                {
-                    pResult->signature = pDictLayout->m_slots[iSlot].m_signature;
-
-                    // We don't store entries outside the first bucket in the layout in the dictionary (they'll be cached in a hash
-                    // instead).
-                    if (!isFirstBucket)
+                    DWORD j;
+                    for (j = 0; j < cbSig; j++)
                     {
-                        return FALSE;
+                        if (pCandidate[j] != pSig[j])
+                            break;
                     }
-                    _ASSERTE(FitsIn<WORD>(nFirstOffset + 1));
-                    pResult->indirections = static_cast<WORD>(nFirstOffset + 1);
-                    pResult->offsets[nFirstOffset] = slot * sizeof(DictionaryEntry);
-                    *pSlotOut = slot;
-                    return TRUE;
+                    _ASSERT(j != cbSig);
                 }
             }
-            // If we hit an empty slot then there's no more so use it
             else
             {
-                {
-                    BaseDomain::LockHolder lh(pAllocator->GetDomain());
+                _ASSERT(pCandidate != pSig);
+            }
+        }
+    }
+#endif
 
-                    if (pDictLayout->m_slots[iSlot].m_signature != NULL)
-                        goto RetryMatch;
+    for (DWORD iSlot = scanFromSlot; iSlot < pDictLayout->m_numSlots; iSlot++)
+    {
+        BYTE* pCandidate = (BYTE*)pDictLayout->m_slots[iSlot].m_signature;
+        if (pCandidate != NULL)
+        {
+            bool signaturesMatch = false;
 
-                    PVOID pResultSignature = pSig;
+            if (pSigBuilder != NULL)
+            {
+                // JIT case: compare signatures by comparing the bytes in them. We exclude
+                // any ReadyToRun signatures from the JIT case.
 
-                    if (pSigBuilder != NULL)
+                if (pDictLayout->m_slots[iSlot].m_signatureSource != FromReadyToRunImage)
+                {
+                    // Compare the signatures. We do not need to worry about the size of pCandidate. 
+                    // As long as we are comparing one byte at a time we are guaranteed to not overrun.
+                    DWORD j;
+                    for (j = 0; j < cbSig; j++)
                     {
-                        pSigBuilder->AppendData(isFirstBucket ? slot : 0);
-
-                        DWORD cbNewSig;
-                        PVOID pNewSig = pSigBuilder->GetSignature(&cbNewSig);
-
-                        pResultSignature = pAllocator->GetLowFrequencyHeap()->AllocMem(S_SIZE_T(cbNewSig));
-                        memcpy(pResultSignature, pNewSig, cbNewSig);
+                        if (pCandidate[j] != pSig[j])
+                            break;
                     }
-
-                    pDictLayout->m_slots[iSlot].m_signature = pResultSignature;
-                    pDictLayout->m_slots[iSlot].m_signatureSource = signatureSource;
+                    signaturesMatch = (j == cbSig);
                 }
+            }
+            else
+            {
+                // ReadyToRun case: compare signatures by comparing their pointer values
+                signaturesMatch = (pCandidate == pSig);
+            }
 
+            // We've found it
+            if (signaturesMatch)
+            {
                 pResult->signature = pDictLayout->m_slots[iSlot].m_signature;
 
-                // Again, we only store entries in the first layout bucket in the dictionary.
-                if (!isFirstBucket)
-                {
-                    return FALSE;
-                }
                 _ASSERTE(FitsIn<WORD>(nFirstOffset + 1));
                 pResult->indirections = static_cast<WORD>(nFirstOffset + 1);
                 pResult->offsets[nFirstOffset] = slot * sizeof(DictionaryEntry);
                 *pSlotOut = slot;
                 return TRUE;
             }
-            slot++;
         }
+        // If we hit an empty slot then there's no more so use it
+        else
+        {
+            if (!useEmptySlotIfFound)
+            {
+                *pSlotOut = static_cast<WORD>(iSlot);
+                return FALSE;
+            }
+
+            // A lock should be taken by FindToken before being allowed to use an empty slot in the layout
+            _ASSERT(SystemDomain::SystemModule()->m_DictionaryCrst.OwnedByCurrentThread());
+
+            PVOID pResultSignature = pSigBuilder == NULL ? pSig : CreateSignatureWithSlotData(pSigBuilder, pAllocator, slot);
+            pDictLayout->m_slots[iSlot].m_signature = pResultSignature;
+            pDictLayout->m_slots[iSlot].m_signatureSource = signatureSource;
 
-        // If we've reached the end of the chain we need to allocate another bucket. Make the pointer update carefully to avoid
-        // orphaning a bucket in a race. We leak the loser in such a race (since the allocation comes from the loader heap) but both
-        // the race and the overflow should be very rare.
-        if (pDictLayout->m_pNext == NULL)
-            FastInterlockCompareExchangePointer(&pDictLayout->m_pNext, Allocate(4, pAllocator, NULL), 0);
+            pResult->signature = pDictLayout->m_slots[iSlot].m_signature;
+
+            _ASSERTE(FitsIn<WORD>(nFirstOffset + 1));
+            pResult->indirections = static_cast<WORD>(nFirstOffset + 1);
+            pResult->offsets[nFirstOffset] = slot * sizeof(DictionaryEntry);
+            *pSlotOut = slot;
+            return TRUE;
+        }
 
-        pDictLayout = pDictLayout->m_pNext;
-        isFirstBucket = FALSE;
+        slot++;
     }
-} // DictionaryLayout::FindToken
 
+    *pSlotOut = pDictLayout->m_numSlots;
+    return FALSE;
+}
+
+#ifndef CROSSGEN_COMPILE
 /* static */
-BOOL
-DictionaryLayout::FindToken(LoaderAllocator *               pAllocator,
-                            DWORD                           numGenericArgs,
-                            DictionaryLayout *              pDictLayout,
-                            CORINFO_RUNTIME_LOOKUP *        pResult,
-                            SigBuilder *                    pSigBuilder,
-                            int                             nFirstOffset,
-                            DictionaryEntrySignatureSource  signatureSource)
+DictionaryLayout* DictionaryLayout::ExpandDictionaryLayout(LoaderAllocator*                 pAllocator, 
+                                                           DictionaryLayout*                pCurrentDictLayout, 
+                                                           DWORD                            numGenericArgs, 
+                                                           SigBuilder*                      pSigBuilder, 
+                                                           BYTE*                            pSig, 
+                                                           int                              nFirstOffset, 
+                                                           DictionaryEntrySignatureSource   signatureSource, 
+                                                           CORINFO_RUNTIME_LOOKUP*          pResult,
+                                                           WORD*                            pSlotOut)
 {
-    WRAPPER_NO_CONTRACT;
+    CONTRACTL
+    {
+        STANDARD_VM_CHECK;
+        INJECT_FAULT(ThrowOutOfMemory(););
+        PRECONDITION(SystemDomain::SystemModule()->m_DictionaryCrst.OwnedByCurrentThread());
+        PRECONDITION(CheckPointer(pResult) && CheckPointer(pSlotOut));
+    }
+    CONTRACTL_END
+        
+    // There shouldn't be any empty slots remaining in the current dictionary.
+    _ASSERTE(pCurrentDictLayout->m_slots[pCurrentDictLayout->m_numSlots - 1].m_signature != NULL);
+
+#ifdef _DEBUG
+    // Stress debug mode by increasing size by only 1 slot for the first 10 slots.
+    DWORD newSize = pCurrentDictLayout->m_numSlots > 10 ? (DWORD)pCurrentDictLayout->m_numSlots * 2 : pCurrentDictLayout->m_numSlots + 1;
+    if (!FitsIn<WORD>(newSize))
+        return NULL;
+    DictionaryLayout* pNewDictionaryLayout = Allocate((WORD)newSize, pAllocator, NULL);
+#else
+    if (!FitsIn<WORD>((DWORD)pCurrentDictLayout->m_numSlots * 2))
+        return NULL;
+    DictionaryLayout* pNewDictionaryLayout = Allocate(pCurrentDictLayout->m_numSlots * 2, pAllocator, NULL);
+#endif
+
+    pNewDictionaryLayout->m_numInitialSlots = pCurrentDictLayout->m_numInitialSlots;
+
+    for (DWORD iSlot = 0; iSlot < pCurrentDictLayout->m_numSlots; iSlot++)
+        pNewDictionaryLayout->m_slots[iSlot] = pCurrentDictLayout->m_slots[iSlot];
+
+    WORD layoutSlotIndex = pCurrentDictLayout->m_numSlots;
+    WORD slot = static_cast<WORD>(numGenericArgs) + 1 + layoutSlotIndex;
 
-    DWORD cbSig;
-    BYTE * pSig = (BYTE *)pSigBuilder->GetSignature(&cbSig);
+    PVOID pResultSignature = pSigBuilder == NULL ? pSig : CreateSignatureWithSlotData(pSigBuilder, pAllocator, slot);
+    pNewDictionaryLayout->m_slots[layoutSlotIndex].m_signature = pResultSignature;
+    pNewDictionaryLayout->m_slots[layoutSlotIndex].m_signatureSource = signatureSource;
 
-    WORD slotDummy;
-    return FindTokenWorker(pAllocator, numGenericArgs, pDictLayout, pResult, pSigBuilder, pSig, cbSig, nFirstOffset, signatureSource, &slotDummy);
+    pResult->signature = pNewDictionaryLayout->m_slots[layoutSlotIndex].m_signature;
+
+    _ASSERTE(FitsIn<WORD>(nFirstOffset + 1));
+    pResult->indirections = static_cast<WORD>(nFirstOffset + 1);
+    pResult->offsets[nFirstOffset] = slot * sizeof(DictionaryEntry);
+    *pSlotOut = slot;
+
+    return pNewDictionaryLayout;
 }
+#endif
 
 /* static */
-BOOL
-DictionaryLayout::FindToken(LoaderAllocator *               pAllocator,
-                            DWORD                           numGenericArgs,
-                            DictionaryLayout *              pDictLayout,
-                            CORINFO_RUNTIME_LOOKUP *        pResult,
-                            BYTE *                          signature,
-                            int                             nFirstOffset,
-                            DictionaryEntrySignatureSource  signatureSource,
-                            WORD *                          pSlotOut)
+BOOL DictionaryLayout::FindToken(MethodTable*                       pMT,
+                                 LoaderAllocator*                   pAllocator,
+                                 int                                nFirstOffset,
+                                 SigBuilder*                        pSigBuilder,
+                                 BYTE*                              pSig,
+                                 DictionaryEntrySignatureSource     signatureSource,
+                                 CORINFO_RUNTIME_LOOKUP*            pResult,
+                                 WORD*                              pSlotOut)
 {
-    WRAPPER_NO_CONTRACT;
+    CONTRACTL
+    {
+        STANDARD_VM_CHECK;
+        PRECONDITION(CheckPointer(pMT));
+        PRECONDITION(CheckPointer(pAllocator));
+        PRECONDITION(CheckPointer(pResult));
+        PRECONDITION(pMT->HasInstantiation());
+    }
+    CONTRACTL_END;
+
+    DWORD cbSig = -1;
+    pSig = pSigBuilder != NULL ? (BYTE*)pSigBuilder->GetSignature(&cbSig) : pSig;
+    if (FindTokenWorker(pAllocator, pMT->GetNumGenericArgs(), pMT->GetClass()->GetDictionaryLayout(), pSigBuilder, pSig, cbSig, nFirstOffset, signatureSource, pResult, pSlotOut, 0, FALSE))
+        return TRUE;
+
+    CrstHolder ch(&SystemDomain::SystemModule()->m_DictionaryCrst);
+    {
+        // Try again under lock in case another thread already expanded the dictionaries or filled an empty slot
+        if (FindTokenWorker(pMT->GetLoaderAllocator(), pMT->GetNumGenericArgs(), pMT->GetClass()->GetDictionaryLayout(), pSigBuilder, pSig, cbSig, nFirstOffset, signatureSource, pResult, pSlotOut, *pSlotOut, TRUE))
+            return TRUE;
 
-    return FindTokenWorker(pAllocator, numGenericArgs, pDictLayout, pResult, NULL, signature, -1, nFirstOffset, signatureSource, pSlotOut);
+#ifndef CROSSGEN_COMPILE
+        DictionaryLayout* pOldLayout = pMT->GetClass()->GetDictionaryLayout();
+        DictionaryLayout* pNewLayout = ExpandDictionaryLayout(pAllocator, pOldLayout, pMT->GetNumGenericArgs(), pSigBuilder, pSig, nFirstOffset, signatureSource, pResult, pSlotOut);
+        if (pNewLayout == NULL)
+        {
+            pResult->signature = pSigBuilder == NULL ? pSig : CreateSignatureWithSlotData(pSigBuilder, pAllocator, 0);
+            return FALSE;
+        }
+
+        // Update the dictionary layout pointer. Note that the expansion of the dictionaries of all instantiated types using this layout
+        // is done lazily, whenever we attempt to access a slot that is beyond the size of the existing dictionary on that type.
+        pMT->GetClass()->SetDictionaryLayout(pNewLayout);
+
+        return TRUE;
+#else
+        pResult->signature = pSigBuilder == NULL ? pSig : CreateSignatureWithSlotData(pSigBuilder, pAllocator, 0);
+        return FALSE;
+#endif
+    }
 }
 
+/* static */
+BOOL DictionaryLayout::FindToken(MethodDesc*                        pMD,
+                                 LoaderAllocator*                   pAllocator,
+                                 int                                nFirstOffset,
+                                 SigBuilder*                        pSigBuilder,
+                                 BYTE*                              pSig,
+                                 DictionaryEntrySignatureSource     signatureSource,
+                                 CORINFO_RUNTIME_LOOKUP*            pResult,
+                                 WORD*                              pSlotOut)
+{
+    CONTRACTL
+    {
+        STANDARD_VM_CHECK;
+        PRECONDITION(CheckPointer(pMD));
+        PRECONDITION(CheckPointer(pAllocator));
+        PRECONDITION(CheckPointer(pResult));
+        PRECONDITION(pMD->HasMethodInstantiation());
+    }
+    CONTRACTL_END;
+
+    DWORD cbSig = -1;
+    pSig = pSigBuilder != NULL ? (BYTE*)pSigBuilder->GetSignature(&cbSig) : pSig;
+    if (FindTokenWorker(pAllocator, pMD->GetNumGenericMethodArgs(), pMD->GetDictionaryLayout(), pSigBuilder, pSig, cbSig, nFirstOffset, signatureSource, pResult, pSlotOut, 0, FALSE))
+        return TRUE;
+
+    CrstHolder ch(&SystemDomain::SystemModule()->m_DictionaryCrst);
+    {
+        // Try again under lock in case another thread already expanded the dictionaries or filled an empty slot
+        if (FindTokenWorker(pAllocator, pMD->GetNumGenericMethodArgs(), pMD->GetDictionaryLayout(), pSigBuilder, pSig, cbSig, nFirstOffset, signatureSource, pResult, pSlotOut, *pSlotOut, TRUE))
+            return TRUE;
+
+#ifndef CROSSGEN_COMPILE
+        DictionaryLayout* pOldLayout = pMD->GetDictionaryLayout();
+        DictionaryLayout* pNewLayout = ExpandDictionaryLayout(pAllocator, pOldLayout, pMD->GetNumGenericMethodArgs(), pSigBuilder, pSig, nFirstOffset, signatureSource, pResult, pSlotOut);
+        if (pNewLayout == NULL)
+        {
+            pResult->signature = pSigBuilder == NULL ? pSig : CreateSignatureWithSlotData(pSigBuilder, pAllocator, 0);
+            return FALSE;
+        }
+
+        // Update the dictionary layout pointer. Note that the expansion of the dictionaries of all instantiated methods using this layout
+        // is done lazily, whenever we attempt to access a slot that is beyond the size of the existing dictionary on that method.
+        pMD->AsInstantiatedMethodDesc()->IMD_SetDictionaryLayout(pNewLayout);
+
+        return TRUE;
+#else
+        pResult->signature = pSigBuilder == NULL ? pSig : CreateSignatureWithSlotData(pSigBuilder, pAllocator, 0);
+        return FALSE;
+#endif
+    }
+}
+
+/* static */
+PVOID DictionaryLayout::CreateSignatureWithSlotData(SigBuilder* pSigBuilder, LoaderAllocator* pAllocator, WORD slot)
+{
+    CONTRACTL
+    {
+        STANDARD_VM_CHECK;
+        PRECONDITION(CheckPointer(pSigBuilder) && CheckPointer(pAllocator));
+    }
+    CONTRACTL_END
+
+    pSigBuilder->AppendData(slot);
+
+    DWORD cbNewSig;
+    PVOID pNewSig = pSigBuilder->GetSignature(&cbNewSig);
+
+    PVOID pResultSignature = pAllocator->GetLowFrequencyHeap()->AllocMem(S_SIZE_T(cbNewSig));
+    _ASSERT(pResultSignature != NULL);
+
+    memcpy(pResultSignature, pNewSig, cbNewSig);
+
+    return pResultSignature;
+}
+
+
 #endif //!DACCESS_COMPILE
 
 //---------------------------------------------------------------------------------------
 //
-DWORD
-DictionaryLayout::GetMaxSlots()
+DWORD DictionaryLayout::GetMaxSlots()
 {
     LIMITED_METHOD_CONTRACT;
     return m_numSlots;
 }
 
+DWORD DictionaryLayout::GetNumInitialSlots()
+{
+    LIMITED_METHOD_CONTRACT;
+    return m_numInitialSlots;
+}
+
 //---------------------------------------------------------------------------------------
 //
 DWORD
@@ -347,21 +496,13 @@ DictionaryLayout::GetObjectSize()
 //---------------------------------------------------------------------------------------
 //
 // Save the dictionary layout for prejitting
-//
-void
-DictionaryLayout::Save(
-    DataImage * image)
+// 
+void 
+DictionaryLayout::Save(DataImage * image)
 {
     STANDARD_VM_CONTRACT;
 
-    DictionaryLayout *pDictLayout = this;
-
-    while (pDictLayout)
-    {
-        image->StoreStructure(pDictLayout, pDictLayout->GetObjectSize(), DataImage::ITEM_DICTIONARY_LAYOUT);
-        pDictLayout = pDictLayout->m_pNext;
-    }
-
+    image->StoreStructure(this, GetObjectSize(), DataImage::ITEM_DICTIONARY_LAYOUT);
 }
 
 //---------------------------------------------------------------------------------------
@@ -378,15 +519,10 @@ DictionaryLayout::Trim()
     }
     CONTRACTL_END;
 
-    // Only the last bucket in the chain may have unused entries
-    DictionaryLayout *pDictLayout = this;
-    while (pDictLayout->m_pNext)
-        pDictLayout = pDictLayout->m_pNext;
-
     // Trim down the size to what's actually used
-    DWORD dwSlots = pDictLayout->GetNumUsedSlots();
+    DWORD dwSlots = GetNumUsedSlots();
     _ASSERTE(FitsIn<WORD>(dwSlots));
-    pDictLayout->m_numSlots = static_cast<WORD>(dwSlots);
+    m_numSlots = static_cast<WORD>(dwSlots);
 
 }
 
@@ -401,21 +537,14 @@ DictionaryLayout::Fixup(
 {
     STANDARD_VM_CONTRACT;
 
-    DictionaryLayout *pDictLayout = this;
-
-    while (pDictLayout)
+    for (DWORD i = 0; i < m_numSlots; i++)
     {
-        for (DWORD i = 0; i < pDictLayout->m_numSlots; i++)
+        PVOID signature = m_slots[i].m_signature;
+        if (signature != NULL)
         {
-            PVOID signature = pDictLayout->m_slots[i].m_signature;
-            if (signature != NULL)
-            {
-                image->FixupFieldToNode(pDictLayout, (BYTE *)&pDictLayout->m_slots[i].m_signature - (BYTE *)pDictLayout,
-                    image->GetGenericSignature(signature, fMethod));
-            }
+            image->FixupFieldToNode(this, (BYTE *)&m_slots[i].m_signature - (BYTE *)this,
+                image->GetGenericSignature(signature, fMethod));
         }
-        image->FixupPointerField(pDictLayout, offsetof(DictionaryLayout, m_pNext));
-        pDictLayout = pDictLayout->m_pNext;
     }
 }
 
@@ -640,6 +769,150 @@ Dictionary::Restore(
 }
 #endif // FEATURE_PREJIT
 
+DWORD Dictionary::GetDictionarySlotsSizeForType(MethodTable* pMT)
+{
+    CONTRACTL
+    {
+        PRECONDITION(pMT->HasPerInstInfo());
+        PRECONDITION(pMT->GetGenericsDictInfo()->m_wNumTyPars > 0);
+        PRECONDITION(pMT->GetClass()->GetDictionaryLayout() != NULL);
+        PRECONDITION(pMT->GetClass()->GetDictionaryLayout()->GetMaxSlots() > 0);
+    }
+    CONTRACTL_END;
+
+    TADDR* pDictionarySlots = (TADDR*)pMT->GetPerInstInfo()[pMT->GetGenericsDictInfo()->m_wNumDicts - 1].GetValue();
+    TADDR* pSizeSlot = pDictionarySlots + pMT->GetGenericsDictInfo()->m_wNumTyPars;
+    return *(DWORD*)pSizeSlot;
+}
+
+DWORD Dictionary::GetDictionarySlotsSizeForMethod(MethodDesc* pMD)
+{
+    CONTRACTL
+    {
+        PRECONDITION(pMD->AsInstantiatedMethodDesc()->IMD_GetMethodDictionary() != NULL);
+    }
+    CONTRACTL_END;
+
+    InstantiatedMethodDesc* pIMD = pMD->AsInstantiatedMethodDesc();
+    TADDR* pDictionarySlots = (TADDR*)pIMD->IMD_GetMethodDictionary();
+    TADDR* pSizeSlot = pDictionarySlots + pIMD->GetNumGenericMethodArgs();
+    return *(DWORD*)pSizeSlot;
+}
+
+Dictionary* Dictionary::GetMethodDictionaryWithSizeCheck(MethodDesc* pMD, ULONG slotIndex)
+{
+    CONTRACT(Dictionary*)
+    {
+        THROWS;
+        GC_TRIGGERS;
+        POSTCONDITION(CheckPointer(RETVAL));
+    }
+    CONTRACT_END;
+
+    Dictionary* pDictionary = pMD->GetMethodDictionary();
+
+#if !defined(CROSSGEN_COMPILE)
+    DWORD currentDictionarySize = GetDictionarySlotsSizeForMethod(pMD);
+
+    if (currentDictionarySize <= (slotIndex * sizeof(DictionaryEntry)))
+    {
+        // Only expand the dictionary if the current slot we're trying to use is beyond the size of the dictionary
+
+        // Take lock and check for size again, just in case another thread already resized the dictionary
+        CrstHolder ch(&SystemDomain::SystemModule()->m_DictionaryCrst);
+
+        pDictionary = pMD->GetMethodDictionary();
+        currentDictionarySize = GetDictionarySlotsSizeForMethod(pMD);
+
+        if (currentDictionarySize <= (slotIndex * sizeof(DictionaryEntry)))
+        {
+            DictionaryLayout* pDictLayout = pMD->GetDictionaryLayout();
+            InstantiatedMethodDesc* pIMD = pMD->AsInstantiatedMethodDesc();
+            _ASSERTE(pDictLayout != NULL && pDictLayout->GetMaxSlots() > 0);
+
+            DWORD expectedDictionarySize = DictionaryLayout::GetDictionarySizeFromLayout(pMD->GetNumGenericMethodArgs(), pDictLayout);
+            _ASSERT(currentDictionarySize < expectedDictionarySize);
+
+            pDictionary = (Dictionary*)(void*)pIMD->GetLoaderAllocator()->GetHighFrequencyHeap()->AllocMem(S_SIZE_T(expectedDictionarySize));
+
+            // Copy old dictionary entry contents
+            DictionaryEntry* pOldEntriesPtr = (DictionaryEntry*)pIMD->m_pPerInstInfo.GetValue();
+            DictionaryEntry* pNewEntriesPtr = (DictionaryEntry*)pDictionary;
+            for (DWORD i = 0; i < currentDictionarySize / sizeof(DictionaryEntry); i++, pOldEntriesPtr++, pNewEntriesPtr++)
+            {
+                *pNewEntriesPtr = VolatileLoadWithoutBarrier(pOldEntriesPtr);
+            }
+
+            DWORD* pSizeSlot = (DWORD*)(pDictionary + pIMD->GetNumGenericMethodArgs());
+            *pSizeSlot = expectedDictionarySize;
+
+            // Publish the new dictionary slots to the type.
+            FastInterlockExchangePointer((TypeHandle**)pIMD->m_pPerInstInfo.GetValuePtr(), (TypeHandle*)pDictionary);
+        }
+    }
+#endif
+
+    RETURN pDictionary;
+}
+
+Dictionary* Dictionary::GetTypeDictionaryWithSizeCheck(MethodTable* pMT, ULONG slotIndex)
+{
+    CONTRACT(Dictionary*)
+    {
+       THROWS;
+       GC_TRIGGERS;
+       POSTCONDITION(CheckPointer(RETVAL));
+    }
+    CONTRACT_END;
+
+    Dictionary* pDictionary = pMT->GetDictionary();
+
+#if !defined(CROSSGEN_COMPILE)
+    DWORD currentDictionarySize = GetDictionarySlotsSizeForType(pMT);
+
+    if (currentDictionarySize <= (slotIndex * sizeof(DictionaryEntry)))
+    {
+        // Only expand the dictionary if the current slot we're trying to use is beyond the size of the dictionary
+
+        // Take lock and check for size again, just in case another thread already resized the dictionary
+        CrstHolder ch(&SystemDomain::SystemModule()->m_DictionaryCrst);
+
+        pDictionary = pMT->GetDictionary();
+        currentDictionarySize = GetDictionarySlotsSizeForType(pMT);
+
+        if (currentDictionarySize <= (slotIndex * sizeof(DictionaryEntry)))
+        {
+            DictionaryLayout* pDictLayout = pMT->GetClass()->GetDictionaryLayout();
+            _ASSERTE(pDictLayout != NULL && pDictLayout->GetMaxSlots() > 0);
+
+            DWORD expectedDictionarySize = DictionaryLayout::GetDictionarySizeFromLayout(pMT->GetNumGenericArgs(), pDictLayout);
+            _ASSERT(currentDictionarySize < expectedDictionarySize);
+
+            // Expand type dictionary
+            pDictionary = (Dictionary*)(void*)pMT->GetLoaderAllocator()->GetHighFrequencyHeap()->AllocMem(S_SIZE_T(expectedDictionarySize));
+
+            // Copy old dictionary entry contents
+            DictionaryEntry* pOldEntriesPtr = (DictionaryEntry*)pMT->GetPerInstInfo()[pMT->GetNumDicts() - 1].GetValue();
+            DictionaryEntry* pNewEntriesPtr = (DictionaryEntry*)pDictionary;
+            for (DWORD i = 0; i < currentDictionarySize / sizeof(DictionaryEntry); i++, pOldEntriesPtr++, pNewEntriesPtr++)
+            {
+                *pNewEntriesPtr = VolatileLoadWithoutBarrier(pOldEntriesPtr);
+            }
+
+            DWORD* pSizeSlot = (DWORD*)(pDictionary + pMT->GetNumGenericArgs());
+            *pSizeSlot = expectedDictionarySize;
+
+            // Publish the new dictionary slots to the type.
+            ULONG dictionaryIndex = pMT->GetNumDicts() - 1;
+            TypeHandle** pPerInstInfo = (TypeHandle**)pMT->GetPerInstInfo()->GetValuePtr();
+            FastInterlockExchangePointer(pPerInstInfo + dictionaryIndex, (TypeHandle*)pDictionary);
+        }
+    }
+#endif
+
+    RETURN pDictionary;
+}
+
 //---------------------------------------------------------------------------------------
 //
 DictionaryEntry
@@ -658,7 +931,6 @@ Dictionary::PopulateEntry(
     } CONTRACTL_END;
 
     CORINFO_GENERIC_HANDLE result = NULL;
-    Dictionary * pDictionary = NULL;
     *ppSlot = NULL;
 
     bool isReadyToRunModule = (pModule != NULL && pModule->IsReadyToRun());
@@ -749,7 +1021,6 @@ Dictionary::PopulateEntry(
         // prepare for every possible derived type of the type containing the method). So instead we have to locate the exactly
         // instantiated (non-shared) super-type of the class passed in.
 
-        pDictionary = pMT->GetDictionary();
 
         ULONG dictionaryIndex = 0;
 
@@ -761,13 +1032,15 @@ Dictionary::PopulateEntry(
         {
             IfFailThrow(ptr.GetData(&dictionaryIndex));
         }
+        
+#if _DEBUG
+        // Lock is needed because dictionary pointers can get updated during dictionary size expansion
+        CrstHolder ch(&SystemDomain::SystemModule()->m_DictionaryCrst);
 
         // MethodTable is expected to be normalized
+        Dictionary* pDictionary = pMT->GetDictionary();
         _ASSERTE(pDictionary == pMT->GetPerInstInfo()[dictionaryIndex].GetValueMaybeNull());
-    }
-    else
-    {
-        pDictionary = pMD->GetMethodDictionary();
+#endif
     }
 
     {
@@ -1245,7 +1518,11 @@ Dictionary::PopulateEntry(
 
         if ((slotIndex != 0) && !IsCompilationProcess())
         {
-            *(pDictionary->GetSlotAddr(0, slotIndex)) = result;
+            Dictionary* pDictionary = (pMT != NULL) ?
+                GetTypeDictionaryWithSizeCheck(pMT, slotIndex) :
+                GetMethodDictionaryWithSizeCheck(pMD, slotIndex);
+
+            VolatileStoreWithoutBarrier(pDictionary->GetSlotAddr(0, slotIndex), (DictionaryEntry)result);
             *ppSlot = pDictionary->GetSlotAddr(0, slotIndex);
         }
     }
index 4499388..75d4554 100644 (file)
@@ -92,6 +92,13 @@ typedef DPTR(DictionaryEntryLayout) PTR_DictionaryEntryLayout;
 class DictionaryLayout;
 typedef DPTR(DictionaryLayout) PTR_DictionaryLayout;
 
+// Number of slots to initially allocate in a generic method dictionary layout.
+#if _DEBUG
+#define NUM_DICTIONARY_SLOTS 1  // Smaller number to stress the dictionary expansion logic
+#else
+#define NUM_DICTIONARY_SLOTS 4
+#endif
+
 // The type of dictionary layouts. We don't include the number of type
 // arguments as this is obtained elsewhere
 class DictionaryLayout
@@ -101,53 +108,69 @@ class DictionaryLayout
     friend class NativeImageDumper;
 #endif
 private:
-    // Next bucket of slots (only used to track entries that won't fit in the dictionary)
-    DictionaryLayout* m_pNext;
-
-    // Number of non-type-argument slots in this bucket
+    // Current number of non-type-argument slots
     WORD m_numSlots;
 
+    // Number of non-type-argument slots of the initial layout before any expansion
+    WORD m_numInitialSlots;
+
     // m_numSlots of these
     DictionaryEntryLayout m_slots[1];
 
-    static BOOL FindTokenWorker(LoaderAllocator *pAllocator,
-                                DWORD numGenericArgs,
-                                DictionaryLayout *pDictLayout,
-                                CORINFO_RUNTIME_LOOKUP *pResult,
-                                SigBuilder * pSigBuilder,
-                                BYTE * pSig,
-                                DWORD cbSig,
-                                int nFirstOffset,
-                                DictionaryEntrySignatureSource signatureSource,
-                                WORD * pSlotOut);
-
+    static BOOL FindTokenWorker(LoaderAllocator*                    pAllocator,
+                                DWORD                               numGenericArgs,
+                                DictionaryLayout*                   pDictLayout,
+                                SigBuilder*                         pSigBuilder,
+                                BYTE*                               pSig,
+                                DWORD                               cbSig,
+                                int                                 nFirstOffset,
+                                DictionaryEntrySignatureSource      signatureSource,
+                                CORINFO_RUNTIME_LOOKUP*             pResult,
+                                WORD*                               pSlotOut,
+                                DWORD                               scanFromSlot,
+                                BOOL                                useEmptySlotIfFound);
+
+
+    static DictionaryLayout* ExpandDictionaryLayout(LoaderAllocator*                pAllocator,
+                                                    DictionaryLayout*               pCurrentDictLayout,
+                                                    DWORD                           numGenericArgs,
+                                                    SigBuilder*                     pSigBuilder,
+                                                    BYTE*                           pSig,
+                                                    int                             nFirstOffset,
+                                                    DictionaryEntrySignatureSource  signatureSource,
+                                                    CORINFO_RUNTIME_LOOKUP*         pResult,
+                                                    WORD*                           pSlotOut);
+     
+    static PVOID CreateSignatureWithSlotData(SigBuilder* pSigBuilder, LoaderAllocator* pAllocator, WORD slot);
 
 public:
-    // Create an initial dictionary layout with a single bucket containing numSlots slots
+    // Create an initial dictionary layout containing numSlots slots
     static DictionaryLayout* Allocate(WORD numSlots, LoaderAllocator *pAllocator, AllocMemTracker *pamTracker);
 
-    // Bytes used for the first bucket of this dictionary, which might be stored inline in
+    // Bytes used for this dictionary, which might be stored inline in
     // another structure (e.g. MethodTable)
-    static DWORD GetFirstDictionaryBucketSize(DWORD numGenericArgs, PTR_DictionaryLayout pDictLayout);
-
-    static BOOL FindToken(LoaderAllocator *pAllocator,
-                          DWORD numGenericArgs,
-                          DictionaryLayout *pDictLayout,
-                          CORINFO_RUNTIME_LOOKUP *pResult,
-                          SigBuilder * pSigBuilder,
-                          int nFirstOffset,
-                          DictionaryEntrySignatureSource signatureSource);
-
-    static BOOL FindToken(LoaderAllocator * pAllocator,
-                          DWORD numGenericArgs,
-                          DictionaryLayout * pDictLayout,
-                          CORINFO_RUNTIME_LOOKUP * pResult,
-                          BYTE * signature,
-                          int nFirstOffset,
-                          DictionaryEntrySignatureSource signatureSource,
-                          WORD * pSlotOut);
+    static DWORD GetDictionarySizeFromLayout(DWORD numGenericArgs, PTR_DictionaryLayout pDictLayout);
+
+    static BOOL FindToken(MethodTable*                      pMT,
+                          LoaderAllocator*                  pAllocator,
+                          int                               nFirstOffset,
+                          SigBuilder*                       pSigBuilder,
+                          BYTE*                             pSig,
+                          DictionaryEntrySignatureSource    signatureSource,
+                          CORINFO_RUNTIME_LOOKUP*           pResult,
+                          WORD*                             pSlotOut);
+
+    static BOOL FindToken(MethodDesc*                       pMD,
+                          LoaderAllocator*                  pAllocator,
+                          int                               nFirstOffset,
+                          SigBuilder*                       pSigBuilder,
+                          BYTE*                             pSig,
+                          DictionaryEntrySignatureSource    signatureSource,
+                          CORINFO_RUNTIME_LOOKUP*           pResult,
+                          WORD*                             pSlotOut);
 
     DWORD GetMaxSlots();
+    DWORD GetNumInitialSlots();
     DWORD GetNumUsedSlots();
 
     PTR_DictionaryEntryLayout GetEntryLayout(DWORD i)
@@ -158,7 +181,6 @@ public:
             dac_cast<TADDR>(this) + offsetof(DictionaryLayout, m_slots) + sizeof(DictionaryEntryLayout) * i);
     }
 
-    DictionaryLayout* GetNextLayout() { LIMITED_METHOD_CONTRACT; return m_pNext; }
 
 #ifdef FEATURE_PREJIT
     DWORD GetObjectSize();
@@ -194,7 +216,7 @@ class Dictionary
 #ifdef DACCESS_COMPILE
     friend class NativeImageDumper;
 #endif
-  private:
+private:
     // First N entries are generic instantiations arguments. They are stored as FixupPointers
     // in NGen images. It means that the lowest bit is used to mark optional indirection (see code:FixupPointer).
     // The rest of the open array are normal pointers (no optional indirection).
@@ -208,7 +230,7 @@ class Dictionary
             idx * sizeof(m_pEntries[0]);
     }
 
-  public:
+public:
     inline DPTR(FixupPointer<TypeHandle>) GetInstantiation()
     {
         LIMITED_METHOD_CONTRACT;
@@ -220,11 +242,11 @@ class Dictionary
     inline void* AsPtr()
     {
         LIMITED_METHOD_CONTRACT;
-        return (void*) m_pEntries;
+        return (void*)m_pEntries;
     }
 #endif // #ifndef DACCESS_COMPILE
 
-  private:
+private:
 
 #ifndef DACCESS_COMPILE
 
@@ -233,50 +255,50 @@ class Dictionary
         LIMITED_METHOD_CONTRACT;
         return *GetTypeHandleSlotAddr(numGenericArgs, i);
     }
-    inline MethodDesc *GetMethodDescSlot(DWORD numGenericArgs, DWORD i)
+    inline MethodDescGetMethodDescSlot(DWORD numGenericArgs, DWORD i)
     {
         LIMITED_METHOD_CONTRACT;
-        return *GetMethodDescSlotAddr(numGenericArgs,i);
+        return *GetMethodDescSlotAddr(numGenericArgs, i);
     }
-    inline FieldDesc *GetFieldDescSlot(DWORD numGenericArgs, DWORD i)
+    inline FieldDescGetFieldDescSlot(DWORD numGenericArgs, DWORD i)
     {
         LIMITED_METHOD_CONTRACT;
-        return *GetFieldDescSlotAddr(numGenericArgs,i);
+        return *GetFieldDescSlotAddr(numGenericArgs, i);
     }
-    inline TypeHandle *GetTypeHandleSlotAddr(DWORD numGenericArgs, DWORD i)
+    inline TypeHandleGetTypeHandleSlotAddr(DWORD numGenericArgs, DWORD i)
     {
         LIMITED_METHOD_CONTRACT;
-        return ((TypeHandle *) &m_pEntries[numGenericArgs + i]);
+        return ((TypeHandle*)&m_pEntries[numGenericArgs + i]);
     }
-    inline MethodDesc **GetMethodDescSlotAddr(DWORD numGenericArgs, DWORD i)
+    inline MethodDesc** GetMethodDescSlotAddr(DWORD numGenericArgs, DWORD i)
     {
         LIMITED_METHOD_CONTRACT;
-        return ((MethodDesc **) &m_pEntries[numGenericArgs + i]);
+        return ((MethodDesc**)&m_pEntries[numGenericArgs + i]);
     }
-    inline FieldDesc **GetFieldDescSlotAddr(DWORD numGenericArgs, DWORD i)
+    inline FieldDesc** GetFieldDescSlotAddr(DWORD numGenericArgs, DWORD i)
     {
         LIMITED_METHOD_CONTRACT;
-        return ((FieldDesc **) &m_pEntries[numGenericArgs + i]);
+        return ((FieldDesc**)&m_pEntries[numGenericArgs + i]);
     }
-    inline DictionaryEntry *GetSlotAddr(DWORD numGenericArgs, DWORD i)
+    inline DictionaryEntryGetSlotAddr(DWORD numGenericArgs, DWORD i)
     {
         LIMITED_METHOD_CONTRACT;
-        return ((void **) &m_pEntries[numGenericArgs + i]);
+        return ((void**)&m_pEntries[numGenericArgs + i]);
     }
     inline DictionaryEntry GetSlot(DWORD numGenericArgs, DWORD i)
     {
         LIMITED_METHOD_CONTRACT;
-        return *GetSlotAddr(numGenericArgs,i);
+        return *GetSlotAddr(numGenericArgs, i);
     }
     inline BOOL IsSlotEmpty(DWORD numGenericArgs, DWORD i)
     {
         LIMITED_METHOD_CONTRACT;
-        return GetSlot(numGenericArgs,i) == NULL;
+        return GetSlot(numGenericArgs, i) == NULL;
     }
 
 #endif // #ifndef DACCESS_COMPILE
 
-  public:
+public:
 
 #ifndef DACCESS_COMPILE
 
@@ -292,9 +314,16 @@ class Dictionary
                                MethodTable * pMT,
                                BOOL nonExpansive);
 
+private:
+    static DWORD GetDictionarySlotsSizeForType(MethodTable* pMT);
+    static DWORD GetDictionarySlotsSizeForMethod(MethodDesc* pMD);
+
+    static Dictionary* GetTypeDictionaryWithSizeCheck(MethodTable* pMT, ULONG slotIndex);
+    static Dictionary* GetMethodDictionaryWithSizeCheck(MethodDesc* pMD, ULONG slotIndex);
+
 #endif // #ifndef DACCESS_COMPILE
 
-  public:
+public:
 
 #ifdef FEATURE_PREJIT
 
@@ -312,13 +341,13 @@ class Dictionary
                BOOL canSaveInstantiation,
                BOOL canSaveSlots,
                DWORD numGenericArgs,            // Must be non-zero
-               Module *pModule, // module of the generic code
+               Module *pModule,                 // module of the generic code
                DictionaryLayout *pDictLayout);  // If NULL, then only type arguments are present
 
     BOOL IsWriteable(DataImage *image,
                BOOL canSaveSlots,
                DWORD numGenericArgs,            // Must be non-zero
-               Module *pModule, // module of the generic code
+               Module *pModule,                 // module of the generic code
                DictionaryLayout *pDictLayout);  // If NULL, then only type arguments are present
 
     BOOL ComputeNeedsRestore(DataImage *image,
index 8a8bc4c..5a19aac 100644 (file)
@@ -278,6 +278,10 @@ ClassLoader::CreateTypeHandleForNonCanonicalGenericInstantiation(
     DWORD cbPerInst = sizeof(GenericsDictInfo) + pOldMT->GetPerInstInfoSize();
 
     // Finally we need space for the instantiation/dictionary for this type
+    // Note that it is possible for the dictionary layout to be expanded in size by other threads while we're still
+    // creating this type. In other words: this type will have a smaller dictionary that its layout. This is not a
+    // problem however because whenever we need to load a value from the dictionary of this type beyond its size, we
+    // will expand the dictionary at that point.
     DWORD cbInstAndDict = pOldMT->GetInstAndDictSize();
 
     // Allocate from the high frequence heap of the correct domain
@@ -488,6 +492,15 @@ ClassLoader::CreateTypeHandleForNonCanonicalGenericInstantiation(
         pInstDest[iArg] = inst[iArg];
     }
 
+    PTR_DictionaryLayout pLayout = pOldMT->GetClass()->GetDictionaryLayout();
+    if (pLayout != NULL)
+    {
+        _ASSERTE(pLayout->GetMaxSlots() > 0);
+        PTR_Dictionary pDictionarySlots = pMT->GetPerInstInfo()[pOldMT->GetNumDicts() - 1].GetValue();
+        DWORD* pSizeSlot = (DWORD*)(pDictionarySlots + ntypars);
+        *pSizeSlot = cbInstAndDict;
+    }
+
     // Copy interface map across
     InterfaceInfo_t * pInterfaceMap = (InterfaceInfo_t *)(pMemory + cbMT + cbOptional + (fHasDynamicInterfaceMap ? sizeof(DWORD_PTR) : 0));
 
index 71f1e30..4c2b3c5 100644 (file)
@@ -373,28 +373,41 @@ InstantiatedMethodDesc::NewInstantiatedMethodDesc(MethodTable *pExactMT,
             {
                 if (pWrappedMD->IsSharedByGenericMethodInstantiations())
                 {
+                    // Note that it is possible for the dictionary layout to be expanded in size by other threads while we're still
+                    // creating this method. In other words: this method will have a smaller dictionary that its layout. This is not a
+                    // problem however because whenever we need to load a value from the dictionary of this method beyond its size, we
+                    // will expand the dictionary at that point.
                     pDL = pWrappedMD->AsInstantiatedMethodDesc()->GetDictLayoutRaw();
                 }
             }
             else if (getWrappedCode)
             {
-                // 4 seems like a good number
-                pDL = DictionaryLayout::Allocate(4, pAllocator, &amt);
-#ifdef _DEBUG
+                pDL = DictionaryLayout::Allocate(NUM_DICTIONARY_SLOTS, pAllocator, &amt);
+#ifdef _DEBUG 
                 {
                     SString name;
                     TypeString::AppendMethodDebug(name, pGenericMDescInRepMT);
                     LOG((LF_JIT, LL_INFO1000, "GENERICS: Created new dictionary layout for dictionary of size %d for %S\n",
-                         DictionaryLayout::GetFirstDictionaryBucketSize(pGenericMDescInRepMT->GetNumGenericMethodArgs(), pDL), name.GetUnicode()));
+                        DictionaryLayout::GetDictionarySizeFromLayout(pGenericMDescInRepMT->GetNumGenericMethodArgs(), pDL), name.GetUnicode()));
                 }
 #endif // _DEBUG
             }
 
             // Allocate space for the instantiation and dictionary
-            infoSize = DictionaryLayout::GetFirstDictionaryBucketSize(methodInst.GetNumArgs(), pDL);
-            pInstOrPerInstInfo = (TypeHandle *) (void*) amt.Track(pAllocator->GetHighFrequencyHeap()->AllocMem(S_SIZE_T(infoSize)));
+            infoSize = DictionaryLayout::GetDictionarySizeFromLayout(methodInst.GetNumArgs(), pDL);
+            pInstOrPerInstInfo = (TypeHandle*)(void*)amt.Track(pAllocator->GetHighFrequencyHeap()->AllocMem(S_SIZE_T(infoSize)));
             for (DWORD i = 0; i < methodInst.GetNumArgs(); i++)
                 pInstOrPerInstInfo[i] = methodInst[i];
+
+            if (pDL != NULL && pDL->GetMaxSlots() > 0)
+            {
+                // Has to be at least larger than the first slots containing the instantiation arguments,
+                // and the slot with size information. Otherwise, we shouldn't really have a size slot
+                _ASSERTE(infoSize > sizeof(TypeHandle*) * (methodInst.GetNumArgs() + 1));
+
+                DWORD* pDictSizeSlot = (DWORD*)(pInstOrPerInstInfo + methodInst.GetNumArgs());
+                *pDictSizeSlot = infoSize;
+            }
         }
 
         BOOL forComInterop = FALSE;
@@ -482,8 +495,8 @@ InstantiatedMethodDesc::NewInstantiatedMethodDesc(MethodTable *pExactMT,
                 const char* verb = "Created";
                 if (pWrappedMD)
                     LOG((LF_CLASSLOADER, LL_INFO1000,
-                         "GENERICS: %s instantiating-stub method desc %s with dictionary size %d\n",
-                         verb, pDebugNameUTF8, infoSize));
+                        "GENERICS: %s instantiating-stub method desc %s with dictionary size %d\n",
+                        verb, pDebugNameUTF8, infoSize));
                 else
                     LOG((LF_CLASSLOADER, LL_INFO1000,
                          "GENERICS: %s instantiated method desc %s\n",
index fb890e8..0de1ab4 100644 (file)
@@ -1663,6 +1663,8 @@ PCODE DynamicHelpers::CreateDictionaryLookupHelper(LoaderAllocator * pAllocator,
 {
     STANDARD_VM_CONTRACT;
 
+    _ASSERTE(!MethodTable::IsPerInstInfoRelative());
+
     PCODE helperAddress = (pLookup->helper == CORINFO_HELP_RUNTIMEHANDLE_METHOD ?
         GetEEFuncEntryPoint(JIT_GenericHandleMethodWithSlotAndModule) :
         GetEEFuncEntryPoint(JIT_GenericHandleClassWithSlotAndModule));
@@ -1672,6 +1674,8 @@ PCODE DynamicHelpers::CreateDictionaryLookupHelper(LoaderAllocator * pAllocator,
     pArgs->signature = pLookup->signature;
     pArgs->module = (CORINFO_MODULE_HANDLE)pModule;
 
+    WORD slotOffset = (WORD)(dictionaryIndexAndSlot & 0xFFFF) * sizeof(Dictionary*);
+
     // It's available only via the run-time helper function
     if (pLookup->indirections == CORINFO_USEHELPER)
     {
@@ -1690,28 +1694,38 @@ PCODE DynamicHelpers::CreateDictionaryLookupHelper(LoaderAllocator * pAllocator,
         for (WORD i = 0; i < pLookup->indirections; i++)
             indirectionsSize += (pLookup->offsets[i] >= 0x80 ? 6 : 3);
 
-        int codeSize = indirectionsSize + (pLookup->testForNull ? 21 : 3);
+        int codeSize = indirectionsSize + (pLookup->testForNull ? 15 : 1) + (pLookup->sizeOffset != CORINFO_NO_SIZE_CHECK ? 12 : 0);
 
         BEGIN_DYNAMIC_HELPER_EMIT(codeSize);
 
-        if (pLookup->testForNull)
-        {
-            // ecx contains the generic context parameter. Save a copy of it in the eax register
-            // mov eax,ecx
-            *(UINT16*)p = 0xc889; p += 2;
-        }
+        BYTE* pJLECall = NULL;
 
         for (WORD i = 0; i < pLookup->indirections; i++)
         {
-            // mov ecx,qword ptr [ecx+offset]
+            if (i == pLookup->indirections - 1 && pLookup->sizeOffset != CORINFO_NO_SIZE_CHECK)
+            {
+                _ASSERTE(pLookup->testForNull && i > 0);
+
+                // cmp dword ptr[eax + sizeOffset],slotOffset
+                *(UINT16*)p = 0xb881; p += 2;
+                *(UINT32*)p = (UINT32)pLookup->sizeOffset; p += 4;
+                *(UINT32*)p = (UINT32)slotOffset; p += 4;
+
+                // jle 'HELPER CALL'
+                *p++ = 0x7e;
+                pJLECall = p++;     // Offset filled later
+            }
+
+            // Move from ecx if it's the first indirection, otherwise from eax
+            // mov eax,dword ptr [ecx|eax + offset]
             if (pLookup->offsets[i] >= 0x80)
             {
-                *(UINT16*)p = 0x898b; p += 2;
+                *(UINT16*)p = (i == 0 ? 0x818b : 0x808b); p += 2;
                 *(UINT32*)p = (UINT32)pLookup->offsets[i]; p += 4;
             }
             else
             {
-                *(UINT16*)p = 0x498b; p += 2;
+                *(UINT16*)p = (i == 0 ? 0x418b : 0x408b); p += 2;
                 *p++ = (BYTE)pLookup->offsets[i];
             }
         }
@@ -1719,10 +1733,9 @@ PCODE DynamicHelpers::CreateDictionaryLookupHelper(LoaderAllocator * pAllocator,
         // No null test required
         if (!pLookup->testForNull)
         {
-            // No fixups needed for R2R
+            _ASSERTE(pLookup->sizeOffset == CORINFO_NO_SIZE_CHECK);
 
-            // mov eax,ecx
-            *(UINT16*)p = 0xc889; p += 2;
+            // No fixups needed for R2R
             *p++ = 0xC3;    // ret
         }
         else
@@ -1731,21 +1744,20 @@ PCODE DynamicHelpers::CreateDictionaryLookupHelper(LoaderAllocator * pAllocator,
 
             _ASSERTE(pLookup->indirections != 0);
 
-            // test ecx,ecx
-            *(UINT16*)p = 0xc985; p += 2;
+            // test eax,eax
+            *(UINT16*)p = 0xc085; p += 2;
 
-            // je 'HELPER_CALL' (a jump of 3 bytes)
-            *(UINT16*)p = 0x0374; p += 2;
+            // je 'HELPER_CALL' (a jump of 1 byte)
+            *(UINT16*)p = 0x0174; p += 2;
 
-            // mov eax,ecx
-            *(UINT16*)p = 0xc889; p += 2;
             *p++ = 0xC3;    // ret
 
             // 'HELPER_CALL'
             {
-                // Put the generic context back into rcx (was previously saved in eax)
-                // mov ecx,eax
-                *(UINT16*)p = 0xc189; p += 2;
+                if (pJLECall != NULL)
+                    *pJLECall = (BYTE)(p - pJLECall - 1);
+
+                // ecx already contains the generic context parameter
 
                 // mov edx,pArgs
                 // jmp helperAddress
index 57c314f..263a29d 100644 (file)
@@ -3307,12 +3307,11 @@ CORINFO_GENERIC_HANDLE JIT_GenericHandleWorker(MethodDesc * pMD, MethodTable * p
         GC_TRIGGERS;
     } CONTRACTL_END;
 
-    MethodTable * pDeclaringMT = NULL;
+     ULONG dictionaryIndex = 0;
+     MethodTable * pDeclaringMT = NULL;
 
     if (pMT != NULL)
     {
-        ULONG dictionaryIndex = 0;
-
         if (pModule != NULL)
         {
 #ifdef _DEBUG
@@ -3387,6 +3386,20 @@ CORINFO_GENERIC_HANDLE JIT_GenericHandleWorker(MethodDesc * pMD, MethodTable * p
         AddToGenericHandleCache(&key, (HashDatum)result);
     }
 
+    if (pMT != NULL && pDeclaringMT != pMT)
+    {
+        // If the dictionary on the base type got expanded, update the current type's base type dictionary
+        // pointer to use the new one on the base type.
+
+        Dictionary* pMTDictionary = pMT->GetPerInstInfo()[dictionaryIndex].GetValue();
+        Dictionary* pDeclaringMTDictionary = pDeclaringMT->GetPerInstInfo()[dictionaryIndex].GetValue();
+        if (pMTDictionary != pDeclaringMTDictionary)
+        {
+            TypeHandle** pPerInstInfo = (TypeHandle**)pMT->GetPerInstInfo()->GetValuePtr();
+            FastInterlockExchangePointer(pPerInstInfo + dictionaryIndex, (TypeHandle*)pDeclaringMTDictionary);
+        }
+    }
+
     return result;
 } // JIT_GenericHandleWorker
 
index d67f6fb..dded642 100644 (file)
@@ -3020,6 +3020,9 @@ void CEEInfo::ComputeRuntimeLookupForSharedGenericToken(DictionaryEntryKind entr
     pResult->indirectFirstOffset = 0;
     pResult->indirectSecondOffset = 0;
 
+    // Dictionary size checks skipped by default, unless we decide otherwise
+    pResult->sizeOffset = CORINFO_NO_SIZE_CHECK;
+
     // Unless we decide otherwise, just do the lookup via a helper function
     pResult->indirections = CORINFO_USEHELPER;
 
@@ -3444,16 +3447,24 @@ NoSpecialCase:
 
     DictionaryEntrySignatureSource signatureSource = (IsCompilationProcess() ? FromZapImage : FromJIT);
 
+    WORD slot;
+
     // It's a method dictionary lookup
     if (pResultLookup->lookupKind.runtimeLookupKind == CORINFO_LOOKUP_METHODPARAM)
     {
         _ASSERTE(pContextMD != NULL);
         _ASSERTE(pContextMD->HasMethodInstantiation());
 
-        if (DictionaryLayout::FindToken(pContextMD->GetLoaderAllocator(), pContextMD->GetNumGenericMethodArgs(), pContextMD->GetDictionaryLayout(), pResult, &sigBuilder, 1, signatureSource))
+        if (DictionaryLayout::FindToken(pContextMD, pContextMD->GetLoaderAllocator(), 1, &sigBuilder, NULL, signatureSource, pResult, &slot))
         {
             pResult->testForNull = 1;
             pResult->testForFixup = 0;
+            int minDictSize = pContextMD->GetNumGenericMethodArgs() + 1 + pContextMD->GetDictionaryLayout()->GetNumInitialSlots();
+            if (slot >= minDictSize)
+            {
+                // Dictionaries are guaranteed to have at least the number of slots allocated initially, so skip size check for smaller indexes
+                pResult->sizeOffset = (WORD)pContextMD->GetNumGenericMethodArgs() * sizeof(DictionaryEntry);
+            }
 
             // Indirect through dictionary table pointer in InstantiatedMethodDesc
             pResult->offsets[0] = offsetof(InstantiatedMethodDesc, m_pPerInstInfo);
@@ -3468,10 +3479,16 @@ NoSpecialCase:
     // It's a class dictionary lookup (CORINFO_LOOKUP_CLASSPARAM or CORINFO_LOOKUP_THISOBJ)
     else
     {
-        if (DictionaryLayout::FindToken(pContextMT->GetLoaderAllocator(), pContextMT->GetNumGenericArgs(), pContextMT->GetClass()->GetDictionaryLayout(), pResult, &sigBuilder, 2, signatureSource))
+        if (DictionaryLayout::FindToken(pContextMT, pContextMT->GetLoaderAllocator(), 2, &sigBuilder, NULL, signatureSource, pResult, &slot))
         {
             pResult->testForNull = 1;
             pResult->testForFixup = 0;
+            int minDictSize = pContextMT->GetNumGenericArgs() + 1 + pContextMT->GetClass()->GetDictionaryLayout()->GetNumInitialSlots();
+            if (slot >= minDictSize)
+            {
+                // Dictionaries are guaranteed to have at least the number of slots allocated initially, so skip size check for smaller indexes
+                pResult->sizeOffset = (WORD)pContextMT->GetNumGenericArgs() * sizeof(DictionaryEntry);
+            }
 
             // Indirect through dictionary table pointer in vtable
             pResult->offsets[0] = MethodTable::GetOffsetOfPerInstInfo();
index 8eb97e9..c1f05bf 100644 (file)
@@ -2645,7 +2645,7 @@ void MethodDesc::Save(DataImage *image)
 
     if (GetMethodDictionary())
     {
-        DWORD cBytes = DictionaryLayout::GetFirstDictionaryBucketSize(GetNumGenericMethodArgs(), GetDictionaryLayout());
+        DWORD cBytes = DictionaryLayout::GetDictionarySizeFromLayout(GetNumGenericMethodArgs(), GetDictionaryLayout());
         void* pBytes = GetMethodDictionary()->AsPtr();
 
         LOG((LF_ZAP, LL_INFO10000, "    MethodDesc::Save dictionary size %d\n", cBytes));
index 18cfc1e..53d581a 100644 (file)
@@ -3469,6 +3469,9 @@ public:
     {
         LIMITED_METHOD_DAC_CONTRACT;
 
+        // No lock needed here. In the case of a generic dictionary expansion, the values of the old dictionary
+        // slots are copied to the newly allocated dictionary, and the old dictionary is kept around. Whether we
+        // return the old or new dictionary here, the values of the instantiation arguments will always be the same.
         return Instantiation(IMD_GetMethodDictionary()->GetInstantiation(), m_wNumGenericArgs);
     }
 
@@ -3574,12 +3577,25 @@ public:
             InstantiatedMethodDesc* pIMD = IMD_GetWrappedMethodDesc()->AsInstantiatedMethodDesc();
             return pIMD->m_pDictLayout.GetValueMaybeNull();
         }
-        else
-        if (IMD_IsSharedByGenericMethodInstantiations())
+        else if (IMD_IsSharedByGenericMethodInstantiations())
             return m_pDictLayout.GetValueMaybeNull();
         else
             return NULL;
     }
+
+    void IMD_SetDictionaryLayout(DictionaryLayout* pNewLayout)
+    {
+        WRAPPER_NO_CONTRACT;
+        if (IMD_IsWrapperStubWithInstantiations() && IMD_HasMethodInstantiation())
+        {
+            InstantiatedMethodDesc* pIMD = IMD_GetWrappedMethodDesc()->AsInstantiatedMethodDesc();
+            pIMD->m_pDictLayout.SetValueMaybeNull(pNewLayout);
+        }
+        else if (IMD_IsSharedByGenericMethodInstantiations())
+        {
+            m_pDictLayout.SetValueMaybeNull(pNewLayout);
+        }
+    }
 #endif // !DACCESS_COMPILE
 
     // Setup the IMD as shared code
index e3fd0a8..656a099 100644 (file)
@@ -1261,7 +1261,7 @@ inline DWORD MethodTable::GetInstAndDictSize()
     if (!HasInstantiation())
         return 0;
     else
-        return DictionaryLayout::GetFirstDictionaryBucketSize(GetNumGenericArgs(), GetClass()->GetDictionaryLayout());
+        return DictionaryLayout::GetDictionarySizeFromLayout(GetNumGenericArgs(), GetClass()->GetDictionaryLayout());
 }
 
 //==========================================================================================
index cf6000a..ada4aa2 100644 (file)
@@ -10163,7 +10163,7 @@ MethodTableBuilder::SetupMethodTable2(
     EEClass *pClass = GetHalfBakedClass();
 
     DWORD cbDict = bmtGenerics->HasInstantiation()
-                   ?  DictionaryLayout::GetFirstDictionaryBucketSize(
+                   ?  DictionaryLayout::GetDictionarySizeFromLayout(
                           bmtGenerics->GetNumGenericArgs(), pClass->GetDictionaryLayout())
                    : 0;
 
@@ -10461,6 +10461,14 @@ MethodTableBuilder::SetupMethodTable2(
         {
             pInstDest[j] = inst[j];
         }
+
+        PTR_DictionaryLayout pLayout = pClass->GetDictionaryLayout();
+        if (pLayout != NULL && pLayout->GetMaxSlots() > 0)
+        {
+            PTR_Dictionary pDictionarySlots = pMT->GetPerInstInfo()[bmtGenerics->numDicts - 1].GetValue();
+            DWORD* pSizeSlot = (DWORD*)(pDictionarySlots + bmtGenerics->GetNumGenericArgs());
+            *pSizeSlot = cbDict;
+        }
     }
 
     CorElementType normalizedType = ELEMENT_TYPE_CLASS;
index 13b5741..5a4c86c 100644 (file)
@@ -2877,7 +2877,8 @@ void ProcessDynamicDictionaryLookup(TransitionBlock *           pTransitionBlock
 
     pResult->indirectFirstOffset = 0;
     pResult->indirectSecondOffset = 0;
-
+    // Dictionary size checks skipped by default, unless we decide otherwise
+    pResult->sizeOffset = CORINFO_NO_SIZE_CHECK;
     pResult->indirections = CORINFO_USEHELPER;
 
     DWORD numGenericArgs = 0;
@@ -2971,9 +2972,15 @@ void ProcessDynamicDictionaryLookup(TransitionBlock *           pTransitionBlock
 
     if (kind == ENCODE_DICTIONARY_LOOKUP_METHOD)
     {
-        if (DictionaryLayout::FindToken(pModule->GetLoaderAllocator(), numGenericArgs, pContextMD->GetDictionaryLayout(), pResult, (BYTE*)pBlobStart, 1, FromReadyToRunImage, &dictionarySlot))
+        if (DictionaryLayout::FindToken(pContextMD, pModule->GetLoaderAllocator(), 1, NULL, (BYTE*)pBlobStart, FromReadyToRunImage, pResult, &dictionarySlot))
         {
             pResult->testForNull = 1;
+            int minDictSize = pContextMD->GetNumGenericMethodArgs() + 1 + pContextMD->GetDictionaryLayout()->GetNumInitialSlots();
+            if (dictionarySlot >= minDictSize)
+            {
+                // Dictionaries are guaranteed to have at least the number of slots allocated initially, so skip size check for smaller indexes
+                pResult->sizeOffset = (WORD)pContextMD->GetNumGenericMethodArgs() * sizeof(DictionaryEntry);
+            }
 
             // Indirect through dictionary table pointer in InstantiatedMethodDesc
             pResult->offsets[0] = offsetof(InstantiatedMethodDesc, m_pPerInstInfo);
@@ -2990,9 +2997,15 @@ void ProcessDynamicDictionaryLookup(TransitionBlock *           pTransitionBlock
     // It's a class dictionary lookup (CORINFO_LOOKUP_CLASSPARAM or CORINFO_LOOKUP_THISOBJ)
     else
     {
-        if (DictionaryLayout::FindToken(pModule->GetLoaderAllocator(), numGenericArgs, pContextMT->GetClass()->GetDictionaryLayout(), pResult, (BYTE*)pBlobStart, 2, FromReadyToRunImage, &dictionarySlot))
+        if (DictionaryLayout::FindToken(pContextMT, pModule->GetLoaderAllocator(), 2, NULL, (BYTE*)pBlobStart, FromReadyToRunImage, pResult, &dictionarySlot))
         {
             pResult->testForNull = 1;
+            int minDictSize = pContextMT->GetNumGenericArgs() + 1 + pContextMT->GetClass()->GetDictionaryLayout()->GetNumInitialSlots();
+            if (dictionarySlot >= minDictSize)
+            {
+                // Dictionaries are guaranteed to have at least the number of slots allocated initially, so skip size check for smaller indexes
+                pResult->sizeOffset = (WORD)pContextMT->GetNumGenericArgs() * sizeof(DictionaryEntry);
+            }
 
             // Indirect through dictionary table pointer in vtable
             pResult->offsets[0] = MethodTable::GetOffsetOfPerInstInfo();
diff --git a/src/coreclr/tests/src/Loader/classloader/DictionaryExpansion/DictionaryExpansion.cs b/src/coreclr/tests/src/Loader/classloader/DictionaryExpansion/DictionaryExpansion.cs
new file mode 100644 (file)
index 0000000..551a146
--- /dev/null
@@ -0,0 +1,335 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.IO;
+using System.Reflection;
+using System.Diagnostics;
+using System.Collections.Generic;
+using System.Reflection.Emit;
+using System.Runtime.CompilerServices;
+using TestLibrary;
+
+class TestType1<T> { }
+class TestType2<T> { }
+class TestType3<T> { }
+class TestType4<T> { }
+class TestType5<T> { }
+class TestType6<T> { }
+class TestType7<T> { }
+class TestType8<T> { }
+class TestType9<T> { }
+class TestType10<T> { }
+class TestType11<T> { }
+class TestType12<T> { }
+class TestType13<T> { }
+class TestType14<T> { }
+class TestType15<T> { }
+class TestType16<T> { }
+class TestType17<T> { }
+class TestType18<T> { }
+class TestType19<T> { }
+class TestType20<T> { }
+class TestType21<T> { }
+class TestType22<T> { }
+class TestType23<T> { }
+class TestType24<T> { }
+class TestType25<T> { }
+
+public class GenBase
+{
+    public virtual void VFunc() { }
+}
+
+public class GenClass<T> : GenBase
+{
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    public Type FuncOnGenClass(int level)
+    {
+        switch (level)
+        {
+            case 0: return typeof(T);
+            case 1: return typeof(TestType1<T>);
+            case 2: return typeof(TestType2<T>);
+            case 3: return typeof(TestType3<T>);
+            case 4: return typeof(TestType4<T>);
+            case 5: return typeof(TestType5<T>);
+            case 6: return typeof(TestType6<T>);
+            case 7: return typeof(TestType7<T>);
+            case 8: return typeof(TestType8<T>);
+            case 9: return typeof(TestType9<T>);
+            case 10: return typeof(TestType10<T>);
+            case 11: return typeof(TestType11<T>);
+            case 12: return typeof(TestType12<T>);
+            case 13: return typeof(TestType13<T>);
+            case 14: return typeof(TestType14<T>);
+            case 15: return typeof(TestType15<T>);
+            case 16: return typeof(TestType16<T>);
+            case 17: return typeof(TestType17<T>);
+            case 18: return typeof(TestType18<T>);
+            case 19: return typeof(TestType19<T>);
+            case 20: return typeof(TestType20<T>);
+            case 21: return typeof(TestType21<T>);
+            case 22: return typeof(TestType22<T>);
+            case 23: return typeof(TestType23<T>);
+            case 24: return typeof(TestType24<T>);
+            case 25: default: return typeof(TestType25<T>);
+        }
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    public Type FuncOnGenClass2(int level)
+    {
+        switch (level)
+        {
+            case 0: return typeof(T);
+            case 1: return typeof(TestType1<T>);
+            case 2: return typeof(TestType2<T>);
+            case 3: return typeof(TestType3<T>);
+            case 4: return typeof(TestType4<T>);
+            case 5: return typeof(TestType5<T>);
+            case 6: return typeof(TestType6<T>);
+            case 7: return typeof(TestType7<T>);
+            case 8: return typeof(TestType8<T>);
+            case 9: return typeof(TestType9<T>);
+            case 10: return typeof(TestType10<T>);
+            case 11: return typeof(TestType11<T>);
+            case 12: return typeof(TestType12<T>);
+            case 13: return typeof(TestType13<T>);
+            case 14: return typeof(TestType14<T>);
+            case 15: return typeof(TestType15<T>);
+            case 16: return typeof(TestType16<T>);
+            case 17: return typeof(TestType17<T>);
+            case 18: return typeof(TestType18<T>);
+            case 19: return typeof(TestType19<T>);
+            case 20: return typeof(TestType20<T>);
+            case 21: return typeof(TestType21<T>);
+            case 22: return typeof(TestType22<T>);
+            case 23: return typeof(TestType23<T>);
+            case 24: return typeof(TestType24<T>);
+            case 25: default: return typeof(TestType25<T>);
+        }
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    static void DoTest_Inner<T1,T2,T3>(int max, GenClass<T1> o1, GenClass<T2> o2, GenClass<T3> o3)
+    {
+        Console.WriteLine("TEST: FuncOnGenClass<{0}>", typeof(T1).Name);
+        for (int i = 0; i < max; i++)
+            Assert.AreEqual(o1.FuncOnGenClass(i).ToString(), i == 0 ? $"{typeof(T1)}" : $"TestType{i}`1[{typeof(T1)}]");
+
+        Console.WriteLine("TEST: FuncOnGenClass<{0}>", typeof(T2).Name);
+        for (int i = 0; i < max; i++)
+            Assert.AreEqual(o2.FuncOnGenClass(i).ToString(), i == 0 ? $"{typeof(T2)}" : $"TestType{i}`1[{typeof(T2)}]");
+
+        Console.WriteLine("TEST: FuncOnGenClass2<{0}>", typeof(T2).Name);
+        for (int i = 0; i < max; i++)
+            Assert.AreEqual(o2.FuncOnGenClass2(i).ToString(), i == 0 ? $"{typeof(T2)}" : $"TestType{i}`1[{typeof(T2)}]");
+
+        Console.WriteLine("TEST: FuncOnGenClass<{0}>", typeof(T3).Name);
+        for (int i = 0; i < max; i++)
+            Assert.AreEqual(o3.FuncOnGenClass(i).ToString(), i == 0 ? $"{typeof(T3)}" : $"TestType{i}`1[{typeof(T3)}]");
+    }
+
+    public static void DoTest_GenClass(int max)
+    {
+        DoTest_Inner<string, object, Test>(max,
+            new GenClass<string>(),
+            new GenClass<object>(),
+            new GenClass<Test>());
+    }
+
+    public static void DoTest_GenDerived(int max)
+    {
+        DoTest_Inner<string, object, Test>(max,
+            new GenDerived<string, int>(),
+            new GenDerived<object, int>(),
+            new GenDerived<Test, int>());
+    }
+
+    public static void DoTest_GenDerived2(int max)
+    {
+        DoTest_Inner<object, object, object>(max,
+            new GenDerived2(),
+            new GenDerived2(),
+            new GenDerived2());
+    }
+
+    public static void DoTest_GenDerived3(int max)
+    {
+        DoTest_Inner<object, object, object>(max,
+            new GenDerived3(),
+            new GenDerived3(),
+            new GenDerived3());
+    }
+
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    public override void VFunc()
+    {
+        Assert.AreEqual(typeof(KeyValuePair<T, string>).ToString(), "System.Collections.Generic.KeyValuePair`2[System.Object,System.String]");
+        Assert.AreEqual(typeof(KeyValuePair<T, string>).ToString(), "System.Collections.Generic.KeyValuePair`2[System.Object,System.String]");
+    }
+}
+
+public class GenDerived<T, U> : GenClass<T>
+{
+}
+
+public class GenDerived2 : GenDerived<object, string>
+{
+}
+
+public class GenDerived3 : GenDerived2
+{
+}
+
+public class GenDerived4 : GenDerived3
+{
+}
+
+public class Test
+{
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    public static Type GFunc<T>(int level)
+    {
+        switch(level)
+        {
+            case 0: return typeof(T);
+            case 1: return typeof(TestType1<T>);
+            case 2: return typeof(TestType2<T>);
+            case 3: return typeof(TestType3<T>);
+            case 4: return typeof(TestType4<T>);
+            case 5: return typeof(TestType5<T>);
+            case 6: return typeof(TestType6<T>);
+            case 7: return typeof(TestType7<T>);
+            case 8: return typeof(TestType8<T>);
+            case 9: return typeof(TestType9<T>);
+            case 10: return typeof(TestType10<T>);
+            case 11: return typeof(TestType11<T>);
+            case 12: return typeof(TestType12<T>);
+            case 13: return typeof(TestType13<T>);
+            case 14: return typeof(TestType14<T>);
+            case 15: return typeof(TestType15<T>);
+            case 16: return typeof(TestType16<T>);
+            case 17: return typeof(TestType17<T>);
+            case 18: return typeof(TestType18<T>);
+            case 19: return typeof(TestType19<T>);
+            case 20: return typeof(TestType20<T>);
+            case 21: return typeof(TestType21<T>);
+            case 22: return typeof(TestType22<T>);
+            case 23: return typeof(TestType23<T>);
+            case 24: return typeof(TestType24<T>);
+            case 25: default: return typeof(TestType25<T>);
+        }
+    }
+    
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    public static Type GFunc2<T>(int level)
+    {
+        switch(level)
+        {
+            case 0: return typeof(T);
+            case 1: return typeof(TestType1<T>);
+            case 2: return typeof(TestType2<T>);
+            case 3: return typeof(TestType3<T>);
+            case 4: return typeof(TestType4<T>);
+            case 5: return typeof(TestType5<T>);
+            case 6: return typeof(TestType6<T>);
+            case 7: return typeof(TestType7<T>);
+            case 8: return typeof(TestType8<T>);
+            case 9: return typeof(TestType9<T>);
+            case 10: return typeof(TestType10<T>);
+            case 11: return typeof(TestType11<T>);
+            case 12: return typeof(TestType12<T>);
+            case 13: return typeof(TestType13<T>);
+            case 14: return typeof(TestType14<T>);
+            case 15: return typeof(TestType15<T>);
+            case 16: return typeof(TestType16<T>);
+            case 17: return typeof(TestType17<T>);
+            case 18: return typeof(TestType18<T>);
+            case 19: return typeof(TestType19<T>);
+            case 20: return typeof(TestType20<T>);
+            case 21: return typeof(TestType21<T>);
+            case 22: return typeof(TestType22<T>);
+            case 23: return typeof(TestType23<T>);
+            case 24: return typeof(TestType24<T>);
+            case 25: default: return typeof(TestType25<T>);
+        }
+    }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    public static void DoTest(int max)
+    {
+        Console.WriteLine("TEST: GFunc<string>");
+        for(int i = 0; i < max; i++)
+            Assert.AreEqual(GFunc<string>(i).ToString(), i == 0 ? "System.String" : $"TestType{i}`1[System.String]");
+
+        Console.WriteLine("TEST: GFunc<object>(i)");
+        for (int i = 0; i < max; i++)
+            Assert.AreEqual(GFunc<object>(i).ToString(), i == 0 ? "System.Object" : $"TestType{i}`1[System.Object]");
+
+        Console.WriteLine("TEST: GFunc2<object>(i)");
+        for (int i = 0; i < max; i++)
+            Assert.AreEqual(GFunc2<object>(i).ToString(), i == 0 ? "System.Object" : $"TestType{i}`1[System.Object]");
+
+        Console.WriteLine("TEST: GFunc<Test>(i)");
+        for (int i = 0; i < max; i++)
+            Assert.AreEqual(GFunc<Test>(i).ToString(), i == 0 ? "Test" : $"TestType{i}`1[Test]");
+    }
+    
+    public static int Main()
+    {
+        GenBase deriv4 = new GenDerived4();
+        
+        for(int i = 5; i <= 25; i += 5)
+        {
+            // Test for generic classes
+            switch(i % 4)
+            {
+                case 0:
+                    GenClass<int>.DoTest_GenClass(i);
+                    break;
+                case 1:
+                    GenClass<int>.DoTest_GenDerived(i);
+                    break;
+                case 2:
+                    GenClass<int>.DoTest_GenDerived2(i);
+                    break;
+                case 3:
+                    GenClass<int>.DoTest_GenDerived3(i);
+                    break;
+
+            }
+
+            // Test for generic methods
+            DoTest(i);
+            
+            {
+                AssemblyBuilder ab = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("CollectibleAsm"+i), AssemblyBuilderAccess.RunAndCollect);
+                var tb = ab.DefineDynamicModule("CollectibleMod" + i).DefineType("CollectibleGenDerived"+i, TypeAttributes.Public, typeof(GenDerived2));
+                var t = tb.CreateType();
+                GenBase col_b = (GenBase)Activator.CreateInstance(t);
+                col_b.VFunc();
+
+                ab = null;
+                tb = null;
+                t = null;
+                col_b = null;
+                for(int k = 0; k < 5; k++)
+                {
+                    GC.Collect();
+                    GC.WaitForPendingFinalizers();
+                }
+            }
+        }
+        
+        // After all expansions to existing dictionaries, use GenDerived4. GenDerived4 was allocated before any of its
+        // base type dictionaries were expanded.
+        for(int i = 0; i < 5; i++)
+            deriv4.VFunc();
+
+        return 100;
+    }
+}
diff --git a/src/coreclr/tests/src/Loader/classloader/DictionaryExpansion/DictionaryExpansion.csproj b/src/coreclr/tests/src/Loader/classloader/DictionaryExpansion/DictionaryExpansion.csproj
new file mode 100644 (file)
index 0000000..30b0e72
--- /dev/null
@@ -0,0 +1,10 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <CLRTestKind>BuildAndRun</CLRTestKind>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="$(MSBuildProjectName).cs" />
+    <ProjectReference Include="$(TestSourceDir)Common/CoreCLRTestLibrary/CoreCLRTestLibrary.csproj" />
+  </ItemGroup>
+</Project>