Skip to content
Merged
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
18 changes: 13 additions & 5 deletions src/FastExpressionCompiler/FastExpressionCompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5993,7 +5993,9 @@ 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.IsIntegerOrUnderlyingIntegerUpTo32bits())
{
var multipleTestValuesLabelsId = 0;
var hasDuplicateTestValue = false;
Expand Down Expand Up @@ -6198,7 +6200,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 @@ -6210,8 +6212,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 @@ -6222,7 +6224,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 Expand Up @@ -9139,6 +9141,12 @@ internal static bool IsIntegerOrUnderlyingInteger(this Type type) =>
type.IsPrimitive && type.IsInteger() ||
type.IsEnum && Enum.GetUnderlyingType(type).IsInteger();

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static bool IsIntegerOrUnderlyingIntegerUpTo32bits(this Type type) =>
(type.IsPrimitive || type.IsEnum) && Type.GetTypeCode(type) is
TypeCode.Char or TypeCode.SByte or TypeCode.Byte or
TypeCode.Int16 or TypeCode.UInt16 or TypeCode.Int32 or TypeCode.UInt32;

internal static bool IsPrimitiveWithZeroDefaultExceptDecimal(this Type type)
{
switch (Type.GetTypeCode(type))
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 @@ -46,6 +46,7 @@ static void RunLightExpressionTests(object state)
t.Run(new LightExpression.UnitTests.ByRefConversionRegressionTests());
t.Run(new LightExpression.UnitTests.RuntimeTypeConstantRegressionTests());
t.Run(new LightExpression.UnitTests.ExtensionReductionIdentityRegressionTests());
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 @@ -71,6 +72,7 @@ static void RunLightExpressionTests(object state)
fecTests.Run(new RuntimeTypeConstantRegressionTests());
fecTests.Run(new CustomReducibleNodeRegressionTests());
fecTests.Run(new ExtensionReductionIdentityRegressionTests());
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 @@ -78,6 +78,7 @@ public static void Main(string[] args)
lt.Run(new LightExpression.UnitTests.ByRefConversionRegressionTests());
lt.Run(new LightExpression.UnitTests.RuntimeTypeConstantRegressionTests());
lt.Run(new LightExpression.UnitTests.ExtensionReductionIdentityRegressionTests());
lt.Run(new LightExpression.UnitTests.IntegerSwitchDispatchRegressionTests());
lt.Run(new LightExpression.UnitTests.ParityTests());
ltTime = ltStopwatch.ElapsedMilliseconds;
});
Expand All @@ -90,6 +91,7 @@ public static void Main(string[] args)
st.Run(new RuntimeTypeConstantRegressionTests());
st.Run(new CustomReducibleNodeRegressionTests());
st.Run(new ExtensionReductionIdentityRegressionTests());
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,131 @@
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)
{
Small_integer_switches_match_system(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 }

public enum SByteKind : sbyte { One = 1, Two = 2, Three = 3 }
public enum ByteKind : byte { One = 1, Two = 2, Three = 3 }
public enum Int16Kind : short { One = 1, Two = 2, Three = 3 }
public enum UInt16Kind : ushort { One = 1, Two = 2, Three = 3 }
public enum UInt32Kind : uint { One = 1, Two = 2, Three = 3 }

public void Small_integer_switches_match_system(TestContext t)
{
foreach (var type in new[] { typeof(char), typeof(sbyte), typeof(byte), typeof(short), typeof(ushort),
typeof(int), typeof(uint), typeof(SByteKind), typeof(ByteKind), typeof(Int16Kind),
typeof(UInt16Kind), typeof(SmallKind), typeof(UInt32Kind) })
{
object Value(int value) => type.IsEnum ? Enum.ToObject(type, value) : Convert.ChangeType(value, type);
var parameter = SysExpr.Parameter(type);
var expression = SysExpr.Lambda(SysExpr.Switch(parameter, SysExpr.Constant(-1),
SysExpr.SwitchCase(SysExpr.Constant(10), SysExpr.Constant(Value(1), type)),
SysExpr.SwitchCase(SysExpr.Constant(20), SysExpr.Constant(Value(2), type)),
SysExpr.SwitchCase(SysExpr.Constant(30), SysExpr.Constant(Value(3), type))), parameter);
var fast = expression.FromSysExpression().CompileFast(true);
if (!t.IsNotNull(fast)) return;
var expected = expression.Compile();
for (var value = 0; value <= 4; ++value)
t.AreEqual(expected.DynamicInvoke(Value(value)), fast.DynamicInvoke(Value(value)));
}
}

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