diff --git a/Cecilifier.Core.Tests/TestResources/Integration/Misc/Pointers/FunctionPointers.cs.txt b/Cecilifier.Core.Tests/TestResources/Integration/Misc/Pointers/FunctionPointers.cs.txt new file mode 100644 index 00000000..a3da5edb --- /dev/null +++ b/Cecilifier.Core.Tests/TestResources/Integration/Misc/Pointers/FunctionPointers.cs.txt @@ -0,0 +1,21 @@ +unsafe class FunctionPointers +{ + static int F() => 10; + static int F2(string s) => s.Length; + + void TestIntFunction() + { + delegate* fp = &F; + //System.Console.Write(fp == null); + + System.Console.Write(fp()); + } + + int TestIntFunctionWithParameters() + { + delegate* fp = &F2; + System.Console.WriteLine(fp == null); + return 0; + //return fp("test"); + } +} \ No newline at end of file diff --git a/Cecilifier.Core.Tests/TestResources/Integration/Misc/Pointers/FunctionPointersAsParameters.cs.txt b/Cecilifier.Core.Tests/TestResources/Integration/Misc/Pointers/FunctionPointersAsParameters.cs.txt new file mode 100644 index 00000000..23945ec1 --- /dev/null +++ b/Cecilifier.Core.Tests/TestResources/Integration/Misc/Pointers/FunctionPointersAsParameters.cs.txt @@ -0,0 +1,9 @@ +unsafe class FunctionPointersAsParameters +{ + static int F() => 10; + + int TestFunctionPointerAsParameter(delegate* func) + { + return func() + TestFunctionPointerAsParameter(&F); + } +} \ No newline at end of file diff --git a/Cecilifier.Core.Tests/TestResources/Integration/Misc/Pointers/GenericFunctionPointers.cs.txt b/Cecilifier.Core.Tests/TestResources/Integration/Misc/Pointers/GenericFunctionPointers.cs.txt new file mode 100644 index 00000000..47a0c660 --- /dev/null +++ b/Cecilifier.Core.Tests/TestResources/Integration/Misc/Pointers/GenericFunctionPointers.cs.txt @@ -0,0 +1,13 @@ +unsafe class GenericFunctionPointers +{ + static T Identity(T t) => t; + + void TestGenericFunctionPointer() + { + delegate* fp = &Identity; + System.Console.Write(fp(1)); + + delegate* fp2 = &Identity; + System.Console.Write(fp2(2)); + } +} \ No newline at end of file diff --git a/Cecilifier.Core.Tests/TestResources/Integration/Misc/Pointers/VoidFunctionPointers.cs.txt b/Cecilifier.Core.Tests/TestResources/Integration/Misc/Pointers/VoidFunctionPointers.cs.txt new file mode 100644 index 00000000..28b07449 --- /dev/null +++ b/Cecilifier.Core.Tests/TestResources/Integration/Misc/Pointers/VoidFunctionPointers.cs.txt @@ -0,0 +1,10 @@ +unsafe class VoidFunctionPointers +{ + static void F() {} + + void TestVoidFunction() + { + delegate* fp = &F; + fp(); + } +} \ No newline at end of file diff --git a/Cecilifier.Core.Tests/Tests/Integration/MiscTestCase.cs b/Cecilifier.Core.Tests/Tests/Integration/MiscTestCase.cs index 8f1df617..5fb6bd47 100644 --- a/Cecilifier.Core.Tests/Tests/Integration/MiscTestCase.cs +++ b/Cecilifier.Core.Tests/Tests/Integration/MiscTestCase.cs @@ -35,6 +35,15 @@ public void TestPointerTypes(string testName) AssertResourceTest($@"Misc/Pointers/{testName}"); } + [TestCase("FunctionPointers", TestName = "Basic Tests")] + [TestCase("VoidFunctionPointers")] + [TestCase("FunctionPointersAsParameters")] + [TestCase("GenericFunctionPointers", IgnoreReason = "Generic methods are not supported. See issue #68")] + public void TestFunctionPointer(string testName) + { + AssertResourceTest($@"Misc/Pointers/{testName}"); + } + [TestCase("Delegate")] [TestCase("ClassAndMembers")] [TestCase("InterfaceAndMembers")] diff --git a/Cecilifier.Core/AST/CompilationUnitVisitor.cs b/Cecilifier.Core/AST/CompilationUnitVisitor.cs index 20f4b9f3..e6dcf86e 100644 --- a/Cecilifier.Core/AST/CompilationUnitVisitor.cs +++ b/Cecilifier.Core/AST/CompilationUnitVisitor.cs @@ -151,8 +151,6 @@ public override void VisitDelegateDeclaration(DelegateDeclarationSyntax node) "ar", RefKind.None, false, - false, - Context.SemanticModel, endInvokeMethodVar, TempLocalVar("ar"), Context.TypeResolver.Resolve("System.IAsyncResult")); diff --git a/Cecilifier.Core/AST/ExpressionVisitor.cs b/Cecilifier.Core/AST/ExpressionVisitor.cs index 35a9d3a8..5159f011 100644 --- a/Cecilifier.Core/AST/ExpressionVisitor.cs +++ b/Cecilifier.Core/AST/ExpressionVisitor.cs @@ -242,7 +242,14 @@ public override void VisitLiteralExpression(LiteralExpressionSyntax node) switch (node.Kind()) { case SyntaxKind.NullLiteralExpression: - AddCilInstruction(ilVar, OpCodes.Ldnull); + var nodeType = Context.SemanticModel.GetTypeInfo(node); + if (nodeType.ConvertedType?.TypeKind == TypeKind.Pointer) + { + AddCilInstruction(ilVar, OpCodes.Ldc_I4_0); + AddCilInstruction(ilVar, OpCodes.Conv_U); + } + else + AddCilInstruction(ilVar, OpCodes.Ldnull); break; case SyntaxKind.StringLiteralExpression: @@ -726,56 +733,64 @@ private void ProcessMethodReference(SimpleNameSyntax node, IMethodSymbol method) if (invocationParent != null) { ProcessMethodCall(node, method); + return; } - else + + // this is not an invocation. We need to figure out whether this is an assignment, return, etc + var firstParentNotPartOfName = node.Ancestors().First(a => a.Kind() != SyntaxKind.QualifiedName + && a.Kind() != SyntaxKind.SimpleMemberAccessExpression + && a.Kind() != SyntaxKind.EqualsValueClause + && a.Kind() != SyntaxKind.VariableDeclarator); + + if (firstParentNotPartOfName is PrefixUnaryExpressionSyntax unaryPrefix && unaryPrefix.IsKind(SyntaxKind.AddressOfExpression)) { - // this is not an invocation. We need to figure out whether this is an assignment, return, etc - var firstParentNotPartOfName = node.Ancestors().First(a => a.Kind() != SyntaxKind.QualifiedName - && a.Kind() != SyntaxKind.SimpleMemberAccessExpression - && a.Kind() != SyntaxKind.EqualsValueClause - && a.Kind() != SyntaxKind.VariableDeclarator); + AddCilInstruction(ilVar, OpCodes.Ldftn, method.MethodResolverExpression(Context)); + return; + } - var delegateType = firstParentNotPartOfName switch - { - ArgumentSyntax arg => ((IMethodSymbol) Context.SemanticModel.GetSymbolInfo(arg.Parent.Parent).Symbol).Parameters[arg.FirstAncestorOrSelf().Arguments.IndexOf(arg)].Type, - AssignmentExpressionSyntax assignment => Context.SemanticModel.GetSymbolInfo(assignment.Left).Symbol switch - { - ILocalSymbol local => local.Type, - IParameterSymbol param => param.Type, - IFieldSymbol field => field.Type, - IPropertySymbol prop => prop.Type, - _ => throw new NotSupportedException($"Assignment to {assignment.Left} ({assignment.Kind()}) is not supported.") - }, - VariableDeclarationSyntax variableDeclaration => Context.SemanticModel.GetTypeInfo(variableDeclaration.Type).Type, - ReturnStatementSyntax returnStatement => returnStatement.FirstAncestorOrSelf() switch - { - MethodDeclarationSyntax md => Context.SemanticModel.GetTypeInfo(md.ReturnType).Type, - _ => throw new NotSupportedException($"Return is not supported.") - }, + var delegateType = firstParentNotPartOfName switch + { + ArgumentSyntax arg => ((IMethodSymbol) Context.SemanticModel.GetSymbolInfo(arg.Parent.Parent).Symbol).Parameters[arg.FirstAncestorOrSelf().Arguments.IndexOf(arg)].Type, - _ => throw new NotSupportedException($"Referencing method {method} in expression {firstParentNotPartOfName} ({firstParentNotPartOfName.Kind()}) is not supported.") - }; - - // we have a reference to a method used to initialize a delegate - // and need to load the referenced method token and instantiate the delegate. For instance: - //IL_0002: ldarg.0 - //IL_0002: ldftn string Test::M(int32) - //IL_0008: newobj instance void class [System.Private.CoreLib]System.Func`2::.ctor(object, native int) - - if (method.IsStatic) + AssignmentExpressionSyntax assignment => Context.SemanticModel.GetSymbolInfo(assignment.Left).Symbol switch { - AddCilInstruction(ilVar, OpCodes.Ldnull); - } - else if (!node.Parent.IsKind(SyntaxKind.ThisExpression) && node.Parent == firstParentNotPartOfName) + ILocalSymbol local => local.Type, + IParameterSymbol param => param.Type, + IFieldSymbol field => field.Type, + IPropertySymbol prop => prop.Type, + _ => throw new NotSupportedException($"Assignment to {assignment.Left} ({assignment.Kind()}) is not supported.") + }, + + VariableDeclarationSyntax variableDeclaration => Context.SemanticModel.GetTypeInfo(variableDeclaration.Type).Type, + + ReturnStatementSyntax returnStatement => returnStatement.FirstAncestorOrSelf() switch { - AddCilInstruction(ilVar, OpCodes.Ldarg_0); - } + MethodDeclarationSyntax md => Context.SemanticModel.GetTypeInfo(md.ReturnType).Type, + _ => throw new NotSupportedException($"Return is not supported.") + }, + + _ => throw new NotSupportedException($"Referencing method {method} in expression {firstParentNotPartOfName} ({firstParentNotPartOfName.GetType().FullName}) is not supported.") + }; - AddCilInstruction(ilVar, OpCodes.Ldftn, method.MethodResolverExpression(Context)); + // we have a reference to a method used to initialize a delegate + // and need to load the referenced method token and instantiate the delegate. For instance: + //IL_0002: ldarg.0 + //IL_0002: ldftn string Test::M(int32) + //IL_0008: newobj instance void class [System.Private.CoreLib]System.Func`2::.ctor(object, native int) - var delegateCtor = delegateType.GetMembers().OfType().FirstOrDefault(m => m.Name == ".ctor"); - AddCilInstruction(ilVar, OpCodes.Newobj, delegateCtor.MethodResolverExpression(Context)); + if (method.IsStatic) + { + AddCilInstruction(ilVar, OpCodes.Ldnull); + } + else if (!node.Parent.IsKind(SyntaxKind.ThisExpression) && node.Parent == firstParentNotPartOfName) + { + AddCilInstruction(ilVar, OpCodes.Ldarg_0); } + + AddCilInstruction(ilVar, OpCodes.Ldftn, method.MethodResolverExpression(Context)); + + var delegateCtor = delegateType.GetMembers().OfType().FirstOrDefault(m => m.Name == ".ctor"); + AddCilInstruction(ilVar, OpCodes.Newobj, delegateCtor.MethodResolverExpression(Context)); } private void ProcessMethodCall(SimpleNameSyntax node, IMethodSymbol method) diff --git a/Cecilifier.Core/AST/GlobalStatementHandler.cs b/Cecilifier.Core/AST/GlobalStatementHandler.cs index 154d9756..761436f0 100644 --- a/Cecilifier.Core/AST/GlobalStatementHandler.cs +++ b/Cecilifier.Core/AST/GlobalStatementHandler.cs @@ -42,8 +42,6 @@ internal GlobalStatementHandler(IVisitorContext context, GlobalStatementSyntax f "args", RefKind.None, false, - true, - context.SemanticModel, methodVar, paramVar, context.TypeResolver.ResolvePredefinedType("String") + ".MakeArrayType()"); diff --git a/Cecilifier.Core/AST/SyntaxWalkerBase.cs b/Cecilifier.Core/AST/SyntaxWalkerBase.cs index f4546796..f00982d3 100644 --- a/Cecilifier.Core/AST/SyntaxWalkerBase.cs +++ b/Cecilifier.Core/AST/SyntaxWalkerBase.cs @@ -6,6 +6,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Mono.Cecil; using Mono.Cecil.Cil; using static Cecilifier.Core.Misc.Utils; @@ -228,7 +229,7 @@ protected static void WriteCecilExpression(IVisitorContext context, string value protected static bool ExcludeHasNoCILRepresentation(SyntaxToken token) { - return token.Kind() != SyntaxKind.PartialKeyword && token.Kind() != SyntaxKind.VolatileKeyword; + return !token.IsKind(SyntaxKind.PartialKeyword) && !token.IsKind(SyntaxKind.VolatileKeyword) && !token.IsKind(SyntaxKind.UnsafeKeyword); } protected string ResolveExpressionType(ExpressionSyntax expression) @@ -311,6 +312,12 @@ protected void HandlePotentialDelegateInvocationOn(SimpleNameSyntax node, ITypeS { return; } + + if (typeSymbol is IFunctionPointerTypeSymbol functionPointer) + { + AddCilInstruction(ilVar, OpCodes.Calli, CecilDefinitionsFactory.CallSite(Context.TypeResolver, functionPointer)); + return; + } var localDelegateDeclaration = Context.TypeResolver.ResolveTypeLocalVariable(typeSymbol); if (localDelegateDeclaration != null) diff --git a/Cecilifier.Core/Misc/CecilDefinitionsFactory.cs b/Cecilifier.Core/Misc/CecilDefinitionsFactory.cs index 9913ed4f..17a4d56a 100644 --- a/Cecilifier.Core/Misc/CecilDefinitionsFactory.cs +++ b/Cecilifier.Core/Misc/CecilDefinitionsFactory.cs @@ -11,6 +11,22 @@ namespace Cecilifier.Core.Misc { internal sealed class CecilDefinitionsFactory { + public static string CallSite(ITypeResolver resolver, IFunctionPointerTypeSymbol functionPointer) + { + return FunctionPointerTypeBasedCecilEquivalent( + resolver, + functionPointer, + (hasThis, parameters, returnType) => $"new CallSite({returnType}) {{ {hasThis}, {parameters} }}"); + } + + public static string FunctionPointerType(ITypeResolver resolver, IFunctionPointerTypeSymbol functionPointer) + { + return FunctionPointerTypeBasedCecilEquivalent( + resolver, + functionPointer, + (hasThis, parameters, returnType) => $"new FunctionPointerType() {{ {hasThis}, ReturnType = {returnType}, {parameters} }}"); + } + public static IEnumerable Method(IVisitorContext context, string methodVar, string methodName, string methodModifiers, string returnType, IList typeParameters) { var exps = new List(); @@ -122,16 +138,21 @@ public static IEnumerable Field(string declaringTypeVar, string fieldVar return exps; } - public static IEnumerable Parameter(string name, RefKind byRef, bool isParams, bool isArray, SemanticModel semanticModel, string methodVar, string paramVar, string resolvedType) + public static string Parameter(string name, RefKind byRef, bool isParams, string resolvedType) { - var exps = new List(); if (RefKind.None != byRef) { resolvedType = "new ByReferenceType(" + resolvedType + ")"; } - exps.Add($"var {paramVar} = new ParameterDefinition(\"{name}\", ParameterAttributes.None, {resolvedType});"); + return $"new ParameterDefinition(\"{name}\", ParameterAttributes.None, {resolvedType})"; + } + + public static IEnumerable Parameter(string name, RefKind byRef, bool isParams, string methodVar, string paramVar, string resolvedType) + { + var exps = new List(); + exps.Add($"var {paramVar} = {Parameter(name, byRef, isParams, resolvedType)};"); AddExtraAttributes(exps, paramVar, byRef); if (isParams) @@ -147,16 +168,24 @@ public static IEnumerable Parameter(string name, RefKind byRef, bool isP public static IEnumerable Parameter(ParameterSyntax node, SemanticModel semanticModel, string methodVar, string paramVar, string resolvedType) { var paramSymbol = semanticModel.GetDeclaredSymbol(node); + //return new[] { Parameter(paramSymbol, resolvedType)}; return Parameter( node.Identifier.Text, paramSymbol.RefKind, - isParams: node.GetFirstToken().Kind() == SyntaxKind.ParamsKeyword, - isArray: false, - semanticModel, + isParams: node.GetFirstToken().Kind() == SyntaxKind.ParamsKeyword, methodVar, paramVar, resolvedType); } + + public static string Parameter(IParameterSymbol paramSymbol, string resolvedType) + { + return Parameter( + paramSymbol.Name, + paramSymbol.RefKind, + isParams: paramSymbol.IsParams, + resolvedType); + } public static IEnumerable Attribute(string attrTargetVar, IVisitorContext context, AttributeSyntax attribute, Func ctorResolver) { @@ -319,7 +348,7 @@ public static string DefaultTypeAttributeFor(SyntaxKind syntaxKind, bool hasStat _ => throw new Exception("Not supported type declaration: " + syntaxKind) }; } - + private static void AddExtraAttributes(IList exps, string paramVar, RefKind byRef) { if (byRef == RefKind.Out) @@ -327,5 +356,13 @@ private static void AddExtraAttributes(IList exps, string paramVar, RefK exps.Add($"{paramVar}.Attributes = ParameterAttributes.Out;"); } } + + private static string FunctionPointerTypeBasedCecilEquivalent(ITypeResolver resolver, IFunctionPointerTypeSymbol functionPointer, Func factory) + { + var hasThis = $"HasThis = false"; + var parameters = $"Parameters={{ {string.Join(',', functionPointer.Signature.Parameters.Select(p => CecilDefinitionsFactory.Parameter(p, resolver.Resolve(p.Type))))} }}"; + var returnType = resolver.Resolve(functionPointer.Signature.ReturnType); + return factory(hasThis, parameters, returnType); + } } } diff --git a/Cecilifier.Core/Misc/TypeResolverImpl.cs b/Cecilifier.Core/Misc/TypeResolverImpl.cs index 2e766d0e..a955fa26 100644 --- a/Cecilifier.Core/Misc/TypeResolverImpl.cs +++ b/Cecilifier.Core/Misc/TypeResolverImpl.cs @@ -40,6 +40,11 @@ public string ResolvePredefinedAndComposedTypes(ITypeSymbol type) return Resolve(pointerType.PointedAtType) + ".MakePointerType()"; } + if (type is IFunctionPointerTypeSymbol functionPointer) + { + return CecilDefinitionsFactory.FunctionPointerType(this, functionPointer); + } + if (type.SpecialType == SpecialType.None || type.TypeKind == TypeKind.Interface || type.SpecialType == SpecialType.System_Enum || type.SpecialType == SpecialType.System_ValueType) { return null;