Skip to content

Commit

Permalink
adds support and tests for initializing arrays with collection expres…
Browse files Browse the repository at this point in the history
…sions (#256)
  • Loading branch information
adrianoc committed Sep 23, 2024
1 parent fbe40a1 commit ed375d9
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 16 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Cecilifier.Core.Tests.Framework;
using NUnit.Framework;

namespace Cecilifier.Core.Tests.OutputBased;

public class CollectionExpressionTests : OutputBasedTestBase
{
[Test]
public void ArrayWith3OrMoreElements()
{
AssertOutput("int[] mediumArray = [1, 2, 3]; System.Console.WriteLine(mediumArray[0] + mediumArray[2]);", "4");
}

[Test]
public void ArrayWith2OrLessElements()
{
AssertOutput("int[] mediumArray = [1, 2]; System.Console.WriteLine(mediumArray[0] + mediumArray[1]);", "3");
}
}
40 changes: 40 additions & 0 deletions Cecilifier.Core.Tests/Tests/Unit/CollectionExpressionTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using NUnit.Framework;

namespace Cecilifier.Core.Tests.Tests.Unit;

[TestFixture]
public class CollectionExpressionTests : CecilifierUnitTestBase
{
[Test]
public void ArrayWith3OrMoreElements_UsesOptimizedInitialization()
{
var result = RunCecilifier("int[] mediumArray = [1, 2, 3];");
var cecilified = result.GeneratedCode.ReadToEnd();
Assert.That(cecilified, Does.Match("//__StaticArrayInitTypeSize=12 struct."));
Assert.That(cecilified, Does.Match(@"var fld_arrayInitializerData_\d+ = new FieldDefinition\(""[A-F0-9]+"",.+, st_rawDataTypeVar_\d+\);"));
Assert.That(cecilified, Does.Match(@"il_topLevelMain_3.Emit\(OpCodes.Ldtoken, fld_arrayInitializerData_\d+\);"));
}

[Test]
public void ArrayWith2OrLessElements_DoesNotUsesOptimizedInitialization()
{
var result = RunCecilifier("int[] mediumArray = [1, 2];");
var cecilified = result.GeneratedCode.ReadToEnd();
Assert.That(cecilified, Does.Not.Match(@"//__StaticArrayInitTypeSize=\d+ struct."));
Assert.That(cecilified, Does.Match("""
//int\[\] mediumArray = \[1, 2\];
\s+var (?<array>l_mediumArray_\d+) = new VariableDefinition\(assembly.MainModule.TypeSystem.Int32.MakeArrayType\(\)\);
\s+m_topLevelStatements_1.Body.Variables.Add\(\k<array>\);
\s+(?<il>il_topLevelMain_\d+.Emit\(OpCodes\.)Ldc_I4, 2\);
\s+\k<il>Newarr, assembly.MainModule.TypeSystem.Int32\);
\s+\k<il>Dup\);
\s+\k<il>Ldc_I4, 0\);
\s+\k<il>Ldc_I4, 1\);
\s+\k<il>Stelem_I4\);
\s+\k<il>Dup\);
\s+\k<il>Ldc_I4, 1\);
\s+\k<il>Ldc_I4, 2\);
\s+\k<il>Stelem_I4\);
"""));
}
}
31 changes: 22 additions & 9 deletions Cecilifier.Core/AST/ExpressionVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -915,6 +915,19 @@ public override void VisitBaseExpression(BaseExpressionSyntax node)
Context.EmitCilInstruction(ilVar, OpCodes.Ldarg_0);
}

public override void VisitCollectionExpression(CollectionExpressionSyntax node)
{
Context.EmitCilInstruction(ilVar, OpCodes.Ldc_I4, node.Elements.Count);
var arrayTypeSymbol = Context.GetTypeInfo(node).ConvertedType.EnsureNotNull<ITypeSymbol, IArrayTypeSymbol>();
AddCilInstruction(ilVar, OpCodes.Newarr, arrayTypeSymbol.ElementType);

using var _ = LineInformationTracker.Track(Context, node);
if (PrivateImplementationDetailsGenerator.IsApplicableTo(node))
InitializeArrayOptimized(arrayTypeSymbol.ElementType, node.Elements);
else
InitializeArrayUnoptimized<CollectionElementSyntax>(arrayTypeSymbol.ElementType, node.Elements);
}

public override void VisitAwaitExpression(AwaitExpressionSyntax node) => LogUnsupportedSyntax(node);
public override void VisitTupleExpression(TupleExpressionSyntax node) => LogUnsupportedSyntax(node);
public override void VisitSwitchExpression(SwitchExpressionSyntax node) => LogUnsupportedSyntax(node);
Expand Down Expand Up @@ -1492,21 +1505,21 @@ private void ProcessArrayCreation(ITypeSymbol elementType, InitializerExpression
{
AddCilInstruction(ilVar, OpCodes.Newarr, elementType);
if (PrivateImplementationDetailsGenerator.IsApplicableTo(initializer, Context))
InitializeArrayOptimized(elementType, initializer);
InitializeArrayOptimized(elementType, initializer.Expressions);
else
InitializeArrayUnoptimized(elementType, initializer);
InitializeArrayUnoptimized(elementType, initializer?.Expressions);
}

private void InitializeArrayUnoptimized(ITypeSymbol elementType, InitializerExpressionSyntax initializer)
private void InitializeArrayUnoptimized<TElement>(ITypeSymbol elementType, SeparatedSyntaxList<TElement>? elements) where TElement : CSharpSyntaxNode
{
var stelemOpCode = elementType.StelemOpCode();
for (var i = 0; i < initializer?.Expressions.Count; i++)
for (var i = 0; i < elements?.Count; i++)
{
Context.EmitCilInstruction(ilVar, OpCodes.Dup);
Context.EmitCilInstruction(ilVar, OpCodes.Ldc_I4, i);
initializer.Expressions[i].Accept(this);
elements.Value[i].Accept(this);

var itemType = Context.GetTypeInfo(initializer.Expressions[i]);
var itemType = Context.SemanticModel.GetTypeInfo(elements.Value[i]);
if (elementType.IsReferenceType && itemType.Type != null && itemType.Type.IsValueType)
{
AddCilInstruction(ilVar, OpCodes.Box, itemType.Type);
Expand All @@ -1516,7 +1529,7 @@ private void InitializeArrayUnoptimized(ITypeSymbol elementType, InitializerExpr
}
}

private void InitializeArrayOptimized(ITypeSymbol elementType, InitializerExpressionSyntax initializer)
private void InitializeArrayOptimized<TElement>(ITypeSymbol elementType, SeparatedSyntaxList<TElement> elements) where TElement: SyntaxNode
{
var initializeArrayHelper = Context.RoslynTypeSystem.SystemRuntimeCompilerServicesRuntimeHelpers
.GetMembers(Constants.Common.RuntimeHelpersInitializeArrayMethodName)
Expand All @@ -1530,9 +1543,9 @@ private void InitializeArrayOptimized(ITypeSymbol elementType, InitializerExpres
//IL_0011: pop
var backingFieldVar = PrivateImplementationDetailsGenerator.GetOrCreateInitializationBackingFieldVariableName(
Context,
elementType.SizeofArrayLikeItemElement() * initializer.Expressions.Count,
elementType.SizeofArrayLikeItemElement() * elements.Count,
elementType.Name,
$"new {elementType.Name}[] {initializer.ToFullString()}");
$"new {elementType.Name}[] {{ {elements.ToFullString()}}}");

Context.EmitCilInstruction(ilVar, OpCodes.Dup);
Context.EmitCilInstruction(ilVar, OpCodes.Ldtoken, backingFieldVar);
Expand Down
2 changes: 1 addition & 1 deletion Cecilifier.Core/AST/InlineArrayProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ internal static bool TryHandleRangeElementAccess(IVisitorContext context, Expres
// Takes the inline array and convert to a Span<T>
HandleInlineArrayConversionToSpan(context, ilVar, inlineArrayType, elementAccess, storageSymbol.LoadAddressOpcodeForMember(), elementAccess.Expression.ToString(), storageVariableMemberKind, memberParentName);

// at this point we have a Span<T> (for the inline array)) at the top of the stack so just delegate to the visitor in charge of handling
// at this point we have a Span<T> (for the inline array) at the top of the stack so just delegate to the visitor in charge of handling
// indexing Span<T> with a range.
elementAccess.Accept(new ElementAccessExpressionWithRangeArgumentVisitor(context, ilVar, expressionVisitor, targetAlreadyLoaded: true));
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -384,16 +384,14 @@ internal static bool IsApplicableTo(InitializerExpressionSyntax node, IVisitorCo
{
return node switch
{
{ RawKind: (int) SyntaxKind.ArrayInitializerExpression } => IsLargeEnoughToWarrantOptimization(node),
{ RawKind: (int) SyntaxKind.ImplicitArrayCreationExpression } => IsLargeEnoughToWarrantOptimization(node),
{ RawKind: (int) SyntaxKind.ArrayInitializerExpression } => IsLargeEnoughToWarrantOptimization(node.Expressions),
{ RawKind: (int) SyntaxKind.ImplicitArrayCreationExpression } => IsLargeEnoughToWarrantOptimization(node.Expressions),

{ RawKind: (int) SyntaxKind.StackAllocArrayCreationExpression } => IsLargeEnoughToWarrantOptimization(node) && HasCompatibleType(node, context),
{ RawKind: (int) SyntaxKind.ImplicitStackAllocArrayCreationExpression} => IsLargeEnoughToWarrantOptimization(node) && HasCompatibleType(node, context),
{ RawKind: (int) SyntaxKind.StackAllocArrayCreationExpression } => IsLargeEnoughToWarrantOptimization(node.Expressions) && HasCompatibleType(node, context),
{ RawKind: (int) SyntaxKind.ImplicitStackAllocArrayCreationExpression} => IsLargeEnoughToWarrantOptimization(node.Expressions) && HasCompatibleType(node, context),
_ => false
};

static bool IsLargeEnoughToWarrantOptimization(InitializerExpressionSyntax initializer) => initializer.Expressions.Count > 2;

// As of Roslyn x, empirically only stackalloc of one byte sized elements are optimized.
static bool HasCompatibleType(InitializerExpressionSyntax expression, IVisitorContext context)
{
Expand All @@ -406,4 +404,8 @@ static bool HasCompatibleType(InitializerExpressionSyntax expression, IVisitorCo
|| type.SpecialType == SpecialType.System_Boolean;
}
}

public static bool IsApplicableTo(CollectionExpressionSyntax node) => IsLargeEnoughToWarrantOptimization(node.Elements);

static bool IsLargeEnoughToWarrantOptimization<TElement>(SeparatedSyntaxList<TElement> elements) where TElement : SyntaxNode => elements.Count > 2;
}

0 comments on commit ed375d9

Please sign in to comment.