Implement `st.lclFld` decomposition and fix #6778.
authorPat Gavlin <pagavlin@microsoft.com>
Tue, 20 Sep 2016 23:44:53 +0000 (16:44 -0700)
committerPat Gavlin <pagavlin@microsoft.com>
Thu, 22 Sep 2016 01:19:05 +0000 (18:19 -0700)
The problem that was blocking both of these issues was an issue in
liveness when analyzing `st.lclFld` nodes that store to an unpromoted
long-typed local variable or any long-typed field. The latter case--a
`st.lclFld` node that targets a long-typed field--is easy to handle.
Such stores are decomposed from:

```
         /--*  t0 int
         +--*  t1 int
    t2 = *  gt_long long

         /--*  t2
         *  st.lclFld long V00
```

To:

```
         /--* t0
         *  st.lclFld int V00 [+0]

         /--* t1
         *  st.lclFld int V00 [+4]
```

The two `st.lclFld` nodes that this transformation generates are marked
with `GTF_VAR_USEASG` to indicate that they are partial (rather than
complete) defs. This is necessary in order to correctly calculate
liveness.

Before this change, stores to unpromoted long-typed local variables
were also decomposed in the above fashion. These local vars can arise in
a number of situations:
- vars that are used to store multi-reg args or return values
- vars that represent long-typed fields of promoted struct-typed vars
- long-typed vars that are marked as unenregisterable before decomp

Unfortunately, the decomposition given above differs in its liveness
semantics when compared to the original IR: in the original IR, the
`st.lclVar` is considered a plain def of its destination, while in
the decomposed IR, each `st.lclFld` is considered both a partial def
and a use of its destination. This difference--namely, that in the
first case the destination is not used at the def and in the second
case it is--causes problems for any analysis that needs to take into
account whether or not a variable is used at a def (e.g. dead store
removal, live-in sets, etc.). In order to retain the original
semantics without complicating the IR, stores to unpromoted, long-typed
local variables are not decomposed. These stores are instead handled
during code generation.

src/jit/codegenxarch.cpp
src/jit/decomposelongs.cpp
src/jit/decomposelongs.h
src/jit/lclvars.cpp
src/jit/liveness.cpp
src/jit/lowerxarch.cpp
tests/issues.targets

index 44bf2cb..9548385 100644 (file)
@@ -9654,11 +9654,13 @@ void CodeGen::genStoreLongLclVar(GenTree* treeNode)
 
         GenTreePtr loVal = op1->gtGetOp1();
         GenTreePtr hiVal = op1->gtGetOp2();
+
         // NYI: Contained immediates.
         NYI_IF((loVal->gtRegNum == REG_NA) || (hiVal->gtRegNum == REG_NA),
                "Store of long lclVar with contained immediate");
-        emit->emitIns_R_S(ins_Store(TYP_INT), EA_4BYTE, loVal->gtRegNum, lclNum, 0);
-        emit->emitIns_R_S(ins_Store(TYP_INT), EA_4BYTE, hiVal->gtRegNum, lclNum, genTypeSize(TYP_INT));
+
+        emit->emitIns_S_R(ins_Store(TYP_INT), EA_4BYTE, loVal->gtRegNum, lclNum, 0);
+        emit->emitIns_S_R(ins_Store(TYP_INT), EA_4BYTE, hiVal->gtRegNum, lclNum, genTypeSize(TYP_INT));
     }
     else if (op1->OperGet() == GT_MUL_LONG)
     {
index c3be74c..cad35a3 100644 (file)
@@ -205,8 +205,7 @@ GenTree* DecomposeLongs::DecomposeNode(GenTree* tree)
             break;
 
         case GT_STORE_LCL_FLD:
-            assert(tree->gtOp.gtOp1->OperGet() == GT_LONG);
-            NYI("st.lclFld of of TYP_LONG");
+            nextNode = DecomposeStoreLclFld(use);
             break;
 
         case GT_IND:
@@ -337,8 +336,6 @@ GenTree* DecomposeLongs::DecomposeLclVar(LIR::Use& use)
     }
     else
     {
-        noway_assert(varDsc->lvLRACandidate == false);
-
         loResult->SetOper(GT_LCL_FLD);
         loResult->AsLclFld()->gtLclOffs  = 0;
         loResult->AsLclFld()->gtFieldSeq = FieldSeqStore::NotAField();
@@ -404,48 +401,60 @@ GenTree* DecomposeLongs::DecomposeStoreLclVar(LIR::Use& use)
     }
 
     noway_assert(rhs->OperGet() == GT_LONG);
+
     unsigned   varNum = tree->AsLclVarCommon()->gtLclNum;
     LclVarDsc* varDsc = m_compiler->lvaTable + varNum;
-    m_compiler->lvaDecRefCnts(tree);
-
-    GenTree* loRhs   = rhs->gtGetOp1();
-    GenTree* hiRhs   = rhs->gtGetOp2();
-    GenTree* hiStore = m_compiler->gtNewLclLNode(varNum, TYP_INT);
-
-    if (varDsc->lvPromoted)
+    if (!varDsc->lvPromoted)
     {
-        assert(varDsc->lvFieldCnt == 2);
-
-        unsigned loVarNum = varDsc->lvFieldLclStart;
-        unsigned hiVarNum = loVarNum + 1;
-        tree->AsLclVarCommon()->SetLclNum(loVarNum);
-        hiStore->SetOper(GT_STORE_LCL_VAR);
-        hiStore->AsLclVarCommon()->SetLclNum(hiVarNum);
+        // We cannot decompose a st.lclVar that is not promoted because doing so
+        // changes its liveness semantics. For example, consider the following
+        // decomposition of a st.lclVar into two st.lclFlds:
+        //
+        // Before:
+        //
+        //          /--* t0      int
+        //          +--* t1      int
+        //     t2 = *  gt_long   long
+        //
+        //          /--* t2      long
+        //          *  st.lclVar long    V0
+        //
+        // After:
+        //          /--* t0      int
+        //          *  st.lclFld int     V0    [+0]
+        //
+        //          /--* t1      int
+        //          *  st.lclFld int     V0    [+4]
+        //
+        // Before decomposition, the `st.lclVar` is a simple def of `V0`. After
+        // decomposition, each `st.lclFld` is a partial def of `V0`. This partial
+        // def is treated as both a use and a def of the appropriate lclVar. This
+        // difference will affect any situation in which the liveness of a variable
+        // at a def matters (e.g. dead store elimination, live-in sets, etc.). As
+        // a result, we leave these stores as-is and generate the decomposed store
+        // in the code generator.
+        return tree->gtNext;
     }
-    else
-    {
-        noway_assert(varDsc->lvLRACandidate == false);
 
-        tree->SetOper(GT_STORE_LCL_FLD);
-        tree->AsLclFld()->gtLclOffs  = 0;
-        tree->AsLclFld()->gtFieldSeq = FieldSeqStore::NotAField();
-
-        hiStore->SetOper(GT_STORE_LCL_FLD);
-        hiStore->AsLclFld()->gtLclOffs  = 4;
-        hiStore->AsLclFld()->gtFieldSeq = FieldSeqStore::NotAField();
-    }
+    assert(varDsc->lvFieldCnt == 2);
+    m_compiler->lvaDecRefCnts(tree);
 
-    // 'tree' is going to steal the loRhs node for itself, so we need to remove the
-    // GT_LONG node from the threading.
-    Range().Remove(rhs);
+    GenTreeOp* value = rhs->AsOp();
+    Range().Remove(value);
 
-    tree->gtOp.gtOp1 = loRhs;
-    tree->gtType     = TYP_INT;
+    const unsigned loVarNum = varDsc->lvFieldLclStart;
+    GenTree*       loStore  = tree;
+    loStore->AsLclVarCommon()->SetLclNum(loVarNum);
+    loStore->gtOp.gtOp1 = value->gtOp1;
+    loStore->gtType     = TYP_INT;
 
-    hiStore->gtOp.gtOp1 = hiRhs;
+    const unsigned hiVarNum = loVarNum + 1;
+    GenTree*       hiStore  = m_compiler->gtNewLclLNode(hiVarNum, TYP_INT);
+    hiStore->SetOper(GT_STORE_LCL_VAR);
+    hiStore->gtOp.gtOp1 = value->gtOp2;
     hiStore->gtFlags |= GTF_VAR_DEF;
 
-    m_compiler->lvaIncRefCnts(tree);
+    m_compiler->lvaIncRefCnts(loStore);
     m_compiler->lvaIncRefCnts(hiStore);
 
     Range().InsertAfter(tree, hiStore);
@@ -454,6 +463,43 @@ GenTree* DecomposeLongs::DecomposeStoreLclVar(LIR::Use& use)
 }
 
 //------------------------------------------------------------------------
+// DecomposeStoreLclFld: Decompose GT_STORE_LCL_FLD.
+//
+// Arguments:
+//    use - the LIR::Use object for the def that needs to be decomposed.
+//
+// Return Value:
+//    The next node to process.
+//
+GenTree* DecomposeLongs::DecomposeStoreLclFld(LIR::Use& use)
+{
+    assert(use.IsInitialized());
+    assert(use.Def()->OperGet() == GT_STORE_LCL_FLD);
+
+    GenTreeLclFld* store = use.Def()->AsLclFld();
+
+    GenTreeOp* value = store->gtOp1->AsOp();
+    assert(value->OperGet() == GT_LONG);
+    Range().Remove(value);
+
+    // The original store node will be repurposed to store the low half of the GT_LONG.
+    GenTreeLclFld* loStore = store;
+    loStore->gtOp1         = value->gtOp1;
+    loStore->gtType        = TYP_INT;
+    loStore->gtFlags |= GTF_VAR_USEASG;
+
+    // Create the store for the upper half of the GT_LONG and insert it after the low store.
+    GenTreeLclFld* hiStore = m_compiler->gtNewLclFldNode(loStore->gtLclNum, TYP_INT, loStore->gtLclOffs + 4);
+    hiStore->SetOper(GT_STORE_LCL_FLD);
+    hiStore->gtOp1 = value->gtOp2;
+    hiStore->gtFlags |= (GTF_VAR_DEF | GTF_VAR_USEASG);
+
+    Range().InsertAfter(loStore, hiStore);
+
+    return hiStore->gtNext;
+}
+
+//------------------------------------------------------------------------
 // DecomposeCast: Decompose GT_CAST.
 //
 // Arguments:
index f087c3e..e45facb 100644 (file)
@@ -42,6 +42,7 @@ private:
     GenTree* DecomposeLclVar(LIR::Use& use);
     GenTree* DecomposeLclFld(LIR::Use& use);
     GenTree* DecomposeStoreLclVar(LIR::Use& use);
+    GenTree* DecomposeStoreLclFld(LIR::Use& use);
     GenTree* DecomposeCast(LIR::Use& use);
     GenTree* DecomposeCnsLng(LIR::Use& use);
     GenTree* DecomposeCall(LIR::Use& use);
index 31e982b..fccbd81 100644 (file)
@@ -1740,7 +1740,7 @@ void Compiler::lvaPromoteStructVar(unsigned lclNum, lvaStructPromotionInfo* Stru
     }
 }
 
-#if !defined(_TARGET_64BIT_)
+#if !defined(LEGACY_BACKEND) && !defined(_TARGET_64BIT_)
 //------------------------------------------------------------------------
 // lvaPromoteLongVars: "Struct promote" all register candidate longs as if they are structs of two ints.
 //
@@ -1762,23 +1762,11 @@ void Compiler::lvaPromoteLongVars()
     {
         LclVarDsc* varDsc = &lvaTable[lclNum];
         if (!varTypeIsLong(varDsc) || varDsc->lvDoNotEnregister || varDsc->lvIsMultiRegArgOrRet() ||
-            (varDsc->lvRefCnt == 0))
+            (varDsc->lvRefCnt == 0) || varDsc->lvIsStructField)
         {
             continue;
         }
 
-        // Will this work ???
-        // We can't have nested promoted structs.
-        if (varDsc->lvIsStructField)
-        {
-            if (lvaGetPromotionType(varDsc->lvParentLcl) != PROMOTION_TYPE_INDEPENDENT)
-            {
-                continue;
-            }
-            varDsc->lvIsStructField = false;
-            varDsc->lvTracked       = false;
-        }
-
         varDsc->lvFieldCnt      = 2;
         varDsc->lvFieldLclStart = lvaCount;
         varDsc->lvPromoted      = true;
@@ -1827,7 +1815,7 @@ void Compiler::lvaPromoteLongVars()
     }
 #endif // DEBUG
 }
-#endif // !_TARGET_64BIT_
+#endif // !defined(LEGACY_BACKEND) && !defined(_TARGET_64BIT_)
 
 /*****************************************************************************
  * Given a fldOffset in a promoted struct var, return the index of the local
index a8c674f..4d81524 100644 (file)
@@ -1133,7 +1133,7 @@ void Compiler::fgExtendDbgLifetimes()
 
 #if !defined(_TARGET_64BIT_) && !defined(LEGACY_BACKEND)
                     DecomposeLongs::DecomposeRange(this, blockWeight, initRange);
-#endif
+#endif // !defined(_TARGET_64BIT_) && !defined(LEGACY_BACKEND)
 
                     // Naively inserting the initializer at the end of the block may add code after the block's
                     // terminator, in which case the inserted code will never be executed (and the IR for the
index 5d6fc0a..f9d5e88 100644 (file)
@@ -194,7 +194,16 @@ void Lowering::TreeNodeInfoInit(GenTree* tree)
 
         case GT_STORE_LCL_FLD:
         case GT_STORE_LCL_VAR:
-            info->srcCount = 1;
+#ifdef _TARGET_X86_
+            if (tree->gtGetOp1()->OperGet() == GT_LONG)
+            {
+                info->srcCount = 2;
+            }
+            else
+#endif // _TARGET_X86_
+            {
+                info->srcCount = 1;
+            }
             info->dstCount = 0;
             LowerStoreLoc(tree->AsLclVarCommon());
             break;
index 41be913..45cb5a9 100644 (file)
     <!-- The following x86 failures only occur with RyuJIT/x86 -->
 
     <ItemGroup Condition="'$(XunitTestBinBase)' != '' and '$(BuildArch)' == 'x86'">
-        <ExcludeList Include="$(XunitTestBinBase)\JIT\jit64\eh\basics\loopEH\loopEH.cmd">
-            <Issue>6778</Issue>
-        </ExcludeList>
-        <ExcludeList Include="$(XunitTestBinBase)\JIT\Methodical\int64\superlong\_il_relsuperlong\_il_relsuperlong.cmd">
-            <Issue>6778</Issue>
-        </ExcludeList>
         <ExcludeList Include="$(XunitTestBinBase)\GC\Scenarios\DoublinkList\dlstack\*">
             <Issue>6553</Issue>
         </ExcludeList>