Skip to content

Commit bd66075

Browse files
committed
more from #558: goto continue with value
1 parent bf93217 commit bd66075

3 files changed

Lines changed: 151 additions & 7 deletions

File tree

‎src/FastExpressionCompiler.LightExpression/Expression.cs‎

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2023,28 +2023,32 @@ public static GotoExpression MakeGoto(GotoExpressionKind kind, LabelTarget targe
20232023
switch (kind)
20242024
{
20252025
case GotoExpressionKind.Return:
2026-
if (value == null && type == null)
2026+
if (value == null & type == null)
20272027
return new ReturnGotoExpression(target);
20282028
if (value == null)
20292029
return new ReturnTypedGotoExpression(target, type);
20302030
if (type == null)
20312031
return new ReturnValueGotoExpression(target, value);
20322032
return new ReturnTypedValueGotoExpression(target, value, type);
20332033
case GotoExpressionKind.Break:
2034-
if (value == null && type == null)
2034+
if (value == null & type == null)
20352035
return new BreakGotoExpression(target);
20362036
if (value == null)
20372037
return new BreakTypedGotoExpression(target, type);
20382038
if (type == null)
20392039
return new BreakValueGotoExpression(target, value);
20402040
return new BreakTypedValueGotoExpression(target, value, type);
20412041
case GotoExpressionKind.Continue:
2042-
return type == null
2043-
? new ContinueGotoExpression(target)
2044-
: new ContinueTypedGotoExpression(target, type);
2042+
if (value == null & type == null)
2043+
return new ContinueGotoExpression(target);
2044+
if (value == null)
2045+
return new ContinueTypedGotoExpression(target, type);
2046+
if (type == null)
2047+
return new ContinueValueGotoExpression(target, value);
2048+
return new ContinueTypedValueGotoExpression(target, value, type);
20452049
case GotoExpressionKind.Goto:
20462050
default:
2047-
if (value == null && type == null)
2051+
if (value == null & type == null)
20482052
return new GotoExpression(target);
20492053
if (value == null)
20502054
return new TypedGotoExpression(target, type);
@@ -2086,6 +2090,14 @@ public static GotoExpression Continue(LabelTarget target) =>
20862090
public static GotoExpression Continue(LabelTarget target, Type type) =>
20872091
type == null ? new ContinueGotoExpression(target) : new ContinueTypedGotoExpression(target, type);
20882092

2093+
public static GotoExpression Continue(LabelTarget target, Expression value) =>
2094+
value == null
2095+
? new ContinueGotoExpression(target)
2096+
: (GotoExpression)new ContinueValueGotoExpression(target, value);
2097+
2098+
public static GotoExpression Continue(LabelTarget target, Expression value, Type type) =>
2099+
MakeGoto(GotoExpressionKind.Continue, target, value, type);
2100+
20892101
public static GotoExpression Return(LabelTarget target) =>
20902102
new ReturnGotoExpression(target);
20912103

@@ -5499,6 +5511,18 @@ public sealed class ContinueTypedGotoExpression : TypedGotoExpression
54995511
internal ContinueTypedGotoExpression(LabelTarget target, Type type) : base(target, type) { }
55005512
}
55015513

5514+
public sealed class ContinueValueGotoExpression : ValueGotoExpression
5515+
{
5516+
public override GotoExpressionKind Kind => GotoExpressionKind.Continue;
5517+
internal ContinueValueGotoExpression(LabelTarget target, Expression value) : base(target, value) { }
5518+
}
5519+
5520+
public sealed class ContinueTypedValueGotoExpression : TypedValueGotoExpression
5521+
{
5522+
public override GotoExpressionKind Kind => GotoExpressionKind.Continue;
5523+
internal ContinueTypedValueGotoExpression(LabelTarget target, Expression value, Type type) : base(target, value, type) { }
5524+
}
5525+
55025526
public struct SwitchCase
55035527
{
55045528
public readonly IReadOnlyList<Expression> TestValues;

‎src/FastExpressionCompiler/FastExpressionCompiler.cs‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -626,6 +626,8 @@ public struct LabelInfo
626626
public short ReturnVariableIndexPlusOneAndIsDefined;
627627
public Label Label;
628628
public Label ReturnLabel;
629+
/// <summary>Loop break label has no default value, so `Break` with a value may keep the value on the stack and jump to it</summary>
630+
public bool IsLoopBreakLabel;
629631
}
630632

631633
/// <summary>Track the info required to build a closure object + some context information not directly related to closure.</summary>
@@ -2400,6 +2402,14 @@ private static bool TryEmitLoop(LoopExpression loopExpr, ILGenerator il, ref Com
24002402
il.DmarkLabel(continueLabel);
24012403
}
24022404

2405+
var breakLabelInfoBeforeBody = loopExpr.BreakLabel;
2406+
if (breakLabelInfoBeforeBody != null)
2407+
{
2408+
ref var loopBreakInfo = ref context.LambdaInvokeStackLabels.GetLabelOrInvokeIndexByTarget(breakLabelInfoBeforeBody, out var loopBreakFound);
2409+
if (loopBreakFound)
2410+
loopBreakInfo.IsLoopBreakLabel = true;
2411+
}
2412+
24032413
if (!TryEmit(loopExpr.Body, il, ref context, parent))
24042414
return false;
24052415

@@ -2509,6 +2519,11 @@ private static bool TryEmitGoto(GotoExpression expr, ILGenerator il, ref Compile
25092519
{
25102520
case GotoExpressionKind.Break:
25112521
case GotoExpressionKind.Continue:
2522+
if (gotoValue != null && !labelInfo.IsLoopBreakLabel)
2523+
{
2524+
EmitGotoToReturnLabel(ref labelInfo, il, gotoValue, (parent & ParentFlags.TryCatch) != 0 ? OpCodes.Leave : OpCodes.Br);
2525+
return true;
2526+
}
25122527
il.Demit(OpCodes.Br, labelInfo.GetOrDefineLabel(il));
25132528
return true;
25142529

‎test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs‎

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,118 @@ public int Run()
6666
Can_compile_nullable_boolean_and_also();
6767
Can_compile_nullable_boolean_or_else();
6868
Can_compile_nullable_boolean_not();
69+
Can_make_goto_of_all_kinds_with_value_and_type();
70+
Can_compile_goto_with_value_of_all_kinds();
71+
Can_compile_loop_with_break_and_continue_with_value();
6972

70-
return 49;
73+
return 52;
7174
}
7275

7376
private static int _sideEffectCalls;
7477
private static bool? SideEffect(bool? value) { ++_sideEffectCalls; return value; }
7578

79+
public void Can_make_goto_of_all_kinds_with_value_and_type()
80+
{
81+
var label = Label(typeof(int), "label");
82+
var voidLabel = Label("voidLabel");
83+
var value = Constant(42);
84+
var kinds = new[] { GotoExpressionKind.Goto, GotoExpressionKind.Return, GotoExpressionKind.Break, GotoExpressionKind.Continue };
85+
86+
foreach (var kind in kinds)
87+
{
88+
var plain = MakeGoto(kind, voidLabel, null, null);
89+
Asserts.AreEqual(kind, plain.Kind);
90+
Asserts.AreEqual(null, plain.Value);
91+
Asserts.AreEqual(typeof(void), plain.Type);
92+
93+
var typed = MakeGoto(kind, voidLabel, null, typeof(string));
94+
Asserts.AreEqual(kind, typed.Kind);
95+
Asserts.AreEqual(null, typed.Value);
96+
Asserts.AreEqual(typeof(string), typed.Type);
97+
98+
var valued = MakeGoto(kind, label, value, null);
99+
Asserts.AreEqual(kind, valued.Kind);
100+
Asserts.IsNotNull(valued.Value);
101+
102+
var typedValued = MakeGoto(kind, label, value, typeof(string));
103+
Asserts.AreEqual(kind, typedValued.Kind);
104+
Asserts.IsNotNull(typedValued.Value);
105+
Asserts.AreEqual(typeof(string), typedValued.Type);
106+
107+
// converting to System expressions validates and preserves the kind, value and type
108+
foreach (var g in new[] { plain, typed, valued, typedValued })
109+
{
110+
var sys = (System.Linq.Expressions.GotoExpression)g.ToExpression();
111+
Asserts.AreEqual(kind, sys.Kind);
112+
Asserts.AreEqual(g.Value != null, sys.Value != null);
113+
Asserts.AreEqual(g.Type, sys.Type);
114+
}
115+
}
116+
117+
// the public factories agree with MakeGoto
118+
Asserts.AreEqual(GotoExpressionKind.Continue, Continue(label).Kind);
119+
Asserts.AreEqual(typeof(string), Continue(label, typeof(string)).Type);
120+
Asserts.AreEqual(GotoExpressionKind.Continue, Continue(label, value).Kind);
121+
Asserts.IsNotNull(Continue(label, value).Value);
122+
Asserts.AreEqual(null, Continue(label, (Expression)null).Value);
123+
Asserts.AreEqual(GotoExpressionKind.Continue, Continue(label, value, typeof(string)).Kind);
124+
Asserts.AreEqual(typeof(string), Continue(label, value, typeof(string)).Type);
125+
Asserts.AreEqual(GotoExpressionKind.Break, Break(label, value).Kind);
126+
Asserts.AreEqual(GotoExpressionKind.Break, Break(label, value, typeof(string)).Kind);
127+
Asserts.AreEqual(GotoExpressionKind.Return, Return(label, value).Kind);
128+
Asserts.AreEqual(GotoExpressionKind.Return, Return(label, value, typeof(string)).Kind);
129+
Asserts.AreEqual(GotoExpressionKind.Goto, Goto(label, value).Kind);
130+
Asserts.AreEqual(GotoExpressionKind.Goto, Goto(label, value, typeof(string)).Kind);
131+
}
132+
133+
public void Can_compile_goto_with_value_of_all_kinds()
134+
{
135+
foreach (var kind in new[] { GotoExpressionKind.Goto, GotoExpressionKind.Return, GotoExpressionKind.Break, GotoExpressionKind.Continue })
136+
foreach (var withType in new[] { false, true })
137+
{
138+
var label = Label(typeof(int), "label");
139+
var @goto = MakeGoto(kind, label, Constant(42), withType ? typeof(void) : null);
140+
var lambda = Lambda<Func<int>>(Block(@goto, Label(label, Constant(0))));
141+
142+
var sys = lambda.CompileSys();
143+
Asserts.AreEqual(42, sys());
144+
145+
var fast = lambda.CompileFast(true);
146+
Asserts.IsNotNull(fast);
147+
Asserts.AreEqual(42, fast());
148+
}
149+
}
150+
151+
public void Can_compile_loop_with_break_and_continue_with_value()
152+
{
153+
// sum of odd numbers below the limit, the loop result is provided by the `Break` with value
154+
foreach (var continueKind in new[] { GotoExpressionKind.Continue, GotoExpressionKind.Goto })
155+
{
156+
var limit = Parameter(typeof(int), "limit");
157+
var i = Parameter(typeof(int), "i");
158+
var sum = Parameter(typeof(int), "sum");
159+
var breakLabel = Label(typeof(int), "break");
160+
var continueLabel = Label("continue");
161+
162+
var body = Block(
163+
IfThen(GreaterThanOrEqual(PreIncrementAssign(i), limit), Break(breakLabel, sum)),
164+
IfThen(Equal(Modulo(i, Constant(2)), Constant(0)), MakeGoto(continueKind, continueLabel, null, null)),
165+
AddAssign(sum, i));
166+
167+
var lambda = Lambda<Func<int, int>>(
168+
Block(new[] { i, sum }, Loop(body, breakLabel, continueLabel)),
169+
limit);
170+
171+
var sys = lambda.CompileSys();
172+
Asserts.AreEqual(1 + 3 + 5 + 7, sys(9));
173+
174+
var fast = lambda.CompileFast(true);
175+
Asserts.IsNotNull(fast);
176+
Asserts.AreEqual(sys(9), fast(9));
177+
Asserts.AreEqual(sys(1), fast(1));
178+
}
179+
}
180+
76181
private static int AndInts(int left, int right) => left & right;
77182

78183
public void Can_compile_nullable_boolean_not()

0 commit comments

Comments
 (0)