Skip to content

Commit 213de31

Browse files
committed
abstracting AsSpan for different Stack sizes
1 parent f36e47e commit 213de31

File tree

2 files changed

+97
-49
lines changed

2 files changed

+97
-49
lines changed

src/FastExpressionCompiler/FastExpressionCompiler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -490,7 +490,7 @@ internal static object TryCompileBoundToFirstClosureParam(Type delegateType, Exp
490490
var closurePlusParamTypes = RentOrNewClosureTypeToParamTypes(paramExprs);
491491
if (bodyExpr is NoArgsNewClassIntrinsicExpression newNoArgs)
492492
{
493-
// there is no Return of the pooled parameter types here, because in the rarest case with the unused lambda arguments we may just exaust the pooled instance
493+
// there is no Return of the pooled parameter types here, because in the rarest case with the unused lambda arguments we may just exhaust the pooled instance
494494
return CompileNoArgsNew(newNoArgs.Constructor, delegateType, closurePlusParamTypes, returnType);
495495
}
496496
#else

src/FastExpressionCompiler/ImTools.cs

Lines changed: 96 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -510,13 +510,56 @@ public interface IStack<T, TStack>
510510
Span<T> AsSpan();
511511
}
512512

513-
#pragma warning restore CS0436
513+
internal static class StackTools<T, TStack>
514+
where TStack : struct, IStack<T, TStack>
515+
{
516+
#if NETSTANDARD2_0_OR_GREATER || NET472
517+
internal static readonly ConstructorInfo SpanConstructor =
518+
typeof(Span<T>).GetConstructor(new[] { typeof(void*), typeof(int) });
519+
520+
internal delegate Span<T> AsSpanDelegate(ref TStack stack, int capacity);
521+
522+
internal static AsSpanDelegate CompileAsSpanDelegate()
523+
{
524+
var dynamicMethod = new DynamicMethod(
525+
"",
526+
typeof(Span<T>),
527+
new[] { typeof(TStack).MakeByRefType(), typeof(int) }, // todo: @perf pool this thing
528+
typeof(TStack),
529+
true
530+
);
531+
532+
// Set capacity to the estimated size to avoid realloc, 1 + 1 + 1 + 5 + 1 = 9 bytes + a small buffer
533+
var il = dynamicMethod.GetILGenerator(16);
534+
535+
// IL to replicate: return new Span<T>(Unsafe.AsPointer(ref this), StackCapacity);
536+
il.Emit(OpCodes.Ldarg_0); // Load 'ref this'
537+
il.Emit(OpCodes.Conv_U); // Convert managed reference to native unsigned int (void*)
538+
il.Emit(OpCodes.Ldarg_1); // Load length (StackCapacity) argument
539+
il.Emit(OpCodes.Newobj, SpanConstructor);
540+
il.Emit(OpCodes.Ret);
514541

515-
internal struct Stack2<T>
542+
return (AsSpanDelegate)dynamicMethod.CreateDelegate(typeof(AsSpanDelegate));
543+
}
544+
545+
// todo: @perf do we even need a lazy here?
546+
internal static readonly Lazy<AsSpanDelegate> LazyCompiledAsSpanDelegate = new(CompileAsSpanDelegate);
547+
#endif
548+
}
549+
550+
/// <summary>Implementation of `IStack` for 2 items on stack</summary>
551+
[StructLayout(LayoutKind.Sequential, Pack = 1)]
552+
public struct Stack2<T> : IStack<T, Stack2<T>>
516553
{
554+
/// <summary>Count of items on stack</summary>
517555
public const int StackCapacity = 2;
556+
518557
internal T _it0, _it1;
519558

559+
/// <inheritdoc/>
560+
public int Capacity => StackCapacity;
561+
562+
/// <inheritdoc/>
520563
[UnscopedRef]
521564
[MethodImpl((MethodImplOptions)256)]
522565
public ref T GetSurePresentRef(int index)
@@ -528,6 +571,45 @@ public ref T GetSurePresentRef(int index)
528571
default: return ref _it1;
529572
}
530573
}
574+
575+
/// <inheritdoc/>
576+
public T this[int index]
577+
{
578+
[MethodImpl((MethodImplOptions)256)]
579+
get
580+
{
581+
Debug.Assert(index < StackCapacity);
582+
return index switch
583+
{
584+
0 => _it0,
585+
_ => _it1,
586+
};
587+
}
588+
[MethodImpl((MethodImplOptions)256)]
589+
set => Set(index, in value);
590+
}
591+
592+
/// <inheritdoc/>
593+
[MethodImpl((MethodImplOptions)256)]
594+
public void Set(int index, in T value)
595+
{
596+
Debug.Assert(index < StackCapacity);
597+
switch (index)
598+
{
599+
case 0: _it0 = value; break;
600+
default: _it1 = value; break;
601+
}
602+
}
603+
604+
/// <inheritdoc/>
605+
[UnscopedRef]
606+
[MethodImpl((MethodImplOptions)256)]
607+
public Span<T> AsSpan() =>
608+
#if NETSTANDARD2_0_OR_GREATER || NET472
609+
StackTools<T, Stack2<T>>.LazyCompiledAsSpanDelegate.Value(ref this, StackCapacity);
610+
#else
611+
MemoryMarshal.CreateSpan(ref Unsafe.As<Stack2<T>, T>(ref this), StackCapacity);
612+
#endif
531613
}
532614

533615
/// <summary>Implementation of `IStack` for 4 items on stack</summary>
@@ -557,48 +639,6 @@ public ref T GetSurePresentRef(int index)
557639
}
558640
}
559641

560-
#if NETSTANDARD2_0_OR_GREATER || NET472
561-
private delegate Span<T> AsSpanDelegate(ref Stack4<T> stack, int capacity);
562-
563-
private static AsSpanDelegate CompileAsSpanDelegate()
564-
{
565-
var dynamicMethod = new DynamicMethod(
566-
"__AsSpan_Stack4_",
567-
typeof(Span<T>),
568-
new[] { typeof(Stack4<T>).MakeByRefType(), typeof(int) },
569-
typeof(Stack4<T>).Module,
570-
true
571-
);
572-
573-
var spanConstructor = typeof(Span<T>).GetConstructor(new[] { typeof(void*), typeof(int) });
574-
Debug.Assert(spanConstructor != null);
575-
576-
// Set capacity to estimated size = 1 + 1 + 1 + 5 + 1 = 9 bytes + a small buffer
577-
var il = dynamicMethod.GetILGenerator(16);
578-
579-
// IL to replicate: return new Span<T>(Unsafe.AsPointer(ref this), StackCapacity);
580-
il.Emit(OpCodes.Ldarg_0); // Load 'ref this'
581-
il.Emit(OpCodes.Conv_U); // Convert managed reference to native unsigned int (void*)
582-
il.Emit(OpCodes.Ldarg_1); // Load length (StackCapacity) argument
583-
il.Emit(OpCodes.Newobj, spanConstructor);
584-
il.Emit(OpCodes.Ret);
585-
586-
return (AsSpanDelegate)dynamicMethod.CreateDelegate(typeof(AsSpanDelegate));
587-
}
588-
589-
private static readonly Lazy<AsSpanDelegate> _lazyCompiledAsSpanDelegate = new(CompileAsSpanDelegate);
590-
591-
/// <inheritdoc/>
592-
[UnscopedRef]
593-
[MethodImpl((MethodImplOptions)256)]
594-
public Span<T> AsSpan() => _lazyCompiledAsSpanDelegate.Value(ref this, StackCapacity);
595-
#else
596-
/// <inheritdoc/>
597-
[UnscopedRef]
598-
[MethodImpl((MethodImplOptions)256)]
599-
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref Unsafe.As<Stack4<T>, T>(ref this), StackCapacity);
600-
#endif
601-
602642
/// <inheritdoc/>
603643
public T this[int index]
604644
{
@@ -611,8 +651,7 @@ public T this[int index]
611651
0 => _it0,
612652
1 => _it1,
613653
2 => _it2,
614-
3 => _it3,
615-
_ => default,
654+
_ => _it3,
616655
};
617656
}
618657
[MethodImpl((MethodImplOptions)256)]
@@ -629,10 +668,19 @@ public void Set(int index, in T value)
629668
case 0: _it0 = value; break;
630669
case 1: _it1 = value; break;
631670
case 2: _it2 = value; break;
632-
case 3: _it3 = value; break;
633-
default: break;
671+
default: _it3 = value; break;
634672
}
635673
}
674+
675+
/// <inheritdoc/>
676+
[UnscopedRef]
677+
[MethodImpl((MethodImplOptions)256)]
678+
public Span<T> AsSpan() =>
679+
#if NETSTANDARD2_0_OR_GREATER || NET472
680+
StackTools<T, Stack4<T>>.LazyCompiledAsSpanDelegate.Value(ref this, StackCapacity);
681+
#else
682+
MemoryMarshal.CreateSpan(ref Unsafe.As<Stack4<T>, T>(ref this), StackCapacity);
683+
#endif
636684
}
637685

638686
// todo: @wip

0 commit comments

Comments
 (0)