Skip to content
Permalink
Browse files

Merge pull request #1561 from icsharpcode/mcs

Improved support for closures generated by Mono C# compiler
  • Loading branch information...
siegfriedpammer committed Jul 7, 2019
2 parents 17a8a19 + b4a59ae commit 132595ac7637ce5ea3ffec6b3b3b70cb291d27ea
@@ -134,7 +134,7 @@ public void ReduceNesting([ValueSource(nameof(defaultOptions))] CompilerOptions
}

[Test]
public void DelegateConstruction([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions)
public void DelegateConstruction([ValueSource(nameof(defaultOptionsWithMcs))] CompilerOptions cscOptions)
{
RunForLibrary(cscOptions: cscOptions);
}
@@ -420,7 +420,7 @@ public void TypeMemberTests([ValueSource(nameof(defaultOptions))] CompilerOption
}

[Test]
public void YieldReturn([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions)
public void YieldReturn([ValueSource(nameof(defaultOptionsWithMcs))] CompilerOptions cscOptions)
{
RunForLibrary(cscOptions: cscOptions);
}
@@ -156,6 +156,7 @@ public static List<IILTransform> GetILTransforms()
},
new ProxyCallReplacer(),
new DelegateConstruction(),
new TransformDisplayClassUsage(),
new HighLevelLoopTransform(),
new ReduceNestingTransform(),
new IntroduceDynamicTypeOnLocals(),
@@ -272,6 +272,7 @@
<Compile Include="IL\Transforms\DynamicIsEventAssignmentTransform.cs" />
<Compile Include="IL\Transforms\ReduceNestingTransform.cs" />
<Compile Include="IL\Transforms\LocalFunctionDecompiler.cs" />
<Compile Include="IL\Transforms\TransformDisplayClassUsage.cs" />
<Compile Include="IL\Transforms\UserDefinedLogicTransform.cs" />
<Compile Include="Metadata\AssemblyReferences.cs" />
<Compile Include="Metadata\CodeMappingInfo.cs" />
@@ -74,6 +74,11 @@ public enum VariableKind

static class VariableKindExtensions
{
public static bool IsThis(this ILVariable v)
{
return v.Kind == VariableKind.Parameter && v.Index < 0;
}

public static bool IsLocal(this VariableKind kind)
{
switch (kind) {
@@ -16,9 +16,7 @@
// 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.CSharp;
@@ -37,9 +35,6 @@ void IILTransform.Run(ILFunction function, ILTransformContext context)
return;
this.context = context;
this.decompilationContext = new SimpleTypeResolveContext(function.Method);
var orphanedVariableInits = new List<ILInstruction>();
var targetsToReplace = new List<IInstructionWithVariableOperand>();
var translatedDisplayClasses = new HashSet<ITypeDefinition>();
var cancellationToken = context.CancellationToken;
foreach (var inst in function.Descendants) {
cancellationToken.ThrowIfCancellationRequested();
@@ -50,49 +45,18 @@ void IILTransform.Run(ILFunction function, ILTransformContext context)
if (instWithVar.Variable.Kind == VariableKind.Local) {
instWithVar.Variable.Kind = VariableKind.DisplayClassLocal;
}
targetsToReplace.Add(instWithVar);
var displayClassTypeDef = instWithVar.Variable.Type.GetDefinition();
if (instWithVar.Variable.IsSingleDefinition && instWithVar.Variable.StoreInstructions.SingleOrDefault() is StLoc store) {
if (store.Value is NewObj newObj) {
instWithVar.Variable.CaptureScope = BlockContainer.FindClosestContainer(store);
}
}
}
context.StepEndGroup();
}
if (inst.MatchStLoc(out ILVariable targetVariable, out ILInstruction value)) {
var newObj = value as NewObj;
// TODO : it is probably not a good idea to remove *all* display-classes
// is there a way to minimize the false-positives?
if (newObj != null && IsInSimpleDisplayClass(newObj.Method)) {
targetVariable.CaptureScope = BlockContainer.FindClosestContainer(inst);
targetsToReplace.Add((IInstructionWithVariableOperand)inst);
translatedDisplayClasses.Add(newObj.Method.DeclaringTypeDefinition);
}
}
}
foreach (var target in targetsToReplace.OrderByDescending(t => ((ILInstruction)t).StartILOffset)) {
context.Step($"TransformDisplayClassUsages {target.Variable}", (ILInstruction)target);
function.AcceptVisitor(new TransformDisplayClassUsages(function, target, target.Variable.CaptureScope, orphanedVariableInits, translatedDisplayClasses));
}
context.Step($"Remove orphanedVariableInits", function);
foreach (var store in orphanedVariableInits) {
if (store.Parent is Block containingBlock)
containingBlock.Instructions.Remove(store);
}
}

static bool IsInSimpleDisplayClass(IMethod method)
{
if (!method.IsCompilerGeneratedOrIsInCompilerGeneratedClass())
return false;
return IsSimpleDisplayClass(method.DeclaringType);
}

internal static bool IsSimpleDisplayClass(IType type)
{
if (!type.HasGeneratedName() || (!type.Name.Contains("DisplayClass") && !type.Name.Contains("AnonStorey")))
return false;
if (type.DirectBaseTypes.Any(t => !t.IsKnownType(KnownTypeCode.Object)))
return false;
return true;
}

#region TransformDelegateConstruction
internal static bool IsDelegateConstruction(NewObj inst, bool allowTransformed = false)
{
if (inst == null || inst.Arguments.Count != 2 || inst.Method.DeclaringType.Kind != TypeKind.Delegate)
@@ -101,12 +65,6 @@ internal static bool IsDelegateConstruction(NewObj inst, bool allowTransformed =

return opCode == OpCode.LdFtn || opCode == OpCode.LdVirtFtn || (allowTransformed && opCode == OpCode.ILFunction);
}

internal static bool IsPotentialClosure(ILTransformContext context, NewObj inst)
{
var decompilationContext = new SimpleTypeResolveContext(context.Function.Method);
return IsPotentialClosure(decompilationContext.CurrentTypeDefinition, inst.Method.DeclaringTypeDefinition);
}

static bool IsAnonymousMethod(ITypeDefinition decompiledTypeDefinition, IMethod method)
{
@@ -115,7 +73,7 @@ static bool IsAnonymousMethod(ITypeDefinition decompiledTypeDefinition, IMethod
if (!(method.HasGeneratedName()
|| method.Name.Contains("$")
|| method.IsCompilerGeneratedOrIsInCompilerGeneratedClass()
|| IsPotentialClosure(decompiledTypeDefinition, method.DeclaringTypeDefinition)
|| TransformDisplayClassUsage.IsPotentialClosure(decompiledTypeDefinition, method.DeclaringTypeDefinition)
|| ContainsAnonymousType(method)))
return false;
return true;
@@ -131,18 +89,6 @@ static bool ContainsAnonymousType(IMethod method)
}
return false;
}

static bool IsPotentialClosure(ITypeDefinition decompiledTypeDefinition, ITypeDefinition potentialDisplayClass)
{
if (potentialDisplayClass == null || !potentialDisplayClass.IsCompilerGeneratedOrIsInCompilerGeneratedClass())
return false;
while (potentialDisplayClass != decompiledTypeDefinition) {
potentialDisplayClass = potentialDisplayClass.DeclaringTypeDefinition;
if (potentialDisplayClass == null)
return false;
}
return true;
}

internal static GenericContext? GenericContextFromTypeArguments(TypeParameterSubstitution subst)
{
@@ -259,139 +205,5 @@ protected internal override void VisitLdObj(LdObj inst)
base.VisitLdObj(inst);
}
}

/// <summary>
/// 1. Stores to display class fields are replaced with stores to local variables (in some
/// cases existing variables are used; otherwise fresh variables are added to the
/// ILFunction-container) and all usages of those fields are replaced with the local variable.
/// (see initValues)
/// 2. Usages of the display class container (or any copy) are removed.
/// </summary>
class TransformDisplayClassUsages : ILVisitor
{
ILFunction currentFunction;
BlockContainer captureScope;
readonly IInstructionWithVariableOperand targetLoad;
readonly List<ILVariable> targetAndCopies = new List<ILVariable>();
readonly List<ILInstruction> orphanedVariableInits;
readonly HashSet<ITypeDefinition> translatedDisplayClasses;
readonly Dictionary<IField, DisplayClassVariable> initValues = new Dictionary<IField, DisplayClassVariable>();

struct DisplayClassVariable
{
public ILVariable variable;
public ILInstruction value;
}

public TransformDisplayClassUsages(ILFunction function, IInstructionWithVariableOperand targetLoad, BlockContainer captureScope, List<ILInstruction> orphanedVariableInits, HashSet<ITypeDefinition> translatedDisplayClasses)
{
this.currentFunction = function;
this.targetLoad = targetLoad;
this.captureScope = captureScope;
this.orphanedVariableInits = orphanedVariableInits;
this.translatedDisplayClasses = translatedDisplayClasses;
this.targetAndCopies.Add(targetLoad.Variable);
}

protected override void Default(ILInstruction inst)
{
foreach (var child in inst.Children) {
child.AcceptVisitor(this);
}
}

protected internal override void VisitStLoc(StLoc inst)
{
base.VisitStLoc(inst);
if (targetLoad is ILInstruction instruction && instruction.MatchLdThis())
return;
if (inst.Variable == targetLoad.Variable)
orphanedVariableInits.Add(inst);
if (MatchesTargetOrCopyLoad(inst.Value)) {
targetAndCopies.Add(inst.Variable);
orphanedVariableInits.Add(inst);
}
}

bool MatchesTargetOrCopyLoad(ILInstruction inst)
{
return targetAndCopies.Any(v => inst.MatchLdLoc(v));
}

protected internal override void VisitStObj(StObj inst)
{
base.VisitStObj(inst);
if (!inst.Target.MatchLdFlda(out ILInstruction target, out IField field) || !MatchesTargetOrCopyLoad(target) || target.MatchLdThis())
return;
field = (IField)field.MemberDefinition;
ILInstruction value;
if (initValues.TryGetValue(field, out DisplayClassVariable info)) {
inst.ReplaceWith(new StLoc(info.variable, inst.Value).WithILRange(inst));
} else {
if (inst.Value.MatchLdLoc(out var v) && v.Kind == VariableKind.Parameter && currentFunction == v.Function) {
// special case for parameters: remove copies of parameter values.
orphanedVariableInits.Add(inst);
value = inst.Value;
} else {
if (!translatedDisplayClasses.Contains(field.DeclaringTypeDefinition))
return;
v = currentFunction.RegisterVariable(VariableKind.Local, field.Type, field.Name);
v.CaptureScope = captureScope;
inst.ReplaceWith(new StLoc(v, inst.Value).WithILRange(inst));
value = new LdLoc(v);
}
initValues.Add(field, new DisplayClassVariable { value = value, variable = v });
}
}

protected internal override void VisitLdObj(LdObj inst)
{
base.VisitLdObj(inst);
if (!inst.Target.MatchLdFlda(out ILInstruction target, out IField field))
return;
if (!initValues.TryGetValue((IField)field.MemberDefinition, out DisplayClassVariable info))
return;
var replacement = info.value.Clone();
replacement.SetILRange(inst);
inst.ReplaceWith(replacement);
}

protected internal override void VisitLdFlda(LdFlda inst)
{
base.VisitLdFlda(inst);
if (inst.Target.MatchLdThis() && inst.Field.Name == "$this"
&& inst.Field.MemberDefinition.ReflectionName.Contains("c__Iterator")) {
var variable = currentFunction.Variables.First((f) => f.Index == -1);
inst.ReplaceWith(new LdLoca(variable).WithILRange(inst));
}
if (inst.Parent is LdObj || inst.Parent is StObj)
return;
if (!MatchesTargetOrCopyLoad(inst.Target))
return;
var field = (IField)inst.Field.MemberDefinition;
if (!initValues.TryGetValue(field, out DisplayClassVariable info)) {
if (!translatedDisplayClasses.Contains(field.DeclaringTypeDefinition))
return;
var v = currentFunction.RegisterVariable(VariableKind.Local, field.Type, field.Name);
v.CaptureScope = captureScope;
inst.ReplaceWith(new LdLoca(v).WithILRange(inst));
var value = new LdLoc(v);
initValues.Add(field, new DisplayClassVariable { value = value, variable = v });
} else if (info.value is LdLoc l) {
inst.ReplaceWith(new LdLoca(l.Variable).WithILRange(inst));
} else {
Debug.Fail("LdFlda pattern not supported!");
}
}

protected internal override void VisitNumericCompoundAssign(NumericCompoundAssign inst)
{
base.VisitNumericCompoundAssign(inst);
if (inst.Target.MatchLdLoc(out var v)) {
inst.ReplaceWith(new StLoc(v, new BinaryNumericInstruction(inst.Operator, inst.Target, inst.Value, inst.CheckForOverflow, inst.Sign).WithILRange(inst)));
}
}
}
#endregion
}
}
@@ -53,6 +53,10 @@ public void Run(ILFunction function, ILTransformContext context)
var v = variableQueue.Dequeue();
if (v.Kind != VariableKind.Local && v.Kind != VariableKind.StackSlot)
continue;
// Skip variables that are captured in a mcs yield state-machine
// loads of these will only be visible after DelegateConstruction step.
if (function.StateMachineCompiledWithMono && v.StateMachineField != null)
continue;
if (v.LoadCount != 0 || v.AddressCount != 0)
continue;
foreach (var stloc in v.StoreInstructions.OfType<StLoc>().ToArray()) {
@@ -62,9 +62,9 @@ bool DoTransform(Block body, int pos)
}
// Do not try to transform display class usages or delegate construction.
// DelegateConstruction transform cannot deal with this.
if (DelegateConstruction.IsSimpleDisplayClass(newObjInst.Method.DeclaringType))
if (TransformDisplayClassUsage.IsSimpleDisplayClass(newObjInst.Method.DeclaringType))
return false;
if (DelegateConstruction.IsDelegateConstruction(newObjInst) || DelegateConstruction.IsPotentialClosure(context, newObjInst))
if (DelegateConstruction.IsDelegateConstruction(newObjInst) || TransformDisplayClassUsage.IsPotentialClosure(context, newObjInst))
return false;
// Cannot build a collection/object initializer attached to an AnonymousTypeCreateExpression:s
// anon = new { A = 5 } { 3,4,5 } is invalid syntax.

0 comments on commit 132595a

Please sign in to comment.
You can’t perform that action at this time.