Skip to content

Commit d987828

Browse files
Add transform to remove unconstrained generic reference type check.
1 parent 9f77f8a commit d987828

File tree

12 files changed

+395
-12
lines changed

12 files changed

+395
-12
lines changed

ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
1818

1919
<NoWarn>1701;1702;1705,67,169,1058,728,1720,649,168,251,660,661,675;1998;162;8632;626;8618;8714;8602;8981</NoWarn>
20-
<DefineConstants>ROSLYN;NET60;CS60;CS70;CS71;CS72;CS73;CS80;CS90;CS100;CS110;CS120</DefineConstants>
20+
<DefineConstants>ROSLYN;ROSLYN2;ROSLYN3;ROSLYN4;NET60;CS60;CS70;CS71;CS72;CS73;CS80;CS90;CS100;CS110;CS120</DefineConstants>
2121

2222
<GenerateAssemblyVersionAttribute>False</GenerateAssemblyVersionAttribute>
2323
<GenerateAssemblyFileVersionAttribute>False</GenerateAssemblyFileVersionAttribute>

ICSharpCode.Decompiler.Tests/TestCases/Pretty/Generics.cs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,11 @@ public interface IInterface
8484
{
8585
void Method1<T>() where T : class;
8686
void Method2<T>() where T : class;
87+
88+
void Method3(int a, string b, Type c);
89+
#if CS72
90+
void Method4(in int a);
91+
#endif
8792
}
8893

8994
public abstract class Base : IInterface
@@ -95,6 +100,16 @@ public abstract class Base : IInterface
95100
void IInterface.Method2<T>()
96101
{
97102
}
103+
104+
void IInterface.Method3(int a, string b, Type c)
105+
{
106+
}
107+
108+
#if CS72
109+
void IInterface.Method4(in int a)
110+
{
111+
}
112+
#endif
98113
}
99114

100115
public class Derived : Base
@@ -302,5 +317,38 @@ public static void ConstrainedCall<T>(T x, ref T y) where T : IDisposable
302317
x.Dispose();
303318
y.Dispose();
304319
}
320+
321+
// prior to C# 7.0 UseRefLocalsForAccurateOrderOfEvaluation is disabled, so we will inline.
322+
// Roslyn 4 generates the explicit ldobj.if.ref pattern, so we can also inline.
323+
// The versions in between, we don't inline, so the code doesn't look pretty.
324+
#if ROSLYN4 || !CS70
325+
public static int[] Issue3438<T>(T[] array)
326+
{
327+
List<int> list = new List<int>();
328+
for (int i = 0; i < array.Length; i++)
329+
{
330+
if (!array[i].Equals(default(T)))
331+
{
332+
list.Add(i);
333+
}
334+
}
335+
return list.ToArray();
336+
}
337+
public void Issue3438b<T>(T[] item1, T item2, int item3) where T : IInterface
338+
{
339+
item1[CastToInt(item2)].Method3(CastToInt(item2), CastToString(item2), TestTypeOf()[1]);
340+
}
341+
public void Issue3438c<T>(T item, T item2, int item3) where T : IInterface
342+
{
343+
CastFromInt<T>(item3).Method3(CastToInt(item2), CastToString(item2), TestTypeOf()[1]);
344+
}
345+
//#if CS72
346+
// Disabled because ILInlining does not support inlining ldloca currently.
347+
// public void Issue3438d<T>(T[] item1, T item2, int item3) where T : IInterface
348+
// {
349+
// item1[CastToInt(item2)].Method4(CastToInt(item2));
350+
// }
351+
//#endif
352+
#endif
305353
}
306354
}

ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ public static List<IILTransform> GetILTransforms()
149149
new IndexRangeTransform(),
150150
new DeconstructionTransform(),
151151
new NamedArgumentTransform(),
152+
new RemoveUnconstrainedGenericReferenceTypeCheck(),
152153
new UserDefinedLogicTransform(),
153154
new InterpolatedStringTransform()
154155
),
@@ -1717,6 +1718,7 @@ void DecompileBody(IMethod method, EntityDeclaration entityDecl, DecompileRun de
17171718
{
17181719
var ilReader = new ILReader(typeSystem.MainModule) {
17191720
UseDebugSymbols = settings.UseDebugSymbols,
1721+
UseRefLocalsForAccurateOrderOfEvaluation = settings.UseRefLocalsForAccurateOrderOfEvaluation,
17201722
DebugInfo = DebugInfoProvider
17211723
};
17221724
var methodDef = metadata.GetMethodDefinition((MethodDefinitionHandle)method.MetadataToken);

ICSharpCode.Decompiler/DecompilerSettings.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,6 @@ public void SetLanguageVersion(CSharp.LanguageVersion languageVersion)
9292
stringInterpolation = false;
9393
dictionaryInitializers = false;
9494
extensionMethodsInCollectionInitializers = false;
95-
useRefLocalsForAccurateOrderOfEvaluation = false;
9695
getterOnlyAutomaticProperties = false;
9796
}
9897
if (languageVersion < CSharp.LanguageVersion.CSharp7)
@@ -105,6 +104,7 @@ public void SetLanguageVersion(CSharp.LanguageVersion languageVersion)
105104
localFunctions = false;
106105
deconstruction = false;
107106
patternMatching = false;
107+
useRefLocalsForAccurateOrderOfEvaluation = false;
108108
}
109109
if (languageVersion < CSharp.LanguageVersion.CSharp7_2)
110110
{
@@ -190,11 +190,11 @@ public CSharp.LanguageVersion GetMinimumRequiredVersion()
190190
return CSharp.LanguageVersion.CSharp7_2;
191191
// C# 7.1 missing
192192
if (outVariables || throwExpressions || tupleTypes || tupleConversions
193-
|| discards || localFunctions || deconstruction || patternMatching)
193+
|| discards || localFunctions || deconstruction || patternMatching || useRefLocalsForAccurateOrderOfEvaluation)
194194
return CSharp.LanguageVersion.CSharp7;
195195
if (awaitInCatchFinally || useExpressionBodyForCalculatedGetterOnlyProperties || nullPropagation
196196
|| stringInterpolation || dictionaryInitializers || extensionMethodsInCollectionInitializers
197-
|| useRefLocalsForAccurateOrderOfEvaluation || getterOnlyAutomaticProperties)
197+
|| getterOnlyAutomaticProperties)
198198
return CSharp.LanguageVersion.CSharp6;
199199
if (asyncAwait)
200200
return CSharp.LanguageVersion.CSharp5;
@@ -1126,7 +1126,7 @@ public bool ExtensionMethodsInCollectionInitializers {
11261126
/// order of evaluation.
11271127
/// See https://github.com/icsharpcode/ILSpy/issues/2050
11281128
/// </summary>
1129-
[Category("C# 6.0 / VS 2015")]
1129+
[Category("C# 7.0 / VS 2017")]
11301130
[Description("DecompilerSettings.UseRefLocalsForAccurateOrderOfEvaluation")]
11311131
public bool UseRefLocalsForAccurateOrderOfEvaluation {
11321132
get { return useRefLocalsForAccurateOrderOfEvaluation; }

ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@
107107
<Compile Include="DecompilationProgress.cs" />
108108
<Compile Include="Disassembler\IEntityProcessor.cs" />
109109
<Compile Include="Disassembler\SortByNameProcessor.cs" />
110+
<Compile Include="IL\Transforms\RemoveUnconstrainedGenericReferenceTypeCheck.cs" />
110111
<Compile Include="Metadata\MetadataFile.cs" />
111112
<Compile Include="Metadata\ModuleReferenceMetadata.cs" />
112113
<Compile Include="NRTAttributes.cs" />

ICSharpCode.Decompiler/IL/ILReader.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ internal void ResetForReimport()
122122
readonly MetadataReader metadata;
123123

124124
public bool UseDebugSymbols { get; set; }
125+
public bool UseRefLocalsForAccurateOrderOfEvaluation { get; set; }
125126
public DebugInfo.IDebugInfoProvider? DebugInfo { get; set; }
126127
public List<string> Warnings { get; } = new List<string>();
127128

@@ -1770,6 +1771,7 @@ ILInstruction[] PrepareArguments(bool firstArgumentIsStObjTarget)
17701771
StackType expectedStackType = CallInstruction.ExpectedTypeForThisPointer(method.DeclaringType, constrainedPrefix);
17711772
bool requiresLdObjIfRef = firstArgument == 1
17721773
&& !firstArgumentIsStObjTarget
1774+
&& UseRefLocalsForAccurateOrderOfEvaluation
17731775
&& expectedStackType == StackType.Ref && typeOfThis.IsReferenceType != false;
17741776
for (int i = method.Parameters.Count - 1; i >= 0; i--)
17751777
{

ICSharpCode.Decompiler/IL/Instructions/PatternMatching.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,5 +581,17 @@ public virtual ILInstruction UnwrapConv(ConversionKind kind)
581581
{
582582
return this;
583583
}
584+
585+
public bool MatchLdObj([NotNullWhen(true)] out ILInstruction? target, IType type)
586+
{
587+
var inst = this as LdObj;
588+
if (inst != null && inst.Type.Equals(type))
589+
{
590+
target = inst.Target;
591+
return true;
592+
}
593+
target = default(ILInstruction);
594+
return false;
595+
}
584596
}
585597
}

ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,16 @@ protected internal override void VisitLdObjIfRef(LdObjIfRef inst)
485485
inst.ReplaceWith(inst.Target);
486486
return;
487487
}
488+
if (inst.Target.MatchLdLoc(out var s) && s.IsSingleDefinition && s.LoadCount == 1
489+
&& s.StoreInstructions.SingleOrDefault() is StLoc {
490+
Value: LdLoca { Variable: { AddressCount: 1, StoreCount: 1 } }
491+
})
492+
{
493+
context.Step("Single use of ldobj.if.ref(ldloc v) -> ldloc v", inst);
494+
// there already is a temporary, so the ldobj.if.ref is a no-op in both cases
495+
inst.ReplaceWith(inst.Target);
496+
return;
497+
}
488498
}
489499

490500
protected internal override void VisitStObj(StObj inst)

ICSharpCode.Decompiler/IL/Transforms/IILTransform.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ internal ILReader CreateILReader()
7979
{
8080
return new ILReader(TypeSystem.MainModule) {
8181
UseDebugSymbols = Settings.UseDebugSymbols,
82+
UseRefLocalsForAccurateOrderOfEvaluation = Settings.UseRefLocalsForAccurateOrderOfEvaluation,
8283
DebugInfo = DebugInfo
8384
};
8485
}

ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -170,14 +170,9 @@ public static bool InlineIfPossible(Block block, int pos, ILTransformContext con
170170
public static bool InlineOneIfPossible(Block block, int pos, InliningOptions options, ILTransformContext context)
171171
{
172172
context.CancellationToken.ThrowIfCancellationRequested();
173-
StLoc stloc = block.Instructions[pos] as StLoc;
174-
if (stloc == null || stloc.Variable.Kind == VariableKind.PinnedLocal)
173+
if (block.Instructions[pos] is not StLoc stloc)
175174
return false;
176-
ILVariable v = stloc.Variable;
177-
// ensure the variable is accessed only a single time
178-
if (v.StoreCount != 1)
179-
return false;
180-
if (v.LoadCount > 1 || v.LoadCount + v.AddressCount != 1)
175+
if (!VariableCanBeUsedForInlining(stloc.Variable))
181176
return false;
182177
// TODO: inlining of small integer types might be semantically incorrect,
183178
// but we can't avoid it this easily without breaking lots of tests.
@@ -186,6 +181,18 @@ public static bool InlineOneIfPossible(Block block, int pos, InliningOptions opt
186181
return InlineOne(stloc, options, context);
187182
}
188183

184+
public static bool VariableCanBeUsedForInlining(ILVariable v)
185+
{
186+
if (v.Kind == VariableKind.PinnedLocal)
187+
return false;
188+
// ensure the variable is accessed only a single time
189+
if (v.StoreCount != 1)
190+
return false;
191+
if (v.LoadCount + v.AddressCount != 1)
192+
return false;
193+
return true;
194+
}
195+
189196
/// <summary>
190197
/// Inlines the stloc instruction at block.Instructions[pos] into the next instruction.
191198
///
@@ -908,6 +915,30 @@ public static bool CanMoveInto(ILInstruction expressionBeingMoved, ILInstruction
908915
return true;
909916
}
910917

918+
/// <summary>
919+
/// Gets whether 'expressionBeingMoved' can be moved from somewhere before 'stmt' to become the replacement of 'targetLoad'.
920+
/// </summary>
921+
public static bool CanMoveIntoCallVirt(ILInstruction expressionBeingMoved, ILInstruction stmt, CallVirt nestedCallVirt, ILInstruction targetLoad)
922+
{
923+
Debug.Assert(targetLoad.IsDescendantOf(stmt) && nestedCallVirt.IsDescendantOf(stmt));
924+
ILInstruction thisArg = nestedCallVirt.Arguments[0];
925+
Debug.Assert(thisArg is LdObjIfRef);
926+
for (ILInstruction inst = targetLoad; inst != stmt; inst = inst.Parent)
927+
{
928+
if (!inst.Parent.CanInlineIntoSlot(inst.ChildIndex, expressionBeingMoved))
929+
return false;
930+
// Check whether re-ordering with predecessors is valid:
931+
int childIndex = inst.ChildIndex;
932+
for (int i = 0; i < childIndex; ++i)
933+
{
934+
ILInstruction predecessor = inst.Parent.Children[i];
935+
if (predecessor != thisArg && !IsSafeForInlineOver(predecessor, expressionBeingMoved))
936+
return false;
937+
}
938+
}
939+
return true;
940+
}
941+
911942
/// <summary>
912943
/// Gets whether arg can be un-inlined out of stmt.
913944
/// </summary>

0 commit comments

Comments
 (0)