Skip to content

Commit eae7caa

Browse files
authored
JIT: Generalize handling of commas in block morphing (#83590)
Eliminate commas early in block morphing, before the rest of the transformation needs to look at it. Do this by extracting their side effects and adding them on top of the returned result instead. This allows us to handle arbitrary COMMAs on destination operand in a general manner. Prerequisite to #83388. Fix #1699.
1 parent 896e632 commit eae7caa

File tree

3 files changed

+149
-145
lines changed

3 files changed

+149
-145
lines changed

src/coreclr/jit/fginline.cpp

+2-9
Original file line numberDiff line numberDiff line change
@@ -393,15 +393,8 @@ class SubstitutePlaceholdersAndDevirtualizeWalker : public GenTreeVisitor<Substi
393393
asg = inlinee;
394394
}
395395

396-
// Block morphing does not support (promoted) locals under commas, as such, instead of "COMMA(asg, lcl)" we
397-
// do "OBJ(COMMA(asg, ADDR(LCL)))". TODO-1stClassStructs: improve block morphing and delete this workaround.
398-
//
399-
GenTree* lcl = m_compiler->gtNewLclvNode(lclNum, varDsc->TypeGet());
400-
GenTree* addr = m_compiler->gtNewOperNode(GT_ADDR, TYP_I_IMPL, lcl);
401-
addr = m_compiler->gtNewOperNode(GT_COMMA, addr->TypeGet(), asg, addr);
402-
GenTree* obj = m_compiler->gtNewObjNode(varDsc->GetLayout(), addr);
403-
404-
return obj;
396+
GenTree* lcl = m_compiler->gtNewLclvNode(lclNum, varDsc->TypeGet());
397+
return m_compiler->gtNewOperNode(GT_COMMA, lcl->TypeGet(), asg, lcl);
405398
}
406399
#endif // FEATURE_MULTIREG_RET
407400

src/coreclr/jit/morphblock.cpp

+145-134
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,10 @@ class MorphInitBlockHelper
2727
return "MorphInitBlock";
2828
}
2929

30-
static GenTree* MorphBlock(Compiler* comp, GenTree* tree, bool isDest);
31-
static GenTree* MorphCommaBlock(Compiler* comp, GenTreeOp* firstComma);
32-
3330
private:
34-
void TryInitFieldByField();
35-
void TryPrimitiveInit();
31+
void TryInitFieldByField();
32+
void TryPrimitiveInit();
33+
GenTree* EliminateCommas(GenTree** commaPool);
3634

3735
protected:
3836
Compiler* m_comp;
@@ -127,6 +125,9 @@ GenTree* MorphInitBlockHelper::Morph()
127125
{
128126
JITDUMP("%s:\n", GetHelperName());
129127

128+
GenTree* commaPool;
129+
GenTree* sideEffects = EliminateCommas(&commaPool);
130+
130131
PrepareDst();
131132
PrepareSrc();
132133
PropagateBlockAssertions();
@@ -147,12 +148,33 @@ GenTree* MorphInitBlockHelper::Morph()
147148
{
148149
m_result->gtDebugFlags |= GTF_DEBUG_NODE_MORPHED;
149150
}
150-
if (m_comp->verbose)
151+
#endif
152+
153+
while (sideEffects != nullptr)
151154
{
152-
printf("%s (after):\n", GetHelperName());
153-
m_comp->gtDispTree(m_result);
155+
if (commaPool != nullptr)
156+
{
157+
GenTree* comma = commaPool;
158+
commaPool = commaPool->gtNext;
159+
160+
assert(comma->OperIs(GT_COMMA));
161+
comma->AsOp()->gtOp1 = sideEffects;
162+
comma->AsOp()->gtOp2 = m_result;
163+
comma->gtFlags = (sideEffects->gtFlags | m_result->gtFlags) & GTF_ALL_EFFECT;
164+
165+
m_result = comma;
166+
}
167+
else
168+
{
169+
m_result = m_comp->gtNewOperNode(GT_COMMA, m_result->TypeGet(), sideEffects, m_result);
170+
}
171+
INDEBUG(m_result->gtDebugFlags |= GTF_DEBUG_NODE_MORPHED);
172+
173+
sideEffects = sideEffects->gtNext;
154174
}
155-
#endif // DEBUG
175+
176+
JITDUMP("%s (after):\n", GetHelperName());
177+
DISPTREE(m_result);
156178

157179
return m_result;
158180
}
@@ -317,125 +339,6 @@ void MorphInitBlockHelper::MorphStructCases()
317339
}
318340
}
319341

320-
//------------------------------------------------------------------------
321-
// MorphBlock: Morph a block node preparatory to morphing a block assignment.
322-
//
323-
// Arguments:
324-
// comp - a compiler instance;
325-
// tree - a struct type node;
326-
// isDest - true if this is the destination of an assignment;
327-
//
328-
// Return Value:
329-
// Returns the possibly-morphed node. The caller is responsible for updating
330-
// the parent of this node.
331-
//
332-
// static
333-
GenTree* MorphInitBlockHelper::MorphBlock(Compiler* comp, GenTree* tree, bool isDest)
334-
{
335-
JITDUMP("MorphBlock for %s tree, before:\n", (isDest ? "dst" : "src"));
336-
DISPTREE(tree);
337-
338-
assert(varTypeIsStruct(tree));
339-
340-
if (tree->OperIs(GT_COMMA))
341-
{
342-
// TODO-Cleanup: this block is not needed for not struct nodes, but
343-
// TryPrimitiveCopy works wrong without this transformation.
344-
tree = MorphCommaBlock(comp, tree->AsOp());
345-
if (isDest)
346-
{
347-
tree->SetDoNotCSE();
348-
}
349-
}
350-
351-
assert(!tree->OperIsIndir() || varTypeIsI(genActualType(tree->AsIndir()->Addr())));
352-
353-
JITDUMP("MorphBlock after:\n");
354-
DISPTREE(tree);
355-
return tree;
356-
}
357-
358-
//------------------------------------------------------------------------
359-
// MorphCommaBlock: transform COMMA<struct>(X) as OBJ<STRUCT>(COMMA byref(ADDR(X)).
360-
//
361-
// Notes:
362-
// In order to CSE and value number array index expressions and bounds checks,
363-
// the commas in which they are contained need to match.
364-
// The pattern is that the COMMA should be the address expression.
365-
// Therefore, we insert a GT_ADDR just above the node, and wrap it in an obj or ind.
366-
// TODO-1stClassStructs: Consider whether this can be improved.
367-
// Example:
368-
// before: [3] comma struct <- [2] comma struct <- [1] LCL_VAR struct
369-
// after: [5] obj <- [3] comma byref <- [2] comma byref <- [4] addr byref <- [1] LCL_VAR struct
370-
//
371-
// static
372-
GenTree* MorphInitBlockHelper::MorphCommaBlock(Compiler* comp, GenTreeOp* firstComma)
373-
{
374-
assert(firstComma->OperIs(GT_COMMA));
375-
376-
ArrayStack<GenTree*> commas(comp->getAllocator(CMK_ArrayStack));
377-
for (GenTree* currComma = firstComma; currComma != nullptr && currComma->OperIs(GT_COMMA);
378-
currComma = currComma->gtGetOp2())
379-
{
380-
commas.Push(currComma);
381-
}
382-
383-
GenTree* lastComma = commas.Top();
384-
385-
GenTree* effectiveVal = lastComma->gtGetOp2();
386-
387-
if (!effectiveVal->OperIsIndir() && !effectiveVal->IsLocal())
388-
{
389-
return firstComma;
390-
}
391-
392-
assert(effectiveVal == firstComma->gtEffectiveVal());
393-
394-
GenTree* effectiveValAddr = comp->gtNewOperNode(GT_ADDR, TYP_BYREF, effectiveVal);
395-
396-
INDEBUG(effectiveValAddr->gtDebugFlags |= GTF_DEBUG_NODE_MORPHED);
397-
398-
lastComma->AsOp()->gtOp2 = effectiveValAddr;
399-
400-
while (!commas.Empty())
401-
{
402-
GenTree* comma = commas.Pop();
403-
comma->gtType = TYP_BYREF;
404-
405-
// The "IND(COMMA)" => "COMMA(IND)" transform may have set NO_CSEs on these COMMAs, clear them.
406-
comma->ClearDoNotCSE();
407-
comp->gtUpdateNodeSideEffects(comma);
408-
}
409-
410-
const var_types blockType = effectiveVal->TypeGet();
411-
GenTree* addr = firstComma;
412-
413-
GenTree* res;
414-
415-
if (blockType == TYP_STRUCT)
416-
{
417-
CORINFO_CLASS_HANDLE structHnd = comp->gtGetStructHandleIfPresent(effectiveVal);
418-
if (structHnd == NO_CLASS_HANDLE)
419-
{
420-
// TODO-1stClassStructs: get rid of all such cases.
421-
res = comp->gtNewIndir(blockType, addr);
422-
}
423-
else
424-
{
425-
res = comp->gtNewObjNode(structHnd, addr);
426-
comp->gtSetObjGcInfo(res->AsObj());
427-
}
428-
}
429-
else
430-
{
431-
res = comp->gtNewIndir(blockType, addr);
432-
}
433-
434-
comp->gtUpdateNodeSideEffects(res);
435-
INDEBUG(res->gtDebugFlags |= GTF_DEBUG_NODE_MORPHED);
436-
return res;
437-
}
438-
439342
//------------------------------------------------------------------------
440343
// InitFieldByField: Attempts to promote a local block init tree to a tree
441344
// of promoted field initialization assignments.
@@ -663,6 +566,119 @@ void MorphInitBlockHelper::TryPrimitiveInit()
663566
}
664567
}
665568

569+
//------------------------------------------------------------------------
570+
// EliminateCommas: Prepare for block morphing by removing commas from the
571+
// source operand of the assignment.
572+
//
573+
// Parameters:
574+
// commaPool - [out] Pool of GT_COMMA nodes linked by their gtNext nodes that
575+
// can be used by the caller to avoid unnecessarily creating
576+
// new commas.
577+
//
578+
// Returns:
579+
// Extracted side effects, in reverse order, linked via the gtNext fields of
580+
// the nodes.
581+
//
582+
// Notes:
583+
// We have a tree like the following (note that location-valued commas are
584+
// illegal, so there cannot be a comma on the left):
585+
//
586+
// ASG
587+
// / \.
588+
// IND COMMA
589+
// | / \.
590+
// B C D
591+
//
592+
// We'd like downstream code to just see and be expand ASG(IND(B), D).
593+
// We will produce:
594+
//
595+
// COMMA
596+
// / \.
597+
// ASG COMMA
598+
// / \ / \.
599+
// tmp B C ASG
600+
// / \.
601+
// IND D
602+
// |
603+
// tmp
604+
//
605+
// If the ASG has GTF_REVERSE_OPS then we will produce:
606+
//
607+
// COMMA
608+
// / \.
609+
// C ASG
610+
// / \.
611+
// IND D
612+
// |
613+
// B
614+
//
615+
// While keeping the GTF_REVERSE_OPS.
616+
//
617+
// Note that the final resulting tree is created in the caller since it also
618+
// needs to propagate side effect flags from the decomposed assignment to all
619+
// the created commas. Therefore this function just returns a linked list of
620+
// the side effects to be used for that purpose.
621+
//
622+
GenTree* MorphInitBlockHelper::EliminateCommas(GenTree** commaPool)
623+
{
624+
*commaPool = nullptr;
625+
626+
GenTree* sideEffects = nullptr;
627+
auto addSideEffect = [&sideEffects](GenTree* sideEff) {
628+
sideEff->gtNext = sideEffects;
629+
sideEffects = sideEff;
630+
};
631+
632+
auto addComma = [commaPool, &addSideEffect](GenTree* comma) {
633+
addSideEffect(comma->gtGetOp1());
634+
comma->gtNext = *commaPool;
635+
*commaPool = comma;
636+
};
637+
638+
GenTree* lhs = m_asg->gtGetOp1();
639+
assert(lhs->OperIsIndir() || lhs->OperIsLocal());
640+
641+
GenTree* rhs = m_asg->gtGetOp2();
642+
643+
if (m_asg->IsReverseOp())
644+
{
645+
while (rhs->OperIs(GT_COMMA))
646+
{
647+
addComma(rhs);
648+
rhs = rhs->gtGetOp2();
649+
}
650+
}
651+
else
652+
{
653+
if (lhs->OperIsIndir() && rhs->OperIs(GT_COMMA))
654+
{
655+
GenTree* addr = lhs->gtGetOp1();
656+
if (((addr->gtFlags & GTF_ALL_EFFECT) != 0) || (((rhs->gtFlags & GTF_ASG) != 0) && !addr->IsInvariant()))
657+
{
658+
unsigned lhsAddrLclNum = m_comp->lvaGrabTemp(true DEBUGARG("Block morph LHS addr"));
659+
660+
addSideEffect(m_comp->gtNewTempAssign(lhsAddrLclNum, addr));
661+
lhs->AsUnOp()->gtOp1 = m_comp->gtNewLclvNode(lhsAddrLclNum, genActualType(addr));
662+
m_comp->gtUpdateNodeSideEffects(lhs);
663+
}
664+
}
665+
666+
while (rhs->OperIs(GT_COMMA))
667+
{
668+
addComma(rhs);
669+
rhs = rhs->gtGetOp2();
670+
}
671+
}
672+
673+
if (sideEffects != nullptr)
674+
{
675+
m_asg->gtOp2 = rhs;
676+
m_comp->gtUpdateNodeSideEffects(m_asg);
677+
}
678+
679+
return sideEffects;
680+
}
681+
666682
class MorphCopyBlockHelper : public MorphInitBlockHelper
667683
{
668684
public:
@@ -733,12 +749,7 @@ MorphCopyBlockHelper::MorphCopyBlockHelper(Compiler* comp, GenTree* asg) : Morph
733749
//
734750
void MorphCopyBlockHelper::PrepareSrc()
735751
{
736-
GenTree* origSrc = m_asg->gtGetOp2();
737-
m_src = MorphBlock(m_comp, origSrc, false);
738-
if (m_src != origSrc)
739-
{
740-
m_asg->gtOp2 = m_src;
741-
}
752+
m_src = m_asg->gtGetOp2();
742753

743754
if (m_src->IsLocal())
744755
{

src/coreclr/jit/rationalize.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,8 @@ void Rationalizer::RewriteIndir(LIR::Use& use)
6969
// Arguments:
7070
// use - A use of a GT_IND node of SIMD type
7171
//
72-
// TODO-ADDR: delete this once block morphing stops taking addresses of locals
73-
// under COMMAs.
72+
// TODO-ADDR: today this only exists because the xarch backend does not handle
73+
// IND<simd12>(LCL_VAR_ADDR/LCL_FLD_ADDR) when the address is contained correctly.
7474
//
7575
void Rationalizer::RewriteSIMDIndir(LIR::Use& use)
7676
{

0 commit comments

Comments
 (0)