Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 13 additions & 3 deletions src/coreclr/jit/hwintrinsiccodegenxarch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,19 @@ static void assertIsContainableHWIntrinsicOp(Lowering* lowering,
// spillage and for isUsedFromMemory contained nodes, in the case where the register allocator decided to not
// allocate a register in the first place).

GenTree* node = containedNode;
bool supportsRegOptional = false;
bool isContainable = lowering->TryGetContainableHWIntrinsicOp(containingNode, &node, &supportsRegOptional);
GenTree* node = containedNode;

// Now that we are doing full memory containment safety checks, we can't properly check nodes that are not
// linked into an evaluation tree, like the special nodes we create in genHWIntrinsic.
// So, just say those are ok.
//
if (node->gtNext == nullptr)
{
return;
}

bool supportsRegOptional = false;
bool isContainable = lowering->TryGetContainableHWIntrinsicOp(containingNode, &node, &supportsRegOptional);

assert(isContainable || supportsRegOptional);
assert(node == containedNode);
Expand Down
59 changes: 58 additions & 1 deletion src/coreclr/jit/lower.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,22 @@ void Lowering::MakeSrcContained(GenTree* parentNode, GenTree* childNode) const
assert(childNode->canBeContained());
childNode->SetContained();
assert(childNode->isContained());

#ifdef DEBUG
if (IsContainableMemoryOp(childNode))
{
// Verify caller of this method checked safety.
//
const bool isSafeToContainMem = IsSafeToContainMem(parentNode, childNode);

if (!isSafeToContainMem)
{
JITDUMP("** Unsafe mem containment of [%06u] in [%06u}, comp->dspTreeID(childNode), "
"comp->dspTreeID(parentNode)\n");
assert(isSafeToContainMem);
}
}
#endif
}

//------------------------------------------------------------------------
Expand Down Expand Up @@ -79,8 +95,15 @@ bool Lowering::CheckImmedAndMakeContained(GenTree* parentNode, GenTree* childNod
// Return value:
// true if it is safe to make childNode a contained memory operand.
//
bool Lowering::IsSafeToContainMem(GenTree* parentNode, GenTree* childNode)
bool Lowering::IsSafeToContainMem(GenTree* parentNode, GenTree* childNode) const
{
// Quick early-out for unary cases
//
if (childNode->gtNext == parentNode)
{
return true;
}

m_scratchSideEffects.Clear();
m_scratchSideEffects.AddNode(comp, childNode);

Expand All @@ -96,6 +119,40 @@ bool Lowering::IsSafeToContainMem(GenTree* parentNode, GenTree* childNode)
return true;
}

//------------------------------------------------------------------------
// IsSafeToContainMem: Checks for conflicts between childNode and grandParentNode
// and returns 'true' iff memory operand childNode can be contained in ancestorNode
//
// Arguments:
// grandParentNode - any non-leaf node
// parentNode - parent of `childNode` and an input to `grandParentNode`
// childNode - some node that is an input to `parentNode`
//
// Return value:
// true if it is safe to make childNode a contained memory operand.
//
Comment on lines +122 to +133
Copy link
Member

Choose a reason for hiding this comment

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

Seems like there's a few argument names here that need to be updated, ancestorNode and grandParentNode => grandparentNode?

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks -- will fix this subsequently so I don't retrigger all the tests.

bool Lowering::IsSafeToContainMem(GenTree* grandparentNode, GenTree* parentNode, GenTree* childNode) const
{
m_scratchSideEffects.Clear();
m_scratchSideEffects.AddNode(comp, childNode);

for (GenTree* node = childNode->gtNext; node != grandparentNode; node = node->gtNext)
{
if (node == parentNode)
{
continue;
}

const bool strict = true;
if (m_scratchSideEffects.InterferesWith(comp, node, strict))
{
return false;
}
}

return true;
}

//------------------------------------------------------------------------
// LowerNode: this is the main entry point for Lowering.
//
Expand Down
20 changes: 13 additions & 7 deletions src/coreclr/jit/lower.h
Original file line number Diff line number Diff line change
Expand Up @@ -560,14 +560,17 @@ class Lowering final : public Phase
bool IsContainableImmed(GenTree* parentNode, GenTree* childNode) const;

// Return true if 'node' is a containable memory op.
bool IsContainableMemoryOp(GenTree* node)
bool IsContainableMemoryOp(GenTree* node) const
{
return m_lsra->isContainableMemoryOp(node);
}

#ifdef FEATURE_HW_INTRINSICS
// Tries to get a containable node for a given HWIntrinsic
bool TryGetContainableHWIntrinsicOp(GenTreeHWIntrinsic* containingNode, GenTree** pNode, bool* supportsRegOptional);
bool TryGetContainableHWIntrinsicOp(GenTreeHWIntrinsic* containingNode,
GenTree** pNode,
bool* supportsRegOptional,
GenTreeHWIntrinsic* transparentParentNode = nullptr);
#endif // FEATURE_HW_INTRINSICS

static void TransformUnusedIndirection(GenTreeIndir* ind, Compiler* comp, BasicBlock* block);
Expand All @@ -585,7 +588,10 @@ class Lowering final : public Phase

// Checks for memory conflicts in the instructions between childNode and parentNode, and returns true if childNode
// can be contained.
bool IsSafeToContainMem(GenTree* parentNode, GenTree* childNode);
bool IsSafeToContainMem(GenTree* parentNode, GenTree* childNode) const;

// Similar to above, but allows bypassing a "transparent" parent.
bool IsSafeToContainMem(GenTree* grandparentNode, GenTree* parentNode, GenTree* childNode) const;

inline LIR::Range& BlockRange() const
{
Expand All @@ -609,10 +615,10 @@ class Lowering final : public Phase
}
}

LinearScan* m_lsra;
unsigned vtableCallTemp; // local variable we use as a temp for vtable calls
SideEffectSet m_scratchSideEffects; // SideEffectSet used for IsSafeToContainMem and isRMWIndirCandidate
BasicBlock* m_block;
LinearScan* m_lsra;
unsigned vtableCallTemp; // local variable we use as a temp for vtable calls
mutable SideEffectSet m_scratchSideEffects; // SideEffectSet used for IsSafeToContainMem and isRMWIndirCandidate
BasicBlock* m_block;
};

#endif // _LOWER_H_
45 changes: 36 additions & 9 deletions src/coreclr/jit/lowerxarch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4723,7 +4723,7 @@ void Lowering::ContainCheckDivOrMod(GenTreeOp* node)
#endif

// divisor can be an r/m, but the memory indirection must be of the same size as the divide
if (IsContainableMemoryOp(divisor) && (divisor->TypeGet() == node->TypeGet()))
if (IsContainableMemoryOp(divisor) && (divisor->TypeGet() == node->TypeGet()) && IsSafeToContainMem(node, divisor))
{
MakeSrcContained(node, divisor);
}
Expand Down Expand Up @@ -4853,7 +4853,7 @@ void Lowering::ContainCheckCast(GenTreeCast* node)
// U8 -> R8 conversion requires that the operand be in a register.
if (srcType != TYP_ULONG)
{
if (IsContainableMemoryOp(castOp) || castOp->IsCnsNonZeroFltOrDbl())
if ((IsContainableMemoryOp(castOp) && IsSafeToContainMem(node, castOp)) || castOp->IsCnsNonZeroFltOrDbl())
{
MakeSrcContained(node, castOp);
}
Expand Down Expand Up @@ -4949,7 +4949,7 @@ void Lowering::ContainCheckCompare(GenTreeOp* cmp)
// we can treat the MemoryOp as contained.
if (op1Type == op2Type)
{
if (IsContainableMemoryOp(op1))
if (IsContainableMemoryOp(op1) && IsSafeToContainMem(cmp, op1))
Copy link
Member

@tannergooding tannergooding Feb 5, 2022

Choose a reason for hiding this comment

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

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks, added checks.

I'll probably leave that last bit as is, IsContainableMemoryOp is not that expensive.

{
MakeSrcContained(cmp, op1);
}
Expand Down Expand Up @@ -5250,7 +5250,7 @@ void Lowering::ContainCheckBoundsChk(GenTreeBoundsChk* node)

if (node->GetIndex()->TypeGet() == node->GetArrayLength()->TypeGet())
{
if (IsContainableMemoryOp(other))
if (IsContainableMemoryOp(other) && IsSafeToContainMem(node, other))
{
MakeSrcContained(node, other);
}
Expand Down Expand Up @@ -5279,7 +5279,7 @@ void Lowering::ContainCheckIntrinsic(GenTreeOp* node)
{
GenTree* op1 = node->gtGetOp1();

if (IsContainableMemoryOp(op1) || op1->IsCnsNonZeroFltOrDbl())
if ((IsContainableMemoryOp(op1) && IsSafeToContainMem(node, op1)) || op1->IsCnsNonZeroFltOrDbl())
{
MakeSrcContained(node, op1);
}
Expand Down Expand Up @@ -5365,6 +5365,7 @@ void Lowering::ContainCheckSIMD(GenTreeSIMD* simdNode)
// [In/Out] pNode - The node to check and potentially replace with the containable node
// [Out] supportsRegOptional - On return, this will be true if 'containingNode' supports regOptional operands
// otherwise, false.
// [In] transparentParentNode - optional "transparent" intrinsic parent like CreateScalarUnsafe
//
// Return Value:
// true if 'node' is a containable by containingNode; otherwise, false.
Expand All @@ -5377,7 +5378,8 @@ void Lowering::ContainCheckSIMD(GenTreeSIMD* simdNode)
//
bool Lowering::TryGetContainableHWIntrinsicOp(GenTreeHWIntrinsic* containingNode,
GenTree** pNode,
bool* supportsRegOptional)
bool* supportsRegOptional,
GenTreeHWIntrinsic* transparentParentNode)
{
assert(containingNode != nullptr);
assert((pNode != nullptr) && (*pNode != nullptr));
Expand Down Expand Up @@ -5800,7 +5802,32 @@ bool Lowering::TryGetContainableHWIntrinsicOp(GenTreeHWIntrinsic* containingNode

if (!node->OperIsHWIntrinsic())
{
return supportsGeneralLoads && (IsContainableMemoryOp(node) || node->IsCnsNonZeroFltOrDbl());
bool canBeContained = false;

if (supportsGeneralLoads)
{
if (IsContainableMemoryOp(node))
{
// Code motion safety checks
//
if (transparentParentNode != nullptr)
{
canBeContained = IsSafeToContainMem(containingNode, transparentParentNode, node);
Copy link
Member

Choose a reason for hiding this comment

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

We don't need this for the Load* intrinsic cases below because its known to always be safe, is that right?

Copy link
Member Author

@AndyAyersMS AndyAyersMS Feb 5, 2022

Choose a reason for hiding this comment

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

I think so, yes. But not 100% sure.

By my understanding, for unary containing nodes, the safety check should immediately return safe, as the child's gtNext is the node, so there is nothing "in between" that can interfere. For higher arity operations the children can interfere and also there can be other interference in between them from COMMA expansion.

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'm going to add a quick early out to IsSafeToContainMem for the unary node case, so we are less tempted to optimize out the safety check call and possibly miss something.

Copy link
Contributor

Choose a reason for hiding this comment

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

By my understanding, for unary containing nodes, the safety check should immediately return safe, as the child's gtNext is the node, so there is nothing "in between" that can interfere

Do you mean that for unary operators operand->gtNext == parent? But that's not true for the linear order.

t1 = IND(addr)
     STOREIND(addr, ...) // Updates [addr] to t2
     UNARY_USER(t1) // Should observe t1, not t2.

Is valid LIR.

Copy link
Member Author

Choose a reason for hiding this comment

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

Do you know of any way we'd create such a pattern? It would be nice to have more examples that fail if we mess this up.

At any rate, best not to be too clever here. With the added safety calls and follow-up check in MakeSrcContained we should have things covered, I hope.

Copy link
Contributor

Choose a reason for hiding this comment

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

Do you know of any way we'd create such a pattern?

Yep:

private static uint Problem(uint a, uint* b)
{
    uint zero = 0;
    return *b + Bmi2.MultiplyNoFlags(a, a, b) * zero;
}
N003 (???,???) [000015] ------------                 IL_OFFSET void   INLRT @ 0x002[E-] REG NA
N005 (  1,  1) [000003] -----------Z         t3 =    LCL_VAR   int    V01 arg1         u:1 edx REG edx $81
                                                  /--*  t3     int
N007 (  3,  2) [000004] *--XG-------         t4 = *  IND       int    REG eax <l:$1c1, c:$1c0>
N009 (  1,  1) [000005] ------------         t5 =    LCL_VAR   int    V00 arg0         u:1 ecx REG ecx $80
N011 (  1,  1) [000006] ------------         t6 =    LCL_VAR   int    V00 arg0         u:1 ecx (last use) REG ecx $80
N013 (  1,  1) [000007] -----------z         t7 =    LCL_VAR   int    V01 arg1         u:1 esi (last use) REG esi $81
                                                  /--*  t5     int
                                                  +--*  t6     int
                                                  +--*  t7     int
N015 (  4,  4) [000008] ---XG-------         t8 = *  HWINTRINSIC int     MultiplyNoFlags REG edx $83
                                                  /--*  t4     int
N017 ( 10,  9) [000012] ---XG-------              *  RETURN    int    REG NA $181

}
else
{
canBeContained = IsSafeToContainMem(containingNode, node);
}
}
else if (node->IsCnsNonZeroFltOrDbl())
{
// Always safe.
//
canBeContained = true;
}
}

return canBeContained;
}

// TODO-XArch: Update this to be table driven, if possible.
Expand All @@ -5821,7 +5848,7 @@ bool Lowering::TryGetContainableHWIntrinsicOp(GenTreeHWIntrinsic* containingNode
GenTree* op1 = hwintrinsic->Op(1);
bool op1SupportsRegOptional = false;

if (!TryGetContainableHWIntrinsicOp(containingNode, &op1, &op1SupportsRegOptional))
if (!TryGetContainableHWIntrinsicOp(containingNode, &op1, &op1SupportsRegOptional, hwintrinsic))
{
return false;
}
Expand Down Expand Up @@ -6287,7 +6314,7 @@ void Lowering::ContainCheckHWIntrinsic(GenTreeHWIntrinsic* node)
MakeSrcContained(node, op2);
}

if (IsContainableMemoryOp(op1))
if (IsContainableMemoryOp(op1) && IsSafeToContainMem(node, op1))
{
MakeSrcContained(node, op1);

Expand Down
13 changes: 12 additions & 1 deletion src/coreclr/jit/sideeffects.h
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,18 @@ class AliasSet final

inline bool WritesAnyLocation() const
{
return (m_flags & (ALIAS_WRITES_ADDRESSABLE_LOCATION | ALIAS_WRITES_LCL_VAR)) != 0;
if ((m_flags & ALIAS_WRITES_ADDRESSABLE_LOCATION) != 0)
{
return true;
}

if ((m_flags & ALIAS_WRITES_LCL_VAR) != 0)
{
LclVarDsc* const varDsc = m_compiler->lvaGetDesc(LclNum());
return varDsc->IsAlwaysAliveInMemory();
}

return false;
}
};

Expand Down
42 changes: 42 additions & 0 deletions src/tests/JIT/opt/ForwardSub/andnotcontained.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

// Generated by Fuzzlyn v1.5 on 2022-02-04 12:51:20
// Run on X86 Windows
// Seed: 5821979800164656837
// Reduced from 60.0 KiB to 0.3 KiB in 00:01:44
// Debug: Outputs 0
// Release: Outputs -1
public class Program
{
public static IRT s_rt;
public static long s_4;
public static int Main()
{
s_rt = new C();
int vr3 = (int)(s_4 & ~M7());
s_rt.WriteLine(vr3);
return vr3 + 100;
}

public static short M7()
{
ref long var1 = ref s_4;
var1 = 9223372036854775807L;
return 0;
}
}


public interface IRT
{
void WriteLine<T>(T value);
}

public class C : IRT
{
public void WriteLine<T>(T value)
{
System.Console.WriteLine(value);
}
}
10 changes: 10 additions & 0 deletions src/tests/JIT/opt/ForwardSub/andnotcontained.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<DebugType />
<Optimize>True</Optimize>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildProjectName).cs" />
</ItemGroup>
</Project>
56 changes: 56 additions & 0 deletions src/tests/JIT/opt/ForwardSub/lowerContainCheckCompare.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

// Generated by Fuzzlyn v1.5 on 2022-02-04 13:20:27
// Run on X64 Windows
// Seed: 7242107031351932946
// Reduced from 319.7 KiB to 0.7 KiB in 00:09:00
// Debug:
// Release: Outputs 0
public class C0
{
public byte F1;
}

public struct S0
{
public C0 F0;
public S0(C0 f0): this()
{
F0 = f0;
}
}

public class C1
{
public long F0;
public C1(long f0)
{
F0 = f0;
}
}

public class Program
{
public static S0 s_25 = new S0(new C0());
public static C1[] s_28;
public static int Main()
{
int result = 100;
s_28 = new C1[]{new C1(0)};
var vr3 = new C1(-1);
if (vr3.F0 >= (M35(vr3) & 0))
{
System.Console.WriteLine(vr3.F0);
result = -1;
}

return result;
}

public static byte M35(C1 argThis)
{
argThis.F0 = s_28[0].F0;
return s_25.F0.F1;
}
}
Loading