@@ -15,9 +15,12 @@ public class EmitHacksTest : ITest
1515 public int Run ( )
1616 {
1717 DynamicMethod_Emit_Hack ( ) ;
18- // DynamicMethod_Emit_Newobj();
19- // DynamicMethod_Hack_Emit_Newobj();
18+ #if NET10_0_OR_GREATER
19+ DynamicMethod_Emit_Hack_Net10 ( ) ;
20+ return 4 ;
21+ #else
2022 return 3 ;
23+ #endif
2124 }
2225
2326 public void DynamicMethod_Emit_Hack ( )
@@ -27,18 +30,27 @@ public void DynamicMethod_Emit_Hack()
2730 Asserts . AreEqual ( 42 , a ) ;
2831 }
2932
30- static Type ilType = typeof ( ILGenerator ) . Assembly . GetType ( "System.Reflection.Emit.DynamicILGenerator" ) ;
31- static FieldInfo mScopeField = ilType . GetField ( "m_scope" , BindingFlags . Instance | BindingFlags . NonPublic ) ;
33+ static readonly Type ilType = typeof ( ILGenerator ) . Assembly . GetType ( "System.Reflection.Emit.DynamicILGenerator" ) ;
3234
33- static Type scopeType = ilType . Assembly . GetType ( "System.Reflection.Emit.DynamicScope" ) ;
34- static FieldInfo mTokensField = scopeType . GetField ( "m_tokens" , BindingFlags . Instance | BindingFlags . NonPublic ) ;
35+ // m_scope field is on DynamicILGenerator (internal class) - accessed via reflection since
36+ // the field type DynamicScope is also internal (UnsafeAccessorType can't return non-public types).
37+ static readonly FieldInfo mScopeField = ilType ? . GetField ( "m_scope" , BindingFlags . Instance | BindingFlags . NonPublic ) ;
3538
36- static FieldInfo mLengthField = typeof ( ILGenerator ) . GetField ( "m_length" , BindingFlags . Instance | BindingFlags . NonPublic ) ;
37- static FieldInfo mILStreamField = typeof ( ILGenerator ) . GetField ( "m_ILStream" , BindingFlags . Instance | BindingFlags . NonPublic ) ;
38- static MethodInfo updateStackSize = typeof ( ILGenerator ) . GetMethod ( "UpdateStackSize" , BindingFlags . Instance | BindingFlags . NonPublic ) ;
39+ static readonly Type scopeType = ilType ? . Assembly . GetType ( "System.Reflection.Emit.DynamicScope" ) ;
40+ static readonly FieldInfo mTokensField = scopeType ? . GetField ( "m_tokens" , BindingFlags . Instance | BindingFlags . NonPublic ) ;
41+
42+ // m_length, m_ILStream, and UpdateStackSize are on RuntimeILGenerator (the internal base class of DynamicILGenerator),
43+ // NOT on the public ILGenerator class. Look up the fields on the correct type.
44+ static readonly Type runtimeILGenType = ilType ? . BaseType ; // System.Reflection.Emit.RuntimeILGenerator
45+ static readonly FieldInfo mLengthField = runtimeILGenType ? . GetField ( "m_length" , BindingFlags . Instance | BindingFlags . NonPublic | BindingFlags . DeclaredOnly ) ;
46+ static readonly FieldInfo mILStreamField = runtimeILGenType ? . GetField ( "m_ILStream" , BindingFlags . Instance | BindingFlags . NonPublic | BindingFlags . DeclaredOnly ) ;
47+ static readonly MethodInfo updateStackSizeMethod = runtimeILGenType ? . GetMethod ( "UpdateStackSize" , BindingFlags . Instance | BindingFlags . NonPublic ) ;
3948
4049 private static Func < ILGenerator , IList < object > > GetScopeTokens ( )
4150 {
51+ if ( mScopeField == null || mTokensField == null )
52+ return null ;
53+
4254 var dynMethod = new DynamicMethod ( string . Empty ,
4355 typeof ( IList < object > ) , new [ ] { typeof ( ExpressionCompiler . ArrayClosure ) , typeof ( ILGenerator ) } ,
4456 typeof ( ExpressionCompiler ) , skipVisibility : true ) ;
@@ -57,6 +69,8 @@ private static Func<ILGenerator, IList<object>> GetScopeTokens()
5769
5870 private static GetFieldRefDelegate < TFieldHolder , TField > CreateFieldAccessor < TFieldHolder , TField > ( FieldInfo field )
5971 {
72+ if ( field == null ) return null ;
73+
6074 var dynMethod = new DynamicMethod ( string . Empty ,
6175 typeof ( TField ) . MakeByRefType ( ) , new [ ] { typeof ( ExpressionCompiler . ArrayClosure ) , typeof ( TFieldHolder ) } ,
6276 typeof ( TFieldHolder ) , skipVisibility : true ) ;
@@ -69,14 +83,34 @@ private static GetFieldRefDelegate<TFieldHolder, TField> CreateFieldAccessor<TFi
6983 return ( GetFieldRefDelegate < TFieldHolder , TField > ) dynMethod . CreateDelegate ( typeof ( GetFieldRefDelegate < TFieldHolder , TField > ) ) ;
7084 }
7185
72- static GetFieldRefDelegate < ILGenerator , int > mLengthFieldAccessor = CreateFieldAccessor < ILGenerator , int > ( mLengthField ) ;
73- static GetFieldRefDelegate < ILGenerator , byte [ ] > mILStreamAccessor = CreateFieldAccessor < ILGenerator , byte [ ] > ( mILStreamField ) ;
86+ static readonly GetFieldRefDelegate < ILGenerator , int > mLengthFieldAccessor = CreateFieldAccessor < ILGenerator , int > ( mLengthField ) ;
87+ static readonly GetFieldRefDelegate < ILGenerator , byte [ ] > mILStreamAccessor = CreateFieldAccessor < ILGenerator , byte [ ] > ( mILStreamField ) ;
88+
89+ static readonly Action < ILGenerator , OpCode , int > updateStackSizeDelegate = GetUpdateStackSizeDelegate ( ) ;
7490
75- static Action < ILGenerator , OpCode , int > updateStackSizeDelegate =
76- ( Action < ILGenerator , OpCode , int > ) Delegate . CreateDelegate ( typeof ( Action < ILGenerator , OpCode , int > ) , null , updateStackSize ) ;
91+ private static Action < ILGenerator , OpCode , int > GetUpdateStackSizeDelegate ( )
92+ {
93+ if ( updateStackSizeMethod == null ) return null ;
94+ // Cannot use Delegate.CreateDelegate with a method from a non-public declaring type (RuntimeILGenerator).
95+ // Instead, wrap the call in a DynamicMethod with skipVisibility: true.
96+ var dynMethod = new DynamicMethod ( string . Empty ,
97+ typeof ( void ) , new [ ] { typeof ( ExpressionCompiler . ArrayClosure ) , typeof ( ILGenerator ) , typeof ( OpCode ) , typeof ( int ) } ,
98+ typeof ( ExpressionCompiler ) , skipVisibility : true ) ;
99+ var il = dynMethod . GetILGenerator ( ) ;
100+ il . Emit ( OpCodes . Ldarg_1 ) ; // ILGenerator (runtime type: DynamicILGenerator : RuntimeILGenerator)
101+ il . Emit ( OpCodes . Ldarg_2 ) ; // OpCode
102+ il . Emit ( OpCodes . Ldarg_3 ) ; // int stackchange
103+ il . Emit ( OpCodes . Call , updateStackSizeMethod ) ;
104+ il . Emit ( OpCodes . Ret ) ;
105+ return ( Action < ILGenerator , OpCode , int > ) dynMethod . CreateDelegate (
106+ typeof ( Action < ILGenerator , OpCode , int > ) , ExpressionCompiler . EmptyArrayClosure ) ;
107+ }
77108
78109 public static Func < int , int > Get_DynamicMethod_Emit_Hack ( )
79110 {
111+ if ( mLengthFieldAccessor == null || mILStreamAccessor == null || updateStackSizeDelegate == null || getScopeTokens == null )
112+ return null ;
113+
80114 var meth = MethodStatic1Arg ;
81115 var paramCount = 1 ;
82116
@@ -128,6 +162,87 @@ public static Func<int, int> Get_DynamicMethod_Emit_Hack()
128162 return ( Func < int , int > ) dynMethod . CreateDelegate ( typeof ( Func < int , int > ) , ExpressionCompiler . EmptyArrayClosure ) ;
129163 }
130164
165+ #if NET10_0_OR_GREATER
166+ // In .NET 10+, use UnsafeAccessorType to access the private fields of non-public types directly,
167+ // without the DynamicMethod-based delegation used in earlier .NET versions.
168+ // RuntimeILGenerator is the internal base class of DynamicILGenerator that holds the IL stream state.
169+
170+ /// <summary>Directly accesses m_length on RuntimeILGenerator via UnsafeAccessorType (NET10+).</summary>
171+ [ UnsafeAccessor ( UnsafeAccessorKind . Field , Name = "m_length" ) ]
172+ private static extern ref int GetMLength_Net10 (
173+ [ UnsafeAccessorType ( "System.Reflection.Emit.RuntimeILGenerator" ) ] object il ) ;
174+
175+ /// <summary>Directly accesses m_ILStream on RuntimeILGenerator via UnsafeAccessorType (NET10+).</summary>
176+ [ UnsafeAccessor ( UnsafeAccessorKind . Field , Name = "m_ILStream" ) ]
177+ private static extern ref byte [ ] GetMILStream_Net10 (
178+ [ UnsafeAccessorType ( "System.Reflection.Emit.RuntimeILGenerator" ) ] object il ) ;
179+
180+ /// <summary>Directly calls UpdateStackSize on RuntimeILGenerator via UnsafeAccessorType (NET10+).</summary>
181+ [ UnsafeAccessor ( UnsafeAccessorKind . Method , Name = "UpdateStackSize" ) ]
182+ private static extern void UpdateStackSize_Net10 (
183+ [ UnsafeAccessorType ( "System.Reflection.Emit.RuntimeILGenerator" ) ] object il ,
184+ OpCode opcode , int stackchange ) ;
185+
186+ /// <summary>Directly accesses m_tokens on DynamicScope via UnsafeAccessorType (NET10+).</summary>
187+ [ UnsafeAccessor ( UnsafeAccessorKind . Field , Name = "m_tokens" ) ]
188+ private static extern ref List < object > GetMTokens_Net10 (
189+ [ UnsafeAccessorType ( "System.Reflection.Emit.DynamicScope" ) ] object scope ) ;
190+
191+ public void DynamicMethod_Emit_Hack_Net10 ( )
192+ {
193+ var f = Get_DynamicMethod_Emit_Hack_Net10 ( ) ;
194+ var a = f ( 41 ) ;
195+ Asserts . AreEqual ( 42 , a ) ;
196+ }
197+
198+ /// <summary>
199+ /// Demonstrates using UnsafeAccessorType (NET10+) to directly access private fields
200+ /// of non-public types (RuntimeILGenerator, DynamicScope) for fast IL emission.
201+ /// Replaces the DynamicMethod-based delegation approach used in earlier .NET versions.
202+ /// </summary>
203+ public static Func < int , int > Get_DynamicMethod_Emit_Hack_Net10 ( )
204+ {
205+ var meth = MethodStatic1Arg ;
206+ var paramCount = 1 ;
207+
208+ var dynMethod = new DynamicMethod ( string . Empty ,
209+ typeof ( int ) , new [ ] { typeof ( ExpressionCompiler . ArrayClosure ) , typeof ( int ) } ,
210+ typeof ( ExpressionCompiler ) ,
211+ skipVisibility : true ) ;
212+
213+ var il = dynMethod . GetILGenerator ( 16 ) ;
214+
215+ // Use UnsafeAccessorType to get refs to the internal IL stream fields directly
216+ ref var mLength = ref GetMLength_Net10 ( il ) ;
217+ ref var mILStream = ref GetMILStream_Net10 ( il ) ;
218+
219+ // il.Emit(OpCodes.Ldarg_1);
220+ mILStream [ mLength ++ ] = ( byte ) OpCodes . Ldarg_1 . Value ;
221+ UpdateStackSize_Net10 ( il , OpCodes . Ldarg_1 , 1 ) ;
222+
223+ // il.Emit(OpCodes.Call, meth);
224+ mILStream [ mLength ++ ] = ( byte ) OpCodes . Call . Value ;
225+ UpdateStackSize_Net10 ( il , OpCodes . Call , CalcStackChange ( meth , paramCount ) ) ;
226+
227+ // Access m_scope via reflection (DynamicILGenerator.m_scope returns DynamicScope which is a non-public type,
228+ // so UnsafeAccessorType cannot currently be used for the return value).
229+ // Then use UnsafeAccessorType to access m_tokens on the DynamicScope instance directly.
230+ if ( mScopeField == null ) return null ;
231+ var scope = mScopeField . GetValue ( il ) ;
232+ ref var mTokens = ref GetMTokens_Net10 ( scope ) ;
233+ mTokens . Add ( meth . MethodHandle ) ;
234+ var token = mTokens . Count - 1 | ( int ) 0x06000000 ; // MetadataTokenType.MethodDef
235+ BinaryPrimitives . WriteInt32LittleEndian ( mILStream . AsSpan ( mLength ) , token ) ;
236+ mLength += 4 ;
237+
238+ // il.Emit(OpCodes.Ret);
239+ mILStream [ mLength ++ ] = ( byte ) OpCodes . Ret . Value ;
240+ UpdateStackSize_Net10 ( il , OpCodes . Ret , 0 ) ;
241+
242+ return ( Func < int , int > ) dynMethod . CreateDelegate ( typeof ( Func < int , int > ) , ExpressionCompiler . EmptyArrayClosure ) ;
243+ }
244+ #endif
245+
131246 [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
132247 private static int CalcStackChange ( MethodInfo meth , int paramCount )
133248 {
@@ -213,15 +328,15 @@ public static Func<A> Get_DynamicMethod_Hack_Emit_Newobj()
213328 // m_tokens.Add(rtConstructor.MethodHandle);
214329 // var tk = m_tokens.Count - 1 | (int)MetadataTokenType.MethodDef;
215330
216- var mScopeField = ilType . GetField ( "m_scope" , BindingFlags . Instance | BindingFlags . NonPublic ) ;
217- if ( mScopeField == null )
331+ var scopeField = ilType . GetField ( "m_scope" , BindingFlags . Instance | BindingFlags . NonPublic ) ;
332+ if ( scopeField == null )
218333 return null ;
219- var mScope = mScopeField . GetValue ( il ) ;
334+ var mScope = scopeField . GetValue ( il ) ;
220335
221- var mTokensField = mScope . GetType ( ) . GetField ( "m_tokens" , BindingFlags . Instance | BindingFlags . NonPublic ) ;
222- if ( mTokensField == null )
336+ var tokensField = mScope . GetType ( ) . GetField ( "m_tokens" , BindingFlags . Instance | BindingFlags . NonPublic ) ;
337+ if ( tokensField == null )
223338 return null ;
224- var mTokens = mTokensField . GetValue ( mScope ) ;
339+ var mTokens = tokensField . GetValue ( mScope ) ;
225340
226341
227342 il . Emit ( OpCodes . Ret ) ;
0 commit comments