From 5d4a5745df4aa8123bab33f113c52b83d19d1524 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Thu, 17 Apr 2025 13:55:09 -0700 Subject: [PATCH 1/2] JIT: boost inlining for methods that may return small arrays Look for inlinees that may be allocating and returning small fixed sized arrays. When inlined, these array allocations may end up non-escaping and be stack allocated. This analysis is approximate; we can't tell for sure in the IL scan what the array size is, and we can't easily tell if the allocated array is actually returned. Contributes to #113236 --- src/coreclr/jit/fgbasic.cpp | 41 +++++++++++++++++++++++++++----- src/coreclr/jit/inline.def | 1 + src/coreclr/jit/inlinepolicy.cpp | 11 +++++++++ src/coreclr/jit/inlinepolicy.h | 2 ++ 4 files changed, 49 insertions(+), 6 deletions(-) diff --git a/src/coreclr/jit/fgbasic.cpp b/src/coreclr/jit/fgbasic.cpp index 9905526766a9cf..2034a75cc25729 100644 --- a/src/coreclr/jit/fgbasic.cpp +++ b/src/coreclr/jit/fgbasic.cpp @@ -915,12 +915,14 @@ void Compiler::fgFindJumpTargets(const BYTE* codeAddr, IL_OFFSET codeSize, Fixed var_types varType = DUMMY_INIT(TYP_UNDEF); // TYP_ type bool typeIsNormed = false; FgStack pushedStack; - const bool isForceInline = (info.compFlags & CORINFO_FLG_FORCEINLINE) != 0; - const bool isInlining = compIsForInlining(); - unsigned retBlocks = 0; - int prefixFlags = 0; - bool preciseScan = makeInlineObservations && compInlineResult->GetPolicy()->RequiresPreciseScan(); - const bool resolveTokens = preciseScan; + const bool isForceInline = (info.compFlags & CORINFO_FLG_FORCEINLINE) != 0; + const bool isInlining = compIsForInlining(); + unsigned retBlocks = 0; + int prefixFlags = 0; + bool preciseScan = makeInlineObservations && compInlineResult->GetPolicy()->RequiresPreciseScan(); + const bool resolveTokens = preciseScan; + bool isReturnsArrayKnown = false; + bool returnsArray = false; // Track offsets where IL instructions begin in DEBUG builds. Used to // validate debug info generated by the JIT. @@ -2441,6 +2443,33 @@ void Compiler::fgFindJumpTargets(const BYTE* codeAddr, IL_OFFSET codeSize, Fixed retBlocks++; break; + case CEE_NEWARR: + + if (makeInlineObservations) + { + if (!isReturnsArrayKnown) + { + CORINFO_CLASS_HANDLE retClass = info.compMethodInfo->args.retTypeClass; + if (retClass != NO_CLASS_HANDLE) + { + uint32_t retClassAttribs = info.compCompHnd->getClassAttribs(retClass); + returnsArray = (retClassAttribs & CORINFO_FLG_ARRAY) != 0; + } + isReturnsArrayKnown = true; + } + + if (returnsArray && pushedStack.IsStackAtLeastOneDeep()) + { + FgStack::FgSlot slot0 = pushedStack.GetSlot0(); + + if (FgStack::IsConstantOrConstArg(slot0, impInlineInfo)) + { + compInlineResult->Note(InlineObservation::CALLEE_MAY_RETURN_SMALL_ARRAY); + } + } + } + break; + default: break; } diff --git a/src/coreclr/jit/inline.def b/src/coreclr/jit/inline.def index 44d6e83929e0ba..ba5e73626dce0e 100644 --- a/src/coreclr/jit/inline.def +++ b/src/coreclr/jit/inline.def @@ -101,6 +101,7 @@ INLINE_OBSERVATION(IS_SIZE_DECREASING_INLINE, bool, "size decreasing inline", INLINE_OBSERVATION(LOG_REPLAY_ACCEPT, bool, "accepted by log replay", INFORMATION, CALLEE) INLINE_OBSERVATION(LOOKS_LIKE_WRAPPER, bool, "thin wrapper around a call", INFORMATION, CALLEE) INLINE_OBSERVATION(MAXSTACK, int, "maxstack", INFORMATION, CALLEE) +INLINE_OBSERVATION(MAY_RETURN_SMALL_ARRAY, bool, "may return a small new array", INFORMATION, CALLEE) INLINE_OBSERVATION(OPCODE, int, "next opcode in IL stream", INFORMATION, CALLEE) INLINE_OBSERVATION(OPCODE_NORMED, int, "next opcode in IL stream", INFORMATION, CALLEE) INLINE_OBSERVATION(NUMBER_OF_ARGUMENTS, int, "number of arguments", INFORMATION, CALLEE) diff --git a/src/coreclr/jit/inlinepolicy.cpp b/src/coreclr/jit/inlinepolicy.cpp index 206e985f8a51b0..d852ce9d8c0bda 100644 --- a/src/coreclr/jit/inlinepolicy.cpp +++ b/src/coreclr/jit/inlinepolicy.cpp @@ -1341,6 +1341,10 @@ void ExtendedDefaultPolicy::NoteBool(InlineObservation obs, bool value) m_ArgUnboxExact++; break; + case InlineObservation::CALLEE_MAY_RETURN_SMALL_ARRAY: + m_MayReturnSmallArray = true; + break; + default: DefaultPolicy::NoteBool(obs, value); break; @@ -1776,6 +1780,12 @@ double ExtendedDefaultPolicy::DetermineMultiplier() } } + if (m_MayReturnSmallArray) + { + multiplier += 4.0; + JITDUMP("\nInline candidate may return small known-size array. Multiplier increased to %g.", multiplier); + } + if (m_HasProfileWeights) { // There are cases when Profile Data can be misleading or polluted: @@ -1889,6 +1899,7 @@ void ExtendedDefaultPolicy::OnDumpXml(FILE* file, unsigned indent) const XATTR_B(m_IsCallsiteInNoReturnRegion) XATTR_B(m_HasProfileWeights) XATTR_B(m_InsideThrowBlock) + XATTR_B(m_MayReturnSmallArray) } #endif diff --git a/src/coreclr/jit/inlinepolicy.h b/src/coreclr/jit/inlinepolicy.h index d08fbf7b32309c..4277a61fa78151 100644 --- a/src/coreclr/jit/inlinepolicy.h +++ b/src/coreclr/jit/inlinepolicy.h @@ -226,6 +226,7 @@ class ExtendedDefaultPolicy : public DefaultPolicy , m_NonGenericCallsGeneric(false) , m_IsCallsiteInNoReturnRegion(false) , m_HasProfileWeights(false) + , m_MayReturnSmallArray(false) { // Empty } @@ -281,6 +282,7 @@ class ExtendedDefaultPolicy : public DefaultPolicy bool m_NonGenericCallsGeneric : 1; bool m_IsCallsiteInNoReturnRegion : 1; bool m_HasProfileWeights : 1; + bool m_MayReturnSmallArray : 1; }; // DiscretionaryPolicy is a variant of the default policy. It From ee7fe32fc44c05adfcddb4237f54e92a5adf62d3 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Thu, 24 Apr 2025 13:44:08 -0700 Subject: [PATCH 2/2] fix --- src/coreclr/jit/fgbasic.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/coreclr/jit/fgbasic.cpp b/src/coreclr/jit/fgbasic.cpp index 2034a75cc25729..b3bcdbed1b3139 100644 --- a/src/coreclr/jit/fgbasic.cpp +++ b/src/coreclr/jit/fgbasic.cpp @@ -2449,11 +2449,14 @@ void Compiler::fgFindJumpTargets(const BYTE* codeAddr, IL_OFFSET codeSize, Fixed { if (!isReturnsArrayKnown) { - CORINFO_CLASS_HANDLE retClass = info.compMethodInfo->args.retTypeClass; - if (retClass != NO_CLASS_HANDLE) + if (info.compRetType == TYP_REF) { - uint32_t retClassAttribs = info.compCompHnd->getClassAttribs(retClass); - returnsArray = (retClassAttribs & CORINFO_FLG_ARRAY) != 0; + CORINFO_CLASS_HANDLE retClass = info.compMethodInfo->args.retTypeClass; + if (retClass != NO_CLASS_HANDLE) + { + uint32_t retClassAttribs = info.compCompHnd->getClassAttribs(retClass); + returnsArray = (retClassAttribs & CORINFO_FLG_ARRAY) != 0; + } } isReturnsArrayKnown = true; }