From aeecbffb1d115924e758fee4d852aeabd6b94852 Mon Sep 17 00:00:00 2001 From: Eugene Rozenfeld Date: Tue, 11 Jul 2017 01:03:26 -0700 Subject: [PATCH] Fix for a bug in tail recursion elimination. Tail recursion elimination transforms a tail call into a loop. If compInitMem is set, we may need to zero-initialize some locals. Normally it's done in the prolog but the loop we are creating can't include the prolog. The fix is to insert zero-initialization for all non-parameter non-temp locals in the loop. Liveness phase will remove unnecessary initializations. We never hit this case with normal C# code since C# definite assignment rules ensure that there are no uninitialized locals in the generated msil. In the repro the method with tail recursion is a dynamic method and it has an uninitialized local. --- src/jit/morph.cpp | 33 +++++++++++++ .../JitBlue/DevDiv_461649/DevDiv_461649.cs | 54 ++++++++++++++++++++++ .../JitBlue/DevDiv_461649/DevDiv_461649.csproj | 44 ++++++++++++++++++ .../JIT/Regression/JitBlue/DevDiv_461649/Input.xml | 4 ++ .../Regression/JitBlue/DevDiv_461649/Transform.xsl | 30 ++++++++++++ 5 files changed, 165 insertions(+) create mode 100644 tests/src/JIT/Regression/JitBlue/DevDiv_461649/DevDiv_461649.cs create mode 100644 tests/src/JIT/Regression/JitBlue/DevDiv_461649/DevDiv_461649.csproj create mode 100644 tests/src/JIT/Regression/JitBlue/DevDiv_461649/Input.xml create mode 100644 tests/src/JIT/Regression/JitBlue/DevDiv_461649/Transform.xsl diff --git a/src/jit/morph.cpp b/src/jit/morph.cpp index 27da028..476e054 100644 --- a/src/jit/morph.cpp +++ b/src/jit/morph.cpp @@ -7586,6 +7586,39 @@ void Compiler::fgMorphRecursiveFastTailCallIntoLoop(BasicBlock* block, GenTreeCa fgInsertStmtBefore(block, paramAssignmentInsertionPoint, arg0AssignmentStmt); } + // If compInitMem is set, we may need to zero-initialize some locals. Normally it's done in the prolog + // but this loop can't include the prolog. Since we don't have liveness information, we insert zero-initialization + // for all non-parameter non-temp locals. Liveness phase will remove unnecessary initializations. + if (info.compInitMem) + { + unsigned varNum; + LclVarDsc* varDsc; + for (varNum = 0, varDsc = lvaTable; varNum < info.compLocalsCount; varNum++, varDsc++) + { + if (!varDsc->lvIsParam) + { + assert(!varDsc->lvIsTemp); + var_types lclType = varDsc->TypeGet(); + GenTreePtr lcl = gtNewLclvNode(varNum, lclType); + GenTreePtr init = nullptr; + if (lclType == TYP_STRUCT) + { + const bool isVolatile = false; + const bool isCopyBlock = false; + init = gtNewBlkOpNode(lcl, gtNewIconNode(0), varDsc->lvSize(), isVolatile, isCopyBlock); + init = fgMorphInitBlock(init); + } + else + { + GenTreePtr zero = gtNewZeroConNode(genActualType(lclType)); + init = gtNewAssignNode(lcl, zero); + } + GenTreePtr initStmt = gtNewStmt(init, callILOffset); + fgInsertStmtBefore(block, last, initStmt); + } + } + } + // Remove the call fgRemoveStmt(block, last); diff --git a/tests/src/JIT/Regression/JitBlue/DevDiv_461649/DevDiv_461649.cs b/tests/src/JIT/Regression/JitBlue/DevDiv_461649/DevDiv_461649.cs new file mode 100644 index 0000000..0265f43 --- /dev/null +++ b/tests/src/JIT/Regression/JitBlue/DevDiv_461649/DevDiv_461649.cs @@ -0,0 +1,54 @@ +// 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; +using System.IO; +using System.Xml; +using System.Xml.XPath; +using System.Xml.Xsl; + + +namespace XSLTest +{ + class Program + { + // In this test a dynamic method with tail-prefixed call is created. + // One of the locals is not explicitly initialized but a flag to init locals is set. + // (That never happens in normal C# methods due to C# definite assignment rules.) + // The jit performs an optimization transforming the tail call into a loop. + // The bug was that the local was only zero-initialized for the first iteration of the loop. + + static int Main(string[] args) + { + string inputXml = "Input.xml"; + string inputXsl = "Transform.xsl"; + + return DotNetXslCompiledTransform(inputXml, inputXsl); + } + + private static int DotNetXslCompiledTransform(string inputXml, string inputXsl) + { + XslCompiledTransform transform = new XslCompiledTransform(); + transform.Load(inputXsl); + + StringWriter stringWriter = new StringWriter(); + XmlWriter writer = new XmlTextWriter(stringWriter); + + transform.Transform(inputXml, null, writer); + + string transformResult = stringWriter.ToString(); + if (transformResult == " 40 40 40 40 40") + { + Console.WriteLine("SUCCESS"); + return 100; + } + else + { + Console.WriteLine("FAILURE"); + return 0; + } + } + } +} + diff --git a/tests/src/JIT/Regression/JitBlue/DevDiv_461649/DevDiv_461649.csproj b/tests/src/JIT/Regression/JitBlue/DevDiv_461649/DevDiv_461649.csproj new file mode 100644 index 0000000..5cfcb81 --- /dev/null +++ b/tests/src/JIT/Regression/JitBlue/DevDiv_461649/DevDiv_461649.csproj @@ -0,0 +1,44 @@ + + + + + Debug + AnyCPU + $(MSBuildProjectName) + 2.0 + {95DFC527-4DC1-495E-97D7-E94EE1F7140D} + Exe + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + ..\..\ + + + + + + + + + False + + + + + True + + + + + + Always + + + Always + + + + + + + + + diff --git a/tests/src/JIT/Regression/JitBlue/DevDiv_461649/Input.xml b/tests/src/JIT/Regression/JitBlue/DevDiv_461649/Input.xml new file mode 100644 index 0000000..d8552e4 --- /dev/null +++ b/tests/src/JIT/Regression/JitBlue/DevDiv_461649/Input.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/tests/src/JIT/Regression/JitBlue/DevDiv_461649/Transform.xsl b/tests/src/JIT/Regression/JitBlue/DevDiv_461649/Transform.xsl new file mode 100644 index 0000000..3f39086 --- /dev/null +++ b/tests/src/JIT/Regression/JitBlue/DevDiv_461649/Transform.xsl @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file -- 2.7.4