diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs
index 5592a588d..ab2770bc6 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs
@@ -28,136 +28,255 @@ namespace ICSharpCode.Decompiler.IL.Transforms
///
/// Transforms closure fields to local variables.
///
- /// This is a post-processing step of , and .
+ /// This is a post-processing step of ,
+ /// and .
+ ///
+ /// In general we can perform SROA (scalar replacement of aggregates) on any variable that
+ /// satisfies the following conditions:
+ /// 1) It is initialized by an empty/default constructor call.
+ /// 2) The variable is never passed to another method.
+ /// 3) The variable is never the target of an invocation.
+ ///
+ /// Note that 2) and 3) apply because declarations and uses of lambdas and local functions
+ /// are already transformed by the time this transform is applied.
///
class TransformDisplayClassUsage : ILVisitor, IILTransform
{
+ [Flags]
+ enum Kind
+ {
+ DelegateConstruction = 1,
+ LocalFunction = 2,
+ ExpressionTree = 4,
+ MonoStateMachine = 8
+ }
+
+ class VariableToDeclare
+ {
+ private readonly DisplayClass container;
+ private readonly IField field;
+ private ILVariable declaredVariable;
+
+ public string Name => field.Name;
+
+ public List AssignedValues { get; } = new List();
+
+ public VariableToDeclare(DisplayClass container, IField field)
+ {
+ this.container = container;
+ this.field = field;
+ }
+
+ public void UseExisting(ILVariable variable)
+ {
+ Debug.Assert(this.declaredVariable == null);
+ this.declaredVariable = variable;
+ }
+
+ public ILVariable GetOrDeclare()
+ {
+ if (declaredVariable != null)
+ return declaredVariable;
+ declaredVariable = container.Variable.Function.RegisterVariable(VariableKind.Local, field.Type, field.Name);
+ declaredVariable.HasInitialValue = true;
+ declaredVariable.CaptureScope = container.CaptureScope;
+ return declaredVariable;
+ }
+ }
+
class DisplayClass
{
- public bool IsMono;
- public ILInstruction Initializer;
- public ILVariable Variable;
- public ITypeDefinition Definition;
- public Dictionary Variables;
+ public readonly ILVariable Variable;
+ public readonly ITypeDefinition Type;
+ public readonly Dictionary VariablesToDeclare;
public BlockContainer CaptureScope;
- public ILFunction DeclaringFunction;
+ public Kind Kind;
+
+ public DisplayClass(ILVariable variable, ITypeDefinition type)
+ {
+ Variable = variable;
+ Type = type;
+ VariablesToDeclare = new Dictionary();
+ }
}
ILTransformContext context;
+ ITypeResolveContext decompilationContext;
readonly Dictionary displayClasses = new Dictionary();
readonly List instructionsToRemove = new List();
- readonly MultiDictionary fieldAssignmentsWithVariableValue = new MultiDictionary();
- public void Run(ILFunction function, ILTransformContext context)
+ void IILTransform.Run(ILFunction function, ILTransformContext context)
{
+ if (this.context != null)
+ throw new InvalidOperationException("Reentrancy in " + nameof(TransformDisplayClassUsage));
try {
- if (this.context != null)
- throw new InvalidOperationException("Reentrancy in " + nameof(TransformDisplayClassUsage));
this.context = context;
- var decompilationContext = new SimpleTypeResolveContext(context.Function.Method);
- // Traverse nested functions in post-order:
- // Inner functions are transformed before outer functions
- foreach (var f in function.Descendants.OfType()) {
- foreach (var v in f.Variables.ToArray()) {
- if (context.Settings.YieldReturn && HandleMonoStateMachine(function, v, decompilationContext, f))
- continue;
- if ((context.Settings.AnonymousMethods || context.Settings.ExpressionTrees) && IsClosure(context, v, out ITypeDefinition closureType, out var inst)) {
- if (!CanRemoveAllReferencesTo(context, v))
- continue;
- if (inst is StObj || inst is StLoc)
- instructionsToRemove.Add(inst);
- AddOrUpdateDisplayClass(f, v, closureType, inst, localFunctionClosureParameter: false);
- continue;
- }
- if (context.Settings.LocalFunctions && f.Kind == ILFunctionKind.LocalFunction && v.Kind == VariableKind.Parameter && v.Index > -1 && f.Method.Parameters[v.Index.Value] is IParameter p && LocalFunctionDecompiler.IsClosureParameter(p, decompilationContext)) {
- AddOrUpdateDisplayClass(f, v, ((ByReferenceType)p.Type).ElementType.GetDefinition(), f.Body, localFunctionClosureParameter: true);
- continue;
- }
- AnalyzeUseSites(v);
- }
- }
- VisitILFunction(function);
- if (instructionsToRemove.Count > 0) {
- context.Step($"Remove instructions", function);
- foreach (var store in instructionsToRemove) {
- if (store.Parent is Block containingBlock)
- containingBlock.Instructions.Remove(store);
- }
- }
- foreach (var f in TreeTraversal.PostOrder(function, f => f.LocalFunctions))
- RemoveDeadVariableInit.ResetHasInitialValueFlag(f, context);
+ this.decompilationContext = new SimpleTypeResolveContext(context.Function.Method);
+ AnalyzeFunction(function);
+ Transform(function);
} finally {
- instructionsToRemove.Clear();
- displayClasses.Clear();
- fieldAssignmentsWithVariableValue.Clear();
- this.context = null;
+ ClearState();
}
}
- private bool CanRemoveAllReferencesTo(ILTransformContext context, ILVariable v)
+ void ClearState()
{
- foreach (var use in v.LoadInstructions) {
- // we only accept stloc, stobj/ldobj and ld(s)flda instructions,
- // as these are required by all patterns this transform understands.
- if (!(use.Parent is StLoc || use.Parent is LdFlda || use.Parent is LdsFlda || use.Parent is StObj || use.Parent is LdObj)) {
- return false;
- }
- if (use.Parent.MatchStLoc(out var targetVar) && !IsClosure(context, targetVar, out _, out _)) {
- return false;
+ instructionsToRemove.Clear();
+ displayClasses.Clear();
+ this.decompilationContext = null;
+ this.context = null;
+ }
+
+ public void Analyze(ILFunction function, ILTransformContext context)
+ {
+ ClearState();
+ this.context = context;
+ this.decompilationContext = new SimpleTypeResolveContext(context.Function.Method);
+ AnalyzeFunction(function);
+ }
+
+ private void AnalyzeFunction(ILFunction function)
+ {
+ // Traverse nested functions in post-order:
+ // Inner functions are transformed before outer functions
+ foreach (var f in function.Descendants.OfType()) {
+ foreach (var v in f.Variables.ToArray()) {
+ var result = AnalyzeVariable(v);
+ if (result == null)
+ continue;
+ displayClasses.Add(v, result);
}
}
- return true;
}
- private void AnalyzeUseSites(ILVariable v)
+ private DisplayClass AnalyzeVariable(ILVariable v)
{
- foreach (var use in v.LoadInstructions) {
- if (!(use.Parent?.Parent is Block))
- continue;
- if (use.Parent.MatchStFld(out _, out var f, out var value) && value == use) {
- fieldAssignmentsWithVariableValue.Add(f, (StObj)use.Parent);
+ ITypeDefinition definition;
+ switch (v.Kind) {
+ case VariableKind.Parameter:
+ if (context.Settings.YieldReturn && v.Function.StateMachineCompiledWithMono && v.IsThis())
+ return HandleMonoStateMachine(v.Function, v);
+ return null;
+ case VariableKind.StackSlot:
+ case VariableKind.Local:
+ case VariableKind.DisplayClassLocal:
+ if (!AnalyzeInitializer(out definition))
+ return null;
+ return Run(definition);
+ case VariableKind.PinnedLocal:
+ case VariableKind.UsingLocal:
+ case VariableKind.ForeachLocal:
+ case VariableKind.InitializerTarget:
+ case VariableKind.NamedArgument:
+ case VariableKind.ExceptionStackSlot:
+ case VariableKind.ExceptionLocal:
+ return null;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+
+ DisplayClass Run(ITypeDefinition type)
+ {
+ var result = new DisplayClass(v, type) {
+ CaptureScope = v.CaptureScope
+ };
+ foreach (var ldloc in v.LoadInstructions) {
+ if (!ValidateUse(result, ldloc))
+ return null;
+ }
+ foreach (var ldloca in v.AddressInstructions) {
+ if (!ValidateUse(result, ldloca))
+ return null;
}
- if (use.Parent.MatchStsFld(out f, out value) && value == use) {
- fieldAssignmentsWithVariableValue.Add(f, (StObj)use.Parent);
+ if (result.VariablesToDeclare.Count == 0)
+ return null;
+ if (IsMonoNestedCaptureScope(type)) {
+ result.CaptureScope = null;
}
+ return result;
}
- foreach (var use in v.AddressInstructions) {
- if (!(use.Parent?.Parent is Block))
- continue;
- if (use.Parent.MatchStFld(out _, out var f, out var value) && value == use) {
- fieldAssignmentsWithVariableValue.Add(f, (StObj)use.Parent);
+
+ bool ValidateUse(DisplayClass result, ILInstruction use)
+ {
+ var current = use.Parent;
+ ILInstruction value;
+ if (current.MatchLdFlda(out _, out IField field)) {
+ var keyField = (IField)field.MemberDefinition;
+ result.VariablesToDeclare.TryGetValue(keyField, out VariableToDeclare variable);
+ if (current.Parent.MatchStObj(out _, out value, out var type)) {
+ if (variable == null) {
+ variable = new VariableToDeclare(result, field);
+ }
+ variable.AssignedValues.Add(value);
+ }
+
+ var containingFunction = current.Ancestors.OfType().FirstOrDefault();
+ if (containingFunction != null && containingFunction != v.Function) {
+ switch (containingFunction.Kind) {
+ case ILFunctionKind.Delegate:
+ result.Kind |= Kind.DelegateConstruction;
+ break;
+ case ILFunctionKind.ExpressionTree:
+ result.Kind |= Kind.ExpressionTree;
+ break;
+ case ILFunctionKind.LocalFunction:
+ result.Kind |= Kind.LocalFunction;
+ break;
+ }
+ }
+ result.VariablesToDeclare[keyField] = variable;
+ return true;
}
- if (use.Parent.MatchStsFld(out f, out value) && value == use) {
- fieldAssignmentsWithVariableValue.Add(f, (StObj)use.Parent);
+ if (current.MatchStObj(out _, out value, out _) && value == use)
+ return true;
+ return false;
+ }
+
+ bool AnalyzeInitializer(out ITypeDefinition t)
+ {
+ if (v.Kind != VariableKind.StackSlot) {
+ t = v.Type.GetDefinition();
+ } else if (v.StoreInstructions.Count > 0 && v.StoreInstructions[0] is StLoc stloc && stloc.Value is NewObj newObj) {
+ t = newObj.Method.DeclaringTypeDefinition;
+ } else {
+ t = null;
}
+ if (t?.DeclaringTypeDefinition == null || t.ParentModule.PEFile != context.PEFile)
+ return false;
+ switch (definition.Kind) {
+ case TypeKind.Class:
+ if (!v.IsSingleDefinition)
+ return false;
+ if (!(v.StoreInstructions[0] is StLoc stloc))
+ return false;
+ if (!(stloc.Value is NewObj newObj && newObj.Method.Parameters.Count == 0))
+ return false;
+ break;
+ case TypeKind.Struct:
+ if (!v.HasInitialValue)
+ return false;
+ if (v.StoreCount != 1)
+ return false;
+ break;
+ }
+
+ return true;
}
}
- private void AddOrUpdateDisplayClass(ILFunction f, ILVariable v, ITypeDefinition closureType, ILInstruction inst, bool localFunctionClosureParameter)
+ private void Transform(ILFunction function)
{
- var displayClass = displayClasses.Values.FirstOrDefault(c => c.Definition == closureType);
- // TODO : figure out whether it is a mono compiled closure, without relying on the type name
- bool isMono = f.StateMachineCompiledWithMono || closureType.Name.Contains("AnonStorey");
- if (displayClass == null) {
- displayClasses.Add(v, new DisplayClass {
- IsMono = isMono,
- Initializer = inst,
- Variable = v,
- Definition = closureType,
- Variables = new Dictionary(),
- CaptureScope = (isMono && IsMonoNestedCaptureScope(closureType)) || localFunctionClosureParameter ? null : v.CaptureScope,
- DeclaringFunction = localFunctionClosureParameter ? f.DeclarationScope.Ancestors.OfType().First() : f
- });
- } else {
- if (displayClass.CaptureScope == null && !localFunctionClosureParameter)
- displayClass.CaptureScope = isMono && IsMonoNestedCaptureScope(closureType) ? null : v.CaptureScope;
- if (displayClass.CaptureScope != null && !localFunctionClosureParameter) {
- displayClass.DeclaringFunction = displayClass.CaptureScope.Ancestors.OfType().First();
+ VisitILFunction(function);
+ if (instructionsToRemove.Count > 0) {
+ context.Step($"Remove instructions", function);
+ foreach (var store in instructionsToRemove) {
+ if (store.Parent is Block containingBlock)
+ containingBlock.Instructions.Remove(store);
}
- displayClass.Variable = v;
- displayClass.Initializer = inst;
- displayClasses.Add(v, displayClass);
}
+ context.Step($"ResetHasInitialValueFlag", function);
+ foreach (var f in TreeTraversal.PostOrder(function, f => f.LocalFunctions))
+ RemoveDeadVariableInit.ResetHasInitialValueFlag(f, context);
}
internal static bool IsClosure(ILTransformContext context, ILVariable variable, out ITypeDefinition closureType, out ILInstruction initializer)
@@ -190,6 +309,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
bool IsMonoNestedCaptureScope(ITypeDefinition closureType)
{
+ if (!closureType.Name.Contains("AnonStorey"))
+ return false;
var decompilationContext = new SimpleTypeResolveContext(context.Function.Method);
return closureType.Fields.Any(f => IsPotentialClosure(decompilationContext.CurrentTypeDefinition, f.ReturnType.GetDefinition()));
}
@@ -198,45 +319,42 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// mcs likes to optimize closures in yield state machines away by moving the captured variables' fields into the state machine type,
/// We construct a that spans the whole method body.
///
- bool HandleMonoStateMachine(ILFunction currentFunction, ILVariable thisVariable, SimpleTypeResolveContext decompilationContext, ILFunction nestedFunction)
+ DisplayClass HandleMonoStateMachine(ILFunction function, ILVariable thisVariable)
{
- if (!(nestedFunction.StateMachineCompiledWithMono && thisVariable.IsThis()))
- return false;
+ if (!(function.StateMachineCompiledWithMono && thisVariable.IsThis()))
+ return null;
// Special case for Mono-compiled yield state machines
ITypeDefinition closureType = thisVariable.Type.GetDefinition();
if (!(closureType != decompilationContext.CurrentTypeDefinition
&& IsPotentialClosure(decompilationContext.CurrentTypeDefinition, closureType, allowTypeImplementingInterfaces: true)))
- return false;
+ return null;
- var displayClass = new DisplayClass {
- IsMono = true,
- Initializer = nestedFunction.Body,
- Variable = thisVariable,
- Definition = thisVariable.Type.GetDefinition(),
- Variables = new Dictionary(),
- CaptureScope = (BlockContainer)nestedFunction.Body
- };
- displayClasses.Add(thisVariable, displayClass);
- foreach (var stateMachineVariable in nestedFunction.Variables) {
- if (stateMachineVariable.StateMachineField == null || displayClass.Variables.ContainsKey(stateMachineVariable.StateMachineField))
+ var displayClass = new DisplayClass(thisVariable, thisVariable.Type.GetDefinition());
+ displayClass.CaptureScope = (BlockContainer)function.Body;
+ foreach (var stateMachineVariable in function.Variables) {
+ if (stateMachineVariable.StateMachineField == null || displayClass.VariablesToDeclare.ContainsKey(stateMachineVariable.StateMachineField))
continue;
- displayClass.Variables.Add(stateMachineVariable.StateMachineField, stateMachineVariable);
+ VariableToDeclare variableToDeclare = new VariableToDeclare(displayClass, stateMachineVariable.StateMachineField);
+ variableToDeclare.UseExisting(stateMachineVariable);
+ displayClass.VariablesToDeclare.Add(stateMachineVariable.StateMachineField, variableToDeclare);
}
- if (!currentFunction.Method.IsStatic && FindThisField(out var thisField)) {
- var thisVar = currentFunction.Variables
+ if (!function.Method.IsStatic && FindThisField(out var thisField)) {
+ var thisVar = function.Variables
.FirstOrDefault(t => t.IsThis() && t.Type.GetDefinition() == decompilationContext.CurrentTypeDefinition);
if (thisVar == null) {
thisVar = new ILVariable(VariableKind.Parameter, decompilationContext.CurrentTypeDefinition, -1) { Name = "this" };
- currentFunction.Variables.Add(thisVar);
+ function.Variables.Add(thisVar);
}
- displayClass.Variables.Add(thisField, thisVar);
+ VariableToDeclare variableToDeclare = new VariableToDeclare(displayClass, thisField);
+ variableToDeclare.UseExisting(thisVar);
+ displayClass.VariablesToDeclare.Add(thisField, variableToDeclare);
}
- return true;
+ return displayClass;
bool FindThisField(out IField foundField)
{
foundField = null;
- foreach (var field in closureType.GetFields(f2 => !f2.IsStatic && !displayClass.Variables.ContainsKey(f2) && f2.Type.GetDefinition() == decompilationContext.CurrentTypeDefinition)) {
+ foreach (var field in closureType.GetFields(f2 => !f2.IsStatic && !displayClass.VariablesToDeclare.ContainsKey(f2) && f2.Type.GetDefinition() == decompilationContext.CurrentTypeDefinition)) {
thisField = field;
return true;
}
@@ -317,11 +435,15 @@ namespace ICSharpCode.Decompiler.IL.Transforms
base.VisitStLoc(inst);
if (inst.Parent is Block && inst.Variable.IsSingleDefinition) {
- if (inst.Variable.Kind == VariableKind.Local && inst.Variable.LoadCount == 0 && inst.Value is StLoc) {
+ if ((inst.Variable.Kind == VariableKind.Local || inst.Variable.Kind == VariableKind.StackSlot) && inst.Variable.LoadCount == 0 && (inst.Value is StLoc || inst.Value is CompoundAssignmentInstruction)) {
context.Step($"Remove unused variable assignment {inst.Variable.Name}", inst);
inst.ReplaceWith(inst.Value);
return;
}
+ if (displayClasses.TryGetValue(inst.Variable, out _) && inst.Value is NewObj) {
+ instructionsToRemove.Add(inst);
+ return;
+ }
if (inst.Value.MatchLdLoc(out var displayClassVariable) && displayClasses.TryGetValue(displayClassVariable, out var displayClass)) {
context.Step($"Found copy-assignment of display-class variable {displayClassVariable.Name}", inst);
displayClasses.Add(inst.Variable, displayClass);
@@ -335,15 +457,15 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{
inst.Value.AcceptVisitor(this);
if (inst.Parent is Block) {
- if (IsParameterAssignment(inst, out var displayClass, out var field, out var parameter)) {
+ if (IsParameterAssignment(inst, out var declarationSlot, out var parameter)) {
context.Step($"Detected parameter assignment {parameter.Name}", inst);
- displayClass.Variables.Add((IField)field.MemberDefinition, parameter);
+ declarationSlot.UseExisting(parameter);
instructionsToRemove.Add(inst);
return;
}
- if (IsDisplayClassAssignment(inst, out displayClass, out field, out var variable)) {
+ if (IsDisplayClassAssignment(inst, out declarationSlot, out var variable)) {
context.Step($"Detected display-class assignment {variable.Name}", inst);
- displayClass.Variables.Add((IField)field.MemberDefinition, variable);
+ declarationSlot.UseExisting(variable);
instructionsToRemove.Add(inst);
return;
}
@@ -366,32 +488,39 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false;
}
- private bool IsDisplayClassAssignment(StObj inst, out DisplayClass displayClass, out IField field, out ILVariable variable)
+ private bool IsDisplayClassAssignment(StObj inst, out VariableToDeclare declarationSlot, out ILVariable variable)
{
variable = null;
- if (!IsDisplayClassFieldAccess(inst.Target, out var displayClassVar, out displayClass, out field))
+ declarationSlot = null;
+ if (!IsDisplayClassFieldAccess(inst.Target, out var displayClassVar, out var displayClass, out var field))
return false;
if (!(inst.Value.MatchLdLoc(out var v) && displayClasses.ContainsKey(v)))
return false;
+ if (!displayClass.VariablesToDeclare.TryGetValue((IField)field.MemberDefinition, out declarationSlot))
+ return false;
if (displayClassVar.Function != currentFunction)
return false;
variable = v;
return true;
}
- private bool IsParameterAssignment(StObj inst, out DisplayClass displayClass, out IField field, out ILVariable parameter)
+ private bool IsParameterAssignment(StObj inst, out VariableToDeclare declarationSlot, out ILVariable parameter)
{
parameter = null;
- if (!IsDisplayClassFieldAccess(inst.Target, out var displayClassVar, out displayClass, out field))
- return false;
- if (fieldAssignmentsWithVariableValue[field].Count != 1)
+ declarationSlot = null;
+ if (!IsDisplayClassFieldAccess(inst.Target, out var displayClassVar, out var displayClass, out var field))
return false;
if (!(inst.Value.MatchLdLoc(out var v) && v.Kind == VariableKind.Parameter && v.Function == currentFunction && v.Type.Equals(field.Type)))
return false;
- if (displayClass.Variables.ContainsKey((IField)field.MemberDefinition))
+ if (!displayClass.VariablesToDeclare.TryGetValue((IField)field.MemberDefinition, out declarationSlot))
return false;
if (displayClassVar.Function != currentFunction)
return false;
+ if (declarationSlot.AssignedValues.Count > 1) {
+ var first = declarationSlot.AssignedValues[0].Ancestors.OfType().First();
+ if (declarationSlot.AssignedValues.All(i => i.Ancestors.OfType().First() == first))
+ return false;
+ }
parameter = v;
return true;
}
@@ -414,24 +543,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// Get display class info
if (!IsDisplayClassFieldAccess(inst, out _, out DisplayClass displayClass, out IField field))
return;
- // We want the specialized version, so that display-class type parameters are
- // substituted with the type parameters from the use-site.
- var fieldType = field.Type;
- // However, use the unspecialized member definition to make reference comparisons in dictionary possible.
- field = (IField)field.MemberDefinition;
- if (!displayClass.Variables.TryGetValue(field, out var v)) {
- context.Step($"Introduce captured variable for {field.FullName}", inst);
- // Introduce a fresh variable for the display class field.
- Debug.Assert(displayClass.Definition == field.DeclaringTypeDefinition);
- v = displayClass.DeclaringFunction.RegisterVariable(VariableKind.Local, fieldType, field.Name);
- v.HasInitialValue = true;
- v.CaptureScope = displayClass.CaptureScope;
- inst.ReplaceWith(new LdLoca(v).WithILRange(inst));
- displayClass.Variables.Add(field, v);
- } else {
- context.Step($"Reuse captured variable {v.Name} for {field.FullName}", inst);
- inst.ReplaceWith(new LdLoca(v).WithILRange(inst));
+ var keyField = (IField)field.MemberDefinition;
+ if (!displayClass.VariablesToDeclare.TryGetValue(keyField, out var v) || v == null) {
+ displayClass.VariablesToDeclare[keyField] = v = new VariableToDeclare(displayClass, field);
}
+ context.Step($"Replace {field.FullName} with captured variable {v.Name}", inst);
+ inst.ReplaceWith(new LdLoca(v.GetOrDeclare()).WithILRange(inst));
}
}
}