Skip to content

Commit 02e3372

Browse files
Copilotdadhi
andauthored
feat(#533): inline small-enum constants in _data to avoid boxing
Extend the inline-constant path to cover enum types whose underlying type is ≤ 32 bits (byte/sbyte/char/short/ushort/int/uint — virtually all C# enums). Raw bits are stored in _data via System.Convert.ToInt64 + uint cast; the Reader reconstructs the typed enum via Enum.ToObject using the underlying TypeCode. Long/ulong-backed enums (extremely rare) continue to be boxed in Obj. Add FlatExpressionThrow.UnsupportedInlineConstantType(Type, TypeCode) overload for the error path in ReadInlineValue. Add Flat_enum_constant_stored_inline_roundtrip test covering all six underlying types (byte/sbyte/short/ushort/int/uint), verifying no ClosureConstants allocation and correct value round-trip. (1679 tests) Agent-Logs-Url: https://github.com/dadhi/FastExpressionCompiler/sessions/0ffda673-c511-49f5-ad08-e070318b3781 Co-authored-by: dadhi <39516+dadhi@users.noreply.github.com>
1 parent 170ad90 commit 02e3372

2 files changed

Lines changed: 78 additions & 5 deletions

File tree

src/FastExpressionCompiler.LightExpression/FlatExpression.cs

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -190,9 +190,18 @@ public int Constant(object value) =>
190190
/// <summary>Adds a constant node with an explicit constant type.</summary>
191191
public int Constant(object value, Type type)
192192
{
193-
if (value == null || value is string || value is Type || type.IsEnum || value is decimal)
193+
if (value == null || value is string || value is Type || value is decimal)
194194
return AddRawExpressionNode(type, value, ExpressionType.Constant);
195195

196+
if (type.IsEnum)
197+
{
198+
var underlyingTc = Type.GetTypeCode(Enum.GetUnderlyingType(type));
199+
if (IsSmallPrimitive(underlyingTc))
200+
return AddInlineConstantNode(type, (uint)System.Convert.ToInt64(value));
201+
// long/ulong-backed enum (extremely rare): store boxed in Obj
202+
return AddRawExpressionNode(type, value, ExpressionType.Constant);
203+
}
204+
196205
if (type.IsPrimitive)
197206
{
198207
var tc = Type.GetTypeCode(type);
@@ -1003,8 +1012,17 @@ private int AddConstant(System.Linq.Expressions.ConstantExpression constant)
10031012
var value = constant.Value;
10041013
var type = constant.Type;
10051014

1006-
if (value == null || value is string || value is Type || type.IsEnum || value is decimal)
1015+
if (value == null || value is string || value is Type || value is decimal)
1016+
return _tree.AddRawExpressionNode(type, value, ExpressionType.Constant);
1017+
1018+
if (type.IsEnum)
1019+
{
1020+
var underlyingTc = Type.GetTypeCode(Enum.GetUnderlyingType(type));
1021+
if (IsSmallPrimitive(underlyingTc))
1022+
return _tree.AddInlineConstantNode(type, (uint)System.Convert.ToInt64(value));
1023+
// long/ulong-backed enum (extremely rare): store boxed in Obj
10071024
return _tree.AddRawExpressionNode(type, value, ExpressionType.Constant);
1025+
}
10081026

10091027
if (type.IsPrimitive)
10101028
{
@@ -1669,8 +1687,21 @@ private ChildList GetChildren(int index)
16691687
}
16701688

16711689
[MethodImpl(MethodImplOptions.AggressiveInlining)]
1672-
private static object ReadInlineValue(Type type, uint data) =>
1673-
Type.GetTypeCode(type) switch
1690+
private static object ReadInlineValue(Type type, uint data)
1691+
{
1692+
if (type.IsEnum)
1693+
return Enum.ToObject(type, Type.GetTypeCode(Enum.GetUnderlyingType(type)) switch
1694+
{
1695+
TypeCode.Byte => (object)(byte)data,
1696+
TypeCode.SByte => (object)(sbyte)(byte)data,
1697+
TypeCode.Char => (object)(char)(ushort)data,
1698+
TypeCode.Int16 => (object)(short)(ushort)data,
1699+
TypeCode.UInt16 => (object)(ushort)data,
1700+
TypeCode.Int32 => (object)(int)data,
1701+
TypeCode.UInt32 => (object)data,
1702+
var tc => FlatExpressionThrow.UnsupportedInlineConstantType<object>(type, tc)
1703+
});
1704+
return Type.GetTypeCode(type) switch
16741705
{
16751706
TypeCode.Boolean => (object)(data != 0),
16761707
TypeCode.Byte => (object)(byte)data,
@@ -1683,6 +1714,7 @@ private static object ReadInlineValue(Type type, uint data) =>
16831714
TypeCode.Single => (object)FloatBits.ToFloat(data),
16841715
_ => FlatExpressionThrow.UnsupportedInlineConstantType<object>(type)
16851716
};
1717+
}
16861718

16871719
[RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)]
16881720
private SysExpr[] ReadExpressions(in ChildList childIndexes)
@@ -1722,6 +1754,10 @@ internal static class FlatExpressionThrow
17221754
internal static T UnsupportedInlineConstantType<T>(Type type) =>
17231755
throw new NotSupportedException($"Cannot reconstruct inline constant of type {type}");
17241756

1757+
[MethodImpl(MethodImplOptions.NoInlining)]
1758+
internal static T UnsupportedInlineConstantType<T>(Type type, TypeCode tc) =>
1759+
throw new NotSupportedException($"Cannot reconstruct inline constant of type {type} with TypeCode {tc}");
1760+
17251761
[MethodImpl(MethodImplOptions.NoInlining)]
17261762
internal static T UnsupportedInlineConstantType<T>(object value, TypeCode tc) =>
17271763
throw new NotSupportedException($"Cannot convert value '{value}' of TypeCode {tc} to an inline constant");

test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ public int Run()
3939
Flat_block_variables_and_refs_yield_same_identity();
4040
Flat_nested_lambda_captures_outer_parameter_identity();
4141
Flat_out_of_order_decl_block_in_lambda_compiles_correctly();
42-
return 22;
42+
Flat_enum_constant_stored_inline_roundtrip();
43+
return 23;
4344
}
4445

4546

@@ -649,5 +650,41 @@ public void Flat_out_of_order_decl_block_in_lambda_compiles_correctly()
649650
// p=0 → v1 = 0, v2 = 0
650651
Asserts.AreEqual(0, func(0));
651652
}
653+
654+
enum ByteEnum : byte { A = 1, B = 200 }
655+
enum SByteEnum : sbyte { A = -1, B = 50 }
656+
enum ShortEnum : short { A = -1000, B = 30000 }
657+
enum UShortEnum : ushort { A = 0, B = 60000 }
658+
enum IntEnum : int { A = int.MinValue, B = 42 }
659+
enum UIntEnum : uint { A = 0, B = uint.MaxValue }
660+
661+
public void Flat_enum_constant_stored_inline_roundtrip()
662+
{
663+
// Verify that enum constants with ≤32-bit underlying types are stored inline
664+
// (no ClosureConstants entry, no boxing) and round-trip correctly.
665+
void Check<TEnum>(TEnum enumValue) where TEnum : Enum
666+
{
667+
var fe = default(ExprTree);
668+
var idx = fe.Constant(enumValue, typeof(TEnum));
669+
Asserts.AreEqual(0, fe.ClosureConstants.Count,
670+
$"{typeof(TEnum).Name}.{enumValue} should be inline (no ClosureConstants), but got {fe.ClosureConstants.Count}");
671+
fe.RootIndex = fe.Lambda<Func<TEnum>>(idx);
672+
var result = (TEnum)((System.Linq.Expressions.LambdaExpression)fe.ToExpression()).Compile().DynamicInvoke()!;
673+
Asserts.AreEqual(enumValue, result, $"Round-trip failed for {typeof(TEnum).Name}.{enumValue}");
674+
}
675+
676+
Check(ByteEnum.A);
677+
Check(ByteEnum.B);
678+
Check(SByteEnum.A);
679+
Check(SByteEnum.B);
680+
Check(ShortEnum.A);
681+
Check(ShortEnum.B);
682+
Check(UShortEnum.A);
683+
Check(UShortEnum.B);
684+
Check(IntEnum.A);
685+
Check(IntEnum.B);
686+
Check(UIntEnum.A);
687+
Check(UIntEnum.B);
688+
}
652689
}
653690
}

0 commit comments

Comments
 (0)