Skip to content

Commit

Permalink
JIT: remove boxing for interface call to shared generic struct
Browse files Browse the repository at this point in the history
Improve the jit's ability to optimize a box-interface call sequence
to handle cases where the unboxed entry point requires a method table
argument.

Added two test cases, one from the inspiring issue, and another where
the jit is then able to inline the method.

Closes #16982.
  • Loading branch information
AndyAyersMS committed Mar 17, 2018
1 parent f6213ca commit 0bb26f3
Show file tree
Hide file tree
Showing 7 changed files with 166 additions and 8 deletions.
3 changes: 2 additions & 1 deletion src/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -2270,7 +2270,8 @@ class Compiler
BR_REMOVE_AND_NARROW, // remove effects, minimize remaining work, return possibly narrowed source tree
BR_REMOVE_AND_NARROW_WANT_TYPE_HANDLE, // remove effects and minimize remaining work, return type handle tree
BR_REMOVE_BUT_NOT_NARROW, // remove effects, return original source tree
BR_DONT_REMOVE, // just check if removal is possible
BR_DONT_REMOVE, // check if removal is possible, return copy source tree
BR_DONT_REMOVE_WANT_TYPE_HANDLE, // check if removal is possible, return type handle tree
BR_MAKE_LOCAL_COPY // revise box to copy to temp local and return local's address
};

Expand Down
7 changes: 6 additions & 1 deletion src/jit/gentree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13468,7 +13468,7 @@ GenTree* Compiler::gtTryRemoveBoxUpstreamEffects(GenTree* op, BoxRemovalOptions

// If we're eventually going to return the type handle, remember it now.
GenTree* boxTypeHandle = nullptr;
if (options == BR_REMOVE_AND_NARROW_WANT_TYPE_HANDLE)
if ((options == BR_REMOVE_AND_NARROW_WANT_TYPE_HANDLE) || (options == BR_DONT_REMOVE_WANT_TYPE_HANDLE))
{
GenTree* asgSrc = asg->gtOp.gtOp2;
genTreeOps asgSrcOper = asgSrc->OperGet();
Expand Down Expand Up @@ -13632,6 +13632,11 @@ GenTree* Compiler::gtTryRemoveBoxUpstreamEffects(GenTree* op, BoxRemovalOptions
return copySrc;
}

if (options == BR_DONT_REMOVE_WANT_TYPE_HANDLE)
{
return boxTypeHandle;
}

// Otherwise, proceed with the optimization.
//
// Change the assignment expression to a NOP.
Expand Down
42 changes: 36 additions & 6 deletions src/jit/importer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19502,8 +19502,8 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call,
}

// Fetch method attributes to see if method is marked final.
const DWORD derivedMethodAttribs = info.compCompHnd->getMethodAttribs(derivedMethod);
const bool derivedMethodIsFinal = ((derivedMethodAttribs & CORINFO_FLG_FINAL) != 0);
DWORD derivedMethodAttribs = info.compCompHnd->getMethodAttribs(derivedMethod);
const bool derivedMethodIsFinal = ((derivedMethodAttribs & CORINFO_FLG_FINAL) != 0);

#if defined(DEBUG)
const char* derivedClassName = "?derivedClass";
Expand Down Expand Up @@ -19597,7 +19597,6 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call,
JITDUMP("Now have direct call to boxed entry point, looking for unboxed entry point\n");

// Note for some shared methods the unboxed entry point requires an extra parameter.
// We defer optimizing if so.
bool requiresInstMethodTableArg = false;
CORINFO_METHOD_HANDLE unboxedEntryMethod =
info.compCompHnd->getUnboxedEntry(derivedMethod, &requiresInstMethodTableArg);
Expand All @@ -19614,9 +19613,40 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call,
// the copy, we can undo the copy too.
if (requiresInstMethodTableArg)
{
// We can likely handle this case by grabbing the argument passed to
// the newobj in the box. But defer for now.
JITDUMP("Found unboxed entry point, but it needs method table arg, deferring\n");
// Perform a trial box removal and ask for the type handle tree.
JITDUMP("Unboxed entry needs method table arg...\n");
GenTree* methodTableArg = gtTryRemoveBoxUpstreamEffects(thisObj, BR_DONT_REMOVE_WANT_TYPE_HANDLE);

if (methodTableArg != nullptr)
{
// If that worked, turn the box into a copy to a local var
JITDUMP("Found suitable method table arg tree [%06u]\n", dspTreeID(methodTableArg));
GenTree* localCopyThis = gtTryRemoveBoxUpstreamEffects(thisObj, BR_MAKE_LOCAL_COPY);

if (localCopyThis != nullptr)
{
// Pass the local var as this and the type handle as a new arg
JITDUMP("Success! invoking unboxed entry point on local copy, and passing method table arg\n");
call->gtCallObjp = localCopyThis;
call->gtCallArgs = gtNewListNode(methodTableArg, call->gtCallArgs);
call->gtCallMethHnd = unboxedEntryMethod;
derivedMethod = unboxedEntryMethod;

// Method attributes will differ because unboxed entry point is shared
const DWORD unboxedMethodAttribs = info.compCompHnd->getMethodAttribs(unboxedEntryMethod);
JITDUMP("Updating method attribs from 0x%08x to 0x%08x\n", derivedMethodAttribs,
unboxedMethodAttribs);
derivedMethodAttribs = unboxedMethodAttribs;
}
else
{
JITDUMP("Sorry, failed to undo the box -- can't convert to local copy\n");
}
}
else
{
JITDUMP("Sorry, failed to undo the box -- can't find method table arg\n");
}
}
else
{
Expand Down
29 changes: 29 additions & 0 deletions tests/src/JIT/opt/Devirtualization/box1.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// 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;

interface IPrint
{
void Print();
}

struct X<T> : IPrint
{
public X(T t) { _t = t; }
public void Print() { Console.WriteLine(_t); }
T _t;
}

class Y
{
static int Main()
{
var s = new X<string>("hello, world!");
// Jit should devirtualize, remove box,
// change to call unboxed entry, then inline.
((IPrint)s).Print();
return 100;
}
}
34 changes: 34 additions & 0 deletions tests/src/JIT/opt/Devirtualization/box1.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?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="box1.cs" />
</ItemGroup>
<ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
<PropertyGroup Condition=" '$(MsBuildProjectDirOverride)' != '' "></PropertyGroup>
</Project>
24 changes: 24 additions & 0 deletions tests/src/JIT/opt/Devirtualization/box2.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// 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.Threading;
using System.Threading.Tasks;

class Program
{
static async Task<int> Main()
{
for (int i = 0; i < 10; i++)
{
// In the associated AwaitUnsafeOnCompleted, the
// jit should devirtualize, remove the box,
// and change to call unboxed entry, passing
// extra context argument.
await new ValueTask<string>(Task.Delay(1).ContinueWith(_ => default(string))).ConfigureAwait(false);
}

return 100;
}
}
35 changes: 35 additions & 0 deletions tests/src/JIT/opt/Devirtualization/box2.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?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>
<Langversion>7.2</Langversion>
<DebugType>None</DebugType>
<Optimize>True</Optimize>
</PropertyGroup>
<ItemGroup>
<Compile Include="box2.cs" />
</ItemGroup>
<ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
<PropertyGroup Condition=" '$(MsBuildProjectDirOverride)' != '' "></PropertyGroup>
</Project>

0 comments on commit 0bb26f3

Please sign in to comment.