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

JIT: remove boxing for interface call to shared generic struct #17006

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
59 changes: 53 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,57 @@ 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;

// Prepend for R2L arg passing or empty L2R passing
if ((Target::g_tgtArgOrder == Target::ARG_ORDER_R2L) || (call->gtCallArgs == nullptr))
{
call->gtCallArgs = gtNewListNode(methodTableArg, call->gtCallArgs);
}
// Append for non-empty L2R
else
{
GenTreeArgList* beforeArg = call->gtCallArgs;
while (beforeArg->Rest() != nullptr)
{
beforeArg = beforeArg->Rest();
}

beforeArg->Rest() = gtNewListNode(methodTableArg, nullptr);
}

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>