// 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
{
///
/// Transforms closure fields to local variables.
///
/// 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.
///
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 Initializers { get; } = new HashSet();
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 VariablesToDeclare;
public BlockContainer CaptureScope;
public ILInstruction Initializer;
public DisplayClass(ILVariable variable, ITypeDefinition type)
{
Variable = variable;
Type = type;
VariablesToDeclare = new Dictionary();
}
}
ILTransformContext context;
ITypeResolveContext decompilationContext;
readonly Dictionary displayClasses = new Dictionary();
readonly Dictionary displayClassCopyMap = new Dictionary();
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(context, 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().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(context, 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;
}
internal static bool ValidateConstructor(ILTransformContext context, 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;
}
}
static 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;
}
///
/// 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.
///
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())
{
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()));
}
///
/// 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.
///
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().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().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 currentFunctions = new Stack();
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);
}
}
}
}