diff --git a/.github/workflows/build-ilspy.yml b/.github/workflows/build-ilspy.yml
index a112c3a39..d36ee142c 100644
--- a/.github/workflows/build-ilspy.yml
+++ b/.github/workflows/build-ilspy.yml
@@ -60,7 +60,7 @@ jobs:
run: msbuild ILSpy.sln /p:Configuration=${{ matrix.configuration }} /p:Platform=$env:BuildPlatform /m
- name: Execute unit tests
- run: dotnet test --logger "junit;LogFileName=${{ matrix.configuration }}.xml" --results-directory test-results $env:Tests1 $env:Tests2 $env:Tests3
+ run: dotnet test --logger "trx;LogFileName=${{ matrix.configuration }}.trx" --results-directory test-results $env:Tests1 $env:Tests2 $env:Tests3
env:
Tests1: ICSharpCode.Decompiler.Tests\bin\${{ matrix.configuration }}\net8.0-windows\win-x64\ICSharpCode.Decompiler.Tests.dll
Tests2: ILSpy.Tests\bin\${{ matrix.configuration }}\net8.0-windows\ILSpy.Tests.dll
@@ -71,13 +71,14 @@ jobs:
if: success() || failure()
with:
name: test-results-${{ matrix.configuration }}
- path: 'test-results/${{ matrix.configuration }}.xml'
+ path: 'test-results/${{ matrix.configuration }}.trx'
- name: Create Test Report
- uses: test-summary/action@v2
+ uses: icsharpcode/test-summary-action@dist
if: always()
with:
- paths: "test-results/${{ matrix.configuration }}.xml"
+ paths: "test-results/${{ matrix.configuration }}.trx"
+ folded: true
- name: Format check
run: dotnet-format whitespace --verify-no-changes --verbosity detailed ILSpy.sln
diff --git a/ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs b/ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs
index a0901f255..cf3974d2f 100644
--- a/ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs
+++ b/ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs
@@ -16,8 +16,6 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
-using System;
-using System.CodeDom.Compiler;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
@@ -313,6 +311,12 @@ namespace ICSharpCode.Decompiler.Tests
await RunIL("Jmp.il");
}
+ [Test]
+ public async Task NonGenericConstrainedCallVirt()
+ {
+ await RunIL("NonGenericConstrainedCallVirt.il", CompilerOptions.UseRoslynLatest);
+ }
+
[Test]
public async Task StackTests()
{
diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
index f19374fd5..ee4393ea7 100644
--- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
+++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
@@ -17,7 +17,7 @@
True
1701;1702;1705,67,169,1058,728,1720,649,168,251,660,661,675;1998;162;8632;626;8618;8714;8602;8981
- ROSLYN;NET60;CS60;CS70;CS71;CS72;CS73;CS80;CS90;CS100;CS110;CS120
+ ROSLYN;ROSLYN2;ROSLYN3;ROSLYN4;NET60;CS60;CS70;CS71;CS72;CS73;CS80;CS90;CS100;CS110;CS120
False
False
@@ -95,6 +95,7 @@
+
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/NonGenericConstrainedCallVirt.il b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/NonGenericConstrainedCallVirt.il
new file mode 100644
index 000000000..2fd15600e
--- /dev/null
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/NonGenericConstrainedCallVirt.il
@@ -0,0 +1,143 @@
+.assembly extern mscorlib
+{
+ .publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4..
+ .ver 4:0:0:0
+}
+
+.assembly _
+{
+ .hash algorithm 0x00008004 // SHA1
+ .ver 0:0:0:0
+}
+
+.class public auto ansi beforefieldinit NonGenericConstrainedCallVirt
+ extends [mscorlib]System.Object
+{
+ // Methods
+ .method public hidebysig static
+ void Main () cil managed
+ {
+ // Method begins at RVA 0x2050
+ // Code size 16 (0x10)
+ .entrypoint
+ .maxstack 8
+
+ IL_0000: ldstr "A"
+ IL_0005: newobj instance void C::.ctor(string)
+ IL_000a: call void NonGenericConstrainedCallVirt::Foo(class C)
+ IL_000f: ret
+ } // end of method NonGenericConstrainedCallVirt::Main
+
+ .method private hidebysig static
+ void Foo (
+ class C arg
+ ) cil managed
+ {
+ // Method begins at RVA 0x2064
+ // Code size 25 (0x19)
+ .maxstack 8
+
+ IL_0000: ldarga arg
+ IL_0004: ldarga arg
+ IL_0008: call int32 NonGenericConstrainedCallVirt::Bar(class C&)
+ IL_000d: constrained. C
+ IL_0013: callvirt instance void C::Baz(int32)
+ IL_0018: ret
+ } // end of method NonGenericConstrainedCallVirt::Foo
+
+ .method private hidebysig static
+ int32 Bar (
+ class C& o
+ ) cil managed
+ {
+ // Method begins at RVA 0x2080
+ // Code size 14 (0xe)
+ .maxstack 8
+
+ IL_0000: ldarg.0
+ IL_0001: ldstr "B"
+ IL_0006: newobj instance void C::.ctor(string)
+ IL_000b: stind.ref
+ IL_000c: ldc.i4.0
+ IL_000d: ret
+ } // end of method NonGenericConstrainedCallVirt::Bar
+
+ .method public hidebysig specialname rtspecialname
+ instance void .ctor () cil managed
+ {
+ // Method begins at RVA 0x2090
+ // Code size 7 (0x7)
+ .maxstack 8
+
+ IL_0000: ldarg.0
+ IL_0001: call instance void [mscorlib]System.Object::.ctor()
+ IL_0006: ret
+ } // end of method NonGenericConstrainedCallVirt::.ctor
+
+} // end of class NonGenericConstrainedCallVirt
+
+.class public auto ansi beforefieldinit C
+ extends [mscorlib]System.Object
+{
+ // Fields
+ .field private initonly string 'k__BackingField'
+ .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
+ 01 00 00 00
+ )
+
+ // Methods
+ .method public hidebysig specialname rtspecialname
+ instance void .ctor (
+ string n
+ ) cil managed
+ {
+ // Method begins at RVA 0x2098
+ // Code size 14 (0xe)
+ .maxstack 8
+
+ IL_0000: ldarg.0
+ IL_0001: call instance void [mscorlib]System.Object::.ctor()
+ IL_0006: ldarg.0
+ IL_0007: ldarg.1
+ IL_0008: stfld string C::'k__BackingField'
+ IL_000d: ret
+ } // end of method C::.ctor
+
+ .method public hidebysig
+ instance void Baz (
+ int32 arg
+ ) cil managed
+ {
+ // Method begins at RVA 0x20a8
+ // Code size 12 (0xc)
+ .maxstack 8
+
+ IL_0000: ldarg.0
+ IL_0001: call instance string C::get_Name()
+ IL_0006: call void [mscorlib]System.Console::WriteLine(string)
+ IL_000b: ret
+ } // end of method C::Baz
+
+ .method public hidebysig specialname
+ instance string get_Name () cil managed
+ {
+ .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
+ 01 00 00 00
+ )
+ // Method begins at RVA 0x20b8
+ // Code size 7 (0x7)
+ .maxstack 8
+
+ IL_0000: ldarg.0
+ IL_0001: ldfld string C::'k__BackingField'
+ IL_0006: ret
+ } // end of method C::get_Name
+
+ // Properties
+ .property instance string Name()
+ {
+ .get instance string C::get_Name()
+ }
+
+} // end of class C
+
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Generics.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Generics.cs
index 6d9d1672a..c02845c9c 100644
--- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Generics.cs
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Generics.cs
@@ -84,6 +84,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
void Method1() where T : class;
void Method2() where T : class;
+
+ void Method3(int a, string b, Type c);
+#if CS72
+ void Method4(in int a);
+#endif
}
public abstract class Base : IInterface
@@ -95,6 +100,16 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
void IInterface.Method2()
{
}
+
+ void IInterface.Method3(int a, string b, Type c)
+ {
+ }
+
+#if CS72
+ void IInterface.Method4(in int a)
+ {
+ }
+#endif
}
public class Derived : Base
@@ -296,5 +311,44 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
return default(T).ToString();
}
+
+ public static void ConstrainedCall(T x, ref T y) where T : IDisposable
+ {
+ x.Dispose();
+ y.Dispose();
+ }
+
+ // prior to C# 7.0 UseRefLocalsForAccurateOrderOfEvaluation is disabled, so we will inline.
+ // Roslyn 4 generates the explicit ldobj.if.ref pattern, so we can also inline.
+ // The versions in between, we don't inline, so the code doesn't look pretty.
+#if ROSLYN4 || !CS70
+ public static int[] Issue3438(T[] array)
+ {
+ List list = new List();
+ for (int i = 0; i < array.Length; i++)
+ {
+ if (!array[i].Equals(default(T)))
+ {
+ list.Add(i);
+ }
+ }
+ return list.ToArray();
+ }
+ public void Issue3438b(T[] item1, T item2, int item3) where T : IInterface
+ {
+ item1[CastToInt(item2)].Method3(CastToInt(item2), CastToString(item2), TestTypeOf()[1]);
+ }
+ public void Issue3438c(T item, T item2, int item3) where T : IInterface
+ {
+ CastFromInt(item3).Method3(CastToInt(item2), CastToString(item2), TestTypeOf()[1]);
+ }
+ //#if CS72
+ // Disabled because ILInlining does not support inlining ldloca currently.
+ // public void Issue3438d(T[] item1, T item2, int item3) where T : IInterface
+ // {
+ // item1[CastToInt(item2)].Method4(CastToInt(item2));
+ // }
+ //#endif
+#endif
}
}
diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
index b239da3a2..07a4126de 100644
--- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
+++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
@@ -149,6 +149,7 @@ namespace ICSharpCode.Decompiler.CSharp
new IndexRangeTransform(),
new DeconstructionTransform(),
new NamedArgumentTransform(),
+ new RemoveUnconstrainedGenericReferenceTypeCheck(),
new UserDefinedLogicTransform(),
new InterpolatedStringTransform()
),
@@ -1717,6 +1718,7 @@ namespace ICSharpCode.Decompiler.CSharp
{
var ilReader = new ILReader(typeSystem.MainModule) {
UseDebugSymbols = settings.UseDebugSymbols,
+ UseRefLocalsForAccurateOrderOfEvaluation = settings.UseRefLocalsForAccurateOrderOfEvaluation,
DebugInfo = DebugInfoProvider
};
var methodDef = metadata.GetMethodDefinition((MethodDefinitionHandle)method.MetadataToken);
diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs
index 5a5fe7802..b8b6680e5 100644
--- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs
+++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs
@@ -356,11 +356,18 @@ namespace ICSharpCode.Decompiler.CSharp
}
else
{
+ var thisArg = callArguments.FirstOrDefault();
+ if (thisArg is LdObjIfRef ldObjIfRef)
+ {
+ Debug.Assert(constrainedTo != null);
+ thisArg = ldObjIfRef.Target;
+ }
target = expressionBuilder.TranslateTarget(
- callArguments.FirstOrDefault(),
+ thisArg,
nonVirtualInvocation: callOpCode == OpCode.Call || method.IsConstructor,
memberStatic: method.IsStatic,
- memberDeclaringType: constrainedTo ?? method.DeclaringType);
+ memberDeclaringType: method.DeclaringType,
+ constrainedTo: constrainedTo);
if (constrainedTo == null
&& target.Expression is CastExpression cast
&& target.ResolveResult is ConversionResolveResult conversion
diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
index 90df1890a..8398be47a 100644
--- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
+++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
@@ -2614,7 +2614,7 @@ namespace ICSharpCode.Decompiler.CSharp
}
internal TranslatedExpression TranslateTarget(ILInstruction target, bool nonVirtualInvocation,
- bool memberStatic, IType memberDeclaringType)
+ bool memberStatic, IType memberDeclaringType, IType constrainedTo = null)
{
// If references are missing member.IsStatic might not be set correctly.
// Additionally check target for null, in order to avoid a crash.
@@ -2630,8 +2630,8 @@ namespace ICSharpCode.Decompiler.CSharp
}
else
{
- IType targetTypeHint = memberDeclaringType;
- if (CallInstruction.ExpectedTypeForThisPointer(memberDeclaringType) == StackType.Ref)
+ IType targetTypeHint = constrainedTo ?? memberDeclaringType;
+ if (CallInstruction.ExpectedTypeForThisPointer(memberDeclaringType, constrainedTo) == StackType.Ref)
{
if (target.ResultType == StackType.Ref)
{
@@ -2643,13 +2643,13 @@ namespace ICSharpCode.Decompiler.CSharp
}
}
var translatedTarget = Translate(target, targetTypeHint);
- if (CallInstruction.ExpectedTypeForThisPointer(memberDeclaringType) == StackType.Ref)
+ if (CallInstruction.ExpectedTypeForThisPointer(memberDeclaringType, constrainedTo) == StackType.Ref)
{
// When accessing members on value types, ensure we use a reference of the correct type,
// and not a pointer or a reference to a different type (issue #1333)
- if (!(translatedTarget.Type is ByReferenceType brt && NormalizeTypeVisitor.TypeErasure.EquivalentTypes(brt.ElementType, memberDeclaringType)))
+ if (!(translatedTarget.Type is ByReferenceType brt && NormalizeTypeVisitor.TypeErasure.EquivalentTypes(brt.ElementType, constrainedTo ?? memberDeclaringType)))
{
- translatedTarget = translatedTarget.ConvertTo(new ByReferenceType(memberDeclaringType), this);
+ translatedTarget = translatedTarget.ConvertTo(new ByReferenceType(constrainedTo ?? memberDeclaringType), this);
}
}
if (translatedTarget.Expression is DirectionExpression)
@@ -2675,9 +2675,9 @@ namespace ICSharpCode.Decompiler.CSharp
}
else
{
- return new TypeReferenceExpression(ConvertType(memberDeclaringType))
+ return new TypeReferenceExpression(ConvertType(constrainedTo ?? memberDeclaringType))
.WithoutILInstruction()
- .WithRR(new TypeResolveResult(memberDeclaringType));
+ .WithRR(new TypeResolveResult(constrainedTo ?? memberDeclaringType));
}
bool ShouldUseBaseReference()
@@ -2686,7 +2686,7 @@ namespace ICSharpCode.Decompiler.CSharp
return false;
if (!MatchLdThis(target))
return false;
- if (memberDeclaringType.GetDefinition() == resolver.CurrentTypeDefinition)
+ if ((constrainedTo ?? memberDeclaringType).GetDefinition() == resolver.CurrentTypeDefinition)
return false;
return true;
}
diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs
index f4fac4485..a0bd68f36 100644
--- a/ICSharpCode.Decompiler/DecompilerSettings.cs
+++ b/ICSharpCode.Decompiler/DecompilerSettings.cs
@@ -92,7 +92,6 @@ namespace ICSharpCode.Decompiler
stringInterpolation = false;
dictionaryInitializers = false;
extensionMethodsInCollectionInitializers = false;
- useRefLocalsForAccurateOrderOfEvaluation = false;
getterOnlyAutomaticProperties = false;
}
if (languageVersion < CSharp.LanguageVersion.CSharp7)
@@ -105,6 +104,7 @@ namespace ICSharpCode.Decompiler
localFunctions = false;
deconstruction = false;
patternMatching = false;
+ useRefLocalsForAccurateOrderOfEvaluation = false;
}
if (languageVersion < CSharp.LanguageVersion.CSharp7_2)
{
@@ -190,11 +190,11 @@ namespace ICSharpCode.Decompiler
return CSharp.LanguageVersion.CSharp7_2;
// C# 7.1 missing
if (outVariables || throwExpressions || tupleTypes || tupleConversions
- || discards || localFunctions || deconstruction || patternMatching)
+ || discards || localFunctions || deconstruction || patternMatching || useRefLocalsForAccurateOrderOfEvaluation)
return CSharp.LanguageVersion.CSharp7;
if (awaitInCatchFinally || useExpressionBodyForCalculatedGetterOnlyProperties || nullPropagation
|| stringInterpolation || dictionaryInitializers || extensionMethodsInCollectionInitializers
- || useRefLocalsForAccurateOrderOfEvaluation || getterOnlyAutomaticProperties)
+ || getterOnlyAutomaticProperties)
return CSharp.LanguageVersion.CSharp6;
if (asyncAwait)
return CSharp.LanguageVersion.CSharp5;
@@ -1126,7 +1126,7 @@ namespace ICSharpCode.Decompiler
/// order of evaluation.
/// See https://github.com/icsharpcode/ILSpy/issues/2050
///
- [Category("C# 6.0 / VS 2015")]
+ [Category("C# 7.0 / VS 2017")]
[Description("DecompilerSettings.UseRefLocalsForAccurateOrderOfEvaluation")]
public bool UseRefLocalsForAccurateOrderOfEvaluation {
get { return useRefLocalsForAccurateOrderOfEvaluation; }
diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
index 2a9eb220b..87797610f 100644
--- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
+++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
@@ -107,6 +107,7 @@
+
diff --git a/ICSharpCode.Decompiler/IL/ILReader.cs b/ICSharpCode.Decompiler/IL/ILReader.cs
index 226218e12..272a8ed91 100644
--- a/ICSharpCode.Decompiler/IL/ILReader.cs
+++ b/ICSharpCode.Decompiler/IL/ILReader.cs
@@ -122,6 +122,7 @@ namespace ICSharpCode.Decompiler.IL
readonly MetadataReader metadata;
public bool UseDebugSymbols { get; set; }
+ public bool UseRefLocalsForAccurateOrderOfEvaluation { get; set; }
public DebugInfo.IDebugInfoProvider? DebugInfo { get; set; }
public List Warnings { get; } = new List();
@@ -1766,15 +1767,37 @@ namespace ICSharpCode.Decompiler.IL
{
int firstArgument = (opCode != OpCode.NewObj && !method.IsStatic) ? 1 : 0;
var arguments = new ILInstruction[firstArgument + method.Parameters.Count];
+ IType typeOfThis = constrainedPrefix ?? method.DeclaringType;
+ StackType expectedStackType = CallInstruction.ExpectedTypeForThisPointer(method.DeclaringType, constrainedPrefix);
+ bool requiresLdObjIfRef = firstArgument == 1
+ && !firstArgumentIsStObjTarget
+ && UseRefLocalsForAccurateOrderOfEvaluation
+ && expectedStackType == StackType.Ref && typeOfThis.IsReferenceType != false;
for (int i = method.Parameters.Count - 1; i >= 0; i--)
{
+ if (requiresLdObjIfRef)
+ {
+ FlushExpressionStack();
+ }
+
arguments[firstArgument + i] = Pop(method.Parameters[i].Type.GetStackType());
}
if (firstArgument == 1)
{
- arguments[0] = firstArgumentIsStObjTarget
- ? PopStObjTarget()
- : Pop(CallInstruction.ExpectedTypeForThisPointer(constrainedPrefix ?? method.DeclaringType));
+ ILInstruction firstArgumentInstruction;
+ if (firstArgumentIsStObjTarget)
+ {
+ firstArgumentInstruction = PopStObjTarget();
+ }
+ else
+ {
+ firstArgumentInstruction = Pop(expectedStackType);
+ if (requiresLdObjIfRef)
+ {
+ firstArgumentInstruction = new LdObjIfRef(firstArgumentInstruction, typeOfThis);
+ }
+ }
+ arguments[0] = firstArgumentInstruction;
}
// arguments is in reverse order of the Pop calls, thus
// arguments is now in the correct evaluation order.
diff --git a/ICSharpCode.Decompiler/IL/Instructions.cs b/ICSharpCode.Decompiler/IL/Instructions.cs
index c4ce2d8af..4f3ef843d 100644
--- a/ICSharpCode.Decompiler/IL/Instructions.cs
+++ b/ICSharpCode.Decompiler/IL/Instructions.cs
@@ -164,6 +164,8 @@ namespace ICSharpCode.Decompiler.IL
IsInst,
/// Indirect load (ref/pointer dereference).
LdObj,
+ /// If argument is a ref to a reference type, loads the object reference, stores it in a temporary, and evaluates to the address of that temporary (address.of(ldobj(arg))). Otherwise, returns the argument ref as-is.This instruction represents the memory-load semantics of callvirt with a generic type as receiver (where the IL always takes a ref, but only methods on value types expect one, for method on reference types there's an implicit ldobj, which this instruction makes explicit in order to preserve the order-of-evaluation).
+ LdObjIfRef,
/// Indirect store (store to ref/pointer).
/// Evaluates to the value that was stored (when using type byte/short: evaluates to the truncated value, sign/zero extended back to I4 based on type.GetSign())
StObj,
@@ -4085,6 +4087,116 @@ namespace ICSharpCode.Decompiler.IL
}
}
namespace ICSharpCode.Decompiler.IL
+{
+ /// If argument is a ref to a reference type, loads the object reference, stores it in a temporary, and evaluates to the address of that temporary (address.of(ldobj(arg))). Otherwise, returns the argument ref as-is.This instruction represents the memory-load semantics of callvirt with a generic type as receiver (where the IL always takes a ref, but only methods on value types expect one, for method on reference types there's an implicit ldobj, which this instruction makes explicit in order to preserve the order-of-evaluation).
+ public sealed partial class LdObjIfRef : ILInstruction
+ {
+ public LdObjIfRef(ILInstruction target, IType type) : base(OpCode.LdObjIfRef)
+ {
+ this.Target = target;
+ this.type = type;
+ }
+ public static readonly SlotInfo TargetSlot = new SlotInfo("Target", canInlineInto: true);
+ ILInstruction target = null!;
+ public ILInstruction Target {
+ get { return this.target; }
+ set {
+ ValidateChild(value);
+ SetChildInstruction(ref this.target, value, 0);
+ }
+ }
+ protected sealed override int GetChildCount()
+ {
+ return 1;
+ }
+ protected sealed override ILInstruction GetChild(int index)
+ {
+ switch (index)
+ {
+ case 0:
+ return this.target;
+ default:
+ throw new IndexOutOfRangeException();
+ }
+ }
+ protected sealed override void SetChild(int index, ILInstruction value)
+ {
+ switch (index)
+ {
+ case 0:
+ this.Target = value;
+ break;
+ default:
+ throw new IndexOutOfRangeException();
+ }
+ }
+ protected sealed override SlotInfo GetChildSlot(int index)
+ {
+ switch (index)
+ {
+ case 0:
+ return TargetSlot;
+ default:
+ throw new IndexOutOfRangeException();
+ }
+ }
+ public sealed override ILInstruction Clone()
+ {
+ var clone = (LdObjIfRef)ShallowClone();
+ clone.Target = this.target.Clone();
+ return clone;
+ }
+ IType type;
+ /// Returns the type operand.
+ public IType Type {
+ get { return type; }
+ set { type = value; InvalidateFlags(); }
+ }
+ public override StackType ResultType { get { return StackType.Ref; } }
+ protected override InstructionFlags ComputeFlags()
+ {
+ return target.Flags | InstructionFlags.SideEffect | InstructionFlags.MayThrow;
+ }
+ public override InstructionFlags DirectFlags {
+ get {
+ return InstructionFlags.SideEffect | InstructionFlags.MayThrow;
+ }
+ }
+ public override void WriteTo(ITextOutput output, ILAstWritingOptions options)
+ {
+ WriteILRange(output, options);
+ output.Write(OpCode);
+ output.Write(' ');
+ type.WriteTo(output);
+ output.Write('(');
+ this.target.WriteTo(output, options);
+ output.Write(')');
+ }
+ public override void AcceptVisitor(ILVisitor visitor)
+ {
+ visitor.VisitLdObjIfRef(this);
+ }
+ public override T AcceptVisitor(ILVisitor visitor)
+ {
+ return visitor.VisitLdObjIfRef(this);
+ }
+ public override T AcceptVisitor(ILVisitor visitor, C context)
+ {
+ return visitor.VisitLdObjIfRef(this, context);
+ }
+ protected internal override bool PerformMatch(ILInstruction? other, ref Patterns.Match match)
+ {
+ var o = other as LdObjIfRef;
+ return o != null && this.target.PerformMatch(o.target, ref match) && type.Equals(o.type);
+ }
+ internal override void CheckInvariant(ILPhase phase)
+ {
+ base.CheckInvariant(phase);
+ DebugAssert(target.ResultType == StackType.Ref || target.ResultType == StackType.I);
+ }
+ }
+}
+namespace ICSharpCode.Decompiler.IL
{
/// Indirect store (store to ref/pointer).
/// Evaluates to the value that was stored (when using type byte/short: evaluates to the truncated value, sign/zero extended back to I4 based on type.GetSign())
@@ -7071,6 +7183,10 @@ namespace ICSharpCode.Decompiler.IL
{
Default(inst);
}
+ protected internal virtual void VisitLdObjIfRef(LdObjIfRef inst)
+ {
+ Default(inst);
+ }
protected internal virtual void VisitStObj(StObj inst)
{
Default(inst);
@@ -7473,6 +7589,10 @@ namespace ICSharpCode.Decompiler.IL
{
return Default(inst);
}
+ protected internal virtual T VisitLdObjIfRef(LdObjIfRef inst)
+ {
+ return Default(inst);
+ }
protected internal virtual T VisitStObj(StObj inst)
{
return Default(inst);
@@ -7875,6 +7995,10 @@ namespace ICSharpCode.Decompiler.IL
{
return Default(inst, context);
}
+ protected internal virtual T VisitLdObjIfRef(LdObjIfRef inst, C context)
+ {
+ return Default(inst, context);
+ }
protected internal virtual T VisitStObj(StObj inst, C context)
{
return Default(inst, context);
@@ -8086,6 +8210,7 @@ namespace ICSharpCode.Decompiler.IL
"castclass",
"isinst",
"ldobj",
+ "ldobj.if.ref",
"stobj",
"box",
"unbox",
@@ -8591,6 +8716,19 @@ namespace ICSharpCode.Decompiler.IL
type = default(IType);
return false;
}
+ public bool MatchLdObjIfRef([NotNullWhen(true)] out ILInstruction? target, [NotNullWhen(true)] out IType? type)
+ {
+ var inst = this as LdObjIfRef;
+ if (inst != null)
+ {
+ target = inst.Target;
+ type = inst.Type;
+ return true;
+ }
+ target = default(ILInstruction);
+ type = default(IType);
+ return false;
+ }
public bool MatchStObj([NotNullWhen(true)] out ILInstruction? target, [NotNullWhen(true)] out ILInstruction? value, [NotNullWhen(true)] out IType? type)
{
var inst = this as StObj;
diff --git a/ICSharpCode.Decompiler/IL/Instructions.tt b/ICSharpCode.Decompiler/IL/Instructions.tt
index 72da23a16..6f42205af 100644
--- a/ICSharpCode.Decompiler/IL/Instructions.tt
+++ b/ICSharpCode.Decompiler/IL/Instructions.tt
@@ -255,6 +255,9 @@
new OpCode("ldobj", "Indirect load (ref/pointer dereference).",
CustomClassName("LdObj"), CustomArguments(("target", new[] { "Ref", "I" })), HasTypeOperand, MemoryAccess, CustomWriteToButKeepOriginal,
SupportsVolatilePrefix, SupportsUnalignedPrefix, MayThrow, ResultType("type.GetStackType()")),
+ new OpCode("ldobj.if.ref", "If argument is a ref to a reference type, loads the object reference, stores it in a temporary, and evaluates to the address of that temporary (address.of(ldobj(arg))). Otherwise, returns the argument ref as-is.This instruction represents the memory-load semantics of callvirt with a generic type as receiver (where the IL always takes a ref, but only methods on value types expect one, for method on reference types there's an implicit ldobj, which this instruction makes explicit in order to preserve the order-of-evaluation).",
+ CustomClassName("LdObjIfRef"), CustomArguments(("target", new[] { "Ref", "I" })), HasTypeOperand, MemoryAccess,
+ MayThrow, ResultType("Ref")),
new OpCode("stobj", "Indirect store (store to ref/pointer)." + Environment.NewLine
+ "Evaluates to the value that was stored (when using type byte/short: evaluates to the truncated value, sign/zero extended back to I4 based on type.GetSign())",
CustomClassName("StObj"), CustomArguments(("target", new[] { "Ref", "I" }), ("value", new[] { "type.GetStackType()" })), HasTypeOperand, MemoryAccess, CustomWriteToButKeepOriginal,
@@ -916,7 +919,8 @@ namespace ICSharpCode.Decompiler.IL
b = new StringBuilder();
b.AppendLine("protected sealed override ILInstruction GetChild(int index)");
b.AppendLine("{");
- b.AppendLine("\tswitch (index) {");
+ b.AppendLine("\tswitch (index)");
+ b.AppendLine("\t{");
for (int i = 0; i < childCount; i++) {
b.AppendLine("\t\tcase " + i + ":");
b.AppendLine("\t\t\treturn this." + children[i].Name + ";");
@@ -933,7 +937,8 @@ namespace ICSharpCode.Decompiler.IL
b = new StringBuilder();
b.AppendLine("protected sealed override void SetChild(int index, ILInstruction value)");
b.AppendLine("{");
- b.AppendLine("\tswitch (index) {");
+ b.AppendLine("\tswitch (index)");
+ b.AppendLine("\t{");
for (int i = 0; i < childCount; i++) {
b.AppendLine("\t\tcase " + i + ":");
b.AppendLine("\t\t\tthis." + children[i].PropertyName + " = value;");
@@ -953,7 +958,8 @@ namespace ICSharpCode.Decompiler.IL
b = new StringBuilder();
b.AppendLine("protected sealed override SlotInfo GetChildSlot(int index)");
b.AppendLine("{");
- b.AppendLine("\tswitch (index) {");
+ b.AppendLine("\tswitch (index)");
+ b.AppendLine("\t{");
for (int i = 0; i < childCount; i++) {
b.AppendLine("\t\tcase " + i + ":");
b.AppendLine("\t\t\treturn " + children[i].SlotName + ";");
@@ -1113,7 +1119,8 @@ protected override void Disconnected()
opCode.Members.Add("/// Returns the method operand." + Environment.NewLine
+ $"public IMethod{n} Method => method;");
opCode.GenerateWriteTo = true;
- opCode.WriteOperand.Add("if (method != null) {");
+ opCode.WriteOperand.Add("if (method != null)");
+ opCode.WriteOperand.Add("{");
opCode.WriteOperand.Add("\toutput.Write(' ');");
opCode.WriteOperand.Add("\tmethod.WriteTo(output);");
opCode.WriteOperand.Add("}");
diff --git a/ICSharpCode.Decompiler/IL/Instructions/CallInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/CallInstruction.cs
index ccdf1f5fa..ab61817eb 100644
--- a/ICSharpCode.Decompiler/IL/Instructions/CallInstruction.cs
+++ b/ICSharpCode.Decompiler/IL/Instructions/CallInstruction.cs
@@ -98,16 +98,19 @@ namespace ICSharpCode.Decompiler.IL
///
/// Gets the expected stack type for passing the this pointer in a method call.
- /// Returns StackType.O for reference types (this pointer passed as object reference),
+ /// Returns StackType.Ref if constrainedTo is not null,
+ /// StackType.O for reference types (this pointer passed as object reference),
/// and StackType.Ref for type parameters and value types (this pointer passed as managed reference).
///
/// Returns StackType.Unknown if the input type is unknown.
///
- internal static StackType ExpectedTypeForThisPointer(IType type)
+ internal static StackType ExpectedTypeForThisPointer(IType declaringType, IType? constrainedTo)
{
- if (type.Kind == TypeKind.TypeParameter)
+ if (constrainedTo != null)
return StackType.Ref;
- switch (type.IsReferenceType)
+ if (declaringType.Kind == TypeKind.TypeParameter)
+ return StackType.Ref;
+ switch (declaringType.IsReferenceType)
{
case true:
return StackType.O;
@@ -125,7 +128,7 @@ namespace ICSharpCode.Decompiler.IL
Debug.Assert(Method.Parameters.Count + firstArgument == Arguments.Count);
if (firstArgument == 1)
{
- if (!(Arguments[0].ResultType == ExpectedTypeForThisPointer(ConstrainedTo ?? Method.DeclaringType)))
+ if (!(Arguments[0].ResultType == ExpectedTypeForThisPointer(Method.DeclaringType, ConstrainedTo)))
Debug.Fail($"Stack type mismatch in 'this' argument in call to {Method.Name}()");
}
for (int i = 0; i < Method.Parameters.Count; ++i)
diff --git a/ICSharpCode.Decompiler/IL/Instructions/PatternMatching.cs b/ICSharpCode.Decompiler/IL/Instructions/PatternMatching.cs
index 5c5391723..25e9a6498 100644
--- a/ICSharpCode.Decompiler/IL/Instructions/PatternMatching.cs
+++ b/ICSharpCode.Decompiler/IL/Instructions/PatternMatching.cs
@@ -581,5 +581,17 @@ namespace ICSharpCode.Decompiler.IL
{
return this;
}
+
+ public bool MatchLdObj([NotNullWhen(true)] out ILInstruction? target, IType type)
+ {
+ var inst = this as LdObj;
+ if (inst != null && inst.Type.Equals(type))
+ {
+ target = inst.Target;
+ return true;
+ }
+ target = default(ILInstruction);
+ return false;
+ }
}
}
diff --git a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs
index 1b8d09264..57b475694 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs
@@ -475,6 +475,28 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
}
+ protected internal override void VisitLdObjIfRef(LdObjIfRef inst)
+ {
+ base.VisitLdObjIfRef(inst);
+ if (inst.Target is AddressOf)
+ {
+ context.Step("ldobj.if.ref(addressof(...)) -> addressof(...)", inst);
+ // there already is a temporary, so the ldobj.if.ref is a no-op in both cases
+ inst.ReplaceWith(inst.Target);
+ return;
+ }
+ if (inst.Target.MatchLdLoc(out var s) && s.IsSingleDefinition && s.LoadCount == 1
+ && s.StoreInstructions.SingleOrDefault() is StLoc {
+ Value: LdLoca { Variable: { AddressCount: 1, StoreCount: 1 } }
+ })
+ {
+ context.Step("Single use of ldobj.if.ref(ldloc v) -> ldloc v", inst);
+ // there already is a temporary, so the ldobj.if.ref is a no-op in both cases
+ inst.ReplaceWith(inst.Target);
+ return;
+ }
+ }
+
protected internal override void VisitStObj(StObj inst)
{
base.VisitStObj(inst);
diff --git a/ICSharpCode.Decompiler/IL/Transforms/IILTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/IILTransform.cs
index e42551471..347e8e35c 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/IILTransform.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/IILTransform.cs
@@ -79,6 +79,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{
return new ILReader(TypeSystem.MainModule) {
UseDebugSymbols = Settings.UseDebugSymbols,
+ UseRefLocalsForAccurateOrderOfEvaluation = Settings.UseRefLocalsForAccurateOrderOfEvaluation,
DebugInfo = DebugInfo
};
}
diff --git a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
index 3e2c743c8..2b92f6cc5 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
@@ -170,14 +170,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
public static bool InlineOneIfPossible(Block block, int pos, InliningOptions options, ILTransformContext context)
{
context.CancellationToken.ThrowIfCancellationRequested();
- StLoc stloc = block.Instructions[pos] as StLoc;
- if (stloc == null || stloc.Variable.Kind == VariableKind.PinnedLocal)
+ if (block.Instructions[pos] is not StLoc stloc)
return false;
- ILVariable v = stloc.Variable;
- // ensure the variable is accessed only a single time
- if (v.StoreCount != 1)
- return false;
- if (v.LoadCount > 1 || v.LoadCount + v.AddressCount != 1)
+ if (!VariableCanBeUsedForInlining(stloc.Variable))
return false;
// TODO: inlining of small integer types might be semantically incorrect,
// but we can't avoid it this easily without breaking lots of tests.
@@ -186,6 +181,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return InlineOne(stloc, options, context);
}
+ public static bool VariableCanBeUsedForInlining(ILVariable v)
+ {
+ if (v.Kind == VariableKind.PinnedLocal)
+ return false;
+ // ensure the variable is accessed only a single time
+ if (v.StoreCount != 1)
+ return false;
+ if (v.LoadCount + v.AddressCount != 1)
+ return false;
+ return true;
+ }
+
///
/// Inlines the stloc instruction at block.Instructions[pos] into the next instruction.
///
@@ -394,6 +401,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (ldloca.Variable.Type.IsReferenceType ?? false)
return false;
ILInstruction inst = ldloca;
+ if (inst.Parent is LdObjIfRef)
+ {
+ inst = inst.Parent;
+ }
while (inst.Parent is LdFlda ldflda)
{
inst = ldflda;
@@ -904,6 +915,30 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return true;
}
+ ///
+ /// Gets whether 'expressionBeingMoved' can be moved from somewhere before 'stmt' to become the replacement of 'targetLoad'.
+ ///
+ public static bool CanMoveIntoCallVirt(ILInstruction expressionBeingMoved, ILInstruction stmt, CallVirt nestedCallVirt, ILInstruction targetLoad)
+ {
+ Debug.Assert(targetLoad.IsDescendantOf(stmt) && nestedCallVirt.IsDescendantOf(stmt));
+ ILInstruction thisArg = nestedCallVirt.Arguments[0];
+ Debug.Assert(thisArg is LdObjIfRef);
+ for (ILInstruction inst = targetLoad; inst != stmt; inst = inst.Parent)
+ {
+ if (!inst.Parent.CanInlineIntoSlot(inst.ChildIndex, expressionBeingMoved))
+ return false;
+ // Check whether re-ordering with predecessors is valid:
+ int childIndex = inst.ChildIndex;
+ for (int i = 0; i < childIndex; ++i)
+ {
+ ILInstruction predecessor = inst.Parent.Children[i];
+ if (predecessor != thisArg && !IsSafeForInlineOver(predecessor, expressionBeingMoved))
+ return false;
+ }
+ }
+ return true;
+ }
+
///
/// Gets whether arg can be un-inlined out of stmt.
///
diff --git a/ICSharpCode.Decompiler/IL/Transforms/NamedArgumentTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/NamedArgumentTransform.cs
index 41e30e598..4454bf75e 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/NamedArgumentTransform.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/NamedArgumentTransform.cs
@@ -99,7 +99,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (call.IsInstanceCall)
{
IType thisVarType = call.ConstrainedTo ?? call.Method.DeclaringType;
- if (CallInstruction.ExpectedTypeForThisPointer(thisVarType) == StackType.Ref)
+ if (CallInstruction.ExpectedTypeForThisPointer(call.Method.DeclaringType, call.ConstrainedTo) == StackType.Ref)
{
thisVarType = new ByReferenceType(thisVarType);
}
diff --git a/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs
index cb7d2161c..7a4b18deb 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs
@@ -283,6 +283,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{
inst = arg;
}
+ else if (inst is LdObjIfRef ldObjIfRef)
+ {
+ inst = ldObjIfRef.Target;
+ }
// ensure the access chain does not contain any 'nullable.unwrap' that aren't directly part of the chain
if (ArgumentsAfterFirstMayUnwrapNull(call.Arguments))
return false;
@@ -362,7 +366,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
&& arg.MatchLdLoc(testedVar);
case Mode.UnconstrainedType:
// unconstrained generic type (expect: ldloc(testedVar))
- return inst.MatchLdLoc(testedVar);
+ return inst.MatchLdLoc(testedVar) || (inst.MatchLdObjIfRef(out var testedVarLoad, out _) && testedVarLoad.MatchLdLoc(testedVar));
default:
throw new ArgumentOutOfRangeException(nameof(mode));
}
diff --git a/ICSharpCode.Decompiler/IL/Transforms/RemoveInfeasiblePathTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/RemoveInfeasiblePathTransform.cs
index a337ea1ac..1a84016bf 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/RemoveInfeasiblePathTransform.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/RemoveInfeasiblePathTransform.cs
@@ -54,8 +54,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
}
}
-
-
private bool DoTransform(Block block, ILTransformContext context)
{
if (!MatchBlock1(block, out var s, out int value, out var br))
diff --git a/ICSharpCode.Decompiler/IL/Transforms/RemoveUnconstrainedGenericReferenceTypeCheck.cs b/ICSharpCode.Decompiler/IL/Transforms/RemoveUnconstrainedGenericReferenceTypeCheck.cs
new file mode 100644
index 000000000..83f48b5f6
--- /dev/null
+++ b/ICSharpCode.Decompiler/IL/Transforms/RemoveUnconstrainedGenericReferenceTypeCheck.cs
@@ -0,0 +1,145 @@
+// Copyright (c) 2025 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.
+
+#nullable enable
+
+using System.Collections.Generic;
+using System.Linq;
+
+namespace ICSharpCode.Decompiler.IL.Transforms
+{
+ ///
+ /// [stloc address(...)]
+ /// stloc temp(default.value ``0)
+ /// if (comp.o(ldloc temp == ldnull)) Block IL_002a {
+ /// stloc temp(ldobj ``0(ldloc address))
+ /// stloc address(ldloca temp)
+ /// }
+ /// stloc V_i(expr_i)
+ /// ...(constrained[``0].callvirt Method(ldobj.if.ref ``0(ldloc address), ldloc V_i ...))...
+ ///
+ ///=>
+ ///
+ /// [stloc address(...)]
+ /// stloc address(ldobj.if.ref(ldloc address))
+ /// stloc V_i(expr_i)
+ /// ...(constrained[``0].callvirt Method(ldobj.if.ref ``0(ldloc address), ldloc V_i ...))...
+ ///
+ /// Then ldobj.if.ref in the call is redundant because any object reference was already loaded into an immutable temporary.
+ /// So we can removed and inlining of the arguments (V_i) becomes possible.
+ ///
+ /// Finally the newly created ldobj.if.ref is inlined into the place where the old ldobj.if.ref was.
+ ///
+ /// =>
+ ///
+ /// [stloc address(...)]
+ /// ...(constrained[``0].callvirt Method(ldobj.if.ref ``0(ldloc address), expr_i ...))...
+ ///
+ ///
+ class RemoveUnconstrainedGenericReferenceTypeCheck : IStatementTransform
+ {
+ void IStatementTransform.Run(Block block, int pos, StatementTransformContext context)
+ {
+ int startPos = pos;
+ // stloc temp(default.value ``0)
+ if (!block.Instructions[pos].MatchStLoc(out var temp, out var defaultValue))
+ return;
+ if (!defaultValue.MatchDefaultValue(out var type))
+ return;
+ if (temp.StoreCount != 2 || temp.LoadCount != 1 || temp.AddressCount != 1)
+ return;
+ pos++;
+ // if (comp.o(ldloc temp == ldnull)) Block IL_002a {
+ // stloc temp(ldobj ``0(ldloc address))
+ // stloc address(ldloca temp)
+ // }
+ if (block.Instructions.ElementAtOrDefault(pos) is not IfInstruction ifInst)
+ return;
+ if (!ifInst.Condition.MatchCompEqualsNull(out var tempLoadForNullCheck))
+ return;
+ if (!tempLoadForNullCheck.MatchLdLoc(temp))
+ return;
+ if (ifInst.TrueInst is not Block dereferenceBlock)
+ return;
+ if (ifInst.FalseInst is not Nop)
+ return;
+ if (dereferenceBlock.Instructions is not [StLoc tempStore, StLoc addressReassign])
+ return;
+ if (tempStore.Variable != temp)
+ return;
+ if (!tempStore.Value.MatchLdObj(out var addressLoadForLdObj, type))
+ return;
+ if (!addressLoadForLdObj.MatchLdLoc(addressReassign.Variable))
+ return;
+ if (!addressReassign.Value.MatchLdLoca(temp))
+ return;
+ var address = addressReassign.Variable;
+ if (address.StoreCount != 2 || address.LoadCount != 2 || address.AddressCount != 0)
+ return;
+ pos++;
+ // pos now is the first store to V_i
+ // ...(constrained[``0].callvirt Method(ldobj.if.ref ``0(ldloc address), ldloc V_i ...))...
+ var callTarget = address.LoadInstructions.Single(l => addressLoadForLdObj != l);
+ if (callTarget.Parent is not LdObjIfRef { Parent: CallVirt call } ldobjIfRef)
+ return;
+ if (call.Arguments.Count == 0 || call.Arguments[0] != ldobjIfRef || !type.Equals(call.ConstrainedTo))
+ return;
+ ILInstruction containingStmt = call;
+ while (containingStmt.Parent != block)
+ {
+ if (containingStmt.Parent == null)
+ return;
+ containingStmt = containingStmt.Parent;
+ }
+ if (containingStmt.ChildIndex < pos)
+ return;
+ // check if we can inline all temporaries used in the call:
+ int temporaryInitIndex = containingStmt.ChildIndex - 1;
+ List<(ILInstruction, ILInstruction)> replacements = new();
+ for (int argIndex = call.Arguments.Count - 1; argIndex > 0 && temporaryInitIndex >= pos; argIndex--)
+ {
+ var argument = call.Arguments[argIndex];
+ switch (argument)
+ {
+ case LdLoc load:
+ if (block.Instructions[temporaryInitIndex].MatchStLoc(load.Variable, out var expr) && ILInlining.VariableCanBeUsedForInlining(load.Variable))
+ {
+ if (!ILInlining.CanMoveIntoCallVirt(expr, containingStmt, call, argument))
+ {
+ return;
+ }
+ replacements.Add((argument, expr));
+ temporaryInitIndex--;
+ }
+ break;
+ }
+ }
+ // all stores to V_i processed?
+ if (temporaryInitIndex != pos - 1)
+ {
+ return;
+ }
+ context.Step("RemoveUnconstrainedGenericReferenceTypeCheck", block.Instructions[startPos]);
+ foreach (var (argument, expr) in replacements)
+ {
+ argument.ReplaceWith(expr);
+ }
+ block.Instructions.RemoveRange(startPos, containingStmt.ChildIndex - startPos);
+ }
+ }
+}
\ No newline at end of file
diff --git a/ICSharpCode.Decompiler/IL/Transforms/SplitVariables.cs b/ICSharpCode.Decompiler/IL/Transforms/SplitVariables.cs
index 4f3bbd698..4699f9f57 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/SplitVariables.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/SplitVariables.cs
@@ -107,6 +107,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
case LdObj _:
case StObj stobj when stobj.Target == addressLoadingInstruction:
return AddressUse.Immediate;
+ case LdObjIfRef:
+ // This is either an immediate use, or we need to check how the parent uses the address.
+ return DetermineAddressUse(addressLoadingInstruction.Parent, targetVar);
case LdFlda ldflda:
return DetermineAddressUse(ldflda, targetVar);
case Await await:
diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs
index c632e22a2..ea32c3972 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs
@@ -318,6 +318,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (resolveContext != null && !IsMethodApplicable(method, call.Arguments, rootType, resolveContext, settings))
goto default;
inst = call.Arguments[0];
+ if (inst is LdObjIfRef ldObjIfRef)
+ {
+ inst = ldObjIfRef.Target;
+ }
if (method.IsAccessor)
{
if (method.AccessorOwner is IProperty property &&
@@ -371,6 +375,22 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
goto default;
}
+ case LdObjIfRef ldobj:
+ {
+ if (ldobj.Target is LdFlda ldflda && (kind != AccessPathKind.Setter || !ldflda.Field.IsReadOnly))
+ {
+ path.Insert(0, new AccessPathElement(ldobj.OpCode, ldflda.Field));
+ inst = ldflda.Target;
+ break;
+ }
+ if (ldobj.Target is LdLoca ldloca)
+ {
+ target = ldloca.Variable;
+ inst = null;
+ break;
+ }
+ goto default;
+ }
case StObj stobj:
{
if (stobj.Target is LdFlda ldflda)
diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs
index 2f4121b2a..b60c1e86a 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs
@@ -643,7 +643,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
ILInstruction PrepareCallTarget(IType expectedType, ILInstruction target, IType targetType)
{
ILInstruction result;
- switch (CallInstruction.ExpectedTypeForThisPointer(expectedType))
+ switch (CallInstruction.ExpectedTypeForThisPointer(expectedType, null))
{
case StackType.Ref:
if (target.ResultType == StackType.Ref)
diff --git a/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs
index 020166ac0..26f8cbda2 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs
@@ -316,7 +316,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// the null check of reference types might have been transformed into "objVar?.Dispose();"
if (!(rewrap.Argument is CallVirt cv))
return false;
- if (!(cv.Arguments.FirstOrDefault() is NullableUnwrap unwrap))
+ target = cv.Arguments.FirstOrDefault();
+ if (target is LdObjIfRef ldObjIfRef)
+ target = ldObjIfRef.Target;
+ if (!(target is NullableUnwrap unwrap))
return false;
numObjVarLoadsInCheck = 1;
disposeCall = cv;
@@ -342,6 +345,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
target = cv.Arguments.FirstOrDefault();
if (target == null)
return false;
+ if (target is LdObjIfRef ldObjIfRef)
+ target = ldObjIfRef.Target;
if (target.MatchBox(out var newTarget, out var type) && type.Equals(objVar.Type))
target = newTarget;
else if (isInlinedIsInst && target.MatchIsInst(out newTarget, out type) && type.IsKnownType(disposeTypeCode))
diff --git a/ILSpy/Languages/ILAstLanguage.cs b/ILSpy/Languages/ILAstLanguage.cs
index 46ccd0fe4..948a79511 100644
--- a/ILSpy/Languages/ILAstLanguage.cs
+++ b/ILSpy/Languages/ILAstLanguage.cs
@@ -109,6 +109,7 @@ namespace ICSharpCode.ILSpy
var typeSystem = new DecompilerTypeSystem(module, assemblyResolver);
var reader = new ILReader(typeSystem.MainModule);
reader.UseDebugSymbols = options.DecompilerSettings.UseDebugSymbols;
+ reader.UseRefLocalsForAccurateOrderOfEvaluation = options.DecompilerSettings.UseRefLocalsForAccurateOrderOfEvaluation;
var methodBody = module.GetMethodBody(methodDef.RelativeVirtualAddress);
ILFunction il = reader.ReadIL((SRM.MethodDefinitionHandle)method.MetadataToken, methodBody, kind: ILFunctionKind.TopLevelFunction, cancellationToken: options.CancellationToken);
var decompiler = new CSharpDecompiler(typeSystem, options.DecompilerSettings) { CancellationToken = options.CancellationToken };