Skip to content

Commit

Permalink
adds support for indexing with ranges (i.e, convert to span and slice) (
Browse files Browse the repository at this point in the history
  • Loading branch information
adrianoc-unity3d authored and adrianoc committed Mar 29, 2024
1 parent f3c547a commit 651c15d
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
.locals init (Buffer`1<System.String> V_0, System.Span`1<System.String> V_1, System.Span`1<System.String> V_2, System.Int32 V_3, System.Int32 V_4)
IL_0000: ldloca.s V_0
IL_0002: initobj Buffer`1<System.String>
IL_0008: ldloca V_0
IL_000c: call TElement& <PrivateImplementationDetails>::InlineArrayFirstElementRef<Buffer`1<System.String>,System.String>(TBuffer&)
IL_0011: ldstr "zero"
IL_0016: stind.ref
IL_0017: ldloca V_0
IL_001b: ldc.i4 1
IL_0020: call TElement& <PrivateImplementationDetails>::InlineArrayElementRef<Buffer`1<System.String>,System.String>(TBuffer&,System.Int32)
IL_0025: ldstr "um, une, one"
IL_002a: stind.ref
IL_002b: ldloca V_0
IL_002f: ldc.i4 2
IL_0034: call TElement& <PrivateImplementationDetails>::InlineArrayElementRef<Buffer`1<System.String>,System.String>(TBuffer&,System.Int32)
IL_0039: ldstr "dois, deux, two"
IL_003e: stind.ref
IL_003f: ldloca V_0
IL_0043: ldarg.1
IL_0044: call TElement& <PrivateImplementationDetails>::InlineArrayElementRef<Buffer`1<System.String>,System.String>(TBuffer&,System.Int32)
IL_0049: ldstr "i"
IL_004e: stind.ref
IL_004f: ldloca.s V_0
IL_0051: ldc.i4 10
IL_0056: call System.Span`1<TElement> <PrivateImplementationDetails>::InlineArrayAsSpan<Buffer`1<System.String>,System.String>(TBuffer&,System.Int32)
IL_005b: stloc V_2
IL_005f: ldloca V_2
IL_0063: ldc.i4 2
IL_0068: stloc V_3
IL_006c: ldc.i4 5
IL_0071: ldloc V_3
IL_0075: sub
IL_0076: stloc V_4
IL_007a: ldloc V_3
IL_007e: ldloc V_4
IL_0082: call System.Span`1<!0> System.Span`1<System.String>::Slice(System.Int32,System.Int32)
IL_0087: stloc V_1
IL_008b: ldloca V_1
IL_008f: ldc.i4 0
IL_0094: call !0& System.Span`1<System.String>::get_Item(System.Int32)
IL_0099: ldind.ref
IL_009a: call System.Void System.Console::WriteLine(System.String)
IL_009f: ldloca V_1
IL_00a3: ldc.i4 1
IL_00a8: call !0& System.Span`1<System.String>::get_Item(System.Int32)
IL_00ad: ldind.ref
IL_00ae: call System.Void System.Console::WriteLine(System.String)
IL_00b3: ldloca V_1
IL_00b7: ldarg.1
IL_00b8: call !0& System.Span`1<System.String>::get_Item(System.Int32)
IL_00bd: ldind.ref
IL_00be: call System.Void System.Console::WriteLine(System.String)
IL_00c3: ret
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System;
class InlineArrayRangeTests
{
void Test(int i)
{
var buffer = new Buffer<string>();
buffer[0] = "zero";
buffer[1] = "um, une, one";
buffer[2] = "dois, deux, two";
buffer[i] = "i";

var span = buffer[2..5];
Console.WriteLine(span[0]);
Console.WriteLine(span[1]);
Console.WriteLine(span[i]);
}
}

[System.Runtime.CompilerServices.InlineArray(10)]
struct Buffer<T>
{
private T _data;
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,16 @@ public void TestInlineArrays()
{
AssertResourceTestWithExplicitExpectation("Misc/InlineArrays", "System.Void InlineArrayTests::Test()");
}

[Test]
public void TestInlineArraysRanges()
{
var options = new ResourceTestOptions()
{
ResourceName = "Misc/InlineArraysRanges",
IgnoredILErrors = "ReturnPtrToStack" // Sounds like a bug in ilverify (this is reproducible with code generated by c# compiler)
};

AssertResourceTestWithExplicitExpectation(options, "System.Void InlineArrayRangeTests::Test(System.Int32)");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,26 @@
using Cecilifier.Core.Extensions;
using Cecilifier.Core.Mappings;
using Cecilifier.Core.Misc;
using Cecilifier.Core.Variables;
using Mono.Cecil.Cil;

namespace Cecilifier.Core.AST;

internal class ElementAccessExpressionWithRangeArgumentVisitor : SyntaxWalkerBase
{
internal ElementAccessExpressionWithRangeArgumentVisitor(IVisitorContext context, string ilVar, ExpressionVisitor expressionVisitor) : base(context)
internal ElementAccessExpressionWithRangeArgumentVisitor(IVisitorContext context, string ilVar, ExpressionVisitor expressionVisitor, bool targetAlreadyLoaded = false) : base(context)
{
_expressionVisitor = expressionVisitor;
_targetAlreadyLoaded = targetAlreadyLoaded;
_ilVar = ilVar;
}

public override void VisitElementAccessExpression(ElementAccessExpressionSyntax node)
{
using var _ = LineInformationTracker.Track(Context, node);
node.Expression.Accept(_expressionVisitor);
if (!_targetAlreadyLoaded)
node.Expression.Accept(_expressionVisitor);

var elementAccessExpressionType = Context.SemanticModel.GetTypeInfo(node.Expression).Type.EnsureNotNull();
var elementAccessExpressionType = Context.SemanticModel.GetTypeInfo(node).Type.EnsureNotNull();
_targetSpanType = elementAccessExpressionType;
_spanCopyVariable = CodeGenerationHelpers.StoreTopOfStackInLocalVariable(Context, _ilVar, "localSpanCopy", elementAccessExpressionType).VariableName;
Context.EmitCilInstruction(_ilVar, OpCodes.Ldloca, _spanCopyVariable);
Expand Down Expand Up @@ -113,6 +114,7 @@ private void ProcessIndexerExpressionWithRangeAsArgument(ExpressionSyntax node)
}

private readonly ExpressionVisitor _expressionVisitor;
private readonly bool _targetAlreadyLoaded;
private string _spanCopyVariable;
private readonly string _ilVar;
private ITypeSymbol _targetSpanType; // Span<T> in which indexer is being invoked
Expand Down
5 changes: 5 additions & 0 deletions Cecilifier.Core/AST/ExpressionVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,11 @@ public override void VisitElementAccessExpression(ElementAccessExpressionSyntax
return;
}

if (InlineArrayProcessor.TryHandleRangeElementAccess(Context, this, ilVar, node, out var elementType1))
{
return;
}

if (InlineArrayProcessor.TryHandleIntIndexElementAccess(Context, ilVar, node, out var elementType))
{
// if the parent of the element access expression is a member access expression the code
Expand Down
34 changes: 34 additions & 0 deletions Cecilifier.Core/AST/InlineArrayProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ internal static bool HandleInlineArrayConversionToSpan(IVisitorContext context,
return false;

if (!IsNodeUsedToInitializeSpanLocalVariable(context, fromNode)
&& !fromNode.IsUsedAsReturnValueOfType(context.SemanticModel)
&& !IsNodeAssignedToLocalVariable(context, fromNode)
&& !IsNodeUsedAsSpanArgument(context, fromNode))
return false;
Expand Down Expand Up @@ -76,6 +77,39 @@ static string InlineArrayAsSpanMethodFor(IVisitorContext context, ITypeSymbol in
inlineArrayType);
}
}

/// <summary>
/// The expression 'InlineArray[range]' returns a sliced Span<T> where 'T' is the element type of the inline array.
/// All this method needs to do is to convert the inline array => Span<T> and use the same code that handles
/// 'indexing' a Span<T> with ranges.
/// </summary>
internal static bool TryHandleRangeElementAccess(IVisitorContext context, ExpressionVisitor expressionVisitor, string ilVar, ElementAccessExpressionSyntax elementAccess, out ITypeSymbol elementType)
{
elementType = null;
if (elementAccess.Expression.IsKind(SyntaxKind.ElementAccessExpression))
return false;

var storageSymbol = context.SemanticModel.GetSymbolInfo(elementAccess.Expression).Symbol.EnsureNotNull();
var inlineArrayType = storageSymbol.GetMemberType();
if (!inlineArrayType.TryGetAttribute<InlineArrayAttribute>(out _))
return false;

if (elementAccess.ArgumentList.Arguments.Count == 1 && SymbolEqualityComparer.Default.Equals(context.GetTypeInfo(elementAccess.ArgumentList.Arguments[0].Expression).Type, context.RoslynTypeSystem.SystemRange))
{
var storageVariableMemberKind = storageSymbol.ToVariableMemberKind();
var memberParentName = storageVariableMemberKind == VariableMemberKind.LocalVariable ? string.Empty : storageSymbol.ContainingSymbol.ToDisplayString();

// 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
// indexing Span<T> with a range.
elementAccess.Accept(new ElementAccessExpressionWithRangeArgumentVisitor(context, ilVar, expressionVisitor, targetAlreadyLoaded: true));
return true;
}

return false;
}

internal static bool TryHandleIntIndexElementAccess(IVisitorContext context, string ilVar, ElementAccessExpressionSyntax elementAccess, out ITypeSymbol elementType)
{
Expand Down
14 changes: 14 additions & 0 deletions Cecilifier.Core/Extensions/SyntaxNodeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -154,5 +154,19 @@ internal static bool TryGetLiteralValueFor<T>(this ExpressionSyntax expressionSy
value = (T) literalExpression.Token.Value;
return true;
}

internal static bool IsUsedAsReturnValueOfType(this SyntaxNode self, SemanticModel semanticModel)
{
if (self.Parent == null)
return false;

if (self.Parent.IsKind(SyntaxKind.ReturnStatement) || self.Parent.IsKind(SyntaxKind.ArrowExpressionClause))
{
var type = semanticModel.GetTypeInfo(self.Parent).Type;
return true;
}

return false;
}
}
}

0 comments on commit 651c15d

Please sign in to comment.