`UnsafeAccessorAttribute` non-generic support (#86932)
authorAaron Robinson <arobins@microsoft.com>
Thu, 15 Jun 2023 13:59:49 +0000 (06:59 -0700)
committerGitHub <noreply@github.com>
Thu, 15 Jun 2023 13:59:49 +0000 (06:59 -0700)
* CoreCLR and NativeAOT

* Add UnsafeAccessorAttribute API

* Implement IL generation for all accessor paths

* Implement static/instance field lookup - non-generic

* Implement static/instance method lookup - non-generic

* Defined ambiguity logic with respect to
custom modifiers.
- First pass ignore custom modifiers
- If ambiguity detected, rerun algorithm but
  require precise matching of custom modifiers.
- If there is no clear match throw AmbiguousImplementationException.

* Cleanup memory management confusion
with ILStubResolver.

* Fix non-standard C++

* Remove CORINFO_MODULE_ALLACCESS scope

* Remove enum METHOD_TYPE.

* Update BOTR on TypeDesc

49 files changed:
docs/design/coreclr/botr/type-loader.md
src/coreclr/debug/ee/controller.cpp
src/coreclr/debug/ee/debugger.cpp
src/coreclr/debug/ee/functioninfo.cpp
src/coreclr/dlls/mscorrc/mscorrc.rc
src/coreclr/dlls/mscorrc/resource.h
src/coreclr/ilasm/assem.cpp
src/coreclr/inc/corhlpr.h
src/coreclr/inc/formattype.cpp
src/coreclr/inc/sarray.h
src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/ThrowHelpers.cs
src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/TypeLoaderExceptionHelper.cs
src/coreclr/tools/Common/TypeSystem/Common/ExceptionStringID.cs
src/coreclr/tools/Common/TypeSystem/Common/MethodDesc.cs
src/coreclr/tools/Common/TypeSystem/Common/Properties/Resources.resx
src/coreclr/tools/Common/TypeSystem/IL/NativeAotILProvider.cs
src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs [new file with mode: 0644]
src/coreclr/tools/aot/ILCompiler.TypeSystem/ILCompiler.TypeSystem.csproj
src/coreclr/vm/amd64/asmconstants.h
src/coreclr/vm/dynamicmethod.cpp
src/coreclr/vm/dynamicmethod.h
src/coreclr/vm/frames.cpp
src/coreclr/vm/frames.h
src/coreclr/vm/gcenv.ee.common.cpp
src/coreclr/vm/ilstubresolver.cpp
src/coreclr/vm/ilstubresolver.h
src/coreclr/vm/jitinterface.cpp
src/coreclr/vm/jitinterface.h
src/coreclr/vm/memberload.cpp
src/coreclr/vm/memberload.h
src/coreclr/vm/method.hpp
src/coreclr/vm/methodtablebuilder.cpp
src/coreclr/vm/methodtablebuilder.h
src/coreclr/vm/methodtablebuilder.inl
src/coreclr/vm/prestub.cpp
src/coreclr/vm/siginfo.cpp
src/coreclr/vm/siginfo.hpp
src/coreclr/vm/stubgen.cpp
src/coreclr/vm/stubgen.h
src/coreclr/vm/stubmgr.cpp
src/coreclr/vm/typehandle.h
src/coreclr/vm/wellknownattributes.h
src/libraries/System.Private.CoreLib/src/Resources/Strings.resx
src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnsafeAccessorAttribute.cs [new file with mode: 0644]
src/libraries/System.Runtime/ref/System.Runtime.cs
src/tests/Common/CoreCLRTestLibrary/AssertExtensions.cs
src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.cs [new file with mode: 0644]
src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.csproj [new file with mode: 0644]

index e8ccf11..630a2c3 100644 (file)
@@ -100,7 +100,7 @@ If `MyClass` fails to load, for example because it's supposed to be defined in a
 
 ## Key Data Structures
 
-The most universal type designation in the CLR is the `TypeHandle`. It's an abstract entity which encapsulates a pointer to either a `MethodTable` (representing "ordinary" types like `System.Object` or `List<string>`) or a `TypeDesc` (representing byrefs, pointers, function pointers, arrays, and generic variables). It constitutes the identity of a type in that two handles are equal if and only if they represent the same type. To save space, the fact that a `TypeHandle` contains a `TypeDesc` is indicated by setting the second lowest bit of the pointer to 1 (i.e. (ptr | 2)) instead of using additional flags<sup>2</sup>. `TypeDesc` is "abstract" and has the following inheritance hierarchy.
+The most universal type designation in the CLR is the `TypeHandle`. It's an abstract entity which encapsulates a pointer to either a `MethodTable` (representing "ordinary" types like `System.Object` or `List<string>`) or a `TypeDesc` (representing byrefs, pointers, function pointers and generic variables). It constitutes the identity of a type in that two handles are equal if and only if they represent the same type. To save space, the fact that a `TypeHandle` contains a `TypeDesc` is indicated by setting the second lowest bit of the pointer to 1 (i.e. (ptr | 2)) instead of using additional flags<sup>2</sup>. `TypeDesc` is "abstract" and has the following inheritance hierarchy.
 
 ![Figure 2](images/typeloader-fig2.png)
 
@@ -122,10 +122,6 @@ Represents a function pointer, essentially a variable-length list of type handle
 
 This descriptor represents a byref and pointer types. Byrefs are the results of the `ref` and `out` C# keywords applied to method parameters<sup>3</sup> whereas pointer types are unmanaged pointers to data used in unsafe C# and managed C++.
 
-**`ArrayTypeDesc`**
-
-Represents array types. It is derived from `ParamTypeDesc` because arrays are also parameterized by a single parameter (the type of their element). This is opposed to generic instantiations whose number of parameters is variable.
-
 **`MethodTable`**
 
 This is by far the central data structure of the runtime. It represents any type which does not fall into one of the categories above (this includes primitive types, and generic types, both "open" and "closed"). It contains everything about the type that needs to be looked up quickly, such as its parent type, implemented interfaces, and the v-table.
index b69f66b..b624162 100644 (file)
@@ -2373,7 +2373,7 @@ bool DebuggerController::PatchTrace(TraceDestination *trace,
         // trace->address is actually a MethodDesc* of the method that we'll
         // soon JIT, so put a relative bp at offset zero in.
         LOG((LF_CORDB, LL_INFO10000,
-            "Setting unjitted method patch in MethodDesc 0x%p %s\n", trace->GetMethodDesc(), trace->GetMethodDesc() ? trace->GetMethodDesc()->m_pszDebugMethodName : ""));
+            "Setting unjitted method patch in MethodDesc %p %s\n", trace->GetMethodDesc(), trace->GetMethodDesc() ? trace->GetMethodDesc()->m_pszDebugMethodName : ""));
 
         // Note: we have to make sure to bind here. If this function is prejitted, this may be our only chance to get a
         // DebuggerJITInfo and thereby cause a JITComplete callback.
@@ -2484,7 +2484,7 @@ bool DebuggerController::MatchPatch(Thread *thread,
         }
     }
 
-    LOG((LF_CORDB, LL_INFO100000, "DC::MP: Returning true"));
+    LOG((LF_CORDB, LL_INFO100000, "DC::MP: Returning true\n"));
 
     return true;
 }
@@ -3317,11 +3317,11 @@ BOOL DebuggerController::DispatchExceptionHook(Thread *thread,
     }
     CONTRACTL_END;
 
-    LOG((LF_CORDB, LL_INFO1000, "DC:: DispatchExceptionHook\n"));
+    LOG((LF_CORDB, LL_INFO1000, "DC::DEH: DispatchExceptionHook\n"));
 
     if (!g_patchTableValid)
     {
-        LOG((LF_CORDB, LL_INFO1000, "DC::DEH returning, no patch table.\n"));
+        LOG((LF_CORDB, LL_INFO1000, "DC::DEH: returning, no patch table.\n"));
         return (TRUE);
     }
 
@@ -3339,16 +3339,16 @@ BOOL DebuggerController::DispatchExceptionHook(Thread *thread,
         DebuggerController *pNext = p->m_next;
 
         if (p->m_exceptionHook
-            && (p->m_thread == NULL || p->m_thread == thread) &&
-            tpr != TPR_IGNORE_AND_STOP)
+            && (p->m_thread == NULL || p->m_thread == thread)
+            && tpr != TPR_IGNORE_AND_STOP)
         {
-                        LOG((LF_CORDB, LL_INFO1000, "DC::DEH calling TEH...\n"));
+            LOG((LF_CORDB, LL_INFO1000, "DC::DEH: calling TEH...\n"));
             tpr = p->TriggerExceptionHook(thread, context , pException);
-                        LOG((LF_CORDB, LL_INFO1000, "DC::DEH ... returned.\n"));
+            LOG((LF_CORDB, LL_INFO1000, "DC::DEH: ... returned.\n"));
 
             if (tpr == TPR_IGNORE_AND_STOP)
             {
-                LOG((LF_CORDB, LL_INFO1000, "DC:: DEH: leaving early!\n"));
+                LOG((LF_CORDB, LL_INFO1000, "DC::DEH: leaving early!\n"));
                 break;
             }
         }
@@ -3356,7 +3356,7 @@ BOOL DebuggerController::DispatchExceptionHook(Thread *thread,
         p = pNext;
     }
 
-    LOG((LF_CORDB, LL_INFO1000, "DC:: DEH: returning 0x%x!\n", tpr));
+    LOG((LF_CORDB, LL_INFO1000, "DC::DEH: returning 0x%x!\n", tpr));
 
     return (tpr != TPR_IGNORE_AND_STOP);
 }
@@ -4725,11 +4725,11 @@ TP_RESULT DebuggerPatchSkip::TriggerExceptionHook(Thread *thread, CONTEXT * cont
         SIZE_T *sp = (SIZE_T *) GetSP(context);
 
         LOG((LF_CORDB, LL_INFO10000,
-             "Bypass call return address redirected from 0x%p\n", *sp));
+             "Bypass call return address redirected from %p\n", *sp));
 
         *sp -= patchBypass - (BYTE*)m_address;
 
-        LOG((LF_CORDB, LL_INFO10000, "to 0x%p\n", *sp));
+        LOG((LF_CORDB, LL_INFO10000, "to %p\n", *sp));
 #else
         PORTABILITY_ASSERT("DebuggerPatchSkip::TriggerExceptionHook -- return address fixup NYI");
 #endif
@@ -4739,7 +4739,7 @@ TP_RESULT DebuggerPatchSkip::TriggerExceptionHook(Thread *thread, CONTEXT * cont
     {
         // Fixup IP
 
-        LOG((LF_CORDB, LL_INFO10000, "Bypass instruction redirected from 0x%p\n", GetIP(context)));
+        LOG((LF_CORDB, LL_INFO10000, "Bypass instruction redirected from %p\n", GetIP(context)));
 
         if (IsSingleStep(exception->ExceptionCode))
         {
@@ -4786,9 +4786,9 @@ TP_RESULT DebuggerPatchSkip::TriggerExceptionHook(Thread *thread, CONTEXT * cont
                 ((size_t)GetIP(context) >  (size_t)patchBypass &&
                  (size_t)GetIP(context) <= (size_t)(patchBypass + MAX_INSTRUCTION_LENGTH + 1)))
             {
-                LOG((LF_CORDB, LL_INFO10000, "Bypass instruction redirected because still in skip area.\n"));
-                LOG((LF_CORDB, LL_INFO10000, "m_fIsCall = %d, patchBypass = 0x%x, m_address = 0x%x\n",
-                    m_instrAttrib.m_fIsCall, patchBypass, m_address));
+                LOG((LF_CORDB, LL_INFO10000, "Bypass instruction redirected because still in skip area.\n"
+                    "\tm_fIsCall = %s, patchBypass = %p, m_address = %p\n",
+                    (m_instrAttrib.m_fIsCall ? "true" : "false"), patchBypass, m_address));
                 SetIP(context, (PCODE)((BYTE *)GetIP(context) - (patchBypass - (BYTE *)m_address)));
             }
             else
@@ -4822,8 +4822,7 @@ TP_RESULT DebuggerPatchSkip::TriggerExceptionHook(Thread *thread, CONTEXT * cont
             SetIP(context, (PCODE)((BYTE *)GetIP(context) - (patchBypass - (BYTE *)m_address)));
         }
 
-        LOG((LF_CORDB, LL_ALWAYS, "to 0x%x\n", GetIP(context)));
-
+        LOG((LF_CORDB, LL_ALWAYS, "DPS::TEH: IP now at %p\n", GetIP(context)));
     }
 
 #endif
@@ -5376,39 +5375,36 @@ bool DebuggerStepper::DetectHandleInterceptors(ControllerStackInfo *info)
 
 BOOL DebuggerStepper::DetectHandleLCGMethods(const PCODE ip, MethodDesc * pMD, ControllerStackInfo * pInfo)
 {
+    // If a MethodDesc is specified, it has to match the given IP.
+    _ASSERTE(pMD == NULL || pMD == g_pEEInterface->GetNativeCodeMethodDesc(ip));
+
     // Look up the MethodDesc for the given IP.
     if (pMD == NULL)
     {
-        if (g_pEEInterface->IsManagedNativeCode((const BYTE *)ip))
-        {
-            pMD = g_pEEInterface->GetNativeCodeMethodDesc(ip);
-            _ASSERTE(pMD != NULL);
-        }
-    }
-#if defined(_DEBUG)
-    else
-    {
-        // If a MethodDesc is specified, it has to match the given IP.
-        _ASSERTE(pMD == g_pEEInterface->GetNativeCodeMethodDesc(ip));
+        // If the given IP is in unmanaged code, then it isn't an LCG method
+        if (!g_pEEInterface->IsManagedNativeCode((const BYTE *)ip))
+            return FALSE;
+
+        pMD = g_pEEInterface->GetNativeCodeMethodDesc(ip);
     }
-#endif // _DEBUG
 
-    // If the given IP is in unmanaged code, then we won't have a MethodDesc by this point.
-    if (pMD != NULL)
-    {
-        if (pMD->IsLCGMethod())
-        {
-            // Enable all the traps to catch the thread.
-            EnableUnwind(m_fp);
-            EnableJMCBackStop(pMD);
+    _ASSERTE(pMD != NULL);
+    LOG((LF_CORDB, LL_INFO10000, "DS::DHLCGM: ip:%zx pMD:%p (%s::%s)\n",
+        ip,
+        pMD,
+        pMD->m_pszDebugClassName,
+        pMD->m_pszDebugMethodName));
 
-            pInfo->SetReturnFrameWithActiveFrame();
-            TrapStepOut(pInfo);
-            return TRUE;
-        }
-    }
+    if (!pMD->IsLCGMethod())
+        return FALSE;
 
-    return FALSE;
+    // Enable all the traps to catch the thread.
+    EnableUnwind(m_fp);
+    EnableJMCBackStop(pMD);
+
+    pInfo->SetReturnFrameWithActiveFrame();
+    TrapStepOut(pInfo);
+    return TRUE;
 }
 
 
@@ -5523,19 +5519,19 @@ bool DebuggerStepper::TrapStepInto(ControllerStackInfo *info,
     if (IsCloserToRoot(info->m_activeFrame.fp, m_fpStepInto))
         m_fpStepInto = info->m_activeFrame.fp;
 
-    LOG((LF_CORDB, LL_INFO1000, "DS::TSI this:0x%p m_fpStepInto:0x%p\n",
-        this, m_fpStepInto.GetSPValue()));
+    LOG((LF_CORDB, LL_INFO1000, "DS::TSI this:%p m_fpStepInto:%p ip:%p\n",
+        this, m_fpStepInto.GetSPValue(), ip));
 
     TraceDestination trace;
 
     // Trace through the stubs.
     // If we're calling from managed code, this should either succeed
-    // or become an ecall into mscorwks.
-    // @Todo - what about stubs in mscorwks.
+    // or become an ecall into coreclr.
     // @todo - if this fails, we want to provide as much info as possible.
     if (!g_pEEInterface->TraceStub(ip, &trace)
         || !g_pEEInterface->FollowTrace(&trace))
     {
+        LOG((LF_CORDB, LL_INFO1000, "DS::TSI Failed to step into\n"));
         return false;
     }
 
@@ -5964,8 +5960,8 @@ bool DebuggerStepper::TrapStep(ControllerStackInfo *info, bool in)
 
             case WALK_UNKNOWN:
     LWALK_UNKNOWN:
-                LOG((LF_CORDB,LL_INFO10000,"DS::TS:WALK_UNKNOWN - curIP:0x%p "
-                    "nextIP:0x%p skipIP:0x%p 1st byte of opcode:0x%x\n", (BYTE*)GetControlPC(&(info->m_activeFrame.
+                LOG((LF_CORDB,LL_INFO10000,"DS::TS:WALK_UNKNOWN - curIP:%p "
+                    "nextIP:%p skipIP:%p 1st byte of opcode:0x%x\n", (BYTE*)GetControlPC(&(info->m_activeFrame.
                     registers)), walker.GetNextIP(),walker.GetSkipIP(),
                     *(BYTE*)GetControlPC(&(info->m_activeFrame.registers))));
 
@@ -6713,7 +6709,7 @@ bool DebuggerStepper::SetRangesFromIL(DebuggerJitInfo *dji, COR_DEBUG_STEP_RANGE
             {
                 // {0...-1} means use the entire method as the range
                 // Code dup'd from below case.
-                LOG((LF_CORDB, LL_INFO10000, "DS:Step: Have DJI, special (0,-1) entry\n"));
+                LOG((LF_CORDB, LL_INFO10000, "DS::Step: Have DJI, special (0,-1) entry\n"));
                 rTo->startOffset = 0;
                 rTo->endOffset   = (ULONG32)g_pEEInterface->GetFunctionSize(fd);
             }
@@ -6777,7 +6773,7 @@ bool DebuggerStepper::SetRangesFromIL(DebuggerJitInfo *dji, COR_DEBUG_STEP_RANGE
                         }
                     }
 
-                    LOG((LF_CORDB, LL_INFO10000, "DS:Step: nat off:0x%x to 0x%x\n", rTo->startOffset, rTo->endOffset));
+                    LOG((LF_CORDB, LL_INFO10000, "DS::Step: nat off:0x%x to 0x%x\n", rTo->startOffset, rTo->endOffset));
                 }
             }
         }
@@ -6799,7 +6795,7 @@ bool DebuggerStepper::SetRangesFromIL(DebuggerJitInfo *dji, COR_DEBUG_STEP_RANGE
         {
             if (r->startOffset == 0 && r->endOffset == (ULONG) ~0)
             {
-                LOG((LF_CORDB, LL_INFO10000, "DS:Step:No DJI, (0,-1) special entry\n"));
+                LOG((LF_CORDB, LL_INFO10000, "DS::Step:No DJI, (0,-1) special entry\n"));
                 // Code dup'd from above case.
                 // {0...-1} means use the entire method as the range
                 rTo->startOffset = 0;
@@ -6807,7 +6803,7 @@ bool DebuggerStepper::SetRangesFromIL(DebuggerJitInfo *dji, COR_DEBUG_STEP_RANGE
             }
             else
             {
-                LOG((LF_CORDB, LL_INFO10000, "DS:Step:No DJI, regular entry\n"));
+                LOG((LF_CORDB, LL_INFO10000, "DS::Step:No DJI, regular entry\n"));
                 // We can't just leave ths IL entry - we have to
                 // get rid of it.
                 // This will just be ignored
@@ -6949,12 +6945,12 @@ bool DebuggerStepper::Step(FramePointer fp, bool in,
     }
     m_eMode = m_stepIn ? cStepIn : cStepOver;
 
-    LOG((LF_CORDB,LL_INFO10000,"DS 0x%p Step: STEP_NORMAL\n",this));
+    LOG((LF_CORDB,LL_INFO10000,"DS::Step %p STEP_NORMAL\n",this));
     m_reason = STEP_NORMAL; //assume it'll be a normal step & set it to
     //something else if we walk over it
     if (fIsILStub)
     {
-        LOG((LF_CORDB, LL_INFO10000, "DS:Step: stepping in an IL stub\n"));
+        LOG((LF_CORDB, LL_INFO10000, "DS::Step: stepping in an IL stub\n"));
 
         // Enable the right triggers if the user wants to step in.
         if (in)
@@ -6978,12 +6974,12 @@ bool DebuggerStepper::Step(FramePointer fp, bool in,
     }
     else if (!TrapStep(&info, in))
     {
-        LOG((LF_CORDB,LL_INFO10000,"DS:Step: Did TS\n"));
+        LOG((LF_CORDB,LL_INFO10000,"DS::Step: Did TS\n"));
         m_stepIn = true;
         TrapStepNext(&info);
     }
 
-    LOG((LF_CORDB,LL_INFO10000,"DS:Step: Did TS,TSO\n"));
+    LOG((LF_CORDB,LL_INFO10000,"DS::Step: Did TS,TSO\n"));
 
     EnableUnwind(m_fp);
 
@@ -7316,13 +7312,13 @@ void DebuggerStepper::TriggerMethodEnter(Thread * thread,
     _ASSERTE(!IsFrozen());
 
     MethodDesc * pDesc = dji->m_nativeCodeVersion.GetMethodDesc();
-    LOG((LF_CORDB, LL_INFO10000, "DebuggerStepper::TME, desc=%p, addr=%p\n",
+    LOG((LF_CORDB, LL_INFO10000, "DS::TME, desc=%p, addr=%p\n",
         pDesc, ip));
 
     // JMC steppers won't stop in Lightweight codegen (LCG). Just return & keep executing.
     if (pDesc->IsNoMetadata())
     {
-        LOG((LF_CORDB, LL_INFO100000, "DebuggerStepper::TME, skipping b/c it's dynamic code (LCG)\n"));
+        LOG((LF_CORDB, LL_INFO100000, "DS::TME, skipping b/c it's dynamic code (LCG)\n"));
         return;
     }
 
@@ -7382,9 +7378,7 @@ void DebuggerStepper::TriggerMethodEnter(Thread * thread,
     }
 #endif
 
-
-
-    // Place a patch to stopus.
+    // Place a patch to stop us.
     // Don't bind to a particular AppDomain so that we can do a Cross-Appdomain step.
     AddBindAndActivateNativeManagedPatch(pDesc,
                   dji,
@@ -7393,9 +7387,9 @@ void DebuggerStepper::TriggerMethodEnter(Thread * thread,
                   NULL // AppDomain
     );
 
-    LOG((LF_CORDB, LL_INFO10000, "DJMCStepper::TME, after setting patch to stop\n"));
+    LOG((LF_CORDB, LL_INFO10000, "DS::TME, after setting patch to stop\n"));
 
-    // Once we resume, we'll go hit that patch (duh, we patched our return address)
+    // Once we resume, we'll go hit that patch since we patched our return address.
     // Furthermore, we know the step will complete with reason = call, so set that now.
     m_reason = STEP_CALL;
 }
@@ -7406,7 +7400,7 @@ void DebuggerStepper::TriggerMethodEnter(Thread * thread,
 // We never single-step into calls (we place a patch at the call destination).
 bool DebuggerStepper::TriggerSingleStep(Thread *thread, const BYTE *ip)
 {
-    LOG((LF_CORDB,LL_INFO10000,"DS:TSS this:0x%p, @ ip:0x%p\n", this, ip));
+    LOG((LF_CORDB,LL_INFO10000,"DS::TSS this:0x%p, @ ip:0x%p\n", this, ip));
 
     _ASSERTE(!IsFrozen());
 
@@ -7507,12 +7501,12 @@ bool DebuggerStepper::TriggerSingleStep(Thread *thread, const BYTE *ip)
 
 void DebuggerStepper::TriggerTraceCall(Thread *thread, const BYTE *ip)
 {
-    LOG((LF_CORDB,LL_INFO10000,"DS:TTC this:0x%x, @ ip:0x%x\n",this,ip));
+    LOG((LF_CORDB,LL_INFO10000,"DS::TTC this:0x%x, @ ip:0x%x\n",this,ip));
     TraceDestination trace;
 
     if (IsFrozen())
     {
-        LOG((LF_CORDB,LL_INFO10000,"DS:TTC exit b/c of Frozen\n"));
+        LOG((LF_CORDB,LL_INFO10000,"DS::TTC exit b/c of Frozen\n"));
         return;
     }
 
@@ -7573,7 +7567,7 @@ void DebuggerStepper::TriggerUnwind(Thread *thread,
 
     if (IsFrozen())
     {
-        LOG((LF_CORDB,LL_INFO10000,"DS:TTC exit b/c of Frozen\n"));
+        LOG((LF_CORDB,LL_INFO10000,"DS::TTC exit b/c of Frozen\n"));
         return;
     }
 
@@ -7853,7 +7847,7 @@ bool DebuggerJMCStepper::TrapStepInHelper(
         SIZE_T offset = CodeRegionInfo::GetCodeRegionInfo(dji, pDesc).AddressToOffset(ipNext);
 
 
-        LOG((LF_CORDB, LL_INFO100000, "DJMCStepper::TSIH, at '%s::%s', calling=0x%p, next=0x%p, offset=%d\n",
+        LOG((LF_CORDB, LL_INFO100000, "DJMCStepper::TSIH, at '%s::%s', calling=%p, next=%p, offset=%d\n",
             pDesc->m_pszDebugClassName,
             pDesc->m_pszDebugMethodName,
             ipCallTarget, ipNext,
index aa43334..b94e325 100644 (file)
@@ -2529,7 +2529,7 @@ void Debugger::JITComplete(NativeCodeVersion nativeCodeVersion, TADDR newAddress
             goto Exit;
         }
 
-        LOG((LF_CORDB, LL_INFO1000000, "D::JITComplete: md:0x%p (%s::%s), address:%p. Created dji:%p\n",
+        LOG((LF_CORDB, LL_INFO1000000, "D::JITComplete: md:%p (%s::%s), address:%p. Created dji:%p\n",
             fd, fd->m_pszDebugClassName, fd->m_pszDebugMethodName, newAddress, dji));
 
         // Bind any IL patches to the newly jitted native code.
@@ -3070,7 +3070,7 @@ CodeRegionInfo CodeRegionInfo::GetCodeRegionInfo(DebuggerJitInfo *dji, MethodDes
 
     if (dji && dji->m_addrOfCode)
     {
-        LOG((LF_CORDB, LL_INFO10000, "CRI::GCRI: simple case: CodeRegionInfo* 0x%p\n", &dji->m_codeRegionInfo));
+        LOG((LF_CORDB, LL_INFO10000, "CRI::GCRI: simple case: CodeRegionInfo* %p\n", &dji->m_codeRegionInfo));
         return dji->m_codeRegionInfo;
     }
     else
@@ -3096,7 +3096,7 @@ CodeRegionInfo CodeRegionInfo::GetCodeRegionInfo(DebuggerJitInfo *dji, MethodDes
                      (addr == dac_cast<PTR_CORDB_ADDRESS_TYPE>(g_pEEInterface->GetFunctionAddress(md))));
         }
 
-        LOG((LF_CORDB, LL_INFO10000, "CRI::GCRI: Initializing CodeRegionInfo from 0x%p, md=0x%p\n", addr, md));
+        LOG((LF_CORDB, LL_INFO10000, "CRI::GCRI: Initializing CodeRegionInfo from %p, md=%p\n", addr, md));
         if (addr)
         {
             PCODE pCode = PINSTRToPCODE(dac_cast<TADDR>(addr));
@@ -3130,7 +3130,7 @@ void Debugger::getBoundariesHelper(MethodDesc * md,
 
     if (dmi != NULL)
     {
-        LOG((LF_CORDB,LL_INFO10000,"De::NGB: Got dmi 0x%x\n",dmi));
+        LOG((LF_CORDB,LL_INFO10000,"De::NGB: Got dmi %p\n",dmi));
 
 #if defined(FEATURE_ISYM_READER)
         // Note: we need to make sure to enable preemptive GC here just in case we block in the symbol reader.
@@ -3161,7 +3161,7 @@ void Debugger::getBoundariesHelper(MethodDesc * md,
 
 
                 LOG((LF_CORDB, LL_INFO100000,
-                     "D::NGB: Reader seq pt count is %d\n", n));
+                     "D::NGB: Reader seq pt count is %u\n", n));
 
                 ULONG32 *p;
 
@@ -13922,7 +13922,7 @@ Debugger::InsertToMethodInfoList( DebuggerMethodInfo *dmi )
     }
     else
     {
-        LOG((LF_CORDB, LL_EVERYTHING, "AddMethodInfo being called in D:IAHOL\n"));
+        LOG((LF_CORDB, LL_EVERYTHING, "D:IAHOL: AddMethodInfo called\n"));
         hr = m_pMethodInfos->AddMethodInfo(dmi->m_module,
                                          dmi->m_token,
                                          dmi);
index 5602dc2..9621b00 100644 (file)
@@ -280,7 +280,7 @@ DebuggerJitInfo::DebuggerJitInfo(DebuggerMethodInfo *minfo, NativeCodeVersion na
     _ASSERTE(minfo);
     m_encVersion = minfo->GetCurrentEnCVersion();
     _ASSERTE(m_encVersion >= CorDB_DEFAULT_ENC_FUNCTION_VERSION);
-    LOG((LF_CORDB,LL_EVERYTHING, "DJI::DJI : created at 0x%p\n", this));
+    LOG((LF_CORDB,LL_EVERYTHING, "DJI::DJI: created at %p\n", this));
 
     // Debugger doesn't track LightWeight codegen methods.
     // We should never even be creating a DJI for one.
@@ -1423,7 +1423,7 @@ DebuggerMethodInfo::DebuggerMethodInfo(Module *module, mdMethodDef token) :
     }
     CONTRACTL_END;
 
-    LOG((LF_CORDB,LL_EVERYTHING, "DMI::DMI : created at 0x%p\n", this));
+    LOG((LF_CORDB,LL_EVERYTHING, "DMI::DMI: created at %p\n", this));
 
     _ASSERTE(g_pDebugger->HasDebuggerDataLock());
 
index 4e69980..8c3c50d 100644 (file)
@@ -606,6 +606,7 @@ BEGIN
         BFA_GENERIC_METHODS_INST                "Generic methods should always be mcInstantiated."
         BFA_BAD_FIELD_TOKEN                     "Field token out of range."
         BFA_INVALID_FIELD_ACC_FLAGS             "Invalid Field Access Flags."
+        BFA_INVALID_UNSAFEACCESSOR              "Invalid usage of UnsafeAccessorAttribute."
         BFA_FIELD_LITERAL_AND_INIT              "Field is Literal and InitOnly."
         BFA_NONSTATIC_GLOBAL_FIELD              "Non-Static Global Field."
         BFA_INSTANCE_FIELD_IN_INT               "Instance Field in an Interface."
index 9942b1a..878fd3c 100644 (file)
 #define BFA_BAD_COMPLUS_SIG                     0x2049
 #define BFA_BAD_ELEM_IN_SIZEOF                  0x204b
 #define BFA_IJW_IN_COLLECTIBLE_ALC              0x204c
+#define BFA_INVALID_UNSAFEACCESSOR              0x204d
 
 #define IDS_CLASSLOAD_INTERFACE_NO_ACCESS       0x204f
 
index 4d50b13..dd2c91a 100644 (file)
@@ -296,41 +296,19 @@ BOOL Assembler::AddMethod(Method *pMethod)
         fIsInterface = IsTdInterface(pMethod->m_pClass->m_Attr);
         fIsImport = IsTdImport(pMethod->m_pClass->m_Attr);
     }
-    if(m_CurPC)
-    {
-        char sz[1024];
-        sz[0] = 0;
-        if(fIsImport) strcat_s(sz,1024," imported");
-        if(IsMdAbstract(pMethod->m_Attr)) strcat_s(sz,1024," abstract");
-        if(IsMdPinvokeImpl(pMethod->m_Attr)) strcat_s(sz,1024," pinvoke");
-        if(!IsMiIL(pMethod->m_wImplAttr)) strcat_s(sz,1024," non-IL");
-        if(IsMiRuntime(pMethod->m_wImplAttr)) strcat_s(sz,1024," runtime-supplied");
-        if(IsMiInternalCall(pMethod->m_wImplAttr)) strcat_s(sz,1024," an internal call");
-        if(strlen(sz))
-        {
-            report->error("Method cannot have body if it is%s\n",sz);
-        }
-    }
-    else // method has no body
+
+    if(!m_CurPC) return TRUE; // method has no body, just emit empty method.
+
+    char sz[1024] = {};
+    if(fIsImport) strcat_s(sz,1024," imported");
+    if(IsMdAbstract(pMethod->m_Attr)) strcat_s(sz,1024," abstract");
+    if(IsMdPinvokeImpl(pMethod->m_Attr)) strcat_s(sz,1024," pinvoke");
+    if(!IsMiIL(pMethod->m_wImplAttr)) strcat_s(sz,1024," non-IL");
+    if(IsMiRuntime(pMethod->m_wImplAttr)) strcat_s(sz,1024," runtime-supplied");
+    if(IsMiInternalCall(pMethod->m_wImplAttr)) strcat_s(sz,1024," an internal call");
+    if(strlen(sz))
     {
-        if(fIsImport || IsMdAbstract(pMethod->m_Attr) || IsMdPinvokeImpl(pMethod->m_Attr)
-           || IsMiRuntime(pMethod->m_wImplAttr) || IsMiInternalCall(pMethod->m_wImplAttr)) return TRUE;
-        if(OnErrGo)
-        {
-            report->error("Method has no body\n");
-            return TRUE;
-        }
-        else
-        {
-            report->warn("Method has no body, 'ret' emitted\n");
-            Instr* pIns = GetInstr();
-            if(pIns)
-            {
-                memset(pIns,0,sizeof(Instr));
-                pIns->opcode = CEE_RET;
-                EmitOpcode(pIns);
-            }
-        }
+        report->error("Method cannot have body if it is%s\n",sz);
     }
 
     if(pMethod->m_Locals.COUNT()) pMethod->m_LocalsSig=0x11000001; // placeholder, the real token 2b defined in EmitMethod
index a26676e..3cc0a36 100644 (file)
@@ -626,9 +626,7 @@ extern "C" {
 class COR_ILMETHOD_DECODER : public COR_ILMETHOD_FAT
 {
 public:
-    // This returns an uninitialized decoder, suitable for placement new but nothing
-    // else. Use with caution.
-    COR_ILMETHOD_DECODER() {}
+    COR_ILMETHOD_DECODER() = default;
 
     // Typically the ONLY way you should access COR_ILMETHOD is through
     // this constructor so format changes are easier.
index 6a79d4b..64d401f 100644 (file)
@@ -688,8 +688,9 @@ PCCOR_SIGNATURE PrettyPrintType(
                         appendStr(out, "(null)");
                 }
 
-                char sz[32];
-                sprintf_s(sz, ARRAY_SIZE(sz), " /* MT: 0x%p */", pMT);
+                const char fmt[] = " /* MT: %p */";
+                char sz[Max64BitHexString + ARRAY_SIZE(fmt)];
+                sprintf_s(sz, ARRAY_SIZE(sz), fmt, pMT);
                 appendStr(out, sz);
                 break;
             }
index 29d02e6..4987af7 100644 (file)
@@ -70,15 +70,7 @@ class SArray
     void Append(ELEMENT elem)
     {
         WRAPPER_NO_CONTRACT;
-        *Append() = elem;
-    }
-
-    ELEMENT AppendEx(ELEMENT elem)
-    {
-        WRAPPER_NO_CONTRACT;
-
-        *Append() = elem;
-        return elem;
+        *Append() = std::move(elem);
     }
 
     void Insert(const Iterator &i);
index 17aa21e..55adc4b 100644 (file)
@@ -118,6 +118,11 @@ namespace Internal.Runtime.CompilerHelpers
             throw TypeLoaderExceptionHelper.CreateMarshalDirectiveException(id);
         }
 
+        public static void ThrowAmbiguousMatchException(ExceptionStringID id)
+        {
+            throw TypeLoaderExceptionHelper.CreateAmbiguousMatchException(id);
+        }
+
         public static void ThrowArgumentException()
         {
             throw new ArgumentException();
index 76b714c..70d17bb 100644 (file)
@@ -2,6 +2,7 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System;
+using System.Reflection;
 using System.Runtime.InteropServices;
 
 using Internal.TypeSystem;
@@ -63,6 +64,11 @@ namespace Internal.Runtime
             throw new MarshalDirectiveException(GetFormatString(id));
         }
 
+        public static Exception CreateAmbiguousMatchException(ExceptionStringID id)
+        {
+            return new AmbiguousMatchException(GetFormatString(id));
+        }
+
         // TODO: move to a place where we can share this with the compiler
         private static string GetFormatString(ExceptionStringID id)
         {
@@ -112,6 +118,8 @@ namespace Internal.Runtime
                     return SR.Arg_BadImageFormatException;
                 case ExceptionStringID.MarshalDirectiveGeneric:
                     return SR.Arg_MarshalDirectiveException;
+                case ExceptionStringID.AmbiguousMatchUnsafeAccessor:
+                    return SR.Arg_AmbiguousMatchException_UnsafeAccessor;
                 default:
                     Debug.Assert(false);
                     return "";
index 4c55874..dd53c1b 100644 (file)
@@ -47,5 +47,8 @@ namespace Internal.TypeSystem
 
         // MarshalDirectiveException
         MarshalDirectiveGeneric,
+
+        // AmbiguousMatchException
+        AmbiguousMatchUnsafeAccessor,
     }
 }
index c0ba423..6e12253 100644 (file)
@@ -2,6 +2,7 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System;
+using System.Collections.Generic;
 using System.Diagnostics;
 using System.Runtime.CompilerServices;
 using Internal.NativeFormat;
@@ -185,10 +186,30 @@ namespace Internal.TypeSystem
 
         public EmbeddedSignatureData[] GetEmbeddedSignatureData()
         {
+            return GetEmbeddedSignatureData(default);
+        }
+
+        public EmbeddedSignatureData[] GetEmbeddedSignatureData(ReadOnlySpan<EmbeddedSignatureDataKind> kinds)
+        {
             if ((_embeddedSignatureData == null) || (_embeddedSignatureData.Length == 0))
                 return null;
 
-            return (EmbeddedSignatureData[])_embeddedSignatureData.Clone();
+            if (kinds.IsEmpty)
+                return (EmbeddedSignatureData[])_embeddedSignatureData.Clone();
+
+            List<EmbeddedSignatureData> ret = new();
+            foreach (var data in _embeddedSignatureData)
+            {
+                foreach (var k in kinds)
+                {
+                    if (data.kind == k)
+                    {
+                        ret.Add(data);
+                        break;
+                    }
+                }
+            }
+            return ret.ToArray();
         }
 
         public bool Equals(MethodSignature otherSignature)
index 6a70612..319bb25 100644 (file)
   <data name="MarshalDirectiveGeneric" xml:space="preserve">
     <value>Marshaling directives are invalid</value>
   </data>
+  <data name="AmbiguousMatchUnsafeAccessor" xml:space="preserve">
+    <value>Ambiguity in binding of UnsafeAccessorAttribute.</value>
+  </data>
 </root>
index 4fec12a..1760aab 100644 (file)
@@ -311,6 +311,10 @@ namespace Internal.IL
                 if (methodIL != null)
                     return methodIL;
 
+                methodIL = UnsafeAccessors.TryGetIL(ecmaMethod);
+                if (methodIL != null)
+                    return methodIL;
+
                 return null;
             }
             else
diff --git a/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs b/src/coreclr/tools/Common/TypeSystem/IL/UnsafeAccessors.cs
new file mode 100644 (file)
index 0000000..0bb3eb4
--- /dev/null
@@ -0,0 +1,525 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Diagnostics;
+using System.Globalization;
+using System.Reflection.Metadata;
+using System.Runtime.InteropServices;
+using Internal.IL.Stubs;
+using Internal.TypeSystem;
+using Internal.TypeSystem.Ecma;
+
+namespace Internal.IL
+{
+    public sealed class UnsafeAccessors
+    {
+        public static MethodIL TryGetIL(EcmaMethod method)
+        {
+            Debug.Assert(method != null);
+            CustomAttributeValue<TypeDesc>? decodedAttribute = method.GetDecodedCustomAttribute("System.Runtime.CompilerServices", "UnsafeAccessorAttribute");
+            if (!decodedAttribute.HasValue)
+            {
+                return null;
+            }
+
+            if (!TryParseUnsafeAccessorAttribute(method, decodedAttribute.Value, out UnsafeAccessorKind kind, out string name))
+            {
+                return GenerateAccessorBadImageFailure(method);
+            }
+
+            GenerationContext context = new()
+            {
+                Kind = kind,
+                Declaration = method
+            };
+
+            MethodSignature sig = method.Signature;
+            TypeDesc retType = sig.ReturnType;
+            TypeDesc firstArgType = null;
+            if (sig.Length > 0)
+            {
+                firstArgType = sig[0];
+            }
+
+            bool isAmbiguous = false;
+
+            // Using the kind type, perform the following:
+            //  1) Validate the basic type information from the signature.
+            //  2) Resolve the name to the appropriate member.
+            switch (kind)
+            {
+                case UnsafeAccessorKind.Constructor:
+                    // A return type is required for a constructor, otherwise
+                    // we don't know the type to construct.
+                    // Types should not be parameterized (that is, byref).
+                    // The name is defined by the runtime and should be empty.
+                    if (retType.IsVoid || retType.IsByRef || !string.IsNullOrEmpty(name))
+                    {
+                        return GenerateAccessorBadImageFailure(method);
+                    }
+
+                    const string ctorName = ".ctor";
+                    context.TargetType = ValidateTargetType(retType);
+                    if (context.TargetType == null)
+                    {
+                        return GenerateAccessorBadImageFailure(method);
+                    }
+
+                    if (!TrySetTargetMethod(ref context, ctorName, out isAmbiguous))
+                    {
+                        return GenerateAccessorSpecificFailure(ref context, ctorName, isAmbiguous);
+                    }
+                    break;
+                case UnsafeAccessorKind.Method:
+                case UnsafeAccessorKind.StaticMethod:
+                    // Method access requires a target type.
+                    if (firstArgType == null)
+                    {
+                        return GenerateAccessorBadImageFailure(method);
+                    }
+
+                    context.TargetType = ValidateTargetType(firstArgType);
+                    if (context.TargetType == null)
+                    {
+                        return GenerateAccessorBadImageFailure(method);
+                    }
+
+                    context.IsTargetStatic = kind == UnsafeAccessorKind.StaticMethod;
+                    if (!TrySetTargetMethod(ref context, name, out isAmbiguous))
+                    {
+                        return GenerateAccessorSpecificFailure(ref context, name, isAmbiguous);
+                    }
+                    break;
+
+                case UnsafeAccessorKind.Field:
+                case UnsafeAccessorKind.StaticField:
+                    // Field access requires a single argument for target type and a return type.
+                    if (sig.Length != 1 || retType.IsVoid)
+                    {
+                        return GenerateAccessorBadImageFailure(method);
+                    }
+
+                    // The return type must be byref.
+                    // If the non-static field access is for a
+                    // value type, the instance must be byref.
+                    if (!retType.IsByRef
+                        || (kind == UnsafeAccessorKind.Field
+                            && firstArgType.IsValueType
+                            && !firstArgType.IsByRef))
+                    {
+                        return GenerateAccessorBadImageFailure(method);
+                    }
+
+                    context.TargetType = ValidateTargetType(firstArgType);
+                    if (context.TargetType == null)
+                    {
+                        return GenerateAccessorBadImageFailure(method);
+                    }
+
+                    context.IsTargetStatic = kind == UnsafeAccessorKind.StaticField;
+                    if (!TrySetTargetField(ref context, name, ((ParameterizedType)retType).GetParameterType()))
+                    {
+                        return GenerateAccessorSpecificFailure(ref context, name, isAmbiguous);
+                    }
+                    break;
+
+                default:
+                    return GenerateAccessorBadImageFailure(method);
+            }
+
+            // Generate the IL for the accessor.
+            return GenerateAccessor(ref context);
+        }
+
+        // This is a redeclaration of the new type in the .NET 8 TFM.
+        private enum UnsafeAccessorKind
+        {
+            Constructor,
+            Method,
+            StaticMethod,
+            Field,
+            StaticField
+        };
+
+        private static bool TryParseUnsafeAccessorAttribute(MethodDesc method, CustomAttributeValue<TypeDesc> decodedValue, out UnsafeAccessorKind kind, out string name)
+        {
+            kind = default;
+            name = default;
+
+            var context = method.Context;
+
+            // Get the kind of accessor
+            if (decodedValue.FixedArguments.Length != 1
+                || decodedValue.FixedArguments[0].Type.UnderlyingType != context.GetWellKnownType(WellKnownType.Int32))
+            {
+                return false;
+            }
+
+            kind = (UnsafeAccessorKind)decodedValue.FixedArguments[0].Value!;
+
+            // Check the name of the target to access. This is the name we
+            // use to look up the intended token in metadata.
+            string nameMaybe = null;
+            foreach (var argument in decodedValue.NamedArguments)
+            {
+                if (argument.Name == "Name")
+                {
+                    nameMaybe = (string)argument.Value;
+                }
+            }
+
+            // If the Name isn't defined, then use the name of the method.
+            if (nameMaybe != null)
+            {
+                name = nameMaybe;
+            }
+            else
+            {
+                // The Constructor case has an implied value provided by
+                // the runtime. We are going to enforce this during consumption
+                // so we avoid the setting of the value. We validate the name
+                // as empty at the use site.
+                if (kind is not UnsafeAccessorKind.Constructor)
+                {
+                    name = method.Name;
+                }
+            }
+
+            return true;
+        }
+
+        [StructLayout(LayoutKind.Sequential)]
+        private struct GenerationContext
+        {
+            public UnsafeAccessorKind Kind;
+            public EcmaMethod Declaration;
+            public TypeDesc TargetType;
+            public bool IsTargetStatic;
+            public MethodDesc TargetMethod;
+            public FieldDesc TargetField;
+        }
+
+        private static TypeDesc ValidateTargetType(TypeDesc targetTypeMaybe)
+        {
+            TypeDesc targetType = targetTypeMaybe.IsByRef
+                ? ((ParameterizedType)targetTypeMaybe).ParameterType
+                : targetTypeMaybe;
+
+            // Due to how some types degrade, we block on parameterized
+            // types. For example ref or pointer.
+            if ((targetType.IsParameterizedType && !targetType.IsArray)
+                || targetType.IsFunctionPointer)
+            {
+                ThrowHelper.ThrowBadImageFormatException();
+            }
+
+            return targetType;
+        }
+
+        private static bool DoesMethodMatchUnsafeAccessorDeclaration(ref GenerationContext context, MethodDesc method, bool ignoreCustomModifiers)
+        {
+            MethodSignature declSig = context.Declaration.Signature;
+            MethodSignature maybeSig = method.Signature;
+
+            // Check if we need to also validate custom modifiers.
+            // If we are, do it first.
+            if (!ignoreCustomModifiers)
+            {
+                // Compare any unmanaged callconv and custom modifiers on the signatures.
+                // We treat unmanaged calling conventions at the same level of precedance
+                // as custom modifiers, eventhough they are normally bits in a signature.
+                ReadOnlySpan<EmbeddedSignatureDataKind> kinds = new EmbeddedSignatureDataKind[]
+                {
+                    EmbeddedSignatureDataKind.UnmanagedCallConv,
+                    EmbeddedSignatureDataKind.RequiredCustomModifier,
+                    EmbeddedSignatureDataKind.OptionalCustomModifier
+                };
+
+                var declData = declSig.GetEmbeddedSignatureData(kinds) ?? Array.Empty<EmbeddedSignatureData>();
+                var maybeData = maybeSig.GetEmbeddedSignatureData(kinds) ?? Array.Empty<EmbeddedSignatureData>();
+                if (declData.Length != maybeData.Length)
+                {
+                    return false;
+                }
+
+                // Validate the custom modifiers match precisely.
+                for (int i = 0; i < declData.Length; ++i)
+                {
+                    EmbeddedSignatureData dd = declData[i];
+                    EmbeddedSignatureData md = maybeData[i];
+                    if (dd.kind != md.kind || dd.type != md.type)
+                    {
+                        return false;
+                    }
+
+                    // The indices on non-constructor declarations require
+                    // some slight modification since there is always an extra
+                    // argument in the declaration compared to the target.
+                    string declIndex = dd.index;
+                    if (context.Kind != UnsafeAccessorKind.Constructor)
+                    {
+                        string unmanagedCallConvMaybe = string.Empty;
+
+                        // Check for and drop the unmanaged calling convention
+                        // value suffix to add it back after updating below.
+                        if (declIndex.Contains('|'))
+                        {
+                            Debug.Assert(dd.kind == EmbeddedSignatureDataKind.UnmanagedCallConv);
+                            var tmp = declIndex.Split('|');
+                            Debug.Assert(tmp.Length == 2);
+                            declIndex = tmp[0];
+                            unmanagedCallConvMaybe = "|" + tmp[1];
+                        }
+
+                        // Decrement the second to last index by one to
+                        // account for the difference in declarations.
+                        string[] lvls = declIndex.Split('.');
+                        int toUpdate = lvls.Length < 2 ? 0 : lvls.Length - 2;
+                        int idx = int.Parse(lvls[toUpdate], CultureInfo.InvariantCulture);
+                        idx--;
+                        lvls[toUpdate] = idx.ToString();
+                        declIndex = string.Join(".", lvls) + unmanagedCallConvMaybe;
+                    }
+
+                    if (declIndex != md.index)
+                    {
+                        return false;
+                    }
+                }
+            }
+
+            // Validate calling convention of declaration.
+            if ((declSig.Flags & MethodSignatureFlags.UnmanagedCallingConventionMask)
+                != (maybeSig.Flags & MethodSignatureFlags.UnmanagedCallingConventionMask))
+            {
+                return false;
+            }
+
+            // Validate argument count and return type
+            if (context.Kind == UnsafeAccessorKind.Constructor)
+            {
+                // Declarations for constructor scenarios have
+                // matching argument counts with the target.
+                if (declSig.Length != maybeSig.Length)
+                {
+                    return false;
+                }
+
+                // Validate the return value for target constructor
+                // candidate is void.
+                if (!maybeSig.ReturnType.IsVoid)
+                {
+                    return false;
+                }
+            }
+            else
+            {
+                // Declarations of non-constructor scenarios have
+                // an additional argument to indicate target type
+                // and to pass an instance for non-static methods.
+                if (declSig.Length != (maybeSig.Length + 1))
+                {
+                    return false;
+                }
+
+                if (declSig.ReturnType != maybeSig.ReturnType)
+                {
+                    return false;
+                }
+            }
+
+            // Validate argument types
+            for (int i = 0; i < maybeSig.Length; ++i)
+            {
+                // Skip over first argument (index 0) on non-constructor accessors.
+                // See argument count validation above.
+                TypeDesc declType = context.Kind == UnsafeAccessorKind.Constructor ? declSig[i] : declSig[i + 1];
+                TypeDesc maybeType = maybeSig[i];
+
+                // Compare the types
+                if (declType != maybeType)
+                {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+        private static bool TrySetTargetMethod(ref GenerationContext context, string name, out bool isAmbiguous, bool ignoreCustomModifiers = true)
+        {
+            TypeDesc targetType = context.TargetType;
+
+            MethodDesc targetMaybe = null;
+            foreach (MethodDesc md in targetType.GetMethods())
+            {
+                // Check the target and current method match static/instance state.
+                if (context.IsTargetStatic != md.Signature.IsStatic)
+                {
+                    continue;
+                }
+
+                // Check for matching name
+                if (!md.Name.Equals(name))
+                {
+                    continue;
+                }
+
+                // Check signature
+                if (!DoesMethodMatchUnsafeAccessorDeclaration(ref context, md, ignoreCustomModifiers))
+                {
+                    continue;
+                }
+
+                // Check if there is some ambiguity.
+                if (targetMaybe != null)
+                {
+                    if (ignoreCustomModifiers)
+                    {
+                        // We have detected ambiguity when ignoring custom modifiers.
+                        // Start over, but look for a match requiring custom modifiers
+                        // to match precisely.
+                        if (TrySetTargetMethod(ref context, name, out isAmbiguous, ignoreCustomModifiers: false))
+                            return true;
+                    }
+
+                    isAmbiguous = true;
+                    return false;
+                }
+
+                targetMaybe = md;
+            }
+
+            isAmbiguous = false;
+            context.TargetMethod = targetMaybe;
+            return context.TargetMethod != null;
+        }
+
+        private static bool TrySetTargetField(ref GenerationContext context, string name, TypeDesc fieldType)
+        {
+            TypeDesc targetType = context.TargetType;
+
+            foreach (FieldDesc fd in targetType.GetFields())
+            {
+                if (context.IsTargetStatic != fd.IsStatic)
+                {
+                    continue;
+                }
+
+                // Validate the name and target type match.
+                if (fd.Name.Equals(name)
+                    && fieldType == fd.FieldType)
+                {
+                    context.TargetField = fd;
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        private static MethodIL GenerateAccessor(ref GenerationContext context)
+        {
+            ILEmitter emit = new ILEmitter();
+            ILCodeStream codeStream = emit.NewCodeStream();
+
+            // Load stub arguments.
+            // When the target is static, the first argument is only
+            // used to look up the target member to access and ignored
+            // during dispatch.
+            int beginIndex = context.IsTargetStatic ? 1 : 0;
+            int stubArgCount = context.Declaration.Signature.Length;
+            for (int i = beginIndex; i < stubArgCount; ++i)
+            {
+                codeStream.EmitLdArg(i);
+            }
+
+            // Provide access to the target member
+            switch (context.Kind)
+            {
+                case UnsafeAccessorKind.Constructor:
+                    Debug.Assert(context.TargetMethod != null);
+                    codeStream.Emit(ILOpcode.newobj, emit.NewToken(context.TargetMethod));
+                    break;
+                case UnsafeAccessorKind.Method:
+                    Debug.Assert(context.TargetMethod != null);
+                    codeStream.Emit(ILOpcode.callvirt, emit.NewToken(context.TargetMethod));
+                    break;
+                case UnsafeAccessorKind.StaticMethod:
+                    Debug.Assert(context.TargetMethod != null);
+                    codeStream.Emit(ILOpcode.call, emit.NewToken(context.TargetMethod));
+                    break;
+                case UnsafeAccessorKind.Field:
+                    Debug.Assert(context.TargetField != null);
+                    codeStream.Emit(ILOpcode.ldflda, emit.NewToken(context.TargetField));
+                    break;
+                case UnsafeAccessorKind.StaticField:
+                    Debug.Assert(context.TargetField != null);
+                    codeStream.Emit(ILOpcode.ldsflda, emit.NewToken(context.TargetField));
+                    break;
+                default:
+                    Debug.Fail("Unknown UnsafeAccessorKind");
+                    break;
+            }
+
+            // Return from the generated stub
+            codeStream.Emit(ILOpcode.ret);
+            return emit.Link(context.Declaration);
+        }
+
+        private static MethodIL GenerateAccessorSpecificFailure(ref GenerationContext context, string name, bool ambiguous)
+        {
+            ILEmitter emit = new ILEmitter();
+            ILCodeStream codeStream = emit.NewCodeStream();
+
+            ILCodeLabel label = emit.NewCodeLabel();
+            codeStream.EmitLabel(label);
+
+            MethodDesc thrower;
+            TypeSystemContext typeSysContext = context.Declaration.Context;
+            if (ambiguous)
+            {
+                codeStream.EmitLdc((int)ExceptionStringID.AmbiguousMatchUnsafeAccessor);
+                thrower = typeSysContext.GetHelperEntryPoint("ThrowHelpers", "ThrowAmbiguousMatchException");
+            }
+            else
+            {
+
+                ExceptionStringID id;
+                if (context.Kind == UnsafeAccessorKind.Field || context.Kind == UnsafeAccessorKind.StaticField)
+                {
+                    id = ExceptionStringID.MissingField;
+                    thrower = typeSysContext.GetHelperEntryPoint("ThrowHelpers", "ThrowMissingFieldException");
+                }
+                else
+                {
+                    id = ExceptionStringID.MissingMethod;
+                    thrower = typeSysContext.GetHelperEntryPoint("ThrowHelpers", "ThrowMissingMethodException");
+                }
+
+                codeStream.EmitLdc((int)id);
+                codeStream.Emit(ILOpcode.ldstr, emit.NewToken(name));
+            }
+
+            Debug.Assert(thrower != null);
+            codeStream.Emit(ILOpcode.call, emit.NewToken(thrower));
+            codeStream.Emit(ILOpcode.br, label);
+            return emit.Link(context.Declaration);
+        }
+
+        private static MethodIL GenerateAccessorBadImageFailure(MethodDesc method)
+        {
+            ILEmitter emit = new ILEmitter();
+            ILCodeStream codeStream = emit.NewCodeStream();
+
+            ILCodeLabel label = emit.NewCodeLabel();
+            codeStream.EmitLabel(label);
+            codeStream.EmitLdc((int)ExceptionStringID.BadImageFormatGeneric);
+            MethodDesc thrower = method.Context.GetHelperEntryPoint("ThrowHelpers", "ThrowBadImageFormatException");
+            codeStream.Emit(ILOpcode.call, emit.NewToken(thrower));
+            codeStream.Emit(ILOpcode.br, label);
+
+            return emit.Link(method);
+        }
+    }
+}
index 6109776..dc9f225 100644 (file)
     <Compile Include="..\..\Common\TypeSystem\IL\EcmaMethodIL.Symbols.cs">
       <Link>IL\EcmaMethodIL.Symbols.cs</Link>
     </Compile>
+    <Compile Include="..\..\Common\TypeSystem\IL\HelperExtensions.cs">
+      <Link>IL\HelperExtensions.cs</Link>
+    </Compile>
     <Compile Include="..\..\Common\TypeSystem\IL\MethodIL.cs">
       <Link>IL\MethodIL.cs</Link>
     </Compile>
     <Compile Include="..\..\Common\TypeSystem\IL\ILReader.cs">
       <Link>IL\ILReader.cs</Link>
     </Compile>
+    <Compile Include="..\..\Common\TypeSystem\IL\UnsafeAccessors.cs">
+      <Link>IL\UnsafeAccessors.cs</Link>
+    </Compile>
     <Compile Include="..\..\Common\TypeSystem\IL\Stubs\ILEmitter.cs">
       <Link>IL\Stubs\ILEmitter.cs</Link>
     </Compile>
index 2208254..ebe9cb8 100644 (file)
@@ -461,13 +461,6 @@ ASMCONSTANTS_C_ASSERT(OFFSETOF__PtrArray__m_NumComponents
 ASMCONSTANTS_C_ASSERT(OFFSETOF__PtrArray__m_Array
                     == offsetof(PtrArray, m_Array));
 
-
-#define MethodDescClassification__mdcClassification 0x7
-ASMCONSTANTS_C_ASSERT(MethodDescClassification__mdcClassification == mdcClassification);
-
-#define MethodDescClassification__mcInstantiated 0x5
-ASMCONSTANTS_C_ASSERT(MethodDescClassification__mcInstantiated == mcInstantiated);
-
 #ifndef TARGET_UNIX
 #define OFFSET__TEB__ThreadLocalStoragePointer 0x58
 ASMCONSTANTS_C_ASSERT(OFFSET__TEB__ThreadLocalStoragePointer == offsetof(TEB, ThreadLocalStoragePointer));
index 37a3add..6bb94a6 100644 (file)
@@ -1104,6 +1104,17 @@ ChunkAllocator* LCGMethodResolver::GetJitMetaHeap()
     return &m_jitMetaHeap;
 }
 
+bool LCGMethodResolver::RequiresAccessCheck()
+{
+    LIMITED_METHOD_CONTRACT;
+    return true;
+}
+
+CORJIT_FLAGS LCGMethodResolver::GetJitFlags()
+{
+    return{};
+}
+
 BYTE* LCGMethodResolver::GetCodeInfo(unsigned *pCodeSize, unsigned *pStackSize, CorInfoOptions *pOptions, unsigned *pEHSize)
 {
     STANDARD_VM_CONTRACT;
index a391732..ddbe3c7 100644 (file)
@@ -52,6 +52,7 @@ public:
         CanSkipCSEvaluation = 0x8,
     };
 
+    virtual ~DynamicResolver() { }
 
     // set up and clean up for jitting
     virtual void FreeCompileTimeState() = 0;
@@ -59,6 +60,17 @@ public:
                                TypeHandle *typeOwner) = 0;
     virtual ChunkAllocator* GetJitMetaHeap() = 0;
 
+    // API to check if visibility checks are needed.
+    // If the API requires checks, the callers should use
+    // the potentially expensive GetJitContext() API.
+    // If it returns "false", there is no need for any
+    // further visibility checks.
+    virtual bool RequiresAccessCheck() = 0;
+
+    // Return the flags that should be used in JIT compilation
+    // of the associated method.
+    virtual CORJIT_FLAGS GetJitFlags() = 0;
+
     //
     // code info data
     virtual BYTE * GetCodeInfo(
@@ -121,6 +133,8 @@ public:
     void GetJitContext(SecurityControlFlags * securityControlFlags,
                        TypeHandle * typeOwner);
     ChunkAllocator* GetJitMetaHeap();
+    bool RequiresAccessCheck();
+    CORJIT_FLAGS GetJitFlags();
 
     BYTE* GetCodeInfo(unsigned *pCodeSize, unsigned *pStackSize, CorInfoOptions *pOptions, unsigned* pEHSize);
     SigPointer GetLocalSig();
@@ -331,18 +345,21 @@ inline MethodDesc* GetMethod(CORINFO_METHOD_HANDLE methodHandle)
 
 #ifndef DACCESS_COMPILE
 
-#define CORINFO_MODULE_HANDLE_TYPE_MASK 1
-
 enum CORINFO_MODULE_HANDLE_TYPES
 {
-    CORINFO_NORMAL_MODULE  = 0,
-    CORINFO_DYNAMIC_MODULE,
+    // The module handle is a Module
+    CORINFO_NORMAL_MODULE   = 0,
+
+    // The module handle is a DynamicResolver
+    CORINFO_DYNAMIC_MODULE  = 1,
 };
 
+#define CORINFO_MODULE_HANDLE_TYPE_MASK (CORINFO_NORMAL_MODULE | CORINFO_DYNAMIC_MODULE)
+
 inline bool IsDynamicScope(CORINFO_MODULE_HANDLE module)
 {
     LIMITED_METHOD_CONTRACT;
-    return (CORINFO_DYNAMIC_MODULE == (((size_t)module) & CORINFO_MODULE_HANDLE_TYPE_MASK));
+    return !!(CORINFO_DYNAMIC_MODULE & (((size_t)module) & CORINFO_MODULE_HANDLE_TYPE_MASK));
 }
 
 inline CORINFO_MODULE_HANDLE MakeDynamicScope(DynamicResolver* pResolver)
@@ -359,6 +376,17 @@ inline DynamicResolver* GetDynamicResolver(CORINFO_MODULE_HANDLE module)
     return (DynamicResolver*)(((size_t)module) & ~((size_t)CORINFO_MODULE_HANDLE_TYPE_MASK));
 }
 
+inline bool RequiresAccessCheck(CORINFO_MODULE_HANDLE module)
+{
+    LIMITED_METHOD_CONTRACT;
+
+    // Non-dynamic scopes always require access checks.
+    if (!IsDynamicScope(module))
+        return true;
+
+    return GetDynamicResolver(module)->RequiresAccessCheck();
+}
+
 inline Module* GetModule(CORINFO_MODULE_HANDLE scope)
 {
     WRAPPER_NO_CONTRACT;
index 0ff440c..fbdafac 100644 (file)
@@ -2200,7 +2200,7 @@ void ComputeCallRefMap(MethodDesc* pMD,
     // an instantiation argument. But if we're in a situation where we haven't resolved the method yet
     // we need to pretent that unresolved default interface methods are like any other interface
     // methods and don't have an instantiation argument.
-    // See code:CEEInfo::getMethodSigInternal
+    // See code:getMethodSigInternal
     //
     assert(!isDispatchCell || !pMD->RequiresInstArg() || pMD->GetMethodTable()->IsInterface());
     if (pMD->RequiresInstArg() && !isDispatchCell)
index 83c218b..a00f915 100644 (file)
@@ -2233,7 +2233,7 @@ public:
         // So we need to pretent that unresolved default interface methods are like any other interface
         // methods and don't have an instantiation argument.
         //
-        // See code:CEEInfo::getMethodSigInternal
+        // See code:getMethodSigInternal
         //
         assert(GetFunction()->GetMethodTable()->IsInterface());
         return TRUE;
index 3137f69..79921a1 100644 (file)
@@ -97,7 +97,7 @@ unsigned FindFirstInterruptiblePoint(CrawlFrame* pCF, unsigned offs, unsigned en
 // such methods require a generic context, but since we didn't resolve the
 // method to an implementation yet, we don't have the right context (in fact,
 // there's no context provided by the caller).
-// See code:CEEInfo::getMethodSigInternal
+// See code:getMethodSigInternal
 //
 inline bool SafeToReportGenericParamContext(CrawlFrame* pCF)
 {
index 1ae8614..c24be26 100644 (file)
@@ -80,6 +80,28 @@ ChunkAllocator* ILStubResolver::GetJitMetaHeap()
     return NULL;
 }
 
+bool ILStubResolver::RequiresAccessCheck()
+{
+    LIMITED_METHOD_CONTRACT;
+
+#ifdef _DEBUG
+    SecurityControlFlags securityControlFlags;
+    TypeHandle typeOwner;
+    GetJitContext(&securityControlFlags, &typeOwner);
+
+    // Verify we can return false below because we skip visibility checks
+    _ASSERTE((securityControlFlags & DynamicResolver::SkipVisibilityChecks) == DynamicResolver::SkipVisibilityChecks);
+#endif // _DEBUG
+
+    return false;
+}
+
+CORJIT_FLAGS ILStubResolver::GetJitFlags()
+{
+    LIMITED_METHOD_CONTRACT;
+    return m_jitFlags;
+}
+
 SigPointer
 ILStubResolver::GetLocalSig()
 {
@@ -268,16 +290,15 @@ void ILStubResolver::SetLoaderHeap(PTR_LoaderHeap pLoaderHeap)
     m_loaderHeap = pLoaderHeap;
 }
 
-void ILStubResolver::CreateILHeader(COR_ILMETHOD_DECODER* pILHeader, size_t cbCode, UINT maxStack, BYTE* pNewILCodeBuffer, BYTE* pNewLocalSig, DWORD cbLocalSig)
+static COR_ILMETHOD_DECODER CreateILHeader(size_t cbCode, UINT maxStack, BYTE* pNewILCodeBuffer, BYTE* pNewLocalSig, DWORD cbLocalSig)
 {
-    pILHeader->Flags = 0;
-    pILHeader->CodeSize = (DWORD)cbCode;
-    pILHeader->MaxStack = maxStack;
-    pILHeader->EH = 0;
-    pILHeader->Sect = 0;
-    pILHeader->Code = pNewILCodeBuffer;
-    pILHeader->LocalVarSig = pNewLocalSig;
-    pILHeader->cbLocalVarSig = cbLocalSig;
+    COR_ILMETHOD_DECODER ilHeader{};
+    ilHeader.CodeSize = (DWORD)cbCode;
+    ilHeader.MaxStack = maxStack;
+    ilHeader.Code = pNewILCodeBuffer;
+    ilHeader.LocalVarSig = pNewLocalSig;
+    ilHeader.cbLocalVarSig = cbLocalSig;
+    return ilHeader;
 }
 
 //---------------------------------------------------------------------------------------
@@ -293,59 +314,43 @@ ILStubResolver::AllocGeneratedIL(
 #if !defined(DACCESS_COMPILE)
     _ASSERTE(0 != cbCode);
 
-    if (!UseLoaderHeap())
-    {
-        NewArrayHolder<BYTE>             pNewILCodeBuffer = new BYTE[cbCode];
-        NewHolder<CompileTimeState>      pNewCompileTimeState = new CompileTimeState{};
-        NewArrayHolder<BYTE>             pNewLocalSig = NULL;
-
-        if (0 != cbLocalSig)
-        {
-            pNewLocalSig = new BYTE[cbLocalSig];
-        }
-
-        COR_ILMETHOD_DECODER* pILHeader = &pNewCompileTimeState->m_ILHeader;
+    // Perform a single allocation for all needed memory
+    AllocMemHolder<BYTE> allocMemory;
+    NewArrayHolder<BYTE> newMemory;
+    BYTE* memory;
 
-        CreateILHeader(pILHeader, cbCode, maxStack, pNewILCodeBuffer, pNewLocalSig, cbLocalSig);
+    S_SIZE_T toAlloc = (S_SIZE_T(sizeof(CompileTimeState)) + S_SIZE_T(cbCode) + S_SIZE_T(cbLocalSig));
+    _ASSERTE(!toAlloc.IsOverflow());
 
-#ifdef _DEBUG
-        LPVOID pPrevCompileTimeState =
-#endif // _DEBUG
-            InterlockedExchangeT(&m_pCompileTimeState, pNewCompileTimeState.GetValue());
-        CONSISTENCY_CHECK(ILNotYetGenerated == (UINT_PTR)pPrevCompileTimeState);
-
-        pNewLocalSig.SuppressRelease();
-        pNewILCodeBuffer.SuppressRelease();
-        pNewCompileTimeState.SuppressRelease();
-        return pILHeader;
+    if (UseLoaderHeap())
+    {
+        allocMemory = m_loaderHeap->AllocMem(toAlloc);
+        memory = allocMemory;
     }
     else
     {
-        AllocMemHolder<BYTE>             pNewILCodeBuffer(m_loaderHeap->AllocMem(S_SIZE_T(cbCode)));
-        AllocMemHolder<CompileTimeState> pNewCompileTimeState(m_loaderHeap->AllocMem(S_SIZE_T(sizeof(CompileTimeState))));
-        memset(pNewCompileTimeState, 0, sizeof(CompileTimeState));
-        AllocMemHolder<BYTE>             pNewLocalSig;
+        newMemory = new BYTE[toAlloc.Value()];
+        memory = newMemory;
+    }
 
-        if (0 != cbLocalSig)
-        {
-            pNewLocalSig = m_loaderHeap->AllocMem(S_SIZE_T(cbLocalSig));
-        }
+    // Using placement new
+    CompileTimeState* pNewCompileTimeState = new (memory) CompileTimeState{};
 
-        COR_ILMETHOD_DECODER* pILHeader = &pNewCompileTimeState->m_ILHeader;
+    BYTE* pNewILCodeBuffer = ((BYTE*)pNewCompileTimeState) + sizeof(*pNewCompileTimeState);
+    BYTE* pNewLocalSig = (0 == cbLocalSig)
+        ? NULL
+        : (pNewILCodeBuffer + cbCode);
 
-        CreateILHeader(pILHeader, cbCode, maxStack, pNewILCodeBuffer, pNewLocalSig, cbLocalSig);
+    COR_ILMETHOD_DECODER* pILHeader = &pNewCompileTimeState->m_ILHeader;
+    *pILHeader = CreateILHeader(cbCode, maxStack, pNewILCodeBuffer, pNewLocalSig, cbLocalSig);
 
-#ifdef _DEBUG
-        LPVOID pPrevCompileTimeState =
-#endif // _DEBUG
-            InterlockedExchangeT(&m_pCompileTimeState, (CompileTimeState*)pNewCompileTimeState);
-        CONSISTENCY_CHECK(ILNotYetGenerated == (UINT_PTR)pPrevCompileTimeState);
+    LPVOID pPrevCompileTimeState = InterlockedExchangeT(&m_pCompileTimeState, pNewCompileTimeState);
+    CONSISTENCY_CHECK(ILNotYetGenerated == (UINT_PTR)pPrevCompileTimeState);
+    (void*)pPrevCompileTimeState;
 
-        pNewLocalSig.SuppressRelease();
-        pNewILCodeBuffer.SuppressRelease();
-        pNewCompileTimeState.SuppressRelease();
-        return pILHeader;
-    }
+    allocMemory.SuppressRelease();
+    newMemory.SuppressRelease();
+    return pILHeader;
 
 #else  // DACCESS_COMPILE
     DacNotImpl();
@@ -374,20 +379,16 @@ COR_ILMETHOD_SECT_EH* ILStubResolver::AllocEHSect(size_t nClauses)
 {
     STANDARD_VM_CONTRACT;
 
-    if (nClauses >= 1)
-    {
-        size_t cbSize = sizeof(COR_ILMETHOD_SECT_EH)
-                        - sizeof(IMAGE_COR_ILMETHOD_SECT_EH_CLAUSE_FAT)
-                        + (nClauses * sizeof(IMAGE_COR_ILMETHOD_SECT_EH_CLAUSE_FAT));
-        m_pCompileTimeState->m_pEHSect = (COR_ILMETHOD_SECT_EH*) new BYTE[cbSize];
-        CONSISTENCY_CHECK(NULL == m_pCompileTimeState->m_ILHeader.EH);
-        m_pCompileTimeState->m_ILHeader.EH = m_pCompileTimeState->m_pEHSect;
-        return m_pCompileTimeState->m_pEHSect;
-    }
-    else
-    {
+    if (nClauses == 0)
         return NULL;
-    }
+
+    size_t cbSize = sizeof(COR_ILMETHOD_SECT_EH)
+                    - sizeof(IMAGE_COR_ILMETHOD_SECT_EH_CLAUSE_FAT)
+                    + (nClauses * sizeof(IMAGE_COR_ILMETHOD_SECT_EH_CLAUSE_FAT));
+    m_pCompileTimeState->m_pEHSect = (COR_ILMETHOD_SECT_EH*) new BYTE[cbSize];
+    CONSISTENCY_CHECK(NULL == m_pCompileTimeState->m_ILHeader.EH);
+    m_pCompileTimeState->m_ILHeader.EH = m_pCompileTimeState->m_pEHSect;
+    return m_pCompileTimeState->m_pEHSect;
 }
 
 bool ILStubResolver::UseLoaderHeap()
@@ -433,30 +434,16 @@ ILStubResolver::ClearCompileTimeState(CompileTimeStatePtrSpecialValues newState)
     CONTRACTL_END;
 
     //
-    // See allocations in AllocGeneratedIL and SetStubTargetMethodSig
+    // See allocations in AllocGeneratedIL, SetStubTargetMethodSig and AllocEHSect
     //
 
-    COR_ILMETHOD_DECODER * pILHeader = &m_pCompileTimeState->m_ILHeader;
-
-    CONSISTENCY_CHECK(NULL != pILHeader->Code);
-    delete[] pILHeader->Code;
-
-    if (NULL != pILHeader->LocalVarSig)
-    {
-        delete[] pILHeader->LocalVarSig;
-    }
-
-    if (!m_pCompileTimeState->m_StubTargetMethodSig.IsNull())
-    {
-        delete[] m_pCompileTimeState->m_StubTargetMethodSig.GetPtr();
-    }
-
-    if (NULL != m_pCompileTimeState->m_pEHSect)
-    {
-        delete[] m_pCompileTimeState->m_pEHSect;
-    }
+    delete[](BYTE*)m_pCompileTimeState->m_StubTargetMethodSig.GetPtr();
+    delete[](BYTE*)m_pCompileTimeState->m_pEHSect;
 
-    delete m_pCompileTimeState;
+    // The allocation being deleted here was allocated using placement new
+    // from a bulk allocation so manually call the destructor.
+    m_pCompileTimeState->~CompileTimeState();
+    delete[](BYTE*)(void*)m_pCompileTimeState;
 
     InterlockedExchangeT(&m_pCompileTimeState, dac_cast<PTR_CompileTimeState>((TADDR)newState));
 } // ILStubResolver::ClearCompileTimeState
@@ -495,12 +482,6 @@ void ILStubResolver::SetJitFlags(CORJIT_FLAGS jitFlags)
     m_jitFlags = jitFlags;
 }
 
-CORJIT_FLAGS ILStubResolver::GetJitFlags()
-{
-    LIMITED_METHOD_CONTRACT;
-    return m_jitFlags;
-}
-
 // static
 void ILStubResolver::StubGenFailed(ILStubResolver* pResolver)
 {
index 99f6f40..82a1217 100644 (file)
@@ -26,6 +26,8 @@ public:
     void GetJitContext(SecurityControlFlags* pSecurityControlFlags,
                        TypeHandle* pTypeOwner);
     ChunkAllocator* GetJitMetaHeap();
+    bool RequiresAccessCheck();
+    CORJIT_FLAGS GetJitFlags();
 
     BYTE* GetCodeInfo(unsigned* pCodeSize, unsigned* pStackSize, CorInfoOptions* pOptions, unsigned* pEHSize);
     SigPointer GetLocalSig();
@@ -51,8 +53,6 @@ public:
     void SetStubTargetMethodSig(PCCOR_SIGNATURE pStubTargetMethodSig, DWORD cbStubTargetSigLength);
     void SetStubMethodDesc(MethodDesc* pStubMD);
 
-    void CreateILHeader(COR_ILMETHOD_DECODER* pILHeader, size_t cbCode, UINT maxStack, BYTE* pNewILCodeBuffer, BYTE* pNewLocalSig, DWORD cbLocalSig);
-
     COR_ILMETHOD_DECODER * AllocGeneratedIL(size_t cbCode, DWORD cbLocalSig, UINT maxStack);
     COR_ILMETHOD_DECODER * GetILHeader();
     COR_ILMETHOD_SECT_EH* AllocEHSect(size_t nClauses);
@@ -65,7 +65,6 @@ public:
     void SetTokenLookupMap(TokenLookupMap* pMap);
 
     void SetJitFlags(CORJIT_FLAGS jitFlags);
-    CORJIT_FLAGS GetJitFlags();
 
     // This is only set for StructMarshal interop stubs.
     // See callsites for more details.
@@ -89,6 +88,9 @@ protected:
     //
     struct CompileTimeState
     {
+        CompileTimeState() = default;
+        ~CompileTimeState() = default;
+
         COR_ILMETHOD_DECODER   m_ILHeader;
         COR_ILMETHOD_SECT_EH * m_pEHSect;
         SigPointer             m_StubTargetMethodSig;
index 15365a9..6727949 100644 (file)
@@ -161,7 +161,7 @@ inline CORINFO_MODULE_HANDLE GetScopeHandle(MethodDesc* method)
 }
 
 //This is common refactored code from within several of the access check functions.
-BOOL ModifyCheckForDynamicMethod(DynamicResolver *pResolver,
+static BOOL ModifyCheckForDynamicMethod(DynamicResolver *pResolver,
                                  TypeHandle *pOwnerTypeForSecurity,
                                  AccessCheckOptions::AccessCheckType *pAccessCheckType,
                                  DynamicResolver** ppAccessContext)
@@ -201,6 +201,56 @@ BOOL ModifyCheckForDynamicMethod(DynamicResolver *pResolver,
     return doAccessCheck;
 }
 
+TransientMethodDetails::TransientMethodDetails(MethodDesc* pMD, _In_opt_ COR_ILMETHOD_DECODER* header, CORINFO_MODULE_HANDLE scope)
+    : Method{ pMD }
+    , Header{ header }
+    , Scope{ scope }
+{
+    LIMITED_METHOD_CONTRACT;
+    _ASSERTE(Method != NULL);
+    _ASSERTE(Scope == NULL || IsDynamicScope(Scope));
+}
+
+TransientMethodDetails::TransientMethodDetails(TransientMethodDetails&& other)
+{
+    LIMITED_METHOD_CONTRACT;
+    *this = std::move(other);
+}
+
+TransientMethodDetails::~TransientMethodDetails()
+{
+    CONTRACTL
+    {
+        NOTHROW;
+        GC_NOTRIGGER;
+        MODE_ANY;
+    }
+    CONTRACTL_END;
+
+    // If the supplied scope is dynamic, release resources.
+    if (IsDynamicScope(Scope))
+    {
+        DynamicResolver* resolver = GetDynamicResolver(Scope);
+        resolver->FreeCompileTimeState();
+        delete resolver;
+    }
+}
+
+TransientMethodDetails& TransientMethodDetails::operator=(TransientMethodDetails&& other)
+{
+    LIMITED_METHOD_CONTRACT;
+    if (this != &other)
+    {
+        Method = other.Method;
+        Header = other.Header;
+        Scope = other.Scope;
+        other.Method = NULL;
+        other.Header = NULL;
+        other.Scope = NULL;
+    }
+    return *this;
+}
+
 /*****************************************************************************/
 
 // Initialize from data we passed across to the JIT
@@ -382,6 +432,12 @@ inline static CorInfoType toJitType(TypeHandle typeHnd, CORINFO_CLASS_HANDLE *cl
     return CEEInfo::asCorInfoType(typeHnd.GetInternalCorElementType(), typeHnd, clsRet);
 }
 
+enum ConvToJitSigFlags : int
+{
+    CONV_TO_JITSIG_FLAGS_NONE       = 0x0,
+    CONV_TO_JITSIG_FLAGS_LOCALSIG   = 0x1,
+};
+
 //---------------------------------------------------------------------------------------
 //
 //@GENERICS:
@@ -396,9 +452,7 @@ inline static CorInfoType toJitType(TypeHandle typeHnd, CORINFO_CLASS_HANDLE *cl
 // localSig     - Is it a local variables declaration, or a method signature (with return type, etc).
 // contextType  - The type with any instantiaton information
 //
-//static
-void
-CEEInfo::ConvToJitSig(
+static void ConvToJitSig(
     PCCOR_SIGNATURE       pSig,
     DWORD                 cbSig,
     CORINFO_MODULE_HANDLE scopeHnd,
@@ -520,7 +574,7 @@ CEEInfo::ConvToJitSig(
     sigRet->flags = sigRetFlags;
 
     _ASSERTE(SigInfoFlagsAreValid(sigRet));
-} // CEEInfo::ConvToJitSig
+} // ConvToJitSig
 
 //---------------------------------------------------------------------------------------
 //
@@ -854,17 +908,6 @@ size_t CEEInfo::findNameOfToken (Module* module,
     return strlen (szFQName);
 }
 
-#ifdef HOST_WINDOWS
-/* static */
-uint32_t CEEInfo::ThreadLocalOffset(void* p)
-{
-    PTEB Teb = NtCurrentTeb();
-    uint8_t** pTls = (uint8_t**)Teb->ThreadLocalStoragePointer;
-    uint8_t* pOurTls = pTls[_tls_index];
-    return (uint32_t)((uint8_t*)p - pOurTls);
-}
-#endif // HOST_WINDOWS
-
 CorInfoHelpFunc CEEInfo::getLazyStringLiteralHelper(CORINFO_MODULE_HANDLE handle)
 {
     CONTRACTL {
@@ -1806,6 +1849,14 @@ uint32_t CEEInfo::getThreadLocalFieldInfo (CORINFO_FIELD_HANDLE  field, bool isG
 }
 
 /*********************************************************************/
+static uint32_t ThreadLocalOffset(void* p)
+{
+    PTEB Teb = NtCurrentTeb();
+    uint8_t** pTls = (uint8_t**)Teb->ThreadLocalStoragePointer;
+    uint8_t* pOurTls = pTls[_tls_index];
+    return (uint32_t)((uint8_t*)p - pOurTls);
+}
+
 void CEEInfo::getThreadLocalStaticBlocksInfo (CORINFO_THREAD_STATIC_BLOCKS_INFO* pInfo, bool isGCType)
 {
     CONTRACTL {
@@ -1822,13 +1873,13 @@ void CEEInfo::getThreadLocalStaticBlocksInfo (CORINFO_THREAD_STATIC_BLOCKS_INFO*
     pInfo->offsetOfThreadLocalStoragePointer = offsetof(_TEB, ThreadLocalStoragePointer);
     if (isGCType)
     {
-        pInfo->offsetOfThreadStaticBlocks = CEEInfo::ThreadLocalOffset(&t_GCThreadStaticBlocks);
-        pInfo->offsetOfMaxThreadStaticBlocks = CEEInfo::ThreadLocalOffset(&t_GCMaxThreadStaticBlocks);
+        pInfo->offsetOfThreadStaticBlocks = ThreadLocalOffset(&t_GCThreadStaticBlocks);
+        pInfo->offsetOfMaxThreadStaticBlocks = ThreadLocalOffset(&t_GCMaxThreadStaticBlocks);
     }
     else
     {
-        pInfo->offsetOfThreadStaticBlocks = CEEInfo::ThreadLocalOffset(&t_NonGCThreadStaticBlocks);
-        pInfo->offsetOfMaxThreadStaticBlocks = CEEInfo::ThreadLocalOffset(&t_NonGCMaxThreadStaticBlocks);
+        pInfo->offsetOfThreadStaticBlocks = ThreadLocalOffset(&t_NonGCThreadStaticBlocks);
+        pInfo->offsetOfMaxThreadStaticBlocks = ThreadLocalOffset(&t_NonGCMaxThreadStaticBlocks);
     }
 
     pInfo->offsetOfGCDataPointer = static_cast<uint32_t>(PtrArray::GetDataOffset());
@@ -1989,7 +2040,7 @@ CEEInfo::findCallSiteSig(
     SigTypeContext typeContext;
     GetTypeContext(context, &typeContext);
 
-    CEEInfo::ConvToJitSig(
+    ConvToJitSig(
         pSig,
         cbSig,
         scopeHnd,
@@ -2040,7 +2091,7 @@ CEEInfo::findSig(
     SigTypeContext typeContext;
     GetTypeContext(context, &typeContext);
 
-    CEEInfo::ConvToJitSig(
+    ConvToJitSig(
         pSig,
         cbSig,
         scopeHnd,
@@ -3371,6 +3422,38 @@ NoSpecialCase:
     }
 }
 
+void CEEInfo::AddTransientMethodDetails(TransientMethodDetails details)
+{
+    STANDARD_VM_CONTRACT;
+    _ASSERTE(details.Method != NULL);
+
+    if (m_transientDetails == NULL)
+        m_transientDetails = new SArray<TransientMethodDetails, FALSE>();
+    m_transientDetails->Append(std::move(details));
+}
+
+bool CEEInfo::FindTransientMethodDetails(MethodDesc* pMD, TransientMethodDetails** details)
+{
+    STANDARD_VM_CONTRACT;
+    _ASSERTE(pMD != NULL);
+    _ASSERTE(details != NULL);
+
+    if (m_transientDetails != NULL)
+    {
+        TransientMethodDetails* curr = m_transientDetails->GetElements();
+        TransientMethodDetails* end = curr + m_transientDetails->GetCount();
+        for (;curr != end; ++curr)
+        {
+            if (curr->Method == pMD)
+            {
+                *details = curr;
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
 /*********************************************************************/
 size_t CEEInfo::printClassName(CORINFO_CLASS_HANDLE cls, char* buffer, size_t bufferSize, size_t* pRequiredBufferSize)
 {
@@ -4901,6 +4984,61 @@ CorInfoIsAccessAllowedResult CEEInfo::canAccessClass(
     return isAccessAllowed;
 }
 
+//---------------------------------------------------------------------------------------
+// Given a method descriptor ftnHnd, extract signature information into sigInfo
+// Obtain (representative) instantiation information from ftnHnd's owner class
+//@GENERICSVER: added explicit owner parameter
+// Internal version without JIT-EE transition
+static void getMethodSigInternal(
+    CORINFO_METHOD_HANDLE ftnHnd,
+    CORINFO_SIG_INFO *    sigRet,
+    CORINFO_CLASS_HANDLE  owner,
+    SignatureKind signatureKind)
+{
+    STANDARD_VM_CONTRACT;
+
+    MethodDesc * ftn = GetMethod(ftnHnd);
+
+    PCCOR_SIGNATURE pSig = NULL;
+    DWORD           cbSig = 0;
+    ftn->GetSig(&pSig, &cbSig);
+
+    SigTypeContext context(ftn, (TypeHandle)owner);
+
+    // Type parameters in the signature are instantiated
+    // according to the class/method/array instantiation of ftnHnd and owner
+    ConvToJitSig(
+        pSig,
+        cbSig,
+        GetScopeHandle(ftn),
+        mdTokenNil,
+        &context,
+        CONV_TO_JITSIG_FLAGS_NONE,
+        sigRet);
+
+    //@GENERICS:
+    // Shared generic methods and shared methods on generic structs take an extra argument representing their instantiation
+    if (ftn->RequiresInstArg())
+    {
+        //
+        // If we are making a virtual call to an instance method on an interface, we need to lie to the JIT.
+        // The reason being that we already made sure target is always directly callable (through instantiation stubs),
+        // JIT should not generate shared generics aware call code and insert the secret argument again at the callsite.
+        // Otherwise we would end up with two secret generic dictionary arguments (since the stub also provides one).
+        //
+        BOOL isCallSiteThatGoesThroughInstantiatingStub =
+            (signatureKind == SK_VIRTUAL_CALLSITE &&
+            !ftn->IsStatic() &&
+            ftn->GetMethodTable()->IsInterface()) ||
+            signatureKind == SK_STATIC_VIRTUAL_CODEPOINTER_CALLSITE;
+        if (!isCallSiteThatGoesThroughInstantiatingStub)
+            sigRet->callConv = (CorInfoCallConv) (sigRet->callConv | CORINFO_CALLCONV_PARAMTYPE);
+    }
+
+    // We want the calling convention bit to be consistant with the method attribute bit
+    _ASSERTE( (IsMdStatic(ftn->GetAttrs()) == 0) == ((sigRet->callConv & CORINFO_CALLCONV_HASTHIS) != 0) );
+}
+
 /***********************************************************************/
 // return the address of a pointer to a callable stub that will do the
 // virtual or interface call
@@ -5341,8 +5479,9 @@ void CEEInfo::getCallInfo(
     pResult->hMethod = CORINFO_METHOD_HANDLE(pTargetMD);
 
     pResult->accessAllowed = CORINFO_ACCESS_ALLOWED;
-    if ((flags & CORINFO_CALLINFO_SECURITYCHECKS) &&
-        !((MethodDesc *)callerHandle)->IsILStub()) // IL stubs can access everything, don't bother doing access checks
+    MethodDesc* callerMethod = (MethodDesc*)callerHandle;
+    if ((flags & CORINFO_CALLINFO_SECURITYCHECKS)
+        && RequiresAccessCheck(pResolvedToken->tokenScope))
     {
         //Our type system doesn't always represent the target exactly with the MethodDesc.  In all cases,
         //carry around the parent MethodTable for both Caller and Callee.
@@ -5384,8 +5523,7 @@ void CEEInfo::getCallInfo(
                 // is not part of instantiation. We have to special case it.
                 pCalleeForSecurity = calleeTypeForSecurity.GetMethodTable()->GetParallelMethodDesc(pCalleeForSecurity);
             }
-            else
-            if (pResolvedToken->pMethodSpec != NULL)
+            else if (pResolvedToken->pMethodSpec != NULL)
             {
                 uint32_t nGenericMethodArgs = 0;
                 CQuickBytes qbGenericMethodArgs;
@@ -5418,8 +5556,7 @@ void CEEInfo::getCallInfo(
 
                 pCalleeForSecurity = MethodDesc::FindOrCreateAssociatedMethodDesc(pMD, calleeTypeForSecurity.GetMethodTable(), FALSE, Instantiation(genericMethodArgs, nGenericMethodArgs), FALSE);
             }
-            else
-            if (pResolvedToken->pTypeSpec != NULL)
+            else if (pResolvedToken->pTypeSpec != NULL)
             {
                 pCalleeForSecurity = MethodDesc::FindOrCreateAssociatedMethodDesc(pMD, calleeTypeForSecurity.GetMethodTable(), FALSE, Instantiation(), TRUE);
             }
@@ -6703,6 +6840,15 @@ mdToken FindGenericMethodArgTypeSpec(IMDInternalImport* pInternalImport)
     COMPlusThrowHR(COR_E_BADIMAGEFORMAT);
 }
 
+static void setILIntrinsicMethodInfo(CORINFO_METHOD_INFO* methInfo,uint8_t* ilcode, int ilsize, int maxstack)
+{
+    methInfo->ILCode = ilcode;
+    methInfo->ILCodeSize = ilsize;
+    methInfo->maxStack = maxstack;
+    methInfo->EHcount = 0;
+    methInfo->options = (CorInfoOptions)0;
+}
+
 /*********************************************************************
 
 IL is the most efficient and portable way to implement certain low level methods
@@ -7554,44 +7700,84 @@ bool getILIntrinsicImplementationForActivator(MethodDesc* ftn,
     return true;
 }
 
-void setILIntrinsicMethodInfo(CORINFO_METHOD_INFO* methInfo,uint8_t* ilcode, int ilsize, int maxstack)
-{
-    methInfo->ILCode = ilcode;
-    methInfo->ILCodeSize = ilsize;
-    methInfo->maxStack = maxstack;
-    methInfo->EHcount = 0;
-    methInfo->options = (CorInfoOptions)0;
-}
-
 //---------------------------------------------------------------------------------------
 //
-//static
-void
-getMethodInfoHelper(
-    MethodDesc *           ftn,
-    CORINFO_METHOD_HANDLE  ftnHnd,
-    COR_ILMETHOD_DECODER * header,
-    CORINFO_METHOD_INFO *  methInfo)
+
+class MethodInfoHelperContext final
+{
+public:
+    MethodDesc* Method;
+    COR_ILMETHOD_DECODER* Header;
+    DynamicResolver* TransientResolver;
+
+    MethodInfoHelperContext(MethodDesc* pMD, _In_opt_ COR_ILMETHOD_DECODER* header = NULL)
+        : Method{ pMD }
+        , Header{ header }
+        , TransientResolver{}
+    {
+        LIMITED_METHOD_CONTRACT;
+        _ASSERTE(pMD != NULL);
+    }
+
+    MethodInfoHelperContext(const TransientMethodDetails* details)
+        : Method{ details->Method }
+        , Header{ details->Header }
+        , TransientResolver{ NULL }
+    {
+        LIMITED_METHOD_CONTRACT;
+        if (IsDynamicScope(details->Scope))
+            TransientResolver = GetDynamicResolver(details->Scope);
+    }
+
+    MethodInfoHelperContext(const MethodInfoHelperContext&) = delete;
+    MethodInfoHelperContext(MethodInfoHelperContext&& other) = delete;
+
+    ~MethodInfoHelperContext() = default;
+
+    MethodInfoHelperContext& operator=(const MethodInfoHelperContext&) = delete;
+    MethodInfoHelperContext& operator=(MethodInfoHelperContext&& other) = delete;
+
+    bool HasTransientMethodDetails() const
+    {
+        return TransientResolver != NULL;
+    }
+
+    CORINFO_MODULE_HANDLE CreateScopeHandle() const
+    {
+        _ASSERTE(HasTransientMethodDetails());
+        return MakeDynamicScope(TransientResolver);
+    }
+
+    TransientMethodDetails CreateTransientMethodDetails() const
+    {
+        _ASSERTE(HasTransientMethodDetails());
+        return TransientMethodDetails{Method, Header, CreateScopeHandle() };
+    }
+};
+
+static void getMethodInfoHelper(
+    MethodInfoHelperContext& cxt,
+    CORINFO_METHOD_INFO* methInfo)
 {
     STANDARD_VM_CONTRACT;
+    _ASSERTE(methInfo != NULL);
 
-    _ASSERTE(ftn == GetMethod(ftnHnd));
+    MethodDesc* ftn      = cxt.Method;
+    methInfo->ftn        = (CORINFO_METHOD_HANDLE)ftn;
+    methInfo->regionKind = CORINFO_REGION_JIT;
 
-    methInfo->ftn             = ftnHnd;
-    methInfo->scope           = GetScopeHandle(ftn);
-    methInfo->regionKind      = CORINFO_REGION_JIT;
-    //
-    // For Jitted code the regionKind is JIT;
-    // For Ngen-ed code the zapper will set this to HOT or COLD, if we
-    // are using IBC data to partition methods into Hot/Cold regions
+    CORINFO_MODULE_HANDLE scopeHnd = NULL;
 
     /* Grab information from the IL header */
+    PCCOR_SIGNATURE pLocalSig   = NULL;
+    uint32_t        cbLocalSig  = 0;
 
-    PCCOR_SIGNATURE pLocalSig = NULL;
-    uint32_t        cbLocalSig = 0;
-
-    if (NULL != header)
+    // Having a header means there is backing IL for the method.
+    if (NULL != cxt.Header)
     {
+        scopeHnd = cxt.HasTransientMethodDetails()
+            ? cxt.CreateScopeHandle()
+            : GetScopeHandle(ftn);
         bool fILIntrinsic = false;
 
         MethodTable * pMT  = ftn->GetMethodTable();
@@ -7627,16 +7813,16 @@ getMethodInfoHelper(
 
         if (!fILIntrinsic)
         {
-            getMethodInfoILMethodHeaderHelper(header, methInfo);
-            pLocalSig = header->LocalVarSig;
-            cbLocalSig = header->cbLocalVarSig;
+            getMethodInfoILMethodHeaderHelper(cxt.Header, methInfo);
+            pLocalSig = cxt.Header->LocalVarSig;
+            cbLocalSig = cxt.Header->cbLocalVarSig;
         }
     }
-    else
+    else if (ftn->IsDynamicMethod())
     {
-        _ASSERTE(ftn->IsDynamicMethod());
+        DynamicResolver* pResolver = ftn->AsDynamicMethodDesc()->GetResolver();
+        scopeHnd = MakeDynamicScope(pResolver);
 
-        DynamicResolver * pResolver = ftn->AsDynamicMethodDesc()->GetResolver();
         unsigned int EHCount;
         methInfo->ILCode = pResolver->GetCodeInfo(&methInfo->ILCodeSize,
                                                   &methInfo->maxStack,
@@ -7646,7 +7832,21 @@ getMethodInfoHelper(
         SigPointer localSig = pResolver->GetLocalSig();
         localSig.GetSignature(&pLocalSig, &cbLocalSig);
     }
+    else
+    {
+        if (!ftn->TryGenerateUnsafeAccessor(&cxt.TransientResolver, &cxt.Header))
+            ThrowHR(COR_E_BADIMAGEFORMAT);
+
+        scopeHnd = cxt.CreateScopeHandle();
+
+        _ASSERTE(cxt.Header != NULL);
+        getMethodInfoILMethodHeaderHelper(cxt.Header, methInfo);
+        pLocalSig = cxt.Header->LocalVarSig;
+        cbLocalSig = cxt.Header->cbLocalVarSig;
+    }
 
+    _ASSERTE(scopeHnd != NULL);
+    methInfo->scope = scopeHnd;
     methInfo->options = (CorInfoOptions)(((UINT32)methInfo->options) |
                             ((ftn->AcquiresInstMethodTableFromThis() ? CORINFO_GENERICS_CTXT_FROM_THIS : 0) |
                              (ftn->RequiresInstMethodTableArg() ? CORINFO_GENERICS_CTXT_FROM_METHODTABLE : 0) |
@@ -7686,13 +7886,13 @@ getMethodInfoHelper(
     /* Fetch the method signature */
     // Type parameters in the signature should be instantiated according to the
     // class/method/array instantiation of ftnHnd
-    CEEInfo::ConvToJitSig(
+    ConvToJitSig(
         pSig,
         cbSig,
-        GetScopeHandle(ftn),
+        methInfo->scope,
         mdTokenNil,
         &context,
-        CEEInfo::CONV_TO_JITSIG_FLAGS_NONE,
+        CONV_TO_JITSIG_FLAGS_NONE,
         &methInfo->args);
 
     // Shared generic or static per-inst methods and shared methods on generic structs
@@ -7705,15 +7905,14 @@ getMethodInfoHelper(
     /* And its local variables */
     // Type parameters in the signature should be instantiated according to the
     // class/method/array instantiation of ftnHnd
-    CEEInfo::ConvToJitSig(
+    ConvToJitSig(
         pLocalSig,
         cbLocalSig,
-        GetScopeHandle(ftn),
+        methInfo->scope,
         mdTokenNil,
         &context,
-        CEEInfo::CONV_TO_JITSIG_FLAGS_LOCALSIG,
+        CONV_TO_JITSIG_FLAGS_LOCALSIG,
         &methInfo->locals);
-
 } // getMethodInfoHelper
 
 //---------------------------------------------------------------------------------------
@@ -7735,30 +7934,39 @@ CEEInfo::getMethodInfo(
 
     MethodDesc * ftn = GetMethod(ftnHnd);
 
-    if (!ftn->IsDynamicMethod() && (!ftn->IsIL() || !ftn->GetRVA() || ftn->IsWrapperStub()))
+    // Get the IL header
+    MethodInfoHelperContext cxt{ ftn };
+    if (ftn->IsDynamicMethod())
     {
-    /* Return false if not IL or has no code */
-        result = false;
+        getMethodInfoHelper(cxt, methInfo);
+        result = true;
     }
-    else
+    else if (!ftn->IsWrapperStub() && ftn->HasILHeader())
     {
-        /* Get the IL header */
-
-        if (ftn->IsDynamicMethod())
+        COR_ILMETHOD_DECODER header(ftn->GetILHeader(TRUE), ftn->GetMDImport(), NULL);
+        cxt.Header = &header;
+        getMethodInfoHelper(cxt, methInfo);
+        result = true;
+    }
+    else if (ftn->IsIL() && ftn->GetRVA() == 0)
+    {
+        // IL methods with no RVA indicate there is no implementation defined in metadata.
+        // Check if we previously generated details/implementation for this method.
+        TransientMethodDetails* detailsMaybe = NULL;
+        if (FindTransientMethodDetails(ftn, &detailsMaybe))
         {
-            getMethodInfoHelper(ftn, ftnHnd, NULL, methInfo);
+            cxt.Header = detailsMaybe->Header;
+            cxt.TransientResolver = GetDynamicResolver(detailsMaybe->Scope);
         }
-        else
-        {
-            COR_ILMETHOD_DECODER header(ftn->GetILHeader(TRUE), ftn->GetMDImport(), NULL);
 
-            getMethodInfoHelper(ftn, ftnHnd, &header, methInfo);
-        }
+        getMethodInfoHelper(cxt, methInfo);
+        result = true;
+    }
 
+    if (result)
+    {
         LOG((LF_JIT, LL_INFO100000, "Getting method info (possible inline) %s::%s%s\n",
             ftn->m_pszDebugClassName, ftn->m_pszDebugMethodName, ftn->m_pszDebugMethodSignature));
-
-        result = true;
     }
 
     EE_TO_JIT_TRANSITION();
@@ -8369,7 +8577,7 @@ void CEEInfo::reportTailCallDecision (CORINFO_METHOD_HANDLE callerHnd,
     EE_TO_JIT_TRANSITION();
 }
 
-void CEEInfo::getEHinfoHelper(
+static void getEHinfoHelper(
     CORINFO_METHOD_HANDLE   ftnHnd,
     unsigned                EHnumber,
     CORINFO_EH_CLAUSE*      clause,
@@ -8441,66 +8649,13 @@ CEEInfo::getMethodSig(
 
     JIT_TO_EE_TRANSITION();
 
-    getMethodSigInternal(ftnHnd, sigRet, owner);
+    getMethodSigInternal(ftnHnd, sigRet, owner, SK_NOT_CALLSITE);
 
     EE_TO_JIT_TRANSITION();
 }
 
 //---------------------------------------------------------------------------------------
 //
-void
-CEEInfo::getMethodSigInternal(
-    CORINFO_METHOD_HANDLE ftnHnd,
-    CORINFO_SIG_INFO *    sigRet,
-    CORINFO_CLASS_HANDLE  owner,
-    SignatureKind signatureKind)
-{
-    STANDARD_VM_CONTRACT;
-
-    MethodDesc * ftn = GetMethod(ftnHnd);
-
-    PCCOR_SIGNATURE pSig = NULL;
-    DWORD           cbSig = 0;
-    ftn->GetSig(&pSig, &cbSig);
-
-    SigTypeContext context(ftn, (TypeHandle)owner);
-
-    // Type parameters in the signature are instantiated
-    // according to the class/method/array instantiation of ftnHnd and owner
-    CEEInfo::ConvToJitSig(
-        pSig,
-        cbSig,
-        GetScopeHandle(ftn),
-        mdTokenNil,
-        &context,
-        CONV_TO_JITSIG_FLAGS_NONE,
-        sigRet);
-
-    //@GENERICS:
-    // Shared generic methods and shared methods on generic structs take an extra argument representing their instantiation
-    if (ftn->RequiresInstArg())
-    {
-        //
-        // If we are making a virtual call to an instance method on an interface, we need to lie to the JIT.
-        // The reason being that we already made sure target is always directly callable (through instantiation stubs),
-        // JIT should not generate shared generics aware call code and insert the secret argument again at the callsite.
-        // Otherwise we would end up with two secret generic dictionary arguments (since the stub also provides one).
-        //
-        BOOL isCallSiteThatGoesThroughInstantiatingStub =
-            (signatureKind == SK_VIRTUAL_CALLSITE &&
-            !ftn->IsStatic() &&
-            ftn->GetMethodTable()->IsInterface()) ||
-            signatureKind == SK_STATIC_VIRTUAL_CODEPOINTER_CALLSITE;
-        if (!isCallSiteThatGoesThroughInstantiatingStub)
-            sigRet->callConv = (CorInfoCallConv) (sigRet->callConv | CORINFO_CALLCONV_PARAMTYPE);
-    }
-
-    // We want the calling convention bit to be consistant with the method attribute bit
-    _ASSERTE( (IsMdStatic(ftn->GetAttrs()) == 0) == ((sigRet->callConv & CORINFO_CALLCONV_HASTHIS) != 0) );
-}
-
-//---------------------------------------------------------------------------------------
-//
 //@GENERICSVER: for a method desc in a typical instantiation of a generic class,
 // this will return the typical instantiation of the generic class,
 // but only provided type variables are never shared.
@@ -12700,12 +12855,15 @@ CORJIT_FLAGS GetDebuggerCompileFlags(Module* pModule, CORJIT_FLAGS flags)
     return flags;
 }
 
-CORJIT_FLAGS GetCompileFlags(MethodDesc * ftn, CORJIT_FLAGS flags, CORINFO_METHOD_INFO * methodInfo)
+static CORJIT_FLAGS GetCompileFlags(PrepareCodeConfig* prepareConfig, MethodDesc* ftn, CORINFO_METHOD_INFO* methodInfo)
 {
     STANDARD_VM_CONTRACT;
-
+    _ASSERTE(prepareConfig != NULL);
+    _ASSERTE(ftn != NULL);
     _ASSERTE(methodInfo->regionKind ==  CORINFO_REGION_JIT);
 
+    CORJIT_FLAGS flags = prepareConfig->GetJitCompilationFlags();
+
     //
     // Get the compile flags that are shared between JIT and NGen
     //
@@ -12767,10 +12925,14 @@ CORJIT_FLAGS GetCompileFlags(MethodDesc * ftn, CORJIT_FLAGS flags, CORINFO_METHO
         }
     }
 
-    if (ftn->IsILStub() && !g_pConfig->GetTrackDynamicMethodDebugInfo())
+    if (IsDynamicScope(methodInfo->scope))
     {
         // no debug info available for IL stubs
-        flags.Clear(CORJIT_FLAGS::CORJIT_FLAG_DEBUG_INFO);
+        if (!g_pConfig->GetTrackDynamicMethodDebugInfo())
+            flags.Clear(CORJIT_FLAGS::CORJIT_FLAG_DEBUG_INFO);
+
+        DynamicResolver* pResolver = GetDynamicResolver(methodInfo->scope);
+        flags.Add(pResolver->GetJitFlags());
     }
 
 #ifdef ARM_SOFTFP
@@ -12845,11 +13007,13 @@ BOOL g_fAllowRel32 = TRUE;
 // Calls to this method that occur to check if inlining can occur on x86,
 // are OK since they discard the return value of this method.
 PCODE UnsafeJitFunction(PrepareCodeConfig* config,
-                        COR_ILMETHOD_DECODER* ILHeader,
-                        CORJIT_FLAGS flags,
-                        ULONG * pSizeOfCode)
+                        _In_opt_ COR_ILMETHOD_DECODER* ILHeader,
+                        _In_ CORJIT_FLAGS* pJitFlags,
+                        _In_opt_ ULONG* pSizeOfCode)
 {
     STANDARD_VM_CONTRACT;
+    _ASSERTE(config != NULL);
+    _ASSERTE(pJitFlags != NULL);
 
     NativeCodeVersion nativeCodeVersion = config->GetCodeVersion();
     MethodDesc* ftn = nativeCodeVersion.GetMethodDesc();
@@ -12907,31 +13071,19 @@ PCODE UnsafeJitFunction(PrepareCodeConfig* config,
 
         LOG((LF_JIT, LL_INFO10000, "{ Jitting method (%p) %s %s\n", ftn, methodString.GetUTF8(), ftn->m_pszDebugMethodSignature));
     }
-
-#if 0
-    if (!SString::_stricmp(cls,"ENC") &&
-       (!SString::_stricmp(name,"G")))
-    {
-       static count = 0;
-       count++;
-       if (count > 0)
-            DebugBreak();
-    }
-#endif // 0
 #endif // _DEBUG
 
-    CORINFO_METHOD_HANDLE ftnHnd = (CORINFO_METHOD_HANDLE)ftn;
+    MethodInfoHelperContext cxt{ ftn, ILHeader };
     CORINFO_METHOD_INFO methodInfo;
+    getMethodInfoHelper(cxt, &methodInfo);
 
-    getMethodInfoHelper(ftn, ftnHnd, ILHeader, &methodInfo);
-
-    // If it's generic then we can only enter through an instantiated md
+    // If it's generic then we can only enter through an instantiated MethodDesc
     _ASSERTE(!ftn->IsGenericMethodDefinition());
 
     // method attributes and signature are consistant
     _ASSERTE(!!ftn->IsStatic() == ((methodInfo.args.callConv & CORINFO_CALLCONV_HASTHIS) == 0));
 
-    flags = GetCompileFlags(ftn, flags, &methodInfo);
+    *pJitFlags = GetCompileFlags(config, ftn, &methodInfo);
 
 #if defined(TARGET_AMD64) || defined(TARGET_ARM64)
     BOOL fForceJumpStubOverflow = FALSE;
@@ -12952,10 +13104,10 @@ PCODE UnsafeJitFunction(PrepareCodeConfig* config,
 
     while (true)
     {
-        CEEJitInfo jitInfo(ftn, ILHeader, jitMgr, !flags.IsSet(CORJIT_FLAGS::CORJIT_FLAG_NO_INLINING));
+        CEEJitInfo jitInfo(ftn, ILHeader, jitMgr, !pJitFlags->IsSet(CORJIT_FLAGS::CORJIT_FLAG_NO_INLINING));
 
-#if (defined(TARGET_AMD64) || defined(TARGET_ARM64))
-#ifdef TARGET_AMD64
+#if defined(TARGET_AMD64) || defined(TARGET_ARM64)
+#if defined(TARGET_AMD64)
         if (fForceJumpStubOverflow)
             jitInfo.SetJumpStubOverflow(fAllowRel32);
         jitInfo.SetAllowRel32(fAllowRel32);
@@ -12964,19 +13116,22 @@ PCODE UnsafeJitFunction(PrepareCodeConfig* config,
             jitInfo.SetJumpStubOverflow(fForceJumpStubOverflow);
 #endif
         jitInfo.SetReserveForJumpStubs(reserveForJumpStubs);
-#endif
+#endif // defined(TARGET_AMD64) || defined(TARGET_ARM64)
 
 #ifdef FEATURE_ON_STACK_REPLACEMENT
         // If this is an OSR jit request, grab the OSR info so we can pass it to the jit
-        if (flags.IsSet(CORJIT_FLAGS::CORJIT_FLAG_OSR))
+        if (pJitFlags->IsSet(CORJIT_FLAGS::CORJIT_FLAG_OSR))
         {
             unsigned ilOffset = 0;
             PatchpointInfo* patchpointInfo = nativeCodeVersion.GetOSRInfo(&ilOffset);
             jitInfo.SetOSRInfo(patchpointInfo, ilOffset);
         }
-#endif
+#endif // FEATURE_ON_STACK_REPLACEMENT
+
+        if (cxt.HasTransientMethodDetails())
+            jitInfo.AddTransientMethodDetails(cxt.CreateTransientMethodDetails());
 
-        MethodDesc * pMethodForSecurity = jitInfo.GetMethodForSecurity(ftnHnd);
+        MethodDesc * pMethodForSecurity = jitInfo.GetMethodForSecurity(methodInfo.ftn);
 
         //Since the check could trigger a demand, we have to do this every time.
         //This is actually an overly complicated way to make sure that a method can access all its arguments
@@ -13039,7 +13194,7 @@ PCODE UnsafeJitFunction(PrepareCodeConfig* config,
             res = invokeCompileMethod(jitMgr,
                                       &jitInfo,
                                       &methodInfo,
-                                      flags,
+                                      *pJitFlags,
                                       &nativeEntry,
                                       &sizeOfCode);
 
@@ -13082,7 +13237,7 @@ PCODE UnsafeJitFunction(PrepareCodeConfig* config,
         {
             jitInfo.WriteCode(jitMgr);
 #if defined(DEBUGGING_SUPPORTED)
-            if (!flags.IsSet(CORJIT_FLAGS::CORJIT_FLAG_MCJIT_BACKGROUND))
+            if (!pJitFlags->IsSet(CORJIT_FLAGS::CORJIT_FLAG_MCJIT_BACKGROUND))
             {
                 //
                 // Notify the debugger that we have successfully jitted the function
index 73778a1..862b363 100644 (file)
@@ -68,19 +68,8 @@ void InitJITHelpers2();
 
 PCODE UnsafeJitFunction(PrepareCodeConfig* config,
                         COR_ILMETHOD_DECODER* header,
-                        CORJIT_FLAGS flags,
-                        ULONG* sizeOfCode = NULL);
-
-void setILIntrinsicMethodInfo(CORINFO_METHOD_INFO* methInfo,
-                              uint8_t* ilcode,
-                              int ilsize,
-                              int maxstack);
-
-
-void getMethodInfoHelper(MethodDesc * ftn,
-                         CORINFO_METHOD_HANDLE ftnHnd,
-                         COR_ILMETHOD_DECODER * header,
-                         CORINFO_METHOD_INFO *  methInfo);
+                        CORJIT_FLAGS* pJitFlags,
+                        ULONG* pSizeOfCode);
 
 void getMethodInfoILMethodHeaderHelper(
     COR_ILMETHOD_DECODER* header,
@@ -406,9 +395,27 @@ extern "C"
 #endif // TARGET_ARM64
 };
 
-
 /*********************************************************************/
 /*********************************************************************/
+
+// Transient data for a MethodDesc involved
+// in the current JIT compilation.
+struct TransientMethodDetails final
+{
+    MethodDesc* Method;
+    COR_ILMETHOD_DECODER* Header;
+    CORINFO_MODULE_HANDLE Scope;
+
+    TransientMethodDetails() = default;
+    TransientMethodDetails(MethodDesc* pMD, _In_opt_ COR_ILMETHOD_DECODER* header, CORINFO_MODULE_HANDLE scope);
+    TransientMethodDetails(const TransientMethodDetails&) = delete;
+    TransientMethodDetails(TransientMethodDetails&&);
+    ~TransientMethodDetails();
+
+    TransientMethodDetails& operator=(const TransientMethodDetails&) = delete;
+    TransientMethodDetails& operator=(TransientMethodDetails&&);
+};
+
 class CEEInfo : public ICorJitInfo
 {
     friend class CEEDynamicCodeInfo;
@@ -439,23 +446,8 @@ public:
     static size_t findNameOfToken (Module* module, mdToken metaTOK,
                             _Out_writes_ (FQNameCapacity) char * szFQName, size_t FQNameCapacity);
 
-#ifdef HOST_WINDOWS
-    static uint32_t ThreadLocalOffset(void* p);
-#endif // HOST_WINDOWS
-
     DWORD getMethodAttribsInternal (CORINFO_METHOD_HANDLE ftnHnd);
 
-    // Given a method descriptor ftnHnd, extract signature information into sigInfo
-    // Obtain (representative) instantiation information from ftnHnd's owner class
-    //@GENERICSVER: added explicit owner parameter
-    // Internal version without JIT-EE transition
-    void getMethodSigInternal (
-            CORINFO_METHOD_HANDLE ftnHnd,
-            CORINFO_SIG_INFO* sigInfo,
-            CORINFO_CLASS_HANDLE owner = NULL,
-            SignatureKind signatureKind = SK_NOT_CALLSITE
-            );
-
     bool resolveVirtualMethodHelper(CORINFO_DEVIRTUALIZATION_INFO * info);
 
     CORINFO_CLASS_HANDLE getDefaultComparerClassHelper(
@@ -469,13 +461,6 @@ public:
     CorInfoType getFieldTypeInternal (CORINFO_FIELD_HANDLE field, CORINFO_CLASS_HANDLE* structType = NULL,CORINFO_CLASS_HANDLE owner = NULL);
 
 protected:
-
-    static void getEHinfoHelper(
-        CORINFO_METHOD_HANDLE   ftnHnd,
-        unsigned                EHnumber,
-        CORINFO_EH_CLAUSE*      clause,
-        COR_ILMETHOD_DECODER*   pILHeader);
-
     void freeArrayInternal(void* array);
 
 public:
@@ -497,6 +482,7 @@ public:
     CEEInfo(MethodDesc * fd = NULL, bool fAllowInlining = true) :
         m_pJitHandles(nullptr),
         m_pMethodBeingCompiled(fd),
+        m_transientDetails(NULL),
         m_pThread(GetThreadNULLOk()),
         m_hMethodForSecurity_Key(NULL),
         m_pMethodForSecurity_Value(NULL),
@@ -523,8 +509,9 @@ public:
                 DestroyHandle(elements[i]);
             }
             delete m_pJitHandles;
-            m_pJitHandles = nullptr;
         }
+
+        delete m_transientDetails;
 #endif
     }
 
@@ -550,27 +537,6 @@ private:
 #endif
 
 public:
-
-    enum ConvToJitSigFlags : int
-    {
-        CONV_TO_JITSIG_FLAGS_NONE                       = 0x0,
-        CONV_TO_JITSIG_FLAGS_LOCALSIG                   = 0x1,
-    };
-
-    //@GENERICS:
-    // The method handle is used to instantiate method and class type parameters
-    // It's also used to determine whether an extra dictionary parameter is required
-    static
-    void
-    ConvToJitSig(
-        PCCOR_SIGNATURE       pSig,
-        DWORD                 cbSig,
-        CORINFO_MODULE_HANDLE scopeHnd,
-        mdToken               token,
-        SigTypeContext*       context,
-        ConvToJitSigFlags     flags,
-        CORINFO_SIG_INFO *    sigRet);
-
     MethodDesc * GetMethodForSecurity(CORINFO_METHOD_HANDLE callerHandle);
 
     // Prepare the information about how to do a runtime lookup of the handle with shared
@@ -585,10 +551,15 @@ public:
     CalledMethod * GetCalledMethods() { return m_pCalledMethods; }
 #endif
 
+    // Add/Find transient method details.
+    void AddTransientMethodDetails(TransientMethodDetails details);
+    bool FindTransientMethodDetails(MethodDesc* pMD, TransientMethodDetails** details);
+
 protected:
-    SArray<OBJECTHANDLE>*   m_pJitHandles;                      // GC handles used by JIT
-    MethodDesc*             m_pMethodBeingCompiled;             // Top-level method being compiled
-    Thread *                m_pThread;                          // Cached current thread for faster JIT-EE transitions
+    SArray<OBJECTHANDLE>*   m_pJitHandles;          // GC handles used by JIT
+    MethodDesc*             m_pMethodBeingCompiled; // Top-level method being compiled
+    SArray<TransientMethodDetails, FALSE>* m_transientDetails;   // Transient details for dynamic codegen scenarios.
+    Thread *                m_pThread;              // Cached current thread for faster JIT-EE transitions
     CORJIT_FLAGS            m_jitFlags;
 
     CORINFO_METHOD_HANDLE getMethodBeingCompiled()
@@ -697,9 +668,7 @@ public:
         } CONTRACTL_END;
 
         if (m_CodeHeaderRW != m_CodeHeader)
-        {
-            delete [] (BYTE*)m_CodeHeaderRW;
-        }
+            freeArrayInternal(m_CodeHeaderRW);
 
         m_CodeHeader = NULL;
         m_CodeHeaderRW = NULL;
@@ -798,7 +767,7 @@ public:
         LIMITED_METHOD_CONTRACT;
         return 0;
     }
-#endif
+#endif // defined(TARGET_AMD64) || defined(TARGET_ARM64)
 
 #ifdef FEATURE_ON_STACK_REPLACEMENT
     // Called by the runtime to supply patchpoint information to the jit.
@@ -873,9 +842,7 @@ public:
         } CONTRACTL_END;
 
         if (m_CodeHeaderRW != m_CodeHeader)
-        {
-            delete [] (BYTE*)m_CodeHeaderRW;
-        }
+            freeArrayInternal(m_CodeHeaderRW);
 
         if (m_pOffsetMapping != NULL)
             freeArrayInternal(m_pOffsetMapping);
index 57c9072..1cb98fd 100644 (file)
@@ -97,7 +97,7 @@ void DECLSPEC_NORETURN MemberLoader::ThrowMissingMethodException(MethodTable* pM
     LPCUTF8 szClassName;
 
     DefineFullyQualifiedNameForClass();
-    if (pMT)
+    if (pMT != NULL)
     {
         szClassName = GetFullyQualifiedNameForClass(pMT);
     }
@@ -106,16 +106,21 @@ void DECLSPEC_NORETURN MemberLoader::ThrowMissingMethodException(MethodTable* pM
         szClassName = "?";
     };
 
+    if (szMember == NULL)
+        szMember = "?";
+
     if (pSig && cSig && pModule && pModule->IsFullModule())
     {
         MetaSig tmp(pSig, cSig, static_cast<Module*>(pModule), pTypeContext);
-        SigFormat sf(tmp, szMember ? szMember : "?", szClassName, NULL);
+        SigFormat sf(tmp, szMember, szClassName, NULL);
         MAKE_WIDEPTR_FROMUTF8(szwFullName, sf.GetCString());
         EX_THROW(EEMessageException, (kMissingMethodException, IDS_EE_MISSING_METHOD, szwFullName));
     }
     else
     {
-        EX_THROW(EEMessageException, (kMissingMethodException, IDS_EE_MISSING_METHOD, W("?")));
+        SString typeName;
+        typeName.Printf("%s.%s", szClassName, szMember);
+        EX_THROW(EEMessageException, (kMissingMethodException, IDS_EE_MISSING_METHOD, typeName.GetUnicode()));
     }
 }
 
index 4ad52f8..f705b1b 100644 (file)
@@ -62,10 +62,10 @@ class MemberLoader
 public:
     static void DECLSPEC_NORETURN ThrowMissingMethodException(MethodTable* pMT,
                                             LPCSTR szMember,
-                                            ModuleBase *pModule,
-                                            PCCOR_SIGNATURE pSig,
-                                            DWORD cSig,
-                                            const SigTypeContext *pTypeContext);
+                                            ModuleBase *pModule = NULL,
+                                            PCCOR_SIGNATURE pSig = NULL,
+                                            DWORD cSig = 0,
+                                            const SigTypeContext *pTypeContext = NULL);
 
     static void DECLSPEC_NORETURN ThrowMissingFieldException( MethodTable *pMT,
                                             LPCSTR szMember);
index 8b1d0dd..0278320 100644 (file)
@@ -1819,12 +1819,12 @@ private:
     PCODE GetPrecompiledCode(PrepareCodeConfig* pConfig, bool shouldTier);
     PCODE GetPrecompiledR2RCode(PrepareCodeConfig* pConfig);
     PCODE GetMulticoreJitCode(PrepareCodeConfig* pConfig, bool* pWasTier0);
-    COR_ILMETHOD_DECODER* GetAndVerifyILHeader(PrepareCodeConfig* pConfig, COR_ILMETHOD_DECODER* pIlDecoderMemory);
-    COR_ILMETHOD_DECODER* GetAndVerifyMetadataILHeader(PrepareCodeConfig* pConfig, COR_ILMETHOD_DECODER* pIlDecoderMemory);
-    COR_ILMETHOD_DECODER* GetAndVerifyNoMetadataILHeader();
     PCODE JitCompileCode(PrepareCodeConfig* pConfig);
     PCODE JitCompileCodeLockedEventWrapper(PrepareCodeConfig* pConfig, JitListLockEntry* pEntry);
-    PCODE JitCompileCodeLocked(PrepareCodeConfig* pConfig, JitListLockEntry* pLockEntry, ULONG* pSizeOfCode, CORJIT_FLAGS* pFlags);
+    PCODE JitCompileCodeLocked(PrepareCodeConfig* pConfig, JitListLockEntry* pLockEntry, ULONG* pSizeOfCode);
+
+public:
+    bool TryGenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMETHOD_DECODER** methodILDecoder);
 #endif // DACCESS_COMPILE
 
 #ifdef HAVE_GCCOVER
index 4087609..2a8d775 100644 (file)
@@ -1014,7 +1014,7 @@ MethodTableBuilder::bmtMDMethod::bmtMDMethod(
     DWORD dwDeclAttrs,
     DWORD dwImplAttrs,
     DWORD dwRVA,
-    METHOD_TYPE type,
+    MethodClassification type,
     METHOD_IMPL_TYPE implType)
     : m_pOwningType(pOwningType),
       m_dwDeclAttrs(dwDeclAttrs),
@@ -2711,7 +2711,7 @@ MethodTableBuilder::EnumerateClassMethods()
     {
         ULONG dwMethodRVA;
         DWORD dwImplFlags;
-        METHOD_TYPE type;
+        MethodClassification type;
         METHOD_IMPL_TYPE implType;
         LPSTR strMethodName;
 
@@ -3160,31 +3160,31 @@ MethodTableBuilder::EnumerateClassMethods()
                 {
                     // ComImport classes have methods which are just used
                     // for implementing all interfaces the class supports
-                    type = METHOD_TYPE_COMINTEROP;
+                    type = mcComInterop;
 
                     // constructor is special
                     if (IsMdRTSpecialName(dwMemberAttrs))
                     {
                         // Note: Method name (.ctor) will be checked in code:ValidateMethods
-                        type = METHOD_TYPE_FCALL;
+                        type = mcFCall;
                     }
                 }
                 else
 #endif //FEATURE_COMINTEROP
                 if (dwMethodRVA == 0)
                 {
-                    type = METHOD_TYPE_FCALL;
+                    type = mcFCall;
                 }
                 else
                 {
-                    type = METHOD_TYPE_NDIRECT;
+                    type = mcNDirect;
                 }
             }
             // The NAT_L attribute is present, marking this method as NDirect
             else
             {
                 CONSISTENCY_CHECK(hr == S_OK);
-                type = METHOD_TYPE_NDIRECT;
+                type = mcNDirect;
             }
         }
         else if (IsMiRuntime(dwImplFlags))
@@ -3204,7 +3204,7 @@ MethodTableBuilder::EnumerateClassMethods()
                     BuildMethodTableThrowException(BFA_BAD_FLAGS_ON_DELEGATE);
                 }
                 newDelegateMethodSeen = SeenCtor;
-                type = METHOD_TYPE_FCALL;
+                type = mcFCall;
             }
             else
             {
@@ -3218,7 +3218,7 @@ MethodTableBuilder::EnumerateClassMethods()
                 {
                     BuildMethodTableThrowException(BFA_UNKNOWN_DELEGATE_METHOD);
                 }
-                type = METHOD_TYPE_EEIMPL;
+                type = mcEEImpl;
             }
 
             // If we get here we have either set newDelegateMethodSeen or we have thrown a BMT exception
@@ -3234,7 +3234,7 @@ MethodTableBuilder::EnumerateClassMethods()
         else if (hasGenericMethodArgs)
         {
             //We use an instantiated method desc to represent a generic method
-            type = METHOD_TYPE_INSTANTIATED;
+            type = mcInstantiated;
         }
         else if (fIsClassInterface)
         {
@@ -3242,18 +3242,18 @@ MethodTableBuilder::EnumerateClassMethods()
             if (IsMdStatic(dwMemberAttrs))
             {
                 // Static methods in interfaces need nothing special.
-                type = METHOD_TYPE_NORMAL;
+                type = mcIL;
             }
             else if (bmtGenerics->GetNumGenericArgs() != 0 &&
                 (bmtGenerics->fSharedByGenericInstantiations))
             {
                 // Methods in instantiated interfaces need nothing special - they are not visible from COM etc.
-                type = METHOD_TYPE_NORMAL;
+                type = mcIL;
             }
             else if (bmtProp->fIsMngStandardItf)
             {
                 // If the interface is a standard managed interface then allocate space for an FCall method desc.
-                type = METHOD_TYPE_FCALL;
+                type = mcFCall;
             }
             else if (IsMdAbstract(dwMemberAttrs))
             {
@@ -3261,21 +3261,21 @@ MethodTableBuilder::EnumerateClassMethods()
                 // accessed via COM interop. mcComInterop MDs have an additional
                 // pointer-sized field pointing to COM interop data which are
                 // allocated lazily when/if the MD actually gets used for interop.
-                type = METHOD_TYPE_COMINTEROP;
+                type = mcComInterop;
             }
             else
 #endif // !FEATURE_COMINTEROP
             {
-                type = METHOD_TYPE_NORMAL;
+                type = mcIL;
             }
         }
         else
         {
-            type = METHOD_TYPE_NORMAL;
+            type = mcIL;
         }
 
-        // Generic methods should always be METHOD_TYPE_INSTANTIATED
-        if (hasGenericMethodArgs && (type != METHOD_TYPE_INSTANTIATED))
+        // Generic methods should always be mcInstantiated
+        if (hasGenericMethodArgs && (type != mcInstantiated))
         {
             BuildMethodTableThrowException(BFA_GENERIC_METHODS_INST);
         }
@@ -3752,7 +3752,7 @@ BOOL MethodTableBuilder::IsSelfReferencingStaticValueTypeField(mdToken     dwByV
     return MetaSig::CompareElementType(pFakeSig, pFieldSig,
                                        pFakeSig + cFakeSig,  pMemberSignature + cMemberSignature,
                                        GetModule(), GetModule(),
-                                       NULL, NULL, FALSE);
+                                       NULL, NULL);
 
 }
 
@@ -5015,23 +5015,6 @@ MethodTableBuilder::ValidateMethods()
     DeclaredMethodIterator it(*this);
     while (it.Next())
     {
-        // The RVA is only valid/testable if it has not been overwritten
-        // for something like edit-and-continue
-        // Complete validation of non-zero RVAs is done later inside MethodDesc::GetILHeader.
-        if ((it.RVA() == 0) && (pModule->GetDynamicIL(it.Token(), FALSE) == NULL))
-        {
-            // for IL code that is implemented here must have a valid code RVA
-            // this came up due to a linker bug where the ImplFlags/DescrOffset were
-            // being set to null and we weren't coping with it
-            if((IsMiIL(it.ImplFlags()) || IsMiOPTIL(it.ImplFlags())) &&
-                   !IsMdAbstract(it.Attrs()) &&
-                   !IsReallyMdPinvokeImpl(it.Attrs()) &&
-                !IsMiInternalCall(it.ImplFlags()))
-            {
-                BuildMethodTableThrowException(IDS_CLASSLOAD_MISSINGMETHODRVA, it.Token());
-            }
-        }
-
         if (IsMdRTSpecialName(it.Attrs()))
         {
             if (IsMdVirtual(it.Attrs()))
@@ -5079,7 +5062,7 @@ MethodTableBuilder::ValidateMethods()
         }
 
         // Make sure that fcalls have a 0 rva.  This is assumed by the prejit fixup logic
-        if (it.MethodType() == METHOD_TYPE_FCALL && it.RVA() != 0)
+        if (it.MethodType() == mcFCall && it.RVA() != 0)
         {
             BuildMethodTableThrowException(BFA_ECALLS_MUST_HAVE_ZERO_RVA, it.Token());
         }
@@ -5116,7 +5099,7 @@ MethodTableBuilder::ValidateMethods()
             {
                 BuildMethodTableThrowException(IDS_CLASSLOAD_BAD_UNMANAGED_RVA, it.Token());
             }
-            if (it.MethodType() != METHOD_TYPE_NDIRECT)
+            if (it.MethodType() != mcNDirect)
             {
                 BuildMethodTableThrowException(BFA_BAD_UNMANAGED_ENTRY_POINT);
             }
@@ -5124,7 +5107,7 @@ MethodTableBuilder::ValidateMethods()
 
         // Vararg methods are not allowed inside generic classes
         // and nor can they be generic methods.
-        if (bmtGenerics->GetNumGenericArgs() > 0 || (it.MethodType() == METHOD_TYPE_INSTANTIATED) )
+        if (bmtGenerics->GetNumGenericArgs() > 0 || (it.MethodType() == mcInstantiated) )
         {
             DWORD cMemberSignature;
             PCCOR_SIGNATURE pMemberSignature = it.GetSig(&cMemberSignature);
@@ -5180,7 +5163,7 @@ MethodTableBuilder::InitNewMethodDesc(
     //
     // First, set all flags that control layout of optional slots
     //
-    pNewMD->SetClassification(GetMethodClassification(pMethod->GetMethodType()));
+    pNewMD->SetClassification(pMethod->GetMethodType());
 
     if (pMethod->GetMethodImplType() == METHOD_IMPL)
         pNewMD->SetHasMethodImplSlot();
@@ -5220,7 +5203,7 @@ MethodTableBuilder::InitNewMethodDesc(
 
     // Do the init specific to each classification of MethodDesc & assign some common fields
     InitMethodDesc(pNewMD,
-                   GetMethodClassification(pMethod->GetMethodType()),
+                   pMethod->GetMethodType(),
                    pMethod->GetMethodSignature().GetToken(),
                    pMethod->GetImplAttrs(),
                    pMethod->GetDeclAttrs(),
@@ -5341,7 +5324,7 @@ MethodTableBuilder::PlaceNonVirtualMethods()
 #endif // _DEBUG
 
         if (!fCanHaveNonVtableSlots ||
-            it->GetMethodType() == METHOD_TYPE_INSTANTIATED)
+            it->GetMethodType() == mcInstantiated)
         {
             // We use slot during remoting and to map methods between generic instantiations
             // (see MethodTable::GetParallelMethodDesc). The current implementation
@@ -6990,7 +6973,7 @@ VOID MethodTableBuilder::AllocAndInitMethodDescs()
         // the code will still work with small performance penalty (method desc chunk layout will be less efficient).
         _ASSERTE(tokenRange >= currentTokenRange);
 
-        SIZE_T size = MethodDesc::GetBaseSize(GetMethodClassification(it->GetMethodType()));
+        SIZE_T size = MethodDesc::GetBaseSize(it->GetMethodType());
 
         // Add size of optional slots
 
@@ -7168,7 +7151,7 @@ MethodTableBuilder::NeedsTightlyBoundUnboxingStub(bmtMDMethod * pMDMethod)
     return IsValueClass() &&
            !IsMdStatic(pMDMethod->GetDeclAttrs()) &&
            IsMdVirtual(pMDMethod->GetDeclAttrs()) &&
-           (pMDMethod->GetMethodType() != METHOD_TYPE_INSTANTIATED) &&
+           (pMDMethod->GetMethodType() != mcInstantiated) &&
            !IsMdRTSpecialName(pMDMethod->GetDeclAttrs());
 }
 
@@ -7187,7 +7170,7 @@ MethodTableBuilder::NeedsNativeCodeSlot(bmtMDMethod * pMDMethod)
         // for tiering currently to avoid some unnecessary overhead
         (g_pConfig->TieredCompilation_QuickJit() || GetModule()->IsReadyToRun()) &&
 
-        (pMDMethod->GetMethodType() == METHOD_TYPE_NORMAL || pMDMethod->GetMethodType() == METHOD_TYPE_INSTANTIATED))
+        (pMDMethod->GetMethodType() == mcIL || pMDMethod->GetMethodType() == mcInstantiated))
 
 #ifdef FEATURE_REJIT
         ||
@@ -7195,7 +7178,7 @@ MethodTableBuilder::NeedsNativeCodeSlot(bmtMDMethod * pMDMethod)
         // Methods that are R2R need precode if ReJIT is enabled. Keep this in sync with MethodDesc::IsEligibleForReJIT()
         (ReJitManager::IsReJITEnabled() &&
 
-            GetMethodClassification(pMDMethod->GetMethodType()) == mcIL &&
+            pMDMethod->GetMethodType() == mcIL &&
 
             !GetModule()->IsCollectible() &&
 
index 34b53db..2988904 100644 (file)
@@ -118,20 +118,6 @@ private:
         METHOD_IMPL
     };
 
-    enum METHOD_TYPE
-    {
-        // The values of the enum are in sync with MethodClassification.
-        // GetMethodClassification depends on this
-        METHOD_TYPE_NORMAL  = 0,
-        METHOD_TYPE_FCALL   = 1,
-        METHOD_TYPE_NDIRECT = 2,
-        METHOD_TYPE_EEIMPL  = 3,
-        METHOD_TYPE_INSTANTIATED = 5,
-#ifdef FEATURE_COMINTEROP
-        METHOD_TYPE_COMINTEROP = 6,
-#endif
-    };
-
 private:
     // Determine if this is the special SIMD type System.Numerics.Vector<T>, and set its size.
     BOOL CheckIfSIMDAndUpdateSize();
@@ -920,14 +906,14 @@ private:
         // Constructor. This takes all the information already extracted from metadata interface
         // because the place that creates these types already has this data. Alternatively,
         // a constructor could be written to take a token and metadata scope instead. Also,
-        // it might be interesting to move METHOD_TYPE and METHOD_IMPL_TYPE to setter functions.
+        // it might be interesting to move MethodClassification and METHOD_IMPL_TYPE to setter functions.
         bmtMDMethod(
             bmtMDType * pOwningType,
             mdMethodDef tok,
             DWORD dwDeclAttrs,
             DWORD dwImplAttrs,
             DWORD dwRVA,
-            METHOD_TYPE type,
+            MethodClassification type,
             METHOD_IMPL_TYPE implType);
 
         //-----------------------------------------------------------------------------------------
@@ -956,7 +942,7 @@ private:
 
         //-----------------------------------------------------------------------------------------
         // Returns the method type (normal, fcall, etc.) that this type was constructed with.
-        METHOD_TYPE
+        MethodClassification
         GetMethodType() const
             { LIMITED_METHOD_CONTRACT; return m_type; }
 
@@ -1037,7 +1023,7 @@ private:
         DWORD             m_dwDeclAttrs;
         DWORD             m_dwImplAttrs;
         DWORD             m_dwRVA;
-        METHOD_TYPE       m_type;               // Specific MethodDesc flavour
+        MethodClassification  m_type;               // Specific MethodDesc flavour
         METHOD_IMPL_TYPE  m_implType;           // Whether or not the method is a methodImpl body
         MethodSignature   m_methodSig;
 
@@ -2279,7 +2265,7 @@ private:
         inline PCCOR_SIGNATURE  GetSig(DWORD *pcbSig);
         inline METHOD_IMPL_TYPE MethodImpl();
         inline BOOL             IsMethodImpl();
-        inline METHOD_TYPE      MethodType();
+        inline MethodClassification MethodType();
         inline bmtMDMethod     *GetMDMethod() const;
         inline MethodDesc      *GetIntroducingMethodDesc();
         inline bmtMDMethod *    operator->();
@@ -2633,11 +2619,6 @@ private:
         COMMA_INDEBUG(LPCUTF8             pszDebugMethodSignature));
 
     // --------------------------------------------------------------------------------------------
-    // Convert code:MethodTableBuilder::METHOD_TYPE to code:MethodClassification
-    static DWORD
-    GetMethodClassification(METHOD_TYPE type);
-
-    // --------------------------------------------------------------------------------------------
     // Essentially, this is a helper method that combines calls to InitMethodDesc and
     // SetSecurityFlagsOnMethod. It then assigns the newly initialized MethodDesc to
     // the bmtMDMethod.
index 78424be..6e55c06 100644 (file)
@@ -15,7 +15,7 @@
 
 //***************************************************************************************
 inline MethodTableBuilder::DeclaredMethodIterator::DeclaredMethodIterator(
-            MethodTableBuilder &mtb) : 
+            MethodTableBuilder &mtb) :
                 m_numDeclaredMethods((int)mtb.NumDeclaredMethods()),
                 m_declaredMethods(mtb.bmtMethod->m_rgDeclaredMethods),
                 m_idx(-1)
@@ -121,7 +121,7 @@ inline BOOL  MethodTableBuilder::DeclaredMethodIterator::IsMethodImpl()
 }
 
 //***************************************************************************************
-inline MethodTableBuilder::METHOD_TYPE MethodTableBuilder::DeclaredMethodIterator::MethodType()
+inline MethodClassification MethodTableBuilder::DeclaredMethodIterator::MethodType()
 {
     LIMITED_METHOD_CONTRACT;
     return GetMDMethod()->GetMethodType();
@@ -502,24 +502,5 @@ MethodTableBuilder::bmtMDMethod::SetUnboxedSlotIndex(SLOT_INDEX idx)
     CONSISTENCY_CHECK(m_pUnboxedMD == NULL);
     m_unboxedSlotIndex = idx;
 }
-
-//***************************************************************************************
-inline DWORD
-MethodTableBuilder::GetMethodClassification(MethodTableBuilder::METHOD_TYPE type)
-{
-    LIMITED_METHOD_CONTRACT;
-    // Verify that the enums are in sync, so we can do the conversion by simple cast.
-    C_ASSERT((DWORD)METHOD_TYPE_NORMAL       == (DWORD)mcIL);
-    C_ASSERT((DWORD)METHOD_TYPE_FCALL        == (DWORD)mcFCall);
-    C_ASSERT((DWORD)METHOD_TYPE_NDIRECT      == (DWORD)mcNDirect);
-    C_ASSERT((DWORD)METHOD_TYPE_EEIMPL       == (DWORD)mcEEImpl);
-    C_ASSERT((DWORD)METHOD_TYPE_INSTANTIATED == (DWORD)mcInstantiated);
-#ifdef FEATURE_COMINTEROP
-    C_ASSERT((DWORD)METHOD_TYPE_COMINTEROP   == (DWORD)mcComInterop);
-#endif
-
-    return (DWORD)type;
-}
-
 #endif  // _METHODTABLEBUILDER_INL_
 
index e555da0..a9b56f6 100644 (file)
@@ -42,6 +42,8 @@
 
 #ifndef DACCESS_COMPILE
 
+#include "customattribute.h"
+
 #if defined(FEATURE_JIT_PITCHING)
 EXTERN_C void CheckStacksAndPitch();
 EXTERN_C void SavePitchingCandidate(MethodDesc* pMD, ULONG sizeOfCode);
@@ -567,69 +569,6 @@ PCODE MethodDesc::GetMulticoreJitCode(PrepareCodeConfig* pConfig, bool* pWasTier
     return codeInfo.GetEntryPoint();
 }
 
-COR_ILMETHOD_DECODER* MethodDesc::GetAndVerifyMetadataILHeader(PrepareCodeConfig* pConfig, COR_ILMETHOD_DECODER* pDecoderMemory)
-{
-    STANDARD_VM_CONTRACT;
-
-    _ASSERTE(!IsNoMetadata());
-
-    COR_ILMETHOD_DECODER* pHeader = NULL;
-    COR_ILMETHOD* ilHeader = pConfig->GetILHeader();
-    if (ilHeader == NULL)
-    {
-        COMPlusThrowHR(COR_E_BADIMAGEFORMAT, BFA_BAD_IL);
-    }
-
-    COR_ILMETHOD_DECODER::DecoderStatus status = COR_ILMETHOD_DECODER::FORMAT_ERROR;
-    {
-        // Decoder ctor can AV on a malformed method header
-        AVInRuntimeImplOkayHolder AVOkay;
-        pHeader = new (pDecoderMemory) COR_ILMETHOD_DECODER(ilHeader, GetMDImport(), &status);
-    }
-
-    if (status == COR_ILMETHOD_DECODER::FORMAT_ERROR)
-    {
-        COMPlusThrowHR(COR_E_BADIMAGEFORMAT, BFA_BAD_IL);
-    }
-
-    return pHeader;
-}
-
-COR_ILMETHOD_DECODER* MethodDesc::GetAndVerifyNoMetadataILHeader()
-{
-    STANDARD_VM_CONTRACT;
-
-    if (IsILStub())
-    {
-        ILStubResolver* pResolver = AsDynamicMethodDesc()->GetILStubResolver();
-        return pResolver->GetILHeader();
-    }
-    else
-    {
-        return NULL;
-    }
-
-    // NoMetadata currently doesn't verify the IL. I'm not sure if that was
-    // a deliberate decision in the past or not, but I've left the behavior
-    // as-is during refactoring.
-}
-
-COR_ILMETHOD_DECODER* MethodDesc::GetAndVerifyILHeader(PrepareCodeConfig* pConfig, COR_ILMETHOD_DECODER* pIlDecoderMemory)
-{
-    STANDARD_VM_CONTRACT;
-    _ASSERTE(IsIL() || IsNoMetadata());
-
-    if (IsNoMetadata())
-    {
-        // The NoMetadata version already has a decoder to use, it doesn't need the stack allocated one
-        return GetAndVerifyNoMetadataILHeader();
-    }
-    else
-    {
-        return GetAndVerifyMetadataILHeader(pConfig, pIlDecoderMemory);
-    }
-}
-
 // ********************************************************************
 //                  README!!
 // ********************************************************************
@@ -646,9 +585,9 @@ PCODE MethodDesc::JitCompileCode(PrepareCodeConfig* pConfig)
     STANDARD_VM_CONTRACT;
 
     LOG((LF_JIT, LL_INFO1000000,
-        "JitCompileCode(" FMT_ADDR ", ILStub: %s) for %s::%s\n",
-        DBG_ADDR(this),
-        IsILStub() ? " TRUE" : "FALSE",
+        "JitCompileCode(%p, ILStub: %s) for %s::%s\n",
+        this,
+        IsILStub() ? "true" : "false",
         GetMethodTable()->GetDebugClassName(),
         m_pszDebugMethodName));
 
@@ -767,7 +706,6 @@ PCODE MethodDesc::JitCompileCodeLockedEventWrapper(PrepareCodeConfig* pConfig, J
 
     PCODE pCode = NULL;
     ULONG sizeOfCode = 0;
-    CORJIT_FLAGS flags;
 
 #ifdef PROFILING_SUPPORTED
     {
@@ -816,7 +754,7 @@ PCODE MethodDesc::JitCompileCodeLockedEventWrapper(PrepareCodeConfig* pConfig, J
         TRACE_LEVEL_VERBOSE,
         CLR_JIT_KEYWORD))
     {
-        pCode = JitCompileCodeLocked(pConfig, pEntry, &sizeOfCode, &flags);
+        pCode = JitCompileCodeLocked(pConfig, pEntry, &sizeOfCode);
     }
     else
     {
@@ -836,7 +774,7 @@ PCODE MethodDesc::JitCompileCodeLockedEventWrapper(PrepareCodeConfig* pConfig, J
             &methodSignature);
 #endif
 
-        pCode = JitCompileCodeLocked(pConfig, pEntry, &sizeOfCode, &flags);
+        pCode = JitCompileCodeLocked(pConfig, pEntry, &sizeOfCode);
 
         // Interpretted methods skip this notification
 #ifdef FEATURE_INTERPRETER
@@ -922,7 +860,54 @@ PCODE MethodDesc::JitCompileCodeLockedEventWrapper(PrepareCodeConfig* pConfig, J
     return pCode;
 }
 
-PCODE MethodDesc::JitCompileCodeLocked(PrepareCodeConfig* pConfig, JitListLockEntry* pEntry, ULONG* pSizeOfCode, CORJIT_FLAGS* pFlags)
+namespace
+{
+    COR_ILMETHOD_DECODER* GetAndVerifyMetadataILHeader(MethodDesc* pMD, PrepareCodeConfig* pConfig, COR_ILMETHOD_DECODER* pDecoderMemory)
+    {
+        STANDARD_VM_CONTRACT;
+        _ASSERTE(pMD != NULL);
+        _ASSERTE(!pMD->IsNoMetadata());
+        _ASSERTE(pConfig != NULL);
+        _ASSERTE(pDecoderMemory != NULL);
+
+        COR_ILMETHOD_DECODER* pHeader = NULL;
+        COR_ILMETHOD* ilHeader = pConfig->GetILHeader();
+        if (ilHeader == NULL)
+            return NULL;
+
+        COR_ILMETHOD_DECODER::DecoderStatus status = COR_ILMETHOD_DECODER::FORMAT_ERROR;
+        {
+            // Decoder ctor can AV on a malformed method header
+            AVInRuntimeImplOkayHolder AVOkay;
+            pHeader = new (pDecoderMemory) COR_ILMETHOD_DECODER(ilHeader, pMD->GetMDImport(), &status);
+        }
+
+        if (status == COR_ILMETHOD_DECODER::FORMAT_ERROR)
+            COMPlusThrowHR(COR_E_BADIMAGEFORMAT, BFA_BAD_IL);
+
+        return pHeader;
+    }
+
+    COR_ILMETHOD_DECODER* GetAndVerifyILHeader(MethodDesc* pMD, PrepareCodeConfig* pConfig, COR_ILMETHOD_DECODER* pIlDecoderMemory)
+    {
+        STANDARD_VM_CONTRACT;
+        _ASSERTE(pMD != NULL);
+        if (pMD->IsIL())
+        {
+            return GetAndVerifyMetadataILHeader(pMD, pConfig, pIlDecoderMemory);
+        }
+        else if (pMD->IsILStub())
+        {
+            ILStubResolver* pResolver = pMD->AsDynamicMethodDesc()->GetILStubResolver();
+            return pResolver->GetILHeader();
+        }
+
+        _ASSERTE(pMD->IsNoMetadata());
+        return NULL;
+    }
+}
+
+PCODE MethodDesc::JitCompileCodeLocked(PrepareCodeConfig* pConfig, JitListLockEntry* pEntry, ULONG* pSizeOfCode)
 {
     STANDARD_VM_CONTRACT;
 
@@ -933,15 +918,16 @@ PCODE MethodDesc::JitCompileCodeLocked(PrepareCodeConfig* pConfig, JitListLockEn
     //
     // (don't want this for OSR, need to see how it works)
     COR_ILMETHOD_DECODER ilDecoderTemp;
-    COR_ILMETHOD_DECODER *pilHeader = GetAndVerifyILHeader(pConfig, &ilDecoderTemp);
-    *pFlags = pConfig->GetJitCompilationFlags();
+    COR_ILMETHOD_DECODER* pilHeader = GetAndVerifyILHeader(this, pConfig, &ilDecoderTemp);
+
+    CORJIT_FLAGS jitFlags;
     PCODE pOtherCode = NULL;
 
     EX_TRY
     {
         Thread::CurrentPrepareCodeConfigHolder threadPrepareCodeConfigHolder(GetThread(), pConfig);
 
-        pCode = UnsafeJitFunction(pConfig, pilHeader, *pFlags, pSizeOfCode);
+        pCode = UnsafeJitFunction(pConfig, pilHeader, &jitFlags, pSizeOfCode);
     }
     EX_CATCH
     {
@@ -1000,7 +986,7 @@ PCODE MethodDesc::JitCompileCodeLocked(PrepareCodeConfig* pConfig, JitListLockEn
 
 #ifdef FEATURE_TIERED_COMPILATION
     // Finalize the optimization tier before SetNativeCode() is called
-    bool shouldCountCalls = pFlags->IsSet(CORJIT_FLAGS::CORJIT_FLAG_TIER0) && pConfig->FinalizeOptimizationTierForTier0LoadOrJit();
+    bool shouldCountCalls = jitFlags.IsSet(CORJIT_FLAGS::CORJIT_FLAG_TIER0) && pConfig->FinalizeOptimizationTierForTier0LoadOrJit();
 #endif
 
     // Aside from rejit, performing a SetNativeCodeInterlocked at this point
@@ -1059,7 +1045,488 @@ PCODE MethodDesc::JitCompileCodeLocked(PrepareCodeConfig* pConfig, JitListLockEn
     return pCode;
 }
 
+namespace
+{
+    enum class UnsafeAccessorKind
+    {
+        Constructor, // call instance constructor (`newobj` in IL)
+        Method, // call instance method (`callvirt` in IL)
+        StaticMethod, // call static method (`call` in IL)
+        Field, // address of instance field (`ldflda` in IL)
+        StaticField // address of static field (`ldsflda` in IL)
+    };
+
+    bool TryParseUnsafeAccessorAttribute(
+        MethodDesc* pMD,
+        CustomAttributeParser& ca,
+        UnsafeAccessorKind& kind,
+        SString& name)
+    {
+        STANDARD_VM_CONTRACT;
+        _ASSERTE(pMD != NULL);
+
+        // Get the kind of accessor
+        CaArg args[1];
+        args[0].InitEnum(SERIALIZATION_TYPE_I4, 0);
+        if (FAILED(::ParseKnownCaArgs(ca, args, ARRAY_SIZE(args))))
+            return false;
+
+        kind = (UnsafeAccessorKind)args[0].val.i4;
+
+        // Check the name of the target to access. This is the name we
+        // use to look up the intended token in metadata.
+        CaNamedArg namedArgs[1];
+        CaType namedArgTypes[1];
+        namedArgTypes[0].Init(SERIALIZATION_TYPE_STRING);
+        namedArgs[0].Init("Name", SERIALIZATION_TYPE_PROPERTY, namedArgTypes[0], NULL);
+        if (FAILED(::ParseKnownCaNamedArgs(ca, namedArgs, ARRAY_SIZE(namedArgs))))
+            return false;
+
+        // If the Name isn't defined, then use the name of the method.
+        if (namedArgs[0].val.type.tag == SERIALIZATION_TYPE_UNDEFINED)
+        {
+            // The Constructor case has an implied value provided by
+            // the runtime. We are going to enforce this during consumption
+            // so we avoid the setting of the value. We validate the name
+            // as empty at the use site.
+            if (kind != UnsafeAccessorKind::Constructor)
+                name.SetUTF8(pMD->GetName());
+        }
+        else
+        {
+            const CaValue& val = namedArgs[0].val;
+            name.SetUTF8(val.str.pStr, val.str.cbStr);
+        }
+
+        return true;
+    }
 
+    struct GenerationContext final
+    {
+        GenerationContext(UnsafeAccessorKind kind, MethodDesc* pMD)
+            : Kind{ kind }
+            , Declaration{ pMD }
+            , DeclarationSig{ pMD }
+            , TargetType{}
+            , IsTargetStatic{ false }
+            , TargetMethod{}
+            , TargetField{}
+        { }
+
+        UnsafeAccessorKind Kind;
+        MethodDesc* Declaration;
+        MetaSig DeclarationSig;
+        TypeHandle TargetType;
+        bool IsTargetStatic;
+        MethodDesc* TargetMethod;
+        FieldDesc* TargetField;
+    };
+
+    TypeHandle ValidateTargetType(TypeHandle targetTypeMaybe)
+    {
+        TypeHandle targetType = targetTypeMaybe.IsByRef()
+            ? targetTypeMaybe.GetTypeParam()
+            : targetTypeMaybe;
+
+        // Due to how some types degrade, we block on parameterized
+        // types that are represented as TypeDesc. For example ref or pointer.
+        if (targetType.IsTypeDesc())
+            ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSOR);
+
+        return targetType;
+    }
+
+    bool DoesMethodMatchUnsafeAccessorDeclaration(
+        GenerationContext& cxt,
+        MethodDesc* method,
+        MetaSig::CompareState& state)
+    {
+        STANDARD_VM_CONTRACT;
+        _ASSERTE(method != NULL);
+
+        PCCOR_SIGNATURE pSig1;
+        DWORD cSig1;
+        cxt.Declaration->GetSig(&pSig1, &cSig1);
+        PCCOR_SIGNATURE pEndSig1 = pSig1 + cSig1;
+        ModuleBase* pModule1 = cxt.Declaration->GetModule();
+        const Substitution* pSubst1 = NULL;
+
+        PCCOR_SIGNATURE pSig2;
+        DWORD cSig2;
+        method->GetSig(&pSig2, &cSig2);
+        PCCOR_SIGNATURE pEndSig2 = pSig2 + cSig2;
+        ModuleBase* pModule2 = method->GetModule();
+        const Substitution* pSubst2 = NULL;
+
+        // Validate calling convention
+        if ((*pSig1 & IMAGE_CEE_CS_CALLCONV_MASK) != (*pSig2 & IMAGE_CEE_CS_CALLCONV_MASK))
+        {
+            return false;
+        }
+
+        BYTE callConv = *pSig1;
+        pSig1++;
+        pSig2++;
+
+        // Generics are not supported
+        _ASSERTE((callConv & IMAGE_CEE_CS_CALLCONV_GENERIC) == 0);
+
+        DWORD declArgCount;
+        DWORD methodArgCount;
+        IfFailThrow(CorSigUncompressData_EndPtr(pSig1, pEndSig1, &declArgCount));
+        IfFailThrow(CorSigUncompressData_EndPtr(pSig2, pEndSig2, &methodArgCount));
+
+        // Validate argument count
+        if (cxt.Kind == UnsafeAccessorKind::Constructor)
+        {
+            // Declarations for constructor scenarios have
+            // matching argument counts with the target.
+            if (declArgCount != methodArgCount)
+                return false;
+        }
+        else
+        {
+            // Declarations of non-constructor scenarios have
+            // an additional argument to indicate target type
+            // and to pass an instance for non-static methods.
+            if (declArgCount != (methodArgCount + 1))
+                return false;
+        }
+
+        // Validate return and argument types
+        for (DWORD i = 0; i <= methodArgCount; ++i)
+        {
+            if (i == 0 && cxt.Kind == UnsafeAccessorKind::Constructor)
+            {
+                // Skip return value (index 0) validation on constructor
+                // accessor declarations.
+                SigPointer ptr1(pSig1, (DWORD)(pEndSig1 - pSig1));
+                IfFailThrow(ptr1.SkipExactlyOne());
+                pSig1 = ptr1.GetPtr();
+
+                CorElementType typ;
+                SigPointer ptr2(pSig2, (DWORD)(pEndSig2 - pSig2));
+                IfFailThrow(ptr2.GetElemType(&typ));
+                pSig2 = ptr2.GetPtr();
+
+                // Validate the return value for target constructor
+                // candidate is void.
+                if (typ != ELEMENT_TYPE_VOID)
+                    return false;
+
+                continue;
+            }
+            else if (i == 1 && cxt.Kind != UnsafeAccessorKind::Constructor)
+            {
+                // Skip over first argument (index 1) on non-constructor accessors.
+                // See argument count validation above.
+                SigPointer ptr1(pSig1, (DWORD)(pEndSig1 - pSig1));
+                IfFailThrow(ptr1.SkipExactlyOne());
+                pSig1 = ptr1.GetPtr();
+            }
+
+            // Compare the types
+            if (FALSE == MetaSig::CompareElementType(
+                pSig1,
+                pSig2,
+                pEndSig1,
+                pEndSig2,
+                pModule1,
+                pModule2,
+                pSubst1,
+                pSubst2,
+                &state))
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    bool TrySetTargetMethod(
+        GenerationContext& cxt,
+        LPCUTF8 methodName,
+        bool ignoreCustomModifiers = true)
+    {
+        STANDARD_VM_CONTRACT;
+        _ASSERTE(methodName != NULL);
+        _ASSERTE(cxt.Kind == UnsafeAccessorKind::Constructor
+                || cxt.Kind == UnsafeAccessorKind::Method
+                || cxt.Kind == UnsafeAccessorKind::StaticMethod);
+
+        TypeHandle targetType = cxt.TargetType;
+        _ASSERTE(!targetType.IsTypeDesc());
+
+        MethodDesc* targetMaybe = NULL;
+
+        // Following the iteration pattern found in MemberLoader::FindMethod().
+        // Reverse order is recommended - see comments in MemberLoader::FindMethod().
+        MethodTable::MethodIterator iter(targetType.AsMethodTable());
+        iter.MoveToEnd();
+        for (; iter.IsValid(); iter.Prev())
+        {
+            MethodDesc* curr = iter.GetDeclMethodDesc();
+
+            // Check the target and current method match static/instance state.
+            if (cxt.IsTargetStatic != (!!curr->IsStatic()))
+                continue;
+
+            // Check for matching name
+            if (strcmp(methodName, curr->GetNameThrowing()) != 0)
+                continue;
+
+            // Check signature
+            MetaSig::CompareState state{};
+            state.IgnoreCustomModifiers = ignoreCustomModifiers;
+            if (!DoesMethodMatchUnsafeAccessorDeclaration(cxt, curr, state))
+                continue;
+
+            // Check if there is some ambiguity.
+            if (targetMaybe != NULL)
+            {
+                if (ignoreCustomModifiers)
+                {
+                    // We have detected ambiguity when ignoring custom modifiers.
+                    // Start over, but look for a match requiring custom modifiers
+                    // to match precisely.
+                    if (TrySetTargetMethod(cxt, methodName, false /* ignoreCustomModifiers */))
+                        return true;
+                }
+                COMPlusThrow(kAmbiguousMatchException, W("Arg_AmbiguousMatchException_UnsafeAccessor"));
+            }
+            targetMaybe = curr;
+        }
+
+        cxt.TargetMethod = targetMaybe;
+        return cxt.TargetMethod != NULL;
+    }
+
+    bool TrySetTargetField(
+        GenerationContext& cxt,
+        LPCUTF8 fieldName,
+        TypeHandle fieldType)
+    {
+        STANDARD_VM_CONTRACT;
+        _ASSERTE(fieldName != NULL);
+        _ASSERTE(cxt.Kind == UnsafeAccessorKind::Field
+                || cxt.Kind == UnsafeAccessorKind::StaticField);
+
+        TypeHandle targetType = cxt.TargetType;
+        _ASSERTE(!targetType.IsTypeDesc());
+
+        ApproxFieldDescIterator fdIterator(
+            targetType.AsMethodTable(),
+            (cxt.IsTargetStatic ? ApproxFieldDescIterator::STATIC_FIELDS : ApproxFieldDescIterator::INSTANCE_FIELDS));
+        PTR_FieldDesc pField;
+        while ((pField = fdIterator.Next()) != NULL)
+        {
+            // Validate the name and target type match.
+            if (strcmp(fieldName, pField->GetName()) == 0
+                && fieldType == pField->LookupFieldTypeHandle())
+            {
+                cxt.TargetField = pField;
+                return true;
+            }
+        }
+        return false;
+    }
+
+    void GenerateAccessor(
+        GenerationContext& cxt,
+        DynamicResolver** resolver,
+        COR_ILMETHOD_DECODER** methodILDecoder)
+    {
+        STANDARD_VM_CONTRACT;
+
+        NewHolder<ILStubResolver> ilResolver = new ILStubResolver();
+
+        // Initialize the resolver target details.
+        ilResolver->SetStubMethodDesc(cxt.Declaration);
+        ilResolver->SetStubTargetMethodDesc(cxt.TargetMethod);
+
+        // [TODO] Handle generics
+        SigTypeContext emptyContext;
+        ILStubLinker sl(
+            cxt.Declaration->GetModule(),
+            cxt.Declaration->GetSignature(),
+            &emptyContext,
+            cxt.TargetMethod,
+            (ILStubLinkerFlags)ILSTUB_LINKER_FLAG_NONE);
+
+        ILCodeStream* pCode = sl.NewCodeStream(ILStubLinker::kDispatch);
+
+        // Load stub arguments.
+        // When the target is static, the first argument is only
+        // used to look up the target member to access and ignored
+        // during dispatch.
+        UINT beginIndex = cxt.IsTargetStatic ? 1 : 0;
+        UINT stubArgCount = cxt.DeclarationSig.NumFixedArgs();
+        for (UINT i = beginIndex; i < stubArgCount; ++i)
+            pCode->EmitLDARG(i);
+
+        // Provide access to the target member
+        UINT targetArgCount = stubArgCount - beginIndex;
+        UINT targetRetCount = cxt.DeclarationSig.IsReturnTypeVoid() ? 0 : 1;
+        switch (cxt.Kind)
+        {
+        case UnsafeAccessorKind::Constructor:
+            _ASSERTE(cxt.TargetMethod != NULL);
+            pCode->EmitNEWOBJ(pCode->GetToken(cxt.TargetMethod), targetArgCount);
+            break;
+        case UnsafeAccessorKind::Method:
+            _ASSERTE(cxt.TargetMethod != NULL);
+            pCode->EmitCALLVIRT(pCode->GetToken(cxt.TargetMethod), targetArgCount, targetRetCount);
+            break;
+        case UnsafeAccessorKind::StaticMethod:
+            _ASSERTE(cxt.TargetMethod != NULL);
+            pCode->EmitCALL(pCode->GetToken(cxt.TargetMethod), targetArgCount, targetRetCount);
+            break;
+        case UnsafeAccessorKind::Field:
+            _ASSERTE(cxt.TargetField != NULL);
+            pCode->EmitLDFLDA(pCode->GetToken(cxt.TargetField));
+            break;
+        case UnsafeAccessorKind::StaticField:
+            _ASSERTE(cxt.TargetField != NULL);
+            pCode->EmitLDSFLDA(pCode->GetToken(cxt.TargetField));
+            break;
+        default:
+            _ASSERTE(!"Unknown UnsafeAccessorKind");
+        }
+
+        // Return from the generated stub
+        pCode->EmitRET();
+
+        // Generate all IL associated data for JIT
+        {
+            UINT maxStack;
+            size_t cbCode = sl.Link(&maxStack);
+            DWORD cbSig = sl.GetLocalSigSize();
+
+            COR_ILMETHOD_DECODER* pILHeader = ilResolver->AllocGeneratedIL(cbCode, cbSig, maxStack);
+            BYTE* pbBuffer = (BYTE*)pILHeader->Code;
+            BYTE* pbLocalSig = (BYTE*)pILHeader->LocalVarSig;
+            _ASSERTE(cbSig == pILHeader->cbLocalVarSig);
+            sl.GenerateCode(pbBuffer, cbCode);
+            sl.GetLocalSig(pbLocalSig, cbSig);
+
+            // Store the token lookup map
+            ilResolver->SetTokenLookupMap(sl.GetTokenLookupMap());
+            ilResolver->SetJitFlags(CORJIT_FLAGS(CORJIT_FLAGS::CORJIT_FLAG_IL_STUB));
+
+            *resolver = (DynamicResolver*)ilResolver;
+            *methodILDecoder = pILHeader;
+        }
+
+        ilResolver.SuppressRelease();
+    }
+}
+
+bool MethodDesc::TryGenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMETHOD_DECODER** methodILDecoder)
+{
+    STANDARD_VM_CONTRACT;
+    _ASSERTE(resolver != NULL);
+    _ASSERTE(methodILDecoder != NULL);
+    _ASSERTE(*resolver == NULL && *methodILDecoder == NULL);
+    _ASSERTE(IsIL());
+    _ASSERTE(GetRVA() == 0);
+
+    // The UnsafeAccessorAttribute is applied to methods with an
+    // RVA of 0 (for example, C#'s extern keyword).
+    const void* data;
+    ULONG dataLen;
+    HRESULT hr = GetCustomAttribute(WellKnownAttribute::UnsafeAccessorAttribute, &data, &dataLen);
+    if (hr != S_OK)
+        return false;
+
+    UnsafeAccessorKind kind;
+    SString name;
+
+    CustomAttributeParser ca(data, dataLen);
+    if (!TryParseUnsafeAccessorAttribute(this, ca, kind, name))
+        ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSOR);
+
+    GenerationContext context{ kind, this };
+
+    // Parse the signature to determine the type to use:
+    //  * Constructor access - examine the return type
+    //  * Instance member access - examine type of first parameter
+    //  * Static member access - examine type of first parameter
+    TypeHandle retType;
+    TypeHandle firstArgType;
+    retType = context.DeclarationSig.GetRetTypeHandleThrowing();
+    UINT argCount = context.DeclarationSig.NumFixedArgs();
+    if (argCount > 0)
+    {
+        context.DeclarationSig.NextArg();
+        firstArgType = context.DeclarationSig.GetLastTypeHandleThrowing();
+    }
+
+    // Using the kind type, perform the following:
+    //  1) Validate the basic type information from the signature.
+    //  2) Resolve the name to the appropriate member.
+    switch (context.Kind)
+    {
+    case UnsafeAccessorKind::Constructor:
+        // A return type is required for a constructor, otherwise
+        // we don't know the type to construct.
+        // Types should not be parameterized (that is, byref).
+        // The name is defined by the runtime and should be empty.
+        if (context.DeclarationSig.IsReturnTypeVoid() || retType.IsByRef() || !name.IsEmpty())
+        {
+            ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSOR);
+        }
+
+        context.TargetType = ValidateTargetType(retType);
+        if (!TrySetTargetMethod(context, ".ctor"))
+            MemberLoader::ThrowMissingMethodException(context.TargetType.AsMethodTable(), ".ctor");
+        break;
+
+    case UnsafeAccessorKind::Method:
+    case UnsafeAccessorKind::StaticMethod:
+        // Method access requires a target type.
+        if (firstArgType.IsNull())
+            ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSOR);
+
+        context.TargetType = ValidateTargetType(firstArgType);
+        context.IsTargetStatic = kind == UnsafeAccessorKind::StaticMethod;
+        if (!TrySetTargetMethod(context, name.GetUTF8()))
+            MemberLoader::ThrowMissingMethodException(context.TargetType.AsMethodTable(), name.GetUTF8());
+        break;
+
+    case UnsafeAccessorKind::Field:
+    case UnsafeAccessorKind::StaticField:
+        // Field access requires a single argument for target type and a return type.
+        if (argCount != 1 || firstArgType.IsNull() || context.DeclarationSig.IsReturnTypeVoid())
+        {
+            ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSOR);
+        }
+
+        // The return type must be byref.
+        // If the non-static field access is for a
+        // value type, the instance must be byref.
+        if (!retType.IsByRef()
+            || (kind == UnsafeAccessorKind::Field
+                && firstArgType.IsValueType()
+                && !firstArgType.IsByRef()))
+        {
+            ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSOR);
+        }
+
+        context.TargetType = ValidateTargetType(firstArgType);
+        context.IsTargetStatic = kind == UnsafeAccessorKind::StaticField;
+        if (!TrySetTargetField(context, name.GetUTF8(), retType.GetTypeParam()))
+            MemberLoader::ThrowMissingFieldException(context.TargetType.AsMethodTable(), name.GetUTF8());
+        break;
+
+    default:
+        ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSOR);
+    }
+
+    // Generate the IL for the accessor.
+    GenerateAccessor(context, resolver, methodILDecoder);
+    return true;
+}
 
 PrepareCodeConfig::PrepareCodeConfig() {}
 
@@ -1161,11 +1628,6 @@ CORJIT_FLAGS PrepareCodeConfig::GetJitCompilationFlags()
     STANDARD_VM_CONTRACT;
 
     CORJIT_FLAGS flags;
-    if (m_pMethodDesc->IsILStub())
-    {
-        ILStubResolver* pResolver = m_pMethodDesc->AsDynamicMethodDesc()->GetILStubResolver();
-        flags = pResolver->GetJitFlags();
-    }
 #ifdef FEATURE_TIERED_COMPILATION
     flags.Add(TieredCompilationManager::GetJitFlags(this));
 #endif
@@ -2066,7 +2528,7 @@ PCODE MethodDesc::DoPrestub(MethodTable *pDispatchingMT, CallerGCMode callerGCMo
     }
 #endif // _DEBUG
 
-    STRESS_LOG1(LF_CLASSLOADER, LL_INFO10000, "Prestubworker: method %pM\n", this);
+    STRESS_LOG1(LF_CLASSLOADER, LL_INFO10000, "DoPrestub: method %p\n", this);
 
 
     GCStress<cfg_any, EeconfigFastGcSPolicy, CoopGcModePolicy>::MaybeTrigger();
index 27bfe45..b64bc86 100644 (file)
@@ -963,6 +963,30 @@ TypeHandle SigPointer::GetTypeHandleNT(Module* pModule,
 
 #endif // #ifndef DACCESS_COMPILE
 
+// Normalizing function pointer calling convention means
+// simply treating it as either "managed" or "unmanaged".
+static uint32_t NormalizeFnPtrCallingConvention(uint32_t callConv)
+{
+    LIMITED_METHOD_CONTRACT;
+
+    // Only have an unmanaged\managed status, and not the unmanaged CALLCONV_ value.
+    switch (callConv & IMAGE_CEE_CS_CALLCONV_MASK)
+    {
+    case IMAGE_CEE_CS_CALLCONV_C:
+    case IMAGE_CEE_CS_CALLCONV_STDCALL:
+    case IMAGE_CEE_CS_CALLCONV_THISCALL:
+    case IMAGE_CEE_CS_CALLCONV_FASTCALL:
+        // Strip the calling convention.
+        callConv &= ~IMAGE_CEE_CS_CALLCONV_MASK;
+        // Normalize to unmanaged.
+        callConv |= IMAGE_CEE_CS_CALLCONV_UNMANAGED;
+        break;
+    default:
+        break;
+    }
+
+    return callConv;
+}
 
 #ifdef _PREFAST_
 #pragma warning(push)
@@ -1016,7 +1040,6 @@ TypeHandle SigPointer::GetTypeHandleThrowing(
         // This may throw an exception using the FullModule
         _ASSERTE(pModule->IsFullModule());
     }
-    
 
     // We have an invariant that before we call a method, we must have loaded all of the valuetype parameters of that
     // method visible from the signature of the method. Normally we do this via type loading before the method is called
@@ -1467,7 +1490,7 @@ TypeHandle SigPointer::GetTypeHandleThrowing(
                                                             argLevel,
                                                             argDrop,
                                                             pSubst,
-                                                            pZapSigContext, 
+                                                            pZapSigContext,
                                                             NULL,
                                                             pRecursiveFieldGenericHandling);
                         if (typeHnd.IsNull())
@@ -1534,7 +1557,7 @@ TypeHandle SigPointer::GetTypeHandleThrowing(
                         else
                         {
                             // At this point thFound is the instantiation over Byte and thRet is set to the instantiation over __Canon.
-                            // If the two have the same GC layout, then the field layout is not affected by the type parameters, and the type load can continue 
+                            // If the two have the same GC layout, then the field layout is not affected by the type parameters, and the type load can continue
                             // with just using the __Canon variant.
                             // To simplify the calculation, all we really need to compute is the number of GC pointers in the representation and the Base size.
                             // For if the type parameter is used in field layout, there will be at least 1 more pointer in the __Canon instantiation as compared to the Byte instantiation.
@@ -1548,7 +1571,7 @@ TypeHandle SigPointer::GetTypeHandleThrowing(
 #ifndef DACCESS_COMPILE
                                 failedLayoutCompare = CGCDesc::GetNumPointers(thRet.AsMethodTable(), objectSizeCanonInstantiation, 0) !=
                                                       CGCDesc::GetNumPointers(thFound.AsMethodTable(), objectSizeCanonInstantiation, 0);
-#else  
+#else
                                 DacNotImpl();
 #endif
                             }
@@ -1557,7 +1580,7 @@ TypeHandle SigPointer::GetTypeHandleThrowing(
                             {
 #ifndef DACCESS_COMPILE
                                 static_cast<Module*>(pOrigModule)->ThrowTypeLoadException(pOrigModule->GetMDImport(), pRecursiveFieldGenericHandling->tkTypeDefToAvoidIfPossible, IDS_INVALID_RECURSIVE_GENERIC_FIELD_LOAD);
-#else  
+#else
                                 DacNotImpl();
 #endif
                             }
@@ -1740,7 +1763,7 @@ TypeHandle SigPointer::GetTypeHandleThrowing(
             TypeHandle *retAndArgTypes = (TypeHandle*) _alloca(cAllocaSize);
             bool fReturnTypeOrParameterNotLoaded = false;
 
-            for (unsigned i = 0; i <= cArgs; i++)
+            for (uint32_t i = 0; i <= cArgs; i++)
             {
                 // Lookup type handle.
                 retAndArgTypes[i] = psig.GetTypeHandleThrowing(pOrigModule,
@@ -1766,21 +1789,10 @@ TypeHandle SigPointer::GetTypeHandleThrowing(
                 break;
             }
 
-            // Only have an unmanaged\managed status, and not the unmanaged CALLCONV_ value.
-            switch (uCallConv & IMAGE_CEE_CS_CALLCONV_MASK)
-            {
-                case IMAGE_CEE_CS_CALLCONV_C:
-                case IMAGE_CEE_CS_CALLCONV_STDCALL:
-                case IMAGE_CEE_CS_CALLCONV_THISCALL:
-                case IMAGE_CEE_CS_CALLCONV_FASTCALL:
-                    // Strip the calling convention.
-                    uCallConv &= ~IMAGE_CEE_CS_CALLCONV_MASK;
-                    // Normalize to unmanaged.
-                    uCallConv |= IMAGE_CEE_CS_CALLCONV_UNMANAGED;
-            }
+            uCallConv = NormalizeFnPtrCallingConvention(uCallConv);
 
             // Find an existing function pointer or make a new one
-            thRet = ClassLoader::LoadFnptrTypeThrowing((BYTE) uCallConv, cArgs, retAndArgTypes, fLoadTypes, level);                
+            thRet = ClassLoader::LoadFnptrTypeThrowing((BYTE) uCallConv, cArgs, retAndArgTypes, fLoadTypes, level);
 #else
             // Function pointers are interpreted as IntPtr to the debugger.
             thRet = TypeHandle(CoreLibBinder::GetElementType(ELEMENT_TYPE_I));
@@ -3595,6 +3607,30 @@ ErrExit:
 #endif //!DACCESS_COMPILE
 } // CompareTypeTokens
 
+static void ConsumeCustomModifiers(PCCOR_SIGNATURE& pSig, PCCOR_SIGNATURE pEndSig)
+{
+    mdToken tk;
+    CorElementType type;
+
+    PCCOR_SIGNATURE pSigTmp = pSig;
+    for (;;)
+    {
+        type = ELEMENT_TYPE_MAX;
+        IfFailThrow(CorSigUncompressElementType_EndPtr(pSigTmp, pEndSig, &type));
+
+        switch (type)
+        {
+        case ELEMENT_TYPE_CMOD_REQD:
+        case ELEMENT_TYPE_CMOD_OPT:
+            IfFailThrow(CorSigUncompressToken_EndPtr(pSigTmp, pEndSig, &tk));
+            pSig = pSigTmp;
+            break;
+        default:
+            return;
+        }
+    }
+}
+
 #ifdef _PREFAST_
 #pragma warning(push)
 #pragma warning(disable:21000) // Suppress PREFast warning about overly large function
@@ -3615,7 +3651,7 @@ MetaSig::CompareElementType(
     ModuleBase *         pModule2,
     const Substitution * pSubst1,
     const Substitution * pSubst2,
-    TokenPairList *      pVisited) // = NULL
+    CompareState *       state) // = NULL
 {
     CONTRACTL
     {
@@ -3626,6 +3662,10 @@ MetaSig::CompareElementType(
     }
     CONTRACTL_END
 
+    CompareState temp{};
+    if (state == NULL)
+        state = &temp;
+
  redo:
     // We jump here if the Type was a ET_CMOD prefix.
     // The caller expects us to handle CMOD's but not present them as types on their own.
@@ -3659,7 +3699,7 @@ MetaSig::CompareElementType(
             pSubst2->GetModule(),
             pSubst1,
             pSubst2->GetNext(),
-            pVisited);
+            state);
     }
 
     if ((*pSig1 == ELEMENT_TYPE_VAR) && (pSubst1 != NULL) && !pSubst1->GetInst().IsNull())
@@ -3686,7 +3726,14 @@ MetaSig::CompareElementType(
             pModule2,
             pSubst1->GetNext(),
             pSubst2,
-            pVisited);
+            state);
+    }
+
+    // Consume custom modifiers if they are being ignored.
+    if (state->IgnoreCustomModifiers)
+    {
+        ConsumeCustomModifiers(pSig1, pEndSig1);
+        ConsumeCustomModifiers(pSig2, pEndSig2);
     }
 
     CorElementType Type1 = ELEMENT_TYPE_MAX; // initialize to illegal
@@ -3782,10 +3829,8 @@ MetaSig::CompareElementType(
                 }
             }
         }
-        else
-        {
-            return FALSE; // types must be the same
-        }
+
+        return FALSE; // types must be the same
     }
 
     switch (Type1)
@@ -3845,7 +3890,7 @@ MetaSig::CompareElementType(
                     pModule2,
                     tk2,
                     pSubst2,
-                    pVisited))
+                    state->Visited))
             {
                 return FALSE;
             }
@@ -3868,7 +3913,7 @@ MetaSig::CompareElementType(
                     pModule2,
                     pSubst1,
                     pSubst2,
-                    pVisited))
+                    state))
             {
                 return FALSE;
             }
@@ -3883,7 +3928,7 @@ MetaSig::CompareElementType(
             IfFailThrow(CorSigUncompressToken_EndPtr(pSig1, pEndSig1, &tk1));
             IfFailThrow(CorSigUncompressToken_EndPtr(pSig2, pEndSig2, &tk2));
 
-            return CompareTypeTokens(tk1, tk2, pModule1, pModule2, pVisited);
+            return CompareTypeTokens(tk1, tk2, pModule1, pModule2, state->Visited);
         }
 
         case ELEMENT_TYPE_FNPTR:
@@ -3895,10 +3940,17 @@ MetaSig::CompareElementType(
             IfFailThrow(CorSigUncompressElementType_EndPtr(pSig1, pEndSig1, &callingConvention1));
             CorElementType callingConvention2 = ELEMENT_TYPE_MAX; // initialize to illegal
             IfFailThrow(CorSigUncompressElementType_EndPtr(pSig2, pEndSig2, &callingConvention2));
-            if (callingConvention1 != callingConvention2)
-            {
+
+            // Calling conventions are generally treated as custom modifiers.
+            // When callers request custom modifiers to be ignored, we also ignore
+            // specific unmanaged calling conventions and this is okay. It is okay
+            // because unmanaged calling conventions, when more than one is defined
+            // (for example, SuppressGCTransition), all become encoded as custom modifiers.
+            bool callConvMismatch = state->IgnoreCustomModifiers
+                ? NormalizeFnPtrCallingConvention(callingConvention1) != NormalizeFnPtrCallingConvention(callingConvention2)
+                : callingConvention1 != callingConvention2;
+            if (callConvMismatch)
                 return FALSE;
-            }
 
             DWORD argCnt1;
             IfFailThrow(CorSigUncompressData_EndPtr(pSig1, pEndSig1, &argCnt1));
@@ -3914,7 +3966,9 @@ MetaSig::CompareElementType(
             // Add return parameter into the parameter count (it cannot overflow)
             argCnt1++;
 
-            TokenPairList newVisited = TokenPairList::AdjustForTypeEquivalenceForbiddenScope(pVisited);
+            TokenPairList newVisited = TokenPairList::AdjustForTypeEquivalenceForbiddenScope(state->Visited);
+            state->Visited = &newVisited;
+
             // Compare all parameters, incl. return parameter
             while (argCnt1 > 0)
             {
@@ -3927,7 +3981,7 @@ MetaSig::CompareElementType(
                         pModule2,
                         pSubst1,
                         pSubst2,
-                        &newVisited))
+                        state))
                 {
                     return FALSE;
                 }
@@ -3939,11 +3993,12 @@ MetaSig::CompareElementType(
         case ELEMENT_TYPE_GENERICINST:
         {
             TokenPairList newVisited = TokenPairList::AdjustForTypeSpec(
-                pVisited,
+                state->Visited,
                 pModule1,
                 pSig1 - 1,
                 (DWORD)(pEndSig1 - pSig1) + 1);
-            TokenPairList newVisitedAlwaysForbidden = TokenPairList::AdjustForTypeEquivalenceForbiddenScope(pVisited);
+            TokenPairList newVisitedAlwaysForbidden = TokenPairList::AdjustForTypeEquivalenceForbiddenScope(state->Visited);
+            state->Visited = &newVisitedAlwaysForbidden;
 
             // Type constructors - The actual type is never permitted to participate in type equivalence.
             if (!CompareElementType(
@@ -3955,7 +4010,7 @@ MetaSig::CompareElementType(
                     pModule2,
                     pSubst1,
                     pSubst2,
-                    &newVisitedAlwaysForbidden))
+                    state))
             {
                 return FALSE;
             }
@@ -3969,6 +4024,7 @@ MetaSig::CompareElementType(
                 return FALSE;
             }
 
+            state->Visited = &newVisited;
             while (argCnt1 > 0)
             {
                 if (!CompareElementType(
@@ -3980,7 +4036,7 @@ MetaSig::CompareElementType(
                         pModule2,
                         pSubst1,
                         pSubst2,
-                        &newVisited))
+                        state))
                 {
                     return FALSE;
                 }
@@ -4008,7 +4064,7 @@ MetaSig::CompareElementType(
                     pModule2,
                     pSubst1,
                     pSubst2,
-                    pVisited))
+                    state))
             {
                 return FALSE;
             }
@@ -4146,6 +4202,8 @@ MetaSig::CompareTypeDefsUnderSubstitutions(
 
     SigPointer inst1 = pSubst1->GetInst();
     SigPointer inst2 = pSubst2->GetInst();
+
+    CompareState state{ pVisited };
     for (DWORD i = 0; i < pTypeDef1->GetNumGenericArgs(); i++)
     {
         PCCOR_SIGNATURE startInst1 = inst1.GetPtr();
@@ -4163,7 +4221,7 @@ MetaSig::CompareTypeDefsUnderSubstitutions(
                 pSubst2->GetModule(),
                 pSubst1->GetNext(),
                 pSubst2->GetNext(),
-                pVisited))
+                &state))
         {
             return FALSE;
         }
@@ -4372,6 +4430,7 @@ MetaSig::CompareMethodSigs(
         // to correctly handle overloads, where there are a number of varargs methods
         // to pick from, like m1(int,...) and m2(int,int,...), etc.
 
+        CompareState state{ pVisited };
         // <= because we want to include a check of the return value!
         for (i = 0; i <= ArgCount1; i++)
         {
@@ -4412,7 +4471,7 @@ MetaSig::CompareMethodSigs(
                     pModule2,
                     pSubst1,
                     pSubst2,
-                    pVisited))
+                    &state))
                 {
                     return FALSE;
                 }
@@ -4427,6 +4486,7 @@ MetaSig::CompareMethodSigs(
     }
 
     // do return type as well
+    CompareState state{ pVisited };
     for (i = 0; i <= ArgCount1; i++)
     {
         if (i == 0 && skipReturnTypeSig)
@@ -4450,7 +4510,7 @@ MetaSig::CompareMethodSigs(
                 pModule2,
                 pSubst1,
                 pSubst2,
-                pVisited))
+                &state))
             {
                 return FALSE;
             }
@@ -4491,7 +4551,8 @@ BOOL MetaSig::CompareFieldSigs(
     pEndSig1 = pSig1 + cSig1;
     pEndSig2 = pSig2 + cSig2;
 
-    return(CompareElementType(++pSig1, ++pSig2, pEndSig1, pEndSig2, pModule1, pModule2, NULL, NULL, pVisited));
+    CompareState state{ pVisited };
+    return(CompareElementType(++pSig1, ++pSig2, pEndSig1, pEndSig2, pModule1, pModule2, NULL, NULL, &state));
 }
 
 #ifndef DACCESS_COMPILE
@@ -4733,7 +4794,8 @@ BOOL MetaSig::CompareTypeDefOrRefOrSpec(ModuleBase *pModule1, mdToken tok1,
     ULONG cSig1,cSig2;
     IfFailThrow(pInternalImport1->GetTypeSpecFromToken(tok1, &pSig1, &cSig1));
     IfFailThrow(pInternalImport2->GetTypeSpecFromToken(tok2, &pSig2, &cSig2));
-    return MetaSig::CompareElementType(pSig1, pSig2, pSig1 + cSig1, pSig2 + cSig2, pModule1, pModule2, pSubst1, pSubst2, pVisited);
+    CompareState state{ pVisited };
+    return MetaSig::CompareElementType(pSig1, pSig2, pSig1 + cSig1, pSig2 + cSig2, pModule1, pModule2, pSubst1, pSubst2, &state);
 } // MetaSig::CompareTypeDefOrRefOrSpec
 
 /* static */
index 13f3fb4..7ea7ada 100644 (file)
@@ -602,7 +602,7 @@ class MetaSig
 
         //------------------------------------------------------------------------
         // Returns # of arguments. Does not count the return value.
-        // Does not count the "this" argument (which is not reflected om the
+        // Does not count the "this" argument (which is not reflected on the
         // sig.) 64-bit arguments are counted as one argument.
         //------------------------------------------------------------------------
         UINT NumFixedArgs()
@@ -759,8 +759,8 @@ class MetaSig
         }
 
         //------------------------------------------------------------------
-        // Like NextArg, but return only normalized type (enums flattned to
-        // underlying type ...
+        // Like NextArg, but return only normalized type (enums flattened to
+        // the underlying type ...
         //------------------------------------------------------------------
         CorElementType
         NextArgNormalized(TypeHandle * pthValueType = NULL)
@@ -944,6 +944,26 @@ class MetaSig
         //------------------------------------------------------------------
         CorElementType GetByRefType(TypeHandle* pTy) const;
 
+        // Struct used to capture in/out state during the comparison
+        // of element types.
+        struct CompareState
+        {
+            // List of tokens that are currently being compared.
+            // See TokenPairList for more use details.
+            TokenPairList*  Visited;
+
+            // Boolean indicating if custom modifiers should
+            // be compared.
+            bool IgnoreCustomModifiers;
+
+            CompareState() = default;
+
+            CompareState(TokenPairList* list)
+                : Visited{ list }
+                , IgnoreCustomModifiers{ false }
+            { }
+        };
+
         //------------------------------------------------------------------
         // Compare types in two signatures, first applying
         // - optional substitutions pSubst1 and pSubst2
@@ -958,7 +978,7 @@ class MetaSig
             ModuleBase *         pModule2,
             const Substitution * pSubst1,
             const Substitution * pSubst2,
-            TokenPairList *      pVisited = NULL);
+            CompareState *       state = NULL);
 
 
 
index 3528b06..d96b377 100644 (file)
@@ -2587,7 +2587,7 @@ void ILStubLinker::GetStubReturnType(LocalDesc* pLoc, Module* pModule)
 
     IfFailThrow(ptr.GetData(&nArgs));
 
-    GetManagedTypeHelper(pLoc, pModule, ptr.GetPtr(), m_pTypeContext, m_pMD);
+    GetManagedTypeHelper(pLoc, pModule, ptr.GetPtr(), m_pTypeContext);
 }
 
 CorCallingConvention ILStubLinker::GetStubTargetCallingConv()
@@ -2897,7 +2897,7 @@ static size_t GetManagedTypeForMDArray(LocalDesc* pLoc, Module* pModule, PCCOR_S
 
 
 // static
-void ILStubLinker::GetManagedTypeHelper(LocalDesc* pLoc, Module* pModule, PCCOR_SIGNATURE psigManagedArg, SigTypeContext *pTypeContext, MethodDesc *pMD)
+void ILStubLinker::GetManagedTypeHelper(LocalDesc* pLoc, Module* pModule, PCCOR_SIGNATURE psigManagedArg, SigTypeContext *pTypeContext)
 {
     CONTRACTL
     {
@@ -3046,7 +3046,7 @@ void ILStubLinker::GetStubTargetReturnType(LocalDesc* pLoc, Module* pModule)
     }
     CONTRACTL_END;
 
-    GetManagedTypeHelper(pLoc, pModule, m_nativeFnSigBuilder.GetReturnSig(), m_pTypeContext, NULL);
+    GetManagedTypeHelper(pLoc, pModule, m_nativeFnSigBuilder.GetReturnSig(), m_pTypeContext);
 }
 
 void ILStubLinker::GetStubArgType(LocalDesc* pLoc)
@@ -3073,7 +3073,7 @@ void ILStubLinker::GetStubArgType(LocalDesc* pLoc, Module* pModule)
     }
     CONTRACTL_END;
 
-    GetManagedTypeHelper(pLoc, pModule, m_managedSigPtr.GetPtr(), m_pTypeContext, m_pMD);
+    GetManagedTypeHelper(pLoc, pModule, m_managedSigPtr.GetPtr(), m_pTypeContext);
 }
 
 //---------------------------------------------------------------------------------------
index 257bd24..5c83f23 100644 (file)
@@ -524,7 +524,7 @@ public:
 
     CorElementType GetStubTargetReturnElementType() { WRAPPER_NO_CONTRACT; return m_nativeFnSigBuilder.GetReturnElementType(); }
 
-    static void GetManagedTypeHelper(LocalDesc* pLoc, Module* pModule, PCCOR_SIGNATURE pSig, SigTypeContext *pTypeContext, MethodDesc *pMD);
+    static void GetManagedTypeHelper(LocalDesc* pLoc, Module* pModule, PCCOR_SIGNATURE pSig, SigTypeContext *pTypeContext);
 
     BOOL StubHasVoidReturnType();
 
index 2a11b63..ee75722 100644 (file)
@@ -37,13 +37,13 @@ void LogTraceDestination(const char * szHint, PCODE stubAddr, TraceDestination *
     if (pTrace->GetTraceType() == TRACE_UNJITTED_METHOD)
     {
         MethodDesc * md = pTrace->GetMethodDesc();
-        LOG((LF_CORDB, LL_INFO10000, "'%s' yields '%s' to method 0x%p for input 0x%p.\n",
+        LOG((LF_CORDB, LL_INFO10000, "'%s' yields '%s' to method %p for input %p.\n",
             szHint, GetTType(pTrace->GetTraceType()),
             md, stubAddr));
     }
     else
     {
-        LOG((LF_CORDB, LL_INFO10000, "'%s' yields '%s' to address 0x%p for input 0x%p.\n",
+        LOG((LF_CORDB, LL_INFO10000, "'%s' yields '%s' to address %p for input %p.\n",
             szHint, GetTType(pTrace->GetTraceType()),
             pTrace->GetAddress(), stubAddr));
     }
@@ -81,40 +81,40 @@ const CHAR * TraceDestination::DbgToString(SString & buffer)
         switch(this->type)
         {
             case TRACE_ENTRY_STUB:
-                buffer.Printf("TRACE_ENTRY_STUB(addr=0x%p)", GetAddress());
+                buffer.Printf("TRACE_ENTRY_STUB(addr=%p)", GetAddress());
                 pValue = buffer.GetUTF8();
                 break;
 
             case TRACE_STUB:
-                buffer.Printf("TRACE_STUB(addr=0x%p)", GetAddress());
+                buffer.Printf("TRACE_STUB(addr=%p)", GetAddress());
                 pValue = buffer.GetUTF8();
                 break;
 
             case TRACE_UNMANAGED:
-                buffer.Printf("TRACE_UNMANAGED(addr=0x%p)", GetAddress());
+                buffer.Printf("TRACE_UNMANAGED(addr=%p)", GetAddress());
                 pValue = buffer.GetUTF8();
                 break;
 
             case TRACE_MANAGED:
-                buffer.Printf("TRACE_MANAGED(addr=0x%p)", GetAddress());
+                buffer.Printf("TRACE_MANAGED(addr=%p)", GetAddress());
                 pValue = buffer.GetUTF8();
                 break;
 
             case TRACE_UNJITTED_METHOD:
             {
                 MethodDesc * md = this->GetMethodDesc();
-                buffer.Printf("TRACE_UNJITTED_METHOD(md=0x%p, %s::%s)", md, md->m_pszDebugClassName, md->m_pszDebugMethodName);
+                buffer.Printf("TRACE_UNJITTED_METHOD(md=%p, %s::%s)", md, md->m_pszDebugClassName, md->m_pszDebugMethodName);
                 pValue = buffer.GetUTF8();
             }
                 break;
 
             case TRACE_FRAME_PUSH:
-                buffer.Printf("TRACE_FRAME_PUSH(addr=0x%p)", GetAddress());
+                buffer.Printf("TRACE_FRAME_PUSH(addr=%p)", GetAddress());
                 pValue = buffer.GetUTF8();
                 break;
 
             case TRACE_MGR_PUSH:
-                buffer.Printf("TRACE_MGR_PUSH(addr=0x%p, sm=%s)", GetAddress(), this->GetStubManager()->DbgGetName());
+                buffer.Printf("TRACE_MGR_PUSH(addr=%p, sm=%s)", GetAddress(), this->GetStubManager()->DbgGetName());
                 pValue = buffer.GetUTF8();
                 break;
 
@@ -471,7 +471,7 @@ BOOL StubManager::CheckIsStub_Worker(PCODE stubStartAddress)
     EX_CATCH
 #endif
     {
-        LOG((LF_CORDB, LL_INFO10000, "D::GASTSI: exception indicated addr is bad.\n"));
+        LOG((LF_CORDB, LL_INFO10000, "SM::CISWorker: exception indicated addr is bad.\n"));
 
         param.fIsStub = FALSE;
     }
@@ -505,10 +505,15 @@ PTR_StubManager StubManager::FindStubManager(PCODE stubAddress)
         if (it.Current()->CheckIsStub_Worker(stubAddress))
         {
             _ASSERTE_IMPL(IsSingleOwner(stubAddress, it.Current()));
+
+            LOG((LF_CORDB, LL_INFO10000, "SM::FSM: %p claims %p\n",
+                it.Current(), stubAddress));
             return it.Current();
         }
     }
 
+    LOG((LF_CORDB, LL_INFO10000, "SM::FSM: no stub manager claims %p\n",
+        stubAddress));
     return NULL;
 }
 
@@ -524,19 +529,19 @@ BOOL StubManager::TraceStub(PCODE stubStartAddress, TraceDestination *trace)
     while (it.Next())
     {
         StubManager * pCurrent = it.Current();
-        if (pCurrent->CheckIsStub_Worker(stubStartAddress))
-        {
-            LOG((LF_CORDB, LL_INFO10000,
-                 "StubManager::TraceStub: addr 0x%p claimed by mgr "
-                 "0x%p.\n", stubStartAddress, pCurrent));
+        if (!pCurrent->CheckIsStub_Worker(stubStartAddress))
+            continue;
+
+        LOG((LF_CORDB, LL_INFO10000,
+                "StubManager::TraceStub: '%s' (%p) claimed %p.\n", pCurrent->DbgGetName(), pCurrent, stubStartAddress));
 
-            _ASSERTE_IMPL(IsSingleOwner(stubStartAddress, pCurrent));
+        _ASSERTE_IMPL(IsSingleOwner(stubStartAddress, pCurrent));
 
-            BOOL fValid = pCurrent->DoTraceStub(stubStartAddress, trace);
+        BOOL fValid = pCurrent->DoTraceStub(stubStartAddress, trace);
 #ifdef _DEBUG
-            if (IsStubLoggingEnabled())
-            {
-            DbgWriteLog("Doing TraceStub for Address 0x%p, claimed by '%s' (0x%p)\n", stubStartAddress, pCurrent->DbgGetName(), pCurrent);
+        if (IsStubLoggingEnabled())
+        {
+            DbgWriteLog("Doing TraceStub for %p, claimed by '%s' (%p)\n", stubStartAddress, pCurrent->DbgGetName(), pCurrent);
             if (fValid)
             {
                 SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE;
@@ -547,35 +552,32 @@ BOOL StubManager::TraceStub(PCODE stubStartAddress, TraceDestination *trace)
             else
             {
                 DbgWriteLog("  stubmanager returned false. Does not expect to call managed code\n");
-
             }
-            } // logging
+        } // logging
 #endif
-            return fValid;
-        }
+        return fValid;
     }
 
     if (ExecutionManager::IsManagedCode(stubStartAddress))
     {
+        LOG((LF_CORDB, LL_INFO10000,
+             "StubManager::TraceStub: addr %p is managed code\n",
+             stubStartAddress));
+
         trace->InitForManaged(stubStartAddress);
 
 #ifdef _DEBUG
-        DbgWriteLog("Doing TraceStub for Address 0x%p is jitted code claimed by codemanager\n", stubStartAddress);
+        DbgWriteLog("Doing TraceStub for Address %p is jitted code claimed by codemanager\n", stubStartAddress);
 #endif
-
-        LOG((LF_CORDB, LL_INFO10000,
-             "StubManager::TraceStub: addr 0x%p is managed code\n",
-             stubStartAddress));
-
         return TRUE;
     }
 
     LOG((LF_CORDB, LL_INFO10000,
-         "StubManager::TraceStub: addr 0x%p unknown. TRACE_OTHER...\n",
+         "StubManager::TraceStub: addr %p unknown. TRACE_OTHER...\n",
          stubStartAddress));
 
 #ifdef _DEBUG
-    DbgWriteLog("Doing TraceStub for Address 0x%p is unknown!!!\n", stubStartAddress);
+    DbgWriteLog("Doing TraceStub for Address %p is unknown!!!\n", stubStartAddress);
 #endif
 
     trace->InitForOther(stubStartAddress);
@@ -593,7 +595,7 @@ BOOL StubManager::FollowTrace(TraceDestination *trace)
     while (trace->GetTraceType() == TRACE_STUB)
     {
         LOG((LF_CORDB, LL_INFO10000,
-             "StubManager::FollowTrace: TRACE_STUB for 0x%p\n",
+             "StubManager::FollowTrace: TRACE_STUB for %p\n",
              trace->GetAddress()));
 
         if (!TraceStub(trace->GetAddress(), trace))
@@ -963,7 +965,6 @@ BOOL ThePreStubManager::CheckIsStub_Internal(PCODE stubStartAddress)
 {
     LIMITED_METHOD_DAC_CONTRACT;
     return stubStartAddress == GetPreStubEntryPoint();
-
 }
 
 
@@ -1175,13 +1176,13 @@ BOOL StubLinkStubManager::DoTraceStub(PCODE stubStartAddress,
     CONTRACTL_END
 
     LOG((LF_CORDB, LL_INFO10000,
-         "StubLinkStubManager::DoTraceStub: stubStartAddress=0x%p\n",
+         "StubLinkStubManager::DoTraceStub: stubStartAddress=%p\n",
          stubStartAddress));
 
     Stub *stub = Stub::RecoverStub(stubStartAddress);
 
     LOG((LF_CORDB, LL_INFO10000,
-         "StubLinkStubManager::DoTraceStub: stub=0x%p\n", stub));
+         "StubLinkStubManager::DoTraceStub: stub=%p\n", stub));
 
     //
     // If this is an intercept stub, we may be able to step
@@ -1265,7 +1266,7 @@ BOOL StubLinkStubManager::TraceManager(Thread *thread,
 
     LPVOID pc = (LPVOID)GetIP(pContext);
     *pRetAddr = (BYTE *)StubManagerHelpers::GetReturnAddress(pContext);
-    LOG((LF_CORDB,LL_INFO10000, "SLSM:TM 0x%p, retAddr is 0x%p\n", pc, (*pRetAddr)));
+    LOG((LF_CORDB,LL_INFO10000, "SLSM:TM %p, retAddr is %p\n", pc, (*pRetAddr)));
 
     Stub *stub = Stub::RecoverStub((PCODE)pc);
     if (stub->IsInstantiatingStub())
@@ -1277,7 +1278,7 @@ BOOL StubLinkStubManager::TraceManager(Thread *thread,
         PCODE target = GetStubTarget(pMD);
         if (target == NULL)
         {
-            LOG((LF_CORDB,LL_INFO10000, "SLSM:TM Unable to determine stub target, fd 0x%p\n", pMD));
+            LOG((LF_CORDB,LL_INFO10000, "SLSM:TM Unable to determine stub target, pMD %p\n", pMD));
             trace->InitForUnjittedMethod(pMD);
             return TRUE;
         }
@@ -2046,7 +2047,7 @@ BOOL DelegateInvokeStubManager::CheckIsStub_Internal(PCODE stubStartAddress)
 
     fIsStub = fIsStub || GetRangeList()->IsInRange(stubStartAddress);
 
-    return fIsStub;
+    return fIsStub ? TRUE : FALSE;
 }
 
 BOOL DelegateInvokeStubManager::DoTraceStub(PCODE stubStartAddress, TraceDestination *trace)
index 38251e8..037bc10 100644 (file)
@@ -486,8 +486,6 @@ public:
     // PTR
     BOOL IsPointer() const;
 
-    BOOL IsUnmanagedFunctionPointer() const;
-
     // True if this type *is* a formal generic type parameter or any component of it is a formal generic type parameter
     BOOL ContainsGenericVariables(BOOL methodOnly=FALSE) const;
 
index 837f75c..85a747b 100644 (file)
@@ -37,6 +37,7 @@ enum class WellKnownAttribute : DWORD
     PreserveBaseOverridesAttribute,
     ObjectiveCTrackedTypeAttribute,
     InlineArrayAttribute,
+    UnsafeAccessorAttribute,
 
     CountOfWellKnownAttributes
 };
@@ -107,6 +108,8 @@ inline const char *GetWellKnownAttributeName(WellKnownAttribute attribute)
             return "System.Runtime.InteropServices.ObjectiveC.ObjectiveCTrackedTypeAttribute";
         case WellKnownAttribute::InlineArrayAttribute:
             return "System.Runtime.CompilerServices.InlineArrayAttribute";
+        case WellKnownAttribute::UnsafeAccessorAttribute:
+            return "System.Runtime.CompilerServices.UnsafeAccessorAttribute";
         case WellKnownAttribute::CountOfWellKnownAttributes:
         default:
             break; // Silence compiler warnings
index dbc8fca..981d4e2 100644 (file)
   <data name="Arg_AmbiguousMatchException_MemberInfo" xml:space="preserve">
     <value>Ambiguous match found for '{0} {1}'.</value>
   </data>
+  <data name="Arg_AmbiguousMatchException_UnsafeAccessor" xml:space="preserve">
+    <value>Ambiguity in binding of UnsafeAccessorAttribute.</value>
+  </data>
   <data name="Arg_ApplicationException" xml:space="preserve">
     <value>Error in the application.</value>
   </data>
index 59ac572..cb40dd1 100644 (file)
     <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\TypeForwardedFromAttribute.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\TypeForwardedToAttribute.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\Unsafe.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\UnsafeAccessorAttribute.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\UnsafeValueTypeAttribute.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\ValueTaskAwaiter.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)System\Runtime\CompilerServices\YieldAwaitable.cs" />
diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnsafeAccessorAttribute.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnsafeAccessorAttribute.cs
new file mode 100644 (file)
index 0000000..dfff035
--- /dev/null
@@ -0,0 +1,92 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace System.Runtime.CompilerServices
+{
+    /// <summary>
+    /// Specifies the kind of target to which an <see cref="UnsafeAccessorAttribute" /> is providing access.
+    /// </summary>
+    public enum UnsafeAccessorKind
+    {
+        /// <summary>
+        /// Provide access to a constructor.
+        /// </summary>
+        Constructor,
+
+        /// <summary>
+        /// Provide access to a method.
+        /// </summary>
+        Method,
+
+        /// <summary>
+        /// Provide access to a static method.
+        /// </summary>
+        StaticMethod,
+
+        /// <summary>
+        /// Provide access to a field.
+        /// </summary>
+        Field,
+
+        /// <summary>
+        /// Provide access to a static field.
+        /// </summary>
+        StaticField
+    };
+
+    /// <summary>
+    /// Provides access to an inaccessible member of a specific type.
+    /// </summary>
+    /// <remarks>
+    /// This attribute may be applied to an <code>extern static</code> method.
+    /// The implementation of the <code>extern static</code> method annotated with
+    /// this attribute will be provided by the runtime based on the information in
+    /// the attribute and the signature of the method that the attribute is applied to.
+    /// The runtime will try to find the matching method or field and forward the call
+    /// to it. If the matching method or field is not found, the body of the <code>extern</code>
+    /// method will throw <see cref="MissingFieldException" /> or <see cref="MissingMethodException" />.
+    ///
+    /// For <see cref="UnsafeAccessorKind.Method"/>, <see cref="UnsafeAccessorKind.StaticMethod"/>,
+    /// <see cref="UnsafeAccessorKind.Field"/>, and <see cref="UnsafeAccessorKind.StaticField"/>, the type of
+    /// the first argument of the annotated <code>extern</code> method identifies the owning type.
+    /// The value of the first argument is treated as <code>this</code> pointer for instance fields and methods.
+    /// The first argument must be passed as <code>ref</code> for instance fields and methods on structs.
+    /// The value of the first argument is not used by the implementation for <code>static</code> fields and methods.
+    ///
+    /// Return type is considered for the signature match. modreqs and modopts are initially not considered for
+    /// the signature match. However, if an ambiguity exists ignoring modreqs and modopts, a precise match
+    /// is attempted. If an ambiguity still exists <see cref="System.Reflection.AmbiguousMatchException" /> is thrown.
+    /// </remarks>
+    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
+    public sealed class UnsafeAccessorAttribute : Attribute
+    {
+        // Block of text to include above when Generics support is added:
+        //
+        // The generic parameters of the <code>extern static</code> method are a concatenation of the type and
+        // method generic arguments of the target method. For example,
+        // <code>extern static void Method1&lt;T1, T2&gt;(Class1&lt;T1&gt; @this)</code>
+        // can be used to call <code>Class1&lt;T1&gt;.Method1&lt;T2&gt;()</code>. The generic constraints of the
+        // <code>extern static</code> method must match generic constraints of the target type, field or method.
+
+        /// <summary>
+        /// Instantiates an <see cref="UnsafeAccessorAttribute"/> providing access to a member of kind <see cref="UnsafeAccessorKind"/>.
+        /// </summary>
+        /// <param name="kind">The kind of the target to which access is provided.</param>
+        public UnsafeAccessorAttribute(UnsafeAccessorKind kind)
+            => Kind = kind;
+
+        /// <summary>
+        /// Gets the kind of member to which access is provided.
+        /// </summary>
+        public UnsafeAccessorKind Kind { get; }
+
+        /// <summary>
+        /// Gets or sets the name of the member to which access is provided.
+        /// </summary>
+        /// <remarks>
+        /// The name defaults to the annotated method name if not specified.
+        /// The name must be unset/<code>null</code> for <see cref="UnsafeAccessorKind.Constructor"/>.
+        /// </remarks>
+        public string? Name { get; set; }
+    }
+}
index 54e75c9..b3b5d17 100644 (file)
@@ -13092,6 +13092,21 @@ namespace System.Runtime.CompilerServices
         [System.CLSCompliantAttribute(false)]
         public unsafe static void Write<T>(void* destination, T value) { }
     }
+    [System.AttributeUsageAttribute(System.AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
+    public sealed class UnsafeAccessorAttribute : Attribute
+    {
+        public UnsafeAccessorAttribute(System.Runtime.CompilerServices.UnsafeAccessorKind kind) { }
+        public System.Runtime.CompilerServices.UnsafeAccessorKind Kind { get; }
+        public string? Name { get; set; }
+    }
+    public enum UnsafeAccessorKind
+    {
+        Constructor,
+        Method,
+        StaticMethod,
+        Field,
+        StaticField
+    };
     [System.AttributeUsageAttribute(System.AttributeTargets.Struct)]
     public sealed partial class UnsafeValueTypeAttribute : System.Attribute
     {
index 4da7bf9..7de166e 100644 (file)
@@ -41,15 +41,12 @@ namespace Xunit
         /// <summary>
         ///     Asserts that the given delegate throws an <see cref="ArgumentException"/> of type <typeparamref name="T"/> with the given parameter name.
         /// </summary>
-        /// <param name="action">
-        ///     The delegate of type <see cref="Action"/> to execute.
-        /// </param>
-        /// <param name="message">
-        ///     A <see cref="String"/> containing additional information for when the assertion fails.
-        /// </param>
         /// <param name="parameterName">
         ///     A <see cref="String"/> containing the parameter of name to check, <see langword="null"/> to skip parameter validation.
         /// </param>
+        /// <param name="action">
+        ///     The delegate of type <see cref="Action"/> to execute.
+        /// </param>
         /// <returns>
         ///     The thrown <see cref="Exception"/>.
         /// </returns>
@@ -75,6 +72,36 @@ namespace Xunit
         }
 
         /// <summary>
+        ///     Asserts that the given delegate throws an <see cref="MissingMemberException"/> of type <typeparamref name="T"/> with the given parameter name.
+        /// </summary>
+        /// <param name="memberName">
+        ///     A <see cref="String"/> containing the parameter of name to check, <see langword="null"/> to skip parameter validation.
+        /// </param>
+        /// <param name="action">
+        ///     The delegate of type <see cref="Action"/> to execute.
+        /// </param>
+        /// <returns>
+        ///     The thrown <see cref="Exception"/>.
+        /// </returns>
+        /// <exception cref="AssertFailedException">
+        ///     <see cref="Exception"/> of type <typeparam name="T"/> was not thrown.
+        ///     <para>
+        ///         -or-
+        ///     </para>
+        ///     <see cref="MissingMemberException.Message"/> does not contain <paramref name="memberName"/> .
+        /// </exception>
+        public static T ThrowsMissingMemberException<T>(string memberName, Action action)
+            where T : MissingMemberException
+        {
+            T exception = Assert.Throws<T>(action);
+
+            if (memberName != null)
+                Assert.True(exception.Message.Contains(memberName));
+
+            return exception;
+        }
+
+        /// <summary>
         ///     Asserts that the given async delegate throws an <see cref="Exception"/> of type <typeparam name="T" /> and <see cref="Exception.InnerException"/>
         ///     returns an <see cref="Exception"/> of type <typeparam name="TInner" />.
         /// </summary>
diff --git a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.cs b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.cs
new file mode 100644 (file)
index 0000000..e8dec8c
--- /dev/null
@@ -0,0 +1,485 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+using Xunit;
+
+static unsafe class UnsafeAccessorsTests
+{
+    const string PrivateStatic = nameof(PrivateStatic);
+    const string Private = nameof(Private);
+    const string PrivateArg = nameof(PrivateArg);
+
+    class UserDataClass
+    {
+        public const string StaticFieldName = nameof(_F);
+        public const string FieldName = nameof(_f);
+        public const string StaticMethodName = nameof(_M);
+        public const string MethodName = nameof(_m);
+        public const string StaticMethodVoidName = nameof(_MVV);
+        public const string MethodVoidName = nameof(_mvv);
+        public const string MethodNameAmbiguous = nameof(_Ambiguous);
+        public const string MethodPointerName = nameof(_Pointer);
+        public const string MethodCdeclCallConvBitName = nameof(_CdeclCallConvBit);
+        public const string MethodStdcallCallConvBitName = nameof(_StdcallCallConvBit);
+        public const string MethodManagedCallConvBitName = nameof(_ManagedCallConvBit);
+
+        private static string _F = PrivateStatic;
+        private string _f;
+
+        public string Value => _f;
+
+        private UserDataClass(string a) { _f = a; }
+        private UserDataClass() { _f = Private; Prop = Private; }
+
+        private static string _M(string s, ref string sr, in string si) => s;
+        private string _m(string s, ref string sr, in string si) => s;
+
+        private static void _MVV() {}
+        private void _mvv() {}
+
+        // The "init" is important to have here - custom modifier test.
+        private string Prop { get; init; }
+
+        // Used to validate ambiguity is handled via custom modifiers.
+        private string _Ambiguous(delegate* unmanaged[Cdecl, MemberFunction]<void> fptr) => nameof(CallConvCdecl);
+        private string _Ambiguous(delegate* unmanaged[Stdcall, MemberFunction]<void> fptr) => nameof(CallConvStdcall);
+
+        // Used to validate pointer values.
+        private static string _Pointer(void* ptr) => "void*";
+
+        // Used to validate the embedded callconv bits in
+        // ECMA-335 signatures for methods.
+        private string _CdeclCallConvBit(delegate* unmanaged[Cdecl]<void> fptr) => nameof(CallConvCdecl);
+        private string _StdcallCallConvBit(delegate* unmanaged[Stdcall]<void> fptr) => nameof(CallConvStdcall);
+        private string _ManagedCallConvBit(delegate* <void> fptr) => "Managed";
+    }
+
+    [StructLayout(LayoutKind.Sequential)]
+    struct UserDataValue
+    {
+        public const string StaticFieldName = nameof(_F);
+        public const string FieldName = nameof(_f);
+        public const string StaticMethodName = nameof(_M);
+        public const string MethodName = nameof(_m);
+
+        private static string _F = PrivateStatic;
+        private string _f;
+
+        public string Value => _f;
+
+        private UserDataValue(string a) { _f = a; }
+
+        // ValueClass are not permitted to define a private default constructor.
+        public UserDataValue() { _f = Private; }
+
+        private static string _M(string s, ref string sr, in string si) => s;
+        private string _m(string s, ref string sr, in string si) => s;
+    }
+
+    [UnsafeAccessor(UnsafeAccessorKind.Constructor)]
+    extern static UserDataClass CallPrivateConstructorClass();
+
+    [UnsafeAccessor(UnsafeAccessorKind.Constructor)]
+    extern static UserDataClass CallPrivateConstructorClass(string a);
+
+    [UnsafeAccessor(UnsafeAccessorKind.Constructor)]
+    extern static UserDataValue CallPrivateConstructorValue(string a);
+
+    [Fact]
+    [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)]
+    public static void Verify_CallDefaultCtorClass()
+    {
+        Console.WriteLine($"Running {nameof(Verify_CallDefaultCtorClass)}");
+
+        var local = CallPrivateConstructorClass();
+        Assert.Equal(nameof(UserDataClass), local.GetType().Name);
+    }
+
+    [Fact]
+    [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)]
+    public static void Verify_CallCtorClass()
+    {
+        Console.WriteLine($"Running {nameof(Verify_CallCtorClass)}");
+
+        var local = CallPrivateConstructorClass(PrivateArg);
+        Assert.Equal(nameof(UserDataClass), local.GetType().Name);
+        Assert.Equal(PrivateArg, local.Value);
+    }
+
+    [Fact]
+    [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)]
+    public static void Verify_CallCtorValue()
+    {
+        Console.WriteLine($"Running {nameof(Verify_CallCtorValue)}");
+
+        var local = CallPrivateConstructorValue(PrivateArg);
+        Assert.Equal(nameof(UserDataValue), local.GetType().Name);
+        Assert.Equal(PrivateArg, local.Value);
+    }
+
+    [Fact]
+    [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)]
+    public static void Verify_CallCtorAsMethod()
+    {
+        Console.WriteLine($"Running {nameof(Verify_CallCtorAsMethod)}");
+
+        UserDataClass ud = (UserDataClass)RuntimeHelpers.GetUninitializedObject(typeof(UserDataClass));
+        Assert.Null(ud.Value);
+
+        CallPrivateConstructor(ud, PrivateArg);
+        Assert.Equal(PrivateArg, ud.Value);
+
+        [UnsafeAccessor(UnsafeAccessorKind.Method, Name=".ctor")]
+        extern static void CallPrivateConstructor(UserDataClass _this, string a);
+    }
+
+    [Fact]
+    [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)]
+    public static void Verify_CallCtorAsMethodValue()
+    {
+        Console.WriteLine($"Running {nameof(Verify_CallCtorAsMethodValue)}");
+
+        UserDataValue ud = new();
+        Assert.Equal(Private, ud.Value);
+
+        CallPrivateConstructor(ref ud, PrivateArg);
+        Assert.Equal(PrivateArg, ud.Value);
+
+        [UnsafeAccessor(UnsafeAccessorKind.Method, Name=".ctor")]
+        extern static void CallPrivateConstructor(ref UserDataValue _this, string a);
+    }
+
+    [Fact]
+    [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)]
+    public static void Verify_AccessStaticFieldClass()
+    {
+        Console.WriteLine($"Running {nameof(Verify_AccessStaticFieldClass)}");
+
+        Assert.Equal(PrivateStatic, GetPrivateStaticField((UserDataClass)null));
+
+        [UnsafeAccessor(UnsafeAccessorKind.StaticField, Name=UserDataClass.StaticFieldName)]
+        extern static ref string GetPrivateStaticField(UserDataClass d);
+    }
+
+    [Fact]
+    [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)]
+    public static void Verify_AccessFieldClass()
+    {
+        Console.WriteLine($"Running {nameof(Verify_AccessFieldClass)}");
+
+        var local = CallPrivateConstructorClass();
+        Assert.Equal(Private, GetPrivateField(local));
+
+        [UnsafeAccessor(UnsafeAccessorKind.Field, Name=UserDataClass.FieldName)]
+        extern static ref string GetPrivateField(UserDataClass d);
+    }
+
+    [Fact]
+    [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)]
+    public static void Verify_AccessStaticFieldValue()
+    {
+        Console.WriteLine($"Running {nameof(Verify_AccessStaticFieldValue)}");
+
+        Assert.Equal(PrivateStatic, GetPrivateStaticField(new UserDataValue()));
+
+        [UnsafeAccessor(UnsafeAccessorKind.StaticField, Name=UserDataValue.StaticFieldName)]
+        extern static ref string GetPrivateStaticField(UserDataValue d);
+    }
+
+    [Fact]
+    [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)]
+    public static void Verify_AccessFieldValue()
+    {
+        Console.WriteLine($"Running {nameof(Verify_AccessFieldValue)}");
+
+        UserDataValue local = new();
+        Assert.Equal(Private, GetPrivateField(ref local));
+
+        [UnsafeAccessor(UnsafeAccessorKind.Field, Name=UserDataValue.FieldName)]
+        extern static ref string GetPrivateField(ref UserDataValue d);
+    }
+
+    [Fact]
+    [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)]
+    public static void Verify_AccessStaticMethodClass()
+    {
+        Console.WriteLine($"Running {nameof(Verify_AccessStaticMethodClass)}");
+
+        var sr = string.Empty;
+        var si = string.Empty;
+        Assert.Equal(PrivateStatic, GetPrivateStaticMethod((UserDataClass)null, PrivateStatic, ref sr, in si));
+
+        [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name=UserDataClass.StaticMethodName)]
+        extern static string GetPrivateStaticMethod(UserDataClass d, string s, ref string sr, in string si);
+    }
+
+    [Fact]
+    [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)]
+    public static void Verify_AccessMethodClass()
+    {
+        Console.WriteLine($"Running {nameof(Verify_AccessMethodClass)}");
+
+        var sr = string.Empty;
+        var si = string.Empty;
+        var local = CallPrivateConstructorClass();
+        Assert.Equal(Private, GetPrivateMethod(local, Private, ref sr, in si));
+
+        [UnsafeAccessor(UnsafeAccessorKind.Method, Name=UserDataClass.MethodName)]
+        extern static string GetPrivateMethod(UserDataClass d, string s, ref string sr, in string si);
+    }
+
+    // These are defined outside of the test to validate lookup using the name of
+    // the declaration as opposed to the Name field.
+    [UnsafeAccessor(UnsafeAccessorKind.StaticMethod)]
+    extern static void _MVV(UserDataClass d);
+    [UnsafeAccessor(UnsafeAccessorKind.Method)]
+    extern static void _mvv(UserDataClass d);
+
+    [Fact]
+    [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)]
+    public static void Verify_AccessStaticMethodVoidClass()
+    {
+        Console.WriteLine($"Running {nameof(Verify_AccessStaticMethodVoidClass)}");
+
+        GetPrivateStaticMethod((UserDataClass)null);
+        _MVV((UserDataClass)null);
+
+        [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name=UserDataClass.StaticMethodVoidName)]
+        extern static void GetPrivateStaticMethod(UserDataClass d);
+    }
+
+    [Fact]
+    [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)]
+    public static void Verify_AccessMethodVoidClass()
+    {
+        Console.WriteLine($"Running {nameof(Verify_AccessMethodVoidClass)}");
+
+        var local = CallPrivateConstructorClass();
+        GetPrivateMethod(local);
+        _mvv(local);
+
+        [UnsafeAccessor(UnsafeAccessorKind.Method, Name=UserDataClass.MethodVoidName)]
+        extern static void GetPrivateMethod(UserDataClass d);
+    }
+
+    [Fact]
+    [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)]
+    public static void Verify_AccessStaticMethodValue()
+    {
+        Console.WriteLine($"Running {nameof(Verify_AccessStaticMethodValue)}");
+
+        var sr = string.Empty;
+        var si = string.Empty;
+        Assert.Equal(PrivateStatic, GetPrivateStaticMethod(new UserDataValue(), PrivateStatic, ref sr, in si));
+
+        [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name=UserDataValue.StaticMethodName)]
+        extern static string GetPrivateStaticMethod(UserDataValue d, string s, ref string sr, in string si);
+    }
+
+    [Fact]
+    [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)]
+    public static void Verify_AccessMethodValue()
+    {
+        Console.WriteLine($"Running {nameof(Verify_AccessMethodValue)}");
+
+        var sr = string.Empty;
+        var si = string.Empty;
+        UserDataValue local = new();
+        Assert.Equal(Private, GetPrivateMethod(ref local, Private, ref sr, in si));
+
+        [UnsafeAccessor(UnsafeAccessorKind.Method, Name=UserDataValue.MethodName)]
+        extern static string GetPrivateMethod(ref UserDataValue d, string s, ref string sr, in string si);
+    }
+
+    [Fact]
+    [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)]
+    public static void Verify_IgnoreCustomModifier()
+    {
+        Console.WriteLine($"Running {nameof(Verify_IgnoreCustomModifier)}");
+
+        var ud = CallPrivateConstructorClass();
+        Assert.Equal(Private, CallPrivateGetter(ud));
+
+        const string newValue = "NewPropValue";
+        CallPrivateSetter(ud, newValue);
+        Assert.Equal(newValue, CallPrivateGetter(ud));
+
+        [UnsafeAccessor(UnsafeAccessorKind.Method, Name="get_Prop")]
+        extern static string CallPrivateGetter(UserDataClass d);
+
+        // Private setter used with "init" to validate default "ignore custom modifier" logic
+        [UnsafeAccessor(UnsafeAccessorKind.Method, Name="set_Prop")]
+        extern static void CallPrivateSetter(UserDataClass d, string v);
+    }
+
+    [Fact]
+    [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)]
+    public static void Verify_PreciseMatchCustomModifier()
+    {
+        Console.WriteLine($"Running {nameof(Verify_PreciseMatchCustomModifier)}");
+
+        var ud = CallPrivateConstructorClass();
+        Assert.Equal(nameof(CallConvStdcall), CallPrivateMethod(ud, null));
+
+        [UnsafeAccessor(UnsafeAccessorKind.Method, Name=UserDataClass.MethodNameAmbiguous)]
+        extern static string CallPrivateMethod(UserDataClass d, delegate* unmanaged[Stdcall, MemberFunction]<void> fptr);
+    }
+
+    [Fact]
+    [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)]
+    public static void Verify_UnmanagedCallConvBitAreTreatedAsCustomModifiersAndIgnored()
+    {
+        Console.WriteLine($"Running {nameof(Verify_UnmanagedCallConvBitAreTreatedAsCustomModifiersAndIgnored)}");
+
+        var ud = CallPrivateConstructorClass();
+        Assert.Equal(nameof(CallConvCdecl), CallCdeclMethod(ud, null));
+        Assert.Equal(nameof(CallConvStdcall), CallStdcallMethod(ud, null));
+
+        // The names of the declarations don't match the calling conventions in the function
+        // pointer signature, this is by design for this test. The intent here is to validate that
+        // calling conventions, when encoded in the ECMA-335 bits, are ignored on the first pass
+        // in the same way custom modifiers are ignored.
+        [UnsafeAccessor(UnsafeAccessorKind.Method, Name=UserDataClass.MethodCdeclCallConvBitName)]
+        extern static string CallCdeclMethod(UserDataClass d, delegate* unmanaged[Stdcall]<void> fptr);
+
+        // See comment above regarding naming.
+        [UnsafeAccessor(UnsafeAccessorKind.Method, Name=UserDataClass.MethodStdcallCallConvBitName)]
+        extern static string CallStdcallMethod(UserDataClass d, delegate* unmanaged[Cdecl]<void> fptr);
+    }
+
+    [Fact]
+    [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)]
+    public static void Verify_ManagedUnmanagedFunctionPointersDontMatch()
+    {
+        Console.WriteLine($"Running {nameof(Verify_ManagedUnmanagedFunctionPointersDontMatch)}");
+
+        var ud = CallPrivateConstructorClass();
+        Assert.Throws<MissingMethodException>(() => CallCdeclMethod(ud, null));
+        Assert.Throws<MissingMethodException>(() => CallManagedMethod(ud, null));
+
+        // Managed calling conventions don't match on unmanaged function pointers
+        [UnsafeAccessor(UnsafeAccessorKind.Method, Name=UserDataClass.MethodCdeclCallConvBitName)]
+        extern static string CallCdeclMethod(UserDataClass d, delegate* <void> fptr);
+
+        // Unmanaged calling conventions don't match on managed function pointers
+        [UnsafeAccessor(UnsafeAccessorKind.Method, Name=UserDataClass.MethodManagedCallConvBitName)]
+        extern static string CallManagedMethod(UserDataClass d, delegate* unmanaged[Cdecl]<void> fptr);
+    }
+
+    [Fact]
+    [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)]
+    public static void Verify_InvalidTargetUnsafeAccessor()
+    {
+        Console.WriteLine($"Running {nameof(Verify_InvalidTargetUnsafeAccessor)}");
+
+        bool isNativeAot = TestLibrary.Utilities.IsNativeAot;
+        const string DoesNotExist = "_DoesNotExist_";
+        AssertExtensions.ThrowsMissingMemberException<MissingMethodException>(
+            isNativeAot ? null : DoesNotExist,
+            () => MethodNotFound(null));
+        AssertExtensions.ThrowsMissingMemberException<MissingMethodException>(
+            isNativeAot ? null : DoesNotExist,
+            () => StaticMethodNotFound(null));
+
+        AssertExtensions.ThrowsMissingMemberException<MissingFieldException>(
+            isNativeAot ? null : DoesNotExist,
+            () => FieldNotFound(null));
+        AssertExtensions.ThrowsMissingMemberException<MissingFieldException>(
+            isNativeAot ? null : DoesNotExist,
+            () => StaticFieldNotFound(null));
+
+        AssertExtensions.ThrowsMissingMemberException<MissingMethodException>(
+            isNativeAot ? null : UserDataClass.MethodPointerName,
+            () => CallPointerMethod(null, null));
+
+        Assert.Throws<AmbiguousMatchException>(
+            () => CallAmbiguousMethod(CallPrivateConstructorClass(), null));
+
+        [UnsafeAccessor(UnsafeAccessorKind.Method, Name=DoesNotExist)]
+        extern static void MethodNotFound(UserDataClass d);
+
+        [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name=DoesNotExist)]
+        extern static void StaticMethodNotFound(UserDataClass d);
+
+        [UnsafeAccessor(UnsafeAccessorKind.Field, Name=DoesNotExist)]
+        extern static ref string FieldNotFound(UserDataClass d);
+
+        [UnsafeAccessor(UnsafeAccessorKind.StaticField, Name=DoesNotExist)]
+        extern static ref string StaticFieldNotFound(UserDataClass d);
+
+        // Pointers generally degrade to `void*`, but that isn't true for UnsafeAccessor signature validation.
+        [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name=UserDataClass.MethodPointerName)]
+        extern static string CallPointerMethod(UserDataClass d, delegate* unmanaged[Stdcall]<void> fptr);
+
+        // This is an ambiguous match since there are two methods each with two custom modifiers.
+        // Therefore the default "ignore custom modifiers" logic fails. The fallback is for a
+        // precise match and that also fails because the custom modifiers don't match precisely.
+        [UnsafeAccessor(UnsafeAccessorKind.Method, Name=UserDataClass.MethodNameAmbiguous)]
+        extern static string CallAmbiguousMethod(UserDataClass d, delegate* unmanaged[Stdcall, SuppressGCTransition]<void> fptr);
+    }
+
+    [Fact]
+    [ActiveIssue("https://github.com/dotnet/runtime/issues/86040", TestRuntimes.Mono)]
+    public static void Verify_InvalidUseUnsafeAccessor()
+    {
+        Console.WriteLine($"Running {nameof(Verify_InvalidUseUnsafeAccessor)}");
+
+        Assert.Throws<BadImageFormatException>(() => FieldReturnMustBeByRefClass((UserDataClass)null));
+        Assert.Throws<BadImageFormatException>(() =>
+        {
+            UserDataValue local = new();
+            FieldReturnMustBeByRefValue(ref local);
+        });
+        Assert.Throws<BadImageFormatException>(() => FieldArgumentMustBeByRef(new UserDataValue()));
+        Assert.Throws<BadImageFormatException>(() => FieldMustHaveSingleArgument((UserDataClass)null, 0));
+        Assert.Throws<BadImageFormatException>(() => StaticFieldMustHaveSingleArgument((UserDataClass)null, 0));
+        Assert.Throws<BadImageFormatException>(() => InvalidKindValue(null));
+        Assert.Throws<BadImageFormatException>(() => InvalidCtorSignatureClass());
+        Assert.Throws<BadImageFormatException>(() => InvalidCtorSignatureValue());
+        Assert.Throws<BadImageFormatException>(() => InvalidCtorName());
+        Assert.Throws<BadImageFormatException>(() => InvalidCtorType());
+        Assert.Throws<BadImageFormatException>(() => LookUpFailsOnPointers(null));
+        Assert.Throws<BadImageFormatException>(() => LookUpFailsOnFunctionPointers(null));
+
+        [UnsafeAccessor(UnsafeAccessorKind.Field, Name=UserDataValue.FieldName)]
+        extern static string FieldReturnMustBeByRefClass(UserDataClass d);
+
+        [UnsafeAccessor(UnsafeAccessorKind.Field, Name=UserDataValue.FieldName)]
+        extern static string FieldReturnMustBeByRefValue(ref UserDataValue d);
+
+        [UnsafeAccessor(UnsafeAccessorKind.Field, Name=UserDataValue.FieldName)]
+        extern static ref string FieldArgumentMustBeByRef(UserDataValue d);
+
+        [UnsafeAccessor(UnsafeAccessorKind.Field, Name=UserDataValue.FieldName)]
+        extern static ref string FieldMustHaveSingleArgument(UserDataClass d, int a);
+
+        [UnsafeAccessor(UnsafeAccessorKind.StaticField, Name=UserDataValue.StaticFieldName)]
+        extern static ref string StaticFieldMustHaveSingleArgument(UserDataClass d, int a);
+
+        [UnsafeAccessor((UnsafeAccessorKind)100, Name=UserDataClass.StaticMethodVoidName)]
+        extern static void InvalidKindValue(UserDataClass d);
+
+        [UnsafeAccessor(UnsafeAccessorKind.Constructor)]
+        extern static ref UserDataClass InvalidCtorSignatureClass();
+
+        [UnsafeAccessor(UnsafeAccessorKind.Constructor)]
+        extern static ref UserDataValue InvalidCtorSignatureValue();
+
+        [UnsafeAccessor(UnsafeAccessorKind.Constructor, Name="_ShouldBeNull_")]
+        extern static UserDataClass InvalidCtorName();
+
+        [UnsafeAccessor(UnsafeAccessorKind.Constructor)]
+        extern static void InvalidCtorType();
+
+        [UnsafeAccessor(UnsafeAccessorKind.Method, Name=nameof(ToString))]
+        extern static string LookUpFailsOnPointers(void* d);
+
+        [UnsafeAccessor(UnsafeAccessorKind.Method, Name=nameof(ToString))]
+        extern static string LookUpFailsOnFunctionPointers(delegate* <void> fptr);
+    }
+}
\ No newline at end of file
diff --git a/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.csproj b/src/tests/baseservices/compilerservices/UnsafeAccessors/UnsafeAccessorsTests.csproj
new file mode 100644 (file)
index 0000000..98cadb5
--- /dev/null
@@ -0,0 +1,12 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="UnsafeAccessorsTests.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="$(TestSourceDir)Common/CoreCLRTestLibrary/CoreCLRTestLibrary.csproj" />
+  </ItemGroup>
+</Project>