info.compCallUnmanaged = 0;
info.compLvFrameListRoot = BAD_VAR_NUM;
-#if FEATURE_FIXED_OUT_ARGS
- lvaOutgoingArgSpaceSize = 0;
-#endif
-
lvaGenericsContextUsed = false;
info.compInitMem = ((methodInfo->options & CORINFO_OPT_INIT_LOCALS) != 0);
// stack pointer before it was adjusted after each Call
#endif
+#if FEATURE_FIXED_OUT_ARGS
+ unsigned outArgSize; // Size of the out arg area for the call, will be at least MIN_ARG_AREA_FOR_CALL
+#endif
+
unsigned argTableSize; // size of argTable array (equal to the argCount when done with fgMorphArgs)
bool hasRegArgs; // true if we have one or more register arguments
bool hasStackArgs; // true if we have one or more stack arguments
{
return argsComplete;
}
-
+#if FEATURE_FIXED_OUT_ARGS
+ unsigned GetOutArgSize() const
+ {
+ return outArgSize;
+ }
+ void SetOutArgSize(unsigned newVal)
+ {
+ outArgSize = newVal;
+ }
+#endif // FEATURE_FIXED_OUT_ARGS
// Get the late arg for arg at position argIndex. Caller must ensure this position has a late arg.
GenTreePtr GetLateArg(unsigned argIndex);
};
// in case there are multiple BBJ_RETURN blocks in the inlinee.
#if FEATURE_FIXED_OUT_ARGS
- unsigned lvaOutgoingArgSpaceVar; // dummy TYP_LCLBLK var for fixed outgoing argument space
- unsigned lvaOutgoingArgSpaceSize; // size of fixed outgoing argument space
-#endif // FEATURE_FIXED_OUT_ARGS
+ unsigned lvaOutgoingArgSpaceVar; // dummy TYP_LCLBLK var for fixed outgoing argument space
+ PhasedVar<unsigned> lvaOutgoingArgSpaceSize; // size of fixed outgoing argument space
+#endif // FEATURE_FIXED_OUT_ARGS
#ifdef _TARGET_ARM_
// On architectures whose ABIs allow structs to be passed in registers, struct promotion will sometimes
void lvaMarkLocalVars(); // Local variable ref-counting
- void lvaAllocOutgoingArgSpace(); // 'Commit' lvaOutgoingArgSpaceSize and lvaOutgoingArgSpaceVar
+ void lvaAllocOutgoingArgSpaceVar(); // Set up lvaOutgoingArgSpaceVar
VARSET_VALRET_TP lvaStmtLclMask(GenTreePtr stmt);
}
}
-/*****************************************************************************/
+//------------------------------------------------------------------------
+// fgSimpleLowering: do full walk of all IR, lowering selected operations
+// and computing lvaOutgoingArgumentAreaSize.
+//
+// Notes:
+// Lowers GT_ARR_LENGTH, GT_ARR_BOUNDS_CHECK, and GT_SIMD_CHK.
+//
+// For target ABIs with fixed out args area, computes upper bound on
+// the size of this area from the calls in the IR.
+//
+// Outgoing arg area size is computed here because we want to run it
+// after optimization (in case calls are removed) and need to look at
+// all possible calls in the method.
+
void Compiler::fgSimpleLowering()
{
+#if FEATURE_FIXED_OUT_ARGS
+ unsigned outgoingArgSpaceSize = 0;
+#endif // FEATURE_FIXED_OUT_ARGS
+
for (BasicBlock* block = fgFirstBB; block; block = block->bbNext)
{
- // Walk the statement trees in this basic block, converting ArrLength nodes.
+ // Walk the statement trees in this basic block.
compCurBB = block; // Used in fgRngChkTarget.
#ifdef LEGACY_BACKEND
{
{
#endif
- if (tree->gtOper == GT_ARR_LENGTH)
+
+ switch (tree->OperGet())
{
- GenTreeArrLen* arrLen = tree->AsArrLen();
- GenTreePtr arr = arrLen->gtArrLen.ArrRef();
- GenTreePtr add;
- GenTreePtr con;
+ case GT_ARR_LENGTH:
+ {
+ GenTreeArrLen* arrLen = tree->AsArrLen();
+ GenTreePtr arr = arrLen->gtArrLen.ArrRef();
+ GenTreePtr add;
+ GenTreePtr con;
- /* Create the expression "*(array_addr + ArrLenOffs)" */
+ /* Create the expression "*(array_addr + ArrLenOffs)" */
- noway_assert(arr->gtNext == tree);
+ noway_assert(arr->gtNext == tree);
- noway_assert(arrLen->ArrLenOffset() == offsetof(CORINFO_Array, length) ||
- arrLen->ArrLenOffset() == offsetof(CORINFO_String, stringLen));
+ noway_assert(arrLen->ArrLenOffset() == offsetof(CORINFO_Array, length) ||
+ arrLen->ArrLenOffset() == offsetof(CORINFO_String, stringLen));
- if ((arr->gtOper == GT_CNS_INT) && (arr->gtIntCon.gtIconVal == 0))
- {
- // If the array is NULL, then we should get a NULL reference
- // exception when computing its length. We need to maintain
- // an invariant where there is no sum of two constants node, so
- // let's simply return an indirection of NULL.
+ if ((arr->gtOper == GT_CNS_INT) && (arr->gtIntCon.gtIconVal == 0))
+ {
+ // If the array is NULL, then we should get a NULL reference
+ // exception when computing its length. We need to maintain
+ // an invariant where there is no sum of two constants node, so
+ // let's simply return an indirection of NULL.
- add = arr;
- }
- else
- {
- con = gtNewIconNode(arrLen->ArrLenOffset(), TYP_I_IMPL);
- con->gtRsvdRegs = 0;
+ add = arr;
+ }
+ else
+ {
+ con = gtNewIconNode(arrLen->ArrLenOffset(), TYP_I_IMPL);
+ con->gtRsvdRegs = 0;
- add = gtNewOperNode(GT_ADD, TYP_REF, arr, con);
- add->gtRsvdRegs = arr->gtRsvdRegs;
+ add = gtNewOperNode(GT_ADD, TYP_REF, arr, con);
+ add->gtRsvdRegs = arr->gtRsvdRegs;
#ifdef LEGACY_BACKEND
- con->gtCopyFPlvl(arr);
+ con->gtCopyFPlvl(arr);
- add->gtCopyFPlvl(arr);
- add->CopyCosts(arr);
+ add->gtCopyFPlvl(arr);
+ add->CopyCosts(arr);
- arr->gtNext = con;
- con->gtPrev = arr;
+ arr->gtNext = con;
+ con->gtPrev = arr;
- con->gtNext = add;
- add->gtPrev = con;
+ con->gtNext = add;
+ add->gtPrev = con;
- add->gtNext = tree;
- tree->gtPrev = add;
+ add->gtNext = tree;
+ tree->gtPrev = add;
#else
- range.InsertAfter(arr, con, add);
+ range.InsertAfter(arr, con, add);
#endif
- }
+ }
- // Change to a GT_IND.
- tree->ChangeOperUnchecked(GT_IND);
+ // Change to a GT_IND.
+ tree->ChangeOperUnchecked(GT_IND);
- tree->gtOp.gtOp1 = add;
- }
- else if (tree->OperGet() == GT_ARR_BOUNDS_CHECK
+ tree->gtOp.gtOp1 = add;
+ break;
+ }
+
+ case GT_ARR_BOUNDS_CHECK:
#ifdef FEATURE_SIMD
- || tree->OperGet() == GT_SIMD_CHK
+ case GT_SIMD_CHK:
#endif // FEATURE_SIMD
- )
- {
- // Add in a call to an error routine.
- fgSetRngChkTarget(tree, false);
+ {
+ // Add in a call to an error routine.
+ fgSetRngChkTarget(tree, false);
+ break;
+ }
+
+#if FEATURE_FIXED_OUT_ARGS
+ case GT_CALL:
+ {
+ GenTreeCall* call = tree->AsCall();
+ // Fast tail calls use the caller-supplied scratch
+ // space so have no impact on this method's outgoing arg size.
+ if (!call->IsFastTailCall())
+ {
+ // Update outgoing arg size to handle this call
+ const unsigned thisCallOutAreaSize = call->fgArgInfo->GetOutArgSize();
+ assert(thisCallOutAreaSize >= MIN_ARG_AREA_FOR_CALL);
+
+ if (thisCallOutAreaSize > outgoingArgSpaceSize)
+ {
+ outgoingArgSpaceSize = thisCallOutAreaSize;
+ JITDUMP("Bumping outgoingArgSpaceSize to %u for call [%06d]\n", outgoingArgSpaceSize,
+ dspTreeID(tree));
+ }
+ else
+ {
+ JITDUMP("outgoingArgSpaceSize %u sufficient for call [%06d], which needs %u\n",
+ outgoingArgSpaceSize, dspTreeID(tree), thisCallOutAreaSize);
+ }
+ }
+ else
+ {
+ JITDUMP("outgoingArgSpaceSize not impacted by fast tail call [%06d]\n", dspTreeID(tree));
+ }
+ break;
+ }
+
+ default:
+ {
+ // No other operators need processing.
+ break;
+ }
+
+#endif // FEATURE_FIXED_OUT_ARGS
}
- }
+ } // foreach gtNext
+ } // foreach Stmt
+ } // foreach BB
+
+#if FEATURE_FIXED_OUT_ARGS
+ // Finish computing the outgoing args area size
+ //
+ // Need to make sure the MIN_ARG_AREA_FOR_CALL space is added to the frame if:
+ // 1. there are calls to THROW_HEPLPER methods.
+ // 2. we are generating profiling Enter/Leave/TailCall hooks. This will ensure
+ // that even methods without any calls will have outgoing arg area space allocated.
+ //
+ // An example for these two cases is Windows Amd64, where the ABI requires to have 4 slots for
+ // the outgoing arg space if the method makes any calls.
+ if (outgoingArgSpaceSize < MIN_ARG_AREA_FOR_CALL)
+ {
+ if (compUsesThrowHelper || compIsProfilerHookNeeded())
+ {
+ outgoingArgSpaceSize = MIN_ARG_AREA_FOR_CALL;
+ JITDUMP("Bumping outgoingArgSpaceSize to %u for throw helper or profile hook", outgoingArgSpaceSize);
}
}
+ // If a function has localloc, we will need to move the outgoing arg space when the
+ // localloc happens. When we do this, we need to maintain stack alignment. To avoid
+ // leaving alignment-related holes when doing this move, make sure the outgoing
+ // argument space size is a multiple of the stack alignment by aligning up to the next
+ // stack alignment boundary.
+ if (compLocallocUsed)
+ {
+ outgoingArgSpaceSize = (unsigned)roundUp(outgoingArgSpaceSize, STACK_ALIGN);
+ JITDUMP("Bumping outgoingArgSpaceSize to %u for localloc", outgoingArgSpaceSize);
+ }
+
+ // Publish the final value and mark it as read only so any update
+ // attempt later will cause an assert.
+ lvaOutgoingArgSpaceSize = outgoingArgSpaceSize;
+ lvaOutgoingArgSpaceSize.MarkAsReadOnly();
+
+#endif // FEATURE_FIXED_OUT_ARGS
+
#ifdef DEBUG
if (verbose && fgRngChkThrowAdded)
{
}
// Currently it is not good to move the rarely run handler regions to the end of the method
- // because fgDetermineFirstColdBlock() must put the start of any handler region in the hot section.
+ // because fgDetermineFirstColdBlock() must put the start of any handler region in the hot
+ // section.
CLANG_FORMAT_COMMENT_ANCHOR;
#if 0
#if FEATURE_FIXED_OUT_ARGS
lvaPInvokeFrameRegSaveVar = BAD_VAR_NUM;
lvaOutgoingArgSpaceVar = BAD_VAR_NUM;
+ lvaOutgoingArgSpaceSize = PhasedVar<unsigned>();
#endif // FEATURE_FIXED_OUT_ARGS
#ifdef _TARGET_ARM_
lvaPromotedStructAssemblyScratchVar = BAD_VAR_NUM;
return lvaTable[varNum].lvGcLayout;
}
-/*****************************************************************************
- * Return the number of bytes needed for a local variable
- */
+//------------------------------------------------------------------------
+// lvaLclSize: returns size of a local variable, in bytes
+//
+// Arguments:
+// varNum -- variable to query
+//
+// Returns:
+// Number of bytes needed on the frame for such a local.
unsigned Compiler::lvaLclSize(unsigned varNum)
{
case TYP_LCLBLK:
#if FEATURE_FIXED_OUT_ARGS
- noway_assert(lvaOutgoingArgSpaceSize >= 0);
noway_assert(varNum == lvaOutgoingArgSpaceVar);
return lvaOutgoingArgSpaceSize;
-
#else // FEATURE_FIXED_OUT_ARGS
assert(!"Unknown size");
NO_WAY("Target doesn't support TYP_LCLBLK");
}
}
- lvaAllocOutgoingArgSpace();
+ lvaAllocOutgoingArgSpaceVar();
#if !FEATURE_EH_FUNCLETS
lvaSortByRefCount();
}
-void Compiler::lvaAllocOutgoingArgSpace()
+void Compiler::lvaAllocOutgoingArgSpaceVar()
{
#if FEATURE_FIXED_OUT_ARGS
lvaTable[lvaOutgoingArgSpaceVar].lvRefCnt = 1;
lvaTable[lvaOutgoingArgSpaceVar].lvRefCntWtd = BB_UNITY_WEIGHT;
-
- if (lvaOutgoingArgSpaceSize == 0)
- {
- if (compUsesThrowHelper || compIsProfilerHookNeeded())
- {
- // Need to make sure the MIN_ARG_AREA_FOR_CALL space is added to the frame if:
- // 1. there are calls to THROW_HEPLPER methods.
- // 2. we are generating profiling Enter/Leave/TailCall hooks. This will ensure
- // that even methods without any calls will have outgoing arg area space allocated.
- //
- // An example for these two cases is Windows Amd64, where the ABI requires to have 4 slots for
- // the outgoing arg space if the method makes any calls.
- lvaOutgoingArgSpaceSize = MIN_ARG_AREA_FOR_CALL;
- }
- }
}
noway_assert(lvaOutgoingArgSpaceVar >= info.compLocalsCount && lvaOutgoingArgSpaceVar < lvaCount);
#if defined(UNIX_X86_ABI)
padStkAlign = 0;
#endif
+#if FEATURE_FIXED_OUT_ARGS
+ outArgSize = 0;
+#endif
+
argTableSize = numArgs; // the allocated table size
hasRegArgs = false;
#if defined(UNIX_X86_ABI)
padStkAlign = oldArgInfo->padStkAlign;
#endif
+#if FEATURE_FIXED_OUT_ARGS
+ outArgSize = oldArgInfo->outArgSize;
+#endif
argTableSize = oldArgInfo->argTableSize;
argsComplete = false;
argTable = nullptr;
#if FEATURE_FIXED_OUT_ARGS
- // Update the outgoing argument size.
- // If the call is a fast tail call, it will setup its arguments in incoming arg
- // area instead of the out-going arg area. Therefore, don't consider fast tail
- // calls to update lvaOutgoingArgSpaceSize.
+ // Record the outgoing argument size. If the call is a fast tail
+ // call, it will setup its arguments in incoming arg area instead
+ // of the out-going arg area, so we don't need to track the
+ // outgoing arg size.
if (!call->IsFastTailCall())
{
unsigned preallocatedArgCount = call->fgArgInfo->GetNextSlotNum();
}
#endif // UNIX_AMD64_ABI
- // Check if we need to increase the size of our Outgoing Arg Space
- if (preallocatedArgCount * REGSIZE_BYTES > lvaOutgoingArgSpaceSize)
- {
- lvaOutgoingArgSpaceSize = preallocatedArgCount * REGSIZE_BYTES;
+ const unsigned outgoingArgSpaceSize = preallocatedArgCount * REGSIZE_BYTES;
+ call->fgArgInfo->SetOutArgSize(max(outgoingArgSpaceSize, MIN_ARG_AREA_FOR_CALL));
- // If a function has localloc, we will need to move the outgoing arg space when the
- // localloc happens. When we do this, we need to maintain stack alignment. To avoid
- // leaving alignment-related holes when doing this move, make sure the outgoing
- // argument space size is a multiple of the stack alignment by aligning up to the next
- // stack alignment boundary.
- if (compLocallocUsed)
- {
- lvaOutgoingArgSpaceSize = (unsigned)roundUp(lvaOutgoingArgSpaceSize, STACK_ALIGN);
- }
- }
#ifdef DEBUG
if (verbose)
{
- printf("argSlots=%d, preallocatedArgCount=%d, nextSlotNum=%d, lvaOutgoingArgSpaceSize=%d\n", argSlots,
- preallocatedArgCount, call->fgArgInfo->GetNextSlotNum(), lvaOutgoingArgSpaceSize);
+ printf("argSlots=%d, preallocatedArgCount=%d, nextSlotNum=%d, outgoingArgSpaceSize=%d\n", argSlots,
+ preallocatedArgCount, call->fgArgInfo->GetNextSlotNum(), outgoingArgSpaceSize);
}
#endif
}
continue;
}
+#if FEATURE_FIXED_OUT_ARGS
+ // Skip the OutgoingArgArea in computing frame size, since
+ // its size is not yet known and it doesn't affect local
+ // offsets from the frame pointer (though it may affect
+ // them from the stack pointer).
+ noway_assert(m_pCompiler->lvaOutgoingArgSpaceVar != BAD_VAR_NUM);
+ if (lclNum == m_pCompiler->lvaOutgoingArgSpaceVar)
+ {
+ continue;
+ }
+#endif // FEATURE_FIXED_OUT_ARGS
+
bool onStack = (regAvailEstimate == 0); // true when it is likely that this LclVar will have a stack home
// Some LclVars always have stack homes
return m_value;
}
+ // Mark the value as read only; explicitly change the variable to the "read" phase.
+ void MarkAsReadOnly() const
+ {
+#ifdef DEBUG
+ assert(m_initialized);
+ (const_cast<PhasedVar*>(this))->m_writePhase = false;
+#endif // DEBUG
+ }
+
// Functions/operators to write the value. Must be in the write phase.
PhasedVar& operator=(const T& value)