mirror of https://github.com/icsharpcode/ILSpy.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
886 lines
30 KiB
886 lines
30 KiB
// Copyright (c) 2019 Siegfried Pammer |
|
// |
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of this |
|
// software and associated documentation files (the "Software"), to deal in the Software |
|
// without restriction, including without limitation the rights to use, copy, modify, merge, |
|
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons |
|
// to whom the Software is furnished to do so, subject to the following conditions: |
|
// |
|
// The above copyright notice and this permission notice shall be included in all copies or |
|
// substantial portions of the Software. |
|
// |
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, |
|
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR |
|
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE |
|
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR |
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
|
// DEALINGS IN THE SOFTWARE. |
|
|
|
using System; |
|
using System.Collections.Generic; |
|
using System.Diagnostics; |
|
using System.Linq; |
|
using System.Reflection.Metadata; |
|
|
|
using ICSharpCode.Decompiler.Disassembler; |
|
using ICSharpCode.Decompiler.IL.ControlFlow; |
|
using ICSharpCode.Decompiler.Metadata; |
|
using ICSharpCode.Decompiler.TypeSystem; |
|
using ICSharpCode.Decompiler.Util; |
|
|
|
namespace ICSharpCode.Decompiler.IL.Transforms |
|
{ |
|
/// <summary> |
|
/// Transforms closure fields to local variables. |
|
/// |
|
/// This is a post-processing step of <see cref="DelegateConstruction"/>, <see cref="LocalFunctionDecompiler"/> |
|
/// and <see cref="TransformExpressionTrees"/>. |
|
/// |
|
/// 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. |
|
/// </summary> |
|
public class TransformDisplayClassUsage : ILVisitor, IILTransform |
|
{ |
|
class VariableToDeclare |
|
{ |
|
private readonly DisplayClass container; |
|
private readonly IField field; |
|
private ILVariable declaredVariable; |
|
|
|
public string Name => field.Name; |
|
|
|
public bool CanPropagate { get; private set; } |
|
public bool UsesInitialValue { get; set; } |
|
|
|
public HashSet<ILInstruction> Initializers { get; } = new HashSet<ILInstruction>(); |
|
|
|
public VariableToDeclare(DisplayClass container, IField field, ILVariable declaredVariable = null) |
|
{ |
|
this.container = container; |
|
this.field = field; |
|
this.declaredVariable = declaredVariable; |
|
|
|
Debug.Assert(declaredVariable == null || declaredVariable.StateMachineField == field); |
|
} |
|
|
|
public void Propagate(ILVariable variable) |
|
{ |
|
this.declaredVariable = variable; |
|
this.CanPropagate = variable != null; |
|
} |
|
|
|
public ILVariable GetOrDeclare() |
|
{ |
|
if (declaredVariable != null) |
|
return declaredVariable; |
|
declaredVariable = container.Variable.Function.RegisterVariable(VariableKind.Local, field.Type, field.Name); |
|
declaredVariable.InitialValueIsInitialized = true; |
|
declaredVariable.UsesInitialValue = UsesInitialValue; |
|
declaredVariable.CaptureScope = container.CaptureScope; |
|
return declaredVariable; |
|
} |
|
} |
|
|
|
[DebuggerDisplay("[DisplayClass {Variable} : {Type}]")] |
|
class DisplayClass |
|
{ |
|
public readonly ILVariable Variable; |
|
public readonly ITypeDefinition Type; |
|
public readonly Dictionary<IField, VariableToDeclare> VariablesToDeclare; |
|
public BlockContainer CaptureScope; |
|
public ILInstruction Initializer; |
|
|
|
public DisplayClass(ILVariable variable, ITypeDefinition type) |
|
{ |
|
Variable = variable; |
|
Type = type; |
|
VariablesToDeclare = new Dictionary<IField, VariableToDeclare>(); |
|
} |
|
} |
|
|
|
ILTransformContext context; |
|
ITypeResolveContext decompilationContext; |
|
readonly Dictionary<ILVariable, DisplayClass> displayClasses = new Dictionary<ILVariable, DisplayClass>(); |
|
readonly Dictionary<ILVariable, ILVariable> displayClassCopyMap = new Dictionary<ILVariable, ILVariable>(); |
|
|
|
void IILTransform.Run(ILFunction function, ILTransformContext context) |
|
{ |
|
if (this.context != null) |
|
throw new InvalidOperationException("Reentrancy in " + nameof(TransformDisplayClassUsage)); |
|
try |
|
{ |
|
this.context = context; |
|
this.decompilationContext = new SimpleTypeResolveContext(context.Function.Method); |
|
AnalyzeFunction(function); |
|
Transform(function); |
|
} |
|
finally |
|
{ |
|
ClearState(); |
|
} |
|
} |
|
|
|
void ClearState() |
|
{ |
|
displayClasses.Clear(); |
|
displayClassCopyMap.Clear(); |
|
this.decompilationContext = null; |
|
this.context = null; |
|
} |
|
|
|
void AnalyzeFunction(ILFunction function) |
|
{ |
|
void VisitFunction(ILFunction f) |
|
{ |
|
foreach (var v in f.Variables.ToArray()) |
|
{ |
|
var result = AnalyzeVariable(v); |
|
if (result == null || displayClasses.ContainsKey(result.Variable)) |
|
continue; |
|
context.Step($"Detected display-class {result.Variable}", result.Initializer ?? f.Body); |
|
displayClasses.Add(result.Variable, result); |
|
} |
|
} |
|
|
|
void VisitChildren(ILInstruction inst) |
|
{ |
|
foreach (var child in inst.Children) |
|
{ |
|
Visit(child); |
|
} |
|
} |
|
|
|
void Visit(ILInstruction inst) |
|
{ |
|
switch (inst) |
|
{ |
|
case ILFunction f: |
|
VisitFunction(f); |
|
VisitChildren(inst); |
|
break; |
|
default: |
|
VisitChildren(inst); |
|
break; |
|
} |
|
} |
|
|
|
Visit(function); |
|
|
|
foreach (var (v, displayClass) in displayClasses.ToArray()) |
|
{ |
|
if (!ValidateDisplayClassUses(v, displayClass)) |
|
displayClasses.Remove(v); |
|
} |
|
|
|
foreach (var displayClass in displayClasses.Values) |
|
{ |
|
// handle uninitialized fields |
|
foreach (var f in displayClass.Type.Fields) |
|
{ |
|
if (displayClass.VariablesToDeclare.ContainsKey(f)) |
|
continue; |
|
var variable = AddVariable(displayClass, null, f); |
|
variable.UsesInitialValue = true; |
|
displayClass.VariablesToDeclare[(IField)f.MemberDefinition] = variable; |
|
} |
|
|
|
foreach (var v in displayClass.VariablesToDeclare.Values) |
|
{ |
|
if (v.CanPropagate) |
|
{ |
|
var variableToPropagate = v.GetOrDeclare(); |
|
if (variableToPropagate.Kind != VariableKind.Parameter && !displayClasses.ContainsKey(variableToPropagate)) |
|
v.Propagate(null); |
|
} |
|
} |
|
} |
|
} |
|
|
|
bool ValidateDisplayClassUses(ILVariable v, DisplayClass displayClass) |
|
{ |
|
foreach (var ldloc in v.LoadInstructions) |
|
{ |
|
if (!ValidateUse(displayClass, ldloc)) |
|
return false; |
|
} |
|
foreach (var ldloca in v.AddressInstructions) |
|
{ |
|
if (!ValidateUse(displayClass, ldloca)) |
|
return false; |
|
} |
|
return true; |
|
|
|
bool ValidateUse(DisplayClass container, ILInstruction use) |
|
{ |
|
IField field; |
|
switch (use.Parent) |
|
{ |
|
case LdFlda ldflda when ldflda.MatchLdFlda(out var target, out field) && target == use: |
|
var keyField = (IField)field.MemberDefinition; |
|
if (!container.VariablesToDeclare.TryGetValue(keyField, out VariableToDeclare variable) || variable == null) |
|
{ |
|
variable = AddVariable(container, null, field); |
|
} |
|
container.VariablesToDeclare[keyField] = variable; |
|
return true; |
|
case StObj stobj when stobj.MatchStObj(out var target, out ILInstruction value, out _) && value == use: |
|
if (target.MatchLdFlda(out var load, out field) && load.MatchLdLocRef(out var otherVariable) && displayClasses.TryGetValue(otherVariable, out var otherDisplayClass)) |
|
{ |
|
if (otherDisplayClass.VariablesToDeclare.TryGetValue((IField)field.MemberDefinition, out var declaredVar)) |
|
return declaredVar.CanPropagate; |
|
} |
|
return false; |
|
case StLoc stloc when stloc.Variable.IsSingleDefinition && stloc.Value == use: |
|
displayClassCopyMap[stloc.Variable] = v; |
|
return ValidateDisplayClassUses(stloc.Variable, displayClass); |
|
default: |
|
return false; |
|
} |
|
} |
|
} |
|
|
|
private DisplayClass AnalyzeVariable(ILVariable v) |
|
{ |
|
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: |
|
return DetectDisplayClass(v); |
|
case VariableKind.InitializerTarget: |
|
return DetectDisplayClassInitializer(v); |
|
default: |
|
return null; |
|
} |
|
} |
|
|
|
DisplayClass DetectDisplayClass(ILVariable v) |
|
{ |
|
ITypeDefinition definition; |
|
if (v.Kind != VariableKind.StackSlot) |
|
{ |
|
definition = v.Type.GetDefinition(); |
|
} |
|
else if (v.StoreInstructions.Count > 0 && v.StoreInstructions[0] is StLoc stloc) |
|
{ |
|
definition = stloc.Value.InferType(context.TypeSystem).GetDefinition(); |
|
} |
|
else |
|
{ |
|
definition = null; |
|
} |
|
if (!ValidateDisplayClassDefinition(definition)) |
|
return null; |
|
DisplayClass result; |
|
switch (definition.Kind) |
|
{ |
|
case TypeKind.Class: |
|
if (!v.IsSingleDefinition) |
|
return null; |
|
if (!(v.StoreInstructions.SingleOrDefault() is StLoc stloc)) |
|
return null; |
|
if (stloc.Value is NewObj newObj && ValidateConstructor(newObj.Method)) |
|
{ |
|
result = new DisplayClass(v, definition) { |
|
CaptureScope = v.CaptureScope, |
|
Initializer = stloc |
|
}; |
|
} |
|
else |
|
{ |
|
return null; |
|
} |
|
|
|
HandleInitBlock(stloc.Parent as Block, stloc.ChildIndex + 1, result, result.Variable); |
|
break; |
|
case TypeKind.Struct: |
|
if (v.StoreInstructions.Count != 0) |
|
return null; |
|
Debug.Assert(v.StoreInstructions.Count == 0); |
|
result = new DisplayClass(v, definition) { CaptureScope = v.CaptureScope }; |
|
HandleInitBlock(FindDisplayStructInitBlock(v), 0, result, result.Variable); |
|
break; |
|
default: |
|
return null; |
|
} |
|
|
|
if (IsMonoNestedCaptureScope(definition)) |
|
{ |
|
result.CaptureScope = null; |
|
} |
|
return result; |
|
} |
|
|
|
void HandleInitBlock(Block initBlock, int startIndex, DisplayClass result, ILVariable targetVariable) |
|
{ |
|
if (initBlock == null) |
|
return; |
|
for (int i = startIndex; i < initBlock.Instructions.Count; i++) |
|
{ |
|
var init = initBlock.Instructions[i]; |
|
if (!init.MatchStFld(out var target, out var field, out _)) |
|
break; |
|
if (!target.MatchLdLocRef(targetVariable)) |
|
break; |
|
if (result.VariablesToDeclare.ContainsKey((IField)field.MemberDefinition)) |
|
break; |
|
var variable = AddVariable(result, (StObj)init, field); |
|
result.VariablesToDeclare[(IField)field.MemberDefinition] = variable; |
|
} |
|
} |
|
|
|
private Block FindDisplayStructInitBlock(ILVariable v) |
|
{ |
|
var root = v.Function.Body; |
|
return Visit(root)?.Ancestors.OfType<Block>().FirstOrDefault(); |
|
|
|
// Try to find a common ancestor of all uses of the variable v. |
|
ILInstruction Visit(ILInstruction inst) |
|
{ |
|
switch (inst) |
|
{ |
|
case LdLoc l when l.Variable == v: |
|
return l; |
|
case StLoc s when s.Variable == v: |
|
return s; |
|
case LdLoca la when la.Variable == v: |
|
return la; |
|
default: |
|
return VisitChildren(inst); |
|
} |
|
} |
|
|
|
ILInstruction VisitChildren(ILInstruction inst) |
|
{ |
|
// Visit all children of the instruction |
|
ILInstruction result = null; |
|
foreach (var child in inst.Children) |
|
{ |
|
var newResult = Visit(child); |
|
// As soon as there is a second use of v in this sub-tree, |
|
// we can skip all other children and just return this node. |
|
if (result == null) |
|
{ |
|
result = newResult; |
|
} |
|
else if (newResult != null) |
|
{ |
|
return inst; |
|
} |
|
} |
|
// returns null, if v is not used in this sub-tree; |
|
// returns a descendant of inst, if it is the only use of v in this sub-tree. |
|
return result; |
|
} |
|
} |
|
|
|
DisplayClass DetectDisplayClassInitializer(ILVariable v) |
|
{ |
|
if (v.StoreInstructions.Count != 1 || !(v.StoreInstructions[0] is StLoc store && store.Parent is Block initializerBlock && initializerBlock.Kind == BlockKind.ObjectInitializer)) |
|
return null; |
|
if (!(store.Value is NewObj newObj)) |
|
return null; |
|
var definition = newObj.Method.DeclaringType.GetDefinition(); |
|
if (!ValidateDisplayClassDefinition(definition)) |
|
return null; |
|
if (!ValidateConstructor(newObj.Method)) |
|
return null; |
|
if (!initializerBlock.Parent.MatchStLoc(out var referenceVariable)) |
|
return null; |
|
if (!referenceVariable.IsSingleDefinition) |
|
return null; |
|
if (!(referenceVariable.StoreInstructions.SingleOrDefault() is StLoc)) |
|
return null; |
|
var result = new DisplayClass(referenceVariable, definition) { |
|
CaptureScope = referenceVariable.CaptureScope, |
|
Initializer = initializerBlock.Parent |
|
}; |
|
HandleInitBlock(initializerBlock, 1, result, v); |
|
return result; |
|
} |
|
|
|
private bool ValidateDisplayClassDefinition(ITypeDefinition definition) |
|
{ |
|
if (definition == null) |
|
return false; |
|
if (definition.ParentModule.PEFile != context.PEFile) |
|
return false; |
|
// We do not want to accidentially transform state-machines and thus destroy them. |
|
var token = (TypeDefinitionHandle)definition.MetadataToken; |
|
var metadata = definition.ParentModule.PEFile.Metadata; |
|
if (YieldReturnDecompiler.IsCompilerGeneratorEnumerator(token, metadata)) |
|
return false; |
|
if (AsyncAwaitDecompiler.IsCompilerGeneratedStateMachine(token, metadata)) |
|
return false; |
|
if (!context.Settings.AggressiveScalarReplacementOfAggregates) |
|
{ |
|
if (definition.DeclaringTypeDefinition == null) |
|
return false; |
|
if (!IsPotentialClosure(context, definition)) |
|
return false; |
|
} |
|
return true; |
|
} |
|
|
|
private bool ValidateConstructor(IMethod method) |
|
{ |
|
try |
|
{ |
|
if (method.Parameters.Count != 0) |
|
return false; |
|
var handle = (MethodDefinitionHandle)method.MetadataToken; |
|
var module = (MetadataModule)method.ParentModule; |
|
var file = module.PEFile; |
|
if (handle.IsNil || file != context.PEFile) |
|
return false; |
|
var def = file.Metadata.GetMethodDefinition(handle); |
|
if (def.RelativeVirtualAddress == 0) |
|
return false; |
|
var body = file.Reader.GetMethodBody(def.RelativeVirtualAddress); |
|
// some compilers produce ctors with unused local variables |
|
// see https://github.com/icsharpcode/ILSpy/issues/2174 |
|
//if (!body.LocalSignature.IsNil) |
|
// return false; |
|
if (body.ExceptionRegions.Length != 0) |
|
return false; |
|
var reader = body.GetILReader(); |
|
if (reader.Length < 7) |
|
return false; |
|
// IL_0000: ldarg.0 |
|
// IL_0001: call instance void [mscorlib]System.Object::.ctor() |
|
// IL_0006: ret |
|
var opCode = DecodeOpCodeSkipNop(ref reader); |
|
switch (opCode) |
|
{ |
|
case ILOpCode.Ldarg: |
|
case ILOpCode.Ldarg_s: |
|
if (reader.DecodeIndex(opCode) != 0) |
|
return false; |
|
break; |
|
case ILOpCode.Ldarg_0: |
|
// OK |
|
break; |
|
default: |
|
return false; |
|
} |
|
if (DecodeOpCodeSkipNop(ref reader) != ILOpCode.Call) |
|
return false; |
|
var baseCtorHandle = MetadataTokenHelpers.EntityHandleOrNil(reader.ReadInt32()); |
|
if (baseCtorHandle.IsNil) |
|
return false; |
|
var objectCtor = module.ResolveMethod(baseCtorHandle, new TypeSystem.GenericContext()); |
|
if (!objectCtor.DeclaringType.IsKnownType(KnownTypeCode.Object)) |
|
return false; |
|
if (!objectCtor.IsConstructor || objectCtor.Parameters.Count != 0) |
|
return false; |
|
return DecodeOpCodeSkipNop(ref reader) == ILOpCode.Ret; |
|
} |
|
catch (BadImageFormatException) |
|
{ |
|
return false; |
|
} |
|
} |
|
|
|
ILOpCode DecodeOpCodeSkipNop(ref BlobReader reader) |
|
{ |
|
ILOpCode code; |
|
do |
|
{ |
|
code = reader.DecodeOpCode(); |
|
} while (code == ILOpCode.Nop); |
|
return code; |
|
} |
|
|
|
VariableToDeclare AddVariable(DisplayClass result, StObj statement, IField field) |
|
{ |
|
VariableToDeclare variable = new VariableToDeclare(result, field); |
|
if (statement != null) |
|
{ |
|
variable.Propagate(ResolveVariableToPropagate(statement.Value, field.Type)); |
|
variable.Initializers.Add(statement); |
|
} |
|
variable.UsesInitialValue = |
|
result.Type.IsReferenceType != false || result.Variable.UsesInitialValue; |
|
return variable; |
|
} |
|
|
|
/// <summary> |
|
/// Resolves references to variables that can be propagated. |
|
/// If a value does not match either LdLoc or a LdObj LdLdFlda* LdLoc chain, null is returned. |
|
/// The if any of the variables/fields in the chain cannot be propagated, null is returned. |
|
/// </summary> |
|
ILVariable ResolveVariableToPropagate(ILInstruction value, IType expectedType = null) |
|
{ |
|
ILVariable v; |
|
switch (value) |
|
{ |
|
case LdLoc load: |
|
v = load.Variable; |
|
if (v.Kind == VariableKind.Parameter) |
|
{ |
|
if (v.LoadCount != 1 && !v.IsThis()) |
|
{ |
|
// If the variable is a parameter and it is used elsewhere, we cannot propagate it. |
|
// "dc.field = v; dc.field.mutate(); use(v);" cannot turn to "v.mutate(); use(v)" |
|
return null; |
|
} |
|
} |
|
else |
|
{ |
|
// Non-parameter propagation will later be checked, and will only be allowed for display classes |
|
if (v.Type.IsReferenceType != true) |
|
{ |
|
// don't allow propagation for display structs (as used with local functions) |
|
return null; |
|
} |
|
} |
|
if (!v.IsSingleDefinition) |
|
{ |
|
// "dc.field = v; v = 42; use(dc.field)" cannot turn to "v = 42; use(v);" |
|
return null; |
|
} |
|
if (!(expectedType == null || v.Kind == VariableKind.StackSlot || v.Type.Equals(expectedType))) |
|
return null; |
|
return v; |
|
case LdObj ldfld: |
|
DisplayClass currentDisplayClass = null; |
|
foreach (var item in ldfld.Target.Descendants) |
|
{ |
|
if (IsDisplayClassLoad(item, out v)) |
|
{ |
|
if (!displayClasses.TryGetValue(v, out currentDisplayClass)) |
|
return null; |
|
} |
|
if (currentDisplayClass == null) |
|
return null; |
|
if (item is LdFlda ldf && currentDisplayClass.VariablesToDeclare.TryGetValue((IField)ldf.Field.MemberDefinition, out var vd)) |
|
{ |
|
if (!vd.CanPropagate) |
|
return null; |
|
if (!displayClasses.TryGetValue(vd.GetOrDeclare(), out currentDisplayClass)) |
|
return null; |
|
} |
|
} |
|
return currentDisplayClass.Variable; |
|
default: |
|
return null; |
|
} |
|
} |
|
|
|
private void Transform(ILFunction function) |
|
{ |
|
VisitILFunction(function); |
|
context.Step($"ResetHasInitialValueFlag", function); |
|
foreach (var f in function.Descendants.OfType<ILFunction>()) |
|
{ |
|
RemoveDeadVariableInit.ResetUsesInitialValueFlag(f, context); |
|
f.CapturedVariables.RemoveWhere(v => v.IsDead); |
|
} |
|
} |
|
|
|
internal static bool IsClosure(ILTransformContext context, ILVariable variable, out ITypeDefinition closureType, out ILInstruction initializer) |
|
{ |
|
closureType = null; |
|
initializer = null; |
|
if (variable.IsSingleDefinition && variable.StoreInstructions.SingleOrDefault() is StLoc inst) |
|
{ |
|
initializer = inst; |
|
if (IsClosureInit(context, inst, out closureType)) |
|
{ |
|
return true; |
|
} |
|
} |
|
closureType = variable.Type.GetDefinition(); |
|
if (context.Settings.LocalFunctions && closureType?.Kind == TypeKind.Struct |
|
&& variable.UsesInitialValue && IsPotentialClosure(context, closureType)) |
|
{ |
|
initializer = LocalFunctionDecompiler.GetStatement(variable.AddressInstructions.OrderBy(i => i.StartILOffset).First()); |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
static bool IsClosureInit(ILTransformContext context, StLoc inst, out ITypeDefinition closureType) |
|
{ |
|
if (inst.Value is NewObj newObj) |
|
{ |
|
closureType = newObj.Method.DeclaringTypeDefinition; |
|
return closureType != null && IsPotentialClosure(context, newObj); |
|
} |
|
closureType = null; |
|
return false; |
|
} |
|
|
|
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())); |
|
} |
|
|
|
/// <summary> |
|
/// mcs likes to optimize closures in yield state machines away by moving the captured variables' fields into the state machine type, |
|
/// We construct a <see cref="DisplayClass"/> that spans the whole method body. |
|
/// </summary> |
|
DisplayClass HandleMonoStateMachine(ILFunction function, ILVariable thisVariable) |
|
{ |
|
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 null; |
|
|
|
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; |
|
VariableToDeclare variableToDeclare = new VariableToDeclare(displayClass, stateMachineVariable.StateMachineField, stateMachineVariable); |
|
displayClass.VariablesToDeclare.Add(stateMachineVariable.StateMachineField, variableToDeclare); |
|
} |
|
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", StateMachineField = thisField |
|
}; |
|
function.Variables.Add(thisVar); |
|
} |
|
else if (thisVar.StateMachineField != null && displayClass.VariablesToDeclare.ContainsKey(thisVar.StateMachineField)) |
|
{ |
|
// "this" was already added previously, no need to add it twice. |
|
return displayClass; |
|
} |
|
VariableToDeclare variableToDeclare = new VariableToDeclare(displayClass, thisField, thisVar); |
|
displayClass.VariablesToDeclare.Add(thisField, variableToDeclare); |
|
} |
|
return displayClass; |
|
|
|
bool FindThisField(out IField foundField) |
|
{ |
|
foundField = null; |
|
foreach (var field in closureType.GetFields(f2 => !f2.IsStatic && !displayClass.VariablesToDeclare.ContainsKey(f2) && f2.Type.GetDefinition() == decompilationContext.CurrentTypeDefinition)) |
|
{ |
|
thisField = field; |
|
return true; |
|
} |
|
return false; |
|
} |
|
} |
|
|
|
internal static bool IsPotentialClosure(ILTransformContext context, NewObj inst) |
|
{ |
|
var decompilationContext = new SimpleTypeResolveContext(context.Function.Ancestors.OfType<ILFunction>().Last().Method); |
|
return IsPotentialClosure(decompilationContext.CurrentTypeDefinition, inst.Method.DeclaringTypeDefinition); |
|
} |
|
|
|
internal static bool IsPotentialClosure(ILTransformContext context, ITypeDefinition potentialDisplayClass) |
|
{ |
|
var decompilationContext = new SimpleTypeResolveContext(context.Function.Ancestors.OfType<ILFunction>().Last().Method); |
|
return IsPotentialClosure(decompilationContext.CurrentTypeDefinition, potentialDisplayClass); |
|
} |
|
|
|
internal static bool IsPotentialClosure(ITypeDefinition decompiledTypeDefinition, ITypeDefinition potentialDisplayClass, bool allowTypeImplementingInterfaces = false) |
|
{ |
|
if (potentialDisplayClass == null || !potentialDisplayClass.IsCompilerGeneratedOrIsInCompilerGeneratedClass()) |
|
return false; |
|
switch (potentialDisplayClass.Kind) |
|
{ |
|
case TypeKind.Struct: |
|
break; |
|
case TypeKind.Class: |
|
if (!allowTypeImplementingInterfaces) |
|
{ |
|
if (!potentialDisplayClass.DirectBaseTypes.All(t => t.IsKnownType(KnownTypeCode.Object))) |
|
return false; |
|
} |
|
break; |
|
default: |
|
return false; |
|
} |
|
|
|
while (potentialDisplayClass != decompiledTypeDefinition) |
|
{ |
|
potentialDisplayClass = potentialDisplayClass.DeclaringTypeDefinition; |
|
if (potentialDisplayClass == null) |
|
return false; |
|
} |
|
return true; |
|
} |
|
|
|
readonly Stack<ILFunction> currentFunctions = new Stack<ILFunction>(); |
|
|
|
protected internal override void VisitILFunction(ILFunction function) |
|
{ |
|
context.StepStartGroup("Visit " + function.Name); |
|
try |
|
{ |
|
this.currentFunctions.Push(function); |
|
base.VisitILFunction(function); |
|
} |
|
finally |
|
{ |
|
this.currentFunctions.Pop(); |
|
context.StepEndGroup(keepIfEmpty: true); |
|
} |
|
} |
|
|
|
protected override void Default(ILInstruction inst) |
|
{ |
|
ILInstruction next; |
|
for (var child = inst.Children.FirstOrDefault(); child != null; child = next) |
|
{ |
|
next = child.GetNextSibling(); |
|
child.AcceptVisitor(this); |
|
} |
|
} |
|
|
|
protected internal override void VisitStLoc(StLoc inst) |
|
{ |
|
DisplayClass displayClass; |
|
if (inst.Parent is Block parentBlock && inst.Variable.IsSingleDefinition) |
|
{ |
|
if ((inst.Variable.Kind == VariableKind.Local || inst.Variable.Kind == VariableKind.StackSlot) && inst.Variable.LoadCount == 0) |
|
{ |
|
// traverse pre-order, so that we do not have to deal with more special cases afterwards |
|
base.VisitStLoc(inst); |
|
if (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 displayClass) && displayClass.Initializer == inst) |
|
{ |
|
// inline contents of object initializer block |
|
if (inst.Value is Block initBlock && initBlock.Kind == BlockKind.ObjectInitializer) |
|
{ |
|
context.Step($"Remove initializer of {inst.Variable.Name}", inst); |
|
for (int i = 1; i < initBlock.Instructions.Count; i++) |
|
{ |
|
var stobj = (StObj)initBlock.Instructions[i]; |
|
var variable = displayClass.VariablesToDeclare[(IField)((LdFlda)stobj.Target).Field.MemberDefinition]; |
|
parentBlock.Instructions.Insert(inst.ChildIndex + i, new StLoc(variable.GetOrDeclare(), stobj.Value).WithILRange(stobj)); |
|
} |
|
} |
|
context.Step($"Remove initializer of {inst.Variable.Name}", inst); |
|
parentBlock.Instructions.Remove(inst); |
|
return; |
|
} |
|
if (inst.Value is LdLoc || inst.Value is LdObj) |
|
{ |
|
// in some cases (e.g. if inlining fails), there can be a reference to a display class in a stack slot, |
|
// in that case it is necessary to resolve the reference and iff it can be propagated, replace all loads |
|
// of the single-definition. |
|
if (!displayClassCopyMap.TryGetValue(inst.Variable, out var referencedDisplayClass)) |
|
{ |
|
referencedDisplayClass = ResolveVariableToPropagate(inst.Value); |
|
} |
|
if (referencedDisplayClass != null && displayClasses.TryGetValue(referencedDisplayClass, out _)) |
|
{ |
|
context.Step($"Propagate reference to {referencedDisplayClass.Name} in {inst.Variable}", inst); |
|
foreach (var ld in inst.Variable.LoadInstructions.ToArray()) |
|
{ |
|
ld.ReplaceWith(new LdLoc(referencedDisplayClass).WithILRange(ld)); |
|
} |
|
parentBlock.Instructions.Remove(inst); |
|
return; |
|
} |
|
} |
|
} |
|
base.VisitStLoc(inst); |
|
} |
|
|
|
protected internal override void VisitStObj(StObj inst) |
|
{ |
|
if (IsDisplayClassFieldAccess(inst.Target, out var v, out var displayClass, out var field)) |
|
{ |
|
VariableToDeclare vd = displayClass.VariablesToDeclare[(IField)field.MemberDefinition]; |
|
if (vd.CanPropagate && vd.Initializers.Contains(inst)) |
|
{ |
|
if (inst.Parent is Block containingBlock) |
|
{ |
|
context.Step($"Remove initializer of {v.Name}.{vd.Name} due to propagation", inst); |
|
containingBlock.Instructions.Remove(inst); |
|
return; |
|
} |
|
} |
|
if (inst.Value is LdLoc ldLoc && ldLoc.Variable is { IsSingleDefinition: true, CaptureScope: null } |
|
&& ldLoc.Variable.StoreInstructions.FirstOrDefault() is StLoc stloc |
|
&& stloc.Parent is Block block) |
|
{ |
|
ILInlining.InlineOneIfPossible(block, stloc.ChildIndex, InliningOptions.None, context); |
|
} |
|
} |
|
base.VisitStObj(inst); |
|
EarlyExpressionTransforms.StObjToStLoc(inst, context); |
|
} |
|
|
|
protected internal override void VisitLdObj(LdObj inst) |
|
{ |
|
base.VisitLdObj(inst); |
|
EarlyExpressionTransforms.LdObjToLdLoc(inst, context); |
|
} |
|
|
|
private bool IsDisplayClassLoad(ILInstruction target, out ILVariable variable) |
|
{ |
|
// We cannot use MatchLdLocRef here because local functions use ref parameters |
|
if (!target.MatchLdLoc(out variable) && !target.MatchLdLoca(out variable)) |
|
return false; |
|
if (displayClassCopyMap.TryGetValue(variable, out ILVariable other)) |
|
variable = other; |
|
return true; |
|
} |
|
|
|
private bool IsDisplayClassFieldAccess(ILInstruction inst, |
|
out ILVariable displayClassVar, out DisplayClass displayClass, out IField field) |
|
{ |
|
displayClass = null; |
|
displayClassVar = null; |
|
field = null; |
|
if (inst is not LdFlda ldflda) |
|
return false; |
|
field = ldflda.Field; |
|
return IsDisplayClassLoad(ldflda.Target, out displayClassVar) |
|
&& displayClasses.TryGetValue(displayClassVar, out displayClass); |
|
} |
|
|
|
protected internal override void VisitLdFlda(LdFlda inst) |
|
{ |
|
base.VisitLdFlda(inst); |
|
// Get display class info |
|
if (!IsDisplayClassFieldAccess(inst, out _, out DisplayClass displayClass, out IField field)) |
|
return; |
|
var keyField = (IField)field.MemberDefinition; |
|
var v = displayClass.VariablesToDeclare[keyField]; |
|
context.Step($"Replace {field.Name} with captured variable {v.Name}", inst); |
|
ILVariable variable = v.GetOrDeclare(); |
|
inst.ReplaceWith(new LdLoca(variable).WithILRange(inst)); |
|
// add captured variable to all descendant functions from the declaring function to this use-site function |
|
foreach (var f in currentFunctions) |
|
{ |
|
if (f == variable.Function) |
|
break; |
|
f.CapturedVariables.Add(variable); |
|
} |
|
} |
|
} |
|
}
|
|
|