FastExpressionCompiler icon indicating copy to clipboard operation
FastExpressionCompiler copied to clipboard

Hack DynamicMethod

Open dadhi opened this issue 1 year ago • 9 comments

  • Hack its construction
  • Hack CreateDelegate in order to reuse the method again.

dadhi avatar Oct 03 '22 06:10 dadhi

I've actually done some experiments with this before. By re-using DynamicMethod and IlGenerators I could reduce (on mono) allocations per delegate creation by 12. Performance wasn't directly impacted that much but since there's a lot of contention for the memory allocator (at least on the version of Mono inside Unity3d) I got some pretty big throughput increases while multi-threading compilation of many delegates.

weichx avatar Oct 03 '22 13:10 weichx

@weichx Wow, how did you do that, can you share?

At the moment, the most promising part for me is to reuse byte codes byte[] array here:

            internal void EnsureCapacity(int size)
            {
                if (m_length + size >= m_ILStream.Length)
                    IncreaseCapacity(size);
            }
            private void IncreaseCapacity(int size)
            {
                byte[] temp = new byte[Math.Max(m_ILStream.Length * 2, m_length + size)]; // todo: @perf how to reuse existing ILStream here
                Array.Copy(m_ILStream, temp, m_ILStream.Length);
                m_ILStream = temp;
            }

dadhi avatar Oct 03 '22 13:10 dadhi

That was just a little prototype so I unfortunately don't have the code anymore but the gist of it was directly calling the internal C# runtime methods via reflection (or compiled expression tree). This let me skip all the api overhead that the .NET library introduces.

// this is a method defined in DynamicMethod.cs in the runtime library
// You can find it via reflection.
// I called this via reflection after setting all the properties on `m` 
// which was reused across compilations
private static extern void create_dynamic_method (DynamicMethod m);

All of the C# -> C runtime methods have static extern C# methods that are marked private or internal. Its just a matter of finding them and calling them yourself. I did the same thing for finding fields/properties/methods via reflection without allocating. The code below won't compile for you since it uses some of my utilities but hopefully it illustrates the idea.

public static unsafe class MonoUtil {

        private static Action<IntPtr> s_DisposePtrFn;
        private static Func<MethodInfo, ParameterInfo[]> s_GetParametersFn;
        private static Func<Type, IntPtr, int, BindingFlags, IntPtr> s_GetMethodsByNameFn;
        private static Func<Type, RuntimeTypeHandle> s_CreateRuntimeTypeHandleFn;
        private static Func<IntPtr, RuntimeTypeHandle, MethodBase> s_CreateMethodInfo;
        private static Func<IntPtr, RuntimeTypeHandle, FieldInfo> s_CreateFieldInfo;
        private static Func<IntPtr, RuntimeTypeHandle, PropertyInfo> s_CreatePropertyInfo;

        private static Func<Type, IntPtr, int, BindingFlags, IntPtr> s_GetFieldsByNameFn;
        private static Func<Type, IntPtr, int, BindingFlags, IntPtr> s_GetPropertiesByNameFn;

        // mirrors mono definition
        private enum MemberListType {

            All,
            CaseSensitive,
            CaseInsensitive,
            HandleToInfo

        }

        public static void Initialize() {
            CreateUtilFunctions();
        }

        public static int GetFields(Type type, BindingFlags bindingFlags, IList<FieldInfo> result) {
            return GetFields(type, default(FixedCharacterSpan), bindingFlags, result);
        }

        public static int GetFields(Type type, string fieldName, BindingFlags bindingFlags, IList<FieldInfo> result) {

            if (string.IsNullOrEmpty(fieldName)) {
                return GetFields(type, default(FixedCharacterSpan), bindingFlags, result);
            }

            fixed (char* cbuffer = fieldName) {
                return GetFields(type, new FixedCharacterSpan(cbuffer, fieldName.Length), bindingFlags, result);
            }
        }

        internal struct GPtrArray {

            internal IntPtr* data;
            internal int len;

        }

        internal static int GetFields(Type type, FixedCharacterSpan fieldName, BindingFlags bindingFlags, IList<FieldInfo> result) {

            IntPtr ptr = default;
            if (fieldName.size != 0) {
                int utf8Size = (fieldName.size * 2) + 4; // buffer a bit extra just in case 
                byte* cbuffer = stackalloc byte[utf8Size];
                CopyError error = UTF8ArrayUnsafeUtility.Copy(cbuffer, out _, utf8Size, fieldName.ptr, fieldName.size);
                if (error == CopyError.Truncation) {
                    cbuffer = TypedUnsafe.Malloc<byte>(2 * utf8Size, Allocator.Temp);
                    UTF8ArrayUnsafeUtility.Copy(cbuffer, out _, 2 * utf8Size, fieldName.ptr, fieldName.size);
                }

                ptr = new IntPtr(cbuffer);
            }

            IntPtr nativeMethodHandle = GetFieldsByName(type, ptr, bindingFlags);
            RuntimeTypeHandle runtimeTypeHandle = CreateRuntimeTypeHandle(type);
            GPtrArray* ptrArray = (GPtrArray*)nativeMethodHandle;

            for (int i = 0; i < ptrArray->len; i++) {
                result.Add(s_CreateFieldInfo(ptrArray->data[i], runtimeTypeHandle));
            }

            int retn = ptrArray->len;
            DisposeMonoArray(nativeMethodHandle);
            return retn;
        }

        public static int GetProperties(Type type, BindingFlags bindingFlags, IList<PropertyInfo> result) {
            return GetProperties(type, default(FixedCharacterSpan), bindingFlags, result);
        }

        public static int GetProperties(Type type, string propertyName, BindingFlags bindingFlags, IList<PropertyInfo> result) {

            if (string.IsNullOrEmpty(propertyName)) {
                return GetProperties(type, default(FixedCharacterSpan), bindingFlags, result);
            }

            fixed (char* cbuffer = propertyName) {
                return GetProperties(type, new FixedCharacterSpan(cbuffer, propertyName.Length), bindingFlags, result);
            }
        }

        internal static int GetProperties(Type type, FixedCharacterSpan propertyName, BindingFlags bindingFlags, IList<PropertyInfo> result) {

            IntPtr ptr = default;
            if (propertyName.size != 0) {
                int utf8Size = (propertyName.size * 2) + 4; // buffer a bit extra just in case 
                byte* cbuffer = stackalloc byte[utf8Size];
                CopyError error = UTF8ArrayUnsafeUtility.Copy(cbuffer, out _, utf8Size, propertyName.ptr, propertyName.size);
                if (error == CopyError.Truncation) {
                    cbuffer = TypedUnsafe.Malloc<byte>(2 * utf8Size, Allocator.Temp);
                    UTF8ArrayUnsafeUtility.Copy(cbuffer, out _, 2 * utf8Size, propertyName.ptr, propertyName.size);
                }

                ptr = new IntPtr(cbuffer);
            }

            IntPtr nativeMethodHandle = GetPropertiesByName(type, ptr, bindingFlags);
            RuntimeTypeHandle runtimeTypeHandle = CreateRuntimeTypeHandle(type);
            GPtrArray* ptrArray = (GPtrArray*)nativeMethodHandle;

            for (int i = 0; i < ptrArray->len; i++) {
                result.Add(s_CreatePropertyInfo(ptrArray->data[i], runtimeTypeHandle));
            }

            int retn = ptrArray->len;
            DisposeMonoArray(nativeMethodHandle);
            return retn;
        }

        internal static int GetMethods(Type type, IList<MethodInfo> result, BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static) {
            return GetMethods(type, default(FixedCharacterSpan), result, bindingFlags);
        }

        public static int GetMethods(Type type, string methodName, IList<MethodInfo> result, BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static) {

            if (string.IsNullOrEmpty(methodName)) {
                return GetMethods(type, default(FixedCharacterSpan), result, bindingFlags);
            }

            fixed (char* cbuffer = methodName) {
                return GetMethods(type, new FixedCharacterSpan(cbuffer, methodName.Length), result, bindingFlags);
            }

        }

        internal static int GetMethods(Type type, FixedCharacterSpan methodName, IList<MethodInfo> result, BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static) {

            IntPtr ptr = default;
            if (methodName.size != 0) {
                int utf8Size = (methodName.size * 2) + 4; // buffer a bit extra just in case 
                byte* cbuffer = stackalloc byte[utf8Size];
                CopyError error = UTF8ArrayUnsafeUtility.Copy(cbuffer, out _, utf8Size, methodName.ptr, methodName.size);
                if (error == CopyError.Truncation) {
                    cbuffer = TypedUnsafe.Malloc<byte>(2 * utf8Size, Allocator.Temp);
                    UTF8ArrayUnsafeUtility.Copy(cbuffer, out _, 2 * utf8Size, methodName.ptr, methodName.size);
                }

                ptr = new IntPtr(cbuffer);
            }

            IntPtr nativeMethodHandle = GetMethodsByName(type, ptr, bindingFlags);
            RuntimeTypeHandle runtimeTypeHandle = CreateRuntimeTypeHandle(type);
            GPtrArray* ptrArray = (GPtrArray*)nativeMethodHandle;

            for (int i = 0; i < ptrArray->len; i++) {
                result.Add((MethodInfo)s_CreateMethodInfo(ptrArray->data[i], runtimeTypeHandle));
            }

            int retn = ptrArray->len;
            DisposeMonoArray(nativeMethodHandle);
            return retn;
        }

        private static MethodInfo CreateRuntimeMethodHandle(IntPtr ptr, RuntimeTypeHandle typeHandle) {
            return (MethodInfo)s_CreateMethodInfo(ptr, typeHandle);
        }

        private static void CreateUtilFunctions() {
            {
                Type monoMethodInfoType = typeof(MethodInfo).Assembly.GetType("System.Reflection.MonoMethodInfo");

                MethodInfo getparams = monoMethodInfoType.GetMethod("GetParametersInfo", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);

                ParameterExpression methodInfoParam = Expression.Parameter(typeof(MethodInfo));

                PropertyInfo methodHandle = typeof(MethodInfo).GetProperty(nameof(MethodInfo.MethodHandle));
                PropertyInfo value = methodHandle.PropertyType.GetProperty(nameof(MethodInfo.MethodHandle.Value));

                MemberExpression handleProp = Expression.Property(methodInfoParam, methodHandle);
                MemberExpression ptrValue = Expression.Property(handleProp, value);

                s_GetParametersFn = Expression.Lambda<Func<MethodInfo, ParameterInfo[]>>(Expression.Call(null, getparams, ptrValue, methodInfoParam), new[] { methodInfoParam }).Compile();
            }

            Type runtimeMethodInfoType = typeof(Action).Assembly.GetType("System.Reflection.RuntimeMethodInfo");
            Type runtimePropertyInfoType = typeof(Action).Assembly.GetType("System.Reflection.RuntimePropertyInfo");
            Type runtimePropertyHandleType = typeof(Action).Assembly.GetType("Mono.RuntimePropertyHandle");

            ConstructorInfo runtimeMethodHandleCtor = typeof(RuntimeMethodHandle).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance)[0];
            ConstructorInfo runtimeFieldHandleCtor = typeof(RuntimeFieldHandle).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance)[0];
            ConstructorInfo runtimePropertyHandleCtor = runtimePropertyHandleType.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance)[0];

            MethodInfo[] runtimeMethodInfoMethods = runtimeMethodInfoType.GetMethods(BindingFlags.NonPublic | BindingFlags.Static);
            MethodInfo[] fieldInfoMethods = typeof(FieldInfo).GetMethods(BindingFlags.Public | BindingFlags.Static);
            MethodInfo[] runtimePropertyInfoMethods = runtimePropertyInfoType.GetMethods(BindingFlags.NonPublic | BindingFlags.Static);

            MethodInfo methodInfoGetMethodFromHandle = null;
            MethodInfo fieldInfoGetFieldFromHandle = null;
            MethodInfo propertyInfoGetPropertyFromHandle = null;

            for (int i = 0; i < runtimeMethodInfoMethods.Length; i++) {
                if (runtimeMethodInfoMethods[i].Name == "GetMethodFromHandleNoGenericCheck" && GetImmutableParameters(runtimeMethodInfoMethods[i]).Length == 2) {
                    methodInfoGetMethodFromHandle = runtimeMethodInfoMethods[i];
                    break;
                }
            }

            for (int i = 0; i < fieldInfoMethods.Length; i++) {
                if (fieldInfoMethods[i].Name == "GetFieldFromHandle" && GetImmutableParameters(fieldInfoMethods[i]).Length == 2) {
                    fieldInfoGetFieldFromHandle = fieldInfoMethods[i];
                    break;
                }
            }

            for (int i = 0; i < runtimePropertyInfoMethods.Length; i++) {
                if (runtimePropertyInfoMethods[i].Name == "GetPropertyFromHandle" && GetImmutableParameters(runtimePropertyInfoMethods[i]).Length == 2) {
                    propertyInfoGetPropertyFromHandle = runtimePropertyInfoMethods[i];
                    break;
                }
            }

            ParameterExpression[] parameters = new[] {
                Expression.Parameter(typeof(IntPtr)),
                Expression.Parameter(typeof(RuntimeTypeHandle)),
            };

            s_CreateMethodInfo = Expression.Lambda<Func<IntPtr, RuntimeTypeHandle, MethodBase>>(Expression.Call(null, methodInfoGetMethodFromHandle, Expression.New(runtimeMethodHandleCtor, parameters[0]), parameters[1]), parameters).Compile();

            s_CreateFieldInfo = Expression.Lambda<Func<IntPtr, RuntimeTypeHandle, FieldInfo>>(Expression.Call(null, fieldInfoGetFieldFromHandle, Expression.New(runtimeFieldHandleCtor, parameters[0]), parameters[1]), parameters).Compile();

            s_CreatePropertyInfo = Expression.Lambda<Func<IntPtr, RuntimeTypeHandle, PropertyInfo>>(Expression.Call(null, propertyInfoGetPropertyFromHandle, Expression.New(runtimePropertyHandleCtor, parameters[0]), parameters[1]), parameters).Compile();

            Type safeGPtrArrayHandle = typeof(Action).Assembly.GetType("Mono.SafeGPtrArrayHandle");
            ParameterExpression intPtrParam = Expression.Parameter(typeof(IntPtr));
            ConstructorInfo safeHandleCtor = safeGPtrArrayHandle.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance)[0];
            // new SafeGPtrArrayHandle(ptr).Dispose();  this lets me invoke the dispose w/o dealing w/ pointers which I can't do via expr trees
            s_DisposePtrFn = Expression.Lambda<Action<IntPtr>>(Expression.Call(
                Expression.New(safeHandleCtor, intPtrParam),
                safeGPtrArrayHandle.GetMethod("Dispose", BindingFlags.Instance | BindingFlags.Public)
            ), intPtrParam).Compile();

            Type systemRuntimeType = typeof(Action).Assembly.GetType("System.RuntimeType");

            ConstructorInfo rtHandleCtor = typeof(RuntimeTypeHandle).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { systemRuntimeType }, null);
            ParameterExpression parameter = Expression.Parameter(typeof(Type), "type");

            s_CreateRuntimeTypeHandleFn = Expression.Lambda<Func<Type, RuntimeTypeHandle>>(
                Expression.New(rtHandleCtor, Expression.Convert(parameter, systemRuntimeType)),
                parameter
            ).Compile();

            MethodInfo getFields_native = systemRuntimeType.GetMethod("GetFields_native", BindingFlags.NonPublic | BindingFlags.Instance);
            MethodInfo getProperties_native = systemRuntimeType.GetMethod("GetPropertiesByName_native", BindingFlags.NonPublic | BindingFlags.Instance);
            MethodInfo getConstructors_native = systemRuntimeType.GetMethod("GetConstructors_native", BindingFlags.NonPublic | BindingFlags.Instance);

            MethodInfo getMethodsByName_native = systemRuntimeType.GetMethod("GetMethodsByName_native", BindingFlags.NonPublic | BindingFlags.Instance);
            Type memberListType = typeof(Action).Assembly.GetType("System.RuntimeType+MemberListType");

            ParameterExpression typeParameter = Expression.Parameter(typeof(Type));
            ParameterExpression nameParameter = Expression.Parameter(typeof(IntPtr));
            ParameterExpression memberTypeListVal = Expression.Parameter(typeof(int));
            ParameterExpression bindingFlags = Expression.Parameter(typeof(BindingFlags));

            // ConstantExpression bindingFlags = Expression.Constant(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);

            ParameterExpression[] paramList3 = new[] { typeParameter, nameParameter, memberTypeListVal };
            ParameterExpression[] paramList4 = new[] { typeParameter, nameParameter, memberTypeListVal, bindingFlags };

            s_GetMethodsByNameFn = Expression.Lambda<Func<Type, IntPtr, int, BindingFlags, IntPtr>>(Expression.Call(
                Expression.Convert(typeParameter, systemRuntimeType),
                getMethodsByName_native,
                nameParameter,
                bindingFlags,
                Expression.Convert(memberTypeListVal, memberListType)
            ), paramList4).Compile();

            s_GetFieldsByNameFn = Expression.Lambda<Func<Type, IntPtr, int, BindingFlags, IntPtr>>(Expression.Call(
                Expression.Convert(typeParameter, systemRuntimeType),
                getFields_native,
                nameParameter,
                bindingFlags,
                Expression.Convert(memberTypeListVal, memberListType)
            ), paramList4).Compile();

            s_GetPropertiesByNameFn = Expression.Lambda<Func<Type, IntPtr, int, BindingFlags, IntPtr>>(Expression.Call(
                Expression.Convert(typeParameter, systemRuntimeType),
                getProperties_native,
                nameParameter,
                bindingFlags,
                Expression.Convert(memberTypeListVal, memberListType)
            ), paramList4).Compile();

        }

        public static void DisposeMonoArray(IntPtr ptr) {
            s_DisposePtrFn(ptr);
        }

        public static RuntimeTypeHandle CreateRuntimeTypeHandle(Type type) {
            return s_CreateRuntimeTypeHandleFn(type);
        }

        private static IntPtr GetMethodsByName(Type type, IntPtr methodName, BindingFlags bindingFlags) {
            return s_GetMethodsByNameFn(type, methodName, methodName.ToInt64() == 0 ? (int)MemberListType.All : (int)MemberListType.CaseSensitive, bindingFlags);
        }

        private static IntPtr GetFieldsByName(Type type, IntPtr fieldName, BindingFlags bindingFlags) {
            return s_GetFieldsByNameFn(type, fieldName, fieldName.ToInt64() == 0 ? (int)MemberListType.All : (int)MemberListType.CaseSensitive, bindingFlags);
        }

        private static IntPtr GetPropertiesByName(Type type, IntPtr fieldName, BindingFlags bindingFlags) {
            return s_GetPropertiesByNameFn(type, fieldName, fieldName.ToInt64() == 0 ? (int)MemberListType.All : (int)MemberListType.CaseSensitive, bindingFlags);
        }

        public static ParameterInfo[] GetImmutableParameters(MethodBase methodBase) {
            if (methodBase is ConstructorInfo ctor) {
                return GetImmutableParameters(ctor);
            }

            return GetImmutableParameters((MethodInfo)methodBase);
        }

        public static ParameterInfo[] GetImmutableParameters(MethodInfo methodInfo) {
            // this proooobablly needs a lock or the caller needs to be locked

            // this is calling directly into mono to get at the parameter list that it uses internally
            // which prevents an array allocation & copy. Because we use their direct copy we CANNOT
            // mutate that array or weird shit will start happening. 
            return s_GetParametersFn.Invoke(methodInfo);
        }

        internal static bool TryGetFieldByName(Type type, string fieldName, BindingFlags bindingFlags, out FieldInfo fieldInfo) {
            fixed (char* cbuffer = fieldName) {
                return TryGetFieldByName(type, new FixedCharacterSpan(cbuffer, fieldName.Length), bindingFlags, out fieldInfo);
            }
        }

        internal static bool TryGetFieldByName(Type type, FixedCharacterSpan fieldName, BindingFlags bindingFlags, out FieldInfo fieldInfo) {
            IntPtr ptr = default;
            if (fieldName.size != 0) {
                int utf8Size = (fieldName.size * 2) + 4; // buffer a bit extra just in case 
                byte* cbuffer = stackalloc byte[utf8Size];
                CopyError error = UTF8ArrayUnsafeUtility.Copy(cbuffer, out _, utf8Size, fieldName.ptr, fieldName.size);
                if (error == CopyError.Truncation) {
                    cbuffer = TypedUnsafe.Malloc<byte>(2 * utf8Size, Allocator.Temp);
                    UTF8ArrayUnsafeUtility.Copy(cbuffer, out _, 2 * utf8Size, fieldName.ptr, fieldName.size);
                }

                ptr = new IntPtr(cbuffer);
            }

            IntPtr nativeMethodHandle = GetFieldsByName(type, ptr, bindingFlags);
            RuntimeTypeHandle runtimeTypeHandle = CreateRuntimeTypeHandle(type);
            GPtrArray* ptrArray = (GPtrArray*)nativeMethodHandle;

            if (ptrArray->len >= 1) {
                // return first match, I don't know when there would ever be more than 1. Maybe w/ base types & shadowing?
                fieldInfo = s_CreateFieldInfo(ptrArray->data[0], runtimeTypeHandle);
                DisposeMonoArray(nativeMethodHandle);
                return true;
            }

            fieldInfo = default;
            DisposeMonoArray(nativeMethodHandle);
            return false;
        }

        internal static bool TryGetPropertyByName(Type type, string propertyName, BindingFlags bindingFlags, out PropertyInfo propertyInfo) {
            fixed (char* cbuffer = propertyName) {
                return TryGetPropertyByName(type, new FixedCharacterSpan(cbuffer, propertyName.Length), bindingFlags, out propertyInfo);
            }
        }

        internal static bool TryGetPropertyByName(Type type, FixedCharacterSpan propertyName, BindingFlags bindingFlags, out PropertyInfo propertyInfo) {
            IntPtr ptr = default;
            if (propertyName.size != 0) {
                int utf8Size = (propertyName.size * 2) + 4; // buffer a bit extra just in case 
                byte* cbuffer = stackalloc byte[utf8Size];
                CopyError error = UTF8ArrayUnsafeUtility.Copy(cbuffer, out _, utf8Size, propertyName.ptr, propertyName.size);
                if (error == CopyError.Truncation) {
                    cbuffer = TypedUnsafe.Malloc<byte>(2 * utf8Size, Allocator.Temp);
                    UTF8ArrayUnsafeUtility.Copy(cbuffer, out _, 2 * utf8Size, propertyName.ptr, propertyName.size);
                }

                ptr = new IntPtr(cbuffer);
            }

            IntPtr nativeMethodHandle = GetPropertiesByName(type, ptr, bindingFlags);
            RuntimeTypeHandle runtimeTypeHandle = CreateRuntimeTypeHandle(type);
            GPtrArray* ptrArray = (GPtrArray*)nativeMethodHandle;

            if (ptrArray->len >= 1) {
                // return first match, I don't know when there would ever be more than 1. Maybe w/ base types & shadowing?
                propertyInfo = s_CreatePropertyInfo(ptrArray->data[0], runtimeTypeHandle);
                DisposeMonoArray(nativeMethodHandle);
                return true;
            }

            propertyInfo = default;
            DisposeMonoArray(nativeMethodHandle);
            return false;
        }

     
        // This will return enumValues and enumNames sorted by the values.
        public static void GetEnumData(Type type, out string[] enumNames, out Array enumValues) {
            FieldInfo[] flds = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);

            object[] values = new object[flds.Length];
            string[] names = new string[flds.Length];

            for (int i = 0; i < flds.Length; i++) {
                names[i] = flds[i].Name;
                values[i] = flds[i].GetRawConstantValue();
            }

            enumNames = names;
            enumValues = values;
        }

    }

weichx avatar Oct 03 '22 14:10 weichx

@weichx Thanks for sharing.. will see what can I steal from it :)

dadhi avatar Oct 03 '22 15:10 dadhi

@weichx May be you tried it?... I was thinking, what is the fastest route from the byte[] array to the method. I am looking at something like https://source.dot.net/#System.Private.CoreLib/src/System/Reflection/Emit/DynamicILGenerator.cs,901

dadhi avatar Oct 05 '22 10:10 dadhi

probably just getting the m_Code field yourself and not using the methods. Also be aware that the Mono implementation looks different, there is a very good chance you'll need two different implementations of this or one of the platforms won't work.

weichx avatar Oct 05 '22 13:10 weichx

If you'd like to use private/internal methods without reflection, this maybe usefull: https://github.com/aelij/IgnoresAccessChecksToGenerator

jogibear9988 avatar Aug 20 '23 17:08 jogibear9988

Use #375 in .NET 8

dadhi avatar Nov 08 '23 10:11 dadhi

Nope for the #375 until UnsafeAccessorTypeAttribute is supported (likely in .NET 9)

dadhi avatar Nov 08 '23 11:11 dadhi