diff --git a/src/coreclr/jit/compiler.cpp b/src/coreclr/jit/compiler.cpp index cca34ff20bbb3..5422fa5a8d681 100644 --- a/src/coreclr/jit/compiler.cpp +++ b/src/coreclr/jit/compiler.cpp @@ -9175,6 +9175,11 @@ void cTreeFlags(Compiler* comp, GenTree* tree) { chars += printf("[BOX_VALUE]"); } + + if (tree->gtFlags & GTF_BOX_CLONED) + { + chars += printf("[BOX_CLONED]"); + } break; case GT_CNS_INT: diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index 0a0f7e42beadb..3f196e5c254e1 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -7935,6 +7935,8 @@ GenTree* Compiler::gtCloneExpr( copy = new (this, GT_BOX) GenTreeBox(tree->TypeGet(), tree->AsOp()->gtOp1, tree->AsBox()->gtAsgStmtWhenInlinedBoxValue, tree->AsBox()->gtCopyStmtWhenInlinedBoxValue); + tree->AsBox()->SetCloned(); + copy->AsBox()->SetCloned(); break; case GT_INTRINSIC: @@ -13834,6 +13836,13 @@ GenTree* Compiler::gtTryRemoveBoxUpstreamEffects(GenTree* op, BoxRemovalOptions return nullptr; } + // If this box is no longer single-use, bail. + if (box->WasCloned()) + { + JITDUMP(" bailing; unsafe to remove box that has been cloned\n"); + return nullptr; + } + // If we're eventually going to return the type handle, remember it now. GenTree* boxTypeHandle = nullptr; if ((options == BR_REMOVE_AND_NARROW_WANT_TYPE_HANDLE) || (options == BR_DONT_REMOVE_WANT_TYPE_HANDLE)) diff --git a/src/coreclr/jit/gentree.h b/src/coreclr/jit/gentree.h index 59489ea12ce5c..1405b91d15188 100644 --- a/src/coreclr/jit/gentree.h +++ b/src/coreclr/jit/gentree.h @@ -543,6 +543,7 @@ enum GenTreeFlags : unsigned int GTF_QMARK_CAST_INSTOF = 0x80000000, // GT_QMARK -- Is this a top (not nested) level qmark created for // castclass or instanceof? + GTF_BOX_CLONED = 0x40000000, // GT_BOX -- this box and its operand has been cloned, cannot assume it to be single-use anymore GTF_BOX_VALUE = 0x80000000, // GT_BOX -- "box" is on a value type GTF_ICON_HDL_MASK = 0xF0000000, // Bits used by handle types below @@ -3638,6 +3639,16 @@ struct GenTreeBox : public GenTreeUnOp { } #endif + + bool WasCloned() + { + return (gtFlags & GTF_BOX_CLONED) != 0; + } + + void SetCloned() + { + gtFlags |= GTF_BOX_CLONED; + } }; /* gtField -- data member ref (GT_FIELD) */ diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_72775/Runtime_72775.cs b/src/tests/JIT/Regression/JitBlue/Runtime_72775/Runtime_72775.cs new file mode 100644 index 0000000000000..a53cb3bb0d494 --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_72775/Runtime_72775.cs @@ -0,0 +1,53 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.CompilerServices; +using System.Threading; + +public class Runtime_72775 +{ + public static int Main() + { + for (int i = 0; i < 100; i++) + { + Call(new Impl1()); + if (i > 30 && i < 40) + Thread.Sleep(10); + } + + // With GDV, JIT would optimize Call by fully removing the box since Impl1.Foo does not use it. + // This would cause null to be passed to Impl2.Foo. + return Call(new Impl2()); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static int Call(I i) => i.Foo(5); +} + +public interface I +{ + int Foo(object o); +} + +class Impl1 : I +{ + public int Foo(object o) => 0; +} + +class Impl2 : I +{ + public int Foo(object o) + { + if (o is not int i || i != 5) + { + Console.WriteLine("FAIL: Got {0}", o?.ToString() ?? "(null)"); + return -1; + } + else + { + Console.WriteLine("PASS: Got 5"); + return 100; + } + } +} diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_72775/Runtime_72775.csproj b/src/tests/JIT/Regression/JitBlue/Runtime_72775/Runtime_72775.csproj new file mode 100644 index 0000000000000..db201bf0c84dc --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_72775/Runtime_72775.csproj @@ -0,0 +1,21 @@ + + + Exe + True + + + + + + + + + \ No newline at end of file