Skip to content

Commit

Permalink
adds support for function pointers
Browse files Browse the repository at this point in the history
Implements enhancement #64
  • Loading branch information
adrianoc committed Jan 1, 2021
1 parent 397de2d commit 89bfb4f
Show file tree
Hide file tree
Showing 11 changed files with 176 additions and 54 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
unsafe class FunctionPointers
{
static int F() => 10;
static int F2(string s) => s.Length;

void TestIntFunction()
{
delegate*<int> fp = &F;
//System.Console.Write(fp == null);

System.Console.Write(fp());
}

int TestIntFunctionWithParameters()
{
delegate*<string, int> fp = &F2;
System.Console.WriteLine(fp == null);
return 0;
//return fp("test");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
unsafe class FunctionPointersAsParameters
{
static int F() => 10;

int TestFunctionPointerAsParameter(delegate*<int> func)
{
return func() + TestFunctionPointerAsParameter(&F);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
unsafe class GenericFunctionPointers
{
static T Identity<T>(T t) => t;

void TestGenericFunctionPointer()
{
delegate*<int,int> fp = &Identity<int>;
System.Console.Write(fp(1));

delegate*<int,int> fp2 = &Identity;
System.Console.Write(fp2(2));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
unsafe class VoidFunctionPointers
{
static void F() {}

void TestVoidFunction()
{
delegate*<void> fp = &F;
fp();
}
}
9 changes: 9 additions & 0 deletions Cecilifier.Core.Tests/Tests/Integration/MiscTestCase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down
2 changes: 0 additions & 2 deletions Cecilifier.Core/AST/CompilationUnitVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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"));
Expand Down
99 changes: 57 additions & 42 deletions Cecilifier.Core/AST/ExpressionVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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<ArgumentListSyntax>().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<MemberDeclarationSyntax>() 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<ArgumentListSyntax>().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<int32, string>::.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<MemberDeclarationSyntax>() 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<int32, string>::.ctor(object, native int)

var delegateCtor = delegateType.GetMembers().OfType<IMethodSymbol>().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<IMethodSymbol>().FirstOrDefault(m => m.Name == ".ctor");
AddCilInstruction(ilVar, OpCodes.Newobj, delegateCtor.MethodResolverExpression(Context));
}

private void ProcessMethodCall(SimpleNameSyntax node, IMethodSymbol method)
Expand Down
2 changes: 0 additions & 2 deletions Cecilifier.Core/AST/GlobalStatementHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,6 @@ internal GlobalStatementHandler(IVisitorContext context, GlobalStatementSyntax f
"args",
RefKind.None,
false,
true,
context.SemanticModel,
methodVar,
paramVar,
context.TypeResolver.ResolvePredefinedType("String") + ".MakeArrayType()");
Expand Down
9 changes: 8 additions & 1 deletion Cecilifier.Core/AST/SyntaxWalkerBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
51 changes: 44 additions & 7 deletions Cecilifier.Core/Misc/CecilDefinitionsFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> Method(IVisitorContext context, string methodVar, string methodName, string methodModifiers, string returnType, IList<TypeParameterSyntax> typeParameters)
{
var exps = new List<string>();
Expand Down Expand Up @@ -122,16 +138,21 @@ public static IEnumerable<string> Field(string declaringTypeVar, string fieldVar
return exps;
}

public static IEnumerable<string> 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<string>();
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<string> Parameter(string name, RefKind byRef, bool isParams, string methodVar, string paramVar, string resolvedType)
{
var exps = new List<string>();

exps.Add($"var {paramVar} = {Parameter(name, byRef, isParams, resolvedType)};");
AddExtraAttributes(exps, paramVar, byRef);

if (isParams)
Expand All @@ -147,16 +168,24 @@ public static IEnumerable<string> Parameter(string name, RefKind byRef, bool isP
public static IEnumerable<string> 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<string> Attribute(string attrTargetVar, IVisitorContext context, AttributeSyntax attribute, Func<ITypeSymbol, AttributeArgumentSyntax[], string> ctorResolver)
{
Expand Down Expand Up @@ -319,13 +348,21 @@ public static string DefaultTypeAttributeFor(SyntaxKind syntaxKind, bool hasStat
_ => throw new Exception("Not supported type declaration: " + syntaxKind)
};
}

private static void AddExtraAttributes(IList<string> exps, string paramVar, RefKind byRef)
{
if (byRef == RefKind.Out)
{
exps.Add($"{paramVar}.Attributes = ParameterAttributes.Out;");
}
}

private static string FunctionPointerTypeBasedCecilEquivalent(ITypeResolver resolver, IFunctionPointerTypeSymbol functionPointer, Func<string, string, string, string> 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);
}
}
}
5 changes: 5 additions & 0 deletions Cecilifier.Core/Misc/TypeResolverImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit 89bfb4f

Please sign in to comment.