Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TypeMap API (Dynamic implementation) #113954

Merged
merged 25 commits into from
Apr 2, 2025
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
bd76011
TypeMap API (Dynamic implementation)
AaronRobinsonMSFT Mar 26, 2025
a2bfc27
Add basic tests
AaronRobinsonMSFT Mar 26, 2025
7ed138b
Feedback and fixes from testing.
AaronRobinsonMSFT Mar 27, 2025
2205ac8
Testing for all attributes
AaronRobinsonMSFT Mar 27, 2025
91862d1
Convert Tuple<,> to private type.
AaronRobinsonMSFT Mar 28, 2025
cf32158
Feedback pass 1
AaronRobinsonMSFT Mar 29, 2025
60e18c9
Always fallback and validate TypeSpec.
AaronRobinsonMSFT Mar 29, 2025
fb1e1a6
Handle exceptions from managed callbacks.
AaronRobinsonMSFT Mar 29, 2025
04d9b10
Convert assert to explicit check for TypeSpec.
AaronRobinsonMSFT Mar 29, 2025
49c5ad6
Delay parsing contents until needed.
AaronRobinsonMSFT Mar 30, 2025
479c15b
Fix build
AaronRobinsonMSFT Mar 31, 2025
e7f9e6a
Call Return on ArrayPool array.
AaronRobinsonMSFT Mar 31, 2025
eb9e9e2
Remove unnecessary lock.
AaronRobinsonMSFT Mar 31, 2025
380b809
Stop processing on error.
AaronRobinsonMSFT Mar 31, 2025
e94234f
Use GetMaxCharCount to estimate needed buffer length.
AaronRobinsonMSFT Mar 31, 2025
d2123d0
Perform type loading via the TypeNameResolver.GetTypeHelper API.
AaronRobinsonMSFT Mar 31, 2025
7e99942
Move managed VersionResilientHashCode logic
AaronRobinsonMSFT Mar 31, 2025
593f09e
Use newly shared VersionResilientHashCode logic.
AaronRobinsonMSFT Mar 31, 2025
9ac6068
Remove TypeMapLazyDictionary.cs from mono build.
AaronRobinsonMSFT Mar 31, 2025
51f8342
Remove unused message.
AaronRobinsonMSFT Mar 31, 2025
697d7e1
Throw NotSupportedException for TypeMap APIs.
AaronRobinsonMSFT Mar 31, 2025
edf0a13
Feedback
AaronRobinsonMSFT Apr 1, 2025
30148a3
Remove assert
AaronRobinsonMSFT Apr 1, 2025
6c6e35c
Add message to SPCL.
AaronRobinsonMSFT Apr 1, 2025
2713896
Remove unnecessary changes.
AaronRobinsonMSFT Apr 1, 2025
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
279 changes: 279 additions & 0 deletions src/coreclr/vm/assemblynative.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include "typeparse.h"
#include "encee.h"
#include "threadsuspend.h"
#include <caparser.h>

#include "appdomainnative.hpp"
#include "../binder/inc/bindertracing.h"
Expand Down Expand Up @@ -1429,3 +1430,281 @@ extern "C" BOOL QCALLTYPE AssemblyNative_IsApplyUpdateSupported()

return result;
}

namespace
{
LPCSTR TypeMapAssemblyTargetAttributeName = "System.Runtime.InteropServices.TypeMapAssemblyTargetAttribute`1";
LPCSTR TypeMapAttributeName = "System.Runtime.InteropServices.TypeMapAttribute`1";
LPCSTR TypeMapAssociationAttributeName = "System.Runtime.InteropServices.TypeMapAssociationAttribute`1";

bool IsTypeSpecForTypeMapGroup(
MethodTable* groupTypeMT,
Assembly* pAssembly,
mdToken typeSpec)
{
STANDARD_VM_CONTRACT;
_ASSERTE(groupTypeMT != NULL);
_ASSERTE(pAssembly != NULL);
_ASSERTE(TypeFromToken(typeSpec) == mdtTypeSpec);

IMDInternalImport* pImport = pAssembly->GetMDImport();

PCCOR_SIGNATURE sig;
ULONG sigLen;
IfFailThrow(pImport->GetTypeSpecFromToken(typeSpec, &sig, &sigLen));

SigPointer sigPointer{ sig, sigLen };

SigTypeContext context{};
TypeHandle typeMapAttribute = sigPointer.GetTypeHandleNT(pAssembly->GetModule(), &context);
if (typeMapAttribute.IsNull()
|| !typeMapAttribute.HasInstantiation()) // All TypeMap attributes are generic.
{
return false;
}

Instantiation genericParams = typeMapAttribute.GetInstantiation();
if (genericParams.GetNumArgs() != 1) // All TypeMap attributes have a single generic parameter.
return false;

return genericParams[0] == groupTypeMT;
}

template<typename ATTR_PROCESSOR>
void ProcessTypeMapAttribute(
LPCSTR attributeName,
ATTR_PROCESSOR& processor,
MethodTable* groupTypeMT,
Assembly* pAssembly)
{
STANDARD_VM_CONTRACT;
_ASSERTE(attributeName != NULL);
_ASSERTE(groupTypeMT != NULL);
_ASSERTE(pAssembly != NULL);

HRESULT hr;
IMDInternalImport* pImport = pAssembly->GetMDImport();

// Find all the CustomAttributes with the supplied name
MDEnumHolder hEnum(pImport);
hr = pImport->EnumCustomAttributeByNameInit(
TokenFromRid(1, mdtAssembly),
attributeName,
&hEnum);
IfFailThrow(hr);

// Enumerate all instances of the CustomAttribute we asked about.
// Since the TypeMap attributes are generic, we need to narrow the
// search to only those that are instantiated over the "GroupType"
// that is supplied by the caller.
mdTypeSpec lastMatchingTypeSpec = mdTypeSpecNil;
mdCustomAttribute tkAttribute;
while (pImport->EnumNext(&hEnum, &tkAttribute))
{
mdToken tokenMember;
IfFailThrow(pImport->GetCustomAttributeProps(tkAttribute, &tokenMember));

mdToken tokenType;
IfFailThrow(pImport->GetParentToken(tokenMember, &tokenType));

// Ensure the parent token is a TypeSpec.
// This can occur if the attribute is redefined externally.
if (TypeFromToken(tokenType) != mdtTypeSpec)
continue;

// Determine if this TypeSpec contains the "GroupType" we are looking for.
// There is no requirement in ECMA-335 that the same TypeSpec be used
// for the same generic instantiation. It is true for Roslyn assemblies so we
// will do a check as an optimization, but we must fall back and re-check
// the TypeSpec contents to be sure it doesn't match.
if (tokenType != lastMatchingTypeSpec)
{
if (!IsTypeSpecForTypeMapGroup(groupTypeMT, pAssembly, tokenType))
continue;

lastMatchingTypeSpec = (mdTypeSpec)tokenType;
}

// We've determined the attribute is the instantiation we want, now process the attribute contents.
void const* blob;
ULONG blobLen;
IfFailThrow(pImport->GetCustomAttributeAsBlob(tkAttribute, &blob, &blobLen));

// Pass the blob data off to the processor.
processor.Process(blob, blobLen);
}
}

class AssemblyPtrCollectionTraits : public DefaultSHashTraits<Assembly*>
{
public:
typedef Assembly* key_t;
static const key_t GetKey(Assembly* e) { LIMITED_METHOD_CONTRACT; return e; }
static count_t Hash(key_t key) { LIMITED_METHOD_CONTRACT; return (count_t)(size_t)key; }
static BOOL Equals(key_t lhs, key_t rhs) { LIMITED_METHOD_CONTRACT; return (lhs == rhs); }
};

using AssemblyPtrCollection = SHash<AssemblyPtrCollectionTraits>;

// Used for TypeMapAssemblyTargetAttribute`1 attribute.
class AssemblyTargetProcessor final
{
AssemblyPtrCollection _toProcess;
AssemblyPtrCollection _processed;

public:
AssemblyTargetProcessor(Assembly* first)
{
_toProcess.Add(first);
}

void Process(void const* blob, ULONG blobLen)
{
CustomAttributeParser cap(blob, blobLen);
IfFailThrow(cap.ValidateProlog());

LPCUTF8 assemblyName;
ULONG assemblyNameLen;
IfFailThrow(cap.GetNonNullString(&assemblyName, &assemblyNameLen));

// Load the assembly
SString assemblyNameString{ SString::Utf8, assemblyName, assemblyNameLen };

AssemblySpec spec;
spec.Init(assemblyNameString);

Assembly* pAssembly = spec.LoadAssembly(FILE_LOADED);

// Only add the assembly if it is unknown.
if (_toProcess.Lookup(pAssembly) == NULL
&& _processed.Lookup(pAssembly) == NULL)
{
_toProcess.Add(pAssembly);
}
}

bool IsEmpty() const
{
return _toProcess.GetCount() == 0;
}

Assembly* GetNext()
{
AssemblyPtrCollection::Iterator first = _toProcess.Begin();
Assembly* tmp = *first;
_toProcess.Remove(first);
_ASSERTE(_toProcess.Lookup(tmp) == NULL);
_processed.Add(tmp);
_ASSERTE(_processed.Lookup(tmp) != NULL);
return tmp;
}
};

// Used for TypeMapAttribute`1 and TypeMapAssociationAttribute`1 attributes.
class MappingsProcessor final
{
SString _currAssemblyName;
LPCUTF8 _currAssemblyNameUTF8;
int32_t _currAssemblyNameLen;
void (*_callback)(void* context, ProcessAttributesCallbackArg* arg);
void* _context;

public:
MappingsProcessor(
Assembly* currAssembly,
void (*callback)(void* context, ProcessAttributesCallbackArg* arg),
void* context)
: _currAssemblyName{}
, _callback{ callback }
, _context{ context }
{
_ASSERTE(_callback != NULL);
_ASSERTE(currAssembly != NULL);
currAssembly->GetDisplayName(_currAssemblyName);
_currAssemblyNameUTF8 = _currAssemblyName.GetUTF8();
_currAssemblyNameLen = (int32_t)strlen(_currAssemblyNameUTF8);
}

void Process(void const* blob, ULONG blobLen)
{
CustomAttributeParser cap(blob, blobLen);
IfFailThrow(cap.ValidateProlog());

// Observe that one of the constructors for TypeMapAttribute`1
// takes three (3) arguments, but we only ever look at two (2).
// This is because the third argument isn't needed by the
// mapping logic and is only used by the Trimmer.

LPCUTF8 str1;
ULONG strLen1;
IfFailThrow(cap.GetNonNullString(&str1, &strLen1));

LPCUTF8 str2;
ULONG strLen2;
IfFailThrow(cap.GetNonNullString(&str2, &strLen2));

ProcessAttributesCallbackArg arg;
arg.Utf8String1 = str1;
arg.Utf8String2 = str2;
arg.Utf8String3 = _currAssemblyNameUTF8;
arg.StringLen1 = strLen1;
arg.StringLen2 = strLen2;
arg.StringLen3 = _currAssemblyNameLen;

_callback(_context, &arg);
}
};
}

extern "C" void QCALLTYPE TypeMapLazyDictionary_ProcessAttributes(
QCall::AssemblyHandle pAssembly,
QCall::TypeHandle pGroupType,
void (*newExternalTypeEntry)(void* context, ProcessAttributesCallbackArg* arg),
void (*newProxyTypeEntry)(void* context, ProcessAttributesCallbackArg* arg),
void* context)
{
QCALL_CONTRACT;
_ASSERTE(pAssembly != NULL);
_ASSERTE(!pGroupType.AsTypeHandle().IsNull());
_ASSERTE(newExternalTypeEntry != NULL || newProxyTypeEntry != NULL);

BEGIN_QCALL;

TypeHandle groupTypeTH = pGroupType.AsTypeHandle();
_ASSERTE(!groupTypeTH.IsTypeDesc());
MethodTable* groupTypeMT = groupTypeTH.AsMethodTable();

AssemblyTargetProcessor assemblies{ pAssembly };
while (!assemblies.IsEmpty())
{
Assembly* currAssembly = assemblies.GetNext();

ProcessTypeMapAttribute(
TypeMapAssemblyTargetAttributeName,
assemblies,
groupTypeMT,
currAssembly);

if (newExternalTypeEntry != NULL)
{
MappingsProcessor onExternalType{ currAssembly, newExternalTypeEntry, context };
ProcessTypeMapAttribute(
TypeMapAttributeName,
onExternalType,
groupTypeMT,
currAssembly);
}

if (newProxyTypeEntry != NULL)
{
MappingsProcessor onProxyType{ currAssembly, newProxyTypeEntry, context };
ProcessTypeMapAttribute(
TypeMapAssociationAttributeName,
onProxyType,
groupTypeMT,
currAssembly);
}
}

END_QCALL;
}
18 changes: 18 additions & 0 deletions src/coreclr/vm/assemblynative.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -144,4 +144,22 @@ extern "C" BOOL QCALLTYPE AssemblyNative_IsApplyUpdateSupported();

extern "C" void QCALLTYPE AssemblyName_InitializeAssemblySpec(NativeAssemblyNameParts* pAssemblyNameParts, BaseAssemblySpec* pAssemblySpec);

// See TypeMapLazyDictionary.cs for managed version.
struct ProcessAttributesCallbackArg final
{
char const* Utf8String1;
char const* Utf8String2;
char const* Utf8String3;
int32_t StringLen1;
int32_t StringLen2;
int32_t StringLen3;
};

extern "C" void QCALLTYPE TypeMapLazyDictionary_ProcessAttributes(
QCall::AssemblyHandle pAssembly,
QCall::TypeHandle pTypeGroup,
void (*newExternalTypeEntry)(void* context, ProcessAttributesCallbackArg* arg),
void (*newProxyTypeEntry)(void* context, ProcessAttributesCallbackArg* arg),
void* context);

#endif
1 change: 1 addition & 0 deletions src/coreclr/vm/qcallentrypoints.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@ static const Entry s_QCall[] =
DllImportEntry(AssemblyNative_GetAssemblyCount)
DllImportEntry(AssemblyNative_GetEntryAssembly)
DllImportEntry(AssemblyNative_GetExecutingAssembly)
DllImportEntry(TypeMapLazyDictionary_ProcessAttributes)
#if defined(FEATURE_MULTICOREJIT)
DllImportEntry(MultiCoreJIT_InternalSetProfileRoot)
DllImportEntry(MultiCoreJIT_InternalStartProfile)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,9 @@
<data name="InvalidOperation_IncompatibleComparer" xml:space="preserve">
<value>The collection's comparer does not support the requested operation.</value>
</data>
<data name="InvalidOperation_TypeMapMissingEntryAssembly" xml:space="preserve">
<value>Entry assembly is required but was not found.</value>
</data>
<data name="Arg_BufferTooSmall" xml:space="preserve">
<value>Not enough space available in the buffer.</value>
</data>
Expand Down Expand Up @@ -669,6 +672,9 @@
<data name="Arg_NotGenericMethodDefinition" xml:space="preserve">
<value>{0} is not a GenericMethodDefinition. MakeGenericMethod may only be called on a method for which MethodBase.IsGenericMethodDefinition is true.</value>
</data>
<data name="Arg_NotSimpleTypeName" xml:space="preserve">
<value>'{0}' is not a simple TypeName.</value>
</data>
<data name="Arg_NotGenericParameter" xml:space="preserve">
<value>Method may only be called on a Type for which Type.IsGenericParameter is true.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1034,6 +1034,11 @@
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\SuppressGCTransitionAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\Swift\SwiftTypes.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\TypeIdentifierAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\TypeMapAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\TypeMapAssociationAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\TypeMapAssemblyTargetAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\TypeMapping.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\TypeMapLazyDictionary.cs" Condition="'$(FeatureNativeAot)' != 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\UnknownWrapper.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\UnmanagedCallConvAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\UnmanagedCallersOnlyAttribute.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;

namespace System.Runtime.InteropServices
{
/// <summary>
/// Declare an assembly that should be inspected during type map building.
/// </summary>
/// <typeparam name="TTypeMapGroup">Type map group</typeparam>
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
public sealed class TypeMapAssemblyTargetAttribute<TTypeMapGroup> : Attribute
{
/// <summary>
/// Provide the assembly to look for type mapping attributes.
/// </summary>
/// <param name="assemblyName">Assembly to reference</param>
public TypeMapAssemblyTargetAttribute(string assemblyName) { }
}
}
Loading
Loading