Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions src/FastExpressionCompiler/FastExpressionCompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5933,7 +5933,10 @@ private static bool TryEmitSwitch(SwitchExpression expr, ILGenerator il, ref Com
// Check the pre-requisite conditions and emit OpCodes.Switch if possible, see #398
const int minSwitchTableSize = 3;
Debug.Assert(minSwitchTableSize >= 3); // The later code will assume at least so few of the cases count
if (customEqualMethod == null && caseCount >= minSwitchTableSize && switchValueType.IsIntegerOrUnderlyingInteger())
// The IL switch index and the normalization arithmetic must use 32-bit values.
// Wider operands retain the ordered comparison dispatch below.
if (customEqualMethod == null && caseCount >= minSwitchTableSize && switchValueType.IsIntegerOrUnderlyingInteger() &&
Type.GetTypeCode(switchValueType) is not (TypeCode.Int64 or TypeCode.UInt64))
{
var multipleTestValuesLabelsId = 0;
var hasDuplicateTestValue = false;
Expand Down Expand Up @@ -6138,7 +6141,7 @@ private static bool TryEmitSwitch(SwitchExpression expr, ILGenerator il, ref Com
{
// store and use the switch value over in case of the outlier, otherwise just use whatever is on stack from the prev TryEmit
EmitStoreAndLoadLocalVariable(il, switchValVar = il.GetNextLocalVarIndex(switchValueType));
EmitLoadConstantLong(il, switchValues.GetSurePresentRef(0).Value); // Load the min outlier
EmitLoadConstantInt(il, unchecked((int)switchValues.GetSurePresentRef(0).Value)); // Load the min outlier
il.Demit(OpCodes.Beq, minOutlierLabel);
EmitLoadLocalVariable(il, switchValVar); // Load the switch var on stack for the next operation
}
Expand All @@ -6150,8 +6153,8 @@ private static bool TryEmitSwitch(SwitchExpression expr, ILGenerator il, ref Com
// Before emitting switch we need to normalize the switch value to start from zero
if (firstTestValue != 0)
{
EmitLoadConstantLong(il, firstTestValue);
il.Demit(firstTestValue > 0 ? OpCodes.Sub : OpCodes.Add);
EmitLoadConstantInt(il, unchecked((int)firstTestValue));
il.Demit(OpCodes.Sub);
}

// Emit the switch instruction
Expand All @@ -6162,7 +6165,7 @@ private static bool TryEmitSwitch(SwitchExpression expr, ILGenerator il, ref Com
{
Debug.Assert(switchValVar != -1);
EmitLoadLocalVariable(il, switchValVar);
EmitLoadConstantLong(il, switchValues.GetSurePresentRef(switchValuesCount - 1).Value); // Load the max outlier
EmitLoadConstantInt(il, unchecked((int)switchValues.GetSurePresentRef(switchValuesCount - 1).Value)); // Load the max outlier
il.Demit(OpCodes.Beq, maxOutlierLabel); // If switch value matches the outlier break to its label
}

Expand Down
2 changes: 2 additions & 0 deletions test/FastExpressionCompiler.TestsRunner.Net472/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ static void RunLightExpressionTests(object state)
t.Run(new LightExpression.IssueTests.Issue476_System_ExecutionEngineException_with_nullables_on_repeated_calls_to_ConcurrentDictionary());
t.Run(new LightExpression.IssueTests.Issue498_InvalidProgramException_when_using_loop());
t.Run(new LightExpression.IssueTests.Issue499_InvalidProgramException_for_Sorting_and_comparison_function());
t.Run(new LightExpression.UnitTests.IntegerSwitchDispatchRegressionTests());
t.Run(new LightExpression.UnitTests.ParityTests());

Console.WriteLine($"Just LightExpression tests are passing in {justLightTestsStopwatch.ElapsedMilliseconds} ms.");
Expand All @@ -64,6 +65,7 @@ static void RunLightExpressionTests(object state)
fecTests.Run(new Issue498_InvalidProgramException_when_using_loop());
fecTests.Run(new Issue499_InvalidProgramException_for_Sorting_and_comparison_function());
fecTests.Run(new ReductionProgressRegressionTests());
fecTests.Run(new IntegerSwitchDispatchRegressionTests());
fecTests.Run(new ParityTests());


Expand Down
2 changes: 2 additions & 0 deletions test/FastExpressionCompiler.TestsRunner/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ public static void Main(string[] args)
lt.Run(new LightExpression.IssueTests.Issue476_System_ExecutionEngineException_with_nullables_on_repeated_calls_to_ConcurrentDictionary());
lt.Run(new LightExpression.IssueTests.Issue499_InvalidProgramException_for_Sorting_and_comparison_function());
lt.Run(new LightExpression.IssueTests.Issue561_NonBooleanEqualityOperator());
lt.Run(new LightExpression.UnitTests.IntegerSwitchDispatchRegressionTests());
lt.Run(new LightExpression.UnitTests.ParityTests());
ltTime = ltStopwatch.ElapsedMilliseconds;
});
Expand All @@ -83,6 +84,7 @@ public static void Main(string[] args)
st.Run(new Issue499_InvalidProgramException_for_Sorting_and_comparison_function());
st.Run(new Issue561_NonBooleanEqualityOperator());
st.Run(new ReductionProgressRegressionTests());
st.Run(new IntegerSwitchDispatchRegressionTests());
st.Run(new ParityTests());
st.Run(new Issue498_InvalidProgramException_when_using_loop());
st.Run(new Issue495_Incomplete_pattern_detection_for_NotSupported_1007_Return_goto_from_TryCatch_with_Assign_generates_invalid_IL());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
using System;
using SysExpr = System.Linq.Expressions.Expression;

#if LIGHT_EXPRESSION
namespace FastExpressionCompiler.LightExpression.UnitTests;
#else
namespace FastExpressionCompiler.UnitTests;
#endif

public struct IntegerSwitchDispatchRegressionTests : ITestX
{
public void Run(TestRun t)
{
Duplicate_switch_tests_choose_the_first_case(t);
Wide_switch_values_do_not_alias_low_bits(t);
Negative_and_unsigned_table_origins_match_system(t);
Wide_switches_evaluate_the_operand_once(t);
}

public enum SignedKind : long { One = 1, Two = 2, Three = 3, Max = long.MaxValue }
public enum UnsignedKind : ulong { One = 1, Two = 2, Three = 3, Max = ulong.MaxValue }
public enum SmallKind { One = 1, Two = 2, Three = 3, Max = int.MaxValue }

private static object AsValue(Type type, ulong value) => type.IsEnum
? Enum.ToObject(type, value)
: type == typeof(long) ? (object)unchecked((long)value)
: type == typeof(ulong) ? value
: type == typeof(uint) ? (object)unchecked((uint)value)
: unchecked((int)value);

public void Duplicate_switch_tests_choose_the_first_case(TestContext t)
{
foreach (var type in new[] { typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(SignedKind), typeof(UnsignedKind), typeof(SmallKind) })
{
var parameter = SysExpr.Parameter(type);
var maximum = type == typeof(long) || type == typeof(SignedKind) ? (ulong)long.MaxValue
: type == typeof(ulong) || type == typeof(UnsignedKind) ? ulong.MaxValue
: type == typeof(uint) ? uint.MaxValue : (ulong)int.MaxValue;
var expression = SysExpr.Lambda(SysExpr.Switch(parameter, SysExpr.Constant("default"),
SysExpr.SwitchCase(SysExpr.Constant("first"), SysExpr.Constant(AsValue(type, 1), type)),
SysExpr.SwitchCase(SysExpr.Constant("two"), SysExpr.Constant(AsValue(type, 2), type)),
SysExpr.SwitchCase(SysExpr.Constant("duplicate"), SysExpr.Constant(AsValue(type, 1), type)),
SysExpr.SwitchCase(SysExpr.Constant("max"), SysExpr.Constant(AsValue(type, maximum), type))), parameter);
var fast = expression.FromSysExpression().CompileFast(true);
if (!t.IsNotNull(fast)) return;
var expected = expression.Compile();
t.AreEqual("first", fast.DynamicInvoke(AsValue(type, 1)));
t.AreEqual("two", fast.DynamicInvoke(AsValue(type, 2)));
t.AreEqual("max", fast.DynamicInvoke(AsValue(type, maximum)));
foreach (var value in new[] { 0UL, 1UL, 2UL, 3UL, maximum })
t.AreEqual(expected.DynamicInvoke(AsValue(type, value)), fast.DynamicInvoke(AsValue(type, value)));
}
}

public void Wide_switch_values_do_not_alias_low_bits(TestContext t)
{
foreach (var type in new[] { typeof(long), typeof(ulong), typeof(SignedKind), typeof(UnsignedKind) })
{
var parameter = SysExpr.Parameter(type);
var expression = SysExpr.Lambda(SysExpr.Switch(parameter, SysExpr.Constant(-1),
SysExpr.SwitchCase(SysExpr.Constant(10), SysExpr.Constant(AsValue(type, 1), type)),
SysExpr.SwitchCase(SysExpr.Constant(20), SysExpr.Constant(AsValue(type, 2), type)),
SysExpr.SwitchCase(SysExpr.Constant(30), SysExpr.Constant(AsValue(type, 3), type))), parameter);
var fast = expression.FromSysExpression().CompileFast(true);
if (!t.IsNotNull(fast)) return;
var expected = expression.Compile();
foreach (var value in new[] { 0UL, 1UL, 2UL, 3UL, 4UL, (1UL << 32) + 1, (1UL << 32) + 2, ulong.MaxValue, (ulong)long.MaxValue })
t.AreEqual(expected.DynamicInvoke(AsValue(type, value)), fast.DynamicInvoke(AsValue(type, value)));
}
}

public void Negative_and_unsigned_table_origins_match_system(TestContext t)
{
foreach (var type in new[] { typeof(int), typeof(uint) })
foreach (var origin in type == typeof(int) ? new[] { int.MinValue, -3, -2, 0, 1, int.MaxValue - 3 } : new[] { 0, 1, int.MinValue, -4 })
{
var parameter = SysExpr.Parameter(type);
object Value(int offset) => type == typeof(int) ? (object)unchecked(origin + offset) : unchecked((uint)(origin + offset));
var expression = SysExpr.Lambda(SysExpr.Switch(parameter, SysExpr.Constant(-1),
SysExpr.SwitchCase(SysExpr.Constant(10), SysExpr.Constant(Value(0), type)),
SysExpr.SwitchCase(SysExpr.Constant(20), SysExpr.Constant(Value(1), type)),
SysExpr.SwitchCase(SysExpr.Constant(30), SysExpr.Constant(Value(2), type))), parameter);
var fast = expression.FromSysExpression().CompileFast(true);
if (!t.IsNotNull(fast)) return;
var expected = expression.Compile();
foreach (var offset in new[] { -1, 0, 1, 2, 3, 5 })
t.AreEqual(expected.DynamicInvoke(Value(offset)), fast.DynamicInvoke(Value(offset)));
}
}

public void Wide_switches_evaluate_the_operand_once(TestContext t)
{
var parameter = SysExpr.Parameter(typeof(Func<long>));
var expression = SysExpr.Lambda<Func<Func<long>, int>>(SysExpr.Switch(SysExpr.Invoke(parameter), SysExpr.Constant(-1),
SysExpr.SwitchCase(SysExpr.Constant(10), SysExpr.Constant(1L)),
SysExpr.SwitchCase(SysExpr.Constant(20), SysExpr.Constant(2L)),
SysExpr.SwitchCase(SysExpr.Constant(30), SysExpr.Constant(3L))), parameter);
var fast = expression.FromSysExpression().CompileFast(true);
if (!t.IsNotNull(fast)) return;
var calls = 0;
t.AreEqual(20, fast(() => { ++calls; return 2; }));
t.AreEqual(1, calls);
}
}
Loading