From df4a1854aa42c1e97874b00fe49339e216e30af9 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Fri, 4 Jan 2019 11:05:43 -0800 Subject: [PATCH] JIT: don't optimize struct copies for call args in debug or minopts (#21792) The jit will opportunistically optimize away copies for some struct call arguments, if that argument is a "last use" for the struct (and some other conditions apply). In cases like #21544 this leads to confusing debug experiences as inputs to a call appear to be modified by the call. Also we really should not be optimizing the code this way in debug or in minopts codegen modes. So, block this optimization for debug and minopts. Closes #21544. --- src/jit/morph.cpp | 77 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 48 insertions(+), 29 deletions(-) diff --git a/src/jit/morph.cpp b/src/jit/morph.cpp index 09c6fc7..df95c93 100644 --- a/src/jit/morph.cpp +++ b/src/jit/morph.cpp @@ -5027,8 +5027,19 @@ GenTreeFieldList* Compiler::fgMorphLclArgToFieldlist(GenTreeLclVarCommon* lcl) return newArg; } -// Make a copy of a struct variable if necessary, to pass to a callee. -// returns: tree that computes address of the outgoing arg +//------------------------------------------------------------------------ +// fgMakeOutgoingStructArgCopy: make a copy of a struct variable if necessary, +// to pass to a callee. +// +// Arguments: +// call - call being processed +// args - args for the call +/// argIndex - arg being processed +// copyBlkClass - class handle for the struct +// +// Return value: +// tree that computes address of the outgoing arg +// void Compiler::fgMakeOutgoingStructArgCopy(GenTreeCall* call, GenTree* args, unsigned argIndex, @@ -5036,38 +5047,46 @@ void Compiler::fgMakeOutgoingStructArgCopy(GenTreeCall* call, { GenTree* argx = args->Current(); noway_assert(argx->gtOper != GT_MKREFANY); - fgArgTabEntry* argEntry = Compiler::gtArgEntryByNode(call, argx); - GenTreeLclVarCommon* lcl = nullptr; + fgArgTabEntry* argEntry = Compiler::gtArgEntryByNode(call, argx); - // See if we need to insert a copy at all - // Case 1: don't need a copy if it is the last use of a local. We can't determine that all of the time - // but if there is only one use and no loops, the use must be last. - if (argx->OperIsLocal()) - { - lcl = argx->AsLclVarCommon(); - } - else if ((argx->OperGet() == GT_OBJ) && argx->AsIndir()->Addr()->OperIsLocal()) - { - lcl = argx->AsObj()->Addr()->AsLclVarCommon(); - } - if (lcl != nullptr) + // If we're optimizing, see if we can avoid making a copy. + // + // We don't need a copy if this is the last use of an implicit by-ref local. + // + // We can't determine that all of the time, but if there is only + // one use and the method has no loops, then this use must be the last. + if (!(opts.compDbgCode || opts.MinOpts())) { - unsigned varNum = lcl->AsLclVarCommon()->GetLclNum(); - if (lvaIsImplicitByRefLocal(varNum)) + GenTreeLclVarCommon* lcl = nullptr; + + if (argx->OperIsLocal()) { - LclVarDsc* varDsc = &lvaTable[varNum]; - // JIT_TailCall helper has an implicit assumption that all tail call arguments live - // on the caller's frame. If an argument lives on the caller caller's frame, it may get - // overwritten if that frame is reused for the tail call. Therefore, we should always copy - // struct parameters if they are passed as arguments to a tail call. - if (!call->IsTailCallViaHelper() && (varDsc->lvRefCnt(RCS_EARLY) == 1) && !fgMightHaveLoop()) + lcl = argx->AsLclVarCommon(); + } + else if ((argx->OperGet() == GT_OBJ) && argx->AsIndir()->Addr()->OperIsLocal()) + { + lcl = argx->AsObj()->Addr()->AsLclVarCommon(); + } + + if (lcl != nullptr) + { + unsigned varNum = lcl->AsLclVarCommon()->GetLclNum(); + if (lvaIsImplicitByRefLocal(varNum)) { - varDsc->setLvRefCnt(0, RCS_EARLY); - args->gtOp.gtOp1 = lcl; - argEntry->node = lcl; + LclVarDsc* varDsc = &lvaTable[varNum]; + // JIT_TailCall helper has an implicit assumption that all tail call arguments live + // on the caller's frame. If an argument lives on the caller caller's frame, it may get + // overwritten if that frame is reused for the tail call. Therefore, we should always copy + // struct parameters if they are passed as arguments to a tail call. + if (!call->IsTailCallViaHelper() && (varDsc->lvRefCnt(RCS_EARLY) == 1) && !fgMightHaveLoop()) + { + varDsc->setLvRefCnt(0, RCS_EARLY); + args->gtOp.gtOp1 = lcl; + argEntry->node = lcl; - JITDUMP("did not have to make outgoing copy for V%2d", varNum); - return; + JITDUMP("did not have to make outgoing copy for V%2d", varNum); + return; + } } } } -- 2.7.4