From b90dcec739bb341700c903b75b5141e1f7a6bc96 Mon Sep 17 00:00:00 2001 From: Carol Eidt Date: Tue, 29 Nov 2016 09:18:31 -0800 Subject: [PATCH] Enable promotion of SIMD fields of structs Only look for SIMD fields if a SIMD type has been found. Also, since more cases of local struct values are no longer marked GTF_GLOB_REF, adjust the heuristics for allocating a temporary for a struct arrRef. Fix dotnet/coreclr#7508 Commit migrated from https://github.com/dotnet/coreclr/commit/e40595edb2c9cba2864a7e39690575cee7af94f4 --- src/coreclr/src/jit/compiler.cpp | 1 + src/coreclr/src/jit/compiler.h | 18 +- src/coreclr/src/jit/compiler.hpp | 13 +- src/coreclr/src/jit/flowgraph.cpp | 7 + src/coreclr/src/jit/lclvars.cpp | 56 ++++++- src/coreclr/src/jit/morph.cpp | 182 ++++++++++----------- src/coreclr/src/jit/simd.cpp | 1 + .../Regression/JitBlue/GitHub_7508/Vector3Test.cs | 152 +++++++++++++++++ .../JitBlue/GitHub_7508/Vector3Test.csproj | 46 ++++++ 9 files changed, 375 insertions(+), 101 deletions(-) create mode 100644 src/coreclr/tests/src/JIT/Regression/JitBlue/GitHub_7508/Vector3Test.cs create mode 100644 src/coreclr/tests/src/JIT/Regression/JitBlue/GitHub_7508/Vector3Test.csproj diff --git a/src/coreclr/src/jit/compiler.cpp b/src/coreclr/src/jit/compiler.cpp index 92c8d29..d8929e0 100644 --- a/src/coreclr/src/jit/compiler.cpp +++ b/src/coreclr/src/jit/compiler.cpp @@ -3022,6 +3022,7 @@ void Compiler::compInitOptions(JitFlags* jitFlags) #ifdef FEATURE_SIMD // Minimum bar for availing SIMD benefits is SSE2 on AMD64/x86. featureSIMD = jitFlags->IsSet(JitFlags::JIT_FLAG_FEATURE_SIMD); + setUsesSIMDTypes(false); #endif // FEATURE_SIMD if (compIsForInlining() || compIsForImportOnly()) diff --git a/src/coreclr/src/jit/compiler.h b/src/coreclr/src/jit/compiler.h index a2ab6b3..33fb8c9 100644 --- a/src/coreclr/src/jit/compiler.h +++ b/src/coreclr/src/jit/compiler.h @@ -322,6 +322,7 @@ public: // type of an arg node is TYP_BYREF and a local node is TYP_SIMD*. unsigned char lvSIMDType : 1; // This is a SIMD struct unsigned char lvUsedInSIMDIntrinsic : 1; // This tells lclvar is used for simd intrinsic + var_types lvBaseType : 5; // Note: this only packs because var_types is a typedef of unsigned char #endif // FEATURE_SIMD unsigned char lvRegStruct : 1; // This is a reg-sized non-field-addressed struct. @@ -330,9 +331,6 @@ public: // local. unsigned lvParentLcl; // The index of the local var representing the parent (i.e. the promoted struct local). // Valid on promoted struct local fields. -#ifdef FEATURE_SIMD - var_types lvBaseType; // The base type of a SIMD local var. Valid on TYP_SIMD locals. -#endif // FEATURE_SIMD }; unsigned char lvFieldCnt; // Number of fields in the promoted VarDsc. @@ -6931,6 +6929,20 @@ private: // Should we support SIMD intrinsics? bool featureSIMD; + // Have we identified any SIMD types? + // This is currently used by struct promotion to avoid getting type information for a struct + // field to see if it is a SIMD type, if we haven't seen any SIMD types or operations in + // the method. + bool _usesSIMDTypes; + bool usesSIMDTypes() + { + return _usesSIMDTypes; + } + void setUsesSIMDTypes(bool value) + { + _usesSIMDTypes = value; + } + // This is a temp lclVar allocated on the stack as TYP_SIMD. It is used to implement intrinsics // that require indexed access to the individual fields of the vector, which is not well supported // by the hardware. It is allocated when/if such situations are encountered during Lowering. diff --git a/src/coreclr/src/jit/compiler.hpp b/src/coreclr/src/jit/compiler.hpp index 9310d05..d88de52 100644 --- a/src/coreclr/src/jit/compiler.hpp +++ b/src/coreclr/src/jit/compiler.hpp @@ -1137,7 +1137,6 @@ inline GenTreePtr Compiler::gtNewFieldRef( tree->gtField.gtFldObj = obj; tree->gtField.gtFldHnd = fldHnd; tree->gtField.gtFldOffset = offset; - tree->gtFlags |= GTF_GLOB_REF; #ifdef FEATURE_READYTORUN_COMPILER tree->gtField.gtFieldLookup.addr = nullptr; @@ -1154,6 +1153,18 @@ inline GenTreePtr Compiler::gtNewFieldRef( { unsigned lclNum = obj->gtOp.gtOp1->gtLclVarCommon.gtLclNum; lvaTable[lclNum].lvFieldAccessed = 1; +#if defined(_TARGET_AMD64_) || defined(_TARGET_ARM64_) + // These structs are passed by reference; we should probably be able to treat these + // as non-global refs, but downstream logic expects these to be marked this way. + if (lvaTable[lclNum].lvIsParam) + { + tree->gtFlags |= GTF_GLOB_REF; + } +#endif // defined(_TARGET_AMD64_) || defined(_TARGET_ARM64_) + } + else + { + tree->gtFlags |= GTF_GLOB_REF; } return tree; diff --git a/src/coreclr/src/jit/flowgraph.cpp b/src/coreclr/src/jit/flowgraph.cpp index 441569c..293c690 100644 --- a/src/coreclr/src/jit/flowgraph.cpp +++ b/src/coreclr/src/jit/flowgraph.cpp @@ -21970,6 +21970,13 @@ _Done: compNeedsGSSecurityCookie |= InlineeCompiler->compNeedsGSSecurityCookie; compGSReorderStackLayout |= InlineeCompiler->compGSReorderStackLayout; +#ifdef FEATURE_SIMD + if (InlineeCompiler->usesSIMDTypes()) + { + setUsesSIMDTypes(true); + } +#endif // FEATURE_SIMD + // Update unmanaged call count info.compCallUnmanaged += InlineeCompiler->info.compCallUnmanaged; diff --git a/src/coreclr/src/jit/lclvars.cpp b/src/coreclr/src/jit/lclvars.cpp index ea9c573..dc20daa 100644 --- a/src/coreclr/src/jit/lclvars.cpp +++ b/src/coreclr/src/jit/lclvars.cpp @@ -1414,9 +1414,16 @@ void Compiler::lvaCanPromoteStructType(CORINFO_CLASS_HANDLE typeHnd, if (typeHnd != StructPromotionInfo->typeHnd) { - // sizeof(double) represents the size of the largest primitive type that we can struct promote - // In the future this may be changing to XMM_REGSIZE_BYTES - const int MaxOffset = MAX_NumOfFieldsInPromotableStruct * sizeof(double); // must be a compile time constant + // sizeof(double) represents the size of the largest primitive type that we can struct promote. + // In the future this may be changing to XMM_REGSIZE_BYTES. + // Note: MaxOffset is used below to declare a local array, and therefore must be a compile-time constant. + CLANG_FORMAT_COMMENT_ANCHOR; +#ifdef FEATURE_SIMD + // This will allow promotion of 2 Vector fields on AVX2, or 4 Vector fields on SSE2. + const int MaxOffset = MAX_NumOfFieldsInPromotableStruct * XMM_REGSIZE_BYTES; +#else // !FEATURE_SIMD + const int MaxOffset = MAX_NumOfFieldsInPromotableStruct * sizeof(double); +#endif // !FEATURE_SIMD assert((BYTE)MaxOffset == MaxOffset); // because lvaStructFieldInfo.fldOffset is byte-sized assert((BYTE)MAX_NumOfFieldsInPromotableStruct == @@ -1507,13 +1514,31 @@ void Compiler::lvaCanPromoteStructType(CORINFO_CLASS_HANDLE typeHnd, CorInfoType corType = info.compCompHnd->getFieldType(pFieldInfo->fldHnd, &pFieldInfo->fldTypeHnd); var_types varType = JITtype2varType(corType); pFieldInfo->fldType = varType; - pFieldInfo->fldSize = genTypeSize(varType); + unsigned size = genTypeSize(varType); + pFieldInfo->fldSize = size; if (varTypeIsGC(varType)) { containsGCpointers = true; } +#ifdef FEATURE_SIMD + // Check to see if this is a SIMD type. + // We will only check this if we have already found a SIMD type, which will be true if + // we have encountered any SIMD intrinsics. + if (usesSIMDTypes() && (pFieldInfo->fldSize == 0) && isSIMDClass(pFieldInfo->fldTypeHnd)) + { + unsigned simdSize; + var_types simdBaseType = getBaseTypeAndSizeOfSIMDType(pFieldInfo->fldTypeHnd, &simdSize); + if (simdBaseType != TYP_UNKNOWN) + { + varType = getSIMDTypeForSize(simdSize); + pFieldInfo->fldType = varType; + pFieldInfo->fldSize = simdSize; + } + } +#endif // FEATURE_SIMD + if (pFieldInfo->fldSize == 0) { // Non-primitive struct field. Don't promote. @@ -1683,7 +1708,7 @@ void Compiler::lvaPromoteStructVar(unsigned lclNum, lvaStructPromotionInfo* Stru { lvaStructFieldInfo* pFieldInfo = &StructPromotionInfo->fields[index]; - if (varTypeIsFloating(pFieldInfo->fldType)) + if (varTypeIsFloating(pFieldInfo->fldType) || varTypeIsSIMD(pFieldInfo->fldType)) { lvaTable[lclNum].lvContainsFloatingFields = 1; // Whenever we promote a struct that contains a floating point field @@ -1727,12 +1752,32 @@ void Compiler::lvaPromoteStructVar(unsigned lclNum, lvaStructPromotionInfo* Stru fieldVarDsc->lvIsRegArg = true; fieldVarDsc->lvArgReg = varDsc->lvArgReg; fieldVarDsc->setPrefReg(varDsc->lvArgReg, this); // Set the preferred register +#if FEATURE_MULTIREG_ARGS && defined(FEATURE_SIMD) + if (varTypeIsSIMD(fieldVarDsc)) + { + // This field is a SIMD type, and will be considered to be passed in multiple registers + // if the parent struct was. Note that this code relies on the fact that if there is + // a SIMD field of an enregisterable struct, it is the only field. + // We will assert that, in case future changes are made to the ABI. + assert(varDsc->lvFieldCnt == 1); + fieldVarDsc->lvOtherArgReg = varDsc->lvOtherArgReg; + } +#endif // FEATURE_MULTIREG_ARGS && defined(FEATURE_SIMD) lvaMarkRefsWeight = BB_UNITY_WEIGHT; // incRefCnts can use this compiler global variable fieldVarDsc->incRefCnts(BB_UNITY_WEIGHT, this); // increment the ref count for prolog initialization } #endif +#ifdef FEATURE_SIMD + if (varTypeIsSIMD(pFieldInfo->fldType)) + { + // Set size to zero so that lvaSetStruct will appropriately set the SIMD-relevant fields. + fieldVarDsc->lvExactSize = 0; + lvaSetStruct(varNum, pFieldInfo->fldTypeHnd, false, true); + } +#endif // FEATURE_SIMD + #ifdef DEBUG // This temporary should not be converted to a double in stress mode, // because we introduce assigns to it after the stress conversion @@ -2029,7 +2074,6 @@ void Compiler::lvaSetStruct(unsigned varNum, CORINFO_CLASS_HANDLE typeHnd, bool } else { - assert(varDsc->lvExactSize != 0); #if FEATURE_SIMD assert(!varTypeIsSIMD(varDsc) || (varDsc->lvBaseType != TYP_UNKNOWN)); #endif // FEATURE_SIMD diff --git a/src/coreclr/src/jit/morph.cpp b/src/coreclr/src/jit/morph.cpp index d2a6843..90b35cc 100644 --- a/src/coreclr/src/jit/morph.cpp +++ b/src/coreclr/src/jit/morph.cpp @@ -5629,8 +5629,13 @@ GenTreePtr Compiler::fgMorphArrayIndex(GenTreePtr tree) // to ensure that the same values are used in the bounds check and the actual // dereference. // Also we allocate the temporary when the arrRef is sufficiently complex/expensive. + // Note that if 'arrRef' is a GT_FIELD, it has not yet been morphed so its true + // complexity is not exposed. (Without that condition there are cases of local struct + // fields that were previously, needlessly, marked as GTF_GLOB_REF, and when that was + // fixed, there were some regressions that were mostly ameliorated by adding this condition.) // - if ((arrRef->gtFlags & (GTF_ASG | GTF_CALL | GTF_GLOB_REF)) || gtComplexityExceeds(&arrRef, MAX_ARR_COMPLEXITY)) + if ((arrRef->gtFlags & (GTF_ASG | GTF_CALL | GTF_GLOB_REF)) || + gtComplexityExceeds(&arrRef, MAX_ARR_COMPLEXITY) || (arrRef->OperGet() == GT_FIELD)) { unsigned arrRefTmpNum = lvaGrabTemp(true DEBUGARG("arr expr")); arrRefDefn = gtNewTempAssign(arrRefTmpNum, arrRef); @@ -5649,7 +5654,8 @@ GenTreePtr Compiler::fgMorphArrayIndex(GenTreePtr tree) // dereference. // Also we allocate the temporary when the index is sufficiently complex/expensive. // - if ((index->gtFlags & (GTF_ASG | GTF_CALL | GTF_GLOB_REF)) || gtComplexityExceeds(&index, MAX_ARR_COMPLEXITY)) + if ((index->gtFlags & (GTF_ASG | GTF_CALL | GTF_GLOB_REF)) || gtComplexityExceeds(&index, MAX_ARR_COMPLEXITY) || + (arrRef->OperGet() == GT_FIELD)) { unsigned indexTmpNum = lvaGrabTemp(true DEBUGARG("arr expr")); indexDefn = gtNewTempAssign(indexTmpNum, index); @@ -6051,14 +6057,15 @@ GenTreePtr Compiler::fgMorphField(GenTreePtr tree, MorphAddrContext* mac) { assert(tree->gtOper == GT_FIELD); - noway_assert(tree->gtFlags & GTF_GLOB_REF); - CORINFO_FIELD_HANDLE symHnd = tree->gtField.gtFldHnd; unsigned fldOffset = tree->gtField.gtFldOffset; GenTreePtr objRef = tree->gtField.gtFldObj; bool fieldMayOverlap = false; bool objIsLocal = false; + noway_assert(((objRef != nullptr) && (objRef->IsLocalAddrExpr() != nullptr)) || + ((tree->gtFlags & GTF_GLOB_REF) != 0)); + if (tree->gtField.gtFldMayOverlap) { fieldMayOverlap = true; @@ -17203,109 +17210,102 @@ void Compiler::fgPromoteStructs() Compiler::fgWalkResult Compiler::fgMorphStructField(GenTreePtr tree, fgWalkData* fgWalkPre) { noway_assert(tree->OperGet() == GT_FIELD); - noway_assert(tree->gtFlags & GTF_GLOB_REF); GenTreePtr objRef = tree->gtField.gtFldObj; + GenTreePtr obj = ((objRef != nullptr) && (objRef->gtOper == GT_ADDR)) ? objRef->gtOp.gtOp1 : nullptr; + noway_assert((tree->gtFlags & GTF_GLOB_REF) || ((obj != nullptr) && (obj->gtOper == GT_LCL_VAR))); /* Is this an instance data member? */ - if (objRef) + if ((obj != nullptr) && (obj->gtOper == GT_LCL_VAR)) { - if (objRef->gtOper == GT_ADDR) - { - GenTreePtr obj = objRef->gtOp.gtOp1; + unsigned lclNum = obj->gtLclVarCommon.gtLclNum; + LclVarDsc* varDsc = &lvaTable[lclNum]; - if (obj->gtOper == GT_LCL_VAR) + if (varTypeIsStruct(obj)) + { + if (varDsc->lvPromoted) { - unsigned lclNum = obj->gtLclVarCommon.gtLclNum; - LclVarDsc* varDsc = &lvaTable[lclNum]; + // Promoted struct + unsigned fldOffset = tree->gtField.gtFldOffset; + unsigned fieldLclIndex = lvaGetFieldLocal(varDsc, fldOffset); + noway_assert(fieldLclIndex != BAD_VAR_NUM); + + tree->SetOper(GT_LCL_VAR); + tree->gtLclVarCommon.SetLclNum(fieldLclIndex); + tree->gtType = lvaTable[fieldLclIndex].TypeGet(); + tree->gtFlags &= GTF_NODE_MASK; + tree->gtFlags &= ~GTF_GLOB_REF; - if (varTypeIsStruct(obj)) + GenTreePtr parent = fgWalkPre->parentStack->Index(1); + if ((parent->gtOper == GT_ASG) && (parent->gtOp.gtOp1 == tree)) { - if (varDsc->lvPromoted) - { - // Promoted struct - unsigned fldOffset = tree->gtField.gtFldOffset; - unsigned fieldLclIndex = lvaGetFieldLocal(varDsc, fldOffset); - noway_assert(fieldLclIndex != BAD_VAR_NUM); - - tree->SetOper(GT_LCL_VAR); - tree->gtLclVarCommon.SetLclNum(fieldLclIndex); - tree->gtType = lvaTable[fieldLclIndex].TypeGet(); - tree->gtFlags &= GTF_NODE_MASK; - tree->gtFlags &= ~GTF_GLOB_REF; - - GenTreePtr parent = fgWalkPre->parentStack->Index(1); - if ((parent->gtOper == GT_ASG) && (parent->gtOp.gtOp1 == tree)) - { - tree->gtFlags |= GTF_VAR_DEF; - tree->gtFlags |= GTF_DONT_CSE; - } -#ifdef DEBUG - if (verbose) - { - printf("Replacing the field in promoted struct with a local var:\n"); - fgWalkPre->printModified = true; - } -#endif // DEBUG - return WALK_SKIP_SUBTREES; - } + tree->gtFlags |= GTF_VAR_DEF; + tree->gtFlags |= GTF_DONT_CSE; } - else +#ifdef DEBUG + if (verbose) { - // Normed struct - // A "normed struct" is a struct that the VM tells us is a basic type. This can only happen if - // the struct contains a single element, and that element is 4 bytes (on x64 it can also be 8 - // bytes). Normally, the type of the local var and the type of GT_FIELD are equivalent. However, - // there is one extremely rare case where that won't be true. An enum type is a special value type - // that contains exactly one element of a primitive integer type (that, for CLS programs is named - // "value__"). The VM tells us that a local var of that enum type is the primitive type of the - // enum's single field. It turns out that it is legal for IL to access this field using ldflda or - // ldfld. For example: - // - // .class public auto ansi sealed mynamespace.e_t extends [mscorlib]System.Enum - // { - // .field public specialname rtspecialname int16 value__ - // .field public static literal valuetype mynamespace.e_t one = int16(0x0000) - // } - // .method public hidebysig static void Main() cil managed - // { - // .locals init (valuetype mynamespace.e_t V_0) - // ... - // ldloca.s V_0 - // ldflda int16 mynamespace.e_t::value__ - // ... - // } - // - // Normally, compilers will not generate the ldflda, since it is superfluous. - // - // In the example, the lclVar is short, but the JIT promotes all trees using this local to the - // "actual type", that is, INT. But the GT_FIELD is still SHORT. So, in the case of a type - // mismatch like this, don't do this morphing. The local var may end up getting marked as - // address taken, and the appropriate SHORT load will be done from memory in that case. + printf("Replacing the field in promoted struct with a local var:\n"); + fgWalkPre->printModified = true; + } +#endif // DEBUG + return WALK_SKIP_SUBTREES; + } + } + else + { + // Normed struct + // A "normed struct" is a struct that the VM tells us is a basic type. This can only happen if + // the struct contains a single element, and that element is 4 bytes (on x64 it can also be 8 + // bytes). Normally, the type of the local var and the type of GT_FIELD are equivalent. However, + // there is one extremely rare case where that won't be true. An enum type is a special value type + // that contains exactly one element of a primitive integer type (that, for CLS programs is named + // "value__"). The VM tells us that a local var of that enum type is the primitive type of the + // enum's single field. It turns out that it is legal for IL to access this field using ldflda or + // ldfld. For example: + // + // .class public auto ansi sealed mynamespace.e_t extends [mscorlib]System.Enum + // { + // .field public specialname rtspecialname int16 value__ + // .field public static literal valuetype mynamespace.e_t one = int16(0x0000) + // } + // .method public hidebysig static void Main() cil managed + // { + // .locals init (valuetype mynamespace.e_t V_0) + // ... + // ldloca.s V_0 + // ldflda int16 mynamespace.e_t::value__ + // ... + // } + // + // Normally, compilers will not generate the ldflda, since it is superfluous. + // + // In the example, the lclVar is short, but the JIT promotes all trees using this local to the + // "actual type", that is, INT. But the GT_FIELD is still SHORT. So, in the case of a type + // mismatch like this, don't do this morphing. The local var may end up getting marked as + // address taken, and the appropriate SHORT load will be done from memory in that case. - if (tree->TypeGet() == obj->TypeGet()) - { - tree->ChangeOper(GT_LCL_VAR); - tree->gtLclVarCommon.SetLclNum(lclNum); - tree->gtFlags &= GTF_NODE_MASK; + if (tree->TypeGet() == obj->TypeGet()) + { + tree->ChangeOper(GT_LCL_VAR); + tree->gtLclVarCommon.SetLclNum(lclNum); + tree->gtFlags &= GTF_NODE_MASK; - GenTreePtr parent = fgWalkPre->parentStack->Index(1); - if ((parent->gtOper == GT_ASG) && (parent->gtOp.gtOp1 == tree)) - { - tree->gtFlags |= GTF_VAR_DEF; - tree->gtFlags |= GTF_DONT_CSE; - } + GenTreePtr parent = fgWalkPre->parentStack->Index(1); + if ((parent->gtOper == GT_ASG) && (parent->gtOp.gtOp1 == tree)) + { + tree->gtFlags |= GTF_VAR_DEF; + tree->gtFlags |= GTF_DONT_CSE; + } #ifdef DEBUG - if (verbose) - { - printf("Replacing the field in normed struct with the local var:\n"); - fgWalkPre->printModified = true; - } -#endif // DEBUG - return WALK_SKIP_SUBTREES; - } + if (verbose) + { + printf("Replacing the field in normed struct with the local var:\n"); + fgWalkPre->printModified = true; } +#endif // DEBUG + return WALK_SKIP_SUBTREES; } } } diff --git a/src/coreclr/src/jit/simd.cpp b/src/coreclr/src/jit/simd.cpp index e0f63c2..2199933 100644 --- a/src/coreclr/src/jit/simd.cpp +++ b/src/coreclr/src/jit/simd.cpp @@ -347,6 +347,7 @@ var_types Compiler::getBaseTypeAndSizeOfSIMDType(CORINFO_CLASS_HANDLE typeHnd, u } *sizeBytes = size; + setUsesSIMDTypes(true); } return simdBaseType; diff --git a/src/coreclr/tests/src/JIT/Regression/JitBlue/GitHub_7508/Vector3Test.cs b/src/coreclr/tests/src/JIT/Regression/JitBlue/GitHub_7508/Vector3Test.cs new file mode 100644 index 0000000..609141f --- /dev/null +++ b/src/coreclr/tests/src/JIT/Regression/JitBlue/GitHub_7508/Vector3Test.cs @@ -0,0 +1,152 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// Since Issue 7508 was a performance issue, there's not really a correctness +// test for this. +// However, this is a very simple test that can be used to compare the code generated +// for a non-accelerated vector of 3 floats, a "raw" Vector3 and a Vector3 +// wrapped in a struct. + +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Numerics; + +namespace Test01 +{ + public struct SimpleVector3 + { + [MethodImpl( MethodImplOptions.AggressiveInlining )] + public SimpleVector3( float x, float y, float z ) + { + this.x = x; + this.y = y; + this.z = z; + } + + [MethodImpl( MethodImplOptions.AggressiveInlining )] + public static SimpleVector3 operator +( SimpleVector3 a, SimpleVector3 b ) + => new SimpleVector3( a.x + b.x, a.y + b.y, a.z + b.z ); + + public float X + { + get { return x; } + set { x = value; } + } + + public float Y + { + get { return y; } + set { y = value; } + } + + public float Z + { + get { return z; } + set { z = value; } + } + + float x, y, z; + } + + public struct WrappedVector3 + { + [MethodImpl( MethodImplOptions.AggressiveInlining )] + public WrappedVector3( float x, float y, float z ) + { + v = new System.Numerics.Vector3( x, y, z ); + } + + [MethodImpl( MethodImplOptions.AggressiveInlining )] + WrappedVector3( System.Numerics.Vector3 v ) + { + this.v = v; + } + + [MethodImpl( MethodImplOptions.AggressiveInlining )] + public static WrappedVector3 operator +( WrappedVector3 a, WrappedVector3 b ) + => new WrappedVector3( a.v + b.v ); + + public float X + { + get { return v.X; } + set { v.X = value; } + } + + public float Y + { + get { return v.Y; } + set { v.Y = value; } + } + + public float Z + { + get { return v.Z; } + set { v.Z = value; } + } + + Vector3 v; + } + + public class Program + { + static Random random = new Random( 12345 ); + [MethodImpl( MethodImplOptions.NoInlining )] + static SimpleVector3 RandomSimpleVector3() + => new SimpleVector3( (float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble() ); + [MethodImpl(MethodImplOptions.NoInlining)] + static WrappedVector3 RandomWrappedVector3() + => new WrappedVector3( (float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble() ); + [MethodImpl(MethodImplOptions.NoInlining)] + static Vector3 RandomVector3() + => new Vector3( (float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble() ); + + public static float TestSimple() + { + var simpleA = RandomSimpleVector3(); + var simpleB = RandomSimpleVector3(); + var simpleC = simpleA + simpleB; + Console.WriteLine("Simple Vector3: {0},{1},{2}", simpleC.X, simpleC.Y, simpleC.Z); + return simpleC.X + simpleC.Y + simpleC.Z; + } + public static float TestWrapped() + { + var wrappedA = RandomWrappedVector3(); + var wrappedB = RandomWrappedVector3(); + var wrappedC = wrappedA + wrappedB; + Console.WriteLine("Wrapped Vector3: {0},{1},{2}", wrappedC.X, wrappedC.Y, wrappedC.Z); + return wrappedC.X + wrappedC.Y + wrappedC.Z; + } + public static float TestSIMD() + { + var a = RandomVector3(); + var b = RandomVector3(); + var c = a + b; + Console.WriteLine("SIMD Vector3: {0},{1},{2}", c.X, c.Y, c.Z); + return c.X + c.Y + c.Z; + } + public static int Main( string[] args ) + { + int returnVal = 100; + + // First, test a simple (non-SIMD) Vector3 type + float f = TestSimple(); + + // Now a wrapped SIMD Vector3. + if (TestWrapped() != f) + { + returnVal = -1; + } + + // Now, SIMD Vector3 + if (TestSIMD() != f) + { + returnVal = -1; + } + + return 100; + } + } +} + diff --git a/src/coreclr/tests/src/JIT/Regression/JitBlue/GitHub_7508/Vector3Test.csproj b/src/coreclr/tests/src/JIT/Regression/JitBlue/GitHub_7508/Vector3Test.csproj new file mode 100644 index 0000000..2e97f36 --- /dev/null +++ b/src/coreclr/tests/src/JIT/Regression/JitBlue/GitHub_7508/Vector3Test.csproj @@ -0,0 +1,46 @@ + + + + + Debug + AnyCPU + $(MSBuildProjectName) + 2.0 + {95DFC527-4DC1-495E-97D7-E94EE1F7140D} + Exe + Properties + 512 + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + $(ProgramFiles)\Common Files\microsoft shared\VSTT\11.0\UITestExtensionPackages + ..\..\ + + 7a9bfb7d + + + + + + + + + False + + + + + True + + + + + + + + + $(JitPackagesConfigFileDirectory)threading+thread\project.json + $(JitPackagesConfigFileDirectory)threading+thread\project.lock.json + + + + + -- 2.7.4