From a9044a132b4807695d386181cf55bb952826e350 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Mon, 10 Apr 2017 12:05:49 -0700 Subject: [PATCH] Jit: fix issue with single-def type propagation To avoid overly aggressive type propagation when there are multiple reaching definitions, only propagate types to single-IL-def locals when the definiting value comes from the same basic block as the store. We check this conservatively by insisting that the block's entry stack be empty. Added a test case where the jit will improperly devirtualize without such a check. Closes #10858. --- src/jit/importer.cpp | 6 ++- .../JIT/opt/Devirtualization/GitHub_10858.cs | 46 +++++++++++++++++++ .../opt/Devirtualization/GitHub_10858.csproj | 37 +++++++++++++++ 3 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 tests/src/JIT/opt/Devirtualization/GitHub_10858.cs create mode 100644 tests/src/JIT/opt/Devirtualization/GitHub_10858.csproj diff --git a/src/jit/importer.cpp b/src/jit/importer.cpp index 64c6f4563850..d47e3dfb97db 100644 --- a/src/jit/importer.cpp +++ b/src/jit/importer.cpp @@ -10094,7 +10094,11 @@ void Compiler::impImportBlockCode(BasicBlock* block) const bool isSingleILStoreLocal = !lvaTable[lclNum].lvHasMultipleILStoreOp && !lvaTable[lclNum].lvHasLdAddrOp; - if (isSingleILStoreLocal) + // Conservative check that there is just one + // definition that reaches this store. + const bool hasSingleReachingDef = (block->bbStackDepthOnEntry() == 0); + + if (isSingleILStoreLocal && hasSingleReachingDef) { lvaUpdateClass(lclNum, op1, clsHnd); } diff --git a/tests/src/JIT/opt/Devirtualization/GitHub_10858.cs b/tests/src/JIT/opt/Devirtualization/GitHub_10858.cs new file mode 100644 index 000000000000..a02d025593c6 --- /dev/null +++ b/tests/src/JIT/opt/Devirtualization/GitHub_10858.cs @@ -0,0 +1,46 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +class B +{ + public virtual string F() { return "B"; } +} + +sealed class D : B +{ + public override string F() { return "D"; } +} + +sealed class E : B +{ + public override string F() { return "E"; } +} + +class X +{ + public static int Main(string[] args) + { + // When optimizing IL, CSC will leave the newobj's on the stack + // across the branches to the common def point. + B b1 = (args.Length > 0) ? (B)new E() : (B)new D(); + B b2 = null; + + // Conditional flow here to forces b1 to a local instead of + // remaining on the stack. So we have a single def point with + // two reaching values. + if (args.Length > 0) + { + b2 = new D(); + } + else + { + b2 = new E(); + } + + // We should not be able to devirtualize either call. + return b2.F()[0] + b1.F()[0] - 37; + } +} diff --git a/tests/src/JIT/opt/Devirtualization/GitHub_10858.csproj b/tests/src/JIT/opt/Devirtualization/GitHub_10858.csproj new file mode 100644 index 000000000000..fdf04a29e00e --- /dev/null +++ b/tests/src/JIT/opt/Devirtualization/GitHub_10858.csproj @@ -0,0 +1,37 @@ + + + + + Debug + AnyCPU + $(MSBuildProjectName) + 2.0 + {95DFC527-4DC1-495E-97D7-E94EE1F7140D} + Exe + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + ..\..\ + + + + + + + + + False + + + + None + True + + + + + + + + + + +