Browse Source

Add support for local function pointers.

pull/2160/head
Siegfried Pammer 5 years ago
parent
commit
0b0a6d94a8
  1. 2
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/FunctionPointers.cs
  2. 120
      ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs

2
ICSharpCode.Decompiler.Tests/TestCases/Pretty/FunctionPointers.cs

@ -37,7 +37,6 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
return (delegate*<object, string>)(&VarianceTest); return (delegate*<object, string>)(&VarianceTest);
} }
#if TODO
public unsafe delegate*<void> AddressOfLocalFunction() public unsafe delegate*<void> AddressOfLocalFunction()
{ {
return &LocalFunction; return &LocalFunction;
@ -47,7 +46,6 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
} }
} }
#endif
} }
internal class FunctionPointersWithDynamicTypes internal class FunctionPointersWithDynamicTypes

120
ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs

@ -23,7 +23,6 @@ using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Reflection.Metadata; using System.Reflection.Metadata;
using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using ICSharpCode.Decompiler.CSharp; using ICSharpCode.Decompiler.CSharp;
@ -44,7 +43,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
struct LocalFunctionInfo struct LocalFunctionInfo
{ {
public List<CallInstruction> UseSites; public List<ILInstruction> UseSites;
public IMethod Method; public IMethod Method;
public ILFunction Definition; public ILFunction Definition;
/// <summary> /// <summary>
@ -80,8 +79,15 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return; return;
// Disable the transform if we are decompiling a display-class or local function method: // Disable the transform if we are decompiling a display-class or local function method:
// This happens if a local function or display class is selected in the ILSpy tree view. // This happens if a local function or display class is selected in the ILSpy tree view.
if (IsLocalFunctionMethod(function.Method, context) || IsLocalFunctionDisplayClass(function.Method.ParentModule.PEFile, (TypeDefinitionHandle)function.Method.DeclaringTypeDefinition.MetadataToken, context)) if (IsLocalFunctionMethod(function.Method, context) || IsLocalFunctionDisplayClass(
function.Method.ParentModule.PEFile,
(TypeDefinitionHandle)function.Method.DeclaringTypeDefinition.MetadataToken,
context)
)
{
return; return;
}
this.context = context; this.context = context;
this.resolveContext = new SimpleTypeResolveContext(function.Method); this.resolveContext = new SimpleTypeResolveContext(function.Method);
var localFunctions = new Dictionary<MethodDefinitionHandle, LocalFunctionInfo>(); var localFunctions = new Dictionary<MethodDefinitionHandle, LocalFunctionInfo>();
@ -103,7 +109,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
var thisVar = localFunction.Variables.SingleOrDefault(VariableKindExtensions.IsThis); var thisVar = localFunction.Variables.SingleOrDefault(VariableKindExtensions.IsThis);
if (thisVar == null) if (thisVar == null)
continue; continue;
var compatibleArgument = FindCompatibleArgument(info, info.UseSites.SelectArray(u => u.Arguments[0]), ignoreStructure: true); var compatibleArgument = FindCompatibleArgument(
info,
info.UseSites.OfType<CallInstruction>().Select(u => u.Arguments[0]).ToArray(),
ignoreStructure: true
);
if (compatibleArgument == null) if (compatibleArgument == null)
continue; continue;
context.Step($"Replace 'this' with {compatibleArgument}", localFunction); context.Step($"Replace 'this' with {compatibleArgument}", localFunction);
@ -181,13 +191,22 @@ namespace ICSharpCode.Decompiler.IL.Transforms
foreach (var useSite in info.UseSites) foreach (var useSite in info.UseSites)
{ {
context.Step($"Transform use-site at IL_{useSite.StartILOffset:x4}", useSite); context.Step($"Transform use-site at IL_{useSite.StartILOffset:x4}", useSite);
if (useSite.OpCode == OpCode.NewObj) switch (useSite)
{
TransformToLocalFunctionReference(info.Definition, useSite);
}
else
{ {
TransformToLocalFunctionInvocation(info.Definition.ReducedMethod, useSite); case NewObj newObj:
TransformToLocalFunctionReference(info.Definition, newObj);
break;
case CallInstruction call:
TransformToLocalFunctionInvocation(info.Definition.ReducedMethod, call);
break;
case LdFtn fnptr:
var specializeMethod = info.Definition.ReducedMethod
.Specialize(fnptr.Method.Substitution);
var replacement = new LdFtn(specializeMethod).WithILRange(fnptr);
fnptr.ReplaceWith(replacement);
break;
default:
throw new NotSupportedException();
} }
} }
} }
@ -210,12 +229,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms
foreach (var useSite in info.UseSites) foreach (var useSite in info.UseSites)
{ {
if (useSite is NewObj) switch (useSite)
{
AddAsArgument(-1, useSite.Arguments[0]);
}
else
{ {
case NewObj newObj:
AddAsArgument(-1, newObj.Arguments[0]);
break;
case CallInstruction call:
int firstArgumentIndex; int firstArgumentIndex;
if (info.Method.IsStatic) if (info.Method.IsStatic)
{ {
@ -224,12 +243,20 @@ namespace ICSharpCode.Decompiler.IL.Transforms
else else
{ {
firstArgumentIndex = 1; firstArgumentIndex = 1;
AddAsArgument(-1, useSite.Arguments[0]); AddAsArgument(-1, call.Arguments[0]);
} }
for (int i = useSite.Arguments.Count - 1; i >= firstArgumentIndex; i--) for (int i = call.Arguments.Count - 1; i >= firstArgumentIndex; i--)
{ {
AddAsArgument(i - firstArgumentIndex, useSite.Arguments[i]); AddAsArgument(i - firstArgumentIndex, call.Arguments[i]);
} }
break;
case LdFtn _:
// &LocalFunction is only possible, if the local function is declared static,
// this means that the local function can be declared in the top-level scope.
// Thus, there are no closure parameters that need propagation.
break;
default:
throw new NotSupportedException();
} }
} }
@ -354,20 +381,23 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{ {
var callerMethod = useSite.Ancestors.OfType<ILFunction>().First().Method; var callerMethod = useSite.Ancestors.OfType<ILFunction>().First().Method;
callerMethod = callerMethod.ReducedFrom ?? callerMethod; callerMethod = callerMethod.ReducedFrom ?? callerMethod;
IMethod method0; IMethod m;
if (useSite.OpCode == OpCode.NewObj) switch (useSite)
{
method0 = ((LdFtn)useSite.Arguments[1]).Method;
}
else
{ {
method0 = useSite.Method; case NewObj newObj:
m = ((LdFtn)newObj.Arguments[1]).Method;
break;
case IInstructionWithMethodOperand withMethod:
m = withMethod.Method;
break;
default:
throw new NotSupportedException();
} }
var totalSkipCount = skipCount + method0.DeclaringType.TypeParameterCount; var totalSkipCount = skipCount + m.DeclaringType.TypeParameterCount;
var methodSkippedArgs = method0.DeclaringType.TypeArguments.Concat(method0.TypeArguments).Take(totalSkipCount); var methodSkippedArgs = m.DeclaringType.TypeArguments.Concat(m.TypeArguments).Take(totalSkipCount);
Debug.Assert(methodSkippedArgs.SequenceEqual(callerMethod.DeclaringType.TypeArguments.Concat(callerMethod.TypeArguments).Take(totalSkipCount))); Debug.Assert(methodSkippedArgs.SequenceEqual(callerMethod.DeclaringType.TypeArguments.Concat(callerMethod.TypeArguments).Take(totalSkipCount)));
Debug.Assert(methodSkippedArgs.All(p => p.Kind == TypeKind.TypeParameter)); Debug.Assert(methodSkippedArgs.All(p => p.Kind == TypeKind.TypeParameter));
Debug.Assert(methodSkippedArgs.Select(p => p.Name).SequenceEqual(method0.DeclaringType.TypeParameters.Concat(method0.TypeParameters).Take(totalSkipCount).Select(p => p.Name))); Debug.Assert(methodSkippedArgs.Select(p => p.Name).SequenceEqual(m.DeclaringType.TypeParameters.Concat(m.TypeParameters).Take(totalSkipCount).Select(p => p.Name)));
} }
#endif #endif
} }
@ -383,19 +413,22 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{ {
HandleUseSite(call.Method, call); HandleUseSite(call.Method, call);
} }
else if (inst is LdFtn ldftn && !ldftn.Method.IsLocalFunction && ldftn.Parent is NewObj newObj && IsLocalFunctionMethod(ldftn.Method, context) && DelegateConstruction.IsDelegateConstruction(newObj)) else if (inst is LdFtn ldftn && !ldftn.Method.IsLocalFunction && IsLocalFunctionMethod(ldftn.Method, context))
{ {
if (ldftn.Parent is NewObj newObj && DelegateConstruction.IsDelegateConstruction(newObj))
HandleUseSite(ldftn.Method, newObj); HandleUseSite(ldftn.Method, newObj);
else
HandleUseSite(ldftn.Method, ldftn);
} }
} }
void HandleUseSite(IMethod targetMethod, CallInstruction inst) void HandleUseSite(IMethod targetMethod, ILInstruction inst)
{ {
if (!localFunctions.TryGetValue((MethodDefinitionHandle)targetMethod.MetadataToken, out var info)) if (!localFunctions.TryGetValue((MethodDefinitionHandle)targetMethod.MetadataToken, out var info))
{ {
context.StepStartGroup($"Read local function '{targetMethod.Name}'", inst); context.StepStartGroup($"Read local function '{targetMethod.Name}'", inst);
info = new LocalFunctionInfo() { info = new LocalFunctionInfo() {
UseSites = new List<CallInstruction>() { inst }, UseSites = new List<ILInstruction>() { inst },
LocalFunctionArguments = new Dictionary<int, List<ILInstruction>>(), LocalFunctionArguments = new Dictionary<int, List<ILInstruction>>(),
Method = (IMethod)targetMethod.MemberDefinition, Method = (IMethod)targetMethod.MemberDefinition,
}; };
@ -618,17 +651,29 @@ namespace ICSharpCode.Decompiler.IL.Transforms
useSite.ReplaceWith(replacement); useSite.ReplaceWith(replacement);
} }
void DetermineCaptureAndDeclarationScope(LocalFunctionInfo info, CallInstruction useSite) void DetermineCaptureAndDeclarationScope(LocalFunctionInfo info, ILInstruction useSite)
{ {
switch (useSite)
{
case CallInstruction call:
int firstArgumentIndex = info.Definition.Method.IsStatic ? 0 : 1; int firstArgumentIndex = info.Definition.Method.IsStatic ? 0 : 1;
for (int i = useSite.Arguments.Count - 1; i >= firstArgumentIndex; i--) for (int i = call.Arguments.Count - 1; i >= firstArgumentIndex; i--)
{ {
if (!DetermineCaptureAndDeclarationScope(info, i - firstArgumentIndex, useSite.Arguments[i])) if (!DetermineCaptureAndDeclarationScope(info, i - firstArgumentIndex, call.Arguments[i]))
break; break;
} }
if (firstArgumentIndex > 0) if (firstArgumentIndex > 0)
{ {
DetermineCaptureAndDeclarationScope(info, -1, useSite.Arguments[0]); DetermineCaptureAndDeclarationScope(info, -1, call.Arguments[0]);
}
break;
case LdFtn _:
// &LocalFunction is only possible, if the local function is declared static,
// this means that the local function can be declared in the top-level scope.
// leave info.DeclarationScope null/unassigned.
break;
default:
throw new NotSupportedException();
} }
} }
@ -638,9 +683,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms
ILVariable closureVar; ILVariable closureVar;
if (parameterIndex >= 0) if (parameterIndex >= 0)
{ {
if (!(parameterIndex < function.Method.Parameters.Count && IsClosureParameter(function.Method.Parameters[parameterIndex], resolveContext))) if (!(parameterIndex < function.Method.Parameters.Count
&& IsClosureParameter(function.Method.Parameters[parameterIndex], resolveContext)))
{
return false; return false;
} }
}
if (!(arg.MatchLdLoc(out closureVar) || arg.MatchLdLoca(out closureVar))) if (!(arg.MatchLdLoc(out closureVar) || arg.MatchLdLoca(out closureVar)))
{ {
closureVar = ResolveAncestorScopeReference(arg); closureVar = ResolveAncestorScopeReference(arg);

Loading…
Cancel
Save