Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stack allocate unescaped boxes #103361

Merged
merged 50 commits into from
Jul 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
e333c8f
wip
AndyAyersMS May 20, 2021
5deb676
post rebase fixes
AndyAyersMS Jun 11, 2024
877927a
wip
AndyAyersMS Jun 11, 2024
0a7f3d4
revert layout changes, rely on KVP instead
AndyAyersMS Jun 11, 2024
56500c3
trying to enable promotion
AndyAyersMS Jun 12, 2024
f9e6e47
Small fixes
jakobbotsch Jun 12, 2024
67402d4
Update src/coreclr/jit/lclmorph.cpp
jakobbotsch Jun 12, 2024
5a08c84
Merge pull request #6 from jakobbotsch/stack-allocate-unescaped-boxes…
AndyAyersMS Jun 12, 2024
350d7ca
format
AndyAyersMS Jun 12, 2024
0328665
avoid escaping nullcheck addrs
AndyAyersMS Jun 12, 2024
315916d
fix nullcheck handling
AndyAyersMS Jun 13, 2024
91b83d3
fix colon retyping
AndyAyersMS Jun 13, 2024
bcc7c3a
metrics, enable for ref classes too
AndyAyersMS Jun 14, 2024
20a83b4
fix object stack allocation test
AndyAyersMS Jun 14, 2024
be657eb
merge main
AndyAyersMS Jun 14, 2024
d9d299c
handle not getting a class handle for a box
AndyAyersMS Jun 14, 2024
03ae7ee
Merge branch 'main' into StackAllocateUnescapedBoxes
AndyAyersMS Jun 15, 2024
cc1037e
don't recycle packet number
AndyAyersMS Jun 15, 2024
3964a09
fix nullcheck exposure in local morph
AndyAyersMS Jun 16, 2024
b1c986d
allow promotion of address-exposed stack allocated boxes
AndyAyersMS Jun 17, 2024
ed41d78
unbox helper only captures type
AndyAyersMS Jun 18, 2024
810abdf
merge main
AndyAyersMS Jun 18, 2024
536b2ae
restore lost guid update and related
AndyAyersMS Jun 18, 2024
5e9e3fb
boost inlining more if we pass a box as an arg
AndyAyersMS Jun 18, 2024
0685c58
try and fix promotion to write back if there are helper call struct uses
AndyAyersMS Jun 18, 2024
07f68ee
fix physical promotion writeback logic
AndyAyersMS Jun 19, 2024
2ae83e6
fix layout access for regular case
AndyAyersMS Jun 19, 2024
1cd1022
handle more address comparisons in local morph
AndyAyersMS Jun 19, 2024
40bb560
unbox type test helper
AndyAyersMS Jun 19, 2024
2beb6b3
rewrite unbox helper calls if we stack allocate the box
AndyAyersMS Jun 19, 2024
3522738
undo hacky changes to promotion
AndyAyersMS Jun 19, 2024
d88b4a9
more extensive rewriting -- anything that might be a stack allocated box
AndyAyersMS Jun 20, 2024
9c7885d
allow method table access for stack allocated objects
AndyAyersMS Jun 20, 2024
93a3f1d
Merge branch 'main' into StackAllocateUnescapedBoxes_UnboxHelper
AndyAyersMS Jun 20, 2024
06cbf8b
do indir/comma swapping in object allocator
AndyAyersMS Jun 20, 2024
946c780
fall back to relying on (lack of) exposure
AndyAyersMS Jun 21, 2024
e63a883
fix type test helper, add range control
AndyAyersMS Jun 21, 2024
41ea7bd
fix unbox helper re-write
AndyAyersMS Jun 22, 2024
337fc7e
need more capable clone
AndyAyersMS Jun 22, 2024
6cdefdf
report all object refs as byrefs for OSR (no retyping to nint)
AndyAyersMS Jun 23, 2024
2c3785e
disable ref class stack allocation for r2r/naot for now
AndyAyersMS Jun 23, 2024
7ca7027
minor TP improvement
AndyAyersMS Jun 24, 2024
351d484
dial back inliner
AndyAyersMS Jun 24, 2024
ccad461
fix spmi capture of ref class gclayout
AndyAyersMS Jun 26, 2024
14d2455
Use StackAllocatedBox<T>
AndyAyersMS Jun 26, 2024
8a077ea
Apply suggestions from code review
AndyAyersMS Jun 27, 2024
dc873e5
more review feedback
AndyAyersMS Jun 28, 2024
2597a44
fix
AndyAyersMS Jun 28, 2024
504012d
fix typo in comment
AndyAyersMS Jun 28, 2024
7af3d98
more surgical sibling type update for qmark/colon
AndyAyersMS Jun 28, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@
<Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\CastHelpers.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\ICastableHelpers.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\RuntimeHelpers.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\StackAllocatedBox.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\ControlledExecution.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\DependentHandle.cs" />
<Compile Include="$(MSBuildThisFileDirectory)..\nativeaot\Common\src\System\Runtime\RhFailFastReason.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Runtime.InteropServices;

namespace System.Runtime.CompilerServices
{
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct StackAllocatedBox<T>
{
// These fields are only accessed from jitted code
private MethodTable* _pMethodTable;
private T _value;
}
}
9 changes: 9 additions & 0 deletions src/coreclr/inc/corinfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,7 @@ enum CorInfoHelpFunc
CORINFO_HELP_BOX, // Fast box helper. Only possible exception is OutOfMemory
CORINFO_HELP_BOX_NULLABLE, // special form of boxing for Nullable<T>
CORINFO_HELP_UNBOX,
CORINFO_HELP_UNBOX_TYPETEST, // Verify unbox type, throws if incompatible
CORINFO_HELP_UNBOX_NULLABLE, // special form of unboxing for Nullable<T>
CORINFO_HELP_GETREFANY, // Extract the byref from a TypedReference, checking that it is the expected type

Expand Down Expand Up @@ -2552,6 +2553,14 @@ class ICorStaticInfo
CORINFO_CLASS_HANDLE cls
) = 0;

// Get a representation for a stack-allocated boxed value type.
//
// This differs from getTypeForBox in that it includes an explicit field
// for the method table pointer.
virtual CORINFO_CLASS_HANDLE getTypeForBoxOnStack(
CORINFO_CLASS_HANDLE cls
) = 0;

// returns the correct box helper for a particular class. Note
// that if this returns CORINFO_HELP_BOX, the JIT can assume
// 'standard' boxing (allocate object and copy), and optimize
Expand Down
3 changes: 3 additions & 0 deletions src/coreclr/inc/icorjitinfoimpl_generated.h
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,9 @@ CorInfoHelpFunc getSharedCCtorHelper(
CORINFO_CLASS_HANDLE getTypeForBox(
CORINFO_CLASS_HANDLE cls) override;

CORINFO_CLASS_HANDLE getTypeForBoxOnStack(
CORINFO_CLASS_HANDLE cls) override;

CorInfoHelpFunc getBoxHelper(
CORINFO_CLASS_HANDLE cls) override;

Expand Down
10 changes: 5 additions & 5 deletions src/coreclr/inc/jiteeversionguid.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@ typedef const GUID *LPCGUID;
#define GUID_DEFINED
#endif // !GUID_DEFINED

constexpr GUID JITEEVersionIdentifier = { /* e428e66d-5e0e-4320-ad8a-fa5a50f6da07 */
0xe428e66d,
0x5e0e,
0x4320,
{0xad, 0x8a, 0xfa, 0x5a, 0x50, 0xf6, 0xda, 0x07}
constexpr GUID JITEEVersionIdentifier = { /* 748cd07e-c686-4085-8f5f-54c1b9cff44c */
0x748cd07e,
0xc686,
0x4085,
{0x8f, 0x5f, 0x54, 0xc1, 0xb9, 0xcf, 0xf4, 0x4c}
};

//////////////////////////////////////////////////////////////////////////////////////////////////////////
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/inc/jithelpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@
DYNAMICJITHELPER(CORINFO_HELP_BOX, JIT_Box, CORINFO_HELP_SIG_REG_ONLY)
JITHELPER(CORINFO_HELP_BOX_NULLABLE, JIT_Box, CORINFO_HELP_SIG_REG_ONLY)
DYNAMICJITHELPER(CORINFO_HELP_UNBOX, NULL, CORINFO_HELP_SIG_REG_ONLY)
JITHELPER(CORINFO_HELP_UNBOX_TYPETEST, JIT_Unbox_TypeTest, CORINFO_HELP_SIG_REG_ONLY)
JITHELPER(CORINFO_HELP_UNBOX_NULLABLE, JIT_Unbox_Nullable, CORINFO_HELP_SIG_4_STACK)

JITHELPER(CORINFO_HELP_GETREFANY, JIT_GetRefAny, CORINFO_HELP_SIG_8_STACK)
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/ICorJitInfo_names_generated.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ DEF_CLR_API(getNewArrHelper)
DEF_CLR_API(getCastingHelper)
DEF_CLR_API(getSharedCCtorHelper)
DEF_CLR_API(getTypeForBox)
DEF_CLR_API(getTypeForBoxOnStack)
DEF_CLR_API(getBoxHelper)
DEF_CLR_API(getUnBoxHelper)
DEF_CLR_API(getRuntimeTypePointer)
Expand Down
9 changes: 9 additions & 0 deletions src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,15 @@ CORINFO_CLASS_HANDLE WrapICorJitInfo::getTypeForBox(
return temp;
}

CORINFO_CLASS_HANDLE WrapICorJitInfo::getTypeForBoxOnStack(
CORINFO_CLASS_HANDLE cls)
{
API_ENTER(getTypeForBoxOnStack);
CORINFO_CLASS_HANDLE temp = wrapHnd->getTypeForBoxOnStack(cls);
API_LEAVE(getTypeForBoxOnStack);
return temp;
}

CorInfoHelpFunc WrapICorJitInfo::getBoxHelper(
CORINFO_CLASS_HANDLE cls)
{
Expand Down
10 changes: 8 additions & 2 deletions src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -691,7 +691,8 @@ class LclVarDsc
unsigned char lvSingleDefDisqualifyReason = 'H';
#endif

unsigned char lvAllDefsAreNoGc : 1; // For pinned locals: true if all defs of this local are no-gc
unsigned char lvAllDefsAreNoGc : 1; // For pinned locals: true if all defs of this local are no-gc
unsigned char lvStackAllocatedBox : 1; // Local is a stack allocated box

#if FEATURE_MULTIREG_ARGS
regNumber lvRegNumForSlot(unsigned slotNum)
Expand Down Expand Up @@ -806,6 +807,11 @@ class LclVarDsc
return lvIsMultiRegArg || lvIsMultiRegRet;
}

bool IsStackAllocatedBox() const
{
return lvStackAllocatedBox;
}

#if defined(DEBUG)
private:
DoNotEnregisterReason m_doNotEnregReason;
Expand Down Expand Up @@ -3524,7 +3530,7 @@ class Compiler

GenTree* gtNewRuntimeLookup(CORINFO_GENERIC_HANDLE hnd, CorInfoGenericHandleType hndTyp, GenTree* lookupTree);

GenTreeIndir* gtNewMethodTableLookup(GenTree* obj);
GenTreeIndir* gtNewMethodTableLookup(GenTree* obj, bool onStack = false);

//------------------------------------------------------------------------
// Other GenTree functions
Expand Down
4 changes: 2 additions & 2 deletions src/coreclr/jit/compiler.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1872,9 +1872,9 @@ inline GenTreeCast* Compiler::gtNewCastNodeL(var_types typ, GenTree* op1, bool f
return cast;
}

inline GenTreeIndir* Compiler::gtNewMethodTableLookup(GenTree* object)
inline GenTreeIndir* Compiler::gtNewMethodTableLookup(GenTree* object, bool onStack)
{
assert(object->TypeIs(TYP_REF));
assert(onStack || object->TypeIs(TYP_REF));
GenTreeIndir* result = gtNewIndir(TYP_I_IMPL, object, GTF_IND_INVARIANT);
return result;
}
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/jit/inlinepolicy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1628,7 +1628,7 @@ double ExtendedDefaultPolicy::DetermineMultiplier()
//
// void Caller() => DoNothing(42); // 42 is going to be boxed at the call site.
//
multiplier += 0.5;
multiplier += 0.5 * m_ArgIsBoxedAtCallsite;
Copy link
Member

Choose a reason for hiding this comment

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

I suppose it's hard to check what the impact of this change is on its own.

Copy link
Member Author

Choose a reason for hiding this comment

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

I had it much more aggressive and it caused some gratuitous size regressions. This seems like a reasonable compromise -- most of the size regressions now are from exposed stack allocated objects.

JITDUMP("\nCallsite is going to box %d arguments. Multiplier increased to %g.", m_ArgIsBoxedAtCallsite,
multiplier);
}
Expand Down
5 changes: 4 additions & 1 deletion src/coreclr/jit/jitconfigvalues.h
Original file line number Diff line number Diff line change
Expand Up @@ -654,7 +654,10 @@ RELEASE_CONFIG_INTEGER(JitExtDefaultPolicyProfScale, W("JitExtDefaultPolicyProfS
RELEASE_CONFIG_INTEGER(JitInlinePolicyModel, W("JitInlinePolicyModel"), 0)
RELEASE_CONFIG_INTEGER(JitInlinePolicyProfile, W("JitInlinePolicyProfile"), 0)
RELEASE_CONFIG_INTEGER(JitInlinePolicyProfileThreshold, W("JitInlinePolicyProfileThreshold"), 40)
RELEASE_CONFIG_INTEGER(JitObjectStackAllocation, W("JitObjectStackAllocation"), 0)
CONFIG_STRING(JitObjectStackAllocationRange, W("JitObjectStackAllocationRange"))
RELEASE_CONFIG_INTEGER(JitObjectStackAllocation, W("JitObjectStackAllocation"), 1)
RELEASE_CONFIG_INTEGER(JitObjectStackAllocationRefClass, W("JitObjectStackAllocationRefClass"), 1)
RELEASE_CONFIG_INTEGER(JitObjectStackAllocationBoxedValueClass, W("JitObjectStackAllocationBoxedValueClass"), 1)

RELEASE_CONFIG_INTEGER(JitEECallTimingInfo, W("JitEECallTimingInfo"), 0)

Expand Down
4 changes: 4 additions & 0 deletions src/coreclr/jit/jitmetadatalist.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ JITMETADATAMETRIC(ProfileInconsistentInlineeScale, int, 0)
JITMETADATAMETRIC(ProfileInconsistentInlinee, int, 0)
JITMETADATAMETRIC(ProfileInconsistentNoReturnInlinee, int, 0)
JITMETADATAMETRIC(ProfileInconsistentMayThrowInlinee, int, 0)
JITMETADATAMETRIC(NewRefClassHelperCalls, int, 0)
JITMETADATAMETRIC(StackAllocatedRefClasses, int, 0)
JITMETADATAMETRIC(NewBoxedValueClassHelperCalls, int, 0)
JITMETADATAMETRIC(StackAllocatedBoxedValueClasses, int, 0)

#undef JITMETADATA
#undef JITMETADATAINFO
Expand Down
80 changes: 79 additions & 1 deletion src/coreclr/jit/lclmorph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,7 @@ class LocalEqualsLocalAddrAssertions
//
void OnExposed(unsigned lclNum)
{
JITDUMP("On exposed: V%02u\n", lclNum);
BitVecTraits localsTraits(m_comp->lvaCount, m_comp);
BitVecOps::AddElemD(&localsTraits, m_localsToExpose, lclNum);
}
Expand Down Expand Up @@ -514,6 +515,13 @@ class LocalAddressVisitor final : public GenTreeVisitor<LocalAddressVisitor>
return m_offset;
}

bool IsSameAddress(const Value& other) const
{
assert(IsAddress() && other.IsAddress());

return ((LclNum() == other.LclNum()) && (Offset() == other.Offset()));
}

//------------------------------------------------------------------------
// Address: Produce an address value from a LCL_ADDR node.
//
Expand Down Expand Up @@ -802,7 +810,6 @@ class LocalAddressVisitor final : public GenTreeVisitor<LocalAddressVisitor>
}

PushValue(use);

return Compiler::WALK_CONTINUE;
}

Expand Down Expand Up @@ -1024,6 +1031,77 @@ class LocalAddressVisitor final : public GenTreeVisitor<LocalAddressVisitor>
SequenceCall(node->AsCall());
break;

case GT_EQ:
case GT_NE:
{
// If we see &lcl EQ/NE null, rewrite to 0/1 comparison
// to reduce overall address exposure.
//
assert(TopValue(2).Node() == node);
assert(TopValue(1).Node() == node->AsOp()->gtOp1);
assert(TopValue(0).Node() == node->AsOp()->gtOp2);

Value& lhs = TopValue(1);
Value& rhs = TopValue(0);

if ((lhs.IsAddress() && rhs.Node()->IsIntegralConst(0)) ||
(rhs.IsAddress() && lhs.Node()->IsIntegralConst(0)))
{
JITDUMP("Rewriting known address vs null comparison [%06u]\n", m_compiler->dspTreeID(node));
*lhs.Use() = m_compiler->gtNewIconNode(0);
*rhs.Use() = m_compiler->gtNewIconNode(1);
m_stmtModified = true;

INDEBUG(TopValue(0).Consume());
INDEBUG(TopValue(1).Consume());
PopValue();
PopValue();
}
else if (lhs.IsAddress() && rhs.IsAddress())
{
JITDUMP("Rewriting known address vs address comparison [%06u]\n", m_compiler->dspTreeID(node));
bool isSameAddress = lhs.IsSameAddress(rhs);
*lhs.Use() = m_compiler->gtNewIconNode(0);
*rhs.Use() = m_compiler->gtNewIconNode(isSameAddress ? 0 : 1);
m_stmtModified = true;

INDEBUG(TopValue(0).Consume());
INDEBUG(TopValue(1).Consume());
PopValue();
PopValue();
}
else
{
EscapeValue(TopValue(0), node);
PopValue();
EscapeValue(TopValue(0), node);
PopValue();
}

break;
}

case GT_NULLCHECK:
{
assert(TopValue(1).Node() == node);
assert(TopValue(0).Node() == node->AsOp()->gtOp1);
Value& op = TopValue(0);
if (op.IsAddress())
{
JITDUMP("Bashing nullcheck of local [%06u] to NOP\n", m_compiler->dspTreeID(node));
node->gtBashToNOP();
INDEBUG(TopValue(0).Consume());
PopValue();
m_stmtModified = true;
}
else
{
EscapeValue(TopValue(0), node);
PopValue();
}
break;
}

default:
while (TopValue(0).Node() != node)
{
Expand Down
9 changes: 9 additions & 0 deletions src/coreclr/jit/lclvars.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2576,6 +2576,13 @@ bool Compiler::StructPromotionHelper::CanPromoteStructVar(unsigned lclNum)

if (varDsc->GetLayout()->IsBlockLayout())
{
JITDUMP(" struct promotion of V%02u is disabled because it has block layout\n", lclNum);
return false;
}

if (varDsc->lvStackAllocatedBox)
{
JITDUMP(" struct promotion of V%02u is disabled because it is a stack allocated box\n", lclNum);
return false;
}

Expand Down Expand Up @@ -3367,6 +3374,8 @@ bool Compiler::lvaIsLocalImplicitlyAccessedByRef(unsigned lclNum) const
// this information is already available on the CallArgABIInformation, and shouldn't need to be
// recomputed.
//
// Also seems like this info could be cached in the layout.
//
bool Compiler::lvaIsMultiregStruct(LclVarDsc* varDsc, bool isVarArg)
{
if (varTypeIsStruct(varDsc->TypeGet()))
Expand Down
Loading
Loading