Skip to content

Commit 116a593

Browse files
Copilotdadhi
andauthored
Fix netstandard2.0 build: use FloatIntBits union instead of BitConverter.SingleToInt32Bits; add XML docs to all public members
Agent-Logs-Url: https://github.com/dadhi/FastExpressionCompiler/sessions/8d5fabaa-d5e2-458c-b771-309266696c44 Co-authored-by: dadhi <39516+dadhi@users.noreply.github.com>
1 parent b9df8ea commit 116a593

1 file changed

Lines changed: 54 additions & 2 deletions

File tree

src/FastExpressionCompiler/FlatExpression.cs

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,17 +48,25 @@ namespace FastExpressionCompiler.FlatExpression;
4848
[StructLayout(LayoutKind.Sequential)]
4949
public struct Idx : IEquatable<Idx>
5050
{
51+
/// <summary>Raw 1-based index; 0 means nil.</summary>
5152
public int It;
5253

54+
/// <summary>True when this index is nil (unset).</summary>
5355
public bool IsNil => It == 0;
56+
/// <summary>The nil sentinel value.</summary>
5457
public static Idx Nil => default;
5558

59+
/// <summary>Creates a 1-based index from the given value.</summary>
5660
[MethodImpl(MethodImplOptions.AggressiveInlining)]
5761
public static Idx Of(int oneBasedIndex) => new Idx { It = oneBasedIndex };
5862

63+
/// <inheritdoc/>
5964
public bool Equals(Idx other) => It == other.It;
65+
/// <inheritdoc/>
6066
public override bool Equals(object obj) => obj is Idx other && Equals(other);
67+
/// <inheritdoc/>
6168
public override int GetHashCode() => It;
69+
/// <inheritdoc/>
6270
public override string ToString() => IsNil ? "nil" : It.ToString();
6371
}
6472

@@ -91,9 +99,13 @@ public struct Idx : IEquatable<Idx>
9199
public struct ExpressionNode // 32 bytes: Type(8)+Info(8)+NodeType(4)+NextIdx(4)+ChildIdx(4)+ExtraIdx(4)
92100
{
93101
// Reference fields placed first to avoid 4-byte padding that would appear after NodeType.
102+
/// <summary>Result type of this node.</summary>
94103
public Type Type;
104+
/// <summary>Method/constructor for Call/New/Unary/Binary; parameter name for Parameter; closure key for Constant; parameter <see cref="Idx"/> array for Lambda.</summary>
95105
public object Info;
106+
/// <summary>Expression kind (mirrors <see cref="System.Linq.Expressions.ExpressionType"/>).</summary>
96107
public ExpressionType NodeType;
108+
/// <summary>Next sibling in an intrusive linked list (arguments, block expressions, etc.).</summary>
97109
public Idx NextIdx;
98110
/// <summary>First child node, or for Constant with ExtraIdx.It==-1: raw int32 value bits.</summary>
99111
public Idx ChildIdx;
@@ -110,13 +122,18 @@ public struct ExpressionNode // 32 bytes: Type(8)+Info(8)+NodeType(4)+NextIdx(4
110122
public struct ExpressionTree
111123
{
112124
// First 16 nodes are on the stack; further nodes spill to a heap array.
125+
/// <summary>Flat node storage. First 16 nodes are stack-resident; further nodes spill to a heap array.</summary>
113126
public SmallList<ExpressionNode, Stack16<ExpressionNode>, NoArrayPool<ExpressionNode>> Nodes;
114127
// First 4 closure constants on stack.
128+
/// <summary>Closure-captured constants. First 4 are stack-resident.</summary>
115129
public SmallList<object, Stack4<object>, NoArrayPool<object>> ClosureConstants;
130+
/// <summary>Index of the root expression node (typically a Lambda).</summary>
116131
public Idx RootIdx;
117132

133+
/// <summary>Total number of nodes in this tree.</summary>
118134
public int NodeCount => Nodes.Count;
119135

136+
/// <summary>Returns a reference to the node at the given index.</summary>
120137
[UnscopedRef]
121138
[MethodImpl(MethodImplOptions.AggressiveInlining)]
122139
public ref ExpressionNode NodeAt(Idx idx)
@@ -155,7 +172,7 @@ private static int ToInt32Bits(object value, Type t)
155172
if (t == typeof(int)) return (int)value;
156173
if (t == typeof(uint)) return (int)(uint)value; // reinterpret bits
157174
if (t == typeof(bool)) return (bool)value ? 1 : 0;
158-
if (t == typeof(float)) return BitConverter.SingleToInt32Bits((float)value);
175+
if (t == typeof(float)) return FloatIntBits.FloatToInt((float)value);
159176
if (t == typeof(byte)) return (byte)value;
160177
if (t == typeof(sbyte)) return (sbyte)value;
161178
if (t == typeof(short)) return (short)value;
@@ -170,7 +187,7 @@ internal static object FromInt32Bits(int bits, Type t)
170187
if (t == typeof(int)) return bits;
171188
if (t == typeof(uint)) return (uint)bits;
172189
if (t == typeof(bool)) return bits != 0;
173-
if (t == typeof(float)) return BitConverter.Int32BitsToSingle(bits);
190+
if (t == typeof(float)) return FloatIntBits.IntToFloat(bits);
174191
if (t == typeof(byte)) return (byte)bits;
175192
if (t == typeof(sbyte)) return (sbyte)bits;
176193
if (t == typeof(short)) return (short)bits;
@@ -179,11 +196,22 @@ internal static object FromInt32Bits(int bits, Type t)
179196
return null; // unreachable
180197
}
181198

199+
// Explicit-layout union to reinterpret float/int bits without Unsafe or BitConverter (portable across all TFMs).
200+
[StructLayout(LayoutKind.Explicit)]
201+
private struct FloatIntBits
202+
{
203+
[FieldOffset(0)] public float F;
204+
[FieldOffset(0)] public int I;
205+
public static int FloatToInt(float f) => new FloatIntBits { F = f }.I;
206+
public static float IntToFloat(int i) => new FloatIntBits { I = i }.F;
207+
}
208+
182209
// Types not fitting in int32 but still safe to keep inline in Info (no special closure treatment needed).
183210
private static bool IsInfoInline(Type t) =>
184211
t == typeof(string) || t == typeof(long) || t == typeof(double) ||
185212
t == typeof(decimal)|| t == typeof(DateTime)|| t == typeof(Guid);
186213

214+
/// <summary>Adds a Constant node. Small value types (int, bool, float, etc.) are stored inline without boxing.</summary>
187215
public Idx Constant(object value, bool putIntoClosure = false)
188216
{
189217
if (value == null)
@@ -207,54 +235,70 @@ public Idx Constant(object value, bool putIntoClosure = false)
207235
return AddNode(ExpressionType.Constant, type, extraIdx: new Idx { It = ci + 1 });
208236
}
209237

238+
/// <summary>Typed overload of <see cref="Constant(object,bool)"/>.</summary>
210239
public Idx Constant<T>(T value, bool putIntoClosure = false) =>
211240
Constant((object)value, putIntoClosure);
212241

242+
/// <summary>Adds a Parameter node with the given type and optional name.</summary>
213243
public Idx Parameter(Type type, string name = null) =>
214244
AddNode(ExpressionType.Parameter, type, info: name);
215245

246+
/// <summary>Alias for <see cref="Parameter"/> — adds a block-local variable node.</summary>
216247
public Idx Variable(Type type, string name = null) =>
217248
AddNode(ExpressionType.Parameter, type, info: name);
218249

250+
/// <summary>Adds a Default(type) node.</summary>
219251
public Idx Default(Type type) =>
220252
AddNode(ExpressionType.Default, type);
221253

254+
/// <summary>Adds a unary expression node.</summary>
222255
public Idx Unary(ExpressionType nodeType, Idx operand, Type type, MethodInfo method = null) =>
223256
AddNode(nodeType, type, info: method, childIdx: operand);
224257

258+
/// <summary>Adds a Convert node.</summary>
225259
public Idx Convert(Idx operand, Type toType) =>
226260
Unary(ExpressionType.Convert, operand, toType);
227261

262+
/// <summary>Adds a Not node.</summary>
228263
public Idx Not(Idx operand) =>
229264
Unary(ExpressionType.Not, operand, typeof(bool));
230265

266+
/// <summary>Adds a Negate node.</summary>
231267
public Idx Negate(Idx operand, Type type) =>
232268
Unary(ExpressionType.Negate, operand, type);
233269

270+
/// <summary>Adds a binary expression node.</summary>
234271
public Idx Binary(ExpressionType nodeType, Idx left, Idx right, Type type, MethodInfo method = null) =>
235272
AddNode(nodeType, type, info: method, childIdx: left, extraIdx: right);
236273

274+
/// <summary>Adds an Add node.</summary>
237275
public Idx Add(Idx left, Idx right, Type type) =>
238276
Binary(ExpressionType.Add, left, right, type);
239277

278+
/// <summary>Adds a Subtract node.</summary>
240279
public Idx Subtract(Idx left, Idx right, Type type) =>
241280
Binary(ExpressionType.Subtract, left, right, type);
242281

282+
/// <summary>Adds a Multiply node.</summary>
243283
public Idx Multiply(Idx left, Idx right, Type type) =>
244284
Binary(ExpressionType.Multiply, left, right, type);
245285

286+
/// <summary>Adds an Equal node (returns bool).</summary>
246287
public Idx Equal(Idx left, Idx right) =>
247288
Binary(ExpressionType.Equal, left, right, typeof(bool));
248289

290+
/// <summary>Adds an Assign node.</summary>
249291
public Idx Assign(Idx target, Idx value, Type type) =>
250292
Binary(ExpressionType.Assign, target, value, type);
251293

294+
/// <summary>Adds a New node calling the given constructor with the provided arguments.</summary>
252295
public Idx New(ConstructorInfo ctor, params Idx[] args)
253296
{
254297
var firstArgIdx = LinkList(args);
255298
return AddNode(ExpressionType.New, ctor.DeclaringType, info: ctor, childIdx: firstArgIdx);
256299
}
257300

301+
/// <summary>Adds a Call node. Pass <see cref="Idx.Nil"/> for <paramref name="instance"/> for static calls.</summary>
258302
public Idx Call(MethodInfo method, Idx instance, params Idx[] args)
259303
{
260304
var returnType = method.ReturnType == typeof(void) ? typeof(void) : method.ReturnType;
@@ -266,6 +310,7 @@ public Idx Call(MethodInfo method, Idx instance, params Idx[] args)
266310

267311
// Parameters stored in Info as Idx[] rather than chained via NextIdx, because the same
268312
// parameter node may already have its NextIdx used as part of a New/Call argument chain.
313+
/// <summary>Adds a Lambda node. Sets <see cref="RootIdx"/> when <paramref name="isRoot"/> is true.</summary>
269314
public Idx Lambda(Type delegateType, Idx body, Idx[] parameters = null, bool isRoot = true)
270315
{
271316
var lambdaIdx = AddNode(ExpressionType.Lambda, delegateType, info: parameters, childIdx: body);
@@ -274,19 +319,22 @@ public Idx Lambda(Type delegateType, Idx body, Idx[] parameters = null, bool isR
274319
return lambdaIdx;
275320
}
276321

322+
/// <summary>Adds a Conditional (ternary) node.</summary>
277323
public Idx Conditional(Idx test, Idx ifTrue, Idx ifFalse, Type type)
278324
{
279325
NodeAt(ifTrue).NextIdx = ifFalse; // ifFalse hangs off ifTrue.NextIdx
280326
return AddNode(ExpressionType.Conditional, type, childIdx: test, extraIdx: ifTrue);
281327
}
282328

329+
/// <summary>Adds a Block node containing the given expressions and optional block-local variables.</summary>
283330
public Idx Block(Type type, Idx[] exprs, Idx[] variables = null)
284331
{
285332
var firstExprIdx = LinkList(exprs);
286333
var firstVarIdx = variables == null || variables.Length == 0 ? Idx.Nil : LinkList(variables);
287334
return AddNode(ExpressionType.Block, type, childIdx: firstExprIdx, extraIdx: firstVarIdx);
288335
}
289336

337+
/// <summary>Chains the given indices via <see cref="ExpressionNode.NextIdx"/> and returns the first index.</summary>
290338
public Idx LinkList(Idx[] indices)
291339
{
292340
if (indices == null || indices.Length == 0)
@@ -298,6 +346,7 @@ public Idx LinkList(Idx[] indices)
298346
}
299347

300348
// Allocates an enumerator — suitable for tests and diagnostics; avoid in hot paths.
349+
/// <summary>Enumerates the sibling chain starting at <paramref name="head"/>. Allocates an enumerator — avoid in hot paths.</summary>
301350
public IEnumerable<Idx> Siblings(Idx head)
302351
{
303352
var cur = head;
@@ -309,6 +358,7 @@ public IEnumerable<Idx> Siblings(Idx head)
309358
}
310359

311360
// Builds body after registering params so they are found in paramMap when encountered in the body.
361+
/// <summary>Converts this flat tree to a <see cref="System.Linq.Expressions.Expression"/> rooted at <see cref="RootIdx"/>.</summary>
312362
public SysExpr ToSystemExpression()
313363
{
314364
var paramMap = default(SmallMap16<int, SysParam, IntEq>);
@@ -459,6 +509,7 @@ private List<SysExpr> SiblingList(Idx head, ref SmallMap16<int, SysParam, IntEq>
459509
}
460510

461511
// O(n) structural equality — no traversal, single pass over the flat arrays.
512+
/// <summary>O(n) structural equality check. Compares both trees node-by-node in a single pass — no recursive traversal.</summary>
462513
public static bool StructurallyEqual(ref ExpressionTree a, ref ExpressionTree b)
463514
{
464515
if (a.NodeCount != b.NodeCount) return false;
@@ -494,6 +545,7 @@ private static bool InfoEqual(object infoA, object infoB)
494545
return Equals(infoA, infoB);
495546
}
496547

548+
/// <summary>Returns a human-readable dump of all nodes and closure constants for diagnostics.</summary>
497549
public string Dump()
498550
{
499551
var sb = new System.Text.StringBuilder();

0 commit comments

Comments
 (0)