// 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. #include "jitpch.h" #include "hwintrinsic.h" #ifdef FEATURE_HW_INTRINSICS static const HWIntrinsicInfo hwIntrinsicInfoArray[] = { // clang-format off #define HARDWARE_INTRINSIC(id, name, isa, ival, size, numarg, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, category, flag) \ {NI_##id, name, InstructionSet_##isa, ival, size, numarg, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, category, static_cast(flag)}, // clang-format on #include "hwintrinsiclistxarch.h" }; //------------------------------------------------------------------------ // lookup: Gets the HWIntrinsicInfo associated with a given NamedIntrinsic // // Arguments: // id -- The NamedIntrinsic associated with the HWIntrinsic to lookup // // Return Value: // The HWIntrinsicInfo associated with id const HWIntrinsicInfo& HWIntrinsicInfo::lookup(NamedIntrinsic id) { assert(id != NI_Illegal); assert(id > NI_HW_INTRINSIC_START); assert(id < NI_HW_INTRINSIC_END); return hwIntrinsicInfoArray[id - NI_HW_INTRINSIC_START - 1]; } //------------------------------------------------------------------------ // lookupId: Gets the NamedIntrinsic for a given method name and InstructionSet // // Arguments: // className -- The name of the class associated with the HWIntrinsic to lookup // methodName -- The name of the method associated with the HWIntrinsic to lookup // enclosingClassName -- The name of the enclosing class of X64 classes // // Return Value: // The NamedIntrinsic associated with methodName and isa NamedIntrinsic HWIntrinsicInfo::lookupId(const char* className, const char* methodName, const char* enclosingClassName) { // TODO-Throughput: replace sequential search by binary search InstructionSet isa = lookupIsa(className, enclosingClassName); assert(isa != InstructionSet_ILLEGAL); assert(methodName != nullptr); for (int i = 0; i < (NI_HW_INTRINSIC_END - NI_HW_INTRINSIC_START - 1); i++) { if (isa != hwIntrinsicInfoArray[i].isa) { continue; } if (strcmp(methodName, hwIntrinsicInfoArray[i].name) == 0) { return hwIntrinsicInfoArray[i].id; } } // There are several helper intrinsics that are implemented in managed code // Those intrinsics will hit this code path and need to return NI_Illegal return NI_Illegal; } //------------------------------------------------------------------------ // X64VersionOfIsa: Gets the corresponding 64-bit only InstructionSet for a given InstructionSet // // Arguments: // isa -- The InstructionSet ID // // Return Value: // The 64-bit only InstructionSet associated with isa static InstructionSet X64VersionOfIsa(InstructionSet isa) { switch (isa) { case InstructionSet_SSE: return InstructionSet_SSE_X64; case InstructionSet_SSE2: return InstructionSet_SSE2_X64; case InstructionSet_SSE41: return InstructionSet_SSE41_X64; case InstructionSet_SSE42: return InstructionSet_SSE42_X64; case InstructionSet_BMI1: return InstructionSet_BMI1_X64; case InstructionSet_BMI2: return InstructionSet_BMI2_X64; case InstructionSet_LZCNT: return InstructionSet_LZCNT_X64; case InstructionSet_POPCNT: return InstructionSet_POPCNT_X64; default: unreached(); return InstructionSet_ILLEGAL; } } //------------------------------------------------------------------------ // lookupInstructionSet: Gets the InstructionSet for a given class name // // Arguments: // className -- The name of the class associated with the InstructionSet to lookup // // Return Value: // The InstructionSet associated with className static InstructionSet lookupInstructionSet(const char* className) { assert(className != nullptr); if (className[0] == 'A') { if (strcmp(className, "Aes") == 0) { return InstructionSet_AES; } if (strcmp(className, "Avx") == 0) { return InstructionSet_AVX; } if (strcmp(className, "Avx2") == 0) { return InstructionSet_AVX2; } } else if (className[0] == 'S') { if (strcmp(className, "Sse") == 0) { return InstructionSet_SSE; } if (strcmp(className, "Sse2") == 0) { return InstructionSet_SSE2; } if (strcmp(className, "Sse3") == 0) { return InstructionSet_SSE3; } if (strcmp(className, "Ssse3") == 0) { return InstructionSet_SSSE3; } if (strcmp(className, "Sse41") == 0) { return InstructionSet_SSE41; } if (strcmp(className, "Sse42") == 0) { return InstructionSet_SSE42; } } else if (className[0] == 'B') { if (strcmp(className, "Bmi1") == 0) { return InstructionSet_BMI1; } if (strcmp(className, "Bmi2") == 0) { return InstructionSet_BMI2; } } else if (className[0] == 'P') { if (strcmp(className, "Pclmulqdq") == 0) { return InstructionSet_PCLMULQDQ; } if (strcmp(className, "Popcnt") == 0) { return InstructionSet_POPCNT; } } else if (strcmp(className, "Fma") == 0) { return InstructionSet_FMA; } else if (strcmp(className, "Lzcnt") == 0) { return InstructionSet_LZCNT; } unreached(); return InstructionSet_ILLEGAL; } //------------------------------------------------------------------------ // lookupIsa: Gets the InstructionSet for a given class name and enclsoing class name // // Arguments: // className -- The name of the class associated with the InstructionSet to lookup // enclosingClassName -- The name of the enclosing class of X64 classes // // Return Value: // The InstructionSet associated with className and enclosingClassName InstructionSet HWIntrinsicInfo::lookupIsa(const char* className, const char* enclosingClassName) { assert(className != nullptr); if (strcmp(className, "X64") == 0) { assert(enclosingClassName != nullptr); return X64VersionOfIsa(lookupInstructionSet(enclosingClassName)); } else { return lookupInstructionSet(className); } } //------------------------------------------------------------------------ // lookupSimdSize: Gets the SimdSize for a given HWIntrinsic and signature // // Arguments: // id -- The ID associated with the HWIntrinsic to lookup // sig -- The signature of the HWIntrinsic to lookup // // Return Value: // The SIMD size for the HWIntrinsic associated with id and sig // // Remarks: // This function is only used by the importer. After importation, we can // get the SIMD size from the GenTreeHWIntrinsic node. unsigned HWIntrinsicInfo::lookupSimdSize(Compiler* comp, NamedIntrinsic id, CORINFO_SIG_INFO* sig) { if (HWIntrinsicInfo::HasFixedSimdSize(id)) { return lookupSimdSize(id); } CORINFO_CLASS_HANDLE typeHnd = nullptr; if (JITtype2varType(sig->retType) == TYP_STRUCT) { typeHnd = sig->retTypeSigClass; } else if (HWIntrinsicInfo::BaseTypeFromFirstArg(id)) { typeHnd = comp->info.compCompHnd->getArgClass(sig, sig->args); } else { assert(HWIntrinsicInfo::BaseTypeFromSecondArg(id)); CORINFO_ARG_LIST_HANDLE secondArg = comp->info.compCompHnd->getArgNext(sig->args); typeHnd = comp->info.compCompHnd->getArgClass(sig, secondArg); } unsigned simdSize = 0; var_types baseType = comp->getBaseTypeAndSizeOfSIMDType(typeHnd, &simdSize); assert((simdSize > 0) && (baseType != TYP_UNKNOWN)); return simdSize; } //------------------------------------------------------------------------ // lookupNumArgs: Gets the number of args for a given HWIntrinsic node // // Arguments: // node -- The HWIntrinsic node to get the number of args for // // Return Value: // The number of args for the HWIntrinsic associated with node int HWIntrinsicInfo::lookupNumArgs(const GenTreeHWIntrinsic* node) { assert(node != nullptr); NamedIntrinsic id = node->gtHWIntrinsicId; int numArgs = lookupNumArgs(id); if (numArgs >= 0) { return numArgs; } assert(numArgs == -1); GenTree* op1 = node->gtGetOp1(); if (op1 == nullptr) { return 0; } if (op1->OperIsList()) { GenTreeArgList* list = op1->AsArgList(); numArgs = 0; do { numArgs++; list = list->Rest(); } while (list != nullptr); return numArgs; } GenTree* op2 = node->gtGetOp2(); return (op2 == nullptr) ? 1 : 2; } //------------------------------------------------------------------------ // lookupLastOp: Gets the last operand for a given HWIntrinsic node // // Arguments: // node -- The HWIntrinsic node to get the last operand for // // Return Value: // The last operand for node GenTree* HWIntrinsicInfo::lookupLastOp(const GenTreeHWIntrinsic* node) { int numArgs = lookupNumArgs(node); switch (numArgs) { case 0: { assert(node->gtGetOp1() == nullptr); assert(node->gtGetOp2() == nullptr); return nullptr; } case 1: { assert(node->gtGetOp1() != nullptr); assert(!node->gtGetOp1()->OperIsList()); assert(node->gtGetOp2() == nullptr); return node->gtGetOp1(); } case 2: { assert(node->gtGetOp1() != nullptr); assert(!node->gtGetOp1()->OperIsList()); assert(node->gtGetOp2() != nullptr); return node->gtGetOp2(); } case 3: { assert(node->gtGetOp1() != nullptr); assert(node->gtGetOp1()->OperIsList()); assert(node->gtGetOp2() == nullptr); assert(node->gtGetOp1()->AsArgList()->Rest()->Rest()->Current() != nullptr); assert(node->gtGetOp1()->AsArgList()->Rest()->Rest()->Rest() == nullptr); return node->gtGetOp1()->AsArgList()->Rest()->Rest()->Current(); } case 5: { assert(node->gtGetOp1() != nullptr); assert(node->gtGetOp1()->OperIsList()); assert(node->gtGetOp2() == nullptr); assert(node->gtGetOp1()->AsArgList()->Rest()->Rest()->Rest()->Rest()->Current() != nullptr); assert(node->gtGetOp1()->AsArgList()->Rest()->Rest()->Rest()->Rest()->Rest() == nullptr); return node->gtGetOp1()->AsArgList()->Rest()->Rest()->Rest()->Rest()->Current(); } default: { unreached(); return nullptr; } } } //------------------------------------------------------------------------ // isImmOp: Gets a value that indicates whether the HWIntrinsic node has an imm operand // // Arguments: // id -- The NamedIntrinsic associated with the HWIntrinsic to lookup // op -- The operand to check // // Return Value: // true if the node has an imm operand; otherwise, false bool HWIntrinsicInfo::isImmOp(NamedIntrinsic id, const GenTree* op) { if (HWIntrinsicInfo::lookupCategory(id) != HW_Category_IMM) { return false; } if (!HWIntrinsicInfo::MaybeImm(id)) { return true; } if (genActualType(op->TypeGet()) != TYP_INT) { return false; } return true; } //------------------------------------------------------------------------ // lookupImmUpperBound: Gets the upper bound for the imm-value of a given NamedIntrinsic // // Arguments: // id -- The NamedIntrinsic associated with the HWIntrinsic to lookup // // Return Value: // The upper bound for the imm-value of the intrinsic associated with id // int HWIntrinsicInfo::lookupImmUpperBound(NamedIntrinsic id) { assert(HWIntrinsicInfo::lookupCategory(id) == HW_Category_IMM); switch (id) { case NI_AVX_Compare: case NI_AVX_CompareScalar: { assert(!HWIntrinsicInfo::HasFullRangeImm(id)); return 31; // enum FloatComparisonMode has 32 values } case NI_AVX2_GatherVector128: case NI_AVX2_GatherVector256: case NI_AVX2_GatherMaskVector128: case NI_AVX2_GatherMaskVector256: return 8; default: { assert(HWIntrinsicInfo::HasFullRangeImm(id)); return 255; } } } //------------------------------------------------------------------------ // isInImmRange: Check if ival is valid for the intrinsic // // Arguments: // id -- The NamedIntrinsic associated with the HWIntrinsic to lookup // ival -- the imm value to be checked // // Return Value: // true if ival is valid for the intrinsic // bool HWIntrinsicInfo::isInImmRange(NamedIntrinsic id, int ival) { assert(HWIntrinsicInfo::lookupCategory(id) == HW_Category_IMM); if (isAVX2GatherIntrinsic(id)) { return ival == 1 || ival == 2 || ival == 4 || ival == 8; } else { return ival <= lookupImmUpperBound(id) && ival >= 0; } } //------------------------------------------------------------------------ // isAVX2GatherIntrinsic: Check if the intrinsic is AVX Gather* // // Arguments: // id -- The NamedIntrinsic associated with the HWIntrinsic to lookup // // Return Value: // true if id is AVX Gather* intrinsic // bool HWIntrinsicInfo::isAVX2GatherIntrinsic(NamedIntrinsic id) { switch (id) { case NI_AVX2_GatherVector128: case NI_AVX2_GatherVector256: case NI_AVX2_GatherMaskVector128: case NI_AVX2_GatherMaskVector256: return true; default: return false; } } //------------------------------------------------------------------------ // isFullyImplementedIsa: Gets a value that indicates whether the InstructionSet is fully implemented // // Arguments: // isa - The InstructionSet to check // // Return Value: // true if isa is supported; otherwise, false bool HWIntrinsicInfo::isFullyImplementedIsa(InstructionSet isa) { switch (isa) { // These ISAs are fully implemented case InstructionSet_AES: case InstructionSet_AVX: case InstructionSet_AVX2: case InstructionSet_BMI1: case InstructionSet_BMI2: case InstructionSet_BMI1_X64: case InstructionSet_BMI2_X64: case InstructionSet_FMA: case InstructionSet_LZCNT: case InstructionSet_LZCNT_X64: case InstructionSet_PCLMULQDQ: case InstructionSet_POPCNT: case InstructionSet_POPCNT_X64: case InstructionSet_SSE: case InstructionSet_SSE_X64: case InstructionSet_SSE2: case InstructionSet_SSE2_X64: case InstructionSet_SSE3: case InstructionSet_SSSE3: case InstructionSet_SSE41: case InstructionSet_SSE41_X64: case InstructionSet_SSE42: case InstructionSet_SSE42_X64: { return true; } default: { unreached(); } } } //------------------------------------------------------------------------ // isScalarIsa: Gets a value that indicates whether the InstructionSet is scalar // // Arguments: // isa - The InstructionSet to check // // Return Value: // true if isa is scalar; otherwise, false bool HWIntrinsicInfo::isScalarIsa(InstructionSet isa) { switch (isa) { case InstructionSet_BMI1: case InstructionSet_BMI2: case InstructionSet_BMI1_X64: case InstructionSet_BMI2_X64: case InstructionSet_LZCNT: case InstructionSet_LZCNT_X64: case InstructionSet_POPCNT: case InstructionSet_POPCNT_X64: { return true; } default: { return false; } } } //------------------------------------------------------------------------ // getArgForHWIntrinsic: get the argument from the stack and match the signature // // Arguments: // argType -- the required type of argument // argClass -- the class handle of argType // // Return Value: // get the argument at the given index from the stack and match the signature // GenTree* Compiler::getArgForHWIntrinsic(var_types argType, CORINFO_CLASS_HANDLE argClass) { GenTree* arg = nullptr; if (argType == TYP_STRUCT) { unsigned int argSizeBytes; var_types base = getBaseTypeAndSizeOfSIMDType(argClass, &argSizeBytes); argType = getSIMDTypeForSize(argSizeBytes); assert((argType == TYP_SIMD32) || (argType == TYP_SIMD16)); arg = impSIMDPopStack(argType); assert((arg->TypeGet() == TYP_SIMD16) || (arg->TypeGet() == TYP_SIMD32)); } else { assert(varTypeIsArithmetic(argType)); arg = impPopStack().val; assert(varTypeIsArithmetic(arg->TypeGet())); assert(genActualType(arg->gtType) == genActualType(argType)); } return arg; } //------------------------------------------------------------------------ // impNonConstFallback: convert certain SSE2/AVX2 shift intrinsic to its semantic alternative when the imm-arg is // not a compile-time constant // // Arguments: // intrinsic -- intrinsic ID // simdType -- Vector type // baseType -- base type of the Vector128/256 // // Return Value: // return the IR of semantic alternative on non-const imm-arg // GenTree* Compiler::impNonConstFallback(NamedIntrinsic intrinsic, var_types simdType, var_types baseType) { assert(HWIntrinsicInfo::NoJmpTableImm(intrinsic)); switch (intrinsic) { case NI_SSE2_ShiftLeftLogical: case NI_SSE2_ShiftRightArithmetic: case NI_SSE2_ShiftRightLogical: case NI_AVX2_ShiftLeftLogical: case NI_AVX2_ShiftRightArithmetic: case NI_AVX2_ShiftRightLogical: { GenTree* op2 = impPopStack().val; GenTree* op1 = impSIMDPopStack(simdType); GenTree* tmpOp = gtNewSimdHWIntrinsicNode(TYP_SIMD16, op2, NI_SSE2_ConvertScalarToVector128Int32, TYP_INT, 16); return gtNewSimdHWIntrinsicNode(simdType, op1, tmpOp, intrinsic, baseType, genTypeSize(simdType)); } default: unreached(); return nullptr; } } //------------------------------------------------------------------------ // addRangeCheckIfNeeded: add a GT_HW_INTRINSIC_CHK node for non-full-range imm-intrinsic // // Arguments: // intrinsic -- intrinsic ID // lastOp -- the last operand of the intrinsic that points to the imm-arg // mustExpand -- true if the compiler is compiling the fallback(GT_CALL) of this intrinsics // // Return Value: // add a GT_HW_INTRINSIC_CHK node for non-full-range imm-intrinsic, which would throw ArgumentOutOfRangeException // when the imm-argument is not in the valid range // GenTree* Compiler::addRangeCheckIfNeeded(NamedIntrinsic intrinsic, GenTree* lastOp, bool mustExpand) { assert(lastOp != nullptr); // Full-range imm-intrinsics do not need the range-check // because the imm-parameter of the intrinsic method is a byte. // AVX2 Gather intrinsics no not need the range-check // because their imm-parameter have discrete valid values that are handle by managed code if (mustExpand && !HWIntrinsicInfo::HasFullRangeImm(intrinsic) && HWIntrinsicInfo::isImmOp(intrinsic, lastOp) && !HWIntrinsicInfo::isAVX2GatherIntrinsic(intrinsic)) { assert(!lastOp->IsCnsIntOrI()); GenTree* upperBoundNode = new (this, GT_CNS_INT) GenTreeIntCon(TYP_INT, HWIntrinsicInfo::lookupImmUpperBound(intrinsic)); GenTree* index = nullptr; if ((lastOp->gtFlags & GTF_SIDE_EFFECT) != 0) { index = fgInsertCommaFormTemp(&lastOp); } else { index = gtCloneExpr(lastOp); } GenTreeBoundsChk* hwIntrinsicChk = new (this, GT_HW_INTRINSIC_CHK) GenTreeBoundsChk(GT_HW_INTRINSIC_CHK, TYP_VOID, index, upperBoundNode, SCK_RNGCHK_FAIL); hwIntrinsicChk->gtThrowKind = SCK_ARG_RNG_EXCPN; return gtNewOperNode(GT_COMMA, lastOp->TypeGet(), hwIntrinsicChk, lastOp); } else { return lastOp; } } //------------------------------------------------------------------------ // compSupportsHWIntrinsic: compiler support of hardware intrinsics // // Arguments: // isa - Instruction set // Return Value: // true if // - isa is a scalar ISA // - isa is a SIMD ISA and featureSIMD=true // - isa is fully implemented or EnableIncompleteISAClass=true bool Compiler::compSupportsHWIntrinsic(InstructionSet isa) { return (featureSIMD || HWIntrinsicInfo::isScalarIsa(isa)) && ( #ifdef DEBUG JitConfig.EnableIncompleteISAClass() || #endif HWIntrinsicInfo::isFullyImplementedIsa(isa)); } //------------------------------------------------------------------------ // impIsTableDrivenHWIntrinsic: // // Arguments: // category - category of a HW intrinsic // // Return Value: // returns true if this category can be table-driven in the importer // static bool impIsTableDrivenHWIntrinsic(NamedIntrinsic intrinsicId, HWIntrinsicCategory category) { // HW_Flag_NoCodeGen implies this intrinsic should be manually morphed in the importer. return (category != HW_Category_Special) && (category != HW_Category_Scalar) && HWIntrinsicInfo::RequiresCodegen(intrinsicId) && !HWIntrinsicInfo::HasSpecialImport(intrinsicId); } //------------------------------------------------------------------------ // impHWIntrinsic: dispatch hardware intrinsics to their own implementation // // Arguments: // intrinsic -- id of the intrinsic function. // method -- method handle of the intrinsic function. // sig -- signature of the intrinsic call // // Return Value: // the expanded intrinsic. // GenTree* Compiler::impHWIntrinsic(NamedIntrinsic intrinsic, CORINFO_METHOD_HANDLE method, CORINFO_SIG_INFO* sig, bool mustExpand) { InstructionSet isa = HWIntrinsicInfo::lookupIsa(intrinsic); HWIntrinsicCategory category = HWIntrinsicInfo::lookupCategory(intrinsic); int numArgs = sig->numArgs; var_types retType = JITtype2varType(sig->retType); var_types baseType = TYP_UNKNOWN; if ((retType == TYP_STRUCT) && featureSIMD) { unsigned int sizeBytes; baseType = getBaseTypeAndSizeOfSIMDType(sig->retTypeSigClass, &sizeBytes); retType = getSIMDTypeForSize(sizeBytes); assert(sizeBytes != 0); } // This intrinsic is supported if // - the ISA is available on the underlying hardware (compSupports returns true) // - the compiler supports this hardware intrinsics (compSupportsHWIntrinsic returns true) bool issupported = compSupports(isa) && compSupportsHWIntrinsic(isa); if (category == HW_Category_IsSupportedProperty) { return gtNewIconNode(issupported); } // - calling to unsupported intrinsics must throw PlatforNotSupportedException else if (!issupported) { return impUnsupportedHWIntrinsic(CORINFO_HELP_THROW_PLATFORM_NOT_SUPPORTED, method, sig, mustExpand); } // Avoid checking stacktop for 0-op intrinsics if (sig->numArgs > 0 && HWIntrinsicInfo::isImmOp(intrinsic, impStackTop().val)) { GenTree* lastOp = impStackTop().val; // The imm-HWintrinsics that do not accept all imm8 values may throw // ArgumentOutOfRangeException when the imm argument is not in the valid range if (!HWIntrinsicInfo::HasFullRangeImm(intrinsic)) { if (!mustExpand && lastOp->IsCnsIntOrI() && !HWIntrinsicInfo::isInImmRange(intrinsic, (int)lastOp->AsIntCon()->IconValue())) { return nullptr; } } if (!lastOp->IsCnsIntOrI()) { if (HWIntrinsicInfo::NoJmpTableImm(intrinsic)) { return impNonConstFallback(intrinsic, retType, baseType); } if (!mustExpand) { // When the imm-argument is not a constant and we are not being forced to expand, we need to // return nullptr so a GT_CALL to the intrinsic method is emitted instead. The // intrinsic method is recursive and will be forced to expand, at which point // we emit some less efficient fallback code. return nullptr; } } } bool isTableDriven = impIsTableDrivenHWIntrinsic(intrinsic, category); if (isTableDriven && ((category == HW_Category_MemoryStore) || HWIntrinsicInfo::BaseTypeFromFirstArg(intrinsic) || HWIntrinsicInfo::BaseTypeFromSecondArg(intrinsic))) { if (HWIntrinsicInfo::BaseTypeFromFirstArg(intrinsic)) { baseType = getBaseTypeOfSIMDType(info.compCompHnd->getArgClass(sig, sig->args)); } else { assert((category == HW_Category_MemoryStore) || HWIntrinsicInfo::BaseTypeFromSecondArg(intrinsic)); CORINFO_ARG_LIST_HANDLE secondArg = info.compCompHnd->getArgNext(sig->args); CORINFO_CLASS_HANDLE secondArgClass = info.compCompHnd->getArgClass(sig, secondArg); baseType = getBaseTypeOfSIMDType(secondArgClass); if (baseType == TYP_UNKNOWN) // the second argument is not a vector { baseType = JITtype2varType(strip(info.compCompHnd->getArgType(sig, secondArg, &secondArgClass))); } } } if (HWIntrinsicInfo::IsFloatingPointUsed(intrinsic)) { // Set `compFloatingPointUsed` to cover the scenario where an intrinsic is being on SIMD fields, but // where no SIMD local vars are in use. This is the same logic as is used for FEATURE_SIMD. compFloatingPointUsed = true; } // table-driven importer of simple intrinsics if (isTableDriven) { unsigned simdSize = HWIntrinsicInfo::lookupSimdSize(this, intrinsic, sig); CORINFO_ARG_LIST_HANDLE argList = sig->args; CORINFO_CLASS_HANDLE argClass; var_types argType = TYP_UNKNOWN; assert(numArgs >= 0); assert(HWIntrinsicInfo::lookupIns(intrinsic, baseType) != INS_invalid); assert(simdSize == 32 || simdSize == 16); GenTreeHWIntrinsic* retNode = nullptr; GenTree* op1 = nullptr; GenTree* op2 = nullptr; switch (numArgs) { case 0: retNode = gtNewSimdHWIntrinsicNode(retType, intrinsic, baseType, simdSize); break; case 1: argType = JITtype2varType(strip(info.compCompHnd->getArgType(sig, argList, &argClass))); op1 = getArgForHWIntrinsic(argType, argClass); retNode = gtNewSimdHWIntrinsicNode(retType, op1, intrinsic, baseType, simdSize); break; case 2: argType = JITtype2varType( strip(info.compCompHnd->getArgType(sig, info.compCompHnd->getArgNext(argList), &argClass))); op2 = getArgForHWIntrinsic(argType, argClass); op2 = addRangeCheckIfNeeded(intrinsic, op2, mustExpand); argType = JITtype2varType(strip(info.compCompHnd->getArgType(sig, argList, &argClass))); op1 = getArgForHWIntrinsic(argType, argClass); retNode = gtNewSimdHWIntrinsicNode(retType, op1, op2, intrinsic, baseType, simdSize); break; case 3: { CORINFO_ARG_LIST_HANDLE arg2 = info.compCompHnd->getArgNext(argList); CORINFO_ARG_LIST_HANDLE arg3 = info.compCompHnd->getArgNext(arg2); argType = JITtype2varType(strip(info.compCompHnd->getArgType(sig, arg3, &argClass))); GenTree* op3 = getArgForHWIntrinsic(argType, argClass); op3 = addRangeCheckIfNeeded(intrinsic, op3, mustExpand); argType = JITtype2varType(strip(info.compCompHnd->getArgType(sig, arg2, &argClass))); op2 = getArgForHWIntrinsic(argType, argClass); var_types op2Type; if (intrinsic == NI_AVX2_GatherVector128 || intrinsic == NI_AVX2_GatherVector256) { assert(varTypeIsSIMD(op2->TypeGet())); op2Type = getBaseTypeOfSIMDType(argClass); } argType = JITtype2varType(strip(info.compCompHnd->getArgType(sig, argList, &argClass))); op1 = getArgForHWIntrinsic(argType, argClass); retNode = gtNewSimdHWIntrinsicNode(retType, op1, op2, op3, intrinsic, baseType, simdSize); if (intrinsic == NI_AVX2_GatherVector128 || intrinsic == NI_AVX2_GatherVector256) { assert(varTypeIsSIMD(op2->TypeGet())); retNode->AsHWIntrinsic()->gtIndexBaseType = op2Type; } break; } 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; } // other intrinsics need special importation switch (isa) { case InstructionSet_SSE: return impSSEIntrinsic(intrinsic, method, sig, mustExpand); case InstructionSet_SSE2: return impSSE2Intrinsic(intrinsic, method, sig, mustExpand); case InstructionSet_SSE42: case InstructionSet_SSE42_X64: return impSSE42Intrinsic(intrinsic, method, sig, mustExpand); case InstructionSet_AVX: case InstructionSet_AVX2: return impAvxOrAvx2Intrinsic(intrinsic, method, sig, mustExpand); case InstructionSet_AES: return impAESIntrinsic(intrinsic, method, sig, mustExpand); case InstructionSet_BMI1: case InstructionSet_BMI1_X64: case InstructionSet_BMI2: case InstructionSet_BMI2_X64: return impBMI1OrBMI2Intrinsic(intrinsic, method, sig, mustExpand); case InstructionSet_FMA: return impFMAIntrinsic(intrinsic, method, sig, mustExpand); case InstructionSet_LZCNT: case InstructionSet_LZCNT_X64: return impLZCNTIntrinsic(intrinsic, method, sig, mustExpand); case InstructionSet_PCLMULQDQ: return impPCLMULQDQIntrinsic(intrinsic, method, sig, mustExpand); case InstructionSet_POPCNT: case InstructionSet_POPCNT_X64: return impPOPCNTIntrinsic(intrinsic, method, sig, mustExpand); default: return nullptr; } } GenTree* Compiler::impSSEIntrinsic(NamedIntrinsic intrinsic, CORINFO_METHOD_HANDLE method, CORINFO_SIG_INFO* sig, bool mustExpand) { GenTree* retNode = nullptr; GenTree* op1 = nullptr; GenTree* op2 = nullptr; GenTree* op3 = nullptr; GenTree* op4 = nullptr; int simdSize = HWIntrinsicInfo::lookupSimdSize(this, intrinsic, sig); // The Prefetch and StoreFence intrinsics don't take any SIMD operands // and have a simdSize of 0 assert((simdSize == 16) || (simdSize == 0)); switch (intrinsic) { case NI_SSE_MoveMask: assert(sig->numArgs == 1); assert(JITtype2varType(sig->retType) == TYP_INT); assert(getBaseTypeOfSIMDType(info.compCompHnd->getArgClass(sig, sig->args)) == TYP_FLOAT); op1 = impSIMDPopStack(TYP_SIMD16); retNode = gtNewSimdHWIntrinsicNode(TYP_INT, op1, intrinsic, TYP_FLOAT, simdSize); break; case NI_SSE_Prefetch0: case NI_SSE_Prefetch1: case NI_SSE_Prefetch2: case NI_SSE_PrefetchNonTemporal: { assert(sig->numArgs == 1); assert(JITtype2varType(sig->retType) == TYP_VOID); op1 = impPopStack().val; retNode = gtNewSimdHWIntrinsicNode(TYP_VOID, op1, intrinsic, TYP_UBYTE, 0); break; } case NI_SSE_StoreFence: assert(sig->numArgs == 0); assert(JITtype2varType(sig->retType) == TYP_VOID); retNode = gtNewSimdHWIntrinsicNode(TYP_VOID, intrinsic, TYP_VOID, 0); break; default: JITDUMP("Not implemented hardware intrinsic"); break; } return retNode; } GenTree* Compiler::impSSE2Intrinsic(NamedIntrinsic intrinsic, CORINFO_METHOD_HANDLE method, CORINFO_SIG_INFO* sig, bool mustExpand) { GenTree* retNode = nullptr; GenTree* op1 = nullptr; GenTree* op2 = nullptr; int ival = -1; int simdSize = HWIntrinsicInfo::lookupSimdSize(this, intrinsic, sig); var_types baseType = TYP_UNKNOWN; var_types retType = TYP_UNKNOWN; // The fencing intrinsics don't take any operands and simdSize is 0 assert((simdSize == 16) || (simdSize == 0)); CORINFO_ARG_LIST_HANDLE argList = sig->args; var_types argType = TYP_UNKNOWN; switch (intrinsic) { case NI_SSE2_CompareLessThan: { assert(sig->numArgs == 2); op2 = impSIMDPopStack(TYP_SIMD16); op1 = impSIMDPopStack(TYP_SIMD16); baseType = getBaseTypeOfSIMDType(sig->retTypeSigClass); if (baseType == TYP_DOUBLE) { retNode = gtNewSimdHWIntrinsicNode(TYP_SIMD16, op1, op2, intrinsic, baseType, simdSize); } else { retNode = gtNewSimdHWIntrinsicNode(TYP_SIMD16, op2, op1, NI_SSE2_CompareGreaterThan, baseType, simdSize); } break; } case NI_SSE2_LoadFence: case NI_SSE2_MemoryFence: { assert(sig->numArgs == 0); assert(JITtype2varType(sig->retType) == TYP_VOID); assert(simdSize == 0); retNode = gtNewSimdHWIntrinsicNode(TYP_VOID, intrinsic, TYP_VOID, simdSize); break; } case NI_SSE2_MoveMask: { assert(sig->numArgs == 1); retType = JITtype2varType(sig->retType); assert(retType == TYP_INT); op1 = impSIMDPopStack(TYP_SIMD16); baseType = getBaseTypeOfSIMDType(info.compCompHnd->getArgClass(sig, sig->args)); retNode = gtNewSimdHWIntrinsicNode(retType, op1, intrinsic, baseType, simdSize); break; } case NI_SSE2_StoreNonTemporal: { assert(sig->numArgs == 2); assert(JITtype2varType(sig->retType) == TYP_VOID); op2 = impPopStack().val; op1 = impPopStack().val; retNode = gtNewSimdHWIntrinsicNode(TYP_VOID, op1, op2, NI_SSE2_StoreNonTemporal, op2->TypeGet(), 0); break; } default: JITDUMP("Not implemented hardware intrinsic"); break; } return retNode; } GenTree* Compiler::impSSE42Intrinsic(NamedIntrinsic intrinsic, CORINFO_METHOD_HANDLE method, CORINFO_SIG_INFO* sig, bool mustExpand) { GenTree* retNode = nullptr; GenTree* op1 = nullptr; GenTree* op2 = nullptr; var_types callType = JITtype2varType(sig->retType); CORINFO_ARG_LIST_HANDLE argList = sig->args; CORINFO_CLASS_HANDLE argClass; CorInfoType corType; switch (intrinsic) { case NI_SSE42_Crc32: case NI_SSE42_X64_Crc32: assert(sig->numArgs == 2); op2 = impPopStack().val; op1 = impPopStack().val; argList = info.compCompHnd->getArgNext(argList); // the second argument corType = strip(info.compCompHnd->getArgType(sig, argList, &argClass)); // type of the second argument retNode = gtNewScalarHWIntrinsicNode(callType, op1, op2, intrinsic); // TODO - currently we use the BaseType to bring the type of the second argument // to the code generator. May encode the overload info in other way. retNode->gtHWIntrinsic.gtSIMDBaseType = JITtype2varType(corType); break; default: JITDUMP("Not implemented hardware intrinsic"); break; } return retNode; } GenTree* Compiler::impAvxOrAvx2Intrinsic(NamedIntrinsic intrinsic, CORINFO_METHOD_HANDLE method, CORINFO_SIG_INFO* sig, bool mustExpand) { GenTree* retNode = nullptr; GenTree* op1 = nullptr; GenTree* op2 = nullptr; var_types baseType = TYP_UNKNOWN; int simdSize = HWIntrinsicInfo::lookupSimdSize(this, intrinsic, sig); switch (intrinsic) { case NI_AVX_ExtractVector128: case NI_AVX2_ExtractVector128: { GenTree* lastOp = impPopStack().val; assert(lastOp->IsCnsIntOrI() || mustExpand); GenTree* vectorOp = impSIMDPopStack(TYP_SIMD32); if (sig->numArgs == 2) { baseType = getBaseTypeOfSIMDType(sig->retTypeSigClass); retNode = gtNewSimdHWIntrinsicNode(TYP_SIMD16, vectorOp, lastOp, intrinsic, baseType, 32); } else { assert(sig->numArgs == 3); op1 = impPopStack().val; CORINFO_ARG_LIST_HANDLE secondArg = info.compCompHnd->getArgNext(sig->args); CORINFO_CLASS_HANDLE secondArgClass = info.compCompHnd->getArgClass(sig, secondArg); baseType = getBaseTypeOfSIMDType(secondArgClass); retNode = gtNewSimdHWIntrinsicNode(TYP_VOID, op1, vectorOp, lastOp, intrinsic, baseType, 32); } break; } case NI_AVX2_PermuteVar8x32: { baseType = getBaseTypeOfSIMDType(sig->retTypeSigClass); // swap the two operands GenTree* indexVector = impSIMDPopStack(TYP_SIMD32); GenTree* sourceVector = impSIMDPopStack(TYP_SIMD32); retNode = gtNewSimdHWIntrinsicNode(TYP_SIMD32, indexVector, sourceVector, NI_AVX2_PermuteVar8x32, baseType, 32); break; } case NI_AVX2_GatherMaskVector128: case NI_AVX2_GatherMaskVector256: { CORINFO_ARG_LIST_HANDLE argList = sig->args; CORINFO_CLASS_HANDLE argClass; var_types argType = TYP_UNKNOWN; unsigned int sizeBytes; baseType = getBaseTypeAndSizeOfSIMDType(sig->retTypeSigClass, &sizeBytes); var_types retType = getSIMDTypeForSize(sizeBytes); assert(sig->numArgs == 5); CORINFO_ARG_LIST_HANDLE arg2 = info.compCompHnd->getArgNext(argList); CORINFO_ARG_LIST_HANDLE arg3 = info.compCompHnd->getArgNext(arg2); CORINFO_ARG_LIST_HANDLE arg4 = info.compCompHnd->getArgNext(arg3); CORINFO_ARG_LIST_HANDLE arg5 = info.compCompHnd->getArgNext(arg4); argType = JITtype2varType(strip(info.compCompHnd->getArgType(sig, arg5, &argClass))); GenTree* op5 = getArgForHWIntrinsic(argType, argClass); SetOpLclRelatedToSIMDIntrinsic(op5); argType = JITtype2varType(strip(info.compCompHnd->getArgType(sig, arg4, &argClass))); GenTree* op4 = getArgForHWIntrinsic(argType, argClass); SetOpLclRelatedToSIMDIntrinsic(op4); argType = JITtype2varType(strip(info.compCompHnd->getArgType(sig, arg3, &argClass))); var_types indexbaseType = getBaseTypeOfSIMDType(argClass); GenTree* op3 = getArgForHWIntrinsic(argType, argClass); SetOpLclRelatedToSIMDIntrinsic(op3); argType = JITtype2varType(strip(info.compCompHnd->getArgType(sig, arg2, &argClass))); op2 = getArgForHWIntrinsic(argType, argClass); SetOpLclRelatedToSIMDIntrinsic(op2); argType = JITtype2varType(strip(info.compCompHnd->getArgType(sig, argList, &argClass))); op1 = getArgForHWIntrinsic(argType, argClass); SetOpLclRelatedToSIMDIntrinsic(op1); GenTree* opList = new (this, GT_LIST) GenTreeArgList(op1, gtNewArgList(op2, op3, op4, op5)); retNode = new (this, GT_HWIntrinsic) GenTreeHWIntrinsic(retType, opList, intrinsic, baseType, simdSize); retNode->AsHWIntrinsic()->gtIndexBaseType = indexbaseType; break; } default: JITDUMP("Not implemented hardware intrinsic"); break; } return retNode; } GenTree* Compiler::impAESIntrinsic(NamedIntrinsic intrinsic, CORINFO_METHOD_HANDLE method, CORINFO_SIG_INFO* sig, bool mustExpand) { return nullptr; } GenTree* Compiler::impBMI1OrBMI2Intrinsic(NamedIntrinsic intrinsic, CORINFO_METHOD_HANDLE method, CORINFO_SIG_INFO* sig, bool mustExpand) { var_types callType = JITtype2varType(sig->retType); switch (intrinsic) { case NI_BMI1_AndNot: case NI_BMI1_X64_AndNot: case NI_BMI2_ParallelBitDeposit: case NI_BMI2_ParallelBitExtract: case NI_BMI2_X64_ParallelBitDeposit: case NI_BMI2_X64_ParallelBitExtract: { assert(sig->numArgs == 2); GenTree* op2 = impPopStack().val; GenTree* op1 = impPopStack().val; return gtNewScalarHWIntrinsicNode(callType, op1, op2, intrinsic); } case NI_BMI2_ZeroHighBits: case NI_BMI2_X64_ZeroHighBits: { assert(sig->numArgs == 2); GenTree* op2 = impPopStack().val; GenTree* op1 = impPopStack().val; // Instruction BZHI requires to encode op2 (3rd register) in VEX.vvvv and op1 maybe memory operand, // so swap op1 and op2 to unify the backend code. return gtNewScalarHWIntrinsicNode(callType, op2, op1, intrinsic); } case NI_BMI1_ExtractLowestSetBit: case NI_BMI1_GetMaskUpToLowestSetBit: case NI_BMI1_ResetLowestSetBit: case NI_BMI1_TrailingZeroCount: case NI_BMI1_X64_ExtractLowestSetBit: case NI_BMI1_X64_GetMaskUpToLowestSetBit: case NI_BMI1_X64_ResetLowestSetBit: case NI_BMI1_X64_TrailingZeroCount: { assert(sig->numArgs == 1); GenTree* op1 = impPopStack().val; return gtNewScalarHWIntrinsicNode(callType, op1, intrinsic); } case NI_BMI1_BitFieldExtract: case NI_BMI1_X64_BitFieldExtract: { // The 3-arg version is implemented in managed code if (sig->numArgs == 3) { return nullptr; } assert(sig->numArgs == 2); GenTree* op2 = impPopStack().val; GenTree* op1 = impPopStack().val; // Instruction BEXTR requires to encode op2 (3rd register) in VEX.vvvv and op1 maybe memory operand, // so swap op1 and op2 to unify the backend code. return gtNewScalarHWIntrinsicNode(callType, op2, op1, intrinsic); } case NI_BMI2_MultiplyNoFlags: case NI_BMI2_X64_MultiplyNoFlags: { assert(sig->numArgs == 2 || sig->numArgs == 3); GenTree* op3 = nullptr; if (sig->numArgs == 3) { op3 = impPopStack().val; } GenTree* op2 = impPopStack().val; GenTree* op1 = impPopStack().val; if (sig->numArgs == 3) { return gtNewScalarHWIntrinsicNode(callType, op1, op2, op3, intrinsic); } else { return gtNewScalarHWIntrinsicNode(callType, op1, op2, intrinsic); } } default: { unreached(); return nullptr; } } } GenTree* Compiler::impFMAIntrinsic(NamedIntrinsic intrinsic, CORINFO_METHOD_HANDLE method, CORINFO_SIG_INFO* sig, bool mustExpand) { return nullptr; } GenTree* Compiler::impLZCNTIntrinsic(NamedIntrinsic intrinsic, CORINFO_METHOD_HANDLE method, CORINFO_SIG_INFO* sig, bool mustExpand) { assert(sig->numArgs == 1); var_types callType = JITtype2varType(sig->retType); return gtNewScalarHWIntrinsicNode(callType, impPopStack().val, intrinsic); } GenTree* Compiler::impPCLMULQDQIntrinsic(NamedIntrinsic intrinsic, CORINFO_METHOD_HANDLE method, CORINFO_SIG_INFO* sig, bool mustExpand) { return nullptr; } GenTree* Compiler::impPOPCNTIntrinsic(NamedIntrinsic intrinsic, CORINFO_METHOD_HANDLE method, CORINFO_SIG_INFO* sig, bool mustExpand) { assert(sig->numArgs == 1); var_types callType = JITtype2varType(sig->retType); return gtNewScalarHWIntrinsicNode(callType, impPopStack().val, intrinsic); } #endif // FEATURE_HW_INTRINSICS