Skip to content

Commit 512249e

Browse files
committed
add PoolOrNewILGenerator for #475
1 parent b6234d6 commit 512249e

File tree

2 files changed

+163
-9
lines changed

2 files changed

+163
-9
lines changed

src/FastExpressionCompiler/FastExpressionCompiler.cs

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,166 @@ internal static object TryCompileBoundToFirstClosureParam(Type delegateType, Exp
550550
return method.CreateDelegate(delegateType, closure);
551551
}
552552

553+
#if NET8_0_OR_GREATER
554+
internal static readonly FieldInfo IlGeneratorField =
555+
typeof(DynamicMethod).GetField("_ilGenerator", BindingFlags.Instance | BindingFlags.NonPublic);
556+
internal static readonly Type DynamicILGeneratorType = IlGeneratorField.FieldType;
557+
internal static readonly ConstructorInfo ScopeTreeCtor =
558+
DynamicILGeneratorType.BaseType
559+
.GetField("m_ScopeTree", BindingFlags.Instance | BindingFlags.NonPublic)
560+
.FieldType.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, null);
561+
562+
internal static readonly MethodInfo ArrayClearMethod =
563+
typeof(Array).GetMethod(nameof(Array.Clear), [typeof(Array), typeof(int), typeof(int)]);
564+
internal static readonly FieldInfo ListOfObjectsSize =
565+
typeof(List<object>).GetField("_size", BindingFlags.Instance | BindingFlags.NonPublic);
566+
567+
internal static readonly FieldInfo DynamicILGeneratorScopeField =
568+
DynamicILGeneratorType.GetField("m_scope", BindingFlags.Instance | BindingFlags.NonPublic);
569+
internal static readonly FieldInfo DynamicScopeTokensField =
570+
DynamicILGeneratorScopeField.FieldType.GetField("m_tokens", BindingFlags.Instance | BindingFlags.NonPublic);
571+
internal static readonly PropertyInfo DynamicScopeTokensItem =
572+
DynamicScopeTokensField.FieldType.GetProperty("Item");
573+
internal static MethodInfo GetMethodSigHelperMethod = typeof(SignatureHelper)
574+
.GetMethod("GetMethodSigHelper", BindingFlags.Static | BindingFlags.Public, null, [typeof(Module), typeof(Type), typeof(Type[])], null);
575+
internal static MethodInfo GetSignatureMethod = typeof(SignatureHelper)
576+
.GetMethod("GetSignature", BindingFlags.Instance | BindingFlags.NonPublic, null, [typeof(bool)], null);
577+
internal static MethodInfo GetTokenForMethod = DynamicILGeneratorScopeField.FieldType
578+
.GetMethod("GetTokenFor", BindingFlags.Instance | BindingFlags.Public, null, [typeof(byte[])], null);
579+
internal static FieldInfo MethodSigTokenField =
580+
DynamicILGeneratorType.GetField("m_methodSigToken", BindingFlags.Instance | BindingFlags.NonPublic);
581+
internal static Action<DynamicMethod, ILGenerator, Type, Type[]> ReuseDynamicILGeneratorOfAnyMethodSignature()
582+
{
583+
const BindingFlags allDeclared = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly;
584+
585+
var dynMethod = new DynamicMethod(string.Empty,
586+
typeof(void),
587+
[typeof(ExpressionCompiler.ArrayClosure), typeof(DynamicMethod), typeof(ILGenerator), typeof(Type), typeof(Type[])],
588+
typeof(ExpressionCompiler.ArrayClosure),
589+
true);
590+
591+
var il = dynMethod.GetILGenerator(256); // precalculating the size to avoid waste
592+
593+
var baseFields = DynamicILGeneratorType.BaseType.GetFields(allDeclared);
594+
foreach (var field in baseFields)
595+
{
596+
var fieldName = field.Name;
597+
if (fieldName == "m_localSignature") // todo: skip, let's see how it works
598+
continue;
599+
600+
// m_ScopeTree = new ScopeTree();
601+
if (fieldName == "m_ScopeTree")
602+
{
603+
il.Demit(OpCodes.Ldarg_2);
604+
il.Demit(OpCodes.Newobj, ScopeTreeCtor);
605+
il.Demit(OpCodes.Stfld, field);
606+
continue;
607+
}
608+
609+
// m_methodBuilder = method; // dynamicMethod
610+
if (fieldName == "m_methodBuilder")
611+
{
612+
il.Demit(OpCodes.Ldarg_2);
613+
il.Demit(OpCodes.Ldarg_1);
614+
il.Demit(OpCodes.Stfld, field);
615+
continue;
616+
}
617+
618+
// instead of m_ILStream = new byte[Math.Max(size, DefaultSize)];
619+
// let's clear it and reuse the buffer
620+
if (fieldName == "m_ILStream")
621+
{
622+
il.Demit(OpCodes.Ldarg_2);
623+
il.Demit(OpCodes.Ldfld, field);
624+
var ilStreamVar = ExpressionCompiler.EmittingVisitor.EmitStoreAndLoadLocalVariable(il, typeof(byte[]));
625+
il.Demit(OpCodes.Ldc_I4_0);
626+
ExpressionCompiler.EmittingVisitor.EmitLoadLocalVariable(il, ilStreamVar);
627+
il.Demit(OpCodes.Ldlen);
628+
il.Demit(OpCodes.Call, ArrayClearMethod);
629+
continue;
630+
}
631+
632+
il.Demit(OpCodes.Ldarg_2);
633+
ExpressionCompiler.EmittingVisitor.EmitDefault(il, field.FieldType);
634+
il.Demit(OpCodes.Stfld, field);
635+
}
636+
637+
il.Emit(OpCodes.Ldarg_2);
638+
il.Emit(OpCodes.Ldfld, DynamicILGeneratorScopeField);
639+
var scopeVar = ExpressionCompiler.EmittingVisitor.EmitStoreAndLoadLocalVariable(il, DynamicILGeneratorScopeField.FieldType);
640+
il.Emit(OpCodes.Ldfld, DynamicScopeTokensField);
641+
il.Emit(OpCodes.Dup);
642+
643+
// reset its List<T>._size to 1, keep the 0th item
644+
il.Emit(OpCodes.Ldc_I4_1);
645+
il.Emit(OpCodes.Stfld, ListOfObjectsSize);
646+
647+
// set the 0th item to null
648+
il.Emit(OpCodes.Ldc_I4_0);
649+
il.Emit(OpCodes.Ldnull);
650+
il.Emit(OpCodes.Call, DynamicScopeTokensItem.SetMethod);
651+
652+
// byte[] methodSignature =
653+
// SignatureHelper.GetMethodSigHelper(Module? mod, Type? returnType, Type[]? parameterTypes).GetSignature(true);
654+
il.Emit(OpCodes.Ldnull); // for the module
655+
il.Emit(OpCodes.Ldarg_3); // load return type
656+
il.Emit(OpCodes.Ldarg_S, 4); // load parameter types arrays
657+
il.Emit(OpCodes.Call, GetMethodSigHelperMethod);
658+
il.Emit(OpCodes.Ldc_I4_1); // load true
659+
il.Emit(OpCodes.Call, GetSignatureMethod);
660+
var signatureBytesVar = ExpressionCompiler.EmittingVisitor.EmitStoreLocalVariable(il, typeof(byte[])); // todo: perf could reuse byte[]?
661+
662+
// m_methodSigToken = m_scope.GetTokenFor(methodSignature);
663+
il.Emit(OpCodes.Ldarg_2);
664+
ExpressionCompiler.EmittingVisitor.EmitLoadLocalVariable(il, scopeVar);
665+
ExpressionCompiler.EmittingVisitor.EmitLoadLocalVariable(il, signatureBytesVar);
666+
il.Emit(OpCodes.Call, GetTokenForMethod);
667+
il.Emit(OpCodes.Stfld, MethodSigTokenField);
668+
669+
// store the reused ILGenerator to
670+
il.Emit(OpCodes.Ldarg_1);
671+
il.Emit(OpCodes.Ldarg_2);
672+
il.Emit(OpCodes.Stfld, IlGeneratorField);
673+
674+
il.Emit(OpCodes.Ret);
675+
676+
var act = dynMethod.CreateDelegate(typeof(Action<DynamicMethod, ILGenerator, Type, Type[]>), ExpressionCompiler.EmptyArrayClosure);
677+
return (Action<DynamicMethod, ILGenerator, Type, Type[]>)act;
678+
}
679+
680+
internal static Action<DynamicMethod, ILGenerator, Type, Type[]>
681+
ReuseDynamicILGeneratorOfAnySignature = ReuseDynamicILGeneratorOfAnyMethodSignature();
682+
683+
[ThreadStatic]
684+
internal static ILGenerator pooledILGenerator;
685+
#endif
686+
687+
/// <summary>Get new or pool and configure existing DynamicILGenerator</summary>
688+
[MethodImpl((MethodImplOptions)256)]
689+
public static ILGenerator PoolOrNewILGenerator(DynamicMethod dynMethod, Type returnType, Type[] paramTypes)
690+
{
691+
#if NET8_0_OR_GREATER
692+
var il = Interlocked.Exchange(ref pooledILGenerator, null);
693+
if (il != null)
694+
ReuseDynamicILGeneratorOfAnySignature(dynMethod, il, typeof(void), paramTypes);
695+
else
696+
il = dynMethod.GetILGenerator();
697+
return il;
698+
#else
699+
return dynMethod.GetILGenerator();
700+
#endif
701+
}
702+
703+
/// <summary>Should be called only after call to DynamicMethod.CreateDelegate</summary>
704+
[MethodImpl((MethodImplOptions)256)]
705+
public static void FreeILGenerator(DynamicMethod dynMethod, ILGenerator il)
706+
{
707+
#if NET8_0_OR_GREATER
708+
IlGeneratorField.SetValue(dynMethod, null); // required to break the link with the current method and avoid memory leak
709+
Interlocked.Exchange(ref pooledILGenerator, il);
710+
#endif
711+
}
712+
553713
private static readonly Type[] _closureAsASingleParamType = { typeof(ArrayClosure) };
554714
private static readonly Type[][] _paramTypesPoolWithElem0OfLength1 = new Type[8][]; // todo: @perf @mem could we use this for other Type arrays?
555715

test/FastExpressionCompiler.IssueTests/Issue475_Reuse_DynamicMethod_if_possible.cs

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -222,8 +222,6 @@ public void TryToReuseIlGenerator_for_any_signature(TestContext t)
222222
t.AreEqual(41, func(41)); // ensure that the first delegate is still working
223223
}
224224

225-
internal static ILGenerator pooledILGenerator;
226-
227225
public static object CreateDynamicILGenerator()
228226
{
229227
var paramTypes = ExpressionCompiler.RentOrNewClosureTypeToParamTypes(typeof(int), typeof(int).MakeByRefType());
@@ -257,11 +255,7 @@ public static object PoolDynamicILGenerator()
257255
typeof(ExpressionCompiler.ArrayClosure),
258256
true);
259257

260-
var il = Interlocked.Exchange(ref pooledILGenerator, null);
261-
if (il != null)
262-
ReuseDynamicILGeneratorOfAnySignature(dynMethod, il, typeof(void), paramTypes);
263-
else
264-
il = dynMethod.GetILGenerator();
258+
var il = ExpressionCompiler.PoolOrNewILGenerator(dynMethod, typeof(void), paramTypes);
265259

266260
il.Emit(OpCodes.Ldarg_2);
267261
il.Emit(OpCodes.Ldarg_2);
@@ -272,9 +266,9 @@ public static object PoolDynamicILGenerator()
272266
il.Emit(OpCodes.Ret);
273267

274268
var func = (Action2ndByRef<int>)dynMethod.CreateDelegate(typeof(Action2ndByRef<int>), ExpressionCompiler.EmptyArrayClosure);
275-
IlGeneratorField.SetValue(dynMethod, null); // required
276269

277-
Interlocked.Exchange(ref pooledILGenerator, il);
270+
ExpressionCompiler.FreeILGenerator(dynMethod, il);
271+
278272
ExpressionCompiler.FreeClosureTypeAndParamTypes(paramTypes);
279273

280274
return func;

0 commit comments

Comments
 (0)