diff options
author | Brian Sullivan <briansul@microsoft.com> | 2018-04-11 17:38:21 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-04-11 17:38:21 -0700 |
commit | 01690dfa800d07b0455b02fa93f792c282dd7c47 (patch) | |
tree | 9bb68f2362081b8f5ab0d195ec393c3f5b3ccf7d | |
parent | 13528d6ddcebfc6724c31c597efb5173f6eed781 (diff) | |
parent | f9a92757413250fb6ef5675ddab8376231d575ab (diff) | |
download | coreclr-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.h | 6 | ||||
-rw-r--r-- | src/jit/gentree.cpp | 188 | ||||
-rw-r--r-- | src/jit/gentree.h | 40 | ||||
-rw-r--r-- | src/jit/hwintrinsicxarch.cpp | 22 | ||||
-rw-r--r-- | src/jit/liveness.cpp | 21 | ||||
-rw-r--r-- | src/jit/valuenum.cpp | 11 | ||||
-rw-r--r-- | tests/src/JIT/HardwareIntrinsics/X86/Regression/GitHub_17435/GitHub_17435.cs | 62 | ||||
-rw-r--r-- | tests/src/JIT/HardwareIntrinsics/X86/Regression/GitHub_17435/GitHub_17435.csproj | 34 |
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> |