diff --git a/src/coreclr/src/jit/lower.cpp b/src/coreclr/src/jit/lower.cpp index b0765b203b9f5..8fadb5f22bde2 100644 --- a/src/coreclr/src/jit/lower.cpp +++ b/src/coreclr/src/jit/lower.cpp @@ -113,64 +113,11 @@ GenTree* Lowering::LowerNode(GenTree* node) { case GT_NULLCHECK: case GT_IND: - // Process struct typed indirs separately unless they are unused; - // they only appear as the source of a block copy operation or a return node. - if (!node->TypeIs(TYP_STRUCT) || node->IsUnusedValue()) - { - // TODO-Cleanup: We're passing isContainable = true but ContainCheckIndir rejects - // address containment in some cases so we end up creating trivial (reg + offfset) - // or (reg + reg) LEAs that are not necessary. - TryCreateAddrMode(node->AsIndir()->Addr(), true); - ContainCheckIndir(node->AsIndir()); - - if (node->OperIs(GT_NULLCHECK) || node->IsUnusedValue()) - { - // A nullcheck is essentially the same as an indirection with no use. - // The difference lies in whether a target register must be allocated. - // On XARCH we can generate a compare with no target register as long as the addresss - // is not contained. - // On ARM64 we can generate a load to REG_ZR in all cases. - // However, on ARM we must always generate a load to a register. - // In the case where we require a target register, it is better to use GT_IND, since - // GT_NULLCHECK is a non-value node and would therefore require an internal register - // to use as the target. That is non-optimal because it will be modeled as conflicting - // with the source register(s). - // So, to summarize: - // - On ARM64, always use GT_NULLCHECK for a dead indirection. - // - On ARM, always use GT_IND. - // - On XARCH, use GT_IND if we have a contained address, and GT_NULLCHECK otherwise. - // In all cases, change the type to TYP_INT. - // - node->gtType = TYP_INT; -#ifdef TARGET_ARM64 - bool useNullCheck = true; -#elif TARGET_ARM - bool useNullCheck = false; -#else // TARGET_XARCH - bool useNullCheck = !node->AsIndir()->Addr()->isContained(); -#endif // !TARGET_XARCH - - if (useNullCheck && node->OperIs(GT_IND)) - { - node->ChangeOper(GT_NULLCHECK); - node->ClearUnusedValue(); - } - else if (!useNullCheck && node->OperIs(GT_NULLCHECK)) - { - node->ChangeOper(GT_IND); - node->SetUnusedValue(); - } - } - } + LowerIndir(node->AsIndir()); break; case GT_STOREIND: - assert(node->TypeGet() != TYP_STRUCT); - TryCreateAddrMode(node->AsIndir()->Addr(), true); - if (!comp->codeGen->gcInfo.gcIsWriteBarrierStoreIndNode(node)) - { - LowerStoreIndir(node->AsIndir()); - } + LowerStoreIndirCommon(node->AsIndir()); break; case GT_ADD: @@ -6365,3 +6312,80 @@ void Lowering::ContainCheckBitCast(GenTree* node) op1->SetContained(); } } + +//------------------------------------------------------------------------ +// LowerStoreIndirCommon: a common logic to lower StoreIndir. +// +// Arguments: +// ind - the store indirection node we are lowering. +// +void Lowering::LowerStoreIndirCommon(GenTreeIndir* ind) +{ + assert(ind->OperIs(GT_STOREIND)); + assert(ind->TypeGet() != TYP_STRUCT); + TryCreateAddrMode(ind->Addr(), true); + if (!comp->codeGen->gcInfo.gcIsWriteBarrierStoreIndNode(ind)) + { + LowerStoreIndir(ind); + } +} + +//------------------------------------------------------------------------ +// LowerIndir: a common logic to lower IND load or NullCheck. +// +// Arguments: +// node - the ind node we are lowering. +// +void Lowering::LowerIndir(GenTreeIndir* ind) +{ + assert(ind->OperIs(GT_IND, GT_NULLCHECK)); + // Process struct typed indirs separately unless they are unused; + // they only appear as the source of a block copy operation or a return node. + if (!ind->TypeIs(TYP_STRUCT) || ind->IsUnusedValue()) + { + // TODO-Cleanup: We're passing isContainable = true but ContainCheckIndir rejects + // address containment in some cases so we end up creating trivial (reg + offfset) + // or (reg + reg) LEAs that are not necessary. + TryCreateAddrMode(ind->Addr(), true); + ContainCheckIndir(ind); + + if (ind->OperIs(GT_NULLCHECK) || ind->IsUnusedValue()) + { + // A nullcheck is essentially the same as an indirection with no use. + // The difference lies in whether a target register must be allocated. + // On XARCH we can generate a compare with no target register as long as the addresss + // is not contained. + // On ARM64 we can generate a load to REG_ZR in all cases. + // However, on ARM we must always generate a load to a register. + // In the case where we require a target register, it is better to use GT_IND, since + // GT_NULLCHECK is a non-value node and would therefore require an internal register + // to use as the target. That is non-optimal because it will be modeled as conflicting + // with the source register(s). + // So, to summarize: + // - On ARM64, always use GT_NULLCHECK for a dead indirection. + // - On ARM, always use GT_IND. + // - On XARCH, use GT_IND if we have a contained address, and GT_NULLCHECK otherwise. + // In all cases, change the type to TYP_INT. + // + ind->gtType = TYP_INT; +#ifdef TARGET_ARM64 + bool useNullCheck = true; +#elif TARGET_ARM + bool useNullCheck = false; +#else // TARGET_XARCH + bool useNullCheck = !ind->Addr()->isContained(); +#endif // !TARGET_XARCH + + if (useNullCheck && ind->OperIs(GT_IND)) + { + ind->ChangeOper(GT_NULLCHECK); + ind->ClearUnusedValue(); + } + else if (!useNullCheck && ind->OperIs(GT_NULLCHECK)) + { + ind->ChangeOper(GT_IND); + ind->SetUnusedValue(); + } + } + } +} diff --git a/src/coreclr/src/jit/lower.h b/src/coreclr/src/jit/lower.h index 5f3e13c9b8313..f51cb1e27f0db 100644 --- a/src/coreclr/src/jit/lower.h +++ b/src/coreclr/src/jit/lower.h @@ -283,6 +283,8 @@ class Lowering final : public Phase #endif // defined(TARGET_XARCH) // Per tree node member functions + void LowerStoreIndirCommon(GenTreeIndir* ind); + void LowerIndir(GenTreeIndir* ind); void LowerStoreIndir(GenTreeIndir* node); GenTree* LowerAdd(GenTreeOp* node); bool LowerUnsignedDivOrMod(GenTreeOp* divMod);