Optimize CSharp scripting runtime to use arena allocator per-assembly

This commit is contained in:
Wojtek Figat
2025-05-25 02:04:16 +02:00
parent f9cb4ddae2
commit 410ec0465c
15 changed files with 126 additions and 78 deletions

View File

@@ -78,7 +78,7 @@ namespace ScriptsBuilderImpl
void onScriptsReloadEnd();
void onScriptsLoaded();
void GetClassName(const StringAnsi& fullname, StringAnsi& className);
void GetClassName(const StringAnsiView fullname, StringAnsi& className);
void onCodeEditorAsyncOpenBegin()
{
@@ -273,7 +273,7 @@ bool ScriptsBuilder::GenerateProject(const StringView& customArgs)
return RunBuildTool(args);
}
void ScriptsBuilderImpl::GetClassName(const StringAnsi& fullname, StringAnsi& className)
void ScriptsBuilderImpl::GetClassName(const StringAnsiView fullname, StringAnsi& className)
{
const auto lastDotIndex = fullname.FindLast('.');
if (lastDotIndex != -1)

View File

@@ -44,9 +44,19 @@ public:
return ptr;
}
// Invokes destructor on values in an array and clears it.
template<typename Value, typename Allocator>
static void ClearDelete(Array<Value, Allocator>& collection)
{
Value* ptr = collection.Get();
for (int32 i = 0; i < collection.Count(); i++)
Memory::DestructItem(ptr[i]);
collection.Clear();
}
// Invokes destructor on values in a dictionary and clears it.
template<typename Key, typename Value, typename Allocator>
void ClearDelete(Dictionary<Key, Value, Allocator>& collection)
static void ClearDelete(Dictionary<Key, Value, Allocator>& collection)
{
for (auto it = collection.Begin(); it.IsNotEnd(); ++it)
Memory::DestructItem(it->Value);

View File

@@ -118,7 +118,7 @@ VariantType::VariantType(Types type, const MClass* klass)
#if USE_CSHARP
if (klass)
{
const StringAnsi& typeName = klass->GetFullName();
const StringAnsiView typeName = klass->GetFullName();
const int32 length = typeName.Length();
TypeName = static_cast<char*>(Allocator::Allocate(length + 1));
Platform::MemoryCopy(TypeName, typeName.Get(), length);

View File

@@ -215,7 +215,7 @@ namespace
{
if (!method->IsStatic())
continue;
const StringAnsi& name = method->GetName();
const StringAnsiView name = method->GetName();
if (name.Contains("Internal_") ||
mclass->GetFullName().Contains(".Interop."))
continue;

View File

@@ -1029,7 +1029,7 @@ void ManagedBinaryModule::InitType(MClass* mclass)
{
#if !COMPILE_WITHOUT_CSHARP
// Skip if already initialized
const StringAnsi& typeName = mclass->GetFullName();
const StringAnsiView typeName = mclass->GetFullName();
if (TypeNameToTypeIndex.ContainsKey(typeName))
return;
PROFILE_MEM(Scripting);

View File

@@ -7,6 +7,7 @@
#include "Engine/Core/Types/String.h"
#include "Engine/Core/Collections/Array.h"
#include "Engine/Core/Collections/Dictionary.h"
#include "Engine/Core/Memory/ArenaAllocation.h"
#include "Engine/Platform/CriticalSection.h"
/// <summary>
@@ -19,7 +20,7 @@ class FLAXENGINE_API MAssembly
friend Scripting;
public:
typedef Dictionary<StringAnsi, MClass*> ClassesDictionary;
typedef Dictionary<StringAnsiView, MClass*> ClassesDictionary;
private:
#if USE_MONO
@@ -67,6 +68,15 @@ public:
/// </summary>
~MAssembly();
public:
/// <summary>
/// Memory storage with all assembly-related data that shares its lifetime (eg. metadata).
/// </summary>
ArenaAllocator Memory;
// Allocates the given string within a memory that has lifetime of assembly.
StringAnsiView AllocString(const char* str);
public:
/// <summary>
/// Managed assembly actions delegate type.

View File

@@ -17,14 +17,13 @@ private:
mutable void* _attrInfo = nullptr;
#elif USE_NETCORE
void* _handle;
StringAnsi _name;
StringAnsi _namespace;
StringAnsiView _name;
StringAnsiView _namespace;
StringAnsiView _fullname;
uint32 _types = 0;
mutable uint32 _size = 0;
#endif
const MAssembly* _assembly;
StringAnsi _fullname;
MAssembly* _assembly;
mutable Array<MMethod*> _methods;
mutable Array<MField*> _fields;
@@ -47,12 +46,13 @@ private:
int32 _isInterface : 1;
int32 _isValueType : 1;
int32 _isEnum : 1;
int32 _isGeneric : 1;
public:
#if USE_MONO
MClass(const MAssembly* parentAssembly, MonoClass* monoClass, const StringAnsi& fullname);
#elif USE_NETCORE
MClass(const MAssembly* parentAssembly, void* handle, const char* name, const char* fullname, const char* namespace_, MTypeAttributes typeAttributes);
MClass(MAssembly* parentAssembly, void* handle, const char* name, const char* fullname, const char* namespace_, MTypeAttributes typeAttributes);
#endif
/// <summary>
@@ -64,7 +64,7 @@ public:
/// <summary>
/// Gets the parent assembly.
/// </summary>
const MAssembly* GetAssembly() const
FORCE_INLINE MAssembly* GetAssembly() const
{
return _assembly;
}
@@ -72,7 +72,7 @@ public:
/// <summary>
/// Gets the full name of the class (namespace and typename).
/// </summary>
FORCE_INLINE const StringAnsi& GetFullName() const
FORCE_INLINE StringAnsiView GetFullName() const
{
return _fullname;
}
@@ -80,12 +80,18 @@ public:
/// <summary>
/// Gets the name of the class.
/// </summary>
StringAnsiView GetName() const;
FORCE_INLINE StringAnsiView GetName() const
{
return _name;
}
/// <summary>
/// Gets the namespace of the class.
/// </summary>
StringAnsiView GetNamespace() const;
FORCE_INLINE StringAnsiView GetNamespace() const
{
return _name;
}
#if USE_MONO
/// <summary>
@@ -161,9 +167,9 @@ public:
/// <summary>
/// Gets if class is generic
/// </summary>
bool IsGeneric() const
FORCE_INLINE bool IsGeneric() const
{
return _fullname.FindLast('`') != -1;
return _isGeneric != 0;
}
/// <summary>

View File

@@ -71,6 +71,17 @@ MAssembly::~MAssembly()
Unload();
}
StringAnsiView MAssembly::AllocString(const char* str)
{
if (!str)
return StringAnsiView::Empty;
int32 len = StringUtils::Length(str);
char* mem = (char*)Memory.Allocate(len + 1);
Platform::MemoryCopy(mem, str, len);
mem[len] = 0;
return StringAnsiView(mem, len);
}
String MAssembly::ToString() const
{
return _name.ToString();
@@ -127,7 +138,11 @@ void MAssembly::Unload(bool isReloading)
_isLoading = false;
_isLoaded = false;
_hasCachedClasses = false;
#if USE_NETCORE
ArenaAllocator::ClearDelete(_classes);
#else
_classes.ClearDelete();
#endif
Unloaded(this);
}

View File

@@ -15,16 +15,16 @@ class FLAXENGINE_API MEvent
protected:
#if USE_MONO
MonoEvent* _monoEvent;
StringAnsi _name;
#elif USE_NETCORE
void* _handle;
StringAnsiView _name;
#endif
mutable MMethod* _addMethod;
mutable MMethod* _removeMethod;
MClass* _parentClass;
StringAnsi _name;
mutable int32 _hasCachedAttributes : 1;
mutable int32 _hasAddMonoMethod : 1;
mutable int32 _hasRemoveMonoMethod : 1;
@@ -42,7 +42,7 @@ public:
/// <summary>
/// Gets the event name.
/// </summary>
FORCE_INLINE const StringAnsi& GetName() const
FORCE_INLINE StringAnsiView GetName() const
{
return _name;
}

View File

@@ -17,14 +17,15 @@ protected:
#if USE_MONO
MonoClassField* _monoField;
MonoType* _monoType;
StringAnsi _name;
#elif USE_NETCORE
void* _handle;
void* _type;
int32 _fieldOffset;
StringAnsiView _name;
#endif
MClass* _parentClass;
StringAnsi _name;
MVisibility _visibility;
@@ -44,7 +45,7 @@ public:
/// <summary>
/// Gets field name.
/// </summary>
FORCE_INLINE const StringAnsi& GetName() const
FORCE_INLINE StringAnsiView GetName() const
{
return _name;
}

View File

@@ -21,15 +21,16 @@ class FLAXENGINE_API MMethod
protected:
#if USE_MONO
MonoMethod* _monoMethod;
StringAnsi _name;
#elif USE_NETCORE
void* _handle;
StringAnsiView _name;
int32 _paramsCount;
mutable void* _returnType;
mutable Array<void*, InlinedAllocation<8>> _parameterTypes;
void CacheSignature() const;
#endif
MClass* _parentClass;
StringAnsi _name;
MVisibility _visibility;
#if !USE_MONO_AOT
void* _cachedThunk = nullptr;
@@ -48,12 +49,11 @@ public:
explicit MMethod(MonoMethod* monoMethod, MClass* parentClass);
explicit MMethod(MonoMethod* monoMethod, const char* name, MClass* parentClass);
#elif USE_NETCORE
MMethod(MClass* parentClass, StringAnsi&& name, void* handle, int32 paramsCount, MMethodAttributes attributes);
MMethod(MClass* parentClass, StringAnsiView name, void* handle, int32 paramsCount, MMethodAttributes attributes);
#endif
public:
#if COMPILE_WITH_PROFILER
StringAnsi ProfilerName;
SourceLocationData ProfilerData;
#endif
@@ -109,7 +109,7 @@ public:
/// <summary>
/// Gets the method name.
/// </summary>
FORCE_INLINE const StringAnsi& GetName() const
FORCE_INLINE StringAnsiView GetName() const
{
return _name;
}

View File

@@ -17,16 +17,16 @@ class FLAXENGINE_API MProperty
protected:
#if USE_MONO
MonoProperty* _monoProperty;
StringAnsi _name;
#elif USE_NETCORE
void* _handle;
StringAnsiView _name;
#endif
mutable MMethod* _getMethod;
mutable MMethod* _setMethod;
MClass* _parentClass;
StringAnsi _name;
mutable int32 _hasCachedAttributes : 1;
mutable int32 _hasSetMethod : 1;
mutable int32 _hasGetMethod : 1;
@@ -49,7 +49,7 @@ public:
/// <summary>
/// Gets the property name.
/// </summary>
FORCE_INLINE const StringAnsi& GetName() const
FORCE_INLINE StringAnsiView GetName() const
{
return _name;
}

View File

@@ -150,7 +150,7 @@ ScriptingTypeHandle MUtils::UnboxScriptingTypeHandle(MTypeObject* value)
MClass* klass = GetClass(value);
if (!klass)
return ScriptingTypeHandle();
const StringAnsi& typeName = klass->GetFullName();
const StringAnsiView typeName = klass->GetFullName();
const ScriptingTypeHandle typeHandle = Scripting::FindScriptingType(typeName);
if (!typeHandle)
LOG(Warning, "Unknown scripting type {}", String(typeName));
@@ -821,14 +821,14 @@ MObject* MUtils::BoxVariant(const Variant& value)
}
}
const StringAnsi& MUtils::GetClassFullname(MObject* obj)
StringAnsiView MUtils::GetClassFullname(MObject* obj)
{
if (obj)
{
MClass* mClass = MCore::Object::GetClass(obj);
return mClass->GetFullName();
}
return StringAnsi::Empty;
return StringAnsiView::Empty;
}
MClass* MUtils::GetClass(MTypeObject* type)

View File

@@ -400,7 +400,7 @@ struct MConverter<Array<T>>
namespace MUtils
{
// Outputs the full typename for the type of the specified object.
extern FLAXENGINE_API const StringAnsi& GetClassFullname(MObject* obj);
extern FLAXENGINE_API StringAnsiView GetClassFullname(MObject* obj);
// Returns the class of the provided object.
extern FLAXENGINE_API MClass* GetClass(MObject* object);

View File

@@ -183,7 +183,7 @@ Dictionary<void*, MAssembly*> CachedAssemblyHandles;
/// <summary>
/// Returns the function pointer to the managed static method in NativeInterop class.
/// </summary>
void* GetStaticMethodPointer(const String& methodName);
void* GetStaticMethodPointer(StringView methodName);
/// <summary>
/// Calls the managed static method with given parameters.
@@ -753,12 +753,13 @@ const MAssembly::ClassesDictionary& MAssembly::GetClasses() const
static void* GetManagedClassesPtr = GetStaticMethodPointer(TEXT("GetManagedClasses"));
CallStaticMethod<void, void*, NativeClassDefinitions**, int*>(GetManagedClassesPtr, _handle, &managedClasses, &classCount);
_classes.EnsureCapacity(classCount);
MAssembly* assembly = const_cast<MAssembly*>(this);
for (int32 i = 0; i < classCount; i++)
{
NativeClassDefinitions& managedClass = managedClasses[i];
// Create class object
MClass* klass = New<MClass>(this, managedClass.typeHandle, managedClass.name, managedClass.fullname, managedClass.namespace_, managedClass.typeAttributes);
MClass* klass = assembly->Memory.New<MClass>(assembly, managedClass.typeHandle, managedClass.name, managedClass.fullname, managedClass.namespace_, managedClass.typeAttributes);
_classes.Add(klass->GetFullName(), klass);
managedClass.nativePointer = klass;
@@ -811,7 +812,7 @@ DEFINE_INTERNAL_CALL(void) NativeInterop_CreateClass(NativeClassDefinitions* man
CachedAssemblyHandles.Add(assemblyHandle, assembly);
}
MClass* klass = New<MClass>(assembly, managedClass->typeHandle, managedClass->name, managedClass->fullname, managedClass->namespace_, managedClass->typeAttributes);
MClass* klass = assembly->Memory.New<MClass>(assembly, managedClass->typeHandle, managedClass->name, managedClass->fullname, managedClass->namespace_, managedClass->typeAttributes);
if (assembly != nullptr)
{
auto& classes = const_cast<MAssembly::ClassesDictionary&>(assembly->GetClasses());
@@ -819,7 +820,7 @@ DEFINE_INTERNAL_CALL(void) NativeInterop_CreateClass(NativeClassDefinitions* man
if (classes.TryGet(klass->GetFullName(), oldKlass))
{
LOG(Warning, "Class '{0}' was already added to assembly '{1}'", String(klass->GetFullName()), String(assembly->GetName()));
Delete(klass);
Memory::DestructItem(klass);
klass = oldKlass;
}
else
@@ -915,12 +916,12 @@ bool MAssembly::UnloadImage(bool isReloading)
return false;
}
MClass::MClass(const MAssembly* parentAssembly, void* handle, const char* name, const char* fullname, const char* namespace_, MTypeAttributes attributes)
MClass::MClass(MAssembly* parentAssembly, void* handle, const char* name, const char* fullname, const char* namespace_, MTypeAttributes attributes)
: _handle(handle)
, _name(name)
, _namespace(namespace_)
, _name(parentAssembly->AllocString(name))
, _namespace(parentAssembly->AllocString(namespace_))
, _fullname(parentAssembly->AllocString(fullname))
, _assembly(parentAssembly)
, _fullname(fullname)
, _hasCachedProperties(false)
, _hasCachedFields(false)
, _hasCachedMethods(false)
@@ -967,6 +968,8 @@ MClass::MClass(const MAssembly* parentAssembly, void* handle, const char* name,
static void* TypeIsEnumPtr = GetStaticMethodPointer(TEXT("TypeIsEnum"));
_isEnum = CallStaticMethod<bool, void*>(TypeIsEnumPtr, handle);
_isGeneric = _fullname.FindLast('`') != -1;
CachedClassHandles[handle] = this;
}
@@ -982,24 +985,14 @@ bool MAssembly::ResolveMissingFile(String& assemblyPath) const
MClass::~MClass()
{
_methods.ClearDelete();
_fields.ClearDelete();
_properties.ClearDelete();
_events.ClearDelete();
ArenaAllocator::ClearDelete(_methods);
ArenaAllocator::ClearDelete(_fields);
ArenaAllocator::ClearDelete(_properties);
ArenaAllocator::ClearDelete(_events);
CachedClassHandles.Remove(_handle);
}
StringAnsiView MClass::GetName() const
{
return _name;
}
StringAnsiView MClass::GetNamespace() const
{
return _namespace;
}
MType* MClass::GetType() const
{
return (MType*)_handle;
@@ -1071,10 +1064,11 @@ const Array<MMethod*>& MClass::GetMethods() const
static void* GetClassMethodsPtr = GetStaticMethodPointer(TEXT("GetClassMethods"));
CallStaticMethod<void, void*, NativeMethodDefinitions**, int*>(GetClassMethodsPtr, _handle, &methods, &methodsCount);
_methods.Resize(methodsCount);
MAssembly* assembly = const_cast<MAssembly*>(_assembly);
for (int32 i = 0; i < methodsCount; i++)
{
NativeMethodDefinitions& definition = methods[i];
MMethod* method = New<MMethod>(const_cast<MClass*>(this), StringAnsi(definition.name), definition.handle, definition.numParameters, definition.methodAttributes);
MMethod* method = assembly->Memory.New<MMethod>(const_cast<MClass*>(this), assembly->AllocString(definition.name), definition.handle, definition.numParameters, definition.methodAttributes);
_methods[i] = method;
MCore::GC::FreeMemory((void*)definition.name);
}
@@ -1112,7 +1106,7 @@ const Array<MField*>& MClass::GetFields() const
for (int32 i = 0; i < numFields; i++)
{
NativeFieldDefinitions& definition = fields[i];
MField* field = New<MField>(const_cast<MClass*>(this), definition.fieldHandle, definition.name, definition.fieldType, definition.fieldOffset, definition.fieldAttributes);
MField* field = _assembly->Memory.New<MField>(const_cast<MClass*>(this), definition.fieldHandle, definition.name, definition.fieldType, definition.fieldOffset, definition.fieldAttributes);
_fields[i] = field;
MCore::GC::FreeMemory((void*)definition.name);
}
@@ -1162,7 +1156,7 @@ const Array<MProperty*>& MClass::GetProperties() const
for (int i = 0; i < numProperties; i++)
{
const NativePropertyDefinitions& definition = foundProperties[i];
MProperty* property = New<MProperty>(const_cast<MClass*>(this), definition.name, definition.propertyHandle, definition.getterHandle, definition.setterHandle, definition.getterAttributes, definition.setterAttributes);
MProperty* property = _assembly->Memory.New<MProperty>(const_cast<MClass*>(this), definition.name, definition.propertyHandle, definition.getterHandle, definition.setterHandle, definition.getterAttributes, definition.setterAttributes);
_properties[i] = property;
MCore::GC::FreeMemory((void*)definition.name);
}
@@ -1241,7 +1235,7 @@ MEvent::MEvent(MClass* parentClass, void* handle, const char* name)
, _addMethod(nullptr)
, _removeMethod(nullptr)
, _parentClass(parentClass)
, _name(name)
, _name(parentClass->GetAssembly()->AllocString(name))
, _hasCachedAttributes(false)
, _hasAddMonoMethod(true)
, _hasRemoveMonoMethod(true)
@@ -1317,7 +1311,7 @@ MField::MField(MClass* parentClass, void* handle, const char* name, void* type,
, _type(type)
, _fieldOffset(fieldOffset)
, _parentClass(parentClass)
, _name(name)
, _name(parentClass->GetAssembly()->AllocString(name))
, _hasCachedAttributes(false)
{
switch (attributes & MFieldAttributes::FieldAccessMask)
@@ -1409,11 +1403,11 @@ const Array<MObject*>& MField::GetAttributes() const
return _attributes;
}
MMethod::MMethod(MClass* parentClass, StringAnsi&& name, void* handle, int32 paramsCount, MMethodAttributes attributes)
MMethod::MMethod(MClass* parentClass, StringAnsiView name, void* handle, int32 paramsCount, MMethodAttributes attributes)
: _handle(handle)
, _paramsCount(paramsCount)
, _parentClass(parentClass)
, _name(MoveTemp(name))
, _name(name)
, _hasCachedAttributes(false)
, _hasCachedSignature(false)
{
@@ -1443,13 +1437,15 @@ MMethod::MMethod(MClass* parentClass, StringAnsi&& name, void* handle, int32 par
_isStatic = (attributes & MMethodAttributes::Static) == MMethodAttributes::Static;
#if COMPILE_WITH_PROFILER
const StringAnsi& className = parentClass->GetFullName();
ProfilerName.Resize(className.Length() + 2 + _name.Length());
Platform::MemoryCopy(ProfilerName.Get(), className.Get(), className.Length());
ProfilerName.Get()[className.Length()] = ':';
ProfilerName.Get()[className.Length() + 1] = ':';
Platform::MemoryCopy(ProfilerName.Get() + className.Length() + 2, _name.Get(), _name.Length());
ProfilerData.name = ProfilerName.Get();
// Setup Tracy profiler entry (use assembly memory)
const StringAnsiView className = parentClass->GetFullName();
char* profilerName = (char*)parentClass->GetAssembly()->Memory.Allocate(className.Length() + _name.Length() + 3);
Platform::MemoryCopy(profilerName, className.Get(), className.Length());
profilerName[className.Length()] = ':';
profilerName[className.Length() + 1] = ':';
Platform::MemoryCopy(profilerName + className.Length() + 2, _name.Get(), _name.Length());
profilerName[className.Length() + 2 + _name.Length()] = 0;
ProfilerData.name = profilerName;
ProfilerData.function = _name.Get();
ProfilerData.file = nullptr;
ProfilerData.line = 0;
@@ -1573,20 +1569,30 @@ const Array<MObject*>& MMethod::GetAttributes() const
return _attributes;
}
FORCE_INLINE StringAnsiView GetPropertyMethodName(MProperty* property, StringAnsiView prefix)
{
StringAnsiView name = property->GetName();
char* mem = (char*)property->GetParentClass()->GetAssembly()->Memory.Allocate(name.Length() + prefix.Length() + 1);
Platform::MemoryCopy(mem, prefix.Get(), prefix.Length());
Platform::MemoryCopy(mem + prefix.Length(), name.Get(), name.Length());
mem[name.Length() + prefix.Length()] = 0;
return StringAnsiView(mem, name.Length() + prefix.Length() + 1);
}
MProperty::MProperty(MClass* parentClass, const char* name, void* handle, void* getterHandle, void* setterHandle, MMethodAttributes getterAttributes, MMethodAttributes setterAttributes)
: _parentClass(parentClass)
, _name(name)
, _name(parentClass->GetAssembly()->AllocString(name))
, _handle(handle)
, _hasCachedAttributes(false)
{
_hasGetMethod = getterHandle != nullptr;
if (_hasGetMethod)
_getMethod = New<MMethod>(parentClass, StringAnsi("get_" + _name), getterHandle, 0, getterAttributes);
_getMethod = parentClass->GetAssembly()->Memory.New<MMethod>(parentClass, GetPropertyMethodName(this, StringAnsiView("get_", 4)), getterHandle, 0, getterAttributes);
else
_getMethod = nullptr;
_hasSetMethod = setterHandle != nullptr;
if (_hasSetMethod)
_setMethod = New<MMethod>(parentClass, StringAnsi("set_" + _name), setterHandle, 1, setterAttributes);
_setMethod = parentClass->GetAssembly()->Memory.New<MMethod>(parentClass, GetPropertyMethodName(this, StringAnsiView("set_", 4)), setterHandle, 1, setterAttributes);
else
_setMethod = nullptr;
}
@@ -1594,9 +1600,9 @@ MProperty::MProperty(MClass* parentClass, const char* name, void* handle, void*
MProperty::~MProperty()
{
if (_getMethod)
Delete(_getMethod);
Memory::DestructItem(_getMethod);
if (_setMethod)
Delete(_setMethod);
Memory::DestructItem(_setMethod);
}
MMethod* MProperty::GetGetMethod() const
@@ -1683,7 +1689,7 @@ MClass* GetOrCreateClass(MType* typeHandle)
static void* GetManagedClassFromTypePtr = GetStaticMethodPointer(TEXT("GetManagedClassFromType"));
CallStaticMethod<void, void*, void*>(GetManagedClassFromTypePtr, typeHandle, &classInfo, &assemblyHandle);
MAssembly* assembly = GetAssembly(assemblyHandle);
klass = New<MClass>(assembly, classInfo.typeHandle, classInfo.name, classInfo.fullname, classInfo.namespace_, classInfo.typeAttributes);
klass = assembly->Memory.New<MClass>(assembly, classInfo.typeHandle, classInfo.name, classInfo.fullname, classInfo.namespace_, classInfo.typeAttributes);
if (assembly != nullptr)
{
auto& classes = const_cast<MAssembly::ClassesDictionary&>(assembly->GetClasses());
@@ -1889,7 +1895,7 @@ void ShutdownHostfxr()
{
}
void* GetStaticMethodPointer(const String& methodName)
void* GetStaticMethodPointer(StringView methodName)
{
void* fun;
if (CachedFunctions.TryGet(methodName, fun))