Skip to content

Commit

Permalink
adds support for initializing spans with collection expressions (#256)
Browse files Browse the repository at this point in the history
  • Loading branch information
adrianoc committed Sep 30, 2024
1 parent 96045c4 commit a1e3500
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 21 deletions.
16 changes: 11 additions & 5 deletions Cecilifier.Core.Tests/Framework/OutputBasedTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,19 @@ public class OutputBasedTestBase : CecilifierTestBase
{
static readonly int NewLineLength = Environment.NewLine.Length;

protected OutputBasedTestResult CecilifyAndExecute(string code)
private OutputBasedTestResult CecilifyAndExecute(string code, string ignoredIlVerificationErrors)
{
var outputBasedTestFolder = GetTestOutputBaseFolderFor("OutputBasedTests");

var cecilifyResult = CecilifyAndExecute(new MemoryStream(Encoding.ASCII.GetBytes(code)), outputBasedTestFolder);
VerifyAssembly(cecilifyResult.CecilifiedOutputAssemblyFilePath, null, new CecilifyTestOptions { CecilifiedCode = cecilifyResult.CecilifiedCode });
VerifyAssembly(
cecilifyResult.CecilifiedOutputAssemblyFilePath,
null,
new CecilifyTestOptions
{
CecilifiedCode = cecilifyResult.CecilifiedCode,
IgnoredILErrors = ignoredIlVerificationErrors
});

var refsToCopy = new List<string>
{
Expand All @@ -45,10 +52,9 @@ protected OutputBasedTestResult CecilifyAndExecute(string code)
return new OutputBasedTestResult(cecilifyResult, output.AsSpan()[..^NewLineLength].ToString()); // remove last new line
}


protected void AssertOutput(string snippet, string expectedOutput)
protected void AssertOutput(string snippet, string expectedOutput, string ignoreIlVerificationErrors = null)
{
var result = CecilifyAndExecute(snippet);
var result = CecilifyAndExecute(snippet, ignoreIlVerificationErrors);
Assert.That(result.Output, Is.EqualTo(expectedOutput), $"Output Assembly: {result.GeneralResult.CecilifiedOutputAssemblyFilePath}");
TestContext.WriteLine($"Output Assembly: {result.GeneralResult.CecilifiedOutputAssemblyFilePath}");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,29 @@ public void ArrayWith2OrLessElements()
{
AssertOutput("int[] mediumArray = [1, 2]; System.Console.WriteLine(mediumArray[0] + mediumArray[1]);", "3");
}

[Test]
public void Span()
{
AssertOutput(
"System.Span<int> span = [1, 2, 3]; System.Console.WriteLine(span[0] + span[2]);",
"4",
"ReturnPtrToStack" // Seems like an issue with ILVerify since verifying the code above compiled with C# compiler
// generates the same error.
);
}

[Test]
public void SpanAsParameter()
{
AssertOutput(
"""
Print([1, 2, 3]);
static void Print(System.Span<int> span) => System.Console.WriteLine(span[0] + span[2]);
""",
"4",
"ReturnPtrToStack" // Seems like an issue with ILVerify since verifying the code above compiled with C# compiler
// generates the same error.
);
}
}
126 changes: 121 additions & 5 deletions Cecilifier.Core/AST/CollectionExpressionProcessor.cs
Original file line number Diff line number Diff line change
@@ -1,22 +1,138 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using Cecilifier.Core.CodeGeneration;
using Cecilifier.Core.Extensions;
using Cecilifier.Core.Mappings;
using Cecilifier.Core.Misc;
using Cecilifier.Core.Naming;
using Cecilifier.Core.Variables;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Mono.Cecil.Cil;

namespace Cecilifier.Core.AST;

internal class CollectionExpressionProcessor
internal static class CollectionExpressionProcessor
{
public static void Process(ExpressionVisitor visitor, CollectionExpressionSyntax node)
{
visitor.Context.EmitCilInstruction(visitor.ILVariable, OpCodes.Ldc_I4, node.Elements.Count);
var targetTypeSymbol = visitor.Context.GetTypeInfo(node).ConvertedType.EnsureNotNull();
if (targetTypeSymbol is IArrayTypeSymbol arrayType)
{
HandleAssignmentToArray(visitor, node, arrayType);
}
else
{
HandleAssignmentToSpan(visitor, node, (INamedTypeSymbol) targetTypeSymbol);
}
}

private static void HandleAssignmentToSpan(ExpressionVisitor visitor, CollectionExpressionSyntax node, INamedTypeSymbol spanTypeSymbol)
{
Debug.Assert(SymbolEqualityComparer.Default.Equals(spanTypeSymbol.OriginalDefinition, visitor.Context.RoslynTypeSystem.SystemSpan));

var context = visitor.Context;
var inlineArrayVar = GetOrEmitSyntheticInlineArrayFor(node, context);

var currentMethodVar = context.DefinitionVariables.GetLastOf(VariableMemberKind.Method).VariableName;
var inlineArrayElementType = spanTypeSymbol.TypeArguments[0];
var inlineArrayLocalVar = context.Naming.SyntheticVariable("buffer", ElementKind.LocalVariable);
var inlineArrayTypeVar = inlineArrayVar.MakeGenericInstanceType(context.TypeResolver.Resolve(inlineArrayElementType));
context.WriteCecilExpression($"var {inlineArrayLocalVar} = {CecilDefinitionsFactory.LocalVariable(inlineArrayTypeVar)};\n");
context.WriteCecilExpression($"{currentMethodVar}.Body.Variables.Add({inlineArrayLocalVar});\n");

// Initializes the inline array
context.EmitCilInstruction(visitor.ILVariable, OpCodes.Ldloca_S, inlineArrayLocalVar);
context.EmitCilInstruction(visitor.ILVariable, OpCodes.Initobj, inlineArrayTypeVar);

var inlineArrayElementRefMethodVar = PrivateImplementationDetailsGenerator
.GetOrEmmitInlineArrayElementRefMethod(context)
.MakeGenericInstanceMethod(context, "InlineArrayElementRef", [$"{inlineArrayLocalVar}.VariableType", context.TypeResolver.Resolve(spanTypeSymbol.TypeArguments[0])]);

var arrayTypeSymbol = visitor.Context.GetTypeInfo(node).ConvertedType.EnsureNotNull<ITypeSymbol, IArrayTypeSymbol>();
var storeOpCode = inlineArrayElementType.StindOpCodeFor();
var index = 0;
foreach (var element in node.Elements)
{
context.EmitCilInstruction(visitor.ILVariable, OpCodes.Ldloca_S, inlineArrayLocalVar);
context.EmitCilInstruction(visitor.ILVariable, OpCodes.Ldc_I4, index);
context.EmitCilInstruction(visitor.ILVariable, OpCodes.Call, inlineArrayElementRefMethodVar);
visitor.Visit(element);

context.EmitCilInstruction(visitor.ILVariable, storeOpCode, storeOpCode == OpCodes.Ldobj ? context.TypeResolver.Resolve(inlineArrayElementType) : null);
index++;
}

// convert the initialized InlineArray to a span and put it in the stack.
var inlineArrayAsSpanMethodVar = PrivateImplementationDetailsGenerator
.GetOrEmmitInlineArrayAsSpanMethod(context)
.MakeGenericInstanceMethod(context, "InlineArrayAsSpan", [$"{inlineArrayLocalVar}.VariableType", context.TypeResolver.Resolve(spanTypeSymbol.TypeArguments[0])]);
context.EmitCilInstruction(visitor.ILVariable, OpCodes.Ldloca_S, inlineArrayLocalVar);
context.EmitCilInstruction(visitor.ILVariable, OpCodes.Ldc_I4, node.Elements.Count);
context.EmitCilInstruction(visitor.ILVariable, OpCodes.Call, inlineArrayAsSpanMethodVar);
}

private static string GetOrEmitSyntheticInlineArrayFor(CollectionExpressionSyntax node, IVisitorContext context)
{
context.WriteNewLine();
context.WriteComment($"Declares an inline array for backing the data for the collection expression: {node.SourceDetails()}");

var typeVar = context.Naming.Type("", ElementKind.Struct);
var typeParameterVar = context.Naming.SyntheticVariable("TElementType", ElementKind.GenericParameter);

string[] typeExps =
[
//[StructLayout(LayoutKind.Auto)]
$"""var {typeVar} = new TypeDefinition(string.Empty, "<>y_InlineArray{node.Elements.Count}`1", TypeAttributes.NotPublic | TypeAttributes.AutoLayout | TypeAttributes.AnsiClass | TypeAttributes.BeforeFieldInit | TypeAttributes.Sealed, {context.TypeResolver.Bcl.System.ValueType});""",
CecilDefinitionsFactory.GenericParameter(context, $"{typeVar}.Name", typeVar, "TElementType", typeParameterVar),
$"""{typeVar}.GenericParameters.Add({typeParameterVar});"""
];

var fieldVar = context.Naming.SyntheticVariable("_element0", ElementKind.Field);
var fieldExps = CecilDefinitionsFactory.Field(
context,
$"{typeVar}.Name",
typeVar,
fieldVar,
"_element0",
typeParameterVar,
"FieldAttributes.Private");

context.WriteCecilExpressions(typeExps);

//[InlineArray(2)]
context.WriteCecilExpressions(
CecilDefinitionsFactory.Attribute(
"inlineArray",
typeVar,
context,
ConstructorFor<InlineArrayAttribute>(context, typeof(int)),
(context.TypeResolver.Bcl.System.Int32, node.Elements.Count.ToString())));

context.WriteCecilExpressions(fieldExps);
context.AddCompilerGeneratedAttributeTo(fieldVar);
context.WriteCecilExpression($"assembly.MainModule.Types.Add({typeVar});\n");

return typeVar;
}

private static string ConstructorFor<TType>(IVisitorContext context, params Type[] ctorParamTypes)
{
var typeSymbol = context.SemanticModel.Compilation.GetTypeByMetadataName(typeof(TType).FullName!).EnsureNotNull();
var ctors = typeSymbol.Constructors.Where(ctor => ctor.Parameters.Length == ctorParamTypes.Length);

if (ctors.Count() == 1)
return ctors.First().MethodResolverExpression(context);

var expectedParamTypes = ctorParamTypes.Select(paramType => context.SemanticModel.Compilation.GetTypeByMetadataName(paramType.FullName!)).ToHashSet();
return ctors.Single(ctor => !ctor.Parameters.Select(p => p.Type).ToHashSet().Except(expectedParamTypes, SymbolEqualityComparer.Default).Any()).MethodResolverExpression(context);
}

private static void HandleAssignmentToArray(ExpressionVisitor visitor, CollectionExpressionSyntax node, IArrayTypeSymbol arrayTypeSymbol)
{
visitor.Context.EmitCilInstruction(visitor.ILVariable, OpCodes.Ldc_I4, node.Elements.Count);
visitor.Context.EmitCilInstruction(visitor.ILVariable, OpCodes.Newarr, visitor.Context.TypeResolver.Resolve(arrayTypeSymbol.ElementType));

using var _ = LineInformationTracker.Track(visitor.Context, node);
if (PrivateImplementationDetailsGenerator.IsApplicableTo(node))
ArrayInitializationProcessor.InitializeOptimized(visitor, arrayTypeSymbol.ElementType, node.Elements);
else
Expand Down
1 change: 1 addition & 0 deletions Cecilifier.Core/AST/ExpressionVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -919,6 +919,7 @@ public override void VisitBaseExpression(BaseExpressionSyntax node)

public override void VisitCollectionExpression(CollectionExpressionSyntax node)
{
using var _ = LineInformationTracker.Track(Context, node);
CollectionExpressionProcessor.Process(this, node);
}

Expand Down
15 changes: 4 additions & 11 deletions Cecilifier.Core/CodeGeneration/Property.Generator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ internal void AddAutoSetterMethodImplementation(ref readonly PropertyGenerationD

var operand = property.DeclaringTypeIsGeneric ? MakeGenericType(in property) : _backingFieldVar;
Context.EmitCilInstruction(ilSetVar, property.StoreOpCode, operand);
AddCompilerGeneratedAttributeTo(Context, setMethodVar);
Context.AddCompilerGeneratedAttributeTo(setMethodVar);
}

internal ScopedDefinitionVariable AddGetterMethodDeclaration(ref readonly PropertyGenerationData property, string accessorMethodVar, bool hasCovariantReturn, string nameForRegistration, string overridenMethod)
Expand Down Expand Up @@ -132,7 +132,7 @@ internal void AddAutoGetterMethodImplementation(ref readonly PropertyGenerationD
Context.EmitCilInstruction(ilVar, propertyGenerationData.LoadOpCode, operand);
Context.EmitCilInstruction(ilVar, OpCodes.Ret);

AddCompilerGeneratedAttributeTo(Context, getMethodVar);
Context.AddCompilerGeneratedAttributeTo(getMethodVar);
}

private void AddToOverridenMethodsIfAppropriated(string accessorMethodVar, string overridenMethod)
Expand Down Expand Up @@ -161,16 +161,9 @@ private void AddBackingFieldIfNeeded(ref readonly PropertyGenerationData propert
property.BackingFieldModifiers);

Context.WriteCecilExpressions(backingFieldExps);
AddCompilerGeneratedAttributeTo(Context, _backingFieldVar);
Context.AddCompilerGeneratedAttributeTo(_backingFieldVar);
}

private void AddCompilerGeneratedAttributeTo(IVisitorContext context, string memberVariable)
{
var compilerGeneratedAttributeCtor = context.RoslynTypeSystem.SystemRuntimeCompilerServicesCompilerGeneratedAttribute.Ctor();
var exps = CecilDefinitionsFactory.Attribute("compilerGenerated", memberVariable, context, compilerGeneratedAttributeCtor.MethodResolverExpression(context));
context.WriteCecilExpressions(exps);
}


private string MakeGenericType(ref readonly PropertyGenerationData property)
{
var genTypeVar = Context.Naming.SyntheticVariable(property.Name, ElementKind.GenericInstance);
Expand Down
14 changes: 14 additions & 0 deletions Cecilifier.Core/Extensions/CecilifierContextExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Cecilifier.Core.AST;
using Cecilifier.Core.Misc;

namespace Cecilifier.Core.Extensions;

public static class CecilifierContextExtensions
{
internal static void AddCompilerGeneratedAttributeTo(this IVisitorContext context, string memberVariable)
{
var compilerGeneratedAttributeCtor = context.RoslynTypeSystem.SystemRuntimeCompilerServicesCompilerGeneratedAttribute.Ctor();
var exps = CecilDefinitionsFactory.Attribute("compilerGenerated", memberVariable, context, compilerGeneratedAttributeCtor.MethodResolverExpression(context));
context.WriteCecilExpressions(exps);
}
}
2 changes: 2 additions & 0 deletions Cecilifier.Core/TypeSystem/SystemTypeSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public SystemTypeSystem(ITypeResolver typeResolver, IVisitorContext context)
[SpecialType.System_Object] = typeResolver.ResolvePredefinedType(context.RoslynTypeSystem.SystemObject),
[SpecialType.System_Boolean] = typeResolver.ResolvePredefinedType(context.RoslynTypeSystem.SystemBoolean),
[SpecialType.System_Enum] = typeResolver.Resolve("System.Enum"),
[SpecialType.System_ValueType] = typeResolver.Resolve("System.ValueType"),
[SpecialType.System_MulticastDelegate] = typeResolver.Resolve("System.MulticastDelegate"),
[SpecialType.System_AsyncCallback] = typeResolver.Resolve("System.AsyncCallback"),
[SpecialType.System_IAsyncResult] = typeResolver.Resolve("System.IAsyncResult"),
Expand All @@ -37,6 +38,7 @@ public SystemTypeSystem(ITypeResolver typeResolver, IVisitorContext context)
public string AsyncCallback => _resolvedTypes[SpecialType.System_AsyncCallback];
public string IAsyncResult => _resolvedTypes[SpecialType.System_IAsyncResult];
public string NullableOfT => _resolvedTypes[SpecialType.System_Nullable_T];
public string ValueType => _resolvedTypes[SpecialType.System_ValueType];

private readonly IReadOnlyDictionary<SpecialType, string> _resolvedTypes;
}
Expand Down

0 comments on commit a1e3500

Please sign in to comment.