From 6e2842992cfba0c772b6592efd2407e2593644f8 Mon Sep 17 00:00:00 2001 From: upstream Date: Sat, 10 Oct 2026 13:07:54 +0200 Subject: [PATCH] Upstream icsharpcode/ILSpy state of the touched files before 29888045ca --- .../TestCases/Pretty/LiftedOperators.cs | 1072 +++++++++++++++++ .../IL/Transforms/ExpressionTransforms.cs | 1044 ++++++++++++++++ 2 files changed, 2116 insertions(+) create mode 100644 ICSharpCode.Decompiler.Tests/TestCases/Pretty/LiftedOperators.cs create mode 100644 ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LiftedOperators.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LiftedOperators.cs new file mode 100644 index 0000000..e92b8ba --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LiftedOperators.cs @@ -0,0 +1,1072 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team +// +// 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.Runtime.InteropServices; + +namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty +{ + public static class T00_LiftedOperators + { + // C# uses 4 different patterns of IL for lifted operators: bool, other primitive types, decimal, other structs. + // Different patterns are used depending on whether both of the operands are nullable or only the left/right operand is nullable. + // Negation must not be pushed through such comparisons because it would change the semantics (except for equality/inequality). + // A comparison used in a condition differs somewhat from a comparison used as a simple value. + + public static void BoolBasic(bool? a, bool? b) + { + if (a == b) + { + Console.WriteLine(); + } + if (a != b) + { + Console.WriteLine(); + } + } + + public static void BoolComplex(bool? a, Func x) + { + if (a == x()) + { + Console.WriteLine(); + } + if (a != x()) + { + Console.WriteLine(); + } + + if (x() == a) + { + Console.WriteLine(); + } + if (x() != a) + { + Console.WriteLine(); + } + if (a ?? x()) + { + Console.WriteLine(); + } + } + + public static void BoolConst(bool? a) + { + if (a == true) + { + Console.WriteLine(); + } + if (a != true) + { + Console.WriteLine(); + } + if (a == false) + { + Console.WriteLine(); + } + if (a != false) + { + Console.WriteLine(); + } + if (a ?? true) + { + Console.WriteLine(); + } +#if !ROSLYN + // Roslyn 3 (VS2019) started optimizing this to "a.GetValueOrDefault()" + if (a ?? false) + { + Console.WriteLine(); + } +#endif + } + + public static void BoolValueBasic(bool? a, bool? b) + { + Console.WriteLine(a == b); + Console.WriteLine(a != b); + + Console.WriteLine(a & b); + Console.WriteLine(a | b); + Console.WriteLine(a ^ b); + Console.WriteLine(a ?? b); + Console.WriteLine(!a); + a &= b; + a |= b; + a ^= b; + } + + public static void BoolValueComplex(bool? a, Func x) + { + Console.WriteLine(a == x()); + Console.WriteLine(a != x()); + + Console.WriteLine(x() == a); + Console.WriteLine(x() != a); + + //Console.WriteLine(a & x()); // we currently can't tell the order + //Console.WriteLine(a | x()); // of the operands in bool [&|] bool? + Console.WriteLine(a ^ x()); + Console.WriteLine(a ?? x()); + //a &= x(); -- also affected by order of operand problem + //a |= x(); + a ^= x(); + + Console.WriteLine(x() & a); + Console.WriteLine(x() | a); + Console.WriteLine(x() ^ a); + (new bool?[0])[0] ^= x(); + (new bool?[0])[0] ^= a; + } + + public static void BoolValueConst(bool? a) + { + Console.WriteLine(a == true); + Console.WriteLine(a != true); + Console.WriteLine(a == false); + Console.WriteLine(a != false); + Console.WriteLine(a ?? true); +#if !ROSLYN + // Roslyn 3 (VS2019) started optimizing this to "a.GetValueOrDefault()" + Console.WriteLine(a ?? false); +#endif + } + + public static void IntBasic(int? a, int? b) + { + if (a == b) + { + Console.WriteLine(); + } + if (a != b) + { + Console.WriteLine(); + } + if (a > b) + { + Console.WriteLine(); + } + if (a < b) + { + Console.WriteLine(); + } + if (a >= b) + { + Console.WriteLine(); + } + if (a <= b) + { + Console.WriteLine(); + } + + if (!(a > b)) + { + Console.WriteLine(); + } + if (!(a <= b)) + { + Console.WriteLine(); + } + } + + public static void IntComplex(int? a, Func x) + { + if (a == x()) + { + Console.WriteLine(); + } + if (a != x()) + { + Console.WriteLine(); + } + if (a > x()) + { + Console.WriteLine(); + } + + if (x() == a) + { + Console.WriteLine(); + } + if (x() != a) + { + Console.WriteLine(); + } + if (x() > a) + { + Console.WriteLine(); + } + + if (!(a > x())) + { + Console.WriteLine(); + } + if (!(a <= x())) + { + Console.WriteLine(); + } + } + + public static void IntConst(int? a) + { + if (a == 2) + { + Console.WriteLine(); + } + if (a != 2) + { + Console.WriteLine(); + } + if (a > 2) + { + Console.WriteLine(); + } + + if (2 == a) + { + Console.WriteLine(); + } + if (2 != a) + { + Console.WriteLine(); + } + if (2 > a) + { + Console.WriteLine(); + } + } + + public static void IntValueBasic(int? a, int? b) + { + Console.WriteLine(a == b); + Console.WriteLine(a != b); + Console.WriteLine(a > b); + + Console.WriteLine(!(a > b)); + Console.WriteLine(!(a >= b)); + + Console.WriteLine(a + b); + Console.WriteLine(a - b); + Console.WriteLine(a * b); + Console.WriteLine(a / b); + Console.WriteLine(a % b); + Console.WriteLine(a & b); + Console.WriteLine(a | b); + Console.WriteLine(a ^ b); + Console.WriteLine(a << b); + Console.WriteLine(a >> b); + Console.WriteLine(a ?? b); + Console.WriteLine(-a); + Console.WriteLine(~a); + // TODO: + //Console.WriteLine(a++); + //Console.WriteLine(a--); + Console.WriteLine(++a); + Console.WriteLine(--a); + a += b; + a -= b; + a *= b; + a /= b; + a %= b; + a &= b; + a |= b; + a ^= b; + a <<= b; + a >>= b; + } + + public static void IntValueComplex(int? a, Func x) + { + Console.WriteLine(a == x()); + Console.WriteLine(a != x()); + Console.WriteLine(a > x()); + + Console.WriteLine(x() == a); + Console.WriteLine(x() != a); + Console.WriteLine(x() > a); + + Console.WriteLine(a + x()); + Console.WriteLine(a - x()); + Console.WriteLine(a * x()); + Console.WriteLine(a / x()); + Console.WriteLine(a % x()); + Console.WriteLine(a & x()); + Console.WriteLine(a | x()); + Console.WriteLine(a ^ x()); + Console.WriteLine(a << x()); + Console.WriteLine(a >> x()); + Console.WriteLine(a ?? x()); + a += x(); + a -= x(); + a *= x(); + a /= x(); + a %= x(); + a &= x(); + a |= x(); + a ^= x(); + a <<= x(); + a >>= x(); + + Console.WriteLine(x() + a); + (new int?[0])[0] += x(); + } + + public static void IntValueConst(int? a) + { + Console.WriteLine(a == 2); + Console.WriteLine(a != 2); + Console.WriteLine(a > 2); + + Console.WriteLine(2 == a); + Console.WriteLine(2 != a); + Console.WriteLine(2 > a); + + Console.WriteLine(a + 2); + Console.WriteLine(a - 2); + Console.WriteLine(a * 2); + Console.WriteLine(a / 2); + Console.WriteLine(a % 2); + Console.WriteLine(a & 2); + Console.WriteLine(a | 2); + Console.WriteLine(a ^ 2); + Console.WriteLine(a << 2); + Console.WriteLine(a >> 2); + Console.WriteLine(a ?? 2); + a += 2; + a -= 2; + a *= 2; + a /= 2; + a %= 2; + a &= 2; + a |= 2; + a ^= 2; + a <<= 2; + a >>= 2; + + Console.WriteLine(2 + a); + } + + public static void NumberBasic(decimal? a, decimal? b) + { + if (a == b) + { + Console.WriteLine(); + } +#if ROSLYN2 + // Roslyn 2.9 started invoking op_Equality even if the source code says 'a != b' + if (!(a == b)) + { + Console.WriteLine(); + } +#else + if (a != b) + { + Console.WriteLine(); + } +#endif + if (a > b) + { + Console.WriteLine(); + } + if (a < b) + { + Console.WriteLine(); + } + if (a >= b) + { + Console.WriteLine(); + } + if (a <= b) + { + Console.WriteLine(); + } + + if (!(a > b)) + { + Console.WriteLine(); + } + if (!(a < b)) + { + Console.WriteLine(); + } + } + + public static void NumberComplex(decimal? a, Func x) + { + // Tests deactivated because we insert redundant casts; + // TODO: revisit after decision has been made regarding the type system. + //if (a == x()) { + // Console.WriteLine(); + //} + //if (a != x()) { + // Console.WriteLine(); + //} + //if (a > x()) { + // Console.WriteLine(); + //} + + //if (x() == a) { + // Console.WriteLine(); + //} + //if (x() != a) { + // Console.WriteLine(); + //} + //if (x() > a) { + // Console.WriteLine(); + //} + } + + public static void NumberConst(decimal? a) + { + // Tests deactivated because we insert redundant casts; + // TODO: revisit after decision has been made regarding the type system. + //if (a == 2m) { + // Console.WriteLine(); + //} + //if (a != 2m) { + // Console.WriteLine(); + //} + //if (a > 2m) { + // Console.WriteLine(); + //} + + //if (2m == a) { + // Console.WriteLine(); + //} + //if (2m != a) { + // Console.WriteLine(); + //} + //if (2m > a) { + // Console.WriteLine(); + //} + } + + public static void NumberValueBasic(decimal? a, decimal? b) + { + Console.WriteLine(a == b); +#if ROSLYN + // Roslyn 2.9 started invoking op_Equality even if the source code says 'a != b' + Console.WriteLine(!(a == b)); +#else + Console.WriteLine(a != b); +#endif + Console.WriteLine(a > b); + + Console.WriteLine(!(a > b)); + Console.WriteLine(!(a <= b)); + + Console.WriteLine(a + b); + Console.WriteLine(a - b); + Console.WriteLine(a * b); + Console.WriteLine(a / b); + Console.WriteLine(a % b); + Console.WriteLine(a ?? b); + Console.WriteLine(-a); + // TODO: + //Console.WriteLine(a++); + //Console.WriteLine(a--); + //Console.WriteLine(++a); + //Console.WriteLine(--a); + a += b; + a -= b; + a *= b; + a /= b; + a %= b; + } + + public static void NumberValueComplex(decimal? a, Func x) + { + // Tests deactivated because we insert redundant casts; + // TODO: revisit after decision has been made regarding the type system. + //Console.WriteLine(a == x()); + //Console.WriteLine(a != x()); + //Console.WriteLine(a > x()); + + //Console.WriteLine(x() == a); + //Console.WriteLine(x() != a); + //Console.WriteLine(x() > a); + + //Console.WriteLine(a + x()); + //Console.WriteLine(a - x()); + //Console.WriteLine(a * x()); + //Console.WriteLine(a / x()); + //Console.WriteLine(a % x()); + //Console.WriteLine(a ?? x()); + //a += x(); + //a -= x(); + //a *= x(); + //a /= x(); + //a %= x(); + + //Console.WriteLine(x() + a); + //(new decimal?[0])[0] += x(); + } + + public static void NumberValueConst(decimal? a) + { + // Tests deactivated because we insert redundant casts; + // TODO: revisit after decision has been made regarding the type system. + //Console.WriteLine(a == 2m); + //Console.WriteLine(a != 2m); + //Console.WriteLine(a > 2m); + + //Console.WriteLine(2m == a); + //Console.WriteLine(2m != a); + //Console.WriteLine(2m > a); + + //Console.WriteLine(a + 2m); + //Console.WriteLine(a - 2m); + //Console.WriteLine(a * 2m); + //Console.WriteLine(a / 2m); + //Console.WriteLine(a % 2m); + //Console.WriteLine(a ?? 2m); + //a += 2m; + //a -= 2m; + //a *= 2m; + //a /= 2m; + //a %= 2m; + + //Console.WriteLine(2m + a); + } + + public static void CompareWithImplictCast(int? a, long? b) + { + if (a < b) + { + Console.WriteLine(); + } + if (a == b) + { + Console.WriteLine(); + } + // TODO: unnecessary cast + //if (a < 10L) { + // Console.WriteLine(); + //} + //if (a == 10L) { + // Console.WriteLine(); + //} + } + + public static void CompareWithSignChange(int? a, int? b) + { + if ((uint?)a < (uint?)b) + { + Console.WriteLine(); + } + // TODO: unnecessary cast + //if ((uint?)a < 10) { + // Console.WriteLine(); + //} + } + + public static void StructBasic(TS? a, TS? b) + { + if (a == b) + { + Console.WriteLine(); + } + if (a != b) + { + Console.WriteLine(); + } + if (a > b) + { + Console.WriteLine(); + } + if (a < b) + { + Console.WriteLine(); + } + if (a >= b) + { + Console.WriteLine(); + } + if (a <= b) + { + Console.WriteLine(); + } + + if (!(a == b)) + { + Console.WriteLine(); + } + if (!(a != b)) + { + Console.WriteLine(); + } + if (!(a > b)) + { + Console.WriteLine(); + } + } + + public static void StructComplex(TS? a, Func x) + { + // Tests deactivated because we insert redundant casts; + // TODO: revisit after decision has been made regarding the type system. + //if (a == x()) { + // Console.WriteLine(); + //} + //if (a != x()) { + // Console.WriteLine(); + //} + //if (a > x()) { + // Console.WriteLine(); + //} + + //if (x() == a) { + // Console.WriteLine(); + //} + //if (x() != a) { + // Console.WriteLine(); + //} + //if (x() > a) { + // Console.WriteLine(); + //} + } + + public static void StructValueBasic(TS? a, TS? b, int? i) + { + Console.WriteLine(a == b); + Console.WriteLine(a != b); + Console.WriteLine(a > b); + + Console.WriteLine(!(a == b)); + Console.WriteLine(!(a != b)); + Console.WriteLine(!(a > b)); + + Console.WriteLine(a + b); + Console.WriteLine(a - b); + Console.WriteLine(a * b); + Console.WriteLine(a / b); + Console.WriteLine(a % b); + Console.WriteLine(a & b); + Console.WriteLine(a | b); + Console.WriteLine(a ^ b); + Console.WriteLine(a << i); + Console.WriteLine(a >> i); + Console.WriteLine(a ?? b); + Console.WriteLine(+a); + Console.WriteLine(-a); + Console.WriteLine(!a); + Console.WriteLine(~a); + // TODO: + //Console.WriteLine(a++); + //Console.WriteLine(a--); + //Console.WriteLine(++a); + //Console.WriteLine(--a); + //Console.WriteLine((int?)a); + a += b; + a -= b; + a *= b; + a /= b; + a %= b; + a &= b; + a |= b; + a ^= b; + a <<= i; + a >>= i; + } + + public static void StructValueComplex(TS? a, Func x, Func i) + { + // Tests deactivated because we insert redundant casts; + // TODO: revisit after decision has been made regarding the type system. + //Console.WriteLine(a == x()); + //Console.WriteLine(a != x()); + //Console.WriteLine(a > x()); + + //Console.WriteLine(x() == a); + //Console.WriteLine(x() != a); + //Console.WriteLine(x() > a); + + //Console.WriteLine(a + x()); + //Console.WriteLine(a - x()); + //Console.WriteLine(a * x()); + //Console.WriteLine(a / x()); + //Console.WriteLine(a % x()); + //Console.WriteLine(a & x()); + //Console.WriteLine(a | x()); + //Console.WriteLine(a ^ x()); + //Console.WriteLine(a << i()); + //Console.WriteLine(a >> i()); + //Console.WriteLine(a ?? x()); + //a += x(); + //a -= x(); + //a *= x(); + //a /= x(); + //a %= x(); + //a &= x(); + //a |= x(); + //a ^= x(); + //a <<= i(); + //a >>= i(); + + //Console.WriteLine(x() + a); + //(new TS?[0])[0] += x(); + } + + public static bool RetEq(int? a, int? b) + { + return a == b; + } + + public static bool RetEqConv(long? a, int? b) + { + return a == b; + } + + public static bool RetEqConst(long? a) + { + return a == 10; + } + + public static bool RetIneqConst(long? a) + { + return a != 10; + } + + public static bool RetLt(int? a, int? b) + { + return a < b; + } + + public static bool RetLtConst(int? a) + { + return a < 10; + } + + public static bool RetLtConv(long? a, int? b) + { + return a < b; + } + + public static bool RetNotLt(int? a, int? b) + { + return !(a < b); + } + } + + internal class T01_LiftedImplicitConversions + { + public int? ExtendI4(byte? b) + { + return b; + } + + public int? ExtendToI4(sbyte? b) + { + return b; + } + + public long? ExtendI8(byte? b) + { + return b; + } + + public long? ExtendToI8(sbyte? b) + { + return b; + } + + public long? ExtendI8(int? b) + { + return b; + } + + public long? ExtendToI8(uint? b) + { + return b; + } + + // TODO: unnecessary cast + //public double? ToFloat(int? b) + //{ + // return b; + //} + + //public long? InArithmetic(uint? b) + //{ + // return 100L + b; + //} + + public long? AfterArithmetic(uint? b) + { + return 100 + b; + } + + // TODO: unnecessary cast + //public static double? InArithmetic2(float? nf, double? nd, float f) + //{ + // return nf + nd + f; + //} + + public static long? InArithmetic3(int? a, long? b, int? c, long d) + { + return a + b + c + d; + } + } + + internal class T02_LiftedExplicitConversions + { + private static void Print(T? x) where T : struct + { + Console.WriteLine(x); + } + + public static void UncheckedCasts(int? i4, long? i8, float? f) + { + Print((byte?)i4); + Print((short?)i4); + Print((uint?)i4); + Print((uint?)i8); + Print((uint?)f); + } + + public static void CheckedCasts(int? i4, long? i8, float? f) + { + checked + { + Print((byte?)i4); + Print((short?)i4); + Print((uint?)i4); + Print((uint?)i8); + //Print((uint?)f); TODO + } + } + } + + internal class T03_NullCoalescingTests + { + private static void Print(T x) + { + Console.WriteLine(x); + } + + public static void Objects(object a, object b) + { + Print(a ?? b); + } + + public static void Nullables(int? a, int? b) + { + Print(a ?? b); + } + + public static void NullableWithNonNullableFallback(int? a, int b) + { + Print(a ?? b); + } + + public static void NullableWithImplicitConversion(short? a, int? b) + { + Print(a ?? b); + } + + public static void NullableWithImplicitConversionAndNonNullableFallback(short? a, int b) + { + // TODO: unnecessary cast + //Print(a ?? b); + } + + public static void Chain(int? a, int? b, int? c, int d) + { + Print(a ?? b ?? c ?? d); + } + + public static void ChainWithImplicitConversions(int? a, short? b, long? c, byte d) + { + // TODO: unnecessary casts + //Print(a ?? b ?? c ?? d); + } + + public static void ChainWithComputation(int? a, short? b, long? c, byte d) + { + // TODO: unnecessary casts + //Print((a + 1) ?? (b + 2) ?? (c + 3) ?? (d + 4)); + } + + public static object ReturnObjects(object a, object b) + { + return a ?? b; + } + + public static int? ReturnNullables(int? a, int? b) + { + return a ?? b; + } + + public static int ReturnNullableWithNonNullableFallback(int? a, int b) + { + return a ?? b; + } + + public static int ReturnChain(int? a, int? b, int? c, int d) + { + return a ?? b ?? c ?? d; + } + + public static long ReturnChainWithImplicitConversions(int? a, short? b, long? c, byte d) + { + //TODO: unnecessary casts + //return a ?? b ?? c ?? d; + return 0L; + } + + public static long ReturnChainWithComputation(int? a, short? b, long? c, byte d) + { + //TODO: unnecessary casts + //return (a + 1) ?? (b + 2) ?? (c + 3) ?? (d + 4); + return 0L; + } + } + + internal class T04_LiftedCompoundAssignAndBoxing + { + public static int? CompoundAssign(int? x, int? y) + { + x += y; + x <<= 2; + return x; + } + + public static object Box(int? x) + { + return x; + } + + public static int? Unbox(object o) + { + return (int?)o; + } + } + + // dummy structure for testing custom operators + [StructLayout(LayoutKind.Sequential, Size = 1)] + public struct TS + { + // unary + public static TS operator +(TS a) + { + throw null; + } + public static TS operator -(TS a) + { + throw null; + } + public static TS operator !(TS a) + { + throw null; + } + public static TS operator ~(TS a) + { + throw null; + } + public static TS operator ++(TS a) + { + throw null; + } + public static TS operator --(TS a) + { + throw null; + } + + public static explicit operator int(TS a) + { + throw null; + } + + // binary + public static TS operator +(TS a, TS b) + { + throw null; + } + public static TS operator -(TS a, TS b) + { + throw null; + } + public static TS operator *(TS a, TS b) + { + throw null; + } + public static TS operator /(TS a, TS b) + { + throw null; + } + public static TS operator %(TS a, TS b) + { + throw null; + } + public static TS operator &(TS a, TS b) + { + throw null; + } + public static TS operator |(TS a, TS b) + { + throw null; + } + public static TS operator ^(TS a, TS b) + { + throw null; + } + public static TS operator <<(TS a, int b) + { + throw null; + } + public static TS operator >>(TS a, int b) + { + throw null; + } + + // comparisons + public static bool operator ==(TS a, TS b) + { + throw null; + } + public static bool operator !=(TS a, TS b) + { + throw null; + } + public static bool operator <(TS a, TS b) + { + throw null; + } + public static bool operator <=(TS a, TS b) + { + throw null; + } + public static bool operator >(TS a, TS b) + { + throw null; + } + public static bool operator >=(TS a, TS b) + { + throw null; + } + + public override bool Equals(object obj) + { + throw null; + } + public override int GetHashCode() + { + throw null; + } + } +} diff --git a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs new file mode 100644 index 0000000..1a29a00 --- /dev/null +++ b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs @@ -0,0 +1,1044 @@ +// Copyright (c) 2014-2017 Daniel Grunwald +// +// 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.Linq.Expressions; + +using ICSharpCode.Decompiler.TypeSystem; + +namespace ICSharpCode.Decompiler.IL.Transforms +{ + /// + /// Collection of transforms that detect simple expression patterns + /// (e.g. 'cgt.un(..., ld.null)') and replace them with different instructions. + /// + /// + /// Should run after inlining so that the expression patterns can be detected. + /// + public class ExpressionTransforms : ILVisitor, IStatementTransform + { + internal StatementTransformContext context; + + public static void RunOnSingleStatement(ILInstruction statement, ILTransformContext context) + { + if (statement == null) + throw new ArgumentNullException(nameof(statement)); + if (!(statement.Parent is Block parent)) + throw new ArgumentException("ILInstruction must be a statement, i.e., direct child of a block."); + new ExpressionTransforms().Run(parent, statement.ChildIndex, new StatementTransformContext(new BlockTransformContext(context))); + } + + public void Run(Block block, int pos, StatementTransformContext context) + { + this.context = context; + context.StepStartGroup($"ExpressionTransforms ({block.Label}:{pos})", block.Instructions[pos]); + block.Instructions[pos].AcceptVisitor(this); + context.StepEndGroup(keepIfEmpty: true); + } + + protected override void Default(ILInstruction inst) + { + foreach (var child in inst.Children) + { + child.AcceptVisitor(this); + } + } + + protected internal override void VisitBlockContainer(BlockContainer container) + { + if (container.Kind == ContainerKind.Switch) + { + // Special case for switch: Only visit the switch condition block. + var switchInst = (SwitchInstruction)container.EntryPoint.Instructions[0]; + switchInst.Value.AcceptVisitor(this); + + HandleSwitchExpression(container, switchInst); + } + // No need to call base.VisitBlockContainer, see comment in VisitBlock. + } + + protected internal override void VisitBlock(Block block) + { + if (block.Kind == BlockKind.ControlFlow) + { + // Don't visit child control flow blocks; + // since this is a block transform + // we know those were already handled previously. + return; + } + base.VisitBlock(block); + } + + protected internal override void VisitComp(Comp inst) + { + // "logic.not(arg)" is sugar for "comp(arg != ldc.i4 0)" + if (inst.MatchLogicNot(out var arg)) + { + var toVisit = HandleLogicNot(inst, arg, context); + (toVisit ?? arg).AcceptVisitor(this); + return; + } + else if (inst.Kind == ComparisonKind.Inequality && inst.LiftingKind == ComparisonLiftingKind.None + && inst.Right.MatchLdcI4(0) + && (inst.Left.InferType(context.TypeSystem).IsKnownType(KnownTypeCode.Boolean) + || inst.Left.MatchLdcI4(0) || inst.Left.MatchLdcI4(1))) + { + // When `x` is known to be 0 or 1: + // `comp(x != 0) => x` + context.Step("Remove redundant comp(... != 0)", inst); + inst.Left.AddILRange(inst); + var left = inst.Left; + inst.ReplaceWith(left); + context.EndStep(left); + left.AcceptVisitor(this); + return; + } + if (context.Settings.LiftNullables) + { + new NullableLiftingTransform(context).Run(inst); + } + + base.VisitComp(inst); + if (inst.IsLifted) + { + return; + } + EarlyExpressionTransforms.FixComparisonKindLdNull(inst, context); + + var rightWithoutConv = inst.Right.UnwrapConv(ConversionKind.SignExtend).UnwrapConv(ConversionKind.ZeroExtend); + if (rightWithoutConv.MatchLdcI4(0) + && inst.Sign == Sign.Unsigned + && (inst.Kind == ComparisonKind.GreaterThan || inst.Kind == ComparisonKind.LessThanOrEqual)) + { + if (inst.Kind == ComparisonKind.GreaterThan) + { + context.Step("comp.unsigned(left > ldc.i4 0) => comp(left != ldc.i4 0)", inst); + inst.Kind = ComparisonKind.Inequality; + VisitComp(inst); + return; + } + else if (inst.Kind == ComparisonKind.LessThanOrEqual) + { + context.Step("comp.unsigned(left <= ldc.i4 0) => comp(left == ldc.i4 0)", inst); + inst.Kind = ComparisonKind.Equality; + VisitComp(inst); + return; + } + } + else if (rightWithoutConv.MatchLdcI4(0) && inst.Kind.IsEqualityOrInequality()) + { + if (inst.Left.MatchLdLen(StackType.I, out ILInstruction array)) + { + // comp.unsigned(ldlen array == conv i4->i(ldc.i4 0)) + // => comp(ldlen.i4 array == ldc.i4 0) + // This is a special case where the C# compiler doesn't generate conv.i4 after ldlen. + context.Step("comp(ldlen.i4 array == ldc.i4 0)", inst); + inst.InputType = StackType.I4; + inst.Left.ReplaceWith(new LdLen(StackType.I4, array).WithILRange(inst.Left)); + inst.Right = rightWithoutConv; + } + else if (inst.Left is Conv conv && conv.TargetType == PrimitiveType.I && conv.Argument.ResultType == StackType.Obj) + { + // C++/CLI sometimes uses this weird comparison with null: + context.Step("comp(conv o->i (ldloc obj) == conv i4->i (ldc.i4 0))", inst); + // -> comp(ldloc obj == ldnull) + inst.InputType = StackType.Obj; + inst.Left = conv.Argument; + inst.Right = new LdNull().WithILRange(inst.Right); + inst.Right.AddILRange(rightWithoutConv); + } + } + } + + protected internal override void VisitConv(Conv inst) + { + inst.Argument.AcceptVisitor(this); + if (inst.Argument.MatchLdLen(StackType.I, out ILInstruction array) && inst.TargetType.IsIntegerType() + && (!inst.CheckForOverflow || context.Settings.AssumeArrayLengthFitsIntoInt32)) + { + context.Step("conv.i4(ldlen array) => ldlen.i4(array)", inst); + inst.AddILRange(inst.Argument); + var ldLen = new LdLen(inst.TargetType.GetStackType(), array).WithILRange(inst); + inst.ReplaceWith(ldLen); + context.EndStep(ldLen); + return; + } + if (inst.TargetType.IsFloatType() && inst.Argument is Conv conv + && conv.Kind == ConversionKind.IntToFloat && conv.TargetType == PrimitiveType.R) + { + // IL conv.r.un does not indicate whether to convert the target type to R4 or R8, + // so the C# compiler usually follows it with an explicit conv.r4 or conv.r8. + // To avoid emitting '(float)(double)val', we combine these two conversions: + context.Step("conv.rN(conv.r.un(...)) => conv.rN.un(...)", inst); + var newConv = new Conv(conv.Argument, conv.InputType, conv.InputSign, inst.TargetType, inst.CheckForOverflow, inst.IsLifted | conv.IsLifted); + inst.ReplaceWith(newConv); + context.EndStep(newConv); + return; + } + } + + protected internal override void VisitBox(Box inst) + { + inst.Argument.AcceptVisitor(this); + if (inst.Type.IsReferenceType == true && inst.Argument.ResultType == inst.ResultType) + { + // For reference types, box is a no-op. + context.Step("box ref-type(arg) => arg", inst); + inst.Argument.AddILRange(inst); + var arg = inst.Argument; + inst.ReplaceWith(arg); + context.EndStep(arg); + } + } + + protected internal override void VisitLdElema(LdElema inst) + { + base.VisitLdElema(inst); + CleanUpArrayIndices(inst.Indices); + if (IndexRangeTransform.HandleLdElema(inst, context)) + return; + } + + protected internal override void VisitNewArr(NewArr inst) + { + base.VisitNewArr(inst); + CleanUpArrayIndices(inst.Indices); + } + + void CleanUpArrayIndices(InstructionCollection indices) + { + foreach (ILInstruction index in indices) + { + if (index is Conv conv && conv.ResultType == StackType.I + && (conv.Kind == ConversionKind.Truncate && conv.CheckForOverflow + || conv.Kind == ConversionKind.ZeroExtend || conv.Kind == ConversionKind.SignExtend) + ) + { + context.Step("Remove conv.i from array index", index); + index.ReplaceWith(conv.Argument); + context.EndStep(conv.Argument); + } + } + } + + /// + /// Handles combining negations with comparisons and logical operations. + /// Returns the instruction that `inst` was replaced with; or null is no transformation was made. + /// + static ILInstruction HandleLogicNot(Comp inst, ILInstruction arg, ILTransformContext context) + { + if (arg is Comp comp) + { + if ((!comp.InputType.IsFloatType() && !comp.IsLifted) || comp.Kind.IsEqualityOrInequality()) + { + context.Step("push negation into comparison", inst); + comp.Kind = comp.Kind.Negate(); + comp.AddILRange(inst); + inst.ReplaceWith(comp); + context.EndStep(comp); + return comp; + } + } + else if (arg.MatchLogicAnd(out var lhs, out var rhs)) + { + // logic.not(if (lhs) rhs else ldc.i4 0) + // ==> if (logic.not(lhs)) ldc.i4 1 else logic.not(rhs) + context.Step("push negation into logic.and", inst); + IfInstruction ifInst = (IfInstruction)arg; + var ldc0 = ifInst.FalseInst; + Debug.Assert(ldc0.MatchLdcI4(0)); + ifInst.Condition = Comp.LogicNot(lhs).WithILRange(inst); + ifInst.TrueInst = new LdcI4(1).WithILRange(ldc0); + ifInst.FalseInst = Comp.LogicNot(rhs).WithILRange(inst); + inst.ReplaceWith(ifInst); + context.EndStep(ifInst); + return ifInst; + } + else if (arg.MatchLogicOr(out lhs, out rhs)) + { + // logic.not(if (lhs) ldc.i4 1 else rhs) + // ==> if (logic.not(lhs)) logic.not(rhs) else ldc.i4 0) + context.Step("push negation into logic.or", inst); + IfInstruction ifInst = (IfInstruction)arg; + var ldc1 = ifInst.TrueInst; + Debug.Assert(ldc1.MatchLdcI4(1)); + ifInst.Condition = Comp.LogicNot(lhs).WithILRange(inst); + ifInst.TrueInst = Comp.LogicNot(rhs).WithILRange(inst); + ifInst.FalseInst = new LdcI4(0).WithILRange(ldc1); + inst.ReplaceWith(ifInst); + context.EndStep(ifInst); + return ifInst; + } + return null; + } + + protected internal override void VisitCall(Call inst) + { + if (NullableLiftingTransform.MatchGetValueOrDefault(inst, out var nullableValue, out var fallback) + && SemanticHelper.IsPure(fallback.Flags)) + { + context.Step("call Nullable{T}.GetValueOrDefault(a, b) -> a ?? b", inst); + var ldObj = new LdObj(nullableValue, inst.Method.DeclaringType); + var replacement = new NullCoalescingInstruction( + NullableType.GetUnderlyingType(inst.Method.DeclaringType), + NullCoalescingKind.NullableWithValueFallback, ldObj, fallback); + inst.ReplaceWith(replacement.WithILRange(inst)); + context.EndStep(replacement); + replacement.AcceptVisitor(this); + return; + } + if (TransformArrayInitializers.TransformRuntimeHelpersCreateSpanInitialization(inst, context, out var replacement2)) + { + context.Step("TransformRuntimeHelpersCreateSpanInitialization: single-dim", inst); + inst.ReplaceWith(replacement2); + context.EndStep(replacement2); + replacement2.AcceptVisitor(this); + return; + } + base.VisitCall(inst); + if (context.Settings.InlineArrays && InlineArrayTransform.RunOnExpression(inst, context)) + { + return; + } + TransformAssignment.HandleCompoundAssign(inst, context); + } + + protected internal override void VisitCallVirt(CallVirt inst) + { + base.VisitCallVirt(inst); + TransformAssignment.HandleCompoundAssign(inst, context); + } + + protected internal override void VisitNewObj(NewObj inst) + { + if (TransformSpanTCtorContainingStackAlloc(inst, out ILInstruction locallocSpan)) + { + context.Step("new Span(stackalloc) -> stackalloc Span", inst); + inst.ReplaceWith(locallocSpan); + context.EndStep(locallocSpan); + ILInstruction stmt = Block.GetContainingStatement(locallocSpan); + // Special case to eliminate extra store + if (stmt.GetNextSibling() is StLoc storeStmt && storeStmt.Value is LdLoc) + ILInlining.InlineIfPossible((Block)stmt.Parent, stmt.ChildIndex, context); + return; + } + if (TransformArrayInitializers.TransformSpanTArrayInitialization(inst, context, out var replacement)) + { + context.Step("TransformSpanTArrayInitialization: single-dim", inst); + inst.ReplaceWith(replacement); + context.EndStep(replacement); + return; + } + if (TransformDelegateCtorLdVirtFtnToLdVirtDelegate(inst, out LdVirtDelegate ldVirtDelegate)) + { + context.Step("new Delegate(target, ldvirtftn Method) -> ldvirtdelegate Delegate Method(target)", inst); + inst.ReplaceWith(ldVirtDelegate); + context.EndStep(ldVirtDelegate); + return; + } + base.VisitNewObj(inst); + } + + /// + /// newobj Delegate..ctor(target, ldvirtftn TargetMethod(target)) + /// => + /// ldvirtdelegate System.Delegate TargetMethod(target) + /// + bool TransformDelegateCtorLdVirtFtnToLdVirtDelegate(NewObj inst, out LdVirtDelegate ldVirtDelegate) + { + ldVirtDelegate = null; + if (inst.Method.DeclaringType.Kind != TypeKind.Delegate) + return false; + if (inst.Arguments.Count != 2) + return false; + if (!(inst.Arguments[1] is LdVirtFtn ldVirtFtn)) + return false; + if (!SemanticHelper.IsPure(inst.Arguments[0].Flags)) + return false; + if (!inst.Arguments[0].Match(ldVirtFtn.Argument).Success) + return false; + ldVirtDelegate = new LdVirtDelegate(inst.Arguments[0], inst.Method.DeclaringType, ldVirtFtn.Method) + .WithILRange(inst).WithILRange(ldVirtFtn).WithILRange(ldVirtFtn.Argument); + return true; + } + + /// + /// newobj Span..ctor(localloc(conv i4->u <zero extend>(ldc.i4 sizeInBytes)), numberOfElementsExpr) + /// => + /// localloc.span T(numberOfElementsExpr) + /// + /// -or- + /// + /// newobj Span..ctor(Block IL_0000 (StackAllocInitializer) { + /// stloc I_0(localloc(conv i4->u<zero extend>(ldc.i4 sizeInBytes))) + /// ... + /// final: ldloc I_0 + /// }, numberOfElementsExpr) + /// => + /// Block IL_0000 (StackAllocInitializer) { + /// stloc I_0(localloc.span T(numberOfElementsExpr)) + /// ... + /// final: ldloc I_0 + /// } + /// + bool TransformSpanTCtorContainingStackAlloc(NewObj newObj, out ILInstruction locallocSpan) + { + locallocSpan = null; + IType type = newObj.Method.DeclaringType; + if (!type.IsKnownType(KnownTypeCode.SpanOfT) && !type.IsKnownType(KnownTypeCode.ReadOnlySpanOfT)) + return false; + if (newObj.Arguments.Count != 2 || type.TypeArguments.Count != 1) + return false; + IType elementType = type.TypeArguments[0]; + if (newObj.Arguments[0].MatchLocAlloc(out var sizeInBytes) && MatchesElementCount(sizeInBytes, elementType, newObj.Arguments[1])) + { + locallocSpan = new LocAllocSpan(newObj.Arguments[1], type); + return true; + } + if (newObj.Arguments[0] is Block initializer && initializer.Kind == BlockKind.StackAllocInitializer) + { + if (!initializer.Instructions[0].MatchStLoc(out var initializerVariable, out var value)) + return false; + if (!TypeUtils.IsCompatiblePointerTypeForMemoryAccess(initializerVariable.Type, elementType)) + return false; + if (!(value.MatchLocAlloc(out sizeInBytes) && MatchesElementCount(sizeInBytes, elementType, newObj.Arguments[1]))) + return false; + // The block addresses the allocation through the localloc pointer and only its + // result is the span, so the constructor becomes the block's final instruction + // instead of retyping the initializer variable to Span<T>. + initializer.FinalInstruction = new NewObj(newObj.Method) { + Arguments = { new LdLoc(initializerVariable), newObj.Arguments[1] } + }.WithILRange(newObj); + locallocSpan = initializer; + return true; + } + return false; + } + + bool MatchesElementCount(ILInstruction sizeInBytesInstr, IType elementType, ILInstruction elementCountInstr2) + { + var pointerType = new PointerType(elementType); + var elementCountInstr = PointerArithmeticOffset.Detect(sizeInBytesInstr, pointerType.ElementType, checkForOverflow: true, unwrapZeroExtension: true); + if (elementCountInstr == null || !elementCountInstr.UnwrapConv(ConversionKind.ZeroExtend).Match(elementCountInstr2).Success) + return false; + return true; + } + + bool TransformDecimalFieldToConstant(LdObj inst, out LdcDecimal result) + { + if (inst.MatchLdsFld(out var field) && field.DeclaringType.IsKnownType(KnownTypeCode.Decimal)) + { + decimal? value = null; + if (field.Name == "One") + { + value = decimal.One; + } + else if (field.Name == "MinusOne") + { + value = decimal.MinusOne; + } + else if (field.Name == "Zero") + { + value = decimal.Zero; + } + if (value != null) + { + result = new LdcDecimal(value.Value).WithILRange(inst).WithILRange(inst.Target); + return true; + } + } + result = null; + return false; + } + + protected internal override void VisitLdObj(LdObj inst) + { + base.VisitLdObj(inst); + EarlyExpressionTransforms.AddressOfLdLocToLdLoca(inst, context); + if (EarlyExpressionTransforms.LdObjToLdLoc(inst, context)) + return; + if (TransformDecimalFieldToConstant(inst, out LdcDecimal decimalConstant)) + { + context.Step("TransformDecimalFieldToConstant", inst); + inst.ReplaceWith(decimalConstant); + context.EndStep(decimalConstant); + return; + } + } + + 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 + var target = inst.Target; + inst.ReplaceWith(target); + context.EndStep(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 + var target = inst.Target; + inst.ReplaceWith(target); + context.EndStep(target); + return; + } + } + + protected internal override void VisitStObj(StObj inst) + { + base.VisitStObj(inst); + if (EarlyExpressionTransforms.StObjToStLoc(inst, context)) + { + context.RequestRerun(); + return; + } + TransformAssignment.HandleCompoundAssign(inst, context); + } + + protected internal override void VisitStLoc(StLoc inst) + { + base.VisitStLoc(inst); + TransformAssignment.HandleCompoundAssign(inst, context); + } + + protected internal override void VisitIfInstruction(IfInstruction inst) + { + inst.TrueInst.AcceptVisitor(this); + inst.FalseInst.AcceptVisitor(this); + inst = HandleConditionalOperator(inst); + + // Bring LogicAnd/LogicOr into their canonical forms: + // if (cond) ldc.i4 0 else RHS --> if (!cond) RHS else ldc.i4 0 + // if (cond) RHS else ldc.i4 1 --> if (!cond) ldc.i4 1 else RHS + // Be careful: when both LHS and RHS are the constant 1, we must not + // swap the arguments as it would lead to an infinite transform loop. + if (inst.TrueInst.MatchLdcI4(0) && !inst.FalseInst.MatchLdcI4(0) + || inst.FalseInst.MatchLdcI4(1) && !inst.TrueInst.MatchLdcI4(1)) + { + context.Step("canonicalize logic and/or", inst); + var t = inst.TrueInst; + inst.TrueInst = inst.FalseInst; + inst.FalseInst = t; + inst.Condition = Comp.LogicNot(inst.Condition); + } + // Process condition after our potential modifications. + inst.Condition.AcceptVisitor(this); + + if (new NullableLiftingTransform(context).Run(inst)) + { + context.Step("NullableLiftingTransform", inst); + return; + } + + if (TransformDynamicAddAssignOrRemoveAssign(inst, context)) + return; + if (inst.MatchIfInstructionPositiveCondition(out var condition, out var trueInst, out var falseInst)) + { + ILInstruction transformed = UserDefinedLogicTransform.Transform(condition, trueInst, falseInst); + if (transformed == null) + { + transformed = UserDefinedLogicTransform.TransformDynamic(condition, trueInst, falseInst); + } + if (transformed != null) + { + context.Step("User-defined short-circuiting logic operator (roslyn pattern)", condition); + transformed.AddILRange(inst); + inst.ReplaceWith(transformed); + context.EndStep(transformed); + return; + } + } + if (MatchInstruction.IsPatternMatch(inst.Condition, out _, context.Settings) + && inst.TrueInst.MatchLdcI4(1) && inst.FalseInst.MatchLdcI4(0)) + { + context.Step("match(x) ? true : false -> match(x)", inst); + inst.Condition.AddILRange(inst); + var matchCondition = inst.Condition; + inst.ReplaceWith(matchCondition); + context.EndStep(matchCondition); + return; + } + } + + IfInstruction HandleConditionalOperator(IfInstruction inst) + { + // if (cond) stloc A(V1) else stloc A(V2) --> stloc A(if (cond) V1 else V2) + if (inst.TrueInst is not Block trueInst || trueInst.Instructions.Count != 1) + return inst; + if (inst.FalseInst is not Block falseInst || falseInst.Instructions.Count != 1) + return inst; + ILVariable v; + ILInstruction value1, value2; + if (trueInst.Instructions[0].MatchStLoc(out v, out value1) + && falseInst.Instructions[0].MatchStLoc(v, out value2)) + { + context.Step("conditional operator", inst); + IType type = v.Type; + // Try to tighten the type to `bool`; this matters esp. for logic.and/logic.or: + IType type1 = value1.InferType(context.TypeSystem); + IType type2 = value2.InferType(context.TypeSystem); + if (type1.IsKnownType(KnownTypeCode.Boolean) + && (type2.IsKnownType(KnownTypeCode.Boolean) || value2 is LdcI4 { Value: 0 or 1 })) + { + type = type1; + } + else if (type2.IsKnownType(KnownTypeCode.Boolean) && value1 is LdcI4 { Value: 0 or 1 }) + { + type = type2; + } + var newIf = new IfInstruction(Comp.LogicNot(inst.Condition), value2, value1, type); + newIf.AddILRange(inst); + var stLoc = new StLoc(v, newIf); + inst.ReplaceWith(stLoc); + context.EndStep(stLoc); + context.RequestRerun(); // trigger potential inlining of the newly created StLoc + return newIf; + } + return inst; + } + + private void HandleSwitchExpression(BlockContainer container, SwitchInstruction switchInst) + { + if (!context.Settings.SwitchExpressions) + return; + Debug.Assert(container.Kind == ContainerKind.Switch); + Debug.Assert(container.ResultType == StackType.Void); + var defaultSection = switchInst.GetDefaultSection(); + BlockContainer leaveTarget = null; + ILVariable resultVariable = null; + foreach (var section in switchInst.Sections) + { + if (section != defaultSection) + { + // every section except for the default must have exactly 1 label + if (section.Labels.Count() != (section.HasNullLabel ? 0u : 1u)) + return; + } + if (!section.Body.MatchBranch(out var sectionBlock)) + return; + if (sectionBlock.IncomingEdgeCount != 1) + return; + if (sectionBlock.Parent != container) + return; + if (sectionBlock.Instructions.Count == 1) + { + if (sectionBlock.Instructions[0] is Throw) + { + // OK + } + else if (sectionBlock.Instructions[0] is Leave leave) + { + if (!leave.IsLeavingFunction) + return; + leaveTarget ??= leave.TargetContainer; + Debug.Assert(leaveTarget == leave.TargetContainer); + } + else + { + return; + } + } + else if (sectionBlock.Instructions.Count == 2) + { + if (!sectionBlock.Instructions[0].MatchStLoc(out var v, out _)) + return; + if (!sectionBlock.Instructions[1].MatchLeave(container)) + return; + resultVariable ??= v; + if (resultVariable != v) + return; + } + else + { + return; + } + } + // Exactly one of resultVariable/leaveTarget must be null + if ((resultVariable == null) == (leaveTarget == null)) + return; + IType resultType = resultVariable?.Type ?? leaveTarget?.ExpectedResultType; + // C# has no ref-returning switch expression: an arm cannot be `0 => ref x`. + if (resultType.Kind == TypeKind.ByReference) + return; + if (switchInst.Value is StringToInt str2int) + { + // validate that each integer is used for exactly one value + var integersUsed = new HashSet(); + foreach ((string key, int val) in str2int.Map) + { + if (!integersUsed.Add(val)) + return; + } + } + + context.Step("Switch Expression", switchInst); + switchInst.SetResultType(resultType); + foreach (var section in switchInst.Sections) + { + var block = ((Branch)section.Body).TargetBlock; + if (block.Instructions.Count == 1) + { + if (block.Instructions[0] is Throw t) + { + section.Body = t; + } + else if (block.Instructions[0] is Leave leave) + { + section.Body = leave.Value; + } + else + { + throw new InvalidOperationException(); + } + } + else + { + section.Body = ((StLoc)block.Instructions[0]).Value; + } + } + if (resultVariable != null) + { + var stLoc = new StLoc(resultVariable, switchInst); + container.ReplaceWith(stLoc); + context.EndStep(stLoc); + } + else + { + var leaveInst = new Leave(leaveTarget, switchInst); + container.ReplaceWith(leaveInst); + context.EndStep(leaveInst); + } + context.RequestRerun(); // new StLoc might trigger inlining + } + + /// + /// op is either add or remove/subtract: + /// if (dynamic.isevent (target)) { + /// dynamic.invokemember.invokespecial.discard op_Name(target, value) + /// } else { + /// dynamic.compound.op (dynamic.getmember Name(target), value) + /// } + /// => + /// dynamic.compound.op (dynamic.getmember Name(target), value) + /// + internal static bool TransformDynamicAddAssignOrRemoveAssign(IfInstruction inst, ILTransformContext context) + { + if (!inst.MatchIfInstructionPositiveCondition(out var condition, out var trueInst, out var falseInst)) + return false; + if (!(condition is DynamicIsEventInstruction isEvent)) + return false; + trueInst = Block.Unwrap(trueInst); + falseInst = Block.Unwrap(falseInst); + if (!(falseInst is DynamicCompoundAssign dynamicCompoundAssign)) + return false; + if (!(dynamicCompoundAssign.Target is DynamicGetMemberInstruction getMember)) + return false; + if (!SemanticHelper.IsPure(isEvent.Argument.Flags)) + return false; + if (!isEvent.Argument.Match(getMember.Target).Success) + return false; + if (!(trueInst is DynamicInvokeMemberInstruction invokeMember)) + return false; + if (!(invokeMember.BinderFlags.HasFlag(CSharpBinderFlags.InvokeSpecialName) && invokeMember.BinderFlags.HasFlag(CSharpBinderFlags.ResultDiscarded))) + return false; + switch (dynamicCompoundAssign.Operation) + { + case ExpressionType.AddAssign: + if (invokeMember.Name != "add_" + getMember.Name) + return false; + break; + case ExpressionType.SubtractAssign: + if (invokeMember.Name != "remove_" + getMember.Name) + return false; + break; + default: + return false; + } + if (!dynamicCompoundAssign.Value.Match(invokeMember.Arguments[1]).Success) + return false; + if (!invokeMember.Arguments[0].Match(getMember.Target).Success) + return false; + context.Step("+= / -= dynamic.isevent pattern -> dynamic.compound.op", inst); + inst.ReplaceWith(dynamicCompoundAssign); + context.EndStep(dynamicCompoundAssign); + return true; + } + + /// + /// dynamic.setmember.compound Name(target, dynamic.binary.operator op(dynamic.getmember Name(target), value)) + /// => + /// dynamic.compound.op (dynamic.getmember Name(target), value) + /// + protected internal override void VisitDynamicSetMemberInstruction(DynamicSetMemberInstruction inst) + { + base.VisitDynamicSetMemberInstruction(inst); + TransformDynamicSetMemberInstruction(inst, context); + } + + internal static void TransformDynamicSetMemberInstruction(DynamicSetMemberInstruction inst, StatementTransformContext context) + { + if (!inst.BinderFlags.HasFlag(CSharpBinderFlags.ValueFromCompoundAssignment)) + return; + if (!(inst.Value is DynamicBinaryOperatorInstruction binaryOp)) + return; + if (!(binaryOp.Left is DynamicGetMemberInstruction dynamicGetMember)) + return; + if (!dynamicGetMember.Target.Match(inst.Target).Success) + return; + if (!SemanticHelper.IsPure(dynamicGetMember.Target.Flags)) + return; + if (inst.Name != dynamicGetMember.Name || !DynamicCompoundAssign.IsExpressionTypeSupported(binaryOp.Operation)) + return; + context.Step("dynamic.setmember.compound -> dynamic.compound.op", inst); + var compoundAssign = new DynamicCompoundAssign(binaryOp.Operation, binaryOp.BinderFlags, binaryOp.Left, binaryOp.LeftArgumentInfo, binaryOp.Right, binaryOp.RightArgumentInfo); + inst.ReplaceWith(compoundAssign); + context.EndStep(compoundAssign); + } + + /// + /// dynamic.setindex.compound(target, index, dynamic.binary.operator op(dynamic.getindex(target, index), value)) + /// => + /// dynamic.compound.op (dynamic.getindex(target, index), value) + /// + protected internal override void VisitDynamicSetIndexInstruction(DynamicSetIndexInstruction inst) + { + base.VisitDynamicSetIndexInstruction(inst); + + if (!inst.BinderFlags.HasFlag(CSharpBinderFlags.ValueFromCompoundAssignment)) + return; + if (!(inst.Arguments.LastOrDefault() is DynamicBinaryOperatorInstruction binaryOp)) + return; + if (!(binaryOp.Left is DynamicGetIndexInstruction dynamicGetIndex)) + return; + if (inst.Arguments.Count != dynamicGetIndex.Arguments.Count + 1) + return; + // Ensure that same arguments are passed to dynamicGetIndex and inst: + for (int j = 0; j < dynamicGetIndex.Arguments.Count; j++) + { + if (!SemanticHelper.IsPure(dynamicGetIndex.Arguments[j].Flags)) + return; + if (!dynamicGetIndex.Arguments[j].Match(inst.Arguments[j]).Success) + return; + } + if (!DynamicCompoundAssign.IsExpressionTypeSupported(binaryOp.Operation)) + return; + context.Step("dynamic.setindex.compound -> dynamic.compound.op", inst); + var compoundAssign = new DynamicCompoundAssign(binaryOp.Operation, binaryOp.BinderFlags, binaryOp.Left, binaryOp.LeftArgumentInfo, binaryOp.Right, binaryOp.RightArgumentInfo); + inst.ReplaceWith(compoundAssign); + context.EndStep(compoundAssign); + } + + protected internal override void VisitBinaryNumericInstruction(BinaryNumericInstruction inst) + { + base.VisitBinaryNumericInstruction(inst); + switch (inst.Operator) + { + case BinaryNumericOperator.ShiftLeft: + case BinaryNumericOperator.ShiftRight: + if (inst.Right.MatchBinaryNumericInstruction(BinaryNumericOperator.BitAnd, out var lhs, out var rhs) + && MatchExpectedShiftSize(rhs)) + { + // a << (b & 31) => a << b + context.Step("Combine bit.and into shift", inst); + inst.Right = lhs; + } + break; + case BinaryNumericOperator.BitAnd: + { + IType leftType = inst.Left.InferType(context.TypeSystem); + IType rightType = inst.Right.InferType(context.TypeSystem); + DetectImprovedTypeForForBitOp(inst, leftType, rightType); + if (leftType.IsKnownType(KnownTypeCode.Boolean) + && rightType.IsKnownType(KnownTypeCode.Boolean)) + { + if (new NullableLiftingTransform(context).Run(inst)) + { + // e.g. "(a.GetValueOrDefault() == b.GetValueOrDefault()) & (a.HasValue & b.HasValue)" + } + } + break; + } + case BinaryNumericOperator.BitOr: + case BinaryNumericOperator.BitXor: + { + IType leftType = inst.Left.InferType(context.TypeSystem); + IType rightType = inst.Right.InferType(context.TypeSystem); + DetectImprovedTypeForForBitOp(inst, leftType, rightType); + break; + } + } + + bool MatchExpectedShiftSize(ILInstruction rhs) + { + switch (inst.ResultType) + { + case StackType.I4: + return rhs.MatchLdcI4(31); + case StackType.I8: + return rhs.MatchLdcI4(63); + case StackType.I: + // sizeof(IntPtr) * 8 - 1 + return rhs.MatchBinaryNumericInstruction(BinaryNumericOperator.Sub, out var mult, out var one) + && mult.MatchBinaryNumericInstruction(BinaryNumericOperator.Mul, out var size, out var eight) + && size.MatchSizeOf(out var sizeofType) && sizeofType.GetStackType() == StackType.I + && eight.MatchLdcI4(8) && one.MatchLdcI4(1); + default: + return false; + } + } + } + + // Given bit.or(a, b), promotes the "known to be bool/short" information from the operands to bit.or + void DetectImprovedTypeForForBitOp(BinaryNumericInstruction inst, IType leftType, IType rightType) + { + if (leftType.Equals(rightType) && (leftType.IsCSharpPrimitiveIntegerType() || leftType.IsCSharpNativeIntegerType() || leftType.IsKnownType(KnownTypeCode.Boolean))) + { + if (!leftType.Equals(inst.CSharpResultType)) + { + context.Step("DetectImprovedTypeForForBitOp", inst); + inst.CSharpResultType = leftType; + context.EndStep(inst); + } + } + } + + protected internal override void VisitTryCatchHandler(TryCatchHandler inst) + { + base.VisitTryCatchHandler(inst); + if (inst.Filter is BlockContainer filterContainer && filterContainer.Blocks.Count == 1) + { + TransformCatchWhen(inst, filterContainer.EntryPoint); + } + if (inst.Body is BlockContainer catchContainer) + TransformCatchVariable(inst, catchContainer.EntryPoint, isCatchBlock: true); + } + + /// + /// catch ex : TException when (...) BlockContainer { + /// Block entryPoint (incoming: 1) { + /// stloc v(ldloc ex) + /// ... + /// } + /// } + /// => + /// catch v : TException when (...) BlockContainer { + /// Block entryPoint (incoming: 1) { + /// ... + /// } + /// } + /// + void TransformCatchVariable(TryCatchHandler handler, Block entryPoint, bool isCatchBlock) + { + if (!handler.Variable.IsSingleDefinition || handler.Variable.LoadCount != 1) + return; // handler.Variable already has non-trivial uses + if (!entryPoint.Instructions[0].MatchStLoc(out var exceptionVar, out var exceptionSlotLoad)) + { + // Not the pattern with a second exceptionVar. + // However, it is still possible that we need to remove a pointless UnboxAny: + if (handler.Variable.LoadInstructions.Single().Parent is UnboxAny inlinedUnboxAny) + { + if (inlinedUnboxAny.Type.Equals(handler.Variable.Type)) + { + context.Step("TransformCatchVariable - remove inlined UnboxAny", inlinedUnboxAny); + var unboxArgument = inlinedUnboxAny.Argument; + inlinedUnboxAny.ReplaceWith(unboxArgument); + context.EndStep(unboxArgument); + foreach (var range in inlinedUnboxAny.ILRanges) + handler.AddExceptionSpecifierILRange(range); + } + } + return; + } + if (exceptionVar.Kind != VariableKind.Local && exceptionVar.Kind != VariableKind.StackSlot) + return; + if (exceptionSlotLoad is UnboxAny unboxAny) + { + // When catching a type parameter, csc emits an unbox.any instruction + if (!unboxAny.Type.Equals(handler.Variable.Type)) + return; + exceptionSlotLoad = unboxAny.Argument; + } + if (!exceptionSlotLoad.MatchLdLoc(handler.Variable)) + return; + // Check that exceptionVar is only used within the catch block: + var allUses = exceptionVar.LoadInstructions + .Concat(exceptionVar.StoreInstructions.Cast()) + .Concat(exceptionVar.AddressInstructions); + foreach (var inst in allUses) + { + if (!inst.IsDescendantOf(handler)) + return; + } + context.Step("TransformCatchVariable", entryPoint.Instructions[0]); + exceptionVar.Kind = VariableKind.ExceptionLocal; + exceptionVar.Name = handler.Variable.Name; + exceptionVar.Type = handler.Variable.Type; + exceptionVar.HasGeneratedName = handler.Variable.HasGeneratedName; + handler.Variable = exceptionVar; + if (isCatchBlock) + { + foreach (var offset in entryPoint.Instructions[0].Descendants.SelectMany(o => o.ILRanges)) + handler.AddExceptionSpecifierILRange(offset); + } + entryPoint.Instructions.RemoveAt(0); + } + + /// + /// Inline condition from catch-when condition BlockContainer, if possible. + /// + void TransformCatchWhen(TryCatchHandler handler, Block entryPoint) + { + TransformCatchVariable(handler, entryPoint, isCatchBlock: false); + if (entryPoint.Instructions.Count == 1 && entryPoint.Instructions[0].MatchLeave(out _, out var condition)) + { + context.Step("TransformCatchWhen", entryPoint.Instructions[0]); + handler.Filter = condition; + context.EndStep(condition); + } + } + + protected internal override void VisitMatchInstruction(MatchInstruction inst) + { + inst.TestedOperand.AcceptVisitor(this); + // Do not recurse into the sub-patterns: patterns are restricted to use only certain ILInstructions, + // and arbitrary transforms might not stay within that allowed set of instructions. + foreach (var subPattern in inst.SubPatterns) + { + // However, we still need to simplify negations: + foreach (var potentialNegation in subPattern.Descendants.OfType()) + { + if (potentialNegation.MatchLogicNot(out var arg)) + { + HandleLogicNot(potentialNegation, arg, context); + } + } + } + } + } +}