From 0d1b6203df8c08673d9bb1c3f010aa1801aabfcb Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Mon, 11 May 2020 09:35:32 +0200 Subject: [PATCH] #1981: Refactor LocalFunctionDecompiler: Propagate closure parameter arguments, so that all arguments can be stripped from use-sites. --- .../IL/Transforms/DelegateConstruction.cs | 2 +- .../IL/Transforms/LocalFunctionDecompiler.cs | 226 ++++++++++++++---- 2 files changed, 186 insertions(+), 42 deletions(-) diff --git a/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs b/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs index 5d68b4131..0ab86c1f5 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs @@ -169,7 +169,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms var nestedContext = new ILTransformContext(context, function); function.RunTransforms(CSharpDecompiler.GetILTransforms().TakeWhile(t => !(t is DelegateConstruction)).Concat(GetTransforms()), nestedContext); nestedContext.Step("DelegateConstruction (ReplaceDelegateTargetVisitor)", function); - function.AcceptVisitor(new ReplaceDelegateTargetVisitor(target, function.Variables.SingleOrDefault(v => v.Index == -1 && v.Kind == VariableKind.Parameter))); + function.AcceptVisitor(new ReplaceDelegateTargetVisitor(target, function.Variables.SingleOrDefault(VariableKindExtensions.IsThis))); // handle nested lambdas nestedContext.StepStartGroup("DelegateConstruction (nested lambdas)", function); ((IILTransform)this).Run(function, nestedContext); diff --git a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs index 073fe8f8f..b4dabbc70 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs @@ -46,6 +46,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms public List UseSites; public IMethod Method; public ILFunction Definition; + public Dictionary> LocalFunctionArguments; } /// @@ -78,38 +79,46 @@ namespace ICSharpCode.Decompiler.IL.Transforms this.context = context; this.resolveContext = new SimpleTypeResolveContext(function.Method); var localFunctions = new Dictionary(); - var cancellationToken = context.CancellationToken; // Find all local functions declared inside this method, including nested local functions or local functions declared in lambdas. FindUseSites(function, context, localFunctions); - foreach (var (_, info) in localFunctions) { - cancellationToken.ThrowIfCancellationRequested(); + ReplaceReferencesToDisplayClassThis(function, localFunctions.Values, context); + DetermineCaptureAndDeclarationScopes(function, localFunctions.Values, context); + PropagateClosureParameterArguments(function, localFunctions, context); + TransformUseSites(localFunctions.Values, context); + } + + private void ReplaceReferencesToDisplayClassThis(ILFunction function, Dictionary.ValueCollection localFunctions, ILTransformContext context) + { + foreach (var info in localFunctions) { + var localFunction = info.Definition; + if (localFunction.Method.IsStatic) + continue; + var thisVar = localFunction.Variables.SingleOrDefault(VariableKindExtensions.IsThis); + if (thisVar == null) + continue; + var compatibleArgument = FindCompatibleArgument(function, info, info.UseSites.SelectArray(u => u.Arguments[0]), ignoreStructure: true); + Debug.Assert(compatibleArgument != null); + context.Step($"Replace 'this' with {compatibleArgument}", localFunction); + localFunction.AcceptVisitor(new DelegateConstruction.ReplaceDelegateTargetVisitor(compatibleArgument, thisVar)); + DetermineCaptureAndDeclarationScope(function, info, -1, compatibleArgument); + } + } + + private void DetermineCaptureAndDeclarationScopes(ILFunction function, Dictionary.ValueCollection localFunctions, ILTransformContext context) + { + foreach (var info in localFunctions) { + context.CancellationToken.ThrowIfCancellationRequested(); if (info.Definition == null) { - function.Warnings.Add($"Could not decode local function '{info.Method}'"); + context.Function.Warnings.Add($"Could not decode local function '{info.Method}'"); continue; } - context.StepStartGroup($"Transform " + info.Definition.Name, info.Definition); + context.StepStartGroup($"Determine and move to declaration scope of " + info.Definition.Name, info.Definition); try { var localFunction = info.Definition; - if (!localFunction.Method.IsStatic) { - var thisVar = localFunction.Variables.SingleOrDefault(VariableKindExtensions.IsThis); - var target = info.UseSites.Where(us => us.Arguments[0].MatchLdLoc(out _)).FirstOrDefault()?.Arguments[0]; - if (target == null) { - target = info.UseSites[0].Arguments[0]; - if (target.MatchLdFld(out var target1, out var field) && thisVar.Type.Equals(field.Type) && field.Type.Kind == TypeKind.Class && TransformDisplayClassUsage.IsPotentialClosure(context, field.Type.GetDefinition())) { - var variable = function.Descendants.OfType().SelectMany(f => f.Variables).Where(v => !v.IsThis() && TransformDisplayClassUsage.IsClosure(context, v, out var varType, out _) && varType.Equals(field.Type)).OnlyOrDefault(); - if (variable != null) { - target = new LdLoc(variable); - HandleArgument(localFunction, 1, 0, target); - } - } - } - context.Step($"Replace 'this' with {target}", localFunction); - localFunction.AcceptVisitor(new DelegateConstruction.ReplaceDelegateTargetVisitor(target, thisVar)); - } foreach (var useSite in info.UseSites) { - DetermineCaptureAndDeclarationScope(localFunction, useSite); + DetermineCaptureAndDeclarationScope(function, info, useSite); if (function.Method.IsConstructor && localFunction.DeclarationScope == null) { localFunction.DeclarationScope = BlockContainer.FindClosestContainer(useSite); @@ -122,6 +131,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms ILFunction declaringFunction = GetDeclaringFunction(localFunction); if (declaringFunction != function) { + context.Step($"Move {localFunction.Name} from {function.Name} to {declaringFunction.Name}", localFunction); function.LocalFunctions.Remove(localFunction); declaringFunction.LocalFunctions.Add(localFunction); } @@ -132,15 +142,26 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (declaringFunction != function) { declaringFunction.LocalFunctions.Remove(localFunction); } - continue; } + } finally { + context.StepEndGroup(keepIfEmpty: true); + } + } + } + private void TransformUseSites(Dictionary.ValueCollection localFunctions, ILTransformContext context) + { + foreach (var info in localFunctions) { + context.CancellationToken.ThrowIfCancellationRequested(); + if (info.Definition == null) continue; + context.StepStartGroup($"TransformUseSites of " + info.Definition.Name, info.Definition); + try { 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) { - TransformToLocalFunctionReference(localFunction, useSite); + TransformToLocalFunctionReference(info.Definition, useSite); } else { - TransformToLocalFunctionInvocation(localFunction.ReducedMethod, useSite); + TransformToLocalFunctionInvocation(info.Definition.ReducedMethod, useSite); } } } finally { @@ -149,6 +170,106 @@ namespace ICSharpCode.Decompiler.IL.Transforms } } + private void PropagateClosureParameterArguments(ILFunction function, Dictionary localFunctions, ILTransformContext context) + { + foreach (var localFunction in function.Descendants.OfType()) { + if (localFunction.Kind != ILFunctionKind.LocalFunction) + continue; + context.CancellationToken.ThrowIfCancellationRequested(); + var token = (MethodDefinitionHandle)localFunction.Method.MetadataToken; + var info = localFunctions[token]; + + foreach (var useSite in info.UseSites) { + if (useSite is NewObj) { + AddAsArgument(-1, useSite.Arguments[0]); + } else { + int firstArgumentIndex; + if (info.Method.IsStatic) { + firstArgumentIndex = 0; + } else { + firstArgumentIndex = 1; + AddAsArgument(-1, useSite.Arguments[0]); + } + for (int i = useSite.Arguments.Count - 1; i >= firstArgumentIndex; i--) { + AddAsArgument(i - firstArgumentIndex, useSite.Arguments[i]); + } + } + } + + context.StepStartGroup($"PropagateClosureParameterArguments of " + info.Definition.Name, info.Definition); + try { + foreach (var (index, arguments) in info.LocalFunctionArguments) { + var targetVariable = info.Definition.Variables.SingleOrDefault(p => p.Kind == VariableKind.Parameter && p.Index == index); + if (targetVariable == null) + continue; + var compatibleArgument = FindCompatibleArgument(function, info, arguments); + Debug.Assert(compatibleArgument != null); + context.Step($"Replace '{targetVariable}' with '{compatibleArgument}'", info.Definition); + info.Definition.AcceptVisitor(new DelegateConstruction.ReplaceDelegateTargetVisitor(compatibleArgument, targetVariable)); + } + } finally { + context.StepEndGroup(keepIfEmpty: true); + } + + void AddAsArgument(int index, ILInstruction argument) + { + switch (argument) { + case LdLoc _: + case LdLoca _: + case LdFlda _: + case LdObj _: + if (index >= 0 && !IsClosureParameter(info.Method.Parameters[index], resolveContext)) + return; + break; + default: + if (index >= 0 && IsClosureParameter(info.Method.Parameters[index], resolveContext)) + info.Definition.Warnings.Add("Could not transform parameter " + index + ": unsupported argument pattern"); + return; + } + + if (!info.LocalFunctionArguments.TryGetValue(index, out var arguments)) { + arguments = new List(); + info.LocalFunctionArguments.Add(index, arguments); + } + arguments.Add(argument); + } + } + } + + private ILInstruction FindCompatibleArgument(ILFunction function, LocalFunctionInfo info, IEnumerable arguments, bool ignoreStructure = false) + { + foreach (var arg in arguments) { + if (arg is IInstructionWithVariableOperand ld2 && (ignoreStructure || info.Definition.IsDescendantOf(ld2.Variable.Function))) + return arg; + var v = ResolveAncestorScopeReference(function, arg); + if (v != null) + return new LdLoc(v); + } + return null; + } + + private ILVariable ResolveAncestorScopeReference(ILFunction function, ILInstruction inst) + { + if (!inst.MatchLdFld(out var target, out var field)) + return null; + if (field.Type.Kind != TypeKind.Class) + return null; + if (!(TransformDisplayClassUsage.IsPotentialClosure(context, field.Type.GetDefinition()) || function.Method.DeclaringType.Equals(field.Type))) + return null; + foreach (var v in function.Descendants.OfType().SelectMany(f => f.Variables)) { + if (v.Kind != VariableKind.Local && v.Kind != VariableKind.DisplayClassLocal && v.Kind != VariableKind.StackSlot) { + if (!(v.Kind == VariableKind.Parameter && v.Index == -1)) + continue; + if (v.Type.Equals(field.Type)) + return v; + } + if (!(TransformDisplayClassUsage.IsClosure(context, v, out var varType, out _) && varType.Equals(field.Type))) + continue; + return v; + } + return null; + } + private ILFunction GetDeclaringFunction(ILFunction localFunction) { if (localFunction.DeclarationScope == null) @@ -217,6 +338,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms context.StepStartGroup($"Read local function '{targetMethod.Name}'", inst); info = new LocalFunctionInfo() { UseSites = new List() { inst }, + LocalFunctionArguments = new Dictionary>(), Method = (IMethod)targetMethod.MemberDefinition, }; var rootFunction = context.Function; @@ -347,7 +469,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms return inst; } - LocalFunctionMethod ReduceToLocalFunction(IMethod method, int skipCount) + LocalFunctionMethod ReduceToLocalFunction(IMethod method, int typeParametersToRemove) { int parametersToRemove = 0; for (int i = method.Parameters.Count - 1; i >= 0; i--) { @@ -355,7 +477,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms break; parametersToRemove++; } - return new LocalFunctionMethod(method, parametersToRemove, skipCount); + return new LocalFunctionMethod(method, parametersToRemove, typeParametersToRemove); } static void TransformToLocalFunctionReference(ILFunction function, CallInstruction useSite) @@ -396,41 +518,63 @@ namespace ICSharpCode.Decompiler.IL.Transforms useSite.ReplaceWith(replacement); } - void DetermineCaptureAndDeclarationScope(ILFunction function, CallInstruction useSite) + void DetermineCaptureAndDeclarationScope(ILFunction function, LocalFunctionInfo info, CallInstruction useSite) { - int firstArgumentIndex = function.Method.IsStatic ? 0 : 1; + int firstArgumentIndex = info.Definition.Method.IsStatic ? 0 : 1; for (int i = useSite.Arguments.Count - 1; i >= firstArgumentIndex; i--) { - if (!HandleArgument(function, firstArgumentIndex, i, useSite.Arguments[i])) + if (!DetermineCaptureAndDeclarationScope(function, info, i - firstArgumentIndex, useSite.Arguments[i])) break; } if (firstArgumentIndex > 0) { - HandleArgument(function, firstArgumentIndex, 0, useSite.Arguments[0]); + DetermineCaptureAndDeclarationScope(function, info, -1, useSite.Arguments[0]); } } - bool HandleArgument(ILFunction function, int firstArgumentIndex, int i, ILInstruction arg) + bool DetermineCaptureAndDeclarationScope(ILFunction root, LocalFunctionInfo info, int parameterIndex, ILInstruction arg) { + ILFunction function = info.Definition; ILVariable closureVar; - if (!(arg.MatchLdLoc(out closureVar) || arg.MatchLdLoca(out closureVar))) - return false; + if (parameterIndex >= 0) { + if (!(parameterIndex < function.Method.Parameters.Count && IsClosureParameter(function.Method.Parameters[parameterIndex], resolveContext))) + return false; + } + if (!(arg.MatchLdLoc(out closureVar) || arg.MatchLdLoca(out closureVar))) { + closureVar = ResolveAncestorScopeReference(root, arg); + if (closureVar == null) + return false; + } if (closureVar.Kind == VariableKind.NamedArgument) return false; - if (!TransformDisplayClassUsage.IsClosure(context, closureVar, out _, out var initializer)) + var initializer = GetClosureInitializer(closureVar); + if (initializer == null) return false; - if (i - firstArgumentIndex >= 0) { - Debug.Assert(i - firstArgumentIndex < function.Method.Parameters.Count && IsClosureParameter(function.Method.Parameters[i - firstArgumentIndex], resolveContext)); - } // determine the capture scope of closureVar and the declaration scope of the function var additionalScope = BlockContainer.FindClosestContainer(initializer); if (closureVar.CaptureScope == null) closureVar.CaptureScope = additionalScope; - else - closureVar.CaptureScope = FindCommonAncestorInstruction(closureVar.CaptureScope, additionalScope); + else { + BlockContainer combinedScope = FindCommonAncestorInstruction(closureVar.CaptureScope, additionalScope); + Debug.Assert(combinedScope != null); + closureVar.CaptureScope = combinedScope; + } if (function.DeclarationScope == null) function.DeclarationScope = closureVar.CaptureScope; else if (!IsInNestedLocalFunction(function.DeclarationScope, closureVar.CaptureScope.Ancestors.OfType().First())) function.DeclarationScope = FindCommonAncestorInstruction(function.DeclarationScope, closureVar.CaptureScope); return true; + + ILInstruction GetClosureInitializer(ILVariable variable) + { + var type = UnwrapByRef(variable.Type).GetDefinition(); + if (type == null) + return null; + if (variable.Kind == VariableKind.Parameter) + return null; + if (type.Kind == TypeKind.Struct) + return GetStatement(variable.AddressInstructions.OrderBy(i => i.StartILOffset).First()); + else + return (StLoc)variable.StoreInstructions[0]; + } } bool IsInNestedLocalFunction(BlockContainer declarationScope, ILFunction function)