diff --git a/Cecilifier.Core.Tests/Tests/Unit/InlineArrayTests.cs b/Cecilifier.Core.Tests/Tests/Unit/InlineArrayTests.cs new file mode 100644 index 00000000..7e1acad1 --- /dev/null +++ b/Cecilifier.Core.Tests/Tests/Unit/InlineArrayTests.cs @@ -0,0 +1,89 @@ +using NUnit.Framework; + +namespace Cecilifier.Core.Tests.Tests.Unit; + +[TestFixture] +public class InlineArrayTests : CecilifierUnitTestBase +{ + [Test] + public void Instantiating_InlineArray_EmitsInitObj() + { + var result = RunCecilifier(""" + var b = new IntBuffer(); + + [System.Runtime.CompilerServices.InlineArray(10)] + public struct IntBuffer + { + private int _element0; + } + """); + + Assert.That(result.GeneratedCode.ReadToEnd(), Does.Match( + @"m_topLevelStatements_\d+.Body.Variables.Add\((?l_b_\d+)\);\s+"+ // local variable *b* + @"(?il_topLevelMain_\d+.Emit\(OpCodes\.)Ldloca_S, \k\);\s+" + // Loads *b* address + @"\kInitobj, st_intBuffer_\d+\);")); // Execute *initobj* on *b* + } + + [TestCase("System.Span span = l;", TestName = "Local variable initialization")] + [TestCase("scoped System.Span span; span = l;", TestName = "Local Variable assignment")] + [TestCase("Consume(l);", TestName = "Local passed as argument")] + [TestCase("Consume(p);", TestName = "Parameter passed as argument")] + public void Assigning_InlineArrayToSpan_EmitsPrivateImplementationDetailsType(string triggeringStatements) + { + var result = RunCecilifier($$""" + void TestMethod(IntBuffer p) + { + var l = new IntBuffer(); + + // This will trigger the emission of .InlineArrayAsSpan() method + {{triggeringStatements}} + } + + void Consume(System.Span span) {} + + [System.Runtime.CompilerServices.InlineArray(10)] + public struct IntBuffer + { + private int _element0; + } + """); + + var cecilifiedCode = result.GeneratedCode.ReadToEnd(); + Assert.That(cecilifiedCode, Does.Match("""new TypeDefinition\("", "", .+\)""")); + } + + [Test] + public void AccessToFirstElement_MapsTo_PrivateImplementationDetailsInlineArrayFirstElementRefMethod() + { + var result = RunCecilifier(""" + var buffer = new IntBuffer(); + buffer[0] = 42; + + [System.Runtime.CompilerServices.InlineArray(10)] + public struct IntBuffer + { + private int _element0; + } + """); + + var cecilified = result.GeneratedCode.ReadToEnd(); + + // assert that the inline array address is being pushed to the stack... + Assert.That(cecilified, Does.Match(""" + il_topLevelMain_\d+\.Emit\(OpCodes\.Ldloca, l_buffer_\d+\); + + \s+// class. + """)); + + // and later .InlineArrayFirstElementRef() static method is being invoked + // and the value 42 stored in the address at the top of the stack. + Assert.That(cecilified, Does.Match(""" + (il_topLevelMain_\d+\.Emit\(OpCodes\.)Call, gi_inlineArrayFirstElementRef_\d+\); + \s+\1Ldc_I4, 42\); + \s+\1Stind_I4 + """)); + } + + // Access to not first element + // +} diff --git a/Cecilifier.Core.Tests/Tests/Unit/PrivateImplementationDetailsGeneratorTests.cs b/Cecilifier.Core.Tests/Tests/Unit/PrivateImplementationDetailsGeneratorTests.cs index 3b522d0a..51cc8c64 100644 --- a/Cecilifier.Core.Tests/Tests/Unit/PrivateImplementationDetailsGeneratorTests.cs +++ b/Cecilifier.Core.Tests/Tests/Unit/PrivateImplementationDetailsGeneratorTests.cs @@ -14,7 +14,7 @@ public class PrivateImplementationDetailsGeneratorTests [Test] public void PrivateImplementationType_IsCached() { - var comp = CompilationFor("class Foo {}"); + var comp = CompilationFor("class Foo {}"); var context = new CecilifierContext(comp.GetSemanticModel(comp.SyntaxTrees[0]), new CecilifierOptions(), 1); var found = context.DefinitionVariables.GetVariablesOf(VariableMemberKind.Type); @@ -33,7 +33,7 @@ public void PrivateImplementationType_IsCached() [Test] public void Int32AndInt64_AreUsedAsFieldBackingType_OfArraysOf4And8Bytes() { - var comp = CompilationFor("class Foo {}"); + var comp = CompilationFor("class Foo {}"); var context = new CecilifierContext(comp.GetSemanticModel(comp.SyntaxTrees[0]), new CecilifierOptions(), 1); var found = context.DefinitionVariables.GetVariablesOf(VariableMemberKind.Type); @@ -93,6 +93,26 @@ public void BackingField_IsUniquePerDataSize() Assert.That(found.Count(), Is.EqualTo(2), context.Output); Assert.That(secondVariableName, Is.Not.EqualTo(variableName), context.Output); } + + [Test] + public void InlineArrayAsSpan_HelperMethod_Properties() + { + var comp = CompilationFor("class Foo {}"); + var context = new CecilifierContext(comp.GetSemanticModel(comp.SyntaxTrees[0]), new CecilifierOptions(), 1); + + var found = context.DefinitionVariables.GetVariablesOf(VariableMemberKind.Method).ToArray(); + Assert.That(found.Length, Is.EqualTo(0)); + + // internal static Span InlineArrayAsSpan(ref TBuffer buffer, int length) + var methodVariableName = PrivateImplementationDetailsGenerator.GetOrEmmitInlineArrayAsSpanMethod(context); + found = context.DefinitionVariables.GetVariablesOf(VariableMemberKind.Method).ToArray(); + Assert.That(found.Length, Is.EqualTo(1)); + Assert.That(found[0].MemberName, Is.EqualTo("InlineArrayAsSpan")); + + Assert.That(context.Output, Does.Match("""var m_inlineArrayAsSpan_\d+ = new MethodDefinition\("InlineArrayAsSpan", MethodAttributes.Assembly | MethodAttributes.Static | MethodAttributes.HideBySig, assembly.MainModule.TypeSystem.Void\);""")); + Assert.That(context.Output, Does.Match("""m_inlineArrayAsSpan_\d+.Parameters.Add\(new ParameterDefinition\("buffer", ParameterAttributes.None, gp_tBuffer_\d+.MakeByReferenceType\(\)\)\);""")); + Assert.That(context.Output, Does.Match("""m_inlineArrayAsSpan_\d+.Parameters.Add\(new ParameterDefinition\("length", ParameterAttributes.None, assembly.MainModule.TypeSystem.Int32\)\);""")); + } static CSharpCompilation CompilationFor(string code) { diff --git a/Cecilifier.Core/AST/AssignmentVisitor.cs b/Cecilifier.Core/AST/AssignmentVisitor.cs index fe73638e..012eea2c 100644 --- a/Cecilifier.Core/AST/AssignmentVisitor.cs +++ b/Cecilifier.Core/AST/AssignmentVisitor.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; using Cecilifier.Core.Extensions; using Cecilifier.Core.Misc; using Cecilifier.Core.Variables; @@ -44,13 +45,20 @@ internal AssignmentVisitor(IVisitorContext ctx, string ilVar) : base(ctx) public override void VisitElementAccessExpression(ElementAccessExpressionSyntax node) { var lastInstructionLoadingRhs = Context.CurrentLine; - + if (InlineArrayProcessor.HandleInlineArrayElementAccess(Context, ilVar, node)) + { + Context.MoveLinesToEnd(InstructionPrecedingValueToLoad, lastInstructionLoadingRhs); + var arrayElementType = Context.SemanticModel.GetTypeInfo(node).Type.EnsureNotNull(); + Context.EmitCilInstruction(ilVar, arrayElementType.Stind()); + return; + } + ExpressionVisitor.Visit(Context, ilVar, node.Expression); foreach (var arg in node.ArgumentList.Arguments) { ExpressionVisitor.Visit(Context, ilVar, arg); } - + if (!HandleIndexer(node, lastInstructionLoadingRhs)) { Context.MoveLinesToEnd(InstructionPrecedingValueToLoad, lastInstructionLoadingRhs); @@ -207,7 +215,6 @@ bool HandleIndexer(SyntaxNode node, LinkedListNode lastInstructionLoadin return true; } - private void EmitIndirectStore(ITypeSymbol typeBeingStored) { var indirectStoreOpCode = typeBeingStored.Stind(); diff --git a/Cecilifier.Core/AST/InlineArrayProcessor.cs b/Cecilifier.Core/AST/InlineArrayProcessor.cs new file mode 100644 index 00000000..6c2ba266 --- /dev/null +++ b/Cecilifier.Core/AST/InlineArrayProcessor.cs @@ -0,0 +1,153 @@ +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; +using Cecilifier.Core.CodeGeneration; +using Cecilifier.Core.Extensions; +using Cecilifier.Core.Naming; +using Cecilifier.Core.Variables; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Mono.Cecil.Cil; + +namespace Cecilifier.Core.AST; +public class InlineArrayProcessor +{ + internal static bool HandleInlineArrayConversionToSpan(IVisitorContext context, string ilVar, ITypeSymbol fromType, SyntaxNode fromNode, OpCode opcode, string name, VariableMemberKind memberKind, string parentName = null) + { + int inlineArrayLength = InlineArrayLengthFrom(fromType); + if (inlineArrayLength == -1) + return false; + + if (!IsNodeUsedToInitializeSpanLocalVariable(context, fromNode) + && !IsNodeAssignedToLocalVariable(context, fromNode) + && !IsNodeUsedAsSpanArgument(context, fromNode)) + return false; + + // ldloca.s address of fromNode. + // ldci4 fromNode.Length (size of the inline array) + context.EmitCilInstruction(ilVar, opcode, context.DefinitionVariables.GetVariable(name, memberKind, parentName).VariableName); + context.EmitCilInstruction(ilVar, OpCodes.Ldc_I4, inlineArrayLength); + context.EmitCilInstruction(ilVar, OpCodes.Call, InlineArrayAsSpanMethodFor(context, fromType)); + return true; + + static bool IsNodeAssignedToLocalVariable(IVisitorContext context, SyntaxNode nodeToCheck) + { + if (nodeToCheck.Parent is not AssignmentExpressionSyntax assignmentExpression) + return false; + + var lhs = context.SemanticModel.GetSymbolInfo(assignmentExpression.Left); + if (lhs.Symbol == null) + return false; + + return SymbolEqualityComparer.Default.Equals(lhs.Symbol.GetMemberType().OriginalDefinition, context.RoslynTypeSystem.SystemSpan); + } + + static bool IsNodeUsedAsSpanArgument(IVisitorContext context, SyntaxNode nodeToCheck) + { + if (nodeToCheck.Parent is not ArgumentSyntax argumentSyntax) + return false; + + var argumentIndex = ((ArgumentListSyntax) argumentSyntax.Parent).Arguments.IndexOf(argumentSyntax); + var invocation = argumentSyntax.FirstAncestorOrSelf(); + var associatedParameterSymbol = ((IMethodSymbol) context.SemanticModel.GetSymbolInfo(invocation.Expression).Symbol).Parameters.ElementAtOrDefault(argumentIndex); + + return SymbolEqualityComparer.Default.Equals(associatedParameterSymbol.Type.OriginalDefinition, context.RoslynTypeSystem.SystemSpan); + } + + static bool IsNodeUsedToInitializeSpanLocalVariable(IVisitorContext context, SyntaxNode nodeToCheck) + { + var parent = nodeToCheck.Parent; + if (!parent.IsKind(SyntaxKind.EqualsValueClause) || !parent.Parent.IsKind(SyntaxKind.VariableDeclarator)) + return false; + + var variableDeclaration = (VariableDeclarationSyntax) parent.Parent.Parent!; + var declaredVariableType = ModelExtensions.GetTypeInfo(context.SemanticModel, variableDeclaration.Type); + + return SymbolEqualityComparer.Default.Equals(declaredVariableType.Type?.OriginalDefinition, context.RoslynTypeSystem.SystemSpan); + } + + static string InlineArrayAsSpanMethodFor(IVisitorContext context, ITypeSymbol inlineArrayType) + { + return PrivateImplementationInlineArrayGenericInstanceMethodFor( + context, + PrivateImplementationDetailsGenerator.GetOrEmmitInlineArrayAsSpanMethod(context), + "InlineArrayAsSpan", + inlineArrayType); + } + } + + internal static bool HandleInlineArrayElementAccess(IVisitorContext context, string ilVar, ElementAccessExpressionSyntax elementAccess) + { + if (elementAccess.Expression.IsKind(SyntaxKind.ElementAccessExpression)) + return false; + + var expSymbol = context.SemanticModel.GetSymbolInfo(elementAccess.Expression).Symbol.EnsureNotNull(); + if (expSymbol.GetMemberType().TryGetAttribute(out _)) + { + ExpressionVisitor.Visit(context, ilVar, elementAccess.Expression); + Debug.Assert(elementAccess.ArgumentList.Arguments.Count == 1); + + var method = string.Empty; + if (elementAccess.ArgumentList.Arguments[0].Expression.TryGetLiteralValueFor(out int index) && index == 0) + { + method = InlineArrayFirstElementRefMethodFor(context, expSymbol.GetMemberType()); + } + else + { + context.EmitCilInstruction(ilVar, OpCodes.Ldc_I4, index); + method = InlineArrayElementRefMethodFor(context, expSymbol.GetMemberType()); + } + context.EmitCilInstruction(ilVar, OpCodes.Call, method); + + return true; + } + + return false; + + static string InlineArrayFirstElementRefMethodFor(IVisitorContext context, ITypeSymbol inlineArrayType) + { + return PrivateImplementationInlineArrayGenericInstanceMethodFor( + context, + PrivateImplementationDetailsGenerator.GetOrEmmitInlineArrayFirstElementRefMethod(context), + "InlineArrayFirstElementRef", + inlineArrayType); + } + + static string InlineArrayElementRefMethodFor(IVisitorContext context, ITypeSymbol inlineArrayType) + { + return PrivateImplementationInlineArrayGenericInstanceMethodFor( + context, + PrivateImplementationDetailsGenerator.GetOrEmmitInlineArrayElementRefMethod(context), + "InlineArrayElementRef", + inlineArrayType); + } + } + + private static string PrivateImplementationInlineArrayGenericInstanceMethodFor(IVisitorContext context, string openGenericTypeVar, string methodName, ITypeSymbol inlineArrayType) + { + var varName = context.Naming.SyntheticVariable(methodName, ElementKind.GenericInstance); + + var exps = openGenericTypeVar.MakeGenericInstanceMethod( + varName, + [ + context.TypeResolver.Resolve(inlineArrayType), // TBuffer + context.TypeResolver.Resolve(((IFieldSymbol) inlineArrayType.GetMembers().First()).Type) // TElement + ]); + + foreach (var exp in exps) + { + context.WriteCecilExpression(exp); + context.WriteNewLine(); + } + + return varName; + } + + private static int InlineArrayLengthFrom(ITypeSymbol rhsType) + { + return rhsType.TryGetAttribute(out var inlineArrayAttribute) + ? (int) inlineArrayAttribute.ConstructorArguments.First().Value + : -1; + } +} diff --git a/Cecilifier.Core/AST/SyntaxWalkerBase.cs b/Cecilifier.Core/AST/SyntaxWalkerBase.cs index 44bc62e7..217c5e9e 100644 --- a/Cecilifier.Core/AST/SyntaxWalkerBase.cs +++ b/Cecilifier.Core/AST/SyntaxWalkerBase.cs @@ -2,8 +2,10 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; +using Cecilifier.Core.CodeGeneration; using Cecilifier.Core.Extensions; using Cecilifier.Core.Misc; using Cecilifier.Core.Naming; @@ -403,7 +405,14 @@ protected string ResolveExpressionType(ExpressionSyntax expression) protected string ResolveType(TypeSyntax type) { - var typeToCheck = type is RefTypeSyntax refType ? refType.Type : type; + // TODO: Ensure there are tests covering all the derived types from TypeSyntax (https://learn.microsoft.com/en-us/dotnet/api/microsoft.codeanalysis.csharp.syntax.typesyntax?view=roslyn-dotnet-4.7.0) + var typeToCheck = type switch + { + RefTypeSyntax refType => refType.Type, + ScopedTypeSyntax scopedTypeSyntax => scopedTypeSyntax.Type, + _ => type + }; + var typeInfo = Context.GetTypeInfo(typeToCheck); TypeDeclarationVisitor.EnsureForwardedTypeDefinition(Context, typeInfo.Type, Array.Empty()); @@ -415,9 +424,13 @@ protected string ResolveType(TypeSyntax type) protected void ProcessParameter(string ilVar, SimpleNameSyntax node, IParameterSymbol paramSymbol) { var method = (IMethodSymbol) paramSymbol.ContainingSymbol; - if (HandleLoadAddressOnStorage(ilVar, paramSymbol.Type, node, OpCodes.Ldarga, paramSymbol.Name, VariableMemberKind.Parameter, (method.AssociatedSymbol ?? method).ToDisplayString())) + var declaringMethodName = (method.AssociatedSymbol ?? method).ToDisplayString(); + if (HandleLoadAddressOnStorage(ilVar, paramSymbol.Type, node, OpCodes.Ldarga, paramSymbol.Name, VariableMemberKind.Parameter, declaringMethodName)) return; + if (InlineArrayProcessor.HandleInlineArrayConversionToSpan(Context, ilVar, paramSymbol.Type, node, OpCodes.Ldarga_S, paramSymbol.Name, VariableMemberKind.Parameter, declaringMethodName)) + return; + Utils.EnsureNotNull(node.Parent); // We only support non-capturing lambda expressions so we handle those as static (even if the code does not mark them explicitly as such) // if/when we decide to support lambdas that captures variables/fields/params/etc we will probably need to revisit this. @@ -484,6 +497,9 @@ protected void ProcessLocalVariable(string ilVar, SimpleNameSyntax localVarSynta if (HandleLoadAddressOnStorage(ilVar, symbol.Type, localVarSyntax, OpCodes.Ldloca, symbol.Name, VariableMemberKind.LocalVariable)) return; + if (InlineArrayProcessor.HandleInlineArrayConversionToSpan(Context, ilVar, symbol.Type, localVarSyntax, OpCodes.Ldloca_S, symbol.Name, VariableMemberKind.LocalVariable)) + return; + var operand = Context.DefinitionVariables.GetVariable(symbol.Name, VariableMemberKind.LocalVariable).VariableName; Context.EmitCilInstruction(ilVar, OpCodes.Ldloc, operand); @@ -508,7 +524,7 @@ private bool HandleLoadAddressOnStorage(string ilVar, ITypeSymbol symbol, CSharp protected bool HandleLoadAddress(string ilVar, ITypeSymbol symbol, CSharpSyntaxNode node, OpCode opCode, string operand) { var parentNode = (CSharpSyntaxNode)node.Parent; - return HandleCallOnTypeParameter() || HandleCallOnValueType() || HandleRefAssignment() || HandleParameter(); + return HandleCallOnTypeParameter() || HandleCallOnValueType() || HandleRefAssignment() || HandleParameter() || HandleInlineArrayElementAccess(); bool HandleCallOnValueType() { @@ -577,6 +593,15 @@ bool HandleParameter() return false; } + bool HandleInlineArrayElementAccess() + { + if (!node.Parent.IsKind(SyntaxKind.ElementAccessExpression) || !symbol.TryGetAttribute(out var _)) + return false; + + Context.EmitCilInstruction(ilVar, opCode, operand); + return true; + } + bool IsPseudoAssignmentToValueType() => Context.HasFlag(Constants.ContextFlags.PseudoAssignmentToIndex); } diff --git a/Cecilifier.Core/CodeGeneration/PrivateImplementationDetails.Generator.Helpers.cs b/Cecilifier.Core/CodeGeneration/PrivateImplementationDetails.Generator.Helpers.cs new file mode 100644 index 00000000..9867ed7b --- /dev/null +++ b/Cecilifier.Core/CodeGeneration/PrivateImplementationDetails.Generator.Helpers.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Cecilifier.Core.AST; +using Microsoft.CodeAnalysis; + +namespace Cecilifier.Core.CodeGeneration; + +internal partial class PrivateImplementationDetailsGenerator +{ + static IMethodSymbol GetUnsafeAsMethod(IVisitorContext context) + { + var candidates = context.RoslynTypeSystem.SystemRuntimeCompilerServicesUnsafe + .GetMembers() + .OfType() + .Where(m => m.Name == "As" && m.Parameters.Length == 1 && m.Parameters[0].RefKind == RefKind.Ref); + + VerifyOnlyOneMatch(candidates); + return candidates.Single(); + } + + static IMethodSymbol GetUnsafeAddMethod(IVisitorContext context) + { + var candidates = context.RoslynTypeSystem.SystemRuntimeCompilerServicesUnsafe + .GetMembers() + .OfType() + .Where(m => m.Name == "Add" && m.Parameters.Length == 2 && m.Parameters[0].RefKind == RefKind.Ref && m.Parameters[1].Type.Name == "Int32"); + + VerifyOnlyOneMatch(candidates); + return candidates.Single(); + } + + [Conditional("DEBUG")] + private static void VerifyOnlyOneMatch(IEnumerable candidates) + { + Debug.Assert(candidates.Count() == 1); + } +} diff --git a/Cecilifier.Core/CodeGeneration/PrivateImplementationDetails.Generator.cs b/Cecilifier.Core/CodeGeneration/PrivateImplementationDetails.Generator.cs index 46359a69..55b914e5 100644 --- a/Cecilifier.Core/CodeGeneration/PrivateImplementationDetails.Generator.cs +++ b/Cecilifier.Core/CodeGeneration/PrivateImplementationDetails.Generator.cs @@ -1,12 +1,16 @@ using System; +using System.Diagnostics; +using System.Linq; using System.Security.Cryptography; using Cecilifier.Core.AST; +using Cecilifier.Core.Extensions; using Cecilifier.Core.Misc; using Cecilifier.Core.Naming; using Cecilifier.Core.Variables; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Mono.Cecil.Cil; namespace Cecilifier.Core.CodeGeneration; @@ -35,8 +39,191 @@ namespace Cecilifier.Core.CodeGeneration; * .data cil I_00002B50 = bytearray (01 02 03) * } // end of class */ -internal class PrivateImplementationDetailsGenerator +internal partial class PrivateImplementationDetailsGenerator { + internal static string GetOrEmmitInlineArrayAsSpanMethod(IVisitorContext context) + { + var found = context.DefinitionVariables.GetVariable("InlineArrayAsSpan", VariableMemberKind.Method, Constants.CompilerGeneratedTypes.PrivateImplementationDetails); + if (found.IsValid) + return found.VariableName; + + var privateImplementationDetailsVar = GetOrCreatePrivateImplementationDetailsTypeVariable(context); + + context.WriteNewLine(); + context.WriteComment($"{Constants.CompilerGeneratedTypes.PrivateImplementationDetails}.InlineArrayAsSpan()"); + var methodVar = context.Naming.SyntheticVariable("inlineArrayAsSpan", ElementKind.Method); + + var methodTypeQualifiedName = $"{privateImplementationDetailsVar.MemberName}.InlineArrayAsSpan"; + var methodExpressions = CecilDefinitionsFactory.Method( + context, + $"{privateImplementationDetailsVar.MemberName}", + methodVar, + "InlineArrayAsSpan", + "MethodAttributes.Assembly | MethodAttributes.Static | MethodAttributes.HideBySig", + [ + new CecilDefinitionsFactory.ParameterSpec("buffer", "TBuffer", RefKind.Ref, (context, name) => ResolveOwnedGenericParameter(context, name, methodTypeQualifiedName)), + new CecilDefinitionsFactory.ParameterSpec("length", context.TypeResolver.Bcl.System.Int32, RefKind.None) + ], + ["TBuffer", "TElement"], + context => + { + var spanTypeParameter = ResolveOwnedGenericParameter(context, "TElement", methodTypeQualifiedName); + return context.TypeResolver.Resolve(context.RoslynTypeSystem.SystemSpan).MakeGenericInstanceType(spanTypeParameter); + }); + + var methodBodyExpressions = CecilDefinitionsFactory.MethodBody( + methodVar, + [ + OpCodes.Ldarg_0, + OpCodes.Call.WithOperand(GetUnsafeAsMethod(context).MethodResolverExpression(context)), + OpCodes.Ldarg_1, + OpCodes.Call.WithOperand(GetMemoryMarshalCreateSpanMethod(context).MethodResolverExpression(context)), + OpCodes.Ret + ]); + + foreach (var exp in methodExpressions.Concat(methodBodyExpressions)) + { + context.WriteCecilExpression(exp); + context.WriteNewLine(); + } + + return methodVar; + + static IMethodSymbol GetMemoryMarshalCreateSpanMethod(IVisitorContext context) + { + VerifyCreateSpanHasOnlyOneOverload(context); + var createSpanMethod= context.RoslynTypeSystem.SystemRuntimeInteropServicesMemoryMarshal + .GetMembers() + .OfType() + .Single(m => m.Name == "CreateSpan" && m.Parameters.Length == 2 && m.Parameters[0].RefKind == RefKind.Ref && m.Parameters[1].Type.Equals(context.RoslynTypeSystem.SystemInt32, SymbolEqualityComparer.Default)); + + return createSpanMethod; + } + + [Conditional("DEBUG")] + static void VerifyCreateSpanHasOnlyOneOverload(IVisitorContext context) + { + var candidates= context.RoslynTypeSystem.SystemRuntimeInteropServicesMemoryMarshal + .GetMembers() + .OfType() + .Where(m => m.Name == "CreateSpan"); + + Debug.Assert(candidates.Count() == 1); + } + } + + public static string GetOrEmmitInlineArrayFirstElementRefMethod(IVisitorContext context) + { + var found = context.DefinitionVariables.GetVariable("InlineArrayFirstElementRef", VariableMemberKind.Method, Constants.CompilerGeneratedTypes.PrivateImplementationDetails); + if (found.IsValid) + return found.VariableName; + + var privateImplementationDetailsVar = GetOrCreatePrivateImplementationDetailsTypeVariable(context); + + context.WriteNewLine(); + context.WriteComment($"{Constants.CompilerGeneratedTypes.PrivateImplementationDetails}.InlineArrayFirstElementRef()"); + var methodVar = context.Naming.SyntheticVariable("inlineArrayFirstElementRef", ElementKind.Method); + + var methodTypeQualifiedName = $"{privateImplementationDetailsVar.MemberName}.InlineArrayFirstElementRef"; + var methodExpressions = CecilDefinitionsFactory.Method( + context, + $"{privateImplementationDetailsVar.MemberName}", + methodVar, + "InlineArrayFirstElementRef", + "MethodAttributes.Assembly | MethodAttributes.Static | MethodAttributes.HideBySig", + [ new CecilDefinitionsFactory.ParameterSpec("buffer", "TBuffer", RefKind.Ref, (ctx, name) => ResolveOwnedGenericParameter(ctx, name, methodTypeQualifiedName))], + ["TBuffer", "TElement"], + ctx => + { + var spanTypeParameter = ResolveOwnedGenericParameter(ctx, "TElement", methodTypeQualifiedName); + return spanTypeParameter.MakeByReferenceType(); + }); + + var methodBodyExpressions = CecilDefinitionsFactory.MethodBody( + methodVar, + [ + OpCodes.Ldarg_0, + OpCodes.Call.WithOperand(GetUnsafeAsMethod(context).MethodResolverExpression(context)), + OpCodes.Ret + ]); + + foreach (var exp in methodExpressions.Concat(methodBodyExpressions)) + { + context.WriteCecilExpression(exp); + context.WriteNewLine(); + } + + context.WriteCecilExpression($"{privateImplementationDetailsVar.VariableName}.Methods.Add({methodVar});"); + context.WriteNewLine(); + context.WriteComment("-------------------------------"); + context.WriteNewLine(); + return methodVar; + } + + public static string GetOrEmmitInlineArrayElementRefMethod(IVisitorContext context) + { + var found = context.DefinitionVariables.GetVariable("InlineArrayElementRef", VariableMemberKind.Method, Constants.CompilerGeneratedTypes.PrivateImplementationDetails); + if (found.IsValid) + return found.VariableName; + + var privateImplementationDetailsVar = GetOrCreatePrivateImplementationDetailsTypeVariable(context); + + context.WriteNewLine(); + context.WriteComment($"{Constants.CompilerGeneratedTypes.PrivateImplementationDetails}.InlineArrayElementRef()"); + var methodVar = context.Naming.SyntheticVariable("inlineArrayElementRef", ElementKind.Method); + + var methodTypeQualifiedName = $"{privateImplementationDetailsVar.MemberName}.InlineArrayElementRef"; + var methodExpressions = CecilDefinitionsFactory.Method( + context, + $"{privateImplementationDetailsVar.MemberName}", + methodVar, + "InlineArrayElementRef", + "MethodAttributes.Assembly | MethodAttributes.Static | MethodAttributes.HideBySig", + [ + new CecilDefinitionsFactory.ParameterSpec("buffer", "TBuffer", RefKind.Ref, (ctx, name) => ResolveOwnedGenericParameter(ctx, name, methodTypeQualifiedName)), + new CecilDefinitionsFactory.ParameterSpec("index", context.TypeResolver.Bcl.System.Int32, RefKind.None) + ], + ["TBuffer", "TElement"], + ctx => + { + var spanTypeParameter = ResolveOwnedGenericParameter(ctx, "TElement", methodTypeQualifiedName); + return spanTypeParameter.MakeByReferenceType(); + }); + + var methodBodyExpressions = CecilDefinitionsFactory.MethodBody( + methodVar, + [ + OpCodes.Ldarg_0, + OpCodes.Call.WithOperand(GetUnsafeAsMethod(context).MethodResolverExpression(context)), + OpCodes.Ldarg_1, + OpCodes.Call.WithOperand(GetUnsafeAddMethod(context).MethodResolverExpression(context)), + OpCodes.Ret + ]); + + foreach (var exp in methodExpressions.Concat(methodBodyExpressions)) + { + context.WriteCecilExpression(exp); + context.WriteNewLine(); + } + + context.WriteCecilExpression($"{privateImplementationDetailsVar.VariableName}.Methods.Add({methodVar});"); + context.WriteNewLine(); + context.WriteComment("-------------------------------"); + context.WriteNewLine(); + + return methodVar; + } + + private static string ResolveOwnedGenericParameter(IVisitorContext context, string name, string methodTypeQualifiedName) + { + var spanTypeParameter = context.DefinitionVariables.GetVariable( + name, + VariableMemberKind.TypeParameter, + methodTypeQualifiedName); + + return spanTypeParameter.VariableName; + } + internal static string GetOrCreateInitializationBackingFieldVariableName(IVisitorContext context, long sizeInBytes, string arrayElementTypeName, string initializationExpression) { Span toBeHashed = stackalloc byte[System.Text.Encoding.UTF8.GetByteCount(initializationExpression)]; diff --git a/Cecilifier.Core/Extensions/MethodExtensions.cs b/Cecilifier.Core/Extensions/MethodExtensions.cs index 4b177ae5..80833772 100644 --- a/Cecilifier.Core/Extensions/MethodExtensions.cs +++ b/Cecilifier.Core/Extensions/MethodExtensions.cs @@ -11,7 +11,6 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Mono.Cecil; using static Cecilifier.Core.Misc.Utils; -using MethodAttributes = Mono.Cecil.MethodAttributes; namespace Cecilifier.Core.Extensions { @@ -215,7 +214,17 @@ public static string ModifiersForSyntheticMethod(this SyntaxTokenList modifiers, public static bool HasCovariantReturnType(this IMethodSymbol method) => method is { IsOverride: true } && !SymbolEqualityComparer.Default.Equals(method.ReturnType, method.OverriddenMethod?.ReturnType); - public static bool IsExplicitMethodImplementation(this IMethodSymbol methodSymbol) + public static IEnumerable MakeGenericInstanceMethod(this string methodReferenceVariable, string targetVarName, IEnumerable resolvedTypeArguments) + { + var expressions = new List(); + expressions.Add($"var {targetVarName} = new GenericInstanceMethod({methodReferenceVariable});"); + foreach (var t in resolvedTypeArguments) + expressions.Add($"{targetVarName}.GenericArguments.Add({t});"); + + return expressions; + } + + private static bool IsExplicitMethodImplementation(this IMethodSymbol methodSymbol) { return methodSymbol.ExplicitInterfaceImplementations.Any(); } diff --git a/Cecilifier.Core/Misc/CecilDefinitionsFactory.cs b/Cecilifier.Core/Misc/CecilDefinitionsFactory.cs index f4b273bb..41a22fee 100644 --- a/Cecilifier.Core/Misc/CecilDefinitionsFactory.cs +++ b/Cecilifier.Core/Misc/CecilDefinitionsFactory.cs @@ -4,6 +4,7 @@ using System.Linq; using Cecilifier.Core.AST; using Cecilifier.Core.Extensions; +using Cecilifier.Core.Naming; using Cecilifier.Core.TypeSystem; using Cecilifier.Core.Variables; using Microsoft.CodeAnalysis; @@ -52,6 +53,35 @@ public static IEnumerable Method(IVisitorContext context, string methodV return exps; } + public record ParameterSpec(string Name, string ElementType, RefKind RefKind, Func ElementTypeResolver = null); + public static IEnumerable Method( + IVisitorContext context, + string containingTypeName, + string methodVar, + string methodName, + string methodModifiers, + IReadOnlyList parameters, + IList typeParameters, + Func returnTypeResolver) + { + var exps = new List(); + + // for type parameters we may need to postpone setting the return type (using void as a placeholder, since we need to pass something) until the generic parameters has been + // handled. This is required because the type parameter may be defined by the method being processed. + exps.Add($"var {methodVar} = new MethodDefinition(\"{methodName}\", {methodModifiers}, {context.TypeResolver.Bcl.System.Void});"); + ProcessGenericTypeParameters(methodVar, context, $"{containingTypeName}.{methodName}", typeParameters, exps); + exps.Add($"{methodVar}.ReturnType = {returnTypeResolver(context)};"); + + foreach (var parameter in parameters) + { + var parameterExp = Parameter(parameter.Name, parameter.RefKind, parameter.ElementTypeResolver != null ? parameter.ElementTypeResolver(context, parameter.ElementType) : parameter.ElementType); + exps.Add($"{methodVar}.Parameters.Add({parameterExp});"); + } + + context.DefinitionVariables.RegisterMethod(containingTypeName, methodName, parameters.Select(p => p.ElementType).ToArray(), methodVar); + return exps; + } + internal static string Constructor(IVisitorContext context, string ctorLocalVar, string typeName, bool isStatic, string methodAccessibility, string[] paramTypes, string methodDefinitionPropertyValues = null) { var ctorName = Utils.ConstructorMethodName(isStatic); @@ -153,6 +183,12 @@ private static string GenericParameter(IVisitorContext context, string typeParam return $"var {genParamDefVar} = new Mono.Cecil.GenericParameter(\"{genericParamName}\", {typeParameterOwnerVar}){Variance(typeParameterSymbol)};"; } + private static string GenericParameter(IVisitorContext context, string ownerContainingTypeName, string typeParameterOwnerVar, string genericParamName, string genParamDefVar) + { + context.DefinitionVariables.RegisterNonMethod(ownerContainingTypeName, genericParamName, VariableMemberKind.TypeParameter, genParamDefVar); + return $"var {genParamDefVar} = new Mono.Cecil.GenericParameter(\"{genericParamName}\", {typeParameterOwnerVar});"; + } + private static string Variance(ITypeParameterSymbol typeParameterSymbol) { if (typeParameterSymbol.Variance == VarianceKind.In) @@ -338,6 +374,19 @@ void AddConstraints(string genParamDefVar, ITypeParameterSymbol typeParam) } } } + + private static void ProcessGenericTypeParameters(string memberDefVar, IVisitorContext context, string ownerQualifiedTypeName, IList typeParamList, IList exps) + { + for (int i = 0; i < typeParamList.Count; i++) + { + var genericParamName = typeParamList[i]; + var genParamDefVar = context.Naming.SyntheticVariable(typeParamList[i], ElementKind.GenericParameter); + //var genParamDefVar = context.Naming.GenericParameterDeclaration(typeParamList[i]); + exps.Add(GenericParameter(context, ownerQualifiedTypeName, memberDefVar, genericParamName, genParamDefVar)); + + exps.Add($"{memberDefVar}.GenericParameters.Add({genParamDefVar});"); + } + } public static IEnumerable MethodBody(string methodVar, InstructionRepresentation[] instructions) { diff --git a/Cecilifier.Core/TypeSystem/RoslynTypeSystem.cs b/Cecilifier.Core/TypeSystem/RoslynTypeSystem.cs index 2db825f0..a370cf8c 100644 --- a/Cecilifier.Core/TypeSystem/RoslynTypeSystem.cs +++ b/Cecilifier.Core/TypeSystem/RoslynTypeSystem.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Generic; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using Cecilifier.Core.AST; using Microsoft.CodeAnalysis; using IsByRefLikeAttribute = System.Runtime.CompilerServices.IsByRefLikeAttribute; @@ -42,6 +43,8 @@ public RoslynTypeSystem(IVisitorContext ctx) SystemCollectionsIEnumerable = ctx.SemanticModel.Compilation.GetSpecialType(SpecialType.System_Collections_IEnumerable); SystemCollectionsGenericIEnumerableOfT = ctx.SemanticModel.Compilation.GetSpecialType(SpecialType.System_Collections_Generic_IEnumerable_T); SystemNullableOfT = ctx.SemanticModel.Compilation.GetSpecialType(SpecialType.System_Nullable_T); + SystemRuntimeCompilerServicesUnsafe = ctx.SemanticModel.Compilation.GetTypeByMetadataName(typeof(Unsafe).FullName); + SystemRuntimeInteropServicesMemoryMarshal = ctx.SemanticModel.Compilation.GetTypeByMetadataName(typeof(MemoryMarshal).FullName); } public ITypeSymbol SystemIndex { get; } @@ -66,8 +69,9 @@ public RoslynTypeSystem(IVisitorContext ctx) public ITypeSymbol IsReadOnlyAttribute { get; } public ITypeSymbol IsByRefLikeAttribute { get; } public ITypeSymbol SystemObsoleteAttribute { get; } - public ITypeSymbol SystemValueType { get; } public ITypeSymbol SystemRuntimeCompilerServicesRuntimeHelpers { get; } public ITypeSymbol SystemNullableOfT { get; } + public ITypeSymbol SystemRuntimeCompilerServicesUnsafe { get; } + public ITypeSymbol SystemRuntimeInteropServicesMemoryMarshal { get; } } diff --git a/Cecilifier.Runtime/TypeHelpers.cs b/Cecilifier.Runtime/TypeHelpers.cs index bb8cea4f..6ca9303a 100644 --- a/Cecilifier.Runtime/TypeHelpers.cs +++ b/Cecilifier.Runtime/TypeHelpers.cs @@ -77,23 +77,17 @@ public static MethodInfo ResolveGenericMethodInstance(string declaringTypeName, { var parameters = mc.GetParameters(); var found = true; - var x = false; + var hasOpenGenericTypes = false; - for (var i = 0; i < parameters.Length; i++) + for (var i = 0; i < parameters.Length && found; i++) { - if (paramTypesArray[i].IsTypeParameter) - x = true; - - if (!CompareParameters(parameters[i], paramTypesArray[i])) - { - found = false; - break; - } + found &= CompareParameters(parameters[i], paramTypesArray[i]); + hasOpenGenericTypes |= paramTypesArray[i].IsTypeParameter; } if (found) { - return x + return hasOpenGenericTypes ? mc : mc.MakeGenericMethod(genericParameters.Select(Type.GetType).ToArray()); }