Skip to content

Commit

Permalink
Add explicit null-check for tailcalls to VSD
Browse files Browse the repository at this point in the history
There is already a comment that this is necessary, but it is only being
done for x86 tailcalls via jit helper. Do it for normal tailcalls to VSD
as well.

Fix #61486
  • Loading branch information
jakobbotsch authored and github-actions committed Dec 14, 2021
1 parent 1208ca1 commit b63403b
Show file tree
Hide file tree
Showing 8 changed files with 69 additions and 15 deletions.
2 changes: 1 addition & 1 deletion src/coreclr/jit/assertionprop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3978,7 +3978,7 @@ AssertionIndex Compiler::optAssertionIsNonNullInternal(GenTree* op,
*/
GenTree* Compiler::optNonNullAssertionProp_Call(ASSERT_VALARG_TP assertions, GenTreeCall* call)
{
if ((call->gtFlags & GTF_CALL_NULLCHECK) == 0)
if (!call->NeedsNullCheck())
{
return nullptr;
}
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/jit/compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9313,7 +9313,7 @@ void cTreeFlags(Compiler* comp, GenTree* tree)
{
chars += printf("[CALL_VIRT_STUB]");
}
if (tree->gtFlags & GTF_CALL_NULLCHECK)
if (tree->AsCall()->NeedsNullCheck())
{
chars += printf("[CALL_NULLCHECK]");
}
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/jit/fginline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1497,7 +1497,7 @@ Statement* Compiler::fgInlinePrependStatements(InlineInfo* inlineInfo)
//
GenTree* nullcheck = nullptr;

if (call->gtFlags & GTF_CALL_NULLCHECK && !inlineInfo->thisDereferencedFirst)
if (call->NeedsNullCheck() && !inlineInfo->thisDereferencedFirst)
{
// Call impInlineFetchArg to "reserve" a temp for the "this" pointer.
GenTree* thisOp = impInlineFetchArg(0, inlArgInfo, lclVarInfo);
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/jit/gentree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10059,7 +10059,7 @@ void Compiler::gtDispNodeName(GenTree* tree)
assert(!"Unknown gtCallType");
}

if (tree->gtFlags & GTF_CALL_NULLCHECK)
if (tree->AsCall()->NeedsNullCheck())
{
gtfType = " nullcheck";
}
Expand Down
4 changes: 2 additions & 2 deletions src/coreclr/jit/importer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8701,7 +8701,7 @@ var_types Compiler::impImportCall(OPCODE opcode,
assert(mflags & CORINFO_FLG_FINAL);

/* It should have the GTF_CALL_NULLCHECK flag set. Reset it */
assert(call->gtFlags & GTF_CALL_NULLCHECK);
assert(call->AsCall()->NeedsNullCheck());
call->gtFlags &= ~GTF_CALL_NULLCHECK;
}
}
Expand Down Expand Up @@ -9375,7 +9375,7 @@ var_types Compiler::impImportCall(OPCODE opcode,
{
GenTree* callObj = call->AsCall()->gtCallThisArg->GetNode();

if ((call->AsCall()->IsVirtual() || (call->gtFlags & GTF_CALL_NULLCHECK)) &&
if ((call->AsCall()->IsVirtual() || call->AsCall()->NeedsNullCheck()) &&
impInlineIsGuaranteedThisDerefBeforeAnySideEffects(nullptr, call->AsCall()->gtCallArgs, callObj,
impInlineInfo->inlArgInfo))
{
Expand Down
18 changes: 9 additions & 9 deletions src/coreclr/jit/morph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7896,6 +7896,15 @@ GenTree* Compiler::fgMorphPotentialTailCall(GenTreeCall* call)
// Avoid potential extra work for the return (for example, vzeroupper)
call->gtType = TYP_VOID;

// The runtime requires that we perform a null check on the `this` argument before
// tail calling to a virtual dispatch stub. This requirement is a consequence of limitations
// in the runtime's ability to map an AV to a NullReferenceException if
// the AV occurs in a dispatch stub that has unmanaged caller.
if (call->IsVirtualStub())
{
call->gtFlags |= GTF_CALL_NULLCHECK;
}

// Do some target-specific transformations (before we process the args,
// etc.) for the JIT helper case.
if (tailCallViaJitHelper)
Expand Down Expand Up @@ -8622,15 +8631,6 @@ void Compiler::fgMorphTailCallViaJitHelper(GenTreeCall* call)
JITDUMP("fgMorphTailCallViaJitHelper (before):\n");
DISPTREE(call);

// The runtime requires that we perform a null check on the `this` argument before
// tail calling to a virtual dispatch stub. This requirement is a consequence of limitations
// in the runtime's ability to map an AV to a NullReferenceException if
// the AV occurs in a dispatch stub that has unmanaged caller.
if (call->IsVirtualStub())
{
call->gtFlags |= GTF_CALL_NULLCHECK;
}

// For the helper-assisted tail calls, we need to push all the arguments
// into a single list, and then add a few extra at the beginning or end.
//
Expand Down
45 changes: 45 additions & 0 deletions src/tests/JIT/Regression/JitBlue/Runtime_61486/Runtime_61486.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// 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.Reflection;
using System.Runtime.CompilerServices;

public class Runtime_61486
{
public static int Main()
{
var my = new My(new My(null));
var m = my.GetType().GetMethod("M");
try
{
m.Invoke(my, null);
return -1;
}
catch (TargetInvocationException ex) when (ex.InnerException is NullReferenceException)
{
return 100;
}
}

public interface IFace
{
void M();
}

public class My : IFace
{
private IFace _face;

public My(IFace face)
{
_face = face;
}

// We cannot handle a null ref inside a VSD if the caller is not
// managed frame. This test is verifying that JIT null checks ahead of
// time in this case.
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
public void M() => _face.M();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<Optimize>True</Optimize>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildProjectName).cs" />
</ItemGroup>
</Project>

0 comments on commit b63403b

Please sign in to comment.