Browse Source

#1981: Refactor LocalFunctionDecompiler: Propagate closure parameter arguments, so that all arguments can be stripped from use-sites.

pull/2005/head
Siegfried Pammer 5 years ago
parent
commit
0d1b6203df
  1. 2
      ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs
  2. 226
      ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs

2
ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs

@ -169,7 +169,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -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);

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

@ -46,6 +46,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -46,6 +46,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
public List<CallInstruction> UseSites;
public IMethod Method;
public ILFunction Definition;
public Dictionary<int, List<ILInstruction>> LocalFunctionArguments;
}
/// <summary>
@ -78,38 +79,46 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -78,38 +79,46 @@ namespace ICSharpCode.Decompiler.IL.Transforms
this.context = context;
this.resolveContext = new SimpleTypeResolveContext(function.Method);
var localFunctions = new Dictionary<MethodDefinitionHandle, LocalFunctionInfo>();
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<MethodDefinitionHandle, LocalFunctionInfo>.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<MethodDefinitionHandle, LocalFunctionInfo>.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<ILFunction>().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 @@ -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 @@ -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<MethodDefinitionHandle, LocalFunctionInfo>.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 @@ -149,6 +170,106 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
}
private void PropagateClosureParameterArguments(ILFunction function, Dictionary<MethodDefinitionHandle, LocalFunctionInfo> localFunctions, ILTransformContext context)
{
foreach (var localFunction in function.Descendants.OfType<ILFunction>()) {
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<ILInstruction>();
info.LocalFunctionArguments.Add(index, arguments);
}
arguments.Add(argument);
}
}
}
private ILInstruction FindCompatibleArgument(ILFunction function, LocalFunctionInfo info, IEnumerable<ILInstruction> 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<ILFunction>().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 @@ -217,6 +338,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
context.StepStartGroup($"Read local function '{targetMethod.Name}'", inst);
info = new LocalFunctionInfo() {
UseSites = new List<CallInstruction>() { inst },
LocalFunctionArguments = new Dictionary<int, List<ILInstruction>>(),
Method = (IMethod)targetMethod.MemberDefinition,
};
var rootFunction = context.Function;
@ -347,7 +469,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -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 @@ -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 @@ -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<BlockContainer>(closureVar.CaptureScope, additionalScope);
else {
BlockContainer combinedScope = FindCommonAncestorInstruction<BlockContainer>(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<ILFunction>().First()))
function.DeclarationScope = FindCommonAncestorInstruction<BlockContainer>(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)

Loading…
Cancel
Save