Skip to content

Commit b6ea388

Browse files
committed
@wip something failing
1 parent cd46984 commit b6ea388

7 files changed

+105
-85
lines changed

src/FastExpressionCompiler/FastExpressionCompiler.cs

Lines changed: 66 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -102,19 +102,21 @@ public interface IDelegateDebugInfo
102102
{
103103
/// <summary>The lambda expression object that was compiled to the delegate</summary>
104104
LambdaExpression Expression { get; }
105+
105106
/// <summary>The lambda expression construction syntax C# code</summary>
106107
string ExpressionString { get; }
108+
107109
/// <summary>The equivalent C# code of the lambda expression</summary>
108110
string CSharpString { get; }
109-
/// <summary>Delegate IL op-codes and tokens</summary>
110-
string ILString { get; }
111111

112-
// todo: @feature add the debug info to the nested lambdas
113-
// /// <summary>Total nested lambda counting</summary>
114-
// ushort NestedLambdaCount { get; } // todo: @wip count nested lambdas and expressions
112+
/// <summary>Delegate IL op-codes</summary>
113+
ILInstruction[] ILInstructions { get; }
114+
115+
/// <summary>Delegate IL op-codes</summary>
116+
string ILString { get; }
115117

116-
// /// <summary>Nested lambda compiled counting, should be less or equal to `NestedLambdaCount` so that the same lambda compiled only once.</summary>
117-
// ushort NestedLambdaCompiledTimesCount { get; }
118+
/// <summary>Enumerates all nested lambdas in the closure object, if any.</summary>
119+
IEnumerable<IDelegateDebugInfo> EnumerateNestedLambdas();
118120
}
119121

120122
/// <summary>Compiles expression to delegate ~20 times faster than Expression.Compile.
@@ -489,6 +491,8 @@ private static Delegate CompileNoArgsNew(NewExpression newExpr, Type delegateTyp
489491
var closure = !hasDebugInfo ? EmptyArrayClosure : new DebugArrayClosure(null, Lambda(newExpr, Tools.Empty<PE>()));
490492
var dlg = method.CreateDelegate(delegateType, closure);
491493
DynamicMethodHacks.FreePooledILGenerator(method, il);
494+
if (hasDebugInfo)
495+
((DebugArrayClosure)closure).ILInstructions = ILReaderFactory.CreateILReader(dlg.Method).ToArray();
492496
return dlg;
493497
}
494498

@@ -553,7 +557,7 @@ internal static object TryCompileBoundToFirstClosureParam(Type delegateType, Exp
553557
il.Demit(OpCodes.Ret);
554558
compiledDelegate = dynMethod.CreateDelegate(delegateType, closure);
555559
if (hasDebugInfo)
556-
((DebugArrayClosure)closure).ILString = compiledDelegate.Method.ToILString().ToString();
560+
((DebugArrayClosure)closure).ILInstructions = ILReaderFactory.CreateILReader(compiledDelegate.Method).ToArray();
557561
}
558562

559563
DynamicMethodHacks.FreePooledILGenerator(dynMethod, il);
@@ -1003,71 +1007,69 @@ public class ArrayClosure
10031007
public ArrayClosure(object[] constantsAndNestedLambdas) => ConstantsAndNestedLambdas = constantsAndNestedLambdas;
10041008
}
10051009

1010+
// todo: @rename to DiagInfoClosure
10061011
[RequiresUnreferencedCode(Trimming.Message)]
10071012
public sealed class DebugArrayClosure : ArrayClosure, IDelegateDebugInfo
10081013
{
10091014
public LambdaExpression Expression { get; internal set; }
10101015

1011-
private readonly Lazy<string> _expressionString;
1012-
public string ExpressionString => _expressionString.Value;
1013-
1014-
private readonly Lazy<string> _csharpString;
1015-
public string CSharpString => _csharpString.Value;
1016-
1017-
public string ILString { get; internal set; }
1018-
1019-
public DebugArrayClosure(object[] constantsAndNestedLambdas, LambdaExpression expr)
1020-
: base(constantsAndNestedLambdas)
1016+
private string _expressionString;
1017+
public string ExpressionString
10211018
{
1022-
Expression = expr;
1023-
_expressionString = new Lazy<string>(() => Expression?.ToExpressionString() ?? "<expression is not available>");
1024-
_csharpString = new Lazy<string>(() => Expression?.ToCSharpString() ?? "<expression is not available>");
1019+
get
1020+
{
1021+
if (_expressionString != null)
1022+
return _expressionString;
1023+
var expr = Expression;
1024+
return expr == null
1025+
? "<Expression is not set to the DebugArrayClosure>"
1026+
: (_expressionString = expr.ToExpressionString());
1027+
}
10251028
}
1026-
}
1027-
1028-
public static bool TryGetDebugClosureNestedLambdaOrConstant(this Delegate parentLambda, out object item, int itemIndex = 0)
1029-
{
1030-
var target = parentLambda.Target;
1031-
if (target is ExpressionCompiler.DebugArrayClosure t)
1029+
private string _csharpString;
1030+
public string CSharpString
10321031
{
1033-
var closureItems = t.ConstantsAndNestedLambdas;
1034-
if (itemIndex < closureItems.Length)
1032+
get
10351033
{
1036-
item = closureItems[itemIndex];
1037-
return true;
1034+
if (_csharpString != null)
1035+
return _csharpString;
1036+
var expr = Expression;
1037+
return expr == null
1038+
? "<Expression is not set to the DebugArrayClosure>"
1039+
: (_csharpString = expr.ToCSharpString());
10381040
}
10391041
}
1040-
item = null;
1041-
return false;
1042-
}
10431042

1044-
public static bool TryGetDebugClosureNestedLambda(this Delegate parentLambda, int itemIndex, out Delegate d)
1045-
{
1046-
var target = parentLambda.Target;
1047-
if (target is ExpressionCompiler.DebugArrayClosure t)
1043+
public ILInstruction[] ILInstructions { get; internal set; }
1044+
1045+
private string _ilString;
1046+
public string ILString
10481047
{
1049-
var closureItems = t.ConstantsAndNestedLambdas;
1050-
if (itemIndex < closureItems.Length)
1048+
get
10511049
{
1052-
var nestedLambda = closureItems[itemIndex];
1053-
d = (Delegate)(nestedLambda is NestedLambdaForNonPassedParams n ? n.NestedLambda : nestedLambda);
1054-
return true;
1050+
if (_ilString != null)
1051+
return _ilString;
1052+
var ilInstructions = ILInstructions;
1053+
return ilInstructions == null
1054+
? "<ILInstructions are not set to the DebugArrayClosure>"
1055+
: (_ilString = ILInstructions.ToILString().ToString());
10551056
}
10561057
}
1057-
d = null;
1058-
return false;
1059-
}
10601058

1061-
public static IEnumerable<object> EnumerateDebugConstantsAndNestedLambdas(this Delegate parentLambda)
1062-
{
1063-
var target = parentLambda.Target;
1064-
if (target is ExpressionCompiler.DebugArrayClosure t)
1059+
public DebugArrayClosure(object[] constantsAndNestedLambdas, LambdaExpression expr)
1060+
: base(constantsAndNestedLambdas) =>
1061+
Expression = expr;
1062+
1063+
// <inheritdoc />
1064+
public IEnumerable<IDelegateDebugInfo> EnumerateNestedLambdas()
10651065
{
1066-
foreach (var item in t.ConstantsAndNestedLambdas)
1067-
if (item is NestedLambdaForNonPassedParams nestedLambda)
1068-
yield return nestedLambda.NestedLambda;
1069-
else
1070-
yield return item;
1066+
if (ConstantsAndNestedLambdas != null)
1067+
foreach (var item in ConstantsAndNestedLambdas)
1068+
{
1069+
var dlg = (item is NestedLambdaForNonPassedParams nestedLambda ? nestedLambda.NestedLambda : item) as Delegate;
1070+
if (dlg != null && dlg.Target is IDelegateDebugInfo delegateDebugInfo)
1071+
yield return delegateDebugInfo;
1072+
}
10711073
}
10721074
}
10731075

@@ -1844,9 +1846,9 @@ private static bool TryCompileNestedLambda(ref ClosureInfo nestedClosureInfo, Ne
18441846

18451847
ArrayClosure nestedLambdaClosure = null;
18461848
var hasNonPassedParameters = nestedLambdaInfo.NonPassedParameters.Count != 0;
1849+
var hasDebugInfo = (flags & CompilerFlags.EnableDelegateDebugInfo) != 0;
18471850
if (!hasNonPassedParameters)
18481851
{
1849-
var hasDebugInfo = (flags & CompilerFlags.EnableDelegateDebugInfo) != 0;
18501852
if (!hasDebugInfo)
18511853
nestedLambdaClosure = constantsAndNestedLambdas == null ? EmptyArrayClosure : new ArrayClosure(constantsAndNestedLambdas);
18521854
else
@@ -1876,9 +1878,14 @@ private static bool TryCompileNestedLambda(ref ClosureInfo nestedClosureInfo, Ne
18761878
? method.CreateDelegate(nestedLambdaExpr.Type, nestedLambdaClosure)
18771879
: method.CreateDelegate(Tools.GetFuncOrActionType(closurePlusParamTypes, nestedReturnType), null);
18781880

1879-
nestedLambdaInfo.Lambda = !hasNonPassedParameters ? nestedLambda
1880-
: constantsAndNestedLambdas == null ? new NestedLambdaForNonPassedParams(nestedLambda)
1881-
: new NestedLambdaForNonPassedParamsWithConstants(nestedLambda, constantsAndNestedLambdas);
1881+
nestedLambdaInfo.Lambda = !hasNonPassedParameters
1882+
? nestedLambda
1883+
: constantsAndNestedLambdas == null
1884+
? new NestedLambdaForNonPassedParams(nestedLambda)
1885+
: new NestedLambdaForNonPassedParamsWithConstants(nestedLambda, constantsAndNestedLambdas);
1886+
1887+
if (nestedLambdaClosure is DebugArrayClosure debugInfoClosure)
1888+
debugInfoClosure.ILInstructions = ILReaderFactory.CreateILReader(nestedLambda).ToArray();
18821889
}
18831890
DynamicMethodHacks.FreePooledILGenerator(method, il);
18841891
FreePooledClosureTypeAndParamTypes(closurePlusParamTypes);

src/FastExpressionCompiler/ILReader.cs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public static class ILReaderFactory
3131
_rtDynamicMethodType.GetField("m_owner", BindingFlags.Instance | BindingFlags.NonPublic);
3232
#endif
3333

34-
public static ILReader Create(object source)
34+
public static ILReader CreateILReader(object source)
3535
{
3636
var sourceType = source.GetType();
3737
var dynamicMethod = source as DynamicMethod;
@@ -62,16 +62,13 @@ public static ILReader Create(object source)
6262
return null;
6363
}
6464

65-
public static StringBuilder ToILString(this MethodInfo method, StringBuilder s = null)
66-
{
67-
if (method is null) throw new ArgumentNullException(nameof(method));
65+
public static StringBuilder ToILString(this MethodInfo method, StringBuilder s = null) => ToILString(CreateILReader(method), s);
6866

67+
public static StringBuilder ToILString(this IEnumerable<ILInstruction> ilInstructions, StringBuilder s = null)
68+
{
6969
s ??= new StringBuilder();
70-
71-
var ilReader = Create(method);
72-
7370
var line = 0;
74-
foreach (var il in ilReader)
71+
foreach (var il in ilInstructions)
7572
{
7673
try
7774
{

src/FastExpressionCompiler/TestTools.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public static void AssertOpCodes(this MethodInfo method, params OpCode[] expecte
4949
{
5050
if (DisableAssertOpCodes) return;
5151

52-
var ilReader = ILReaderFactory.Create(method);
52+
var ilReader = ILReaderFactory.CreateILReader(method);
5353
if (ilReader is null)
5454
{
5555
Debug.WriteLine($"Reading IL is currently not supported");
@@ -142,6 +142,16 @@ public static void PrintIL(this MethodInfo method, string tag = null)
142142
s.AppendLine().Append(tag == null ? "</il>" : "</" + tag + ">");
143143
Console.WriteLine(s);
144144
}
145+
146+
public static void PrintIL(this IDelegateDebugInfo delegateDebugInfo, string tag = null)
147+
{
148+
if (!AllowPrintIL) return;
149+
var s = new StringBuilder();
150+
s.Append(tag == null ? "<il>" : "<" + tag + ">").AppendLine();
151+
s.Append(delegateDebugInfo.ILString);
152+
s.AppendLine().Append(tag == null ? "</il>" : "</" + tag + ">");
153+
Console.WriteLine(s);
154+
}
145155
}
146156

147157
public sealed class AssertionException : Exception

test/FastExpressionCompiler.IssueTests/Issue341_Equality_comparison_between_nullable_and_null_inside_Any_produces_incorrect_compiled_expression.cs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -307,13 +307,16 @@ public void Nullable_decimal_member_not_equal_to_null_inside_predicate()
307307
var compiledSys = expression.CompileSys();
308308
compiledSys.PrintIL("sys");
309309

310-
var compiledFast = expression.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo);
311-
Asserts.IsNotNull(compiledFast);
310+
var f = expression.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo);
311+
Asserts.IsNotNull(f);
312312

313-
compiledFast.PrintIL("fast");
313+
f.PrintIL("fast");
314314

315-
if (compiledFast.TryGetDebugClosureNestedLambdaOrConstant(out var item) && item is Delegate d)
316-
d.PrintIL("predicate");
315+
var dis = f.TryGetDebugInfo();
316+
foreach (var di in dis.EnumerateNestedLambdas())
317+
{
318+
di.PrintIL("predicate");
319+
}
317320

318321
var instance = new Test()
319322
{
@@ -326,7 +329,7 @@ public void Nullable_decimal_member_not_equal_to_null_inside_predicate()
326329
var result = compiledSys(instance);
327330
Asserts.IsTrue(result);
328331

329-
result = compiledFast(instance);
332+
result = f(instance);
330333
Asserts.IsTrue(result);
331334
}
332335

test/FastExpressionCompiler.IssueTests/Issue347_InvalidProgramException_on_compiling_an_expression_that_returns_a_record_which_implements_IList.cs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ namespace FastExpressionCompiler.LightExpression.IssueTests
1717
namespace FastExpressionCompiler.IssueTests
1818
#endif
1919
{
20-
2120
public class Issue347_InvalidProgramException_on_compiling_an_expression_that_returns_a_record_which_implements_IList : ITest
2221
{
2322
public int Run()
@@ -30,7 +29,6 @@ public int Run()
3029
return 5;
3130
}
3231

33-
3432
public void Test_passing_struct_item_in_object_array_parameter()
3533
{
3634
var incMethod = GetType().GetMethod(nameof(Inc), BindingFlags.Public | BindingFlags.Static);
@@ -62,7 +60,6 @@ public void Test_passing_struct_item_in_object_array_parameter()
6260
Asserts.AreEqual(42, y);
6361
}
6462

65-
6663
public void Test_struct_parameter_in_closure_of_the_nested_lambda()
6764
{
6865
var incMethod = GetType().GetMethod(nameof(Inc), BindingFlags.Public | BindingFlags.Static);
@@ -88,7 +85,11 @@ public void Test_struct_parameter_in_closure_of_the_nested_lambda()
8885
Asserts.IsNotNull(f);
8986
f.PrintIL();
9087

91-
// todo: @wip #475 better diagnostics in the presence of the pooling
88+
var di = f.TryGetDebugInfo();
89+
foreach (var ni in di.EnumerateNestedLambdas())
90+
ni.PrintIL("nested");
91+
92+
//todo: @wip #475
9293
// if (f.TryGetDebugClosureNestedLambda(0, out var d))
9394
// {
9495
// d.PrintIL("nested");
@@ -109,7 +110,6 @@ public void Test_struct_parameter_in_closure_of_the_nested_lambda()
109110
Asserts.AreEqual(43, y);
110111
}
111112

112-
113113
public void Test_nullable_param_in_closure_of_the_nested_lambda()
114114
{
115115
var incMethod = GetType().GetMethod(nameof(Inc), BindingFlags.Public | BindingFlags.Static);
@@ -139,7 +139,6 @@ public void Test_nullable_param_in_closure_of_the_nested_lambda()
139139

140140
public static int Inc(Func<int> f) => f() + 1;
141141

142-
143142
public void Test_nullable_of_struct_and_struct_field_in_the_nested_lambda()
144143
{
145144
var incMethod = GetType().GetMethod(nameof(Inc), BindingFlags.Public | BindingFlags.Static);
@@ -181,7 +180,6 @@ public void Test_nullable_of_struct_and_struct_field_in_the_nested_lambda()
181180
Asserts.AreEqual(43, y);
182181
}
183182

184-
185183
public void Test_original()
186184
{
187185
// Expression<Func<NotifyContainer?, IReadOnlyListRecord<NotifyModel>>> e =
@@ -247,8 +245,11 @@ public void Test_original()
247245
Asserts.IsNotNull(f);
248246
f.PrintIL();
249247

250-
if (f.TryGetDebugClosureNestedLambdaOrConstant(out var item) && item is Delegate d)
251-
d.PrintIL("nested");
248+
var dis = f.TryGetDebugInfo();
249+
foreach (var di in dis.EnumerateNestedLambdas())
250+
{
251+
di.PrintIL("nested");
252+
}
252253

253254
var y = f(container);
254255
Asserts.AreEqual(1, y.Count);

test/FastExpressionCompiler.IssueTests/Issue353_NullReferenceException_when_calling_CompileFast_results.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,8 +210,9 @@ public void Test1_simplified()
210210
Asserts.IsNotNull(f);
211211
f.PrintIL();
212212

213-
if (f.TryGetDebugClosureNestedLambdaOrConstant(out var item) && item is Delegate d)
214-
d.PrintIL("sumFunc");
213+
var di = f.TryGetDebugInfo();
214+
foreach (var ni in di.EnumerateNestedLambdas())
215+
ni.PrintIL("sumFunc");
215216

216217
var y = f(10);
217218
Asserts.AreEqual(1009, y);

test/FastExpressionCompiler.TestsRunner/Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public static void Main()
1515
// LightExpression.ILGeneratorTools.DisableILGeneratorPooling = true;
1616

1717
// new Issue461_InvalidProgramException_when_null_checking_type_by_ref().Run();
18+
new LightExpression.IssueTests.Issue347_InvalidProgramException_on_compiling_an_expression_that_returns_a_record_which_implements_IList().Run();
1819
new LightExpression.UnitTests.NestedLambdasSharedToExpressionCodeStringTest().Run();
1920
// new Issue55_CompileFast_crash_with_ref_parameter().Run();
2021

0 commit comments

Comments
 (0)