Skip to content

Commit 53c1d20

Browse files
committed
#475 stuck on this, likely not possible due the internal asumptions in the Release mode
1 parent b333573 commit 53c1d20

File tree

5 files changed

+195
-55
lines changed

5 files changed

+195
-55
lines changed

test/FastExpressionCompiler.Benchmarks/Issue468_Compile_vs_FastCompile.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using BenchmarkDotNet.Attributes;
44
using BenchmarkDotNet.Diagnosers;
55
using BenchmarkDotNet.Jobs;
6+
using BenchmarkDotNet.Order;
67

78
namespace FastExpressionCompiler.Benchmarks;
89

@@ -281,3 +282,22 @@ public object CompiledFast()
281282
return _expr.CompileFast();
282283
}
283284
}
285+
286+
[MemoryDiagnoser, RankColumn, Orderer(SummaryOrderPolicy.FastestToSlowest)]
287+
public class Issue475_ReuseVsNoReuse
288+
{
289+
/*
290+
291+
*/
292+
[Benchmark(Baseline = true)]
293+
public object NoReuse()
294+
{
295+
return IssueTests.Issue475_Reuse_DynamicMethod_if_possible.NoReuse();
296+
}
297+
298+
[Benchmark]
299+
public object Reuse()
300+
{
301+
return IssueTests.Issue475_Reuse_DynamicMethod_if_possible.Reuse();
302+
}
303+
}

test/FastExpressionCompiler.Benchmarks/Program.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ public static void Main()
2525

2626
//--------------------------------------------
2727

28-
BenchmarkRunner.Run<Issue468_Compile_vs_FastCompile>();
28+
BenchmarkRunner.Run<Issue475_ReuseVsNoReuse>();
29+
30+
// BenchmarkRunner.Run<Issue468_Compile_vs_FastCompile>();
2931
// BenchmarkRunner.Run<Issue468_InvokeCompiled_vs_InvokeCompiledFast>();
3032
// BenchmarkRunner.Run<Issue468_Eval_Optimization>();
3133

test/FastExpressionCompiler.IssueTests/EmitHacksTest.cs

Lines changed: 1 addition & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,11 @@
77
using System.Buffers.Binary;
88
using System.Runtime.CompilerServices;
99

10-
1110
namespace FastExpressionCompiler.IssueTests
1211
{
1312

14-
public class EmitHacksTest : ITest, ITestX
13+
public class EmitHacksTest : ITest
1514
{
16-
public void Run(TestRun t)
17-
{
18-
TryToReuseTheDynamicMethod(t);
19-
}
20-
2115
public int Run()
2216
{
2317
DynamicMethod_Emit_Hack();
@@ -26,51 +20,6 @@ public int Run()
2620
return 3;
2721
}
2822

29-
void TryToReuseTheDynamicMethod(TestContext t)
30-
{
31-
// Let's say there is a DynamicMethod of Func<int, int> returning its argument,
32-
// at the end I want to reuse the created DynamicMethod for another Func<int, int> returning arg + 42
33-
34-
var dynMethod = new DynamicMethod(string.Empty,
35-
typeof(int),
36-
[typeof(ExpressionCompiler.ArrayClosure), typeof(int)],
37-
typeof(ExpressionCompiler.ArrayClosure),
38-
true);
39-
40-
var il = dynMethod.GetILGenerator();
41-
42-
il.Emit(OpCodes.Ldarg_1);
43-
il.Emit(OpCodes.Ret);
44-
45-
var func = (Func<int, int>)dynMethod.CreateDelegate(typeof(Func<int, int>), ExpressionCompiler.EmptyArrayClosure);
46-
t.AreEqual(41, func(41));
47-
48-
// Reset the DynamicMethod internals that we need to reuse:
49-
// _ilGenerator = null;
50-
// _initLocals = true;
51-
// _methodHandle = null;
52-
var dynMethodType = dynMethod.GetType();
53-
var ilGeneratorField = dynMethodType.GetField("_ilGenerator", BindingFlags.Instance | BindingFlags.NonPublic);
54-
var initLocalsField = dynMethodType.GetField("_initLocals", BindingFlags.Instance | BindingFlags.NonPublic);
55-
var methodHandleField = dynMethodType.GetField("_methodHandle", BindingFlags.Instance | BindingFlags.NonPublic);
56-
57-
Console.WriteLine($"{{_ilGenerator: {ilGeneratorField?.GetValue(dynMethod) ?? "null"}, _initLocals: {initLocalsField.GetValue(dynMethod)}, _methodHandle: {methodHandleField?.GetValue(dynMethod) ?? "null"}}}");
58-
ilGeneratorField.SetValue(dynMethod, null);
59-
initLocalsField.SetValue(dynMethod, true);
60-
// methodHandleField.SetValue(dynMethod, null); // breaks the CLR
61-
62-
var il2 = dynMethod.GetILGenerator();
63-
64-
il2.Emit(OpCodes.Ldarg_1);
65-
il2.Emit(OpCodes.Ldc_I4, 42);
66-
il2.Emit(OpCodes.Add);
67-
il2.Emit(OpCodes.Ret);
68-
t.AreEqual(41, func(41));
69-
70-
var func2 = (Func<int, int>)dynMethod.CreateDelegate(typeof(Func<int, int>), ExpressionCompiler.EmptyArrayClosure);
71-
t.AreEqual(83, func2(41));
72-
}
73-
7423
public void DynamicMethod_Emit_Hack()
7524
{
7625
var f = Get_DynamicMethod_Emit_Hack();
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
using System;
2+
using System.Reflection;
3+
using System.Reflection.Emit;
4+
using System.Diagnostics;
5+
6+
7+
#if LIGHT_EXPRESSION
8+
using ExpressionType = System.Linq.Expressions.ExpressionType;
9+
using static FastExpressionCompiler.LightExpression.Expression;
10+
namespace FastExpressionCompiler.LightExpression.IssueTests;
11+
#else
12+
using System.Linq.Expressions;
13+
using static System.Linq.Expressions.Expression;
14+
namespace FastExpressionCompiler.IssueTests;
15+
#endif
16+
17+
public struct Issue475_Reuse_DynamicMethod_if_possible : ITestX
18+
{
19+
public void Run(TestRun t)
20+
{
21+
TryToReuseTheDynamicMethod(t);
22+
}
23+
24+
internal static FieldInfo IlGeneratorField = typeof(DynamicMethod).GetField("_ilGenerator", BindingFlags.Instance | BindingFlags.NonPublic);
25+
internal static FieldInfo MethodHandleField = typeof(DynamicMethod).GetField("_methodHandle", BindingFlags.Instance | BindingFlags.NonPublic);
26+
internal static MethodInfo GetMethodDescriptorMethod = typeof(DynamicMethod).GetMethod("GetMethodDescriptor", BindingFlags.Instance | BindingFlags.NonPublic);
27+
internal static MethodInfo CreateDelegateMethod = typeof(Delegate).GetMethod("CreateDelegateNoSecurityCheck", BindingFlags.Static | BindingFlags.NonPublic);
28+
29+
void TryToReuseTheDynamicMethod(TestContext t)
30+
{
31+
// Let's say there is a DynamicMethod of Func<int, int> returning its argument,
32+
// at the end I want to reuse the created DynamicMethod for another Func<int, int> returning arg + 42
33+
34+
var dynMethod = new DynamicMethod(string.Empty,
35+
typeof(int),
36+
[typeof(ExpressionCompiler.ArrayClosure), typeof(int)],
37+
typeof(ExpressionCompiler.ArrayClosure),
38+
true);
39+
40+
var il = dynMethod.GetILGenerator();
41+
42+
il.Emit(OpCodes.Ldarg_1);
43+
il.Emit(OpCodes.Ret);
44+
45+
var runtimeMethodHandle = GetMethodDescriptorMethod.Invoke(dynMethod, null);
46+
var func = (Func<int, int>)CreateDelegateMethod.Invoke(null, [typeof(Func<int, int>), ExpressionCompiler.EmptyArrayClosure, runtimeMethodHandle]);
47+
48+
// var func = (Func<int, int>)dynMethod.CreateDelegate(typeof(Func<int, int>), ExpressionCompiler.EmptyArrayClosure);
49+
t.AreEqual(41, func(41));
50+
51+
// Debug.WriteLine($"Field_initLocals: {Field_initLocals.GetValue(dynMethod)}");
52+
IlGeneratorField.SetValue(dynMethod, null);
53+
54+
// required: Keeps the old delegate if not set to null
55+
MethodHandleField.SetValue(dynMethod, null);
56+
57+
var il2 = dynMethod.GetILGenerator();
58+
59+
il2.Emit(OpCodes.Ldarg_1);
60+
il2.Emit(OpCodes.Ldc_I4, 42);
61+
il2.Emit(OpCodes.Add);
62+
il2.Emit(OpCodes.Ret);
63+
64+
t.AreEqual(41, func(41));
65+
66+
// CreateDelegate does:
67+
// RuntimeMethodHandle runtimeMethodHandle = GetMethodDescriptor();
68+
// MulticastDelegate d = (MulticastDelegate)Delegate.CreateDelegateNoSecurityCheck(delegateType, target, runtimeMethodHandle);
69+
// // stash this MethodInfo by brute force.
70+
// d.StoreDynamicMethod(this);
71+
// return d;
72+
73+
// todo: @wip Calling this second time in `-c:Release` will produce `Fatal error. Internal CLR error. (0x80131506) ## :-( Failed with ERROR: -1073741819` (the -c:Debug is working fine)
74+
// runtimeMethodHandle = GetMethodDescriptorMethod.Invoke(dynMethod, null);
75+
76+
// var func2 = (Func<int, int>)CreateDelegateMethod.Invoke(null, [typeof(Func<int, int>), ExpressionCompiler.EmptyArrayClosure, runtimeMethodHandle]);
77+
78+
// var func2 = (Func<int, int>)dynMethod.CreateDelegate(typeof(Func<int, int>), ExpressionCompiler.EmptyArrayClosure);
79+
// t.AreEqual(83, func2(41));
80+
}
81+
82+
public static object Reuse()
83+
{
84+
var dynMethod = new DynamicMethod(string.Empty,
85+
typeof(int),
86+
[typeof(ExpressionCompiler.ArrayClosure), typeof(int)],
87+
typeof(ExpressionCompiler.ArrayClosure),
88+
true);
89+
90+
var il = dynMethod.GetILGenerator();
91+
92+
il.Emit(OpCodes.Ldarg_1);
93+
il.Emit(OpCodes.Ret);
94+
95+
_ = (Func<int, int>)dynMethod.CreateDelegate(typeof(Func<int, int>), ExpressionCompiler.EmptyArrayClosure);
96+
97+
IlGeneratorField.SetValue(dynMethod, null);
98+
// Field_initLocals.SetValue(dynMethod, true);
99+
100+
var il2 = dynMethod.GetILGenerator();
101+
102+
il2.Emit(OpCodes.Ldarg_1);
103+
il2.Emit(OpCodes.Ldc_I4, 42);
104+
il2.Emit(OpCodes.Add);
105+
il2.Emit(OpCodes.Ret);
106+
107+
return (Func<int, int>)dynMethod.CreateDelegate(typeof(Func<int, int>), ExpressionCompiler.EmptyArrayClosure);
108+
}
109+
110+
public static object NoReuse()
111+
{
112+
var dynMethod = new DynamicMethod(string.Empty,
113+
typeof(int),
114+
[typeof(ExpressionCompiler.ArrayClosure), typeof(int)],
115+
typeof(ExpressionCompiler.ArrayClosure),
116+
true);
117+
118+
var il = dynMethod.GetILGenerator();
119+
120+
il.Emit(OpCodes.Ldarg_1);
121+
il.Emit(OpCodes.Ret);
122+
123+
_ = (Func<int, int>)dynMethod.CreateDelegate(typeof(Func<int, int>), ExpressionCompiler.EmptyArrayClosure);
124+
125+
var dynMethod2 = new DynamicMethod(string.Empty,
126+
typeof(int),
127+
[typeof(ExpressionCompiler.ArrayClosure), typeof(int)],
128+
typeof(ExpressionCompiler.ArrayClosure),
129+
true);
130+
131+
var il2 = dynMethod2.GetILGenerator();
132+
133+
il2.Emit(OpCodes.Ldarg_1);
134+
il2.Emit(OpCodes.Ldc_I4, 42);
135+
il2.Emit(OpCodes.Add);
136+
il2.Emit(OpCodes.Ret);
137+
138+
return (Func<int, int>)dynMethod2.CreateDelegate(typeof(Func<int, int>), ExpressionCompiler.EmptyArrayClosure);
139+
}
140+
141+
// internal static FieldInfo Field_initLocals = typeof(DynamicMethod).GetField("_initLocals", BindingFlags.Instance | BindingFlags.NonPublic);
142+
143+
internal static Action<DynamicMethod> GetDynamicMethodResetDelegate()
144+
{
145+
// Reset the DynamicMethod internals to enable its reuse
146+
// _ilGenerator = null;
147+
// _initLocals = true;
148+
// _methodHandle = null; // but this reset breaks the CLR
149+
150+
var dynMethod = new DynamicMethod(string.Empty,
151+
typeof(void),
152+
[typeof(ExpressionCompiler.ArrayClosure), typeof(DynamicMethod)],
153+
typeof(ExpressionCompiler.ArrayClosure),
154+
true);
155+
156+
var il = dynMethod.GetILGenerator();
157+
il.Emit(OpCodes.Ldarg_1);
158+
il.Emit(OpCodes.Ldnull);
159+
il.Emit(OpCodes.Stfld, IlGeneratorField);
160+
// il.Emit(OpCodes.Ldarg_1);
161+
// il.Emit(OpCodes.Ldc_I4_1);
162+
// il.Emit(OpCodes.Stfld, Field_initLocals);
163+
il.Emit(OpCodes.Ret);
164+
165+
return (Action<DynamicMethod>)dynMethod.CreateDelegate(typeof(Action<DynamicMethod>), ExpressionCompiler.EmptyArrayClosure);
166+
}
167+
168+
internal static Lazy<Action<DynamicMethod>> ResetDynamicMethod = new(GetDynamicMethodResetDelegate);
169+
}

test/FastExpressionCompiler.TestsRunner/Program.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ public class Program
1111
{
1212
public static void Main()
1313
{
14-
#if NET8_0_OR_GREATER && !LIGHT_EXPRESSION
14+
#if NET8_0_OR_GREATER
1515
var ts = new TestRun();
16-
ts.Run(new EmitHacksTest());
16+
ts.Run(new Issue475_Reuse_DynamicMethod_if_possible());
1717
#endif
1818

1919
var t = new LightExpression.TestRun();

0 commit comments

Comments
 (0)