#endif
}
+#if FEATURE_FASTTAILCALL
//------------------------------------------------------------------------
// fgCallHasMustCopyByrefParameter: Check to see if this call has a byref parameter that
// requires a struct copy in the caller.
//
// Arguments:
-// callee - The callee to check
+// call - The call to check
//
// Return Value:
// Returns true or false based on whether this call has a byref parameter that
// requires a struct copy in the caller.
+//
+bool Compiler::fgCallHasMustCopyByrefParameter(GenTreeCall* call)
+{
+#if FEATURE_IMPLICIT_BYREFS
+ for (CallArg& arg : call->gtArgs.Args())
+ {
+ if (fgCallArgWillPointIntoLocalFrame(call, arg))
+ {
+ return true;
+ }
+ }
+#endif
-#if FEATURE_FASTTAILCALL
-bool Compiler::fgCallHasMustCopyByrefParameter(GenTreeCall* callee)
+ return false;
+}
+
+//------------------------------------------------------------------------
+// fgCallArgWillPointIntoLocalFrame:
+// Check to see if a call arg will end up pointing into the local frame after morph.
+//
+// Arguments:
+// call - The call to check
+//
+// Return Value:
+// True if the arg will be passed as an implicit byref pointing to a local
+// on this function's frame; otherwise false.
+//
+// Remarks:
+// The logic here runs before relevant nodes have been morphed.
+//
+bool Compiler::fgCallArgWillPointIntoLocalFrame(GenTreeCall* call, CallArg& arg)
{
- bool hasMustCopyByrefParameter = false;
+ if (!arg.AbiInfo.PassedByRef)
+ {
+ return false;
+ }
- unsigned argCount = callee->gtArgs.CountArgs();
- for (CallArg& arg : callee->gtArgs.Args())
+ // If we're optimizing, we may be able to pass our caller's byref to our callee,
+ // and so still be able to avoid a struct copy.
+ if (opts.OptimizationDisabled())
{
- if (arg.AbiInfo.IsStruct)
- {
- if (arg.AbiInfo.PassedByRef)
- {
- // Generally a byref arg will block tail calling, as we have to
- // make a local copy of the struct for the callee.
- hasMustCopyByrefParameter = true;
+ return true;
+ }
- // If we're optimizing, we may be able to pass our caller's byref to our callee,
- // and so still be able to avoid a struct copy.
- if (opts.OptimizationEnabled())
- {
- // First, see if this is an arg off of an implicit byref param.
- GenTreeLclVarCommon* const lcl = arg.GetNode()->IsImplicitByrefParameterValuePreMorph(this);
+ // First, see if this arg is an implicit byref param.
+ GenTreeLclVarCommon* const lcl = arg.GetNode()->IsImplicitByrefParameterValuePreMorph(this);
- if (lcl != nullptr)
- {
- // Yes, the arg is an implicit byref param.
- const unsigned lclNum = lcl->GetLclNum();
- LclVarDsc* const varDsc = lvaGetDesc(lcl);
+ if (lcl == nullptr)
+ {
+ return true;
+ }
- // The param must not be promoted; if we've promoted, then the arg will be
- // a local struct assembled from the promoted fields.
- if (varDsc->lvPromoted)
- {
- JITDUMP("Arg [%06u] is promoted implicit byref V%02u, so no tail call\n",
- dspTreeID(arg.GetNode()), lclNum);
- }
- else
- {
- JITDUMP("Arg [%06u] is unpromoted implicit byref V%02u, seeing if we can still tail call\n",
- dspTreeID(arg.GetNode()), lclNum);
+ // Yes, the arg is an implicit byref param.
+ const unsigned lclNum = lcl->GetLclNum();
+ LclVarDsc* const varDsc = lvaGetDesc(lcl);
- // We have to worry about introducing aliases if we bypass copying
- // the struct at the call. We'll do some limited analysis to see if we
- // can rule this out.
- const unsigned argLimit = 6;
+ // The param must not be promoted; if we've promoted, then the arg will be
+ // a local struct assembled from the promoted fields.
+ if (varDsc->lvPromoted)
+ {
+ JITDUMP("Arg [%06u] is promoted implicit byref V%02u, so no tail call\n", dspTreeID(arg.GetNode()), lclNum);
- // If this is the only appearance of the byref in the method, then
- // aliasing is not possible.
- //
- // If no other call arg refers to this byref, and no other arg is
- // a pointer which could refer to this byref, we can optimize.
- //
- // We only check this for calls with small numbers of arguments,
- // as the analysis cost will be quadratic.
- //
- const unsigned totalAppearances = varDsc->lvRefCnt(RCS_EARLY);
- const unsigned callAppearances = (unsigned)varDsc->lvRefCntWtd(RCS_EARLY);
- assert(totalAppearances >= callAppearances);
+ return true;
+ }
- if (totalAppearances == 1)
- {
- JITDUMP("... yes, arg is the only appearance of V%02u\n", lclNum);
- hasMustCopyByrefParameter = false;
- }
- else if (totalAppearances > callAppearances)
- {
- // lvRefCntWtd tracks the number of appearances of the arg at call sites.
- // If this number doesn't match the regular ref count, there is
- // a non-call appearance, and we must be conservative.
- //
- JITDUMP("... no, arg has %u non-call appearance(s)\n",
- totalAppearances - callAppearances);
- }
- else if (argCount <= argLimit)
- {
- JITDUMP("... all %u appearance(s) are as implicit byref args to calls.\n"
- "... Running alias analysis on this call's args\n",
- totalAppearances);
- GenTree* interferingArg = nullptr;
- for (CallArg& arg2 : callee->gtArgs.Args())
- {
- if (&arg2 == &arg)
- {
- continue;
- }
-
- JITDUMP("... checking other arg [%06u]...\n", dspTreeID(arg2.GetNode()));
- DISPTREE(arg2.GetNode());
-
- // Do we pass 'lcl' more than once to the callee?
- if (arg2.AbiInfo.IsStruct && arg2.AbiInfo.PassedByRef)
- {
- GenTreeLclVarCommon* const lcl2 =
- arg2.GetNode()->IsImplicitByrefParameterValuePreMorph(this);
-
- if ((lcl2 != nullptr) && (lclNum == lcl2->GetLclNum()))
- {
- // not copying would introduce aliased implicit byref structs
- // in the callee ... we can't optimize.
- interferingArg = arg2.GetNode();
- break;
- }
- else
- {
- JITDUMP("... arg refers to different implicit byref V%02u\n",
- lcl2->GetLclNum());
- continue;
- }
- }
-
- // Do we pass a byref pointer which might point within 'lcl'?
- //
- // We can assume the 'lcl' is unaliased on entry to the
- // method, so the only way we can have an aliasing byref pointer at
- // the call is if 'lcl' is address taken/exposed in the method.
- //
- // Note even though 'lcl' is not promoted, we are in the middle
- // of the promote->rewrite->undo->(morph)->demote cycle, and so
- // might see references to promoted fields of 'lcl' that haven't yet
- // been demoted (see fgMarkDemotedImplicitByRefArgs).
- //
- // So, we also need to scan all 'lcl's fields, if any, to see if they
- // are exposed.
- //
- // When looking for aliases from other args, we check for both TYP_BYREF
- // and TYP_I_IMPL typed args here. Conceptually anything that points into
- // an implicit byref parameter should be TYP_BYREF, as these parameters could
- // refer to boxed heap locations (say if the method is invoked by reflection)
- // but there are some stack only structs (like typed references) where
- // the importer/runtime code uses TYP_I_IMPL, and AddFinalArgsAndDetermineABIInfo
- // will transiently retype all simple address-of implicit parameter args as
- // TYP_I_IMPL.
- //
- if ((arg2.AbiInfo.ArgType == TYP_BYREF) || (arg2.AbiInfo.ArgType == TYP_I_IMPL))
- {
- JITDUMP("...arg is a byref, must run an alias check\n");
- bool checkExposure = true;
- bool hasExposure = false;
-
- // See if there is any way arg could refer to a parameter struct.
- GenTree* arg2Node = arg2.GetNode();
- if (arg2Node->OperIs(GT_LCL_VAR))
- {
- GenTreeLclVarCommon* arg2LclNode = arg2Node->AsLclVarCommon();
- assert(arg2LclNode->GetLclNum() != lclNum);
- LclVarDsc* arg2Dsc = lvaGetDesc(arg2LclNode);
-
- // Other params can't alias implicit byref params
- if (arg2Dsc->lvIsParam)
- {
- checkExposure = false;
- }
- }
- // Because we're checking TYP_I_IMPL above, at least
- // screen out obvious things that can't cause aliases.
- else if (arg2Node->IsIntegralConst())
- {
- checkExposure = false;
- }
-
- if (checkExposure)
- {
- JITDUMP(
- "... not sure where byref arg points, checking if V%02u is exposed\n",
- lclNum);
- // arg2 might alias arg, see if we've exposed
- // arg somewhere in the method.
- if (varDsc->lvHasLdAddrOp || varDsc->IsAddressExposed())
- {
- // Struct as a whole is exposed, can't optimize
- JITDUMP("... V%02u is exposed\n", lclNum);
- hasExposure = true;
- }
- else if (varDsc->lvFieldLclStart != 0)
- {
- // This is the promoted/undone struct case.
- //
- // The field start is actually the local number of the promoted local,
- // use it to enumerate the fields.
- const unsigned promotedLcl = varDsc->lvFieldLclStart;
- LclVarDsc* const promotedVarDsc = lvaGetDesc(promotedLcl);
- JITDUMP("...promoted-unpromoted case -- also checking exposure of "
- "fields of V%02u\n",
- promotedLcl);
-
- for (unsigned fieldIndex = 0; fieldIndex < promotedVarDsc->lvFieldCnt;
- fieldIndex++)
- {
- LclVarDsc* fieldDsc =
- lvaGetDesc(promotedVarDsc->lvFieldLclStart + fieldIndex);
-
- if (fieldDsc->lvHasLdAddrOp || fieldDsc->IsAddressExposed())
- {
- // Promoted and not yet demoted field is exposed, can't optimize
- JITDUMP("... field V%02u is exposed\n",
- promotedVarDsc->lvFieldLclStart + fieldIndex);
- hasExposure = true;
- break;
- }
- }
- }
- }
-
- if (hasExposure)
- {
- interferingArg = arg2.GetNode();
- break;
- }
- }
- else
- {
- JITDUMP("...arg is not a byref or implicit byref (%s)\n",
- varTypeName(arg2.GetNode()->TypeGet()));
- }
- }
+ assert(!varDsc->lvIsStructField);
- if (interferingArg != nullptr)
- {
- JITDUMP("... no, arg [%06u] may alias with V%02u\n", dspTreeID(interferingArg),
- lclNum);
- }
- else
- {
- JITDUMP("... yes, no other arg in call can alias V%02u\n", lclNum);
- hasMustCopyByrefParameter = false;
- }
- }
- else
- {
- JITDUMP(" ... no, call has %u > %u args, alias analysis deemed too costly\n", argCount,
- argLimit);
- }
- }
- }
- }
+ JITDUMP("Arg [%06u] is unpromoted implicit byref V%02u, seeing if we can still tail call\n",
+ dspTreeID(arg.GetNode()), lclNum);
- if (hasMustCopyByrefParameter)
- {
- // This arg requires a struct copy. No reason to keep scanning the remaining args.
- break;
- }
- }
- }
+ GenTreeFlags deathFlags;
+ if (varDsc->lvFieldLclStart != 0)
+ {
+ // Undone promotion case.
+ deathFlags = lvaGetDesc(varDsc->lvFieldLclStart)->AllFieldDeathFlags();
+ }
+ else
+ {
+ deathFlags = GTF_VAR_DEATH;
+ }
+
+ if ((lcl->gtFlags & deathFlags) == deathFlags)
+ {
+ JITDUMP("... yes, arg is a last use\n");
+ return false;
}
- return hasMustCopyByrefParameter;
+ JITDUMP("... no, arg is not a last use\n");
+ return true;
}
+
#endif
//------------------------------------------------------------------------