Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 0eeff1a

Browse files
erozenfeldRussKeldorph
authored andcommitted
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.
1 parent 90e0340 commit 0eeff1a

File tree

5 files changed

+165
-0
lines changed

5 files changed

+165
-0
lines changed

src/jit/morph.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7529,6 +7529,39 @@ void Compiler::fgMorphRecursiveFastTailCallIntoLoop(BasicBlock* block, GenTreeCa
75297529
fgInsertStmtBefore(block, paramAssignmentInsertionPoint, arg0AssignmentStmt);
75307530
}
75317531

7532+
// If compInitMem is set, we may need to zero-initialize some locals. Normally it's done in the prolog
7533+
// but this loop can't include the prolog. Since we don't have liveness information, we insert zero-initialization
7534+
// for all non-parameter non-temp locals. Liveness phase will remove unnecessary initializations.
7535+
if (info.compInitMem)
7536+
{
7537+
unsigned varNum;
7538+
LclVarDsc* varDsc;
7539+
for (varNum = 0, varDsc = lvaTable; varNum < info.compLocalsCount; varNum++, varDsc++)
7540+
{
7541+
if (!varDsc->lvIsParam)
7542+
{
7543+
assert(!varDsc->lvIsTemp);
7544+
var_types lclType = varDsc->TypeGet();
7545+
GenTreePtr lcl = gtNewLclvNode(varNum, lclType);
7546+
GenTreePtr init = nullptr;
7547+
if (lclType == TYP_STRUCT)
7548+
{
7549+
const bool isVolatile = false;
7550+
const bool isCopyBlock = false;
7551+
init = gtNewBlkOpNode(lcl, gtNewIconNode(0), varDsc->lvSize(), isVolatile, isCopyBlock);
7552+
init = fgMorphInitBlock(init);
7553+
}
7554+
else
7555+
{
7556+
GenTreePtr zero = gtNewZeroConNode(genActualType(lclType));
7557+
init = gtNewAssignNode(lcl, zero);
7558+
}
7559+
GenTreePtr initStmt = gtNewStmt(init, callILOffset);
7560+
fgInsertStmtBefore(block, last, initStmt);
7561+
}
7562+
}
7563+
}
7564+
75327565
// Remove the call
75337566
fgRemoveStmt(block, last);
75347567

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.IO;
7+
using System.Xml;
8+
using System.Xml.XPath;
9+
using System.Xml.Xsl;
10+
11+
12+
namespace XSLTest
13+
{
14+
class Program
15+
{
16+
// In this test a dynamic method with tail-prefixed call is created.
17+
// One of the locals is not explicitly initialized but a flag to init locals is set.
18+
// (That never happens in normal C# methods due to C# definite assignment rules.)
19+
// The jit performs an optimization transforming the tail call into a loop.
20+
// The bug was that the local was only zero-initialized for the first iteration of the loop.
21+
22+
static int Main(string[] args)
23+
{
24+
string inputXml = "Input.xml";
25+
string inputXsl = "Transform.xsl";
26+
27+
return DotNetXslCompiledTransform(inputXml, inputXsl);
28+
}
29+
30+
private static int DotNetXslCompiledTransform(string inputXml, string inputXsl)
31+
{
32+
XslCompiledTransform transform = new XslCompiledTransform();
33+
transform.Load(inputXsl);
34+
35+
StringWriter stringWriter = new StringWriter();
36+
XmlWriter writer = new XmlTextWriter(stringWriter);
37+
38+
transform.Transform(inputXml, null, writer);
39+
40+
string transformResult = stringWriter.ToString();
41+
if (transformResult == "<!--20.0 20.0 20.0 20.0 20.0--> 40 40 40 40 40")
42+
{
43+
Console.WriteLine("SUCCESS");
44+
return 100;
45+
}
46+
else
47+
{
48+
Console.WriteLine("FAILURE");
49+
return 0;
50+
}
51+
}
52+
}
53+
}
54+
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3+
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
4+
<PropertyGroup>
5+
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
6+
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
7+
<AssemblyName>$(MSBuildProjectName)</AssemblyName>
8+
<SchemaVersion>2.0</SchemaVersion>
9+
<ProjectGuid>{95DFC527-4DC1-495E-97D7-E94EE1F7140D}</ProjectGuid>
10+
<OutputType>Exe</OutputType>
11+
<ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
12+
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
13+
</PropertyGroup>
14+
<!-- Default configurations to help VS understand the configurations -->
15+
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
16+
</PropertyGroup>
17+
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
18+
</PropertyGroup>
19+
<ItemGroup>
20+
<CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
21+
<Visible>False</Visible>
22+
</CodeAnalysisDependentAssemblyPaths>
23+
</ItemGroup>
24+
<PropertyGroup>
25+
<DebugType></DebugType>
26+
<Optimize>True</Optimize>
27+
</PropertyGroup>
28+
<ItemGroup>
29+
<Compile Include="$(MSBuildProjectName).cs" />
30+
<Reference Include="System.Private.Xml" />
31+
<Content Include="Input.xml">
32+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
33+
</Content>
34+
<Content Include="Transform.xsl">
35+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
36+
</Content>
37+
</ItemGroup>
38+
<ItemGroup>
39+
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
40+
</ItemGroup>
41+
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
42+
<PropertyGroup Condition=" '$(MsBuildProjectDirOverride)' != '' ">
43+
</PropertyGroup>
44+
</Project>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<root>
3+
<bla test="20.0 20.0 20.0 20.0 20.0"/>
4+
</root>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:l="http://www.schema.de/XSL/ST4DocuManagerlang" version="1.0">
3+
<xsl:template match="/">
4+
<xsl:for-each select="/root/bla">
5+
<xsl:comment>
6+
<xsl:value-of select="@test"/>
7+
</xsl:comment>
8+
<xsl:call-template name="duplicate">
9+
<xsl:with-param name="value" select="@test" />
10+
</xsl:call-template>
11+
</xsl:for-each>
12+
</xsl:template>
13+
14+
<xsl:template name="duplicate">
15+
<xsl:param name="value" />
16+
<xsl:param name="result" />
17+
<xsl:choose>
18+
<xsl:when test="contains($value, ' ')">
19+
<xsl:call-template name="duplicate">
20+
<xsl:with-param name="value" select="substring-after($value, ' ')" />
21+
<xsl:with-param name="result" select="concat($result,' ', substring-before($value, ' ') * 2)" />
22+
</xsl:call-template>
23+
</xsl:when>
24+
<xsl:otherwise>
25+
<xsl:value-of select="concat($result,' ', $value * 2)" />
26+
</xsl:otherwise>
27+
</xsl:choose>
28+
</xsl:template>
29+
30+
</xsl:stylesheet>

0 commit comments

Comments
 (0)