summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrian Sullivan <briansul@microsoft.com>2018-04-11 17:38:21 -0700
committerGitHub <noreply@github.com>2018-04-11 17:38:21 -0700
commit01690dfa800d07b0455b02fa93f792c282dd7c47 (patch)
tree9bb68f2362081b8f5ab0d195ec393c3f5b3ccf7d
parent13528d6ddcebfc6724c31c597efb5173f6eed781 (diff)
parentf9a92757413250fb6ef5675ddab8376231d575ab (diff)
downloadcoreclr-01690dfa800d07b0455b02fa93f792c282dd7c47.tar.gz
coreclr-01690dfa800d07b0455b02fa93f792c282dd7c47.tar.bz2
coreclr-01690dfa800d07b0455b02fa93f792c282dd7c47.zip
Merge pull request #17464 from briansull/wip-17435
Fix for issue #17435 - Incorrect CSE with SSE Hardware intrinsics
-rw-r--r--src/jit/compiler.h6
-rw-r--r--src/jit/gentree.cpp188
-rw-r--r--src/jit/gentree.h40
-rw-r--r--src/jit/hwintrinsicxarch.cpp22
-rw-r--r--src/jit/liveness.cpp21
-rw-r--r--src/jit/valuenum.cpp11
-rw-r--r--tests/src/JIT/HardwareIntrinsics/X86/Regression/GitHub_17435/GitHub_17435.cs62
-rw-r--r--tests/src/JIT/HardwareIntrinsics/X86/Regression/GitHub_17435/GitHub_17435.csproj34
8 files changed, 350 insertions, 34 deletions
diff --git a/src/jit/compiler.h b/src/jit/compiler.h
index a39f714d9e..ebaf58e3c6 100644
--- a/src/jit/compiler.h
+++ b/src/jit/compiler.h
@@ -3100,10 +3100,14 @@ protected:
bool isScalarISA(InstructionSet isa);
static int ivalOfHWIntrinsic(NamedIntrinsic intrinsic);
unsigned simdSizeOfHWIntrinsic(NamedIntrinsic intrinsic, CORINFO_SIG_INFO* sig);
- static int numArgsOfHWIntrinsic(GenTreeHWIntrinsic* node);
static GenTree* lastOpOfHWIntrinsic(GenTreeHWIntrinsic* node, int numArgs);
static instruction insOfHWIntrinsic(NamedIntrinsic intrinsic, var_types type);
+
+public:
static HWIntrinsicCategory categoryOfHWIntrinsic(NamedIntrinsic intrinsic);
+ static int numArgsOfHWIntrinsic(GenTreeHWIntrinsic* node);
+
+protected:
static HWIntrinsicFlag flagsOfHWIntrinsic(NamedIntrinsic intrinsic);
GenTree* getArgForHWIntrinsic(var_types argType, CORINFO_CLASS_HANDLE argClass);
static int immUpperBoundOfHWIntrinsic(NamedIntrinsic intrinsic);
diff --git a/src/jit/gentree.cpp b/src/jit/gentree.cpp
index 4614df0e37..d3234036e3 100644
--- a/src/jit/gentree.cpp
+++ b/src/jit/gentree.cpp
@@ -2118,6 +2118,7 @@ AGAIN:
case GT_SIMD:
hash += tree->gtSIMD.gtSIMDIntrinsicID;
hash += tree->gtSIMD.gtSIMDBaseType;
+ hash += tree->gtSIMD.gtSIMDSize;
break;
#endif // FEATURE_SIMD
@@ -2125,6 +2126,7 @@ AGAIN:
case GT_HWIntrinsic:
hash += tree->gtHWIntrinsic.gtHWIntrinsicId;
hash += tree->gtHWIntrinsic.gtSIMDBaseType;
+ hash += tree->gtHWIntrinsic.gtSIMDSize;
break;
#endif // FEATURE_HW_INTRINSICS
@@ -5992,8 +5994,66 @@ GenTree* GenTree::gtGetParent(GenTree*** parentChildPtrPtr) const
bool GenTree::OperRequiresAsgFlag()
{
- return (OperIsAssignment() || (gtOper == GT_XADD) || (gtOper == GT_XCHG) || (gtOper == GT_LOCKADD) ||
- (gtOper == GT_CMPXCHG) || (gtOper == GT_MEMORYBARRIER));
+ if (OperIsAssignment() || OperIs(GT_XADD, GT_XCHG, GT_LOCKADD, GT_CMPXCHG, GT_MEMORYBARRIER))
+ {
+ return true;
+ }
+#ifdef FEATURE_HW_INTRINSICS
+ if (gtOper == GT_HWIntrinsic)
+ {
+ GenTreeHWIntrinsic* hwIntrinsicNode = this->AsHWIntrinsic();
+ if (hwIntrinsicNode->OperIsMemoryStore())
+ {
+ // A MemoryStore operation is an assignment
+ return true;
+ }
+ }
+#endif // FEATURE_HW_INTRINSICS
+ return false;
+}
+
+//------------------------------------------------------------------------------
+// OperIsImplicitIndir : Check whether the operation contains an implicit
+// indirection.
+// Arguments:
+// this - a GenTree node
+//
+// Return Value:
+// True if the given node contains an implicit indirection
+//
+// Note that for the GT_HWIntrinsic node we have to examine the
+// details of the node to determine its result.
+//
+
+bool GenTree::OperIsImplicitIndir() const
+{
+ switch (gtOper)
+ {
+ case GT_LOCKADD:
+ case GT_XADD:
+ case GT_XCHG:
+ case GT_CMPXCHG:
+ case GT_BLK:
+ case GT_OBJ:
+ case GT_DYN_BLK:
+ case GT_STORE_BLK:
+ case GT_STORE_OBJ:
+ case GT_STORE_DYN_BLK:
+ case GT_BOX:
+ case GT_ARR_INDEX:
+ case GT_ARR_ELEM:
+ case GT_ARR_OFFSET:
+ return true;
+#ifdef FEATURE_HW_INTRINSICS
+ case GT_HWIntrinsic:
+ {
+ GenTreeHWIntrinsic* hwIntrinsicNode = (const_cast<GenTree*>(this))->AsHWIntrinsic();
+ return hwIntrinsicNode->OperIsMemoryLoadOrStore();
+ }
+#endif // FEATURE_HW_INTRINSICS
+ default:
+ return false;
+ }
}
//------------------------------------------------------------------------------
@@ -6079,6 +6139,22 @@ bool GenTree::OperMayThrow(Compiler* comp)
#endif // FEATURE_HW_INTRINSICS
case GT_INDEX_ADDR:
return true;
+
+#ifdef FEATURE_HW_INTRINSICS
+ case GT_HWIntrinsic:
+ {
+ GenTreeHWIntrinsic* hwIntrinsicNode = this->AsHWIntrinsic();
+ assert(hwIntrinsicNode != nullptr);
+ if (hwIntrinsicNode->OperIsMemoryStore() || hwIntrinsicNode->OperIsMemoryLoad())
+ {
+ // This operation contains an implicit indirection
+ // it could throw a null reference exception.
+ //
+ return true;
+ }
+ }
+#endif // FEATURE_HW_INTRINSICS
+
default:
break;
}
@@ -18151,6 +18227,114 @@ GenTree* Compiler::gtNewMustThrowException(unsigned helper, var_types type, CORI
}
return node;
}
+
+// Returns true for the HW Instrinsic instructions that have MemoryLoad semantics, false otherwise
+bool GenTreeHWIntrinsic::OperIsMemoryLoad()
+{
+#ifdef _TARGET_XARCH_
+ // Some xarch instructions have MemoryLoad sematics
+ HWIntrinsicCategory category = Compiler::categoryOfHWIntrinsic(gtHWIntrinsicId);
+ if (category == HW_Category_MemoryLoad)
+ {
+ return true;
+ }
+ else if (category == HW_Category_IMM)
+ {
+ // Some AVX instructions here also have MemoryLoad sematics
+
+ // Do we have 3 operands?
+ if (Compiler::numArgsOfHWIntrinsic(this) != 3)
+ {
+ return false;
+ }
+ else // We have 3 operands/args
+ {
+ GenTreeArgList* argList = gtOp.gtOp1->AsArgList();
+
+ if ((gtHWIntrinsicId == NI_AVX_InsertVector128 || gtHWIntrinsicId == NI_AVX2_InsertVector128) &&
+ (argList->Rest()->Current()->TypeGet() == TYP_I_IMPL)) // Is the type of the second arg TYP_I_IMPL?
+ {
+ // This is Avx/Avx2.InsertVector128
+ return true;
+ }
+ }
+ }
+#endif // _TARGET_XARCH_
+ return false;
+}
+
+// Returns true for the HW Instrinsic instructions that have MemoryStore semantics, false otherwise
+bool GenTreeHWIntrinsic::OperIsMemoryStore()
+{
+#ifdef _TARGET_XARCH_
+ // Some xarch instructions have MemoryStore sematics
+ HWIntrinsicCategory category = Compiler::categoryOfHWIntrinsic(gtHWIntrinsicId);
+ if (category == HW_Category_MemoryStore)
+ {
+ return true;
+ }
+ else if (category == HW_Category_IMM)
+ {
+ // Some AVX instructions here also have MemoryStore sematics
+
+ // Do we have 3 operands?
+ if (Compiler::numArgsOfHWIntrinsic(this) != 3)
+ {
+ return false;
+ }
+ else // We have 3 operands/args
+ {
+ if ((gtHWIntrinsicId == NI_AVX_ExtractVector128 || gtHWIntrinsicId == NI_AVX2_ExtractVector128))
+ {
+ // This is Avx/Avx2.ExtractVector128
+ return true;
+ }
+ }
+ }
+#endif // _TARGET_XARCH_
+ return false;
+}
+
+// Returns true for the HW Instrinsic instructions that have MemoryLoad semantics, false otherwise
+bool GenTreeHWIntrinsic::OperIsMemoryLoadOrStore()
+{
+#ifdef _TARGET_XARCH_
+ // Some xarch instructions have MemoryLoad sematics
+ HWIntrinsicCategory category = Compiler::categoryOfHWIntrinsic(gtHWIntrinsicId);
+ if ((category == HW_Category_MemoryLoad) || (category == HW_Category_MemoryStore))
+ {
+ return true;
+ }
+ else if (category == HW_Category_IMM)
+ {
+ // Some AVX instructions here also have MemoryLoad or MemoryStore sematics
+
+ // Do we have 3 operands?
+ if (Compiler::numArgsOfHWIntrinsic(this) != 3)
+ {
+ return false;
+ }
+ else // We have 3 operands/args
+ {
+ GenTreeArgList* argList = gtOp.gtOp1->AsArgList();
+
+ if ((gtHWIntrinsicId == NI_AVX_InsertVector128 || gtHWIntrinsicId == NI_AVX2_InsertVector128) &&
+ (argList->Rest()->Current()->TypeGet() == TYP_I_IMPL)) // Is the type of the second arg TYP_I_IMPL?
+ {
+ // This is Avx/Avx2.InsertVector128
+ return true;
+ }
+ else if ((gtHWIntrinsicId == NI_AVX_ExtractVector128 || gtHWIntrinsicId == NI_AVX2_ExtractVector128))
+ {
+ // This is Avx/Avx2.ExtractVector128
+ return true;
+ }
+ }
+ }
+#endif // _TARGET_XARCH_
+ return false;
+}
+
#endif // FEATURE_HW_INTRINSICS
//---------------------------------------------------------------------------------------
diff --git a/src/jit/gentree.h b/src/jit/gentree.h
index 4ed7cf0461..8b9198fd13 100644
--- a/src/jit/gentree.h
+++ b/src/jit/gentree.h
@@ -1553,34 +1553,7 @@ public:
return OperIsIndirOrArrLength(gtOper);
}
- static bool OperIsImplicitIndir(genTreeOps gtOper)
- {
- switch (gtOper)
- {
- case GT_LOCKADD:
- case GT_XADD:
- case GT_XCHG:
- case GT_CMPXCHG:
- case GT_BLK:
- case GT_OBJ:
- case GT_DYN_BLK:
- case GT_STORE_BLK:
- case GT_STORE_OBJ:
- case GT_STORE_DYN_BLK:
- case GT_BOX:
- case GT_ARR_INDEX:
- case GT_ARR_ELEM:
- case GT_ARR_OFFSET:
- return true;
- default:
- return false;
- }
- }
-
- bool OperIsImplicitIndir() const
- {
- return OperIsImplicitIndir(gtOper);
- }
+ bool OperIsImplicitIndir() const;
bool OperIsStore() const
{
@@ -4288,6 +4261,17 @@ struct GenTreeHWIntrinsic : public GenTreeJitIntrinsic
{
}
+ // Note that HW Instrinsic instructions are a sub class of GenTreeOp which only supports two operands
+ // However there are HW Instrinsic instructions that have 3 or even 4 operands and this is
+ // supported using a single op1 and using an ArgList for it: gtNewArgList(op1, op2, op3)
+
+ bool OperIsMemoryLoad(); // Returns true for the HW Instrinsic instructions that have MemoryLoad semantics,
+ // false otherwise
+ bool OperIsMemoryStore(); // Returns true for the HW Instrinsic instructions that have MemoryStore semantics,
+ // false otherwise
+ bool OperIsMemoryLoadOrStore(); // Returns true for the HW Instrinsic instructions that have MemoryLoad or
+ // MemoryStore semantics, false otherwise
+
#if DEBUGGABLE_GENTREE
GenTreeHWIntrinsic() : GenTreeJitIntrinsic()
{
diff --git a/src/jit/hwintrinsicxarch.cpp b/src/jit/hwintrinsicxarch.cpp
index 9edf5facf1..002a22026c 100644
--- a/src/jit/hwintrinsicxarch.cpp
+++ b/src/jit/hwintrinsicxarch.cpp
@@ -816,9 +816,9 @@ GenTree* Compiler::impHWIntrinsic(NamedIntrinsic intrinsic,
assert(insOfHWIntrinsic(intrinsic, baseType) != INS_invalid);
assert(simdSize == 32 || simdSize == 16);
- GenTree* retNode = nullptr;
- GenTree* op1 = nullptr;
- GenTree* op2 = nullptr;
+ GenTreeHWIntrinsic* retNode = nullptr;
+ GenTree* op1 = nullptr;
+ GenTree* op2 = nullptr;
switch (numArgs)
{
@@ -865,6 +865,22 @@ GenTree* Compiler::impHWIntrinsic(NamedIntrinsic intrinsic,
default:
unreached();
}
+
+ bool isMemoryStore = retNode->OperIsMemoryStore();
+ if (isMemoryStore || retNode->OperIsMemoryLoad())
+ {
+ if (isMemoryStore)
+ {
+ // A MemoryStore operation is an assignment
+ retNode->gtFlags |= GTF_ASG;
+ }
+
+ // This operation contains an implicit indirection
+ // it could point into the gloabal heap or
+ // it could throw a null reference exception.
+ //
+ retNode->gtFlags |= (GTF_GLOB_REF | GTF_EXCEPT);
+ }
return retNode;
}
diff --git a/src/jit/liveness.cpp b/src/jit/liveness.cpp
index 7ae9487ecd..a4ef055782 100644
--- a/src/jit/liveness.cpp
+++ b/src/jit/liveness.cpp
@@ -329,6 +329,27 @@ void Compiler::fgPerNodeLocalVarLiveness(GenTree* tree)
fgCurMemoryDef |= memoryKindSet(GcHeap, ByrefExposed);
break;
+#ifdef FEATURE_HW_INTRINSICS
+ case GT_HWIntrinsic:
+ {
+ GenTreeHWIntrinsic* hwIntrinsicNode = tree->AsHWIntrinsic();
+
+ // We can't call fgMutateGcHeap unless the block has recorded a MemoryDef
+ //
+ if (hwIntrinsicNode->OperIsMemoryStore())
+ {
+ // We currently handle this like a Volatile store, so it counts as a definition of GcHeap/ByrefExposed
+ fgCurMemoryDef |= memoryKindSet(GcHeap, ByrefExposed);
+ }
+ if (hwIntrinsicNode->OperIsMemoryLoad())
+ {
+ // This instruction loads from memory and we need to record this information
+ fgCurMemoryUse |= memoryKindSet(GcHeap, ByrefExposed);
+ }
+ break;
+ }
+#endif
+
// For now, all calls read/write GcHeap/ByrefExposed, writes in their entirety. Might tighten this case later.
case GT_CALL:
{
diff --git a/src/jit/valuenum.cpp b/src/jit/valuenum.cpp
index 15a970d725..5b8537e599 100644
--- a/src/jit/valuenum.cpp
+++ b/src/jit/valuenum.cpp
@@ -5632,6 +5632,17 @@ void Compiler::fgValueNumberTree(GenTree* tree, bool evalAsgLhsInd)
{
// TODO-CQ: For now hardware intrinsics are not handled by value numbering to be amenable for CSE'ing.
tree->gtVNPair.SetBoth(vnStore->VNForExpr(compCurBB, TYP_UNKNOWN));
+
+ GenTreeHWIntrinsic* hwIntrinsicNode = tree->AsHWIntrinsic();
+ assert(hwIntrinsicNode != nullptr);
+
+ // For safety/correctness we must mutate the global heap valuenumber
+ // for any HW intrinsic that performs a memory store operation
+ if (hwIntrinsicNode->OperIsMemoryStore())
+ {
+ fgMutateGcHeap(tree DEBUGARG("HWIntrinsic - MemoryStore"));
+ }
+
return;
}
#endif // FEATURE_HW_INTRINSICS
diff --git a/tests/src/JIT/HardwareIntrinsics/X86/Regression/GitHub_17435/GitHub_17435.cs b/tests/src/JIT/HardwareIntrinsics/X86/Regression/GitHub_17435/GitHub_17435.cs
new file mode 100644
index 0000000000..bed639c202
--- /dev/null
+++ b/tests/src/JIT/HardwareIntrinsics/X86/Regression/GitHub_17435/GitHub_17435.cs
@@ -0,0 +1,62 @@
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics.X86;
+using System.Runtime.Intrinsics;
+
+namespace GitHub_17435
+{
+ class Program
+ {
+ const int Pass = 100;
+ const int Fail = 0;
+
+ static unsafe int Main(string[] args)
+ {
+
+ if (Sse2.IsSupported)
+ {
+ (uint a, uint b) = Program.Repro();
+ if ((a !=3) || (b != 6))
+ {
+ Console.WriteLine($"FAILED {a}, {b}");
+ return Fail;
+ }
+ else
+ {
+ Console.WriteLine("Passed");
+ return Pass;
+ }
+ }
+ else
+ {
+ Console.WriteLine("SSE2 not supported");
+ return Pass;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public unsafe static (uint, uint) Repro()
+ {
+ uint* a = stackalloc uint[4];
+ a[0] = 1;
+ a[1] = 1;
+ a[2] = 1;
+ a[3] = 1;
+
+ // Here we force populate the registers
+ var b = a[0];
+ var h = a[3];
+ var c = a[1];
+
+ // We operate the values in xmm0
+ Vector128<uint> v = Sse2.LoadVector128(a);
+ v = Sse2.Add(v, v);
+ // We send to the memory the modified values
+ Sse2.Store(a, v);
+
+ // We return both sums (from registers and from memory)
+ return (b + h + c, a[0]+a[1]+a[3]);
+ }
+ }
+}
diff --git a/tests/src/JIT/HardwareIntrinsics/X86/Regression/GitHub_17435/GitHub_17435.csproj b/tests/src/JIT/HardwareIntrinsics/X86/Regression/GitHub_17435/GitHub_17435.csproj
new file mode 100644
index 0000000000..70fdafb626
--- /dev/null
+++ b/tests/src/JIT/HardwareIntrinsics/X86/Regression/GitHub_17435/GitHub_17435.csproj
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{95DFC527-4DC1-495E-97D7-E94EE1F7140D}</ProjectGuid>
+ <OutputType>Exe</OutputType>
+ <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+ <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ </PropertyGroup>
+ <!-- Default configurations to help VS understand the configurations -->
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "></PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' " />
+ <ItemGroup>
+ <CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
+ <Visible>False</Visible>
+ </CodeAnalysisDependentAssemblyPaths>
+ </ItemGroup>
+ <PropertyGroup>
+ <DebugType>None</DebugType>
+ <Optimize>True</Optimize>
+ </PropertyGroup>
+ <ItemGroup>
+ <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="GitHub_17435.cs" />
+ </ItemGroup>
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
+ <PropertyGroup Condition=" '$(MsBuildProjectDirOverride)' != '' "></PropertyGroup>
+</Project>