Skip to content

JIT: Inlining via splitting#124870

Draft
jakobbotsch wants to merge 34 commits intodotnet:mainfrom
jakobbotsch:inlining-via-splitting
Draft

JIT: Inlining via splitting#124870
jakobbotsch wants to merge 34 commits intodotnet:mainfrom
jakobbotsch:inlining-via-splitting

Conversation

@jakobbotsch
Copy link
Member

Reviving my prototype as I have some runtime async work that this would simplify...

Copilot AI review requested due to automatic review settings February 25, 2026 19:59
@github-actions github-actions bot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Feb 25, 2026
@dotnet-policy-service
Copy link
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR revives/implements an alternative inlining pipeline in the CoreCLR JIT based on splitting trees/blocks around inline candidates (instead of using GT_RET_EXPR placeholders), with follow-on updates across importer, inliner, and various optimizations that previously depended on GT_RET_EXPR.

Changes:

  • Remove GT_RET_EXPR from the IR and update related utilities (node lists, cloning, side-effect checks, class-handle queries, etc.).
  • Rework inlining to inline directly from GT_CALL sites using statement/tree splitting plus new setup/teardown statement list plumbing.
  • Update guarded devirtualization/fat calli transformations, struct return/retbuf handling, and instrumentation to work without GT_RET_EXPR.

Reviewed changes

Copilot reviewed 20 out of 20 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/coreclr/jit/simd.cpp Stop treating GT_RET_EXPR as a call-like SIMD stack value; normalize only GT_CALL.
src/coreclr/jit/lclvars.cpp Add a null-layout guard to struct promotion eligibility checks.
src/coreclr/jit/inline.h Adjust inline policy/debug hooks, InlineResult ctor signature, introduce InlineIRResult, add StatementListBuilder, track retbuf arg info.
src/coreclr/jit/inline.cpp Wire InlineResult construction to accept an InlineContext* and update policy notes accordingly.
src/coreclr/jit/indirectcalltransformer.cpp Refactor call discovery and operand spilling; handle STORE_LCL_VAR(call) statement shapes.
src/coreclr/jit/importervectorization.cpp Remove GT_RET_EXPR-based span/call handling paths.
src/coreclr/jit/importercalls.cpp Rework importer handling for inline/GDV candidates without GT_RET_EXPR; adjust some intrinsic patterns and candidate bookkeeping.
src/coreclr/jit/importer.cpp Remove various GT_RET_EXPR-specific normalization/spill/return handling paths; adapt retbuf and inline return plumbing.
src/coreclr/jit/gtstructs.h Remove RetExpr node struct mapping.
src/coreclr/jit/gtlist.h Remove GTNODE(RET_EXPR, ...) from the node list.
src/coreclr/jit/gentree.h Remove GenTreeRetExpr definition.
src/coreclr/jit/gentree.cpp Remove GT_RET_EXPR handling across layout queries, cloning constraints, leaf display, side-effect logic; extend gtSplitTree signature/behavior.
src/coreclr/jit/fgstmt.cpp Add helpers to splice whole statement lists into blocks (before/at end).
src/coreclr/jit/fgprofile.cpp Instrumentation temporarily links inlinee “return” IR using InlineIRResult instead of GT_RET_EXPR.
src/coreclr/jit/fgopt.cpp Simplify async-call scanning by removing GT_RET_EXPR recursion.
src/coreclr/jit/fginline.cpp Major rewrite: new walker that inlines from GT_CALL via splitting + statement/block insertion; adds setup/teardown list building.
src/coreclr/jit/fgbasic.cpp Add fgSplitBlockBeforeStatement helper.
src/coreclr/jit/debuginfo.cpp Disable (comment out) debug-info validation checks.
src/coreclr/jit/compiler.hpp Remove GT_RET_EXPR from operand visitation cases.
src/coreclr/jit/compiler.h Update signatures (gtSplitTree, inline helpers), add friendship and statement-list splicing APIs.
Comments suppressed due to low confidence (2)

src/coreclr/jit/inline.cpp:636

  • The comment for InlineResult::InlineResult still describes a "stmt" parameter, but the constructor now takes an InlineContext* instead. Update the parameter documentation to reflect the new signature to avoid confusion for future call sites.
// Arguments:
//   compiler      - the compiler instance examining a call for inlining
//   call          - the call in question
//   stmt          - statement containing the call (if known)
//   description   - string describing the context of the decision

src/coreclr/jit/importer.cpp:405

  • In impAppendStmt, the inner if (call->ShouldHaveRetBufArg()) is redundant because it's nested under if (call->TypeIs(TYP_VOID) && call->ShouldHaveRetBufArg()). As written, the else branch is unreachable; consider simplifying to a single path (or, if the two branches were meant to distinguish different shapes, adjust the condition so both are reachable as intended).
            if (call->TypeIs(TYP_VOID) && call->ShouldHaveRetBufArg())
            {
                GenTree* retBuf;
                if (call->ShouldHaveRetBufArg())
                {
                    assert(call->gtArgs.HasRetBuffer());
                    retBuf = call->gtArgs.GetRetBufferArg()->GetNode();
                }
                else
                {
                    assert(!call->gtArgs.HasThisPointer());
                    retBuf = call->gtArgs.GetArgByIndex(0)->GetNode();
                }

Copilot AI review requested due to automatic review settings February 25, 2026 21:43
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 20 out of 20 changed files in this pull request and generated 5 comments.

Comments suppressed due to low confidence (3)

src/coreclr/jit/inline.cpp:1314

  • Line 1314 contains a commented-out DebugInfo validation call that appears to be intentionally disabled. This should either be removed if no longer needed, or uncommented if the validation is important. The comment doesn't explain why it's disabled, which makes it unclear whether this is temporary debugging code or intentionally disabled for a reason.
    // DebugInfo(parentContext, context->m_Location).Validate();

src/coreclr/jit/indirectcalltransformer.cpp:997

  • On line 996, lvSingleDef is unconditionally set to false when doesReturnValue is true, but this is done after potentially demoting the call to non-inline in the if block above (lines 968-978). If the call was demoted and the statement was already added, we're still modifying lvSingleDef. While this may be correct (since multiple defs will exist after GDV transformation), the logic would be clearer if this was moved inside the else block starting at line 980 where we know the call remains an inline candidate.
            if (doesReturnValue)
            {
                compiler->lvaGetDesc(stmt->GetRootNode()->AsLclVarCommon())->lvSingleDef = false;
            }

src/coreclr/jit/importer.cpp:789

  • On line 785, the condition checks !srcCall->IsInlineCandidate() before marking the local as DNER. However, this means that if a call IS an inline candidate, the local won't be marked DNER even though it's being used as a hidden buffer struct arg. If the inline later fails, the local may still be in an enregisterable state which could cause issues. The old code didn't have this condition. Consider whether this check is correct or if it should be removed.
            if (destAddr->OperIs(GT_LCL_ADDR) && !srcCall->IsInlineCandidate())
            {
                lvaSetVarDoNotEnregister(destAddr->AsLclVarCommon()->GetLclNum()
                                             DEBUGARG(DoNotEnregisterReason::HiddenBufferStructArg));
            }

Comment on lines +594 to +599
// TODO: move to InlineInfo
struct InlineIRResult
{
GenTree* substExpr;
BasicBlock* substBB;
};
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The TODO comment "TODO: move to InlineInfo" on line 594 suggests this is incomplete work. The InlineIRResult struct should likely be moved into InlineInfo rather than being a separate struct that's embedded in InlineCandidateInfo. This would make the structure more logical since the result is specific to each inline attempt.

Copilot uses AI. Check for mistakes.
@jakobbotsch
Copy link
Member Author

jakobbotsch commented Feb 25, 2026

One source of regressions is that for stfld we create IR of the shape

[000011] nACXG---R--                           STORE_BLK struct<System.TimeSpan, 8> (copy)
[000010] ---X-------                         ├──▌  FIELD_ADDR byref  <unknown class>:<unknown field>
[000006] -----------                           └──▌  LCL_VAR   ref    V00 this         
[000009] I-C-G------                         └──▌  CALL      struct System.TimeSpan:FromSeconds(long):System.TimeSpan (exactContextHandle=0x00007FF94C1F7CE1)
[000008] ----------- arg0                       └──▌  CNS_INT   long   10

Note GTF_EXCEPT on [000010]. That's clearly wrong when [000009] is the data that came from the IL stack and should be evaluated before [000010].

We make up for it in morph since we usually fold the NRE side effect of the address computation into the store itself, and when we do that we end up evaluating the call first. But when the call is an inline candidate we now end up spilling [000010] when we split, whereas before it would be a RET_EXPR and the rest of the inlinee statements would end up before the entire store.

Some kind of fix is needed here (as a separate PR). We could set GTF_REVERSE_OPS on the store, but that is not something we typically do during import.

(this issue is similar to #77650)

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 20 out of 20 changed files in this pull request and generated 1 comment.

Copilot AI review requested due to automatic review settings February 26, 2026 13:58
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 20 out of 20 changed files in this pull request and generated 2 comments.

Comment on lines 1258 to 1260
// Detach the GT_CALL tree from the original statement by
// hanging a "nothing" node to it. Later the "nothing" node will be removed
// and the original GT_CALL tree will be picked up by the GT_RET_EXPR node.
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment describes the old GT_RET_EXPR-based detachment/reattachment flow ("picked up by the GT_RET_EXPR node"), but GT_RET_EXPR is removed in this PR and the substitution is now represented via InlineCandidateInfo::result. Please update the comment to reflect the new mechanism (or remove it) to avoid confusion when debugging inline failures.

Suggested change
// Detach the GT_CALL tree from the original statement by
// hanging a "nothing" node to it. Later the "nothing" node will be removed
// and the original GT_CALL tree will be picked up by the GT_RET_EXPR node.
// Mark this candidate as having no successful inline substitution by
// clearing the recorded inline result. This ensures the original GT_CALL
// remains in the IR instead of being replaced by an inlined expression.

Copilot uses AI. Check for mistakes.
Comment on lines +343 to +351
JITDUMP("Inlining candidate [%06u] in " FMT_STMT "\n", Compiler::dspTreeID(call), m_statement->GetID());
DISPSTMT(m_statement);

fgWalkResult PostOrderVisit(GenTree** use, GenTree* user)
{
LateDevirtualization(use, user);
return fgWalkResult::WALK_CONTINUE;
}
if (InsertMidStatement(inlineInfo, use))
{
m_nextBlock = m_block;
m_nextStatement = m_statement;
return true;
}
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On successful inlines (including the splitting/CFG rewrite paths), this method returns true without setting m_madeChanges. As a result, fgInline() can incorrectly return MODIFIED_NOTHING even though it mutated IR/CFG, which can break downstream phase gating and debug asserts. Set m_madeChanges = true whenever an inline succeeds/changes IR (e.g., before returning true from TryInline, and/or have fgInline() treat any restart as a modification).

Copilot uses AI. Check for mistakes.
jakobbotsch added a commit that referenced this pull request Mar 3, 2026
Add ILLocation field to LateDevirtualizationInfo, set from impCurStmtDI,
so the new statement created during late devirtualization carries proper
debug info. Remove LateDevirtualizationInfo::inlinersContext in favor of
GenTreeCall::gtInlineContext. Add assert verifying
GenTreeCall::gtInlineContext matches the inline context from
impCurStmtDI.

Noticed this while working on #124870.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings March 10, 2026 13:42
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 20 out of 20 changed files in this pull request and generated 8 comments.

Comment on lines 6966 to 6979
@@ -7059,8 +6976,6 @@

setMethodHasFatPointer();
call->SetFatPointerCandidate();
SpillRetExprHelper helper(this);
helper.StoreRetExprResultsInArgs(call);
}
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says we "spill inline candidates in the call node", but addFatPointerCandidate no longer does any spilling (the previous helper was removed). Either restore the needed spilling here, or update the comment to reflect where/when arguments are actually spilled now (e.g., during transformation).

Copilot uses AI. Check for mistakes.
Comment on lines 13050 to 13062
// pass the argument into the inlinee.

void Compiler::impInlineRecordArgInfo(InlineInfo* pInlineInfo,
CallArg* arg,
InlArgInfo* argInfo,
CallArg* arg,
InlineResult* inlineResult)
{
argInfo->arg = arg;
GenTree* curArgVal = arg->GetNode();

assert(!curArgVal->OperIs(GT_RET_EXPR));

GenTree* lclVarTree;
const bool isAddressInLocal = impIsAddressInLocal(curArgVal, &lclVarTree);
if (isAddressInLocal)
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

impInlineRecordArgInfo no longer supports GT_RET_EXPR (and the asserts referencing it were removed), but nearby documentation still describes special GT_RET_EXPR handling for arguments. Please update those comments to match the new inline-candidate representation so future maintenance/debugging isn't misleading.

Copilot uses AI. Check for mistakes.
Comment on lines +1273 to +1279
JITDUMP("Inline [%06u] marked as failed\n", dspTreeID(call));

// Detach the GT_CALL tree from the original statement by
// hanging a "nothing" node to it. Later the "nothing" node will be removed
// and the original GT_CALL tree will be picked up by the GT_RET_EXPR node.
inlCandInfo->retExpr->gtSubstExpr = call;
inlCandInfo->retExpr->gtSubstBB = compCurBB;

noway_assert(fgMorphStmt->GetRootNode() == call);
fgMorphStmt->SetRootNode(gtNewNothingNode());
inlCandInfo->result.substExpr = nullptr;
inlCandInfo->result.substBB = nullptr;
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This failure path comment still describes the old GT_RET_EXPR placeholder mechanics (detaching the call and later being picked up by a RET_EXPR). With RET_EXPR removed, this is misleading; please update the comment to describe the current substitution/inline-result flow (or remove it).

Copilot uses AI. Check for mistakes.
Comment on lines 1493 to 1511
//------------------------------------------------------------------------
// fgDebugCheckInlineCandidates: Callback to make sure there is no more
// GT_RET_EXPR and GTF_CALL_INLINE_CANDIDATE nodes.
//
// Arguments:
// pTree - pointer to the tree node being walked
// data - walk data
//
// Return Value:
// WALK_CONTINUE
//
// static
Compiler::fgWalkResult Compiler::fgDebugCheckInlineCandidates(GenTree** pTree, fgWalkData* data)
{
GenTree* tree = *pTree;
if (tree->OperIs(GT_CALL))
{
assert((tree->gtFlags & GTF_CALL_INLINE_CANDIDATE) == 0);
}
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment for fgDebugCheckInlineCandidates still references GT_RET_EXPR, but GT_RET_EXPR nodes appear to have been removed in this PR. Update the comment to reflect the new invariant being checked (e.g., no remaining GTF_CALL_INLINE_CANDIDATE calls in the IR).

Copilot uses AI. Check for mistakes.
Comment on lines +388 to 398
else if (expr->OperIs(GT_CALL)) // The special case of calls with return buffers.
{
GenTree* call = expr->OperIs(GT_RET_EXPR) ? expr->AsRetExpr()->gtInlineCandidate : expr;
GenTreeCall* call = expr->AsCall();

if (call->TypeIs(TYP_VOID) && call->AsCall()->ShouldHaveRetBufArg())
if (call->TypeIs(TYP_VOID) && call->ShouldHaveRetBufArg())
{
GenTree* retBuf;
if (call->AsCall()->ShouldHaveRetBufArg())
{
assert(call->AsCall()->gtArgs.HasRetBuffer());
retBuf = call->AsCall()->gtArgs.GetRetBufferArg()->GetNode();
}
else
{
assert(!call->AsCall()->gtArgs.HasThisPointer());
retBuf = call->AsCall()->gtArgs.GetArgByIndex(0)->GetNode();
}
assert(call->gtArgs.HasRetBuffer());
GenTree* retBuf = call->gtArgs.GetRetBufferArg()->GetNode();

assert(retBuf->TypeIs(TYP_I_IMPL, TYP_BYREF));

Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This block was updated to remove GT_RET_EXPR handling, but impAppendStmt still has a comment and a Statement* lastStmt = impLastStmt; variable later in the same function that were only needed for the old GT_RET_EXPR spill reordering. Consider removing that dead code and updating the comment to avoid build warnings and confusion.

Copilot uses AI. Check for mistakes.
Comment on lines 3486 to 3491
GenTree* allocBoxStore = gtNewTempStore(impBoxTemp, op1);
Statement* allocBoxStmt = impAppendTree(allocBoxStore, CHECK_SPILL_NONE, impCurStmtDI);

// If the exprToBox is a call that returns its value via a ret buf arg,
// move the store statement(s) before the call (which must be a top level tree).
//
// We do this because impStoreStructPtr (invoked below) will
// back-substitute into a call when it sees a GT_RET_EXPR and the call
// has a hidden buffer pointer, So we need to reorder things to avoid
// creating out-of-sequence IR.
//
if (varTypeIsStruct(exprToBox) && exprToBox->OperIs(GT_RET_EXPR))
{
GenTreeCall* const call = exprToBox->AsRetExpr()->gtInlineCandidate->AsCall();

// If the call was flagged for possible enumerator cloning, flag the allocation as well.
//
if (compIsForInlining() && hasImpEnumeratorGdvLocalMap())
{
NodeToUnsignedMap* const map = getImpEnumeratorGdvLocalMap();
unsigned enumeratorLcl = BAD_VAR_NUM;
GenTreeCall* const call = impInlineInfo->iciCall;
if (map->Lookup(call, &enumeratorLcl))
{
JITDUMP("Flagging [%06u] for enumerator cloning via V%02u\n", dspTreeID(op1), enumeratorLcl);
map->Remove(call);
map->Set(op1, enumeratorLcl);
}
}

if (call->ShouldHaveRetBufArg())
{
JITDUMP("Must insert newobj stmts for box before call [%06u]\n", dspTreeID(call));

// Walk back through the statements in this block, looking for the one
// that has this call as the root node.
//
// Because gtNewTempStore (above) may have added statements that
// feed into the actual store we need to move this set of added
// statements as a group.
//
// Note boxed allocations are side-effect free (no com or finalizer) so
// our only worries here are (correctness) not overlapping the box temp
// lifetime and (perf) stretching the temp lifetime across the inlinee
// body.
//
// Since this is an inline candidate, we must be optimizing, and so we have
// a unique box temp per call. So no worries about overlap.
//
assert(!opts.OptimizationDisabled());

// Lifetime stretching could addressed with some extra cleverness--sinking
// the allocation back down to just before the copy, once we figure out
// where the copy is. We defer for now.
//
Statement* insertBeforeStmt = cursor;
noway_assert(insertBeforeStmt != nullptr);

while (true)
{
if (insertBeforeStmt->GetRootNode() == call)
{
break;
}

// If we've searched all the statements in the block and failed to
// find the call, then something's wrong.
//
noway_assert(insertBeforeStmt != impStmtList);

insertBeforeStmt = insertBeforeStmt->GetPrevStmt();
}

// Found the call. Move the statements comprising the store.
//
JITDUMP("Moving " FMT_STMT "..." FMT_STMT " before " FMT_STMT "\n", cursor->GetNextStmt()->GetID(),
allocBoxStmt->GetID(), insertBeforeStmt->GetID());
assert(allocBoxStmt == impLastStmt);
do
{
Statement* movingStmt = impExtractLastStmt();
impInsertStmtBefore(movingStmt, insertBeforeStmt);
insertBeforeStmt = movingStmt;
} while (impLastStmt != cursor);
}
}

// Create a pointer to the box payload in op1.
//
op1 = gtNewLclvNode(impBoxTemp, TYP_REF);
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In impImportAndPushBox, the cursor variable (set to impLastStmt earlier) appears to be leftover from the removed GT_RET_EXPR/retbuf statement-reordering logic and is now unused. Please remove it (or reintroduce its use) to avoid unused-variable warnings in builds that treat warnings as errors.

Copilot uses AI. Check for mistakes.
Comment on lines 7931 to +7935
InlineResult inlineResult(this, call, nullptr, "impMarkInlineCandidate for GDV");

// Do the actual evaluation
impMarkInlineCandidateHelper(call, candidateId, exactContextHnd, exactContextNeedsRuntimeLookup, callInfo,
inlinersContext, &inlineResult);
inlinersContext, &inlineResult, debugInfo);
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

InlineResult is constructed with a null InlineContext here, so the inline policy can't see the caller's inline context (used by some debug/replay/data-collection policies). Since inlinersContext is already available and represents the caller context, consider passing it to InlineResult instead of nullptr.

Copilot uses AI. Check for mistakes.
Comment on lines 7957 to 7960
InlineResult inlineResult(this, call, nullptr, "impMarkInlineCandidate");
impMarkInlineCandidateHelper(call, 0, exactContextHnd, exactContextNeedsRuntimeLookup, callInfo,
inlinersContext, &inlineResult);
inlinersContext, &inlineResult, debugInfo);
}
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above: this InlineResult is created with a null InlineContext, which may limit inline policy diagnostics/replay in debug builds. Prefer passing the caller's inlinersContext when available.

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings March 10, 2026 17:21
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 20 out of 20 changed files in this pull request and generated 5 comments.

ClearFlag();
return;
}
assert(origCall->IsInlineCandidate());
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GuardedDevirtualizationTransformer::Run now asserts the call is an inline candidate. This can be false when CLFLG_INLINING is disabled (impMarkInlineCandidate returns early), while guarded devirtualization candidates can still be created, leading to checked-build asserts or release-only behavioral differences. Consider either (1) gating GDV candidate creation/transform on inlining being enabled, or (2) restoring the previous runtime bailout path that clears the GDV flag when the call isn't an inline candidate.

Suggested change
assert(origCall->IsInlineCandidate());
if (!origCall->IsInlineCandidate())
{
JITDUMP("Guarded devirtualization bailout: call is not an inline candidate; clearing GDV flag\n");
origCall->gtCallMoreFlags &= ~GTF_CALL_M_GUARDED_DEVIRT;
return;
}

Copilot uses AI. Check for mistakes.
Comment on lines 1345 to 1347
// Detach the GT_CALL tree from the original statement by
// hanging a "nothing" node to it. Later the "nothing" node will be removed
// and the original GT_CALL tree will be picked up by the GT_RET_EXPR node.
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This failure-path comment still refers to GT_RET_EXPR and detaching the call statement, but GT_RET_EXPR has been removed and the code no longer performs that detachment. Please update/remove the comment so it matches the new inlining pipeline (substExpr/substBB + call remaining in-tree on failure).

Suggested change
// Detach the GT_CALL tree from the original statement by
// hanging a "nothing" node to it. Later the "nothing" node will be removed
// and the original GT_CALL tree will be picked up by the GT_RET_EXPR node.
// Clear any prepared inline substitution so the original GT_CALL
// remains in the tree and executes normally. These fields are only
// used when inlining succeeds.

Copilot uses AI. Check for mistakes.
Comment on lines +2662 to 2663
// If there's a retExpr but no substBB, we assume the retExpr is a temp
// and so not interesting to instrumentation.
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The instrumentation comment still refers to a "retExpr" temp, but the code now uses InlineIRResult (substExpr/substBB) and there is no GT_RET_EXPR anymore. Please update the wording to avoid confusion about what is being temporarily linked into the return block.

Suggested change
// If there's a retExpr but no substBB, we assume the retExpr is a temp
// and so not interesting to instrumentation.
// If there's a substExpr but no substBB, the inlinee's return value is
// not currently linked into any block, so it is not interesting to instrument.

Copilot uses AI. Check for mistakes.
Comment on lines 9927 to 9932
// We can't safely clone calls that have GT_RET_EXPRs via gtCloneExpr.
// You must use gtCloneCandidateCall for these calls (and then do appropriate other fixup)
if (tree->AsCall()->IsInlineCandidate() || tree->AsCall()->IsGuardedDevirtualizationCandidate())
{
NO_WAY("Cloning of calls with associated GT_RET_EXPR nodes is not supported");
NO_WAY("Cloning of calls containing inline candidates is not supported");
}
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This clone failure message/comment still mentions GT_RET_EXPR even though GT_RET_EXPR was removed, and the NO_WAY text says "inline candidates" but also triggers for guarded devirtualization candidates. Update the comment and message to reflect the actual condition (inline candidate OR guarded devirt candidate) and remove the stale GT_RET_EXPR reference.

Copilot uses AI. Check for mistakes.
Comment on lines +395 to +413
void Compiler::fgInsertStmtListAtEnd(BasicBlock* block, Statement* stmtList)
{
if (stmtList == nullptr)
{
return;
}

Statement* firstStmt = block->firstStmt();
if (firstStmt != nullptr)
{
Statement* lastStmt = firstStmt->GetPrevStmt();
noway_assert(lastStmt != nullptr && lastStmt->GetNextStmt() == nullptr);

// Append the statement after the last one.
Statement* stmtLast = stmtList->GetPrevStmt();
lastStmt->SetNextStmt(stmtList);
stmtList->SetPrevStmt(lastStmt);
firstStmt->SetPrevStmt(stmtLast);
}
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fgInsertStmtListAtEnd/fgInsertStmtListBefore rely on stmtList being a well-formed circular statement list (head->GetPrevStmt() is the last statement and last->GetNextStmt()==nullptr), but unlike fgInsertStmtListAfter they don't assert those invariants. Adding noway_asserts (and optionally verifying before is in block) would help catch statement list corruption early.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants