From 0e5227d3924ea11f28c5f62dee681b780ee7ecaa Mon Sep 17 00:00:00 2001 From: Bruce Forstall Date: Fri, 8 Jul 2016 18:38:59 -0700 Subject: [PATCH] Refactor RyuJIT/x86 long decomposition code All decomposition code is now in decompose.cpp, in a Decompose class. Each node type that is decomposed has its own member function to do the decomposition. Various helpers have been added to reduce code duplication. In general, though, the code is as it was before. --- src/jit/CMakeLists.txt | 2 + src/jit/compiler.h | 4 + src/jit/decomposelongs.cpp | 1206 ++++++++++++++++++++++++++++++++++ src/jit/decomposelongs.h | 61 ++ src/jit/jit.settings.targets | 2 + src/jit/lower.cpp | 832 +---------------------- src/jit/lower.h | 10 - 7 files changed, 1284 insertions(+), 833 deletions(-) create mode 100644 src/jit/decomposelongs.cpp create mode 100644 src/jit/decomposelongs.h diff --git a/src/jit/CMakeLists.txt b/src/jit/CMakeLists.txt index 7ebf3f7d52c7..b9f0c840a54e 100644 --- a/src/jit/CMakeLists.txt +++ b/src/jit/CMakeLists.txt @@ -84,6 +84,7 @@ if(CLR_CMAKE_TARGET_ARCH_AMD64) elseif(CLR_CMAKE_TARGET_ARCH_ARM) set( ARCH_SOURCES codegenarm.cpp + decomposelongs.cpp emitarm.cpp lowerarm.cpp targetarm.cpp @@ -92,6 +93,7 @@ elseif(CLR_CMAKE_TARGET_ARCH_ARM) elseif(CLR_CMAKE_TARGET_ARCH_I386) set( ARCH_SOURCES codegenxarch.cpp + decomposelongs.cpp emitxarch.cpp lowerxarch.cpp simd.cpp diff --git a/src/jit/compiler.h b/src/jit/compiler.h index 220b86220a6d..41b902a28a10 100644 --- a/src/jit/compiler.h +++ b/src/jit/compiler.h @@ -1385,6 +1385,10 @@ class Compiler friend class LclVarDsc; friend class TempDsc; +#ifndef _TARGET_64BIT_ + friend class DecomposeLongs; +#endif // !_TARGET_64BIT_ + /* XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX diff --git a/src/jit/decomposelongs.cpp b/src/jit/decomposelongs.cpp new file mode 100644 index 000000000000..f505ed0faf50 --- /dev/null +++ b/src/jit/decomposelongs.cpp @@ -0,0 +1,1206 @@ +// 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. + +/*XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +XX XX +XX DecomposeLongs XX +XX XX +XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX*/ + +// +// This file contains code to decompose 64-bit LONG operations on 32-bit platforms +// into multiple single-register operations so individual register usage and requirements +// are explicit for LSRA. The rationale behind this is to avoid adding code complexity +// downstream caused by the introduction of handling longs as special cases, +// especially in LSRA. +// +// Long decomposition happens on a statement immediately prior to more general +// purpose lowering. +// + +#include "jitpch.h" +#ifdef _MSC_VER +#pragma hdrstop +#endif + +#ifndef LEGACY_BACKEND // This file is ONLY used for the RyuJIT backend that uses the linear scan register allocator +#ifndef _TARGET_64BIT_ // DecomposeLongs is only used on 32-bit platforms + +#include "decomposelongs.h" + +//------------------------------------------------------------------------ + +//------------------------------------------------------------------------ +// PrepareForDecomposition: Do one-time preparation required for LONG decomposition. +// Namely, promote long variables to multi-register structs. +// +// Arguments: +// None +// +// Return Value: +// None. +// +void DecomposeLongs::PrepareForDecomposition() +{ + m_compiler->lvaPromoteLongVars(); +} + + +//------------------------------------------------------------------------ +// DecomposeBlock: Do LONG decomposition to all the statements in the given block. +// This must be done before lowering the block, as decomposition can insert +// additional statements. +// +// Decomposition is done as a post-order tree walk. Lower levels of the tree can +// create new nodes that need to be further decomposed at higher levels. That is, +// the decomposition "bubbles up" the tree. +// +// Arguments: +// block - the block to process +// +// Return Value: +// None. +// +void DecomposeLongs::DecomposeBlock(BasicBlock* block) +{ + assert(block == m_compiler->compCurBB); // compCurBB must already be set. + + for (GenTree* stmt = block->bbTreeList; stmt != nullptr; stmt = stmt->gtNext) + { +#ifdef DEBUG + if (m_compiler->verbose) + { + printf("Decomposing BB%02u, stmt id %u\n", block->bbNum, stmt->gtTreeID); + } +#endif // DEBUG + + DecomposeStmt(stmt->AsStmt()); + } +} + + +//------------------------------------------------------------------------ +// DecomposeStmt: Do LONG decomposition to a statement tree. +// +// Arguments: +// stmt - the statement to process +// +// Return Value: +// None. +// +void DecomposeLongs::DecomposeStmt(GenTreeStmt* stmt) +{ + GenTree* savedStmt = m_compiler->compCurStmt; // We'll need to restore this later, in case this call was recursive. + m_compiler->compCurStmt = stmt; // Publish the current statement globally. One reason: fgInsertEmbeddedFormTemp requires it. + m_compiler->fgWalkTreePost(&stmt->gtStmt.gtStmtExpr, &DecomposeLongs::DecompNodeHelper, this, true); + m_compiler->compCurStmt = savedStmt; +} + + +//------------------------------------------------------------------------ +// DecompNodeHelper: fgWalkTreePost callback helper for LONG decomposition +// +// Arguments: +// ppTree - tree node we are working on. +// data - tree walk context, with data->pCallbackData as a DecomposeLongs* +// +// Return Value: +// Standard tree walk result. +// +// static +Compiler::fgWalkResult DecomposeLongs::DecompNodeHelper(GenTree** ppTree, Compiler::fgWalkData* data) +{ + DecomposeLongs* decomp = (DecomposeLongs*)data->pCallbackData; + decomp->DecomposeNode(ppTree, data); + return Compiler::WALK_CONTINUE; +} + + +//------------------------------------------------------------------------ +// DecomposeNode: Decompose long-type trees into lower and upper halves. +// +// Arguments: +// *ppTree - A node that may or may not require decomposition. +// data - The tree-walk data that provides the context. +// +// Return Value: +// None. It the tree at *ppTree is of TYP_LONG, it will generally be replaced. +// +void DecomposeLongs::DecomposeNode(GenTree** ppTree, Compiler::fgWalkData* data) +{ + GenTree* tree = *ppTree; + + // Handle the case where we are implicitly using the lower half of a long lclVar. + if ((tree->TypeGet() == TYP_INT) && tree->OperIsLocal()) + { + LclVarDsc* varDsc = m_compiler->lvaTable + tree->AsLclVarCommon()->gtLclNum; + if (varTypeIsLong(varDsc) && varDsc->lvPromoted) + { +#ifdef DEBUG + if (m_compiler->verbose) + { + printf("Changing implicit reference to lo half of long lclVar to an explicit reference of its promoted half:\n"); + m_compiler->gtDispTree(tree); + } +#endif // DEBUG + m_compiler->lvaDecRefCnts(tree); + unsigned loVarNum = varDsc->lvFieldLclStart; + tree->AsLclVarCommon()->SetLclNum(loVarNum); + m_compiler->lvaIncRefCnts(tree); + return; + } + } + + if (tree->TypeGet() != TYP_LONG) + { + return; + } + +#ifdef DEBUG + if (m_compiler->verbose) + { + printf("Decomposing TYP_LONG tree. BEFORE:\n"); + m_compiler->gtDispTree(tree); + } +#endif // DEBUG + + switch (tree->OperGet()) + { + case GT_PHI: + case GT_PHI_ARG: + break; + + case GT_LCL_VAR: + DecomposeLclVar(ppTree, data); + break; + + case GT_LCL_FLD: + DecomposeLclFld(ppTree, data); + break; + + case GT_STORE_LCL_VAR: + DecomposeStoreLclVar(ppTree, data); + break; + + case GT_CAST: + DecomposeCast(ppTree, data); + break; + + case GT_CNS_LNG: + DecomposeCnsLng(ppTree, data); + break; + + case GT_CALL: + DecomposeCall(ppTree, data); + break; + + case GT_RETURN: + assert(tree->gtOp.gtOp1->OperGet() == GT_LONG); + break; + + case GT_STOREIND: + DecomposeStoreInd(ppTree, data); + break; + + case GT_STORE_LCL_FLD: + assert(tree->gtOp.gtOp1->OperGet() == GT_LONG); + NYI("st.lclFld of of TYP_LONG"); + break; + + case GT_IND: + NYI("GT_IND of TYP_LONG"); + break; + + case GT_NOT: + DecomposeNot(ppTree, data); + break; + + case GT_NEG: + NYI("GT_NEG of TYP_LONG"); + break; + + // Binary operators. Those that require different computation for upper and lower half are + // handled by the use of GetHiOper(). + case GT_ADD: + case GT_SUB: + case GT_OR: + case GT_XOR: + case GT_AND: + DecomposeArith(ppTree, data); + break; + + case GT_MUL: + NYI("Arithmetic binary operators on TYP_LONG - GT_MUL"); + break; + + case GT_DIV: + NYI("Arithmetic binary operators on TYP_LONG - GT_DIV"); + break; + + case GT_MOD: + NYI("Arithmetic binary operators on TYP_LONG - GT_MOD"); + break; + + case GT_UDIV: + NYI("Arithmetic binary operators on TYP_LONG - GT_UDIV"); + break; + + case GT_UMOD: + NYI("Arithmetic binary operators on TYP_LONG - GT_UMOD"); + break; + + case GT_LSH: + case GT_RSH: + case GT_RSZ: + NYI("Arithmetic binary operators on TYP_LONG - SHIFT"); + break; + + case GT_ROL: + case GT_ROR: + NYI("Arithmetic binary operators on TYP_LONG - ROTATE"); + break; + + case GT_MULHI: + NYI("Arithmetic binary operators on TYP_LONG - MULHI"); + break; + + case GT_LOCKADD: + case GT_XADD: + case GT_XCHG: + case GT_CMPXCHG: + NYI("Interlocked operations on TYP_LONG"); + break; + + default: + { + JITDUMP("Illegal TYP_LONG node %s in Decomposition.", GenTree::NodeName(tree->OperGet())); + noway_assert(!"Illegal TYP_LONG node in Decomposition."); + break; + } + } + +#ifdef DEBUG + if (m_compiler->verbose) + { + printf(" AFTER:\n"); + m_compiler->gtDispTree(*ppTree); + } +#endif +} + + +//------------------------------------------------------------------------ +// FinalizeDecomposition: A helper function to finalize LONG decomposition by +// taking the resulting two halves of the decomposition, and tie them together +// with a new GT_LONG node that will replace the original node. +// +// Arguments: +// ppTree - the original tree node +// data - tree walk context +// loResult - the decomposed low part +// hiResult - the decomposed high part +// +// Return Value: +// None. +// +void DecomposeLongs::FinalizeDecomposition(GenTree** ppTree, Compiler::fgWalkData* data, GenTree* loResult, GenTree* hiResult) +{ + assert(ppTree != nullptr); + assert(*ppTree != nullptr); + assert(data != nullptr); + assert(loResult != nullptr); + assert(hiResult != nullptr); + assert(m_compiler->compCurStmt != nullptr); + + GenTree* tree = *ppTree; + + m_compiler->fgInsertTreeInListAfter(hiResult, loResult, m_compiler->compCurStmt->AsStmt()); + hiResult->CopyCosts(tree); + + GenTree* newTree = new (m_compiler, GT_LONG) GenTreeOp(GT_LONG, TYP_LONG, loResult, hiResult); + SimpleLinkNodeAfter(hiResult, newTree); + m_compiler->fgFixupIfCallArg(data->parentStack, tree, newTree); + newTree->CopyCosts(tree); + *ppTree = newTree; +} + + +//------------------------------------------------------------------------ +// DecomposeLclVar: Decompose GT_LCL_VAR. +// +// Arguments: +// ppTree - the tree to decompose +// data - tree walk context +// +// Return Value: +// None. +// +void DecomposeLongs::DecomposeLclVar(GenTree** ppTree, Compiler::fgWalkData* data) +{ + assert(ppTree != nullptr); + assert(*ppTree != nullptr); + assert(data != nullptr); + assert((*ppTree)->OperGet() == GT_LCL_VAR); + + GenTree* tree = *ppTree; + unsigned varNum = tree->AsLclVarCommon()->gtLclNum; + LclVarDsc* varDsc = m_compiler->lvaTable + varNum; + m_compiler->lvaDecRefCnts(tree); + + GenTree* loResult = tree; + loResult->gtType = TYP_INT; + GenTree* hiResult = m_compiler->gtNewLclLNode(varNum, TYP_INT); + + if (varDsc->lvPromoted) + { + assert(varDsc->lvFieldCnt == 2); + unsigned loVarNum = varDsc->lvFieldLclStart; + unsigned hiVarNum = loVarNum + 1; + loResult->AsLclVarCommon()->SetLclNum(loVarNum); + hiResult->AsLclVarCommon()->SetLclNum(hiVarNum); + } + else + { + noway_assert(varDsc->lvLRACandidate == false); + + loResult->SetOper(GT_LCL_FLD); + loResult->AsLclFld()->gtLclOffs = 0; + loResult->AsLclFld()->gtFieldSeq = FieldSeqStore::NotAField(); + + hiResult->SetOper(GT_LCL_FLD); + hiResult->AsLclFld()->gtLclOffs = 4; + hiResult->AsLclFld()->gtFieldSeq = FieldSeqStore::NotAField(); + } + + m_compiler->lvaIncRefCnts(loResult); + m_compiler->lvaIncRefCnts(hiResult); + + FinalizeDecomposition(ppTree, data, loResult, hiResult); +} + + +//------------------------------------------------------------------------ +// DecomposeLclFld: Decompose GT_LCL_FLD. +// +// Arguments: +// ppTree - the tree to decompose +// data - tree walk context +// +// Return Value: +// None. +// +void DecomposeLongs::DecomposeLclFld(GenTree** ppTree, Compiler::fgWalkData* data) +{ + assert(ppTree != nullptr); + assert(*ppTree != nullptr); + assert(data != nullptr); + assert((*ppTree)->OperGet() == GT_LCL_FLD); + + GenTree* tree = *ppTree; + GenTreeLclFld* loResult = tree->AsLclFld(); + loResult->gtType = TYP_INT; + + GenTree* hiResult = m_compiler->gtNewLclFldNode(loResult->gtLclNum, + TYP_INT, + loResult->gtLclOffs + 4); + + FinalizeDecomposition(ppTree, data, loResult, hiResult); +} + + +//------------------------------------------------------------------------ +// DecomposeStoreLclVar: Decompose GT_STORE_LCL_VAR. +// +// Arguments: +// ppTree - the tree to decompose +// data - tree walk context +// +// Return Value: +// None. +// +void DecomposeLongs::DecomposeStoreLclVar(GenTree** ppTree, Compiler::fgWalkData* data) +{ + assert(ppTree != nullptr); + assert(*ppTree != nullptr); + assert(data != nullptr); + assert((*ppTree)->OperGet() == GT_STORE_LCL_VAR); + assert(m_compiler->compCurStmt != nullptr); + + GenTreeStmt* curStmt = m_compiler->compCurStmt->AsStmt(); + + GenTree* tree = *ppTree; + GenTree* nextTree = tree->gtNext; + GenTree* rhs = tree->gtGetOp1(); + if ((rhs->OperGet() == GT_PHI) || (rhs->OperGet() == GT_CALL)) + { + // GT_CALLs are not decomposed, so will not be converted to GT_LONG + // GT_STORE_LCL_VAR = GT_CALL are handled in genMultiRegCallStoreToLocal + return; + } + + 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) + { + 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); + } + 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(); + } + + tree->gtOp.gtOp1 = loRhs; + tree->gtType = TYP_INT; + + loRhs->gtNext = tree; + tree->gtPrev = loRhs; + + hiStore->gtOp.gtOp1 = hiRhs; + hiStore->CopyCosts(tree); + hiStore->gtFlags |= GTF_VAR_DEF; + + m_compiler->lvaIncRefCnts(tree); + m_compiler->lvaIncRefCnts(hiStore); + + tree->gtNext = hiRhs; + hiRhs->gtPrev = tree; + hiRhs->gtNext = hiStore; + hiStore->gtPrev = hiRhs; + hiStore->gtNext = nextTree; + if (nextTree != nullptr) + { + nextTree->gtPrev = hiStore; + } + nextTree = hiRhs; + + bool isEmbeddedStmt = !curStmt->gtStmtIsTopLevel(); + if (!isEmbeddedStmt) + { + tree->gtNext = nullptr; + hiRhs->gtPrev = nullptr; + } + + InsertNodeAsStmt(hiStore); +} + + +//------------------------------------------------------------------------ +// DecomposeCast: Decompose GT_CAST. +// +// Arguments: +// ppTree - the tree to decompose +// data - tree walk context +// +// Return Value: +// None. +// +void DecomposeLongs::DecomposeCast(GenTree** ppTree, Compiler::fgWalkData* data) +{ + assert(ppTree != nullptr); + assert(*ppTree != nullptr); + assert(data != nullptr); + assert((*ppTree)->OperGet() == GT_CAST); + assert(m_compiler->compCurStmt != nullptr); + + GenTree* tree = *ppTree; + GenTree* loResult = nullptr; + GenTree* hiResult = nullptr; + GenTreeStmt* curStmt = m_compiler->compCurStmt->AsStmt(); + + assert(tree->gtPrev == tree->gtGetOp1()); + NYI_IF(tree->gtOverflow(), "TYP_LONG cast with overflow"); + switch (tree->AsCast()->CastFromType()) + { + case TYP_INT: + if (tree->gtFlags & GTF_UNSIGNED) + { + loResult = tree->gtGetOp1(); + hiResult = new (m_compiler, GT_CNS_INT) GenTreeIntCon(TYP_INT, 0); + m_compiler->fgSnipNode(curStmt, tree); + } + else + { + NYI("Lowering of signed cast TYP_INT->TYP_LONG"); + } + break; + + default: + NYI("Unimplemented type for Lowering of cast to TYP_LONG"); + break; + } + + FinalizeDecomposition(ppTree, data, loResult, hiResult); +} + + +//------------------------------------------------------------------------ +// DecomposeCnsLng: Decompose GT_CNS_LNG. +// +// Arguments: +// ppTree - the tree to decompose +// data - tree walk context +// +// Return Value: +// None. +// +void DecomposeLongs::DecomposeCnsLng(GenTree** ppTree, Compiler::fgWalkData* data) +{ + assert(ppTree != nullptr); + assert(*ppTree != nullptr); + assert(data != nullptr); + assert((*ppTree)->OperGet() == GT_CNS_LNG); + + GenTree* tree = *ppTree; + INT32 hiVal = tree->AsLngCon()->HiVal(); + + GenTree* loResult = tree; + loResult->ChangeOperConst(GT_CNS_INT); + loResult->gtType = TYP_INT; + + GenTree* hiResult = new (m_compiler, GT_CNS_INT) GenTreeIntCon(TYP_INT, hiVal); + + FinalizeDecomposition(ppTree, data, loResult, hiResult); +} + + +//------------------------------------------------------------------------ +// DecomposeCall: Decompose GT_CALL. +// +// Arguments: +// ppTree - the tree to decompose +// data - tree walk context +// +// Return Value: +// None. +// +void DecomposeLongs::DecomposeCall(GenTree** ppTree, Compiler::fgWalkData* data) +{ + assert(ppTree != nullptr); + assert(*ppTree != nullptr); + assert(data != nullptr); + assert((*ppTree)->OperGet() == GT_CALL); + + GenTree* parent = data->parent; + + // We only need to force var = call() if the call is not a top-level node. + if (parent == nullptr) + return; + + if (parent->gtOper == GT_STORE_LCL_VAR) + { + // If parent is already a STORE_LCL_VAR, we can skip it if + // it is already marked as lvIsMultiRegRet. + unsigned varNum = parent->AsLclVarCommon()->gtLclNum; + if (m_compiler->lvaTable[varNum].lvIsMultiRegRet) + { + return; + } + else if (!m_compiler->lvaTable[varNum].lvPromoted) + { + // If var wasn't promoted, we can just set lvIsMultiRegRet. + m_compiler->lvaTable[varNum].lvIsMultiRegRet = true; + return; + } + } + + // Otherwise, we need to force var = call() + GenTree* tree = *ppTree; + GenTree** treePtr = nullptr; + parent = tree->gtGetParent(&treePtr); + + assert(treePtr != nullptr); + + GenTreeStmt* asgStmt = m_compiler->fgInsertEmbeddedFormTemp(treePtr); + GenTree* stLclVar = asgStmt->gtStmtExpr; + assert(stLclVar->OperIsLocalStore()); + + unsigned varNum = stLclVar->AsLclVarCommon()->gtLclNum; + m_compiler->lvaTable[varNum].lvIsMultiRegRet = true; + m_compiler->fgFixupIfCallArg(data->parentStack, tree, *treePtr); + + // Decompose new node + DecomposeNode(treePtr, data); +} + + +//------------------------------------------------------------------------ +// DecomposeStoreInd: Decompose GT_STOREIND. +// +// Arguments: +// tree - the tree to decompose +// +// Return Value: +// None. +// +void DecomposeLongs::DecomposeStoreInd(GenTree** ppTree, Compiler::fgWalkData* data) +{ + assert(ppTree != nullptr); + assert(*ppTree != nullptr); + assert(data != nullptr); + assert((*ppTree)->OperGet() == GT_STOREIND); + assert(m_compiler->compCurStmt != nullptr); + + GenTree* tree = *ppTree; + + assert(tree->gtOp.gtOp2->OperGet() == GT_LONG); + + GenTreeStmt* curStmt = m_compiler->compCurStmt->AsStmt(); + bool isEmbeddedStmt = !curStmt->gtStmtIsTopLevel(); + + // Example input trees (a nested embedded statement case) + // + // + // * stmtExpr void (top level) (IL ???... ???) + // | /--* argPlace ref $280 + // | +--* argPlace int $4a + // | | { * stmtExpr void (embedded) (IL ???... ???) + // | | { | /--* lclVar ref V11 tmp9 u:3 $21c + // | | { | +--* const int 4 $44 + // | | { | /--* + byref $2c8 + // | | { | | { * stmtExpr void (embedded) (IL ???... ???) + // | | { | | { | /--* lclFld long V01 arg1 u:2[+8] Fseq[i] $380 + // | | { | | { \--* st.lclVar long (P) V21 cse8 + // | | { | | { \--* int V21.hi (offs=0x00) -> V22 rat0 + // | | { | | { \--* int V21.hi (offs=0x04) -> V23 rat1 + // | | { | | /--* lclVar int V22 rat0 $380 + // | | { | | +--* lclVar int V23 rat1 + // | | { | +--* gt_long long + // | | { \--* storeIndir long + // | +--* lclVar ref V11 tmp9 u:3 (last use) $21c + // | +--* lclVar ref V02 tmp0 u:3 $280 + // | +--* const int 8 $4a + // \--* call help void HELPER.CORINFO_HELP_ARRADDR_ST $205 + // + // + // (editor brace matching compensation: }}}}}}}}}}}}}}}}}}) + + GenTree* linkBegin = m_compiler->fgGetFirstNode(tree)->gtPrev; + GenTree* linkEnd = tree->gtNext; + GenTree* gtLong = tree->gtOp.gtOp2; + + // Save address to a temp. It is used in storeIndLow and storeIndHigh trees. + GenTreeStmt* addrStmt = CreateTemporary(&tree->gtOp.gtOp1); + JITDUMP("[DecomposeStoreInd]: Saving address tree to a temp var:\n"); + DISPTREE(addrStmt); + + if (!gtLong->gtOp.gtOp1->OperIsLeaf()) + { + GenTreeStmt* dataLowStmt = CreateTemporary(>Long->gtOp.gtOp1); + JITDUMP("[DecomposeStoreInd]: Saving low data tree to a temp var:\n"); + DISPTREE(dataLowStmt); + } + + if (!gtLong->gtOp.gtOp2->OperIsLeaf()) + { + GenTreeStmt* dataHighStmt = CreateTemporary(>Long->gtOp.gtOp2); + JITDUMP("[DecomposeStoreInd]: Saving high data tree to a temp var:\n"); + DISPTREE(dataHighStmt); + } + + // Example trees after embedded statements for address and data are added. + // This example saves all address and data trees into temp variables + // to show how those embedded statements are created. + // + // * stmtExpr void (top level) (IL ???... ???) + // | /--* argPlace ref $280 + // | +--* argPlace int $4a + // | | { * stmtExpr void (embedded) (IL ???... ???) + // | | { | /--* lclVar ref V11 tmp9 u:3 $21c + // | | { | +--* const int 4 $44 + // | | { | /--* + byref $2c8 + // | | { \--* st.lclVar byref V24 rat2 + // | | { * stmtExpr void (embedded) (IL ???... ???) + // | | { | /--* lclVar byref V24 rat2 + // | | { | | { * stmtExpr void (embedded) (IL ???... ???) + // | | { | | { | /--* lclFld long V01 arg1 u:2[+8] Fseq[i] $380380 + // | | { | | { \--* st.lclVar long (P) V21 cse8 + // | | { | | { \--* int V21.hi (offs=0x00) -> V22 rat0 + // | | { | | { \--* int V21.hi (offs=0x04) -> V23 rat1 + // | | { | | { * stmtExpr void (embedded) (IL ???... ???) + // | | { | | { | /--* lclVar int V22 rat0 $380 + // | | { | | { \--* st.lclVar int V25 rat3 + // | | { | | /--* lclVar int V25 rat3 + // | | { | | | { * stmtExpr void (embedded) (IL ???... ???) + // | | { | | | { | /--* lclVar int V23 rat1 + // | | { | | | { \--* st.lclVar int V26 rat4 + // | | { | | +--* lclVar int V26 rat4 + // | | { | +--* gt_long long + // | | { \--* storeIndir long + // | +--* lclVar ref V11 tmp9 u:3 (last use) $21c + // | +--* lclVar ref V02 tmp0 u:3 $280 + // | +--* const int 8 $4a + // \--* call help void HELPER.CORINFO_HELP_ARRADDR_ST $205 + // + // (editor brace matching compensation: }}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}) + + GenTree* addrBase = tree->gtOp.gtOp1; + GenTree* dataHigh = gtLong->gtOp.gtOp2; + GenTree* dataLow = gtLong->gtOp.gtOp1; + GenTree* storeIndLow = tree; + + // Rewrite storeIndLow tree to save only lower 32-bit data. + // + // | | { | /--* lclVar byref V24 rat2 (address) + // ... + // | | { | +--* lclVar int V25 rat3 (lower 32-bit data) + // | | { | { * stmtExpr void (embedded) (IL ???... ???) + // | | { | { | /--* lclVar int V23 rat1 + // | | { | { \--* st.lclVar int V26 rat4 + // | | { \--* storeIndir int + // + // (editor brace matching compensation: }}}}}}}}}) + + m_compiler->fgSnipNode(curStmt, gtLong); + m_compiler->fgSnipNode(curStmt, dataHigh); + storeIndLow->gtOp.gtOp2 = dataLow; + storeIndLow->gtType = TYP_INT; + + // Construct storeIndHigh tree + // + // | | { *stmtExpr void (embedded)(IL ? ? ? ... ? ? ? ) + // | | { | / --* lclVar int V26 rat4 + // | | { | | / --* lclVar byref V24 rat2 + // | | { | +--* lea(b + 4) ref + // | | { \--* storeIndir int + // + // (editor brace matching compensation: }}}}}) + + GenTree* addrBaseHigh = new(m_compiler, GT_LCL_VAR) GenTreeLclVar(GT_LCL_VAR, + addrBase->TypeGet(), addrBase->AsLclVarCommon()->GetLclNum(), BAD_IL_OFFSET); + GenTree* addrHigh = new(m_compiler, GT_LEA) GenTreeAddrMode(TYP_REF, addrBaseHigh, nullptr, 0, genTypeSize(TYP_INT)); + GenTree* storeIndHigh = new(m_compiler, GT_STOREIND) GenTreeStoreInd(TYP_INT, addrHigh, dataHigh); + storeIndHigh->gtFlags = (storeIndLow->gtFlags & (GTF_ALL_EFFECT | GTF_LIVENESS_MASK)); + storeIndHigh->gtFlags |= GTF_REVERSE_OPS; + storeIndHigh->CopyCosts(storeIndLow); + + // Internal links of storeIndHigh tree + dataHigh->gtPrev = nullptr; + dataHigh->gtNext = nullptr; + SimpleLinkNodeAfter(dataHigh, addrBaseHigh); + SimpleLinkNodeAfter(addrBaseHigh, addrHigh); + SimpleLinkNodeAfter(addrHigh, storeIndHigh); + + // External links of storeIndHigh tree + //dataHigh->gtPrev = nullptr; + if (isEmbeddedStmt) + { + // If storeIndTree is an embedded statement, connect storeIndLow + // and dataHigh + storeIndLow->gtNext = dataHigh; + dataHigh->gtPrev = storeIndLow; + } + storeIndHigh->gtNext = linkEnd; + if (linkEnd != nullptr) + { + linkEnd->gtPrev = storeIndHigh; + } + + InsertNodeAsStmt(storeIndHigh); + + // Example final output + // + // * stmtExpr void (top level) (IL ???... ???) + // | /--* argPlace ref $280 + // | +--* argPlace int $4a + // | | { * stmtExpr void (embedded) (IL ???... ???) + // | | { | /--* lclVar ref V11 tmp9 u:3 $21c + // | | { | +--* const int 4 $44 + // | | { | /--* + byref $2c8 + // | | { \--* st.lclVar byref V24 rat2 + // | | { * stmtExpr void (embedded) (IL ???... ???) + // | | { | /--* lclVar byref V24 rat2 + // | | { | | { * stmtExpr void (embedded) (IL ???... ???) + // | | { | | { | /--* lclFld int V01 arg1 u:2[+8] Fseq[i] $380 + // | | { | | { | +--* lclFld int V01 arg1 [+12] + // | | { | | { | /--* gt_long long + // | | { | | { \--* st.lclVar long (P) V21 cse8 + // | | { | | { \--* int V21.hi (offs=0x00) -> V22 rat0 + // | | { | | { \--* int V21.hi (offs=0x04) -> V23 rat1 + // | | { | | { * stmtExpr void (embedded) (IL ???... ???) + // | | { | | { | /--* lclVar int V22 rat0 $380 + // | | { | | { \--* st.lclVar int V25 rat3 + // | | { | +--* lclVar int V25 rat3 + // | | { | { * stmtExpr void (embedded) (IL ???... ???) + // | | { | { | /--* lclVar int V23 rat1 + // | | { | { \--* st.lclVar int V26 rat4 + // | | { \--* storeIndir int + // | | { * stmtExpr void (embedded) (IL ???... ???) + // | | { | /--* lclVar int V26 rat4 + // | | { | | /--* lclVar byref V24 rat2 + // | | { | +--* lea(b+4) ref + // | | { \--* storeIndir int + // | | /--* lclVar ref V11 tmp9 u:3 (last use) $21c + // | +--* putarg_stk [+0x00] ref + // | | /--* lclVar ref V02 tmp0 u:3 $280 + // | +--* putarg_reg ref + // | | /--* const int 8 $4a + // | +--* putarg_reg int + // \--* call help void HELPER.CORINFO_HELP_ARRADDR_ST $205 + // + // (editor brace matching compensation: }}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}) +} + + +//------------------------------------------------------------------------ +// DecomposeNot: Decompose GT_NOT. +// +// Arguments: +// ppTree - the tree to decompose +// data - tree walk context +// +// Return Value: +// None. +// +void DecomposeLongs::DecomposeNot(GenTree** ppTree, Compiler::fgWalkData* data) +{ + assert(ppTree != nullptr); + assert(*ppTree != nullptr); + assert(data != nullptr); + assert((*ppTree)->OperGet() == GT_NOT); + assert(m_compiler->compCurStmt != nullptr); + + GenTreeStmt* curStmt = m_compiler->compCurStmt->AsStmt(); + + GenTree* tree = *ppTree; + GenTree* op1 = tree->gtGetOp1(); + noway_assert(op1->OperGet() == GT_LONG); + GenTree* loOp1 = op1->gtGetOp1(); + GenTree* hiOp1 = op1->gtGetOp2(); + m_compiler->fgSnipNode(curStmt, op1); + + GenTree* loResult = tree; + loResult->gtType = TYP_INT; + loResult->gtOp.gtOp1 = loOp1; + loOp1->gtNext = loResult; + loResult->gtPrev = loOp1; + + GenTree* hiResult = new (m_compiler, GT_NOT) GenTreeOp(GT_NOT, TYP_INT, hiOp1, nullptr); + hiOp1->gtNext = hiResult; + hiResult->gtPrev = hiOp1; + + FinalizeDecomposition(ppTree, data, loResult, hiResult); +} + + +//------------------------------------------------------------------------ +// DecomposeArith: Decompose GT_ADD, GT_SUB, GT_OR, GT_XOR, GT_AND. +// +// Arguments: +// ppTree - the tree to decompose +// data - tree walk context +// +// Return Value: +// None. +// +void DecomposeLongs::DecomposeArith(GenTree** ppTree, Compiler::fgWalkData* data) +{ + assert(ppTree != nullptr); + assert(*ppTree != nullptr); + assert(data != nullptr); + assert(m_compiler->compCurStmt != nullptr); + + GenTreeStmt* curStmt = m_compiler->compCurStmt->AsStmt(); + GenTree* tree = *ppTree; + genTreeOps oper = tree->OperGet(); + + assert((oper == GT_ADD) || + (oper == GT_SUB) || + (oper == GT_OR) || + (oper == GT_XOR) || + (oper == GT_AND)); + + NYI_IF((tree->gtFlags & GTF_REVERSE_OPS) != 0, "Binary operator with GTF_REVERSE_OPS"); + + GenTree* op1 = tree->gtGetOp1(); + GenTree* op2 = tree->gtGetOp2(); + + // Both operands must have already been decomposed into GT_LONG operators. + noway_assert((op1->OperGet() == GT_LONG) && (op2->OperGet() == GT_LONG)); + + // Capture the lo and hi halves of op1 and op2. + GenTree* loOp1 = op1->gtGetOp1(); + GenTree* hiOp1 = op1->gtGetOp2(); + GenTree* loOp2 = op2->gtGetOp1(); + GenTree* hiOp2 = op2->gtGetOp2(); + + // We don't have support to decompose a TYP_LONG node that already has a child that has + // been decomposed into parts, where the high part depends on the value generated by the + // low part (via the flags register). For example, if we have: + // +(gt_long(+(lo3, lo4), +Hi(hi3, hi4)), gt_long(lo2, hi2)) + // We would decompose it here to: + // gt_long(+(+(lo3, lo4), lo2), +Hi(+Hi(hi3, hi4), hi2)) + // But this would generate incorrect code, because the "+Hi(hi3, hi4)" code generation + // needs to immediately follow the "+(lo3, lo4)" part. Also, if this node is one that + // requires a unique high operator, and the child nodes are not simple locals (e.g., + // they are decomposed nodes), then we also can't decompose the node, as we aren't + // guaranteed the high and low parts will be executed immediately after each other. + + NYI_IF(hiOp1->OperIsHigh() || + hiOp2->OperIsHigh() || + (GenTree::OperIsHigh(GetHiOper(oper)) && + (!loOp1->OperIsLeaf() || + !hiOp1->OperIsLeaf() || + !loOp1->OperIsLeaf() || + !hiOp2->OperIsLeaf())), + "Can't decompose expression tree TYP_LONG node"); + + // Now, remove op1 and op2 from the node list. + m_compiler->fgSnipNode(curStmt, op1); + m_compiler->fgSnipNode(curStmt, op2); + + // We will reuse "tree" for the loResult, which will now be of TYP_INT, and its operands + // will be the lo halves of op1 from above. + GenTree* loResult = tree; + loResult->SetOper(GetLoOper(loResult->OperGet())); + loResult->gtType = TYP_INT; + loResult->gtOp.gtOp1 = loOp1; + loResult->gtOp.gtOp2 = loOp2; + + // The various halves will be correctly threaded internally. We simply need to + // relink them into the proper order, i.e. loOp1 is followed by loOp2, and then + // the loResult node. + // (This rethreading, and that below, are where we need to address the reverse ops case). + // The current order is (after snipping op1 and op2): + // ... loOp1-> ... hiOp1->loOp2First ... loOp2->hiOp2First ... hiOp2 + // The order we want is: + // ... loOp1->loOp2First ... loOp2->loResult + // ... hiOp1->hiOp2First ... hiOp2->hiResult + // i.e. we swap hiOp1 and loOp2, and create (for now) separate loResult and hiResult trees + GenTree* loOp2First = hiOp1->gtNext; + GenTree* hiOp2First = loOp2->gtNext; + + // First, we will NYI if both hiOp1 and loOp2 have side effects. + NYI_IF(((loOp2->gtFlags & GTF_ALL_EFFECT) != 0) && ((hiOp1->gtFlags & GTF_ALL_EFFECT) != 0), + "Binary long operator with non-reorderable sub expressions"); + + // Now, we reorder the loOps and the loResult. + loOp1->gtNext = loOp2First; + loOp2First->gtPrev = loOp1; + loOp2->gtNext = loResult; + loResult->gtPrev = loOp2; + + // Next, reorder the hiOps and the hiResult. + GenTree* hiResult = new (m_compiler, oper) GenTreeOp(GetHiOper(oper), TYP_INT, hiOp1, hiOp2); + hiOp1->gtNext = hiOp2First; + hiOp2First->gtPrev = hiOp1; + hiOp2->gtNext = hiResult; + hiResult->gtPrev = hiOp2; + + if ((oper == GT_ADD) || (oper == GT_SUB)) + { + if (loResult->gtOverflow()) + { + hiResult->gtFlags |= GTF_OVERFLOW; + loResult->gtFlags &= ~GTF_OVERFLOW; + } + if (loResult->gtFlags & GTF_UNSIGNED) + { + hiResult->gtFlags |= GTF_UNSIGNED; + } + } + + FinalizeDecomposition(ppTree, data, loResult, hiResult); +} + + +//------------------------------------------------------------------------ +// CreateTemporary: call fgInsertEmbeddedFormTemp to replace *ppTree with +// a new temp that is assigned to the value previously at *ppTree by inserting +// an embedded statement. In addition, if the resulting statement actually ends +// up being top-level, it might pull along some embedded statements that have +// not yet been decomposed. So recursively decompose those before returning. +// +// Arguments: +// *ppTree - tree to replace with a temp. +// +// Return Value: +// The new statement that was created to create the temp. +// +GenTreeStmt* DecomposeLongs::CreateTemporary(GenTree** ppTree) +{ + GenTreeStmt* newStmt = m_compiler->fgInsertEmbeddedFormTemp(ppTree); + if (newStmt->gtStmtIsTopLevel()) + { + for (GenTreeStmt* nextEmbeddedStmt = newStmt->gtStmtNextIfEmbedded(); + nextEmbeddedStmt != nullptr; + nextEmbeddedStmt = nextEmbeddedStmt->gtStmt.gtStmtNextIfEmbedded()) + { + DecomposeStmt(nextEmbeddedStmt); + } + } + + return newStmt; +} + + +//------------------------------------------------------------------------ +// InsertNodeAsStmt: Insert a node as the root node of a new statement. +// If the current statement is embedded, the new statement will also be +// embedded. Otherwise, the new statement will be top level. +// +// Arguments: +// node - node to insert +// +// Return Value: +// None +// +// Notes: +// compCurStmt and compCurBB must be correctly set. +// +void DecomposeLongs::InsertNodeAsStmt(GenTree* node) +{ + assert(node != nullptr); + assert(m_compiler->compCurBB != nullptr); + assert(m_compiler->compCurStmt != nullptr); + + GenTreeStmt* curStmt = m_compiler->compCurStmt->AsStmt(); + GenTreeStmt* newStmt; + + if (curStmt->gtStmtIsTopLevel()) + { + newStmt = m_compiler->fgNewStmtFromTree(node); + + // Find an insert point. Skip all embedded statements. + GenTree* insertPt = curStmt; + while ((insertPt->gtNext != nullptr) && (!insertPt->gtNext->AsStmt()->gtStmtIsTopLevel())) + { + insertPt = insertPt->gtNext; + } + + m_compiler->fgInsertStmtAfter(m_compiler->compCurBB, insertPt, newStmt); + } + else + { + // The current statement is an embedded statement. Create a new embedded statement to + // contain the node. First, find the parent non-embedded statement containing the + // current statement. + GenTree* parentStmt = curStmt; + while ((parentStmt != nullptr) && (!parentStmt->AsStmt()->gtStmtIsTopLevel())) + { + parentStmt = parentStmt->gtPrev; + } + assert(parentStmt != nullptr); + + newStmt = m_compiler->fgMakeEmbeddedStmt(m_compiler->compCurBB, node, parentStmt); + } + + newStmt->gtStmtILoffsx = curStmt->gtStmtILoffsx; +#ifdef DEBUG + newStmt->gtStmtLastILoffs = curStmt->gtStmtLastILoffs; +#endif // DEBUG +} + + +//------------------------------------------------------------------------ +// GetHiOper: Convert arithmetic operator to "high half" operator of decomposed node. +// +// Arguments: +// oper - operator to map +// +// Return Value: +// mapped operator +// +// static +genTreeOps DecomposeLongs::GetHiOper(genTreeOps oper) +{ + switch (oper) + { + case GT_ADD: return GT_ADD_HI; break; + case GT_SUB: return GT_SUB_HI; break; + case GT_MUL: return GT_MUL_HI; break; + case GT_DIV: return GT_DIV_HI; break; + case GT_MOD: return GT_MOD_HI; break; + case GT_OR: return GT_OR; break; + case GT_AND: return GT_AND; break; + case GT_XOR: return GT_XOR; break; + default: + assert(!"GetHiOper called for invalid oper"); + return GT_NONE; + } +} + + +//------------------------------------------------------------------------ +// GetLoOper: Convert arithmetic operator to "low half" operator of decomposed node. +// +// Arguments: +// oper - operator to map +// +// Return Value: +// mapped operator +// +// static +genTreeOps DecomposeLongs::GetLoOper(genTreeOps oper) +{ + switch (oper) + { + case GT_ADD: return GT_ADD_LO; break; + case GT_SUB: return GT_SUB_LO; break; + case GT_OR: return GT_OR; break; + case GT_AND: return GT_AND; break; + case GT_XOR: return GT_XOR; break; + default: + assert(!"GetLoOper called for invalid oper"); + return GT_NONE; + } +} + + +//------------------------------------------------------------------------ +// SimpleLinkNodeAfter: insert a node after a given node in the execution order. +// NOTE: Does not support inserting after the last node of a statement, which +// would require updating the statement links. +// +// Arguments: +// insertionPoint - Insert after this tree node. +// node - The node to insert. +// +// Return Value: +// None +// +// Notes: +// Seems like this should be moved to someplace that houses all the flowgraph +// manipulation functions. +// +void DecomposeLongs::SimpleLinkNodeAfter(GenTree* insertionPoint, GenTree* node) +{ + assert(insertionPoint != nullptr); + assert(node != nullptr); + + GenTree* nextTree = insertionPoint->gtNext; + node->gtPrev = insertionPoint; + node->gtNext = nextTree; + insertionPoint->gtNext = node; + if (nextTree != nullptr) + { + nextTree->gtPrev = node; + } +} + + +#endif // !_TARGET_64BIT_ +#endif // !LEGACY_BACKEND \ No newline at end of file diff --git a/src/jit/decomposelongs.h b/src/jit/decomposelongs.h new file mode 100644 index 000000000000..376f655d53e6 --- /dev/null +++ b/src/jit/decomposelongs.h @@ -0,0 +1,61 @@ +// 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. + +/*XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +XX XX +XX DecomposeLongs XX +XX XX +XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +*/ + +#ifndef _DECOMPOSELONGS_H_ +#define _DECOMPOSELONGS_H_ + +#include "compiler.h" + +class DecomposeLongs +{ +public: + + DecomposeLongs(Compiler* compiler) + : m_compiler(compiler) + { + } + + void PrepareForDecomposition(); + void DecomposeBlock(BasicBlock* block); + +private: + + // Driver functions + static Compiler::fgWalkResult DecompNodeHelper(GenTree** ppTree, Compiler::fgWalkData* data); + void DecomposeStmt(GenTreeStmt* stmt); + void DecomposeNode(GenTree** ppTree, Compiler::fgWalkData* data); + + // Per-node type decompose cases + void DecomposeLclVar(GenTree** ppTree, Compiler::fgWalkData* data); + void DecomposeLclFld(GenTree** ppTree, Compiler::fgWalkData* data); + void DecomposeStoreLclVar(GenTree** ppTree, Compiler::fgWalkData* data); + void DecomposeCast(GenTree** ppTree, Compiler::fgWalkData* data); + void DecomposeCnsLng(GenTree** ppTree, Compiler::fgWalkData* data); + void DecomposeCall(GenTree** ppTree, Compiler::fgWalkData* data); + void DecomposeStoreInd(GenTree** ppTree, Compiler::fgWalkData* data); + void DecomposeNot(GenTree** ppTree, Compiler::fgWalkData* data); + void DecomposeArith(GenTree** ppTree, Compiler::fgWalkData* data); + + // Helper functions + void FinalizeDecomposition(GenTree** ppTree, Compiler::fgWalkData* data, GenTree* loResult, GenTree* hiResult); + void InsertNodeAsStmt(GenTree* node); + GenTreeStmt* CreateTemporary(GenTree** ppTree); + static genTreeOps GetHiOper(genTreeOps oper); + static genTreeOps GetLoOper(genTreeOps oper); + void SimpleLinkNodeAfter(GenTree* insertionPoint, GenTree* node); + + // Data + Compiler* m_compiler; +}; + +#endif // _DECOMPOSELONGS_H_ diff --git a/src/jit/jit.settings.targets b/src/jit/jit.settings.targets index 26b5af903de0..2ffcfcb69cdd 100644 --- a/src/jit/jit.settings.targets +++ b/src/jit/jit.settings.targets @@ -92,6 +92,7 @@ + @@ -111,6 +112,7 @@ + diff --git a/src/jit/lower.cpp b/src/jit/lower.cpp index 140b5cbf976a..7a133f78c7d2 100755 --- a/src/jit/lower.cpp +++ b/src/jit/lower.cpp @@ -25,6 +25,10 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX #include "lower.h" +#if !defined(_TARGET_64BIT_) +#include "decomposelongs.h" +#endif // !defined(_TARGET_64BIT_) + //------------------------------------------------------------------------ // MakeSrcContained: Make "tree" a contained node // @@ -119,13 +123,7 @@ bool Lowering::IsSafeToContainMem(GenTree* parentNode, GenTree* childNode) //------------------------------------------------------------------------ -Compiler::fgWalkResult Lowering::DecompNodeHelper(GenTreePtr* pTree, Compiler::fgWalkData* data) -{ - Lowering* lower = (Lowering*)data->pCallbackData; - lower->DecomposeNode(pTree, data); - return Compiler::WALK_CONTINUE; -} - +//static Compiler::fgWalkResult Lowering::LowerNodeHelper(GenTreePtr* pTree, Compiler::fgWalkData* data) { Lowering* lower = (Lowering*)data->pCallbackData; @@ -133,790 +131,6 @@ Compiler::fgWalkResult Lowering::LowerNodeHelper(GenTreePtr* pTree, Compiler::fg return Compiler::WALK_CONTINUE; } -#if !defined(_TARGET_64BIT_) -genTreeOps getHiOper(genTreeOps oper) -{ - switch(oper) - { - case GT_ADD: return GT_ADD_HI; break; - case GT_SUB: return GT_SUB_HI; break; - case GT_MUL: return GT_MUL_HI; break; - case GT_DIV: return GT_DIV_HI; break; - case GT_MOD: return GT_MOD_HI; break; - case GT_OR: return GT_OR; break; - case GT_AND: return GT_AND; break; - case GT_XOR: return GT_XOR; break; - default: - assert(!"getHiOper called for invalid oper"); - return GT_NONE; - } -} - -genTreeOps getLoOper(genTreeOps oper) -{ - switch(oper) - { - case GT_ADD: return GT_ADD_LO; break; - case GT_SUB: return GT_SUB_LO; break; - case GT_OR: return GT_OR; break; - case GT_AND: return GT_AND; break; - case GT_XOR: return GT_XOR; break; - default: - assert(!"getLoOper called for invalid oper"); - return GT_NONE; - } -} -#endif // !defined(_TARGET_64BIT_) - -//------------------------------------------------------------------------ -// DecomposeNode: Decompose long-type trees into lower & upper halves. -// -// Arguments: -// *pTree - A node that may or may not require decomposition. -// data - The tree-walk data that provides the context. -// -// Return Value: -// None. -// -// Notes: The rationale behind this is to avoid adding code complexity -// downstream caused by the introduction of handling longs as special cases, especially in -// LSRA. -// This function is called just prior to the more general purpose lowering. -// -void Lowering::DecomposeNode(GenTreePtr* pTree, Compiler::fgWalkData* data) -{ -#if !defined(_TARGET_64BIT_) - GenTree* tree = *(pTree); - - // Handle the case where we are implicitly using the lower half of a long lclVar. - if ((tree->TypeGet() == TYP_INT) && tree->OperIsLocal()) - { - LclVarDsc * varDsc = comp->lvaTable + tree->AsLclVarCommon()->gtLclNum; - if (varTypeIsLong(varDsc) && varDsc->lvPromoted) - { -#ifdef DEBUG - if (comp->verbose) - { - printf("Changing implicit reference to lo half of long lclVar to an explicit reference of its promoted half:\n"); - comp->gtDispTree(tree); - } -#endif - comp->lvaDecRefCnts(tree); - unsigned loVarNum = varDsc->lvFieldLclStart; - tree->AsLclVarCommon()->SetLclNum(loVarNum); - comp->lvaIncRefCnts(tree); - return; - } - } - if (tree->TypeGet() != TYP_LONG) - { - return; - } -#ifdef DEBUG - if (comp->verbose) - { - printf("Decomposing TYP_LONG tree. BEFORE:\n"); - comp->gtDispTree(tree); - } -#endif - // The most common pattern is that we will create a loResult and a hiResult, where - // the loResult reuses some or all of the existing tree, and the hiResult is new. - // For these, we will set loResult and hiResult in the switch case below, ensuring - // that loResult is correctly linked into the statement, and hiResult is not, but - // is internally sequenced appropriately (if it is not a single node). - // Common code after the switch will link in hiResult and create a GT_LONG node. - - GenTree* newTree = nullptr; - GenTree* loResult = nullptr; - GenTree* hiResult = nullptr; - GenTreeStmt* curStmt = comp->compCurStmt->AsStmt(); - genTreeOps oper = tree->OperGet(); - switch(oper) - { - case GT_PHI: - case GT_PHI_ARG: - break; - case GT_LCL_VAR: - { - loResult = tree; - unsigned varNum = tree->AsLclVarCommon()->gtLclNum; - LclVarDsc * varDsc = comp->lvaTable + varNum; - comp->lvaDecRefCnts(tree); - hiResult = comp->gtNewLclLNode(varNum, TYP_INT); - - if (varDsc->lvPromoted) - { - assert(varDsc->lvFieldCnt == 2); - unsigned loVarNum = varDsc->lvFieldLclStart; - unsigned hiVarNum = loVarNum + 1; - tree->AsLclVarCommon()->SetLclNum(loVarNum); - hiResult->AsLclVarCommon()->SetLclNum(hiVarNum); - } - else - { - noway_assert(varDsc->lvLRACandidate == false); - tree->SetOper(GT_LCL_FLD); - tree->AsLclFld()->gtLclOffs = 0; - tree->AsLclFld()->gtFieldSeq = FieldSeqStore::NotAField(); - hiResult->SetOper(GT_LCL_FLD); - hiResult->AsLclFld()->gtLclOffs = 4; - hiResult->AsLclFld()->gtFieldSeq = FieldSeqStore::NotAField(); - } - tree->gtType = TYP_INT; - - comp->lvaIncRefCnts(loResult); - comp->lvaIncRefCnts(hiResult); - break; - } - case GT_LCL_FLD: - loResult = tree; - loResult->gtType = TYP_INT; - - hiResult = comp->gtNewLclFldNode(loResult->AsLclFld()->gtLclNum, - TYP_INT, - loResult->AsLclFld()->gtLclOffs + 4); - break; - case GT_STORE_LCL_VAR: - { - GenTree* nextTree = tree->gtNext; - GenTree* rhs = tree->gtGetOp1(); - if (rhs->OperGet() == GT_PHI || rhs->OperGet() == GT_CALL) - { - // GT_CALLs are not decomposed, so will not be converted to GT_LONG - // GT_STORE_LCL_VAR = GT_CALL are handled in genMultiRegCallStoreToLocal - break; - } - noway_assert(rhs->OperGet() == GT_LONG); - unsigned varNum = tree->AsLclVarCommon()->gtLclNum; - LclVarDsc * varDsc = comp->lvaTable + varNum; - comp->lvaDecRefCnts(tree); - GenTree* loRhs = rhs->gtGetOp1(); - GenTree* hiRhs = rhs->gtGetOp2(); - GenTree* hiStore = comp->gtNewLclLNode(varNum, TYP_INT); - 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); - } - 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(); - } - tree->gtOp.gtOp1 = loRhs; - tree->gtType = TYP_INT; - - loRhs->gtNext = tree; - tree->gtPrev = loRhs; - - hiStore->gtOp.gtOp1 = hiRhs; - hiStore->CopyCosts(tree); - hiStore->gtFlags |= GTF_VAR_DEF; - - comp->lvaIncRefCnts(tree); - comp->lvaIncRefCnts(hiStore); - - tree->gtNext = hiRhs; - hiRhs->gtPrev = tree; - hiRhs->gtNext = hiStore; - hiStore->gtPrev = hiRhs; - hiStore->gtNext = nextTree; - if (nextTree != nullptr) - { - nextTree->gtPrev = hiStore; - } - nextTree = hiRhs; - GenTreeStmt* currStmt = comp->compCurStmt->AsStmt(); - bool isEmbeddedStmt = !currStmt->gtStmtIsTopLevel(); - if (!isEmbeddedStmt) - { - tree->gtNext = nullptr; - hiRhs->gtPrev = nullptr; - } - - // TODO-Cleanup: DecomposeStoreInd contains identical code, - // this should be moved to a common function - if (isEmbeddedStmt) - { - // Find a parent statement containing storeIndHigh. - GenTree* parentStmt = currStmt; - while ((parentStmt != nullptr) && (!parentStmt->AsStmt()->gtStmtIsTopLevel())) - { - parentStmt = parentStmt->gtPrev; - } - assert(parentStmt); - - GenTreeStmt* stmt = comp->fgMakeEmbeddedStmt(comp->compCurBB, hiStore, parentStmt); - stmt->gtStmtILoffsx = comp->compCurStmt->gtStmt.gtStmtILoffsx; -#ifdef DEBUG - stmt->gtStmtLastILoffs = comp->compCurStmt->gtStmt.gtStmtLastILoffs; -#endif // DEBUG - } - else - { - GenTreeStmt* stmt = comp->fgNewStmtFromTree(hiStore); - stmt->gtStmtILoffsx = comp->compCurStmt->gtStmt.gtStmtILoffsx; -#ifdef DEBUG - stmt->gtStmtLastILoffs = comp->compCurStmt->gtStmt.gtStmtLastILoffs; -#endif // DEBUG - - // Find an insert point. Skip all embedded statements. - GenTree* insertPt = currStmt; - while ((insertPt->gtNext != nullptr) && (!insertPt->gtNext->AsStmt()->gtStmtIsTopLevel())) - { - insertPt = insertPt->gtNext; - } - - comp->fgInsertStmtAfter(comp->compCurBB, insertPt, stmt); - } - } - break; - case GT_CAST: - { - assert(tree->gtPrev == tree->gtGetOp1()); - NYI_IF(tree->gtOverflow(), "TYP_LONG cast with overflow"); - switch (tree->AsCast()->CastFromType()) - { - case TYP_INT: - if (tree->gtFlags & GTF_UNSIGNED) - { - loResult = tree->gtGetOp1(); - hiResult = new (comp, GT_CNS_INT) GenTreeIntCon(TYP_INT, 0); - comp->fgSnipNode(curStmt, tree); - } - else - { - NYI("Lowering of signed cast TYP_INT->TYP_LONG"); - } - break; - default: - NYI("Unimplemented type for Lowering of cast to TYP_LONG"); - break; - } - break; - } - case GT_CNS_LNG: - { - INT32 hiVal = tree->AsLngCon()->HiVal(); - - loResult = tree; - loResult->ChangeOperConst(GT_CNS_INT); - loResult->gtType = TYP_INT; - - hiResult = new (comp, GT_CNS_INT) GenTreeIntCon(TYP_INT, hiVal); - hiResult->CopyCosts(tree); - } - break; - case GT_CALL: - { - GenTree* parent = data->parent; - - // We only need to force var = call() if the call is not a top-level node. - if (parent != nullptr) - { - if (parent->gtOper == GT_STORE_LCL_VAR) - { - // If parent is already a STORE_LCL_VAR, we can skip it if - // it is already marked as lvIsMultiRegRet - unsigned varNum = parent->AsLclVarCommon()->gtLclNum; - if (comp->lvaTable[varNum].lvIsMultiRegRet) - { - break; - } - else if (!comp->lvaTable[varNum].lvPromoted) - { - // If var wasn't promoted, we can just set lvIsMultiRegRet - comp->lvaTable[varNum].lvIsMultiRegRet = true; - break; - } - } - - // Otherwise, we need to force var = call() - GenTreePtr* treePtr = nullptr; - parent = tree->gtGetParent(&treePtr); - - assert(treePtr != nullptr); - - GenTreeStmt* asgStmt = comp->fgInsertEmbeddedFormTemp(treePtr); - GenTreePtr stLclVar = asgStmt->gtStmtExpr; - assert(stLclVar->OperIsLocalStore()); - - unsigned varNum = stLclVar->AsLclVarCommon()->gtLclNum; - comp->lvaTable[varNum].lvIsMultiRegRet = true; - comp->fgFixupIfCallArg(data->parentStack, tree, *treePtr); - - // Decompose new node - DecomposeNode(treePtr, data); - } - break; - } - case GT_RETURN: - assert(tree->gtOp.gtOp1->OperGet() == GT_LONG); - break; - case GT_STOREIND: - DecomposeStoreInd(tree); - break; - case GT_STORE_LCL_FLD: - assert(tree->gtOp.gtOp1->OperGet() == GT_LONG); - NYI("st.lclFld of of TYP_LONG"); - break; - case GT_IND: - NYI("GT_IND of TYP_LONG"); - break; - case GT_NOT: - { - GenTree* op1 = tree->gtGetOp1(); - noway_assert(op1->OperGet() == GT_LONG); - GenTree* loOp1 = op1->gtGetOp1(); - GenTree* hiOp1 = op1->gtGetOp2(); - comp->fgSnipNode(curStmt, op1); - loResult = tree; - loResult->gtType = TYP_INT; - loResult->gtOp.gtOp1 = loOp1; - loOp1->gtNext = loResult; - loResult->gtPrev = loOp1; - - hiResult = new (comp, oper) GenTreeOp(oper, TYP_INT, hiOp1, nullptr); - hiOp1->gtNext = hiResult; - hiResult->gtPrev = hiOp1; - } - break; - case GT_NEG: - NYI("GT_NEG of TYP_LONG"); - break; - // Binary operators. Those that require different computation for upper and lower half are - // handled by the use of getHiOper(). - case GT_ADD: - case GT_SUB: - case GT_OR: - case GT_XOR: - case GT_AND: - { - NYI_IF((tree->gtFlags & GTF_REVERSE_OPS) != 0, "Binary operator with GTF_REVERSE_OPS"); - GenTree* op1 = tree->gtGetOp1(); - GenTree* op2 = tree->gtGetOp2(); - // Both operands must have already been decomposed into GT_LONG operators. - noway_assert((op1->OperGet() == GT_LONG) && (op2->OperGet() == GT_LONG)); - // Capture the lo and hi halves of op1 and op2. - GenTree* loOp1 = op1->gtGetOp1(); - GenTree* hiOp1 = op1->gtGetOp2(); - GenTree* loOp2 = op2->gtGetOp1(); - GenTree* hiOp2 = op2->gtGetOp2(); - - // We don't have support to decompose a TYP_LONG node that already has a child that has - // been decomposed into parts, where the high part depends on the value generated by the - // low part (via the flags register). For example, if we have: - // +(gt_long(+(lo3, lo4), +Hi(hi3, hi4)), gt_long(lo2, hi2)) - // We would decompose it here to: - // gt_long(+(+(lo3, lo4), lo2), +Hi(+Hi(hi3, hi4), hi2)) - // But this would generate incorrect code, because the "+Hi(hi3, hi4)" code generation - // needs to immediately follow the "+(lo3, lo4)" part. Also, if this node is one that - // requires a unique high operator, and the child nodes are not simple locals (e.g., - // they are decomposed nodes), then we also can't decompose the node, as we aren't - // guaranteed the high and low parts will be executed immediately after each other. - - NYI_IF(hiOp1->OperIsHigh() || - hiOp2->OperIsHigh() || - (GenTree::OperIsHigh(getHiOper(oper)) && - (!loOp1->OperIsLeaf() || - !hiOp1->OperIsLeaf() || - !loOp1->OperIsLeaf() || - !hiOp2->OperIsLeaf())), - "Can't decompose expression tree TYP_LONG node"); - - // Now, remove op1 and op2 from the node list. - comp->fgSnipNode(curStmt, op1); - comp->fgSnipNode(curStmt, op2); - - // We will reuse "tree" for the loResult, which will now be of TYP_INT, and its operands - // will be the lo halves of op1 from above. - loResult = tree; - loResult->SetOper(getLoOper(loResult->OperGet())); - loResult->gtType = TYP_INT; - loResult->gtOp.gtOp1 = loOp1; - loResult->gtOp.gtOp2 = loOp2; - - // The various halves will be correctly threaded internally. We simply need to - // relink them into the proper order, i.e. loOp1 is followed by loOp2, and then - // the loResult node. - // (This rethreading, and that below, are where we need to address the reverse ops case). - // The current order is (after snipping op1 and op2): - // ... loOp1-> ... hiOp1->loOp2First ... loOp2->hiOp2First ... hiOp2 - // The order we want is: - // ... loOp1->loOp2First ... loOp2->loResult - // ... hiOp1->hiOp2First ... hiOp2->hiResult - // i.e. we swap hiOp1 and loOp2, and create (for now) separate loResult and hiResult trees - GenTree* loOp2First = hiOp1->gtNext; - GenTree* hiOp2First = loOp2->gtNext; - - // First, we will NYI if both hiOp1 and loOp2 have side effects. - NYI_IF(((loOp2->gtFlags & GTF_ALL_EFFECT) != 0) && ((hiOp1->gtFlags & GTF_ALL_EFFECT) != 0), - "Binary long operator with non-reorderable sub expressions"); - - // Now, we reorder the loOps and the loResult. - loOp1->gtNext = loOp2First; - loOp2First->gtPrev = loOp1; - loOp2->gtNext = loResult; - loResult->gtPrev = loOp2; - - // Next, reorder the hiOps and the hiResult. - hiResult = new (comp, oper) GenTreeOp(getHiOper(oper), TYP_INT, hiOp1, hiOp2); - hiOp1->gtNext = hiOp2First; - hiOp2First->gtPrev = hiOp1; - hiOp2->gtNext = hiResult; - hiResult->gtPrev = hiOp2; - - if (oper == GT_ADD || oper == GT_SUB) - { - if (loResult->gtOverflow()) - { - hiResult->gtFlags |= GTF_OVERFLOW; - loResult->gtFlags &= ~GTF_OVERFLOW; - } - if (loResult->gtFlags & GTF_UNSIGNED) - { - hiResult->gtFlags |= GTF_UNSIGNED; - } - } - // Below, we'll put the loResult and hiResult trees together, using the more - // general fgInsertTreeInListAfter() method. - } - break; - case GT_MUL: - NYI("Arithmetic binary operators on TYP_LONG - GT_MUL"); - break; - case GT_DIV: - NYI("Arithmetic binary operators on TYP_LONG - GT_DIV"); - break; - case GT_MOD: - NYI("Arithmetic binary operators on TYP_LONG - GT_MOD"); - break; - case GT_UDIV: - NYI("Arithmetic binary operators on TYP_LONG - GT_UDIV"); - break; - case GT_UMOD: - NYI("Arithmetic binary operators on TYP_LONG - GT_UMOD"); - break; - case GT_LSH: - case GT_RSH: - case GT_RSZ: - NYI("Arithmetic binary operators on TYP_LONG - SHIFT"); - break; - case GT_ROL: - case GT_ROR: - NYI("Arithmetic binary operators on TYP_LONG - ROTATE"); - break; - case GT_MULHI: - NYI("Arithmetic binary operators on TYP_LONG - MULHI"); - break; - case GT_LOCKADD: - case GT_XADD: - case GT_XCHG: - case GT_CMPXCHG: - NYI("Interlocked operations on TYP_LONG"); - break; - default: - { - JITDUMP("Illegal TYP_LONG node %s in Lowering.", GenTree::NodeName(tree->OperGet())); - noway_assert(!"Illegal TYP_LONG node in Lowering."); - break; - } - } - if (loResult != nullptr) - { - noway_assert(hiResult != nullptr); - comp->fgInsertTreeInListAfter(hiResult, loResult, curStmt); - hiResult->CopyCosts(tree); - - newTree = new (comp, GT_LONG) GenTreeOp(GT_LONG, TYP_LONG, loResult, hiResult); - SimpleLinkNodeAfter(hiResult, newTree); - } - if (newTree != nullptr) - { - comp->fgFixupIfCallArg(data->parentStack, tree, newTree); - newTree->CopyCosts(tree); - *pTree = newTree; - } -#ifdef DEBUG - if (comp->verbose) - { - printf(" AFTER:\n"); - comp->gtDispTree(*pTree); - } -#endif -#endif // //_TARGET_64BIT_ -} - -// Decompose 64-bit storeIndir tree into multiple 32-bit trees. -#if !defined(_TARGET_64BIT_) -void Lowering::DecomposeStoreInd(GenTree* tree) -{ - assert(tree->gtOp.gtOp2->OperGet() == GT_LONG); - - GenTreeStmt* currStmt = comp->compCurStmt->AsStmt(); - bool isEmbeddedStmt = !currStmt->gtStmtIsTopLevel(); - - // Example input trees (a nested embedded statement case) - // - // - // * stmtExpr void (top level) (IL ???... ???) - // | /--* argPlace ref $280 - // | +--* argPlace int $4a - // | | { * stmtExpr void (embedded) (IL ???... ???) - // | | { | /--* lclVar ref V11 tmp9 u:3 $21c - // | | { | +--* const int 4 $44 - // | | { | /--* + byref $2c8 - // | | { | | { * stmtExpr void (embedded) (IL ???... ???) - // | | { | | { | /--* lclFld long V01 arg1 u:2[+8] Fseq[i] $380 - // | | { | | { \--* st.lclVar long (P) V21 cse8 - // | | { | | { \--* int V21.hi (offs=0x00) -> V22 rat0 - // | | { | | { \--* int V21.hi (offs=0x04) -> V23 rat1 - // | | { | | /--* lclVar int V22 rat0 $380 - // | | { | | +--* lclVar int V23 rat1 - // | | { | +--* gt_long long - // | | { \--* storeIndir long - // | +--* lclVar ref V11 tmp9 u:3 (last use) $21c - // | +--* lclVar ref V02 tmp0 u:3 $280 - // | +--* const int 8 $4a - // \--* call help void HELPER.CORINFO_HELP_ARRADDR_ST $205 - // - - GenTree* linkBegin = comp->fgGetFirstNode(tree)->gtPrev; - GenTree* linkEnd = tree->gtNext; - GenTree* gtLong = tree->gtOp.gtOp2; - - // Save address to a temp. It is used in storeIndLow and storeIndHigh trees. - GenTreeStmt* addrStmt = comp->fgInsertEmbeddedFormTemp(&tree->gtOp.gtOp1); -#ifdef DEBUG - if (comp->verbose) - { - printf("[DecomposeStoreInd]: Saving address tree to a temp var:\n"); - comp->gtDispTree(addrStmt); - } -#endif - - // If we have made a new top-level statement, and it has inherited any - // embedded statements from curStmt, they have not yet been decomposed. - if (addrStmt->gtStmtIsTopLevel()) - { - for (GenTreePtr nextEmbeddedStmt = addrStmt->gtStmtNextIfEmbedded(); - nextEmbeddedStmt != nullptr; - nextEmbeddedStmt = nextEmbeddedStmt->gtStmt.gtStmtNextIfEmbedded()) - { - comp->compCurStmt = nextEmbeddedStmt; - comp->fgWalkTreePost(&nextEmbeddedStmt->gtStmt.gtStmtExpr, &Lowering::DecompNodeHelper, this, true); - } - } - - // Restore curStmt. - comp->compCurStmt = currStmt; - - if (!gtLong->gtOp.gtOp1->OperIsLeaf()) - { - GenTreeStmt* dataLowStmt = comp->fgInsertEmbeddedFormTemp(>Long->gtOp.gtOp1); -#ifdef DEBUG - if (comp->verbose) - { - printf("[DecomposeStoreInd]: Saving low data tree to a temp var:\n"); - comp->gtDispTree(dataLowStmt); - } -#endif - // If we have made a new top-level statement, and it has inherited any - // embedded statements from curStmt, they have not yet been decomposed. - if (dataLowStmt->gtStmtIsTopLevel()) - { - for (GenTreePtr nextEmbeddedStmt = dataLowStmt->gtStmtNextIfEmbedded(); - nextEmbeddedStmt != nullptr; - nextEmbeddedStmt = nextEmbeddedStmt->gtStmt.gtStmtNextIfEmbedded()) - { - comp->compCurStmt = nextEmbeddedStmt; - comp->fgWalkTreePost(&nextEmbeddedStmt->gtStmt.gtStmtExpr, &Lowering::DecompNodeHelper, this, true); - } - } - - // Restore curStmt. - comp->compCurStmt = currStmt; - } - - if (!gtLong->gtOp.gtOp2->OperIsLeaf()) - { - GenTreeStmt* dataHighStmt = comp->fgInsertEmbeddedFormTemp(>Long->gtOp.gtOp2); -#ifdef DEBUG - if (comp->verbose) - { - printf("[DecomposeStoreInd]: Saving high data tree to a temp var:\n"); - comp->gtDispTree(dataHighStmt); - } -#endif - } - - // Example trees after embedded statements for address and data are added. - // This example saves all address and data trees into temp variables - // to show how those embedded statements are created. - // - // * stmtExpr void (top level) (IL ???... ???) - // | /--* argPlace ref $280 - // | +--* argPlace int $4a - // | | { * stmtExpr void (embedded) (IL ???... ???) - // | | { | /--* lclVar ref V11 tmp9 u:3 $21c - // | | { | +--* const int 4 $44 - // | | { | /--* + byref $2c8 - // | | { \--* st.lclVar byref V24 rat2 - // | | { * stmtExpr void (embedded) (IL ???... ???) - // | | { | /--* lclVar byref V24 rat2 - // | | { | | { * stmtExpr void (embedded) (IL ???... ???) - // | | { | | { | /--* lclFld long V01 arg1 u:2[+8] Fseq[i] $380380 - // | | { | | { \--* st.lclVar long (P) V21 cse8 - // | | { | | { \--* int V21.hi (offs=0x00) -> V22 rat0 - // | | { | | { \--* int V21.hi (offs=0x04) -> V23 rat1 - // | | { | | { * stmtExpr void (embedded) (IL ???... ???) - // | | { | | { | /--* lclVar int V22 rat0 $380 - // | | { | | { \--* st.lclVar int V25 rat3 - // | | { | | /--* lclVar int V25 rat3 - // | | { | | | { * stmtExpr void (embedded) (IL ???... ???) - // | | { | | | { | /--* lclVar int V23 rat1 - // | | { | | | { \--* st.lclVar int V26 rat4 - // | | { | | +--* lclVar int V26 rat4 - // | | { | +--* gt_long long - // | | { \--* storeIndir long - // | +--* lclVar ref V11 tmp9 u:3 (last use) $21c - // | +--* lclVar ref V02 tmp0 u:3 $280 - // | +--* const int 8 $4a - // \--* call help void HELPER.CORINFO_HELP_ARRADDR_ST $205 - - GenTree* addrBase = tree->gtOp.gtOp1; - GenTree* dataHigh = gtLong->gtOp.gtOp2; - GenTree* dataLow = gtLong->gtOp.gtOp1; - GenTree* storeIndLow = tree; - - // Rewrite storeIndLow tree to save only lower 32-bit data. - // - // | | { | /--* lclVar byref V24 rat2 (address) - // ... - // | | { | +--* lclVar int V25 rat3 (lower 32-bit data) - // | | { | { * stmtExpr void (embedded) (IL ???... ???) - // | | { | { | /--* lclVar int V23 rat1 - // | | { | { \--* st.lclVar int V26 rat4 - // | | { \--* storeIndir int - comp->fgSnipNode(currStmt, gtLong); - comp->fgSnipNode(currStmt, dataHigh); - storeIndLow->gtOp.gtOp2 = dataLow; - storeIndLow->gtType = TYP_INT; - - // Construct storeIndHigh tree - // - // | | { *stmtExpr void (embedded)(IL ? ? ? ... ? ? ? ) - // | | { | / --* lclVar int V26 rat4 - // | | { | | / --* lclVar byref V24 rat2 - // | | { | +--* lea(b + 4) ref - // | | { \--* storeIndir int - GenTree* addrBaseHigh = new(comp, GT_LCL_VAR) GenTreeLclVar(GT_LCL_VAR, - addrBase->TypeGet(), addrBase->AsLclVarCommon()->GetLclNum(), BAD_IL_OFFSET); - GenTree* addrHigh = new(comp, GT_LEA) GenTreeAddrMode(TYP_REF, addrBaseHigh, nullptr, 0, genTypeSize(TYP_INT)); - GenTree* storeIndHigh = new(comp, GT_STOREIND) GenTreeStoreInd(TYP_INT, addrHigh, dataHigh); - storeIndHigh->gtFlags = (storeIndLow->gtFlags & (GTF_ALL_EFFECT | GTF_LIVENESS_MASK)); - storeIndHigh->gtFlags |= GTF_REVERSE_OPS; - storeIndHigh->CopyCosts(storeIndLow); - - // Internal links of storeIndHigh tree - dataHigh->gtPrev = dataHigh->gtNext = nullptr; - SimpleLinkNodeAfter(dataHigh, addrBaseHigh); - SimpleLinkNodeAfter(addrBaseHigh, addrHigh); - SimpleLinkNodeAfter(addrHigh, storeIndHigh); - - // External links of storeIndHigh tree - //dataHigh->gtPrev = nullptr; - if (isEmbeddedStmt) - { - // If storeIndTree is an embedded statement, connect storeIndLow - // and dataHigh - storeIndLow->gtNext = dataHigh; - dataHigh->gtPrev = storeIndLow; - } - storeIndHigh->gtNext = linkEnd; - if (linkEnd != nullptr) - { - linkEnd->gtPrev = storeIndHigh; - } - - if (isEmbeddedStmt) - { - // Find a parent statement containing storeIndHigh. - GenTree* parentStmt = currStmt; - while ((parentStmt != nullptr) && (!parentStmt->AsStmt()->gtStmtIsTopLevel())) - { - parentStmt = parentStmt->gtPrev; - } - assert(parentStmt); - - GenTreeStmt* stmt = comp->fgMakeEmbeddedStmt(comp->compCurBB, storeIndHigh, parentStmt); - stmt->gtStmtILoffsx = comp->compCurStmt->gtStmt.gtStmtILoffsx; - } - else - { - GenTreeStmt* stmt = comp->fgNewStmtFromTree(storeIndHigh); - stmt->gtStmtILoffsx = comp->compCurStmt->gtStmt.gtStmtILoffsx; - - // Find an insert point. Skip all embedded statements. - GenTree* insertPt = currStmt; - while ((insertPt->gtNext != nullptr) && (!insertPt->gtNext->AsStmt()->gtStmtIsTopLevel())) - { - insertPt = insertPt->gtNext; - } - - comp->fgInsertStmtAfter(comp->compCurBB, insertPt, stmt); - } - - // Example final output - // - // * stmtExpr void (top level) (IL ???... ???) - // | /--* argPlace ref $280 - // | +--* argPlace int $4a - // | | { * stmtExpr void (embedded) (IL ???... ???) - // | | { | /--* lclVar ref V11 tmp9 u:3 $21c - // | | { | +--* const int 4 $44 - // | | { | /--* + byref $2c8 - // | | { \--* st.lclVar byref V24 rat2 - // | | { * stmtExpr void (embedded) (IL ???... ???) - // | | { | /--* lclVar byref V24 rat2 - // | | { | | { * stmtExpr void (embedded) (IL ???... ???) - // | | { | | { | /--* lclFld int V01 arg1 u:2[+8] Fseq[i] $380 - // | | { | | { | +--* lclFld int V01 arg1 [+12] - // | | { | | { | /--* gt_long long - // | | { | | { \--* st.lclVar long (P) V21 cse8 - // | | { | | { \--* int V21.hi (offs=0x00) -> V22 rat0 - // | | { | | { \--* int V21.hi (offs=0x04) -> V23 rat1 - // | | { | | { * stmtExpr void (embedded) (IL ???... ???) - // | | { | | { | /--* lclVar int V22 rat0 $380 - // | | { | | { \--* st.lclVar int V25 rat3 - // | | { | +--* lclVar int V25 rat3 - // | | { | { * stmtExpr void (embedded) (IL ???... ???) - // | | { | { | /--* lclVar int V23 rat1 - // | | { | { \--* st.lclVar int V26 rat4 - // | | { \--* storeIndir int - // | | { * stmtExpr void (embedded) (IL ???... ???) - // | | { | /--* lclVar int V26 rat4 - // | | { | | /--* lclVar byref V24 rat2 - // | | { | +--* lea(b+4) ref - // | | { \--* storeIndir int - // | | /--* lclVar ref V11 tmp9 u:3 (last use) $21c - // | +--* putarg_stk [+0x00] ref - // | | /--* lclVar ref V02 tmp0 u:3 $280 - // | +--* putarg_reg ref - // | | /--* const int 8 $4a - // | +--* putarg_reg int - // \--* call help void HELPER.CORINFO_HELP_ARRADDR_ST $205 -} -#endif //!_TARGET_64BIT_ /** Creates an assignment of an existing tree to a new temporary local variable * and the specified reference count for the new variable. @@ -4471,7 +3685,8 @@ void Lowering::DoPhase() #endif #if !defined(_TARGET_64BIT_) - comp->lvaPromoteLongVars(); + DecomposeLongs decomp(comp); // Initialize the long decomposition class. + decomp.PrepareForDecomposition(); #endif // !defined(_TARGET_64BIT_) for (BasicBlock* block = comp->fgFirstBB; block; block = block->bbNext) @@ -4483,24 +3698,7 @@ void Lowering::DoPhase() comp->compCurBB = block; #if !defined(_TARGET_64BIT_) - // Walk the statement trees in this basic block - // Decompose all long trees first before lowering. Decomposition could - // insert statements before current statement. - for (stmt = block->bbTreeList; stmt; stmt = stmt->gtNext) - { - if (stmt->gtFlags & GTF_STMT_SKIP_LOWER) - { - continue; - } -#ifdef DEBUG - if (comp->verbose) - { - printf("Decomposing BB%02u, stmt id %u\n", block->bbNum, stmt->gtTreeID); - } -#endif - comp->compCurStmt = stmt; - comp->fgWalkTreePost(&stmt->gtStmt.gtStmtExpr, &Lowering::DecompNodeHelper, this, true); - } + decomp.DecomposeBlock(block); #endif //!_TARGET_64BIT_ // Walk the statement trees in this basic block @@ -4520,7 +3718,7 @@ void Lowering::DoPhase() comp->fgWalkTreePost(&stmt->gtStmt.gtStmtExpr, &Lowering::LowerNodeHelper, this, true); // We may have removed "stmt" in LowerNode(). stmt = comp->compCurStmt; - } + } } // If we have any PInvoke calls, insert the one-time prolog code. We've already inserted the epilog code in the appropriate spots. @@ -4877,18 +4075,6 @@ void Lowering::UnlinkNode(GenTree** ppParentLink, GenTree* stmt, BasicBlock* blo ReplaceNode(ppParentLink, comp->gtNewNothingNode(), stmt, block); } -void Lowering::SimpleLinkNodeAfter(GenTree* prevTree, GenTree* newTree) -{ - GenTree* nextTree = prevTree->gtNext; - newTree->gtPrev = prevTree; - prevTree->gtNext = newTree; - if (nextTree != nullptr) - { - newTree->gtNext = nextTree; - nextTree->gtPrev = newTree; - } -} - #ifdef _TARGET_64BIT_ /** diff --git a/src/jit/lower.h b/src/jit/lower.h index 9419557ad5a3..4baeb7e1fc57 100644 --- a/src/jit/lower.h +++ b/src/jit/lower.h @@ -52,11 +52,9 @@ class Lowering : public Phase private: // Friends static Compiler::fgWalkResult LowerNodeHelper (GenTreePtr* ppTree, Compiler::fgWalkData* data); - static Compiler::fgWalkResult DecompNodeHelper (GenTreePtr* ppTree, Compiler::fgWalkData* data); static Compiler::fgWalkResult TreeInfoInitHelper(GenTreePtr* ppTree, Compiler::fgWalkData* data); // Member Functions - void DecomposeNode(GenTreePtr* tree, Compiler::fgWalkData* data); void LowerNode(GenTreePtr* tree, Compiler::fgWalkData* data); GenTreeStmt* LowerMorphAndSeqTree(GenTree *tree); void CheckVSQuirkStackPaddingNeeded(GenTreeCall* call); @@ -197,13 +195,6 @@ class Lowering : public Phase void LowerArrElem(GenTree **ppTree, Compiler::fgWalkData* data); void LowerRotate(GenTree *tree); - // ------------------------------ - // Decompose helper functions - // ------------------------------ -#if !defined(_TARGET_64BIT_) - void DecomposeStoreInd(GenTree* tree); -#endif //!_TARGET_64BIT_ - // Utility functions void MorphBlkIntoHelperCall (GenTreePtr pTree, GenTreePtr treeStmt); public: @@ -220,7 +211,6 @@ class Lowering : public Phase BasicBlock* block); void UnlinkNode (GenTree** ppParentLink, GenTree* stmt, BasicBlock* block); - void SimpleLinkNodeAfter (GenTree* prevTree, GenTree* newTree); // return true if 'childNode' is an immediate that can be contained // by the 'parentNode' (i.e. folded into an instruction)