Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 29 additions & 10 deletions docs/design/datacontracts/RuntimeTypeSystem.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,15 @@ public enum OptimizationTier
}
```

```csharp
public enum GenericContextLoc
{
None,
InstArg,
ThisPtr,
}
```

```csharp
partial interface IRuntimeTypeSystem : IContract
{
Expand Down Expand Up @@ -199,9 +208,9 @@ partial interface IRuntimeTypeSystem : IContract
// Return true if a MethodDesc represents an IL stub with a special MethodDesc context arg
public virtual bool HasMDContextArg(MethodDescHandle);

// Return true if the method requires a hidden instantiation argument (generic context parameter).
// Corresponds to native MethodDesc::RequiresInstArg().
public virtual bool RequiresInstArg(MethodDescHandle methodDesc);
// Determine where a shared generic method obtains its generic context.
// Returns None if not shared, InstArg if via a hidden parameter, or ThisPtr if via the 'this' pointer's MethodTable.
public virtual GenericContextLoc GetGenericContextLoc(MethodDescHandle methodDesc);

// Return true if the method uses the async calling convention.
// Corresponds to native MethodDesc::IsAsyncMethod().
Expand Down Expand Up @@ -1637,26 +1646,36 @@ Determining if a method is an async thunk method:
}
```

Determining if a method requires a hidden instantiation argument (generic context parameter):
Determining where a shared generic method obtains its generic context:

```csharp
public bool RequiresInstArg(MethodDescHandle methodDescHandle)
public GenericContextLoc GetGenericContextLoc(MethodDescHandle methodDescHandle)
{
MethodDesc md = _methodDescs[methodDescHandle.Address];

// RequiresInstArg = IsSharedByGenericInstantiations && (HasMethodInstantiation || IsStatic || IsValueType || IsInterface)
if (!IsSharedByGenericInstantiations(md))
return false;
return GenericContextLoc.None;
else if (RequiresInstArg(md))
return GenericContextLoc.InstArg;
else
return GenericContextLoc.ThisPtr;
}

private bool RequiresInstArg(MethodDesc md)
{
if (HasMethodInstantiation(md))
return true;

// md.IsStatic reads from MethodDescFlags.Static (0x0080)
if (md.IsStatic)
return true;

MethodTable mt = _methodTables[md.MethodTable];
return mt.Flags.IsInterface || mt.Flags.IsValueType;
if (mt.Flags.IsValueType)
return true;

if (mt.Flags.IsInterface && !IsAbstract(md) /* checked from md classification and metadata attributes */)
return true;

return false;
}

private bool IsSharedByGenericInstantiations(MethodDesc md)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,13 @@ public enum OptimizationTier : uint
OptimizationTier1Instrumented,
}

public enum GenericContextLoc
{
None,
InstArg,
ThisPtr,
}


public interface IRuntimeTypeSystem : IContract
{
Expand Down Expand Up @@ -193,9 +200,7 @@ public interface IRuntimeTypeSystem : IContract
bool IsGenericMethodDefinition(MethodDescHandle methodDesc) => throw new NotImplementedException();
ReadOnlySpan<TypeHandle> GetGenericMethodInstantiation(MethodDescHandle methodDesc) => throw new NotImplementedException();

// Return true if the method requires a hidden instantiation argument (generic context parameter).
// This corresponds to native MethodDesc::RequiresInstArg().
bool RequiresInstArg(MethodDescHandle methodDesc) => throw new NotImplementedException();
GenericContextLoc GetGenericContextLoc(MethodDescHandle methodDescHandle) => throw new NotImplementedException();

// Return true if the method uses the async calling convention (CORINFO_CALLCONV_ASYNCCALL).
// This corresponds to native MethodDesc::IsAsyncMethod().
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ public static class CorDbgHResults
public const int CORDBG_E_READVIRTUAL_FAILURE = unchecked((int)0x80131c49);
public const int ERROR_BUFFER_OVERFLOW = unchecked((int)0x8007006F); // HRESULT_FROM_WIN32(ERROR_BUFFER_OVERFLOW)
public const int CORDBG_E_CLASS_NOT_LOADED = unchecked((int)0x80131303);
public const int CORDBG_E_TARGET_INCONSISTENT = unchecked((int)0x80131c36);
public const int CORDBG_S_NOT_ALL_BITS_SET = unchecked((int)0x00131c13);
}
Original file line number Diff line number Diff line change
Expand Up @@ -1348,34 +1348,35 @@ public ReadOnlySpan<TypeHandle> GetGenericMethodInstantiation(MethodDescHandle m
return AsInstantiatedMethodDesc(methodDesc).Instantiation;
}

/// <summary>
/// Returns true if the method requires a hidden instantiation argument (generic context parameter).
/// Matches native MethodDesc::RequiresInstArg().
/// </summary>
public bool RequiresInstArg(MethodDescHandle methodDescHandle)
private bool RequiresInstArg(MethodDesc methodDesc)
{
MethodDesc methodDesc = _methodDescs[methodDescHandle.Address];

// RequiresInstArg = IsSharedByGenericInstantiations && (HasMethodInstantiation || IsStatic || IsValueType || IsInterface)
if (!IsSharedByGenericInstantiations(methodDesc))
return false;

if (HasMethodInstantiation(methodDesc))
return true;

if (methodDesc.IsStatic)
return true;

MethodTable mt = _methodTables[methodDesc.MethodTable];
if (mt.Flags.IsInterface)
if (mt.Flags.IsValueType)
return true;

if (mt.Flags.IsValueType)
if (mt.Flags.IsInterface && !IsAbstract(methodDesc))
return true;

return false;
}

public GenericContextLoc GetGenericContextLoc(MethodDescHandle methodDescHandle)
{
MethodDesc methodDesc = _methodDescs[methodDescHandle.Address];
if (!IsSharedByGenericInstantiations(methodDesc))
return GenericContextLoc.None;
else if (RequiresInstArg(methodDesc))
return GenericContextLoc.InstArg;
else
return GenericContextLoc.ThisPtr;
}

private bool IsSharedByGenericInstantiations(MethodDesc methodDesc)
{
// Check method-level sharing: InstantiatedMethodDesc with SharedMethodInstantiation
Expand All @@ -1397,6 +1398,25 @@ private bool IsSharedByGenericInstantiations(MethodDesc methodDesc)
return mt.IsCanonMT && mt.Flags.HasInstantiation;
}

private bool IsAbstract(MethodDesc methodDesc)
{
if (methodDesc.Classification == MethodClassification.Array || methodDesc.Classification == MethodClassification.Dynamic)
return false;
uint token = methodDesc.Token;
if (EcmaMetadataUtils.GetRowId(token) == 0)
return false;

TargetPointer modulePtr = _methodTables[methodDesc.MethodTable].Module;
ModuleHandle moduleHandle = _target.Contracts.Loader.GetModuleHandleFromModulePtr(modulePtr);
MetadataReader? mdReader = _target.Contracts.EcmaMetadata.GetMetadata(moduleHandle);
if (mdReader is null)
return false;

MethodDefinitionHandle methodDefHandle = MetadataTokens.MethodDefinitionHandle((int)EcmaMetadataUtils.GetRowId(token));
MethodDefinition methodDef = mdReader.GetMethodDefinition(methodDefHandle);
return (methodDef.Attributes & MethodAttributes.Abstract) != 0;
}

public bool IsAsyncMethod(MethodDescHandle methodDescHandle)
{
MethodDesc methodDesc = _methodDescs[methodDescHandle.Address];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ private void PromoteCallerStack(

try
{
requiresInstArg = rts.RequiresInstArg(mdh);
requiresInstArg = rts.GetGenericContextLoc(mdh) == GenericContextLoc.InstArg;
isAsync = rts.IsAsyncMethod(mdh);
}
catch
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1013,7 +1013,37 @@ public int GetCurrentAppDomain(ulong* pRetVal)
}

public int ResolveAssembly(ulong vmScope, uint tkAssemblyRef, ulong* pRetVal)
=> LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.ResolveAssembly(vmScope, tkAssemblyRef, pRetVal) : HResults.E_NOTIMPL;
{
*pRetVal = 0;
int hr = HResults.S_OK;
try
{
Contracts.ILoader loader = _target.Contracts.Loader;
Contracts.ModuleHandle scopeModule = loader.GetModuleHandleFromAssemblyPtr(new TargetPointer(vmScope));
Contracts.ModuleLookupTables lookupTables = loader.GetLookupTables(scopeModule);
TargetPointer referencedModule = loader.GetModuleLookupMapElement(lookupTables.ManifestModuleReferences, tkAssemblyRef, out _);
if (referencedModule != TargetPointer.Null)
{
Contracts.ModuleHandle referencedModuleHandle = loader.GetModuleHandleFromModulePtr(referencedModule);
*pRetVal = loader.GetAssembly(referencedModuleHandle).Value;
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacy is not null)
{
ulong retValLocal;
int hrLocal = _legacy.ResolveAssembly(vmScope, tkAssemblyRef, &retValLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal}, DAC: {retValLocal}");
}
#endif
return hr;
}

public int GetNativeCodeSequencePointsAndVarInfo(ulong vmMethodDesc, ulong startAddress, Interop.BOOL fCodeAvailable, nint pNativeVarData, nint pSequencePoints)
=> LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetNativeCodeSequencePointsAndVarInfo(vmMethodDesc, startAddress, fCodeAvailable, pNativeVarData, pSequencePoints) : HResults.E_NOTIMPL;
Expand Down Expand Up @@ -1206,7 +1236,56 @@ public int RequiresAlign8(ulong thExact, Interop.BOOL* pResult)
}

public int ResolveExactGenericArgsToken(uint dwExactGenericArgsTokenIndex, ulong rawToken, ulong* pRetVal)
=> LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.ResolveExactGenericArgsToken(dwExactGenericArgsTokenIndex, rawToken, pRetVal) : HResults.E_NOTIMPL;
{
*pRetVal = 0;
int hr = HResults.S_OK;
try
{
if (dwExactGenericArgsTokenIndex == 0)
{
// In a rare case of VS4Mac debugging VS4Mac ARM64 optimized code we get a null
// generics argument token. We aren't sure why the token is null, it may be a bug
// or it may be by design in the runtime. In the interest of time we are working
// around the issue rather than investigating the root cause.
if (rawToken == 0)
{
*pRetVal = rawToken;
}
else
{
// The real generics type token is the MethodTable of the "this" object.
// The incoming rawToken is a target address of the 'this' Object.
*pRetVal = _target.Contracts.Object.GetMethodTableAddress(new TargetPointer(rawToken)).Value;
}
}
else if (dwExactGenericArgsTokenIndex == unchecked((uint)IlNum.TYPECTXT_ILNUM))
{
// rawToken is already the real generics type token. Nothing to do.
*pRetVal = rawToken;
}
else
{
// The index of the generics type token should not be anything else.
Debug.Fail($"Unexpected generics type token index: {dwExactGenericArgsTokenIndex}");
throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_TARGET_INCONSISTENT)!;
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacy is not null)
{
ulong retValLocal;
int hrLocal = _legacy.ResolveExactGenericArgsToken(dwExactGenericArgsTokenIndex, rawToken, &retValLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
Debug.Assert(*pRetVal == retValLocal, $"cDAC: {*pRetVal}, DAC: {retValLocal}");
}
#endif
return hr;
}

public int GetILCodeAndSig(ulong vmAssembly, uint functionToken, DacDbiTargetBuffer* pTargetBuffer, uint* pLocalSigToken)
=> LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetILCodeAndSig(vmAssembly, functionToken, pTargetBuffer, pLocalSigToken) : HResults.E_NOTIMPL;
Expand Down Expand Up @@ -1379,7 +1458,49 @@ public int GetTypeHandleParams(ulong vmTypeHandle, nint pParams)
=> LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetTypeHandleParams(vmTypeHandle, pParams) : HResults.E_NOTIMPL;

public int GetSimpleType(int simpleType, uint* pMetadataToken, ulong* pVmModule)
=> LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetSimpleType(simpleType, pMetadataToken, pVmModule) : HResults.E_NOTIMPL;
{
Debug.Assert(pVmModule != null);
*pVmModule = 0;
int hr = HResults.S_OK;
try
{
Contracts.IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
Contracts.TypeHandle typeHandle = rts.GetPrimitiveType((CorElementType)simpleType);

if (typeHandle.IsNull)
{
throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_CLASS_NOT_LOADED)!;
}

Debug.Assert(pMetadataToken != null);
*pMetadataToken = rts.GetTypeDefToken(typeHandle);

TargetPointer module = rts.GetModule(typeHandle);
if (module == TargetPointer.Null)
throw Marshal.GetExceptionForHR(CorDbgHResults.CORDBG_E_TARGET_INCONSISTENT)!;

*pVmModule = module.Value;
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacy is not null)
{
uint metadataTokenLocal;
ulong vmModuleLocal;
int hrLocal = _legacy.GetSimpleType(simpleType, &metadataTokenLocal, &vmModuleLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(*pMetadataToken == metadataTokenLocal, $"cDAC: {*pMetadataToken}, DAC: {metadataTokenLocal}");
Debug.Assert(*pVmModule == vmModuleLocal, $"cDAC: {*pVmModule}, DAC: {vmModuleLocal}");
}
}
#endif
return hr;
}

public int IsExceptionObject(ulong vmObject, Interop.BOOL* pResult)
=> LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.IsExceptionObject(vmObject, pResult) : HResults.E_NOTIMPL;
Expand Down Expand Up @@ -2201,6 +2322,47 @@ public int GetAsyncLocals(ulong vmMethod, ulong codeAddr, uint state, nint pAsyn
=> LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetAsyncLocals(vmMethod, codeAddr, state, pAsyncLocals) : HResults.E_NOTIMPL;

public int GetGenericArgTokenIndex(ulong vmMethod, uint* pIndex)
=> LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetGenericArgTokenIndex(vmMethod, pIndex) : HResults.E_NOTIMPL;
{
int hr = HResults.S_OK;
try
{
if (vmMethod == 0)
throw new ArgumentException("vmMethod must not be zero.", nameof(vmMethod));
if (pIndex is null)
throw new ArgumentException("pIndex must not be null.", nameof(pIndex));
Contracts.IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
Contracts.MethodDescHandle md = rts.GetMethodDescHandle(new TargetPointer(vmMethod));

switch (rts.GetGenericContextLoc(md))
{
case GenericContextLoc.None:
hr = HResults.S_FALSE;
break;
case GenericContextLoc.InstArg:
*pIndex = unchecked((uint)IlNum.TYPECTXT_ILNUM);
break;
case GenericContextLoc.ThisPtr:
*pIndex = 0u;
break;
default:
throw new InvalidOperationException($"Unexpected generic context location: {rts.GetGenericContextLoc(md)}");
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacy is not null)
{
uint indexLocal;
int hrLocal = _legacy.GetGenericArgTokenIndex(vmMethod, &indexLocal);
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
Debug.Assert(*pIndex == indexLocal, $"cDAC: {*pIndex}, DAC: {indexLocal}");
}
#endif
return hr;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,11 @@ public enum CorDebugUserState
USER_THREADPOOL = 0x100,
}

public enum IlNum : int
{
TYPECTXT_ILNUM = -3,
}

// Name-surface projection of IDacDbiInterface in native method order for COM binding validation.
// Parameter shapes are intentionally coarse placeholders and will be refined with method implementation work.
[GeneratedComInterface]
Expand Down
Loading