Remove redundant zero-initialization of struct temps with GC fields.
Structs with GC pointer fields are fully zero-initialized in the prolog if compInitMem is true.
Therefore, we don't need to insert zero-initialization for the result of newobj or for inlinee locals
when they are structs with GC pointer fields and the basic bock is not in a loop.
// Returns "true" iff lcl "lclNum" should be excluded from SSA.
inline bool fgExcludeFromSsa(unsigned lclNum);
+ // Returns "true" if a struct temp of the given type requires needs zero init in this block
+ inline bool fgStructTempNeedsExplicitZeroInit(LclVarDsc* varDsc, BasicBlock* block);
+
// The value numbers for this compilation.
ValueNumStore* vnStore;
#endif // MEASURE_CLRAPI_CALLS
+//------------------------------------------------------------------------------
+// fgStructTempNeedsExplicitZeroInit : Check whether temp struct needs
+// explicit zero initialization in this basic block.
+//
+// Arguments:
+// varDsc - struct local var description
+// block - basic block to check
+//
+// Returns:
+// true if the struct temp needs explicit zero-initialization in this basic block;
+// false otherwise
+//
+// Notes:
+// Structs with GC pointer fields are fully zero-initialized in the prolog if compInitMem is true.
+// Therefore, we don't need to insert zero-initialization if this block is not in a loop.
+
+bool Compiler::fgStructTempNeedsExplicitZeroInit(LclVarDsc* varDsc, BasicBlock* block)
+{
+ bool containsGCPtr = (varDsc->lvStructGcCount > 0);
+ return (!containsGCPtr || !info.compInitMem || ((block->bbFlags & BBF_BACKWARD_JUMP) != 0));
+}
+
/*****************************************************************************/
bool Compiler::fgExcludeFromSsa(unsigned lclNum)
{
CORINFO_CLASS_HANDLE structType =
lclVarInfo[lclNum + inlineInfo->argCnt].lclVerTypeInfo.GetClassHandle();
- tree = gtNewBlkOpNode(gtNewLclvNode(tmpNum, lclTyp), // Dest
- gtNewIconNode(0), // Value
- info.compCompHnd->getClassSize(structType), // Size
- false, // isVolatile
- false); // not copyBlock
-
- newStmt = gtNewStmt(tree, callILOffset);
- afterStmt = fgInsertStmtAfter(block, afterStmt, newStmt);
+ if (fgStructTempNeedsExplicitZeroInit(lvaTable + tmpNum, block))
+ {
+ tree = gtNewBlkOpNode(gtNewLclvNode(tmpNum, lclTyp), // Dest
+ gtNewIconNode(0), // Value
+ info.compCompHnd->getClassSize(structType), // Size
+ false, // isVolatile
+ false); // not copyBlock
+
+ newStmt = gtNewStmt(tree, callILOffset);
+ afterStmt = fgInsertStmtAfter(block, afterStmt, newStmt);
+ }
}
#ifdef DEBUG
exactContextHnd = callInfo->contextHandle;
exactContextNeedsRuntimeLookup = callInfo->exactContextNeedsRuntimeLookup == TRUE;
- // Recursive call is treaded as a loop to the begining of the method.
+ // Recursive call is treated as a loop to the begining of the method.
if (methHnd == info.compMethodHnd)
{
#ifdef DEBUG
// and potentially exploitable.
lvaSetStruct(lclNum, resolvedToken.hClass, true /* unsafe value cls check */);
}
-
- // Append a tree to zero-out the temp
- newObjThisPtr = gtNewLclvNode(lclNum, lvaTable[lclNum].TypeGet());
-
- newObjThisPtr = gtNewBlkOpNode(newObjThisPtr, // Dest
- gtNewIconNode(0), // Value
- size, // Size
- false, // isVolatile
- false); // not copyBlock
- impAppendTree(newObjThisPtr, (unsigned)CHECK_SPILL_NONE, impCurStmtOffs);
+ if (compIsForInlining() || fgStructTempNeedsExplicitZeroInit(lvaTable + lclNum, block))
+ {
+ // Append a tree to zero-out the temp
+ newObjThisPtr = gtNewLclvNode(lclNum, lvaTable[lclNum].TypeGet());
+
+ newObjThisPtr = gtNewBlkOpNode(newObjThisPtr, // Dest
+ gtNewIconNode(0), // Value
+ size, // Size
+ false, // isVolatile
+ false); // not copyBlock
+ impAppendTree(newObjThisPtr, (unsigned)CHECK_SPILL_NONE, impCurStmtOffs);
+ }
// Obtain the address of the temp
newObjThisPtr =
// If compInitMem is set, we may need to zero-initialize some locals. Normally it's done in the prolog
// but this loop can't include the prolog. Since we don't have liveness information, we insert zero-initialization
- // for all non-parameter non-temp locals. Liveness phase will remove unnecessary initializations.
+ // for all non-parameter non-temp locals as well as temp structs with GC fields.
+ // Liveness phase will remove unnecessary initializations.
if (info.compInitMem)
{
unsigned varNum;
LclVarDsc* varDsc;
- for (varNum = 0, varDsc = lvaTable; varNum < info.compLocalsCount; varNum++, varDsc++)
+ for (varNum = 0, varDsc = lvaTable; varNum < lvaCount; varNum++, varDsc++)
{
+ var_types lclType = varDsc->TypeGet();
if (!varDsc->lvIsParam)
{
- assert(!varDsc->lvIsTemp);
- var_types lclType = varDsc->TypeGet();
- GenTreePtr lcl = gtNewLclvNode(varNum, lclType);
- GenTreePtr init = nullptr;
- if (lclType == TYP_STRUCT)
+ if (!varDsc->lvIsTemp || ((lclType == TYP_STRUCT) && (varDsc->lvStructGcCount > 0)))
{
- const bool isVolatile = false;
- const bool isCopyBlock = false;
- init = gtNewBlkOpNode(lcl, gtNewIconNode(0), varDsc->lvSize(), isVolatile, isCopyBlock);
- init = fgMorphInitBlock(init);
- }
- else
- {
- GenTreePtr zero = gtNewZeroConNode(genActualType(lclType));
- init = gtNewAssignNode(lcl, zero);
+ var_types lclType = varDsc->TypeGet();
+ GenTreePtr lcl = gtNewLclvNode(varNum, lclType);
+ GenTreePtr init = nullptr;
+ if (lclType == TYP_STRUCT)
+ {
+ const bool isVolatile = false;
+ const bool isCopyBlock = false;
+ init = gtNewBlkOpNode(lcl, gtNewIconNode(0), varDsc->lvSize(), isVolatile, isCopyBlock);
+ init = fgMorphInitBlock(init);
+ }
+ else
+ {
+ GenTreePtr zero = gtNewZeroConNode(genActualType(lclType));
+ init = gtNewAssignNode(lcl, zero);
+ }
+ GenTreePtr initStmt = gtNewStmt(init, callILOffset);
+ fgInsertStmtBefore(block, last, initStmt);
}
- GenTreePtr initStmt = gtNewStmt(init, callILOffset);
- fgInsertStmtBefore(block, last, initStmt);
}
}
}
--- /dev/null
+// 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.
+
+.assembly extern mscorlib {}
+
+.assembly GitHub_13822
+{
+}
+
+
+// =============== CLASS MEMBERS DECLARATION ===================
+
+.class public sequential ansi sealed beforefieldinit GCStruct
+ extends [System.Private.CoreLib]System.ValueType
+{
+ .field public string str
+ .field public int32 i
+ .field public int32 j
+ .field public int32 k
+ .field public int32 l
+
+ .method public hidebysig specialname rtspecialname
+ instance void .ctor(int32 i) cil managed aggressiveinlining
+ {
+ .maxstack 8
+ ldarg.0
+ ldstr "Hello"
+ stfld string GCStruct::str
+ ldarg.1
+ brtrue.s RETURN
+ ldarg.0
+ ldc.i4.1
+ stfld int32 GCStruct::i
+ RETURN: ret
+ } // end of method GCStruct::.ctor
+
+} // end of class GCStruct
+
+ // Method A has tail-recursive call that the jit turns into a loop.
+ // This test verifies that the temp struct returned by newobj is zero-initialized
+ // before calling the constructor on each iteration of the loop.
+
+.class public auto ansi beforefieldinit Test
+ extends [System.Private.CoreLib]System.Object
+{
+ .method public hidebysig static int32 Main() cil managed
+ {
+ .entrypoint
+ .maxstack 8
+ ldc.i4.0
+ call int32 Test::A(int32)
+ ret
+ } // end of method Test::Main
+
+ .method public hidebysig static int32 A(int32 i) cil managed
+ {
+ .maxstack 2
+ .locals init (int32 V_0)
+ call int32 Test::GetZero()
+ stloc.0
+ ldarg.0
+ ldc.i4.3
+ blt.s L1
+ ldc.i4.s 100
+ ret
+ L1: ldarg.0
+ newobj instance void GCStruct::.ctor(int32)
+ ldfld int32 GCStruct::i
+ ldarg.0
+ mul
+ brfalse L2
+ ldc.i4.m1
+ ret
+ L2: ldarg.0
+ ldc.i4.1
+ add
+ call int32 Test::A(int32)
+ ret
+ } // end of method Test::A
+
+ .method public hidebysig static int32 GetZero() cil managed noinlining
+ {
+ .maxstack 8
+ ldc.i4.0
+ ret
+ } // end of method Test::GetZero
+
+ .method public hidebysig specialname rtspecialname
+ instance void .ctor() cil managed
+ {
+ // Code size 7 (0x7)
+ .maxstack 8
+ ldarg.0
+ call instance void [System.Private.CoreLib]System.Object::.ctor()
+ ret
+ } // end of method Test::.ctor
+
+} // end of class Test
+
+
+// =============================================================
--- /dev/null
+<?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>
+ <CLRTestPriority>1</CLRTestPriority>
+ </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_13822.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>
\ No newline at end of file