* Add a repro.
* Add a helper function `fgTryRemoveNonLocal`.
* Fix the issue.
* extract `gtChangeOperToNullCheck`.
* update comments.
return false;
}
+
+//------------------------------------------------------------------------------
+// gtChangeOperToNullCheck: helper to change tree oper to a NULLCHECK.
+//
+// Arguments:
+// tree - the node to change;
+// basicBlock - basic block of the node.
+//
+void Compiler::gtChangeOperToNullCheck(GenTree* tree, BasicBlock* block)
+{
+ assert(tree->OperIs(GT_FIELD, GT_IND, GT_OBJ, GT_BLK, GT_DYN_BLK));
+ tree->ChangeOper(GT_NULLCHECK);
+ tree->ChangeType(TYP_BYTE);
+ block->bbFlags |= BBF_HAS_NULLCHECK;
+ optMethodFlags |= OMF_HAS_NULLCHECK;
+}
GenTree* gtNewNullCheck(GenTree* addr, BasicBlock* basicBlock);
+ void gtChangeOperToNullCheck(GenTree* tree, BasicBlock* block);
+
GenTreeArgList* gtNewArgList(GenTree* op);
GenTreeArgList* gtNewArgList(GenTree* op1, GenTree* op2);
GenTreeArgList* gtNewArgList(GenTree* op1, GenTree* op2, GenTree* op3);
void fgComputeLifeLIR(VARSET_TP& life, BasicBlock* block, VARSET_VALARG_TP volatileVars);
+ bool fgTryRemoveNonLocal(GenTree* node, LIR::Range* blockRange);
+
bool fgRemoveDeadStore(GenTree** pTree,
LclVarDsc* varDsc,
VARSET_VALARG_TP life,
// For struct types read the first byte of the
// source struct; there's no need to read the
// entire thing, and no place to put it.
- assert(copySrc->gtOper == GT_OBJ || copySrc->gtOper == GT_IND || copySrc->gtOper == GT_FIELD);
+ assert(copySrc->OperIs(GT_OBJ, GT_IND, GT_FIELD));
copyStmt->SetRootNode(copySrc);
if (options == BR_REMOVE_AND_NARROW || options == BR_REMOVE_AND_NARROW_WANT_TYPE_HANDLE)
{
JITDUMP(" to read first byte of struct via modified [%06u]\n", dspTreeID(copySrc));
- copySrc->ChangeOper(GT_NULLCHECK);
- copySrc->gtType = TYP_BYTE;
- compCurBB->bbFlags |= BBF_HAS_NULLCHECK;
- optMethodFlags |= OMF_HAS_NULLCHECK;
+ gtChangeOperToNullCheck(copySrc, compCurBB);
}
else
{
if (m_compiler->gtNodeHasSideEffects(node, m_flags))
{
m_sideEffects.Push(node);
+ if (node->OperIsBlk() && !node->OperIsStoreBlk())
+ {
+ JITDUMP("Replace an unused OBJ/BLK node [%06d] with a NULLCHECK\n", dspTreeID(node));
+ m_compiler->gtChangeOperToNullCheck(node, m_compiler->compCurBB);
+ }
return Compiler::WALK_SKIP_SUBTREES;
}
// via an underlying address, just null check the address.
if (op1->OperIs(GT_FIELD, GT_IND, GT_OBJ))
{
- op1->ChangeOper(GT_NULLCHECK);
- block->bbFlags |= BBF_HAS_NULLCHECK;
- optMethodFlags |= OMF_HAS_NULLCHECK;
- op1->gtType = TYP_BYTE;
+ gtChangeOperToNullCheck(op1, block);
}
else
{
break;
case GT_NOP:
+ {
// NOTE: we need to keep some NOPs around because they are referenced by calls. See the dead store
// removal code above (case GT_STORE_LCL_VAR) for more explanation.
if ((node->gtFlags & GTF_ORDER_SIDEEFF) != 0)
{
break;
}
- __fallthrough;
+ fgTryRemoveNonLocal(node, &blockRange);
+ }
+ break;
- default:
- assert(!node->OperIsLocal());
- if (!node->IsValue() || node->IsUnusedValue())
+ case GT_BLK:
+ case GT_OBJ:
+ case GT_DYN_BLK:
+ {
+ bool removed = fgTryRemoveNonLocal(node, &blockRange);
+ if (!removed && node->IsUnusedValue())
{
- // We are only interested in avoiding the removal of nodes with direct side-effects
- // (as opposed to side effects of their children).
- // This default case should never include calls or assignments.
- assert(!node->OperRequiresAsgFlag() && !node->OperIs(GT_CALL));
- if (!node->gtSetFlags() && !node->OperMayThrow(this))
- {
- JITDUMP("Removing dead node:\n");
- DISPNODE(node);
-
- node->VisitOperands([](GenTree* operand) -> GenTree::VisitResult {
- operand->SetUnusedValue();
- return GenTree::VisitResult::Continue;
- });
-
- blockRange.Remove(node);
- }
+ // IR doesn't expect dummy uses of `GT_OBJ/BLK/DYN_BLK`.
+ JITDUMP("Replace an unused OBJ/BLK node [%06d] with a NULLCHECK\n", dspTreeID(node));
+ gtChangeOperToNullCheck(node, block);
}
+ }
+ break;
+
+ default:
+ fgTryRemoveNonLocal(node, &blockRange);
break;
}
}
}
+//---------------------------------------------------------------------
+// fgTryRemoveNonLocal - try to remove a node if it is unused and has no direct
+// side effects.
+//
+// Arguments
+// node - the non-local node to try;
+// blockRange - the block range that contains the node.
+//
+// Return value:
+// None
+//
+// Notes: local nodes are processed independently and are not expected in this function.
+//
+bool Compiler::fgTryRemoveNonLocal(GenTree* node, LIR::Range* blockRange)
+{
+ assert(!node->OperIsLocal());
+ if (!node->IsValue() || node->IsUnusedValue())
+ {
+ // We are only interested in avoiding the removal of nodes with direct side effects
+ // (as opposed to side effects of their children).
+ // This default case should never include calls or assignments.
+ assert(!node->OperRequiresAsgFlag() && !node->OperIs(GT_CALL));
+ if (!node->gtSetFlags() && !node->OperMayThrow(this))
+ {
+ JITDUMP("Removing dead node:\n");
+ DISPNODE(node);
+
+ node->VisitOperands([](GenTree* operand) -> GenTree::VisitResult {
+ operand->SetUnusedValue();
+ return GenTree::VisitResult::Continue;
+ });
+
+ blockRange->Remove(node);
+ return true;
+ }
+ }
+ return false;
+}
+
// fgRemoveDeadStore - remove a store to a local which has no exposed uses.
//
// pTree - GenTree** to local, including store-form local or local addr (post-rationalize)
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+// The test was deleting the hardware intrinsic leaving unconsumed GT_OBJ on top of the stack
+// that was leading to an assert failure.
+
+using System.Runtime.Intrinsics;
+using System.Runtime.Intrinsics.X86;
+using System;
+
+class Runtime_39403
+{
+ public static int Main()
+ {
+ if (Sse41.IsSupported)
+ {
+ Vector128<int> left = Vector128.Create(1);
+ Vector128<int> right = Vector128.Create(2);
+ ref var rightRef = ref right;
+ Vector128<int> mask = Vector128.Create(3);
+ Sse41.BlendVariable(left, rightRef, mask);
+ }
+ return 100;
+ }
+}
+
--- /dev/null
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ </PropertyGroup>
+ <PropertyGroup>
+ <DebugType />
+ <Optimize>True</Optimize>
+ <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
+ </PropertyGroup>
+ <ItemGroup>
+ <Compile Include="$(MSBuildProjectName).cs" />
+ </ItemGroup>
+</Project>