Skip to content

Commit 4512bf1

Browse files
committed
JIT: Generalize handling of commas in block morphing
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 d795694 commit 4512bf1

File tree

4 files changed

+149
-209
lines changed

4 files changed

+149
-209
lines changed

src/coreclr/jit/fginline.cpp

Lines changed: 1 addition & 8 deletions
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-
//
399396
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;
397+
return m_compiler->gtNewOperNode(GT_COMMA, lcl->TypeGet(), asg, lcl);
405398
}
406399
#endif // FEATURE_MULTIREG_RET
407400

src/coreclr/jit/morphblock.cpp

Lines changed: 146 additions & 134 deletions
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,34 @@ 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+
164+
comma->gtFlags &= ~GTF_ALL_EFFECT;
165+
comma->gtFlags |= (sideEffects->gtFlags | m_result->gtFlags) & GTF_ALL_EFFECT;
166+
m_result = comma;
167+
}
168+
else
169+
{
170+
m_result = m_comp->gtNewOperNode(GT_COMMA, m_result->TypeGet(), sideEffects, m_result);
171+
}
172+
INDEBUG(m_result->gtDebugFlags |= GTF_DEBUG_NODE_MORPHED);
173+
174+
sideEffects = sideEffects->gtNext;
154175
}
155-
#endif // DEBUG
176+
177+
JITDUMP("%s (after):\n", GetHelperName());
178+
DISPTREE(m_result);
156179

157180
return m_result;
158181
}
@@ -317,125 +340,6 @@ void MorphInitBlockHelper::MorphStructCases()
317340
}
318341
}
319342

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-
439343
//------------------------------------------------------------------------
440344
// InitFieldByField: Attempts to promote a local block init tree to a tree
441345
// of promoted field initialization assignments.
@@ -663,6 +567,119 @@ void MorphInitBlockHelper::TryPrimitiveInit()
663567
}
664568
}
665569

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

743755
if (m_src->IsLocal())
744756
{

0 commit comments

Comments
 (0)