Lower fast tail call wasn't patching control expression (#20740)
authorEgor Chesakov <Egor.Chesakov@microsoft.com>
Tue, 13 Nov 2018 00:41:01 +0000 (16:41 -0800)
committerGitHub <noreply@github.com>
Tue, 13 Nov 2018 00:41:01 +0000 (16:41 -0800)
Lower fast tail call can replace local variables (holding Caller stack arguments) with new temps in order to set up Callee stack arguments correctly. This involves patching corresponding LCL_VAR and LCL_VAR_ADDR nodes and replacing them with the location of a new temp.

This was not done for control expression which continued pointing to the old location and could contain a Callee argument.

src/jit/lower.cpp
tests/src/JIT/Regression/JitBlue/GitHub_20625/GitHub_20625.il [new file with mode: 0644]
tests/src/JIT/Regression/JitBlue/GitHub_20625/GitHub_20625.ilproj [new file with mode: 0644]

index 5e29c0b..f125eaa 100644 (file)
@@ -1572,7 +1572,7 @@ void Lowering::LowerCall(GenTree* node)
     LowerArgsForCall(call);
 
     // note that everything generated from this point on runs AFTER the outgoing args are placed
-    GenTree* result = nullptr;
+    GenTree* controlExpr = nullptr;
 
     // for x86, this is where we record ESP for checking later to make sure stack is balanced
 
@@ -1581,7 +1581,7 @@ void Lowering::LowerCall(GenTree* node)
     // an indirect call.
     if (call->IsDelegateInvoke())
     {
-        result = LowerDelegateInvoke(call);
+        controlExpr = LowerDelegateInvoke(call);
     }
     else
     {
@@ -1589,26 +1589,26 @@ void Lowering::LowerCall(GenTree* node)
         switch (call->gtFlags & GTF_CALL_VIRT_KIND_MASK)
         {
             case GTF_CALL_VIRT_STUB:
-                result = LowerVirtualStubCall(call);
+                controlExpr = LowerVirtualStubCall(call);
                 break;
 
             case GTF_CALL_VIRT_VTABLE:
                 // stub dispatching is off or this is not a virtual call (could be a tailcall)
-                result = LowerVirtualVtableCall(call);
+                controlExpr = LowerVirtualVtableCall(call);
                 break;
 
             case GTF_CALL_NONVIRT:
                 if (call->IsUnmanaged())
                 {
-                    result = LowerNonvirtPinvokeCall(call);
+                    controlExpr = LowerNonvirtPinvokeCall(call);
                 }
                 else if (call->gtCallType == CT_INDIRECT)
                 {
-                    result = LowerIndirectNonvirtCall(call);
+                    controlExpr = LowerIndirectNonvirtCall(call);
                 }
                 else
                 {
-                    result = LowerDirectCall(call);
+                    controlExpr = LowerDirectCall(call);
                 }
                 break;
 
@@ -1621,26 +1621,22 @@ void Lowering::LowerCall(GenTree* node)
     if (call->IsTailCallViaHelper())
     {
         // Either controlExpr or gtCallAddr must contain real call target.
-        if (result == nullptr)
+        if (controlExpr == nullptr)
         {
             assert(call->gtCallType == CT_INDIRECT);
             assert(call->gtCallAddr != nullptr);
-            result = call->gtCallAddr;
+            controlExpr = call->gtCallAddr;
         }
 
-        result = LowerTailCallViaHelper(call, result);
-    }
-    else if (call->IsFastTailCall())
-    {
-        LowerFastTailCall(call);
+        controlExpr = LowerTailCallViaHelper(call, controlExpr);
     }
 
-    if (result != nullptr)
+    if (controlExpr != nullptr)
     {
-        LIR::Range resultRange = LIR::SeqTree(comp, result);
+        LIR::Range controlExprRange = LIR::SeqTree(comp, controlExpr);
 
         JITDUMP("results of lowering call:\n");
-        DISPRANGE(resultRange);
+        DISPRANGE(controlExprRange);
 
         GenTree* insertionPoint = call;
         if (!call->IsTailCallViaHelper())
@@ -1671,10 +1667,21 @@ void Lowering::LowerCall(GenTree* node)
             }
         }
 
-        ContainCheckRange(resultRange);
-        BlockRange().InsertBefore(insertionPoint, std::move(resultRange));
+        ContainCheckRange(controlExprRange);
+        BlockRange().InsertBefore(insertionPoint, std::move(controlExprRange));
 
-        call->gtControlExpr = result;
+        call->gtControlExpr = controlExpr;
+    }
+    if (call->IsFastTailCall())
+    {
+        // Lower fast tail call can introduce new temps to set up args correctly for Callee.
+        // This involves patching LCL_VAR and LCL_VAR_ADDR nodes holding Caller stack args
+        // and replacing them with a new temp. Control expr also can contain nodes that need
+        // to be patched.
+        // Therefore lower fast tail call must be done after controlExpr is inserted into LIR.
+        // There is one side effect which is flipping the order of PME and control expression
+        // since LowerFastTailCall calls InsertPInvokeMethodEpilog.
+        LowerFastTailCall(call);
     }
 
     if (comp->opts.IsJit64Compat())
diff --git a/tests/src/JIT/Regression/JitBlue/GitHub_20625/GitHub_20625.il b/tests/src/JIT/Regression/JitBlue/GitHub_20625/GitHub_20625.il
new file mode 100644 (file)
index 0000000..c9133fa
--- /dev/null
@@ -0,0 +1,147 @@
+// 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.
+
+// GitHub 20625: bug with LowerFastTailCall on platforms where fast tail call is supported.
+// Lower fast tail call can replace local variables (holding Caller stack arguments) with new temps
+// in order to set up Callee stack arguments correctly.
+// This involves patching corresponding LCL_VAR and LCL_VAR_ADDR nodes and replacing them with the location
+// of a new temp. This was not done for control expression which continued pointing to the old location
+// and could contain a Callee argument.
+// In this example, in Der1:M during tail call Caller arg6 points to the same location as Callee arg6
+// (which is null) and causes NullReferenceException.
+
+.assembly extern System.Runtime
+{
+}
+
+.assembly GitHub_20625
+{
+}
+
+.class private abstract auto ansi beforefieldinit Base
+       extends [System.Runtime]System.Object
+{
+  .method public hidebysig newslot abstract virtual
+          instance void  M(int32 arg1, int32 arg2, int32 arg3, int32 arg4, int32 arg5, class Base arg6) cil managed
+  {
+  }
+
+  .method family hidebysig specialname rtspecialname
+          instance void  .ctor() cil managed
+  {
+    .maxstack  8
+    IL_0000:  ldarg.0
+    IL_0001:  call       instance void [System.Runtime]System.Object::.ctor()
+    IL_0006:  ret
+  }
+}
+
+.class private auto ansi beforefieldinit Der1
+       extends Base
+{
+  .method public hidebysig virtual instance void
+          M(int32 arg1, int32 arg2, int32 arg3, int32 arg4, int32 arg5, class Base arg6) cil managed nooptimization
+  {
+    .maxstack  8
+    IL_0000:  ldarg.s    arg6
+    IL_0002:  ldarg.1
+    IL_0003:  ldarg.2
+    IL_0004:  ldarg.3
+    IL_0005:  ldarg.s    arg4
+    IL_0007:  ldarg.s    arg5
+    IL_0009:  ldnull
+    IL_000a:  tail.
+              callvirt   instance void Base::M(int32, int32, int32, int32, int32, class Base)
+    IL_000f:  ret
+  }
+
+  .method public hidebysig specialname rtspecialname
+          instance void  .ctor() cil managed
+  {
+    .maxstack  8
+    IL_0000:  ldarg.0
+    IL_0001:  call       instance void Base::.ctor()
+    IL_0006:  ret
+  }
+
+}
+
+.class private auto ansi beforefieldinit Der2
+       extends Base
+{
+  .method public hidebysig virtual instance void
+          M(int32 arg1, int32 arg2, int32 arg3, int32 arg4, int32 arg5, class Base arg6) cil managed
+  {
+    .maxstack  8
+    IL_0000:  ret
+  }
+
+  .method public hidebysig specialname rtspecialname
+          instance void  .ctor() cil managed
+  {
+    .maxstack  8
+    IL_0000:  ldarg.0
+    IL_0001:  call       instance void Base::.ctor()
+    IL_0006:  ret
+  }
+}
+
+.class private auto ansi beforefieldinit Program
+       extends [System.Runtime]System.Object
+{
+  .method private hidebysig static int32
+          Run(class Base der1, class Base der2) cil managed noinlining
+  {
+    .maxstack  7
+    .locals init (int32 V_0)
+    .try
+    {
+      IL_0000:  ldarg.0
+      IL_0001:  ldc.i4.2
+      IL_0002:  ldc.i4.0
+      IL_0003:  ldc.i4.6
+      IL_0004:  ldc.i4.2
+      IL_0005:  ldc.i4.5
+      IL_0006:  ldarg.1
+      IL_0007:  callvirt   instance void Base::M(int32, int32, int32, int32, int32, class Base)
+      IL_000c:  ldc.i4.s   100
+      IL_000e:  stloc.0
+      IL_000f:  leave.s    IL_0016
+
+    }
+    catch [System.Runtime]System.NullReferenceException
+    {
+      IL_0011:  pop
+      IL_0012:  ldc.i4.1
+      IL_0013:  stloc.0
+      IL_0014:  leave.s    IL_0016
+    }
+    IL_0016:  ldloc.0
+    IL_0017:  ret
+  }
+
+  .method private hidebysig static int32
+          Main(string[] args) cil managed
+  {
+    .entrypoint
+
+    .maxstack  2
+    .locals init (class Der2 V_0)
+    IL_0000:  newobj     instance void Der1::.ctor()
+    IL_0005:  newobj     instance void Der2::.ctor()
+    IL_000a:  stloc.0
+    IL_000b:  ldloc.0
+    IL_000c:  call       int32 Program::Run(class Base, class Base)
+    IL_0011:  ret
+  }
+
+  .method public hidebysig specialname rtspecialname
+          instance void  .ctor() cil managed
+  {
+    .maxstack  8
+    IL_0000:  ldarg.0
+    IL_0001:  call       instance void [System.Runtime]System.Object::.ctor()
+    IL_0006:  ret
+  }
+}
diff --git a/tests/src/JIT/Regression/JitBlue/GitHub_20625/GitHub_20625.ilproj b/tests/src/JIT/Regression/JitBlue/GitHub_20625/GitHub_20625.ilproj
new file mode 100644 (file)
index 0000000..abc30a8
--- /dev/null
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <AssemblyName>$(MSBuildProjectName)</AssemblyName>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{95DFC527-4DC1-495E-97D7-E94EE1F7140D}</ProjectGuid>
+    <OutputType>Exe</OutputType>
+    <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+    <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
+  </PropertyGroup>
+  <!-- Default configurations to help VS understand the configurations -->
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+  </PropertyGroup>
+  <ItemGroup>
+    <CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
+      <Visible>False</Visible>
+    </CodeAnalysisDependentAssemblyPaths>
+  </ItemGroup>
+  <PropertyGroup>
+    <DebugType>None</DebugType>
+    <Optimize>True</Optimize>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="GitHub_20625.il" />
+  </ItemGroup>
+  <ItemGroup>
+    <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
+  </ItemGroup>
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
+  <PropertyGroup Condition=" '$(MsBuildProjectDirOverride)' != '' ">
+  </PropertyGroup>
+</Project>