`fgArgInfo::ArgsComplete()` checks for additional conditions requiring temps that are not checked for in the body of `fgMorphArgs()`. However, if there are no register args, it won't be called.
Fix #19256
bool hasStackArgs; // true if we have one or more stack arguments
bool argsComplete; // marker for state
bool argsSorted; // marker for state
+ bool needsTemps; // one or more arguments must be copied to a temp by EvalArgsToTemps
fgArgTabEntry** argTable; // variable sized array of per argument descrption: (i.e. argTable[argTableSize])
private:
{
return hasRegArgs;
}
+ bool NeedsTemps()
+ {
+ return needsTemps;
+ }
bool HasStackArgs()
{
return hasStackArgs;
assert(varTypeIsStruct(src->gtOp.gtOp2) || src->gtOp.gtOp2->gtType == TYP_BYREF);
if (pAfterStmt)
{
+ // Insert op1 after '*pAfterStmt'
*pAfterStmt = fgInsertStmtAfter(block, *pAfterStmt, gtNewStmt(src->gtOp.gtOp1, ilOffset));
+ // Evaluate the second thing using recursion.
+ return impAssignStructPtr(destAddr, src->gtOp.gtOp2, structHnd, curLevel, pAfterStmt, ilOffset, block);
}
else
{
- impAppendTree(src->gtOp.gtOp1, curLevel, ilOffset); // do the side effect
+ // We don't have an instruction to insert after, so use the entire comma expression as our rhs.
+ asgType = impNormStructType(structHnd);
}
-
- // Evaluate the second thing using recursion.
- return impAssignStructPtr(destAddr, src->gtOp.gtOp2, structHnd, curLevel, pAfterStmt, ilOffset, block);
}
else if (src->IsLocal())
{
hasStackArgs = false;
argsComplete = false;
argsSorted = false;
+ needsTemps = false;
if (argTableSize == 0)
{
)
{
curArgTabEntry->needTmp = true;
+ needsTemps = true;
}
// For all previous arguments, unless they are a simple constant
if (prevArgTabEntry->node->gtOper != GT_CNS_INT)
{
prevArgTabEntry->needTmp = true;
+ needsTemps = true;
}
}
}
if (argCount > 1) // If this is not the only argument
{
curArgTabEntry->needTmp = true;
+ needsTemps = true;
}
else if (varTypeIsFloating(argx->TypeGet()) && (argx->OperGet() == GT_CALL))
{
// Spill all arguments that are floating point calls
curArgTabEntry->needTmp = true;
+ needsTemps = true;
}
// All previous arguments may need to be evaluated into temps
if ((prevArgTabEntry->node->gtFlags & GTF_ALL_EFFECT) != 0)
{
prevArgTabEntry->needTmp = true;
+ needsTemps = true;
}
#if FEATURE_FIXED_OUT_ARGS
// Or, if they are stored into the FIXED_OUT_ARG area
{
// Spill multireg struct arguments that have Assignments or Calls embedded in them
curArgTabEntry->needTmp = true;
+ needsTemps = true;
}
else
{
{
// Spill multireg struct arguments that are expensive to evaluate twice
curArgTabEntry->needTmp = true;
+ needsTemps = true;
}
#if defined(FEATURE_SIMD) && defined(_TARGET_ARM64_)
else if (isMultiRegArg && varTypeIsSIMD(argx->TypeGet()))
argx->AsObj()->gtOp1->gtOp.gtOp1->OperIsSimdOrHWintrinsic()))
{
curArgTabEntry->needTmp = true;
+ needsTemps = true;
}
}
#endif
// For now we use a a GT_CPBLK to copy the exact size into a GT_LCL_VAR temp.
//
curArgTabEntry->needTmp = true;
+ needsTemps = true;
}
break;
case 11:
// the argument.
//
curArgTabEntry->needTmp = true;
+ needsTemps = true;
break;
default:
if (argx->gtFlags & GTF_EXCEPT)
{
curArgTabEntry->needTmp = true;
+ needsTemps = true;
continue;
}
#else
if (compiler->fgWalkTreePre(&argx, Compiler::fgChkLocAllocCB) == Compiler::WALK_ABORT)
{
curArgTabEntry->needTmp = true;
+ needsTemps = true;
continue;
}
}
if (compiler->fgWalkTreePre(&argx, Compiler::fgChkQmarkCB) == Compiler::WALK_ABORT)
{
curArgTabEntry->needTmp = true;
+ needsTemps = true;
continue;
}
}
void fgArgInfo::EvalArgsToTemps()
{
- assert(argsSorted == true);
+ assert(argsSorted);
unsigned regArgInx = 0;
// Now go through the argument table and perform the necessary evaluation into temps
#ifdef DEBUG
if (verbose)
{
+ JITDUMP("ArgTable for %d.%s after fgInitArgInfo:\n", call->gtTreeID, GenTree::OpName(call->gtOper));
call->fgArgInfo->Dump(this);
JITDUMP("\n");
}
// Union in the side effect flags from the call's operands
call->gtFlags |= flagsSummary & GTF_ALL_EFFECT;
- // If the register arguments have already been determined
- // or we have no register arguments then we don't need to
- // call SortArgs() and EvalArgsToTemps()
+ // If we are remorphing or don't have any register arguments or other arguments that need
+ // temps, then we don't need to call SortArgs() and EvalArgsToTemps().
//
- // For UNIX_AMD64, the condition without hasStackArgCopy cannot catch
- // all cases of fgMakeOutgoingStructArgCopy() being called. hasStackArgCopy
- // is added to make sure to call EvalArgsToTemp.
- if (!reMorphing && (call->fgArgInfo->HasRegArgs()))
+ if (!reMorphing && (call->fgArgInfo->HasRegArgs() || call->fgArgInfo->NeedsTemps()))
{
- // This is the first time that we morph this call AND it has register arguments.
- // Follow into the code below and do the 'defer or eval to temp' analysis.
+ // Do the 'defer or eval to temp' analysis.
call->fgArgInfo->SortArgs();
#ifdef DEBUG
if (verbose)
{
+ JITDUMP("ArgTable for %d.%s after fgMorphArgs:\n", call->gtTreeID, GenTree::OpName(call->gtOper));
call->fgArgInfo->Dump(this);
JITDUMP("\n");
}
--- /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.
+//
+
+using System;
+using System.Runtime.CompilerServices;
+
+
+struct S2
+{
+ public uint F0;
+ public ulong F1, F2;
+ public S2(uint f0) : this() { F0 = f0; }
+}
+
+public class GitHub_19256
+{
+ static S2 s_one = new S2(1);
+ static S2 s_two = new S2(2);
+ static uint sum = 0;
+ public static int Main()
+ {
+ M28(s_two, M28(s_one, s_one));
+ return sum == 3 ? 100 : -1;
+ }
+
+ static ref S2 M28(S2 arg0, S2 arg1)
+ {
+ sum += arg0.F0;
+ System.Console.WriteLine(arg0.F0);
+ arg0.F0 = 1234; // this is printed in next invocation
+ System.GC.KeepAlive(arg0); // ensure that assignment isn't removed
+ return ref s_one;
+ }
+}
+
+
--- /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>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{2649FAFE-07BF-4F93-8120-BA9A69285ABB}</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>
+ <PropertyGroup>
+ <LangVersion>latest</LangVersion>
+ <DebugType>None</DebugType>
+ <Optimize>True</Optimize>
+ </PropertyGroup>
+ <ItemGroup>
+ <CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
+ <Visible>False</Visible>
+ </CodeAnalysisDependentAssemblyPaths>
+ </ItemGroup>
+ <ItemGroup>
+ <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="$(MSBuildProjectName).cs" />
+ </ItemGroup>
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
+ <PropertyGroup Condition=" '$(MsBuildProjectDirOverride)' != '' "></PropertyGroup>
+</Project>
\ No newline at end of file