diff --git a/src/coreclr/System.Private.CoreLib/CompatibilitySuppressions.xml b/src/coreclr/System.Private.CoreLib/CompatibilitySuppressions.xml index 6a201651f126a..ae179baa9c50e 100644 --- a/src/coreclr/System.Private.CoreLib/CompatibilitySuppressions.xml +++ b/src/coreclr/System.Private.CoreLib/CompatibilitySuppressions.xml @@ -1,3 +1,24 @@  + + + CP0001 + T:Internal.Console + + + CP0001 + T:System.Runtime.CompilerServices.ICastable + + + CP0002 + F:System.Resources.ResourceManager.BaseNameField + + + CP0002 + F:System.Resources.ResourceSet.Reader + + + CP0014 + M:System.Runtime.InteropServices.Marshal.CreateWrapperOfType(System.Object,System.Type)->object?:[T:System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute] + \ No newline at end of file diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index e565c419f06ab..54dd040b20e53 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -3925,6 +3925,7 @@ class Compiler GenTree* addRangeCheckIfNeeded( NamedIntrinsic intrinsic, GenTree* immOp, bool mustExpand, int immLowerBound, int immUpperBound); GenTree* addRangeCheckForHWIntrinsic(GenTree* immOp, int immLowerBound, int immUpperBound); + #endif // FEATURE_HW_INTRINSICS GenTree* impArrayAccessIntrinsic(CORINFO_CLASS_HANDLE clsHnd, CORINFO_SIG_INFO* sig, diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index 0edd913a00bab..8413aab972054 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -19090,6 +19090,8 @@ bool GenTree::isRMWHWIntrinsic(Compiler* comp) case NI_FMA_MultiplySubtractNegated: case NI_FMA_MultiplySubtractNegatedScalar: case NI_FMA_MultiplySubtractScalar: + case NI_X86Base_DivRem: + case NI_X86Base_X64_DivRem: { return true; } @@ -23943,6 +23945,12 @@ ClassLayout* GenTreeHWIntrinsic::GetLayout(Compiler* compiler) const switch (GetHWIntrinsicId()) { +#ifdef TARGET_XARCH + case NI_X86Base_DivRem: + return compiler->typGetBlkLayout(genTypeSize(GetSimdBaseType()) * 2); + case NI_X86Base_X64_DivRem: + return compiler->typGetBlkLayout(16); +#endif // TARGET_XARCH #ifdef TARGET_ARM64 case NI_AdvSimd_Arm64_LoadPairScalarVector64: case NI_AdvSimd_Arm64_LoadPairScalarVector64NonTemporal: diff --git a/src/coreclr/jit/gentree.h b/src/coreclr/jit/gentree.h index 1e3f87248589d..2da896ef3283b 100644 --- a/src/coreclr/jit/gentree.h +++ b/src/coreclr/jit/gentree.h @@ -3683,8 +3683,8 @@ static const unsigned PACKED_GTF_SPILLED = 2; // inline GenTreeFlags GetMultiRegSpillFlagsByIdx(MultiRegSpillFlags flags, unsigned idx) { - static_assert_no_msg(MAX_RET_REG_COUNT * 2 <= sizeof(unsigned char) * BITS_PER_BYTE); - assert(idx < MAX_RET_REG_COUNT); + static_assert_no_msg(MAX_MULTIREG_COUNT * 2 <= sizeof(unsigned char) * BITS_PER_BYTE); + assert(idx < MAX_MULTIREG_COUNT); unsigned bits = flags >> (idx * 2); // It doesn't matter that we possibly leave other high bits here. GenTreeFlags spillFlags = GTF_EMPTY; @@ -3715,8 +3715,8 @@ inline GenTreeFlags GetMultiRegSpillFlagsByIdx(MultiRegSpillFlags flags, unsigne // inline MultiRegSpillFlags SetMultiRegSpillFlagsByIdx(MultiRegSpillFlags oldFlags, GenTreeFlags flagsToSet, unsigned idx) { - static_assert_no_msg(MAX_RET_REG_COUNT * 2 <= sizeof(unsigned char) * BITS_PER_BYTE); - assert(idx < MAX_RET_REG_COUNT); + static_assert_no_msg(MAX_MULTIREG_COUNT * 2 <= sizeof(unsigned char) * BITS_PER_BYTE); + assert(idx < MAX_MULTIREG_COUNT); MultiRegSpillFlags newFlags = oldFlags; unsigned bits = 0; @@ -8057,7 +8057,7 @@ struct GenTreeCopyOrReload : public GenTreeUnOp // State required to support copy/reload of a multi-reg call node. // The first register is always given by GetRegNum(). // - regNumberSmall gtOtherRegs[MAX_RET_REG_COUNT - 1]; + regNumberSmall gtOtherRegs[MAX_MULTIREG_COUNT - 1]; #endif //---------------------------------------------------------- @@ -8072,7 +8072,7 @@ struct GenTreeCopyOrReload : public GenTreeUnOp void ClearOtherRegs() { #if FEATURE_MULTIREG_RET - for (unsigned i = 0; i < MAX_RET_REG_COUNT - 1; ++i) + for (unsigned i = 0; i < MAX_MULTIREG_COUNT - 1; ++i) { gtOtherRegs[i] = REG_NA; } @@ -8090,7 +8090,7 @@ struct GenTreeCopyOrReload : public GenTreeUnOp // regNumber GetRegNumByIdx(unsigned idx) const { - assert(idx < MAX_RET_REG_COUNT); + assert(idx < MAX_MULTIREG_COUNT); if (idx == 0) { @@ -8116,7 +8116,7 @@ struct GenTreeCopyOrReload : public GenTreeUnOp // void SetRegNumByIdx(regNumber reg, unsigned idx) { - assert(idx < MAX_RET_REG_COUNT); + assert(idx < MAX_MULTIREG_COUNT); if (idx == 0) { @@ -8153,7 +8153,7 @@ struct GenTreeCopyOrReload : public GenTreeUnOp assert(OperGet() == from->OperGet()); #ifdef UNIX_AMD64_ABI - for (unsigned i = 0; i < MAX_RET_REG_COUNT - 1; ++i) + for (unsigned i = 0; i < MAX_MULTIREG_COUNT - 1; ++i) { gtOtherRegs[i] = from->gtOtherRegs[i]; } @@ -8170,7 +8170,7 @@ struct GenTreeCopyOrReload : public GenTreeUnOp // but for COPY or RELOAD there is only a valid register for the register positions // that must be copied or reloaded. // - for (unsigned i = MAX_RET_REG_COUNT; i > 1; i--) + for (unsigned i = MAX_MULTIREG_COUNT; i > 1; i--) { if (gtOtherRegs[i - 2] != REG_NA) { @@ -9166,6 +9166,7 @@ inline var_types GenTree::GetRegTypeByIndex(int regIndex) const #endif // !defined(TARGET_64BIT) #endif // FEATURE_MULTIREG_RET +#ifdef FEATURE_HW_INTRINSICS if (OperIsHWIntrinsic()) { assert(TypeGet() == TYP_STRUCT); @@ -9182,9 +9183,10 @@ inline var_types GenTree::GetRegTypeByIndex(int regIndex) const #elif defined(TARGET_XARCH) // At this time, the only multi-reg HW intrinsics all return the type of their // arguments. If this changes, we will need a way to record or determine this. - return gtGetOp1()->TypeGet(); + return AsHWIntrinsic()->Op(1)->TypeGet(); #endif } +#endif // FEATURE_HW_INTRINSICS if (OperIsScalarLocal()) { diff --git a/src/coreclr/jit/hwintrinsic.h b/src/coreclr/jit/hwintrinsic.h index b1299df1c1f1c..f610e61e4f06f 100644 --- a/src/coreclr/jit/hwintrinsic.h +++ b/src/coreclr/jit/hwintrinsic.h @@ -790,6 +790,12 @@ struct HWIntrinsicInfo return 2; #endif +#ifdef TARGET_XARCH + case NI_X86Base_DivRem: + case NI_X86Base_X64_DivRem: + return 2; +#endif // TARGET_XARCH + default: unreached(); } diff --git a/src/coreclr/jit/hwintrinsiccodegenxarch.cpp b/src/coreclr/jit/hwintrinsiccodegenxarch.cpp index cb114b2d19701..cd5f6aa29ed8d 100644 --- a/src/coreclr/jit/hwintrinsiccodegenxarch.cpp +++ b/src/coreclr/jit/hwintrinsiccodegenxarch.cpp @@ -1187,6 +1187,43 @@ void CodeGen::genX86BaseIntrinsic(GenTreeHWIntrinsic* node) break; } + case NI_X86Base_DivRem: + case NI_X86Base_X64_DivRem: + { + assert(node->GetOperandCount() == 3); + + // SIMD base type is from signature and can distinguish signed and unsigned + var_types targetType = node->GetSimdBaseType(); + GenTree* op1 = node->Op(1); + GenTree* op2 = node->Op(2); + GenTree* op3 = node->Op(3); + instruction ins = HWIntrinsicInfo::lookupIns(intrinsicId, targetType); + + regNumber op1Reg = op1->GetRegNum(); + regNumber op2Reg = op2->GetRegNum(); + regNumber op3Reg = op3->GetRegNum(); + + emitAttr attr = emitTypeSize(targetType); + emitter* emit = GetEmitter(); + + // op1: EAX, op2: EDX, op3: free + assert(op1Reg != REG_EDX); + assert(op2Reg != REG_EAX); + if (op3->isUsedFromReg()) + { + assert(op3Reg != REG_EDX); + assert(op3Reg != REG_EAX); + } + + emit->emitIns_Mov(INS_mov, attr, REG_EAX, op1Reg, /* canSkip */ true); + emit->emitIns_Mov(INS_mov, attr, REG_EDX, op2Reg, /* canSkip */ true); + + // emit the DIV/IDIV instruction + emit->emitInsBinary(ins, attr, node, op3); + + break; + } + default: unreached(); break; diff --git a/src/coreclr/jit/hwintrinsiclistxarch.h b/src/coreclr/jit/hwintrinsiclistxarch.h index 8d5c2d16a35cb..d95ba8f43ef69 100644 --- a/src/coreclr/jit/hwintrinsiclistxarch.h +++ b/src/coreclr/jit/hwintrinsiclistxarch.h @@ -238,6 +238,7 @@ HARDWARE_INTRINSIC(Vector256, Xor, HARDWARE_INTRINSIC(X86Base, BitScanForward, 0, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_bsf, INS_bsf, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Scalar, HW_Flag_NoFloatingPointUsed|HW_Flag_NoRMWSemantics) HARDWARE_INTRINSIC(X86Base, BitScanReverse, 0, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_bsr, INS_bsr, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Scalar, HW_Flag_NoFloatingPointUsed|HW_Flag_NoRMWSemantics) HARDWARE_INTRINSIC(X86Base, Pause, 0, 0, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Special, HW_Flag_NoContainment|HW_Flag_NoRMWSemantics) +HARDWARE_INTRINSIC(X86Base, DivRem, 0, 3, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_idiv, INS_div, INS_idiv, INS_div, INS_invalid, INS_invalid}, HW_Category_Scalar, HW_Flag_NoFloatingPointUsed|HW_Flag_BaseTypeFromSecondArg|HW_Flag_MultiReg|HW_Flag_SpecialImport|HW_Flag_SpecialCodeGen) // *************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************** // ISA Function name SIMD size NumArg Instructions Category Flags @@ -246,6 +247,7 @@ HARDWARE_INTRINSIC(X86Base, Pause, // X86Base 64-bit-only Intrinsics HARDWARE_INTRINSIC(X86Base_X64, BitScanForward, 0, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_bsf, INS_bsf, INS_invalid, INS_invalid}, HW_Category_Scalar, HW_Flag_NoFloatingPointUsed|HW_Flag_NoRMWSemantics) HARDWARE_INTRINSIC(X86Base_X64, BitScanReverse, 0, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_bsr, INS_bsr, INS_invalid, INS_invalid}, HW_Category_Scalar, HW_Flag_NoFloatingPointUsed|HW_Flag_NoRMWSemantics) +HARDWARE_INTRINSIC(X86Base_X64, DivRem, 0, 3, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_idiv, INS_div, INS_invalid, INS_invalid}, HW_Category_Scalar, HW_Flag_NoFloatingPointUsed|HW_Flag_BaseTypeFromSecondArg|HW_Flag_MultiReg|HW_Flag_SpecialImport|HW_Flag_SpecialCodeGen) // *************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************** // ISA Function name SIMD size NumArg Instructions Category Flags diff --git a/src/coreclr/jit/hwintrinsicxarch.cpp b/src/coreclr/jit/hwintrinsicxarch.cpp index a253aedc39704..22303b4ad12e4 100644 --- a/src/coreclr/jit/hwintrinsicxarch.cpp +++ b/src/coreclr/jit/hwintrinsicxarch.cpp @@ -513,7 +513,6 @@ GenTree* Compiler::impSpecialIntrinsic(NamedIntrinsic intrinsic, } var_types simdBaseType = TYP_UNKNOWN; - if (simdSize != 0) { simdBaseType = JitType2PreciseVarType(simdBaseJitType); @@ -2366,6 +2365,28 @@ GenTree* Compiler::impSpecialIntrinsic(NamedIntrinsic intrinsic, break; } + case NI_X86Base_DivRem: + case NI_X86Base_X64_DivRem: + { + assert(sig->numArgs == 3); + assert(HWIntrinsicInfo::IsMultiReg(intrinsic)); + assert(retType == TYP_STRUCT); + assert(simdBaseJitType != CORINFO_TYPE_UNDEF); + + op3 = impPopStack().val; + op2 = impPopStack().val; + op1 = impPopStack().val; + + GenTreeHWIntrinsic* divRemIntrinsic = gtNewScalarHWIntrinsicNode(retType, op1, op2, op3, intrinsic); + + // Store the type from signature into SIMD base type for convenience + divRemIntrinsic->SetSimdBaseJitType(simdBaseJitType); + + retNode = impAssignMultiRegTypeToVar(divRemIntrinsic, + sig->retTypeSigClass DEBUGARG(CorInfoCallConvExtension::Managed)); + break; + } + case NI_SSE_CompareScalarGreaterThan: case NI_SSE_CompareScalarGreaterThanOrEqual: case NI_SSE_CompareScalarNotGreaterThan: diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index 5f67160555849..dc327ff6fd426 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -11009,16 +11009,19 @@ GenTree* Compiler::impAssignMultiRegTypeToVar(GenTree* op, { unsigned tmpNum = lvaGrabTemp(true DEBUGARG("Return value temp for multireg return")); impAssignTempGen(tmpNum, op, hClass, CHECK_SPILL_ALL); - GenTree* ret = gtNewLclvNode(tmpNum, lvaTable[tmpNum].lvType); + + LclVarDsc* varDsc = lvaGetDesc(tmpNum); + + // Set "lvIsMultiRegRet" to block promotion under "!lvaEnregMultiRegVars". + varDsc->lvIsMultiRegRet = true; + + GenTreeLclVar* ret = gtNewLclvNode(tmpNum, varDsc->lvType); // TODO-1stClassStructs: Handle constant propagation and CSE-ing of multireg returns. - ret->gtFlags |= GTF_DONT_CSE; + ret->SetDoNotCSE(); assert(IsMultiRegReturnedType(hClass, callConv) || op->IsMultiRegNode()); - // Set "lvIsMultiRegRet" to block promotion under "!lvaEnregMultiRegVars". - lvaTable[tmpNum].lvIsMultiRegRet = true; - return ret; } diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index b25165f3f38b0..ba36ca369b5e6 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -3448,7 +3448,7 @@ void Lowering::LowerRet(GenTreeUnOp* ret) #if FEATURE_MULTIREG_RET if (comp->compMethodReturnsMultiRegRetType() && retVal->OperIs(GT_LCL_VAR)) { - CheckMultiRegLclVar(retVal->AsLclVar(), &comp->compRetTypeDesc); + CheckMultiRegLclVar(retVal->AsLclVar(), comp->compRetTypeDesc.GetReturnRegCount()); } #endif // FEATURE_MULTIREG_RET #ifdef DEBUG @@ -3533,12 +3533,7 @@ void Lowering::LowerStoreLocCommon(GenTreeLclVarCommon* lclStore) if (srcIsMultiReg) { - const ReturnTypeDesc* retTypeDesc = nullptr; - if (src->OperIs(GT_CALL)) - { - retTypeDesc = src->AsCall()->GetReturnTypeDesc(); - } - CheckMultiRegLclVar(lclStore->AsLclVar(), retTypeDesc); + CheckMultiRegLclVar(lclStore->AsLclVar(), src->GetMultiRegCount(comp)); } const var_types lclRegType = varDsc->GetRegisterType(lclStore); @@ -6888,68 +6883,61 @@ bool Lowering::NodesAreEquivalentLeaves(GenTree* tree1, GenTree* tree2) // remain a multi-reg. // // Arguments: -// lclNode - the GT_LCL_VAR or GT_STORE_LCL_VAR node. -// retTypeDesc - a return type descriptor either for a call source of a store of -// the local, or for the GT_RETURN consumer of the local. -// -// Notes: -// If retTypeDesc is non-null, this method will check that the fields are compatible. -// Otherwise, it will only check that the lclVar is independently promoted -// (i.e. it is marked lvPromoted and not lvDoNotEnregister). +// lclNode - the GT_LCL_VAR or GT_STORE_LCL_VAR node. +// registerCount - use register count for uses; source register count for stores. // -bool Lowering::CheckMultiRegLclVar(GenTreeLclVar* lclNode, const ReturnTypeDesc* retTypeDesc) +bool Lowering::CheckMultiRegLclVar(GenTreeLclVar* lclNode, int registerCount) { - bool canEnregister = false; -#if FEATURE_MULTIREG_RET + bool canEnregisterAsMultiReg = false; + bool canEnregisterAsSingleReg = false; + +#if FEATURE_MULTIREG_RET || defined(FEATURE_HW_INTRINSICS) LclVarDsc* varDsc = comp->lvaGetDesc(lclNode->GetLclNum()); + if (varDsc->lvDoNotEnregister) + { + assert(!lclNode->IsMultiReg()); + return false; + } + if ((comp->lvaEnregMultiRegVars) && varDsc->lvPromoted) { // We can enregister if we have a promoted struct and all the fields' types match the ABI requirements. // Note that we don't promote structs with explicit layout, so we don't need to check field offsets, and // if we have multiple types packed into a single register, we won't have matching reg and field counts, // so we can tolerate mismatches of integer size. - if (varDsc->lvPromoted && (comp->lvaGetPromotionType(varDsc) == Compiler::PROMOTION_TYPE_INDEPENDENT)) + if (comp->lvaGetPromotionType(varDsc) == Compiler::PROMOTION_TYPE_INDEPENDENT) { - // If we have no retTypeDesc, we only care that it is independently promoted. - if (retTypeDesc == nullptr) + if (registerCount == varDsc->lvFieldCnt) { - canEnregister = true; - } - else - { - unsigned regCount = retTypeDesc->GetReturnRegCount(); - - if (regCount == varDsc->lvFieldCnt) - { - canEnregister = true; - } + canEnregisterAsMultiReg = true; } } } -#ifdef TARGET_XARCH - // For local stores on XARCH we only handle mismatched src/dest register count for calls of SIMD type. - // If the source was another lclVar similarly promoted, we would have broken it into multiple stores. - if (lclNode->OperIs(GT_STORE_LCL_VAR) && varTypeIsStruct(lclNode->Data()) && !lclNode->Data()->OperIs(GT_CALL)) + else { - canEnregister = false; - } + canEnregisterAsSingleReg = varTypeIsSIMD(lclNode); +#ifdef TARGET_XARCH + if (lclNode->OperIs(GT_STORE_LCL_VAR) && varTypeIsStruct(lclNode->Data()) && !lclNode->Data()->OperIs(GT_CALL)) + { + canEnregisterAsSingleReg = false; + } #endif // TARGET_XARCH + } - if (canEnregister) + if (canEnregisterAsSingleReg || canEnregisterAsMultiReg) { - lclNode->SetMultiReg(); + if (canEnregisterAsMultiReg) + { + lclNode->SetMultiReg(); + } } else { - lclNode->ClearMultiReg(); - if (varDsc->lvPromoted && !varDsc->lvDoNotEnregister) - { - comp->lvaSetVarDoNotEnregister(lclNode->GetLclNum() DEBUGARG(DoNotEnregisterReason::BlockOp)); - } + comp->lvaSetVarDoNotEnregister(lclNode->GetLclNum() DEBUGARG(DoNotEnregisterReason::BlockOp)); } -#endif +#endif // FEATURE_MULTIREG_RET || defined(FEATURE_HW_INTRINSICS) - return canEnregister; + return canEnregisterAsSingleReg || canEnregisterAsMultiReg; } //------------------------------------------------------------------------ diff --git a/src/coreclr/jit/lower.h b/src/coreclr/jit/lower.h index 23aef6fbaea2e..e1e30f8a675af 100644 --- a/src/coreclr/jit/lower.h +++ b/src/coreclr/jit/lower.h @@ -339,7 +339,7 @@ class Lowering final : public Phase #endif void WidenSIMD12IfNecessary(GenTreeLclVarCommon* node); - bool CheckMultiRegLclVar(GenTreeLclVar* lclNode, const ReturnTypeDesc* retTypeDesc); + bool CheckMultiRegLclVar(GenTreeLclVar* lclNode, int registerCount); void LowerStoreLoc(GenTreeLclVarCommon* tree); GenTree* LowerArrElem(GenTreeArrElem* arrElem); void LowerRotate(GenTree* tree); diff --git a/src/coreclr/jit/lowerxarch.cpp b/src/coreclr/jit/lowerxarch.cpp index 06fe0feb6019f..df2d4ea3a78c6 100644 --- a/src/coreclr/jit/lowerxarch.cpp +++ b/src/coreclr/jit/lowerxarch.cpp @@ -7354,6 +7354,20 @@ void Lowering::ContainCheckHWIntrinsic(GenTreeHWIntrinsic* node) } break; } + case NI_X86Base_DivRem: + case NI_X86Base_X64_DivRem: + { + // DIV only allows divisor (op3) in memory + if (IsContainableHWIntrinsicOp(node, op3, &supportsRegOptional)) + { + MakeSrcContained(node, op3); + } + else if (supportsRegOptional) + { + MakeSrcRegOptional(node, op3); + } + break; + } default: { diff --git a/src/coreclr/jit/lsra.h b/src/coreclr/jit/lsra.h index c63d0755ba4b1..888ca543d3f6a 100644 --- a/src/coreclr/jit/lsra.h +++ b/src/coreclr/jit/lsra.h @@ -1866,7 +1866,11 @@ class LinearScan : public LinearScanInterface int BuildSimple(GenTree* tree); int BuildOperandUses(GenTree* node, regMaskTP candidates = RBM_NONE); - int BuildDelayFreeUses(GenTree* node, GenTree* rmwNode = nullptr, regMaskTP candidates = RBM_NONE); + void AddDelayFreeUses(RefPosition* refPosition, GenTree* rmwNode); + int BuildDelayFreeUses(GenTree* node, + GenTree* rmwNode = nullptr, + regMaskTP candidates = RBM_NONE, + RefPosition** useRefPosition = nullptr); int BuildIndirUses(GenTreeIndir* indirTree, regMaskTP candidates = RBM_NONE); int BuildAddrUses(GenTree* addr, regMaskTP candidates = RBM_NONE); void HandleFloatVarArgs(GenTreeCall* call, GenTree* argNode, bool* callHasFloatRegArgs); @@ -2262,9 +2266,9 @@ class RefPosition // Used by RefTypeDef/Use positions of a multi-reg call node. // Indicates the position of the register that this ref position refers to. - // The max bits needed is based on max value of MAX_RET_REG_COUNT value + // The max bits needed is based on max value of MAX_MULTIREG_COUNT value // across all targets and that happened to be 4 on Arm. Hence index value - // would be 0..MAX_RET_REG_COUNT-1. + // would be 0..MAX_MULTIREG_COUNT-1. unsigned char multiRegIdx : 2; // Last Use - this may be true for multiple RefPositions in the same Interval diff --git a/src/coreclr/jit/lsrabuild.cpp b/src/coreclr/jit/lsrabuild.cpp index 3908f1998792a..e8638c7d15eb0 100644 --- a/src/coreclr/jit/lsrabuild.cpp +++ b/src/coreclr/jit/lsrabuild.cpp @@ -3257,23 +3257,20 @@ void LinearScan::setDelayFree(RefPosition* use) } //------------------------------------------------------------------------ -// BuildDelayFreeUses: Build Use RefPositions for an operand that might be contained, -// and which may need to be marked delayRegFree +// AddDelayFreeUses: Mark useRefPosition as delay-free, if applicable, for the +// rmw node. // // Arguments: -// node - The node of interest -// rmwNode - The node that has RMW semantics (if applicable) -// candidates - The set of candidates for the uses -// -// Return Value: -// The number of source registers used by the *parent* of this node. +// useRefPosition - The use refposition that need to be delay-freed. +// rmwNode - The node that has RMW semantics (if applicable) // -int LinearScan::BuildDelayFreeUses(GenTree* node, GenTree* rmwNode, regMaskTP candidates) +void LinearScan::AddDelayFreeUses(RefPosition* useRefPosition, GenTree* rmwNode) { - RefPosition* use = nullptr; - Interval* rmwInterval = nullptr; - bool rmwIsLastUse = false; - GenTree* addr = nullptr; + assert(useRefPosition != nullptr); + + Interval* rmwInterval = nullptr; + bool rmwIsLastUse = false; + GenTree* addr = nullptr; if ((rmwNode != nullptr) && isCandidateLocalRef(rmwNode)) { rmwInterval = getIntervalForLocalVarNode(rmwNode->AsLclVar()); @@ -3282,6 +3279,41 @@ int LinearScan::BuildDelayFreeUses(GenTree* node, GenTree* rmwNode, regMaskTP ca assert(!rmwNode->AsLclVar()->IsMultiReg()); rmwIsLastUse = rmwNode->AsLclVar()->IsLastUse(0); } + // If node != rmwNode, then definitely node should be marked as "delayFree". + // However, if node == rmwNode, then we can mark node as "delayFree" only if + // none of the node/rmwNode are the last uses. If either of them are last use, + // we can safely reuse the rmwNode as destination. + if ((useRefPosition->getInterval() != rmwInterval) || (!rmwIsLastUse && !useRefPosition->lastUse)) + { + setDelayFree(useRefPosition); + } +} + +//------------------------------------------------------------------------ +// BuildDelayFreeUses: Build Use RefPositions for an operand that might be contained, +// and which may need to be marked delayRegFree +// +// Arguments: +// node - The node of interest +// rmwNode - The node that has RMW semantics (if applicable) +// candidates - The set of candidates for the uses +// useRefPositionRef - If a use refposition is created, returns it. If none created, sets it to nullptr. +// +// Return Value: +// The number of source registers used by the *parent* of this node. +// +int LinearScan::BuildDelayFreeUses(GenTree* node, + GenTree* rmwNode, + regMaskTP candidates, + RefPosition** useRefPositionRef) +{ + RefPosition* use = nullptr; + GenTree* addr = nullptr; + if (useRefPositionRef != nullptr) + { + *useRefPositionRef = nullptr; + } + if (!node->isContained()) { use = BuildUse(node, candidates); @@ -3320,14 +3352,7 @@ int LinearScan::BuildDelayFreeUses(GenTree* node, GenTree* rmwNode, regMaskTP ca } if (use != nullptr) { - // If node != rmwNode, then definitely node should be marked as "delayFree". - // However, if node == rmwNode, then we can mark node as "delayFree" only if - // none of the node/rmwNode are the last uses. If either of them are last use, - // we can safely reuse the rmwNode as destination. - if ((use->getInterval() != rmwInterval) || (!rmwIsLastUse && !use->lastUse)) - { - setDelayFree(use); - } + AddDelayFreeUses(use, rmwNode); return 1; } @@ -3339,21 +3364,22 @@ int LinearScan::BuildDelayFreeUses(GenTree* node, GenTree* rmwNode, regMaskTP ca if ((addrMode->Base() != nullptr) && !addrMode->Base()->isContained()) { use = BuildUse(addrMode->Base(), candidates); - if ((use->getInterval() != rmwInterval) || (!rmwIsLastUse && !use->lastUse)) - { - setDelayFree(use); - } + AddDelayFreeUses(use, rmwNode); + srcCount++; } if ((addrMode->Index() != nullptr) && !addrMode->Index()->isContained()) { use = BuildUse(addrMode->Index(), candidates); - if ((use->getInterval() != rmwInterval) || (!rmwIsLastUse && !use->lastUse)) - { - setDelayFree(use); - } + AddDelayFreeUses(use, rmwNode); + srcCount++; } + + if (useRefPositionRef != nullptr) + { + *useRefPositionRef = use; + } return srcCount; } diff --git a/src/coreclr/jit/lsraxarch.cpp b/src/coreclr/jit/lsraxarch.cpp index 524bbc8577e96..3b9b11def2cb5 100644 --- a/src/coreclr/jit/lsraxarch.cpp +++ b/src/coreclr/jit/lsraxarch.cpp @@ -1967,7 +1967,23 @@ int LinearScan::BuildHWIntrinsic(GenTreeHWIntrinsic* intrinsicTree, int* pDstCou } int srcCount = 0; - int dstCount = intrinsicTree->IsValue() ? 1 : 0; + int dstCount; + + if (intrinsicTree->IsValue()) + { + if (HWIntrinsicInfo::IsMultiReg(intrinsicId)) + { + dstCount = HWIntrinsicInfo::GetMultiRegCount(intrinsicId); + } + else + { + dstCount = 1; + } + } + else + { + dstCount = 0; + } regMaskTP dstCandidates = RBM_NONE; @@ -2152,6 +2168,45 @@ int LinearScan::BuildHWIntrinsic(GenTreeHWIntrinsic* intrinsicTree, int* pDstCou } #endif // TARGET_X86 + case NI_X86Base_DivRem: + case NI_X86Base_X64_DivRem: + { + assert(numArgs == 3); + assert(dstCount == 2); + assert(isRMW); + + // DIV implicitly put op1(lower) to EAX and op2(upper) to EDX + srcCount += BuildOperandUses(op1, RBM_EAX); + srcCount += BuildOperandUses(op2, RBM_EDX); + + if (!op3->isContained()) + { + // For non-contained nodes, we want to make sure we delay free the register for + // op3 with respect to both op1 and op2. In other words, op3 shouldn't get same + // register that is assigned to either of op1 and op2. + + RefPosition* op3RefPosition; + srcCount += BuildDelayFreeUses(op3, op1, RBM_NONE, &op3RefPosition); + if ((op3RefPosition != nullptr) && !op3RefPosition->delayRegFree) + { + // If op3 was not marked as delay-free for op1, mark it as delay-free + // if needed for op2. + AddDelayFreeUses(op3RefPosition, op2); + } + } + else + { + srcCount += BuildOperandUses(op3); + } + + // result put in EAX and EDX + BuildDef(intrinsicTree, RBM_EAX, 0); + BuildDef(intrinsicTree, RBM_EDX, 1); + + buildUses = false; + break; + } + case NI_BMI2_MultiplyNoFlags: case NI_BMI2_X64_MultiplyNoFlags: { @@ -2422,7 +2477,9 @@ int LinearScan::BuildHWIntrinsic(GenTreeHWIntrinsic* intrinsicTree, int* pDstCou } else { - assert(dstCount == 0); + // Currently dstCount = 2 is only used for DivRem, which has special constriants and handled above + assert((dstCount == 0) || + ((dstCount == 2) && ((intrinsicId == NI_X86Base_DivRem) || (intrinsicId == NI_X86Base_X64_DivRem)))); } *pDstCount = dstCount; diff --git a/src/libraries/System.Private.CoreLib/ref/System.Private.CoreLib.ExtraApis.cs b/src/libraries/System.Private.CoreLib/ref/System.Private.CoreLib.ExtraApis.cs index 84e99da5aa050..5465d3601a7b7 100644 --- a/src/libraries/System.Private.CoreLib/ref/System.Private.CoreLib.ExtraApis.cs +++ b/src/libraries/System.Private.CoreLib/ref/System.Private.CoreLib.ExtraApis.cs @@ -37,3 +37,19 @@ public static partial class Debug public static System.Diagnostics.DebugProvider SetProvider(System.Diagnostics.DebugProvider provider) { throw null; } } } +namespace System.Runtime.Intrinsics.X86 +{ + public abstract partial class X86Base + { + public abstract partial class X64 + { + public static (ulong Quotient, ulong Remainder) DivRem(ulong lower, ulong upper, ulong divisor) { throw null; } + public static (long Quotient, long Remainder) DivRem(ulong lower, long upper, long divisor) { throw null; } + } + + public static (uint Quotient, uint Remainder) DivRem(uint lower, uint upper, uint divisor) { throw null; } + public static (int Quotient, int Remainder) DivRem(uint lower, int upper, int divisor) { throw null; } + public static (nuint Quotient, nuint Remainder) DivRem(nuint lower, nuint upper, nuint divisor) { throw null; } + public static (nint Quotient, nint Remainder) DivRem(nuint lower, nint upper, nint divisor) { throw null; } + } +} \ No newline at end of file diff --git a/src/libraries/System.Private.CoreLib/ref/System.Private.CoreLib.ExtraApis.txt b/src/libraries/System.Private.CoreLib/ref/System.Private.CoreLib.ExtraApis.txt index 0babd819e25d0..ef258ffce6cdd 100644 --- a/src/libraries/System.Private.CoreLib/ref/System.Private.CoreLib.ExtraApis.txt +++ b/src/libraries/System.Private.CoreLib/ref/System.Private.CoreLib.ExtraApis.txt @@ -5,3 +5,9 @@ T:System.Runtime.Serialization.DeserializationToken M:System.Runtime.Serialization.SerializationInfo.StartDeserialization T:System.Diagnostics.DebugProvider M:System.Diagnostics.Debug.SetProvider(System.Diagnostics.DebugProvider) +M:System.Runtime.Intrinsics.X86.X86Base.X64.DivRem(System.UInt64 lower, System.UInt64 upper, System.UInt64 divisor) +M:System.Runtime.Intrinsics.X86.X86Base.X64.DivRem(System.UInt64 lower, System.Int64 upper, System.Int64 divisor) +M:System.Runtime.Intrinsics.X86.X86Base.DivRem(uint lower, uint upper, uint divisor) +M:System.Runtime.Intrinsics.X86.X86Base.DivRem(int lower, int upper, int divisor) +M:System.Runtime.Intrinsics.X86.X86Base.DivRem(nuint lower, nuint upper, nuint divisor) +M:System.Runtime.Intrinsics.X86.X86Base.DivRem(nuint lower, nint upper, nint divisor) \ No newline at end of file diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/X86/X86Base.PlatformNotSupported.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/X86/X86Base.PlatformNotSupported.cs index ad93f26912ee1..ba25f79678146 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/X86/X86Base.PlatformNotSupported.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/X86/X86Base.PlatformNotSupported.cs @@ -9,6 +9,7 @@ namespace System.Runtime.Intrinsics.X86 /// /// This class provides access to the x86 base hardware instructions via intrinsics /// + [CLSCompliant(false)] public abstract partial class X86Base { internal X86Base() { } @@ -44,6 +45,18 @@ internal X64() { } /// Its functionality is exposed in the public class. /// internal static ulong BitScanReverse(ulong value) { throw new PlatformNotSupportedException(); } + + /// + /// unsigned __int64 _udiv128(unsigned __int64 highdividend, unsigned __int64 lowdividend, unsigned __int64 divisor, unsigned __int64* remainder) + /// DIV reg/m64 + /// + public static (ulong Quotient, ulong Remainder) DivRem(ulong lower, ulong upper, ulong divisor) { throw new PlatformNotSupportedException(); } + + /// + /// __int64 _div128(__int64 highdividend, __int64 lowdividend, __int64 divisor, __int64* remainder) + /// DIV reg/m64 + /// + public static (long Quotient, long Remainder) DivRem(ulong lower, long upper, long divisor) { throw new PlatformNotSupportedException(); } } /// @@ -74,6 +87,26 @@ internal X64() { } /// public static (int Eax, int Ebx, int Ecx, int Edx) CpuId(int functionId, int subFunctionId) { throw new PlatformNotSupportedException(); } + /// + /// DIV reg/m32 + /// + public static (uint Quotient, uint Remainder) DivRem(uint lower, uint upper, uint divisor) { throw new PlatformNotSupportedException(); } + + /// + /// IDIV reg/m32 + /// + public static (int Quotient, int Remainder) DivRem(uint lower, int upper, int divisor) { throw new PlatformNotSupportedException(); } + + /// + /// IDIV reg/m + /// + public static (nuint Quotient, nuint Remainder) DivRem(nuint lower, nuint upper, nuint divisor) { throw new PlatformNotSupportedException(); } + + /// + /// IDIV reg/m + /// + public static (nint Quotient, nint Remainder) DivRem(nuint lower, nint upper, nint divisor) { throw new PlatformNotSupportedException(); } + /// /// void _mm_pause (void); /// PAUSE diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/X86/X86Base.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/X86/X86Base.cs index c8b230b86166b..6ba4107549e42 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/X86/X86Base.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/X86/X86Base.cs @@ -10,6 +10,9 @@ namespace System.Runtime.Intrinsics.X86 /// This class provides access to the x86 base hardware instructions via intrinsics /// [Intrinsic] +#if SYSTEM_PRIVATE_CORELIB + [CLSCompliant(false)] +#endif public abstract partial class X86Base { internal X86Base() { } @@ -44,6 +47,18 @@ internal X64() { } /// Its functionality is exposed in the public class. /// internal static ulong BitScanReverse(ulong value) => BitScanReverse(value); + + /// + /// unsigned __int64 _udiv128(unsigned __int64 highdividend, unsigned __int64 lowdividend, unsigned __int64 divisor, unsigned __int64* remainder) + /// DIV reg/m64 + /// + public static (ulong Quotient, ulong Remainder) DivRem(ulong lower, ulong upper, ulong divisor) => DivRem(lower, upper, divisor); + + /// + /// __int64 _div128(__int64 highdividend, __int64 lowdividend, __int64 divisor, __int64* remainder) + /// DIV reg/m64 + /// + public static (long Quotient, long Remainder) DivRem(ulong lower, long upper, long divisor) => DivRem(lower, upper, divisor); } /// @@ -74,11 +89,37 @@ internal X64() { } /// public static unsafe (int Eax, int Ebx, int Ecx, int Edx) CpuId(int functionId, int subFunctionId) { +#if SYSTEM_PRIVATE_CORELIB int* cpuInfo = stackalloc int[4]; __cpuidex(cpuInfo, functionId, subFunctionId); return (cpuInfo[0], cpuInfo[1], cpuInfo[2], cpuInfo[3]); +#else + return (0, 0, 0, 0); +#endif } + /// + /// unsigned _udiv64(unsigned __int64 dividend, unsigned divisor, unsigned* remainder) + /// DIV reg/m32 + /// + public static (uint Quotient, uint Remainder) DivRem(uint lower, uint upper, uint divisor) => DivRem(lower, upper, divisor); + + /// + /// int _div64(__int64 dividend, int divisor, int* remainder) + /// IDIV reg/m32 + /// + public static (int Quotient, int Remainder) DivRem(uint lower, int upper, int divisor) => DivRem(lower, upper, divisor); + + /// + /// IDIV reg/m + /// + public static (nuint Quotient, nuint Remainder) DivRem(nuint lower, nuint upper, nuint divisor) => DivRem(lower, upper, divisor); + + /// + /// IDIV reg/m + /// + public static (nint Quotient, nint Remainder) DivRem(nuint lower, nint upper, nint divisor) => DivRem(lower, upper, divisor); + /// /// void _mm_pause (void); /// PAUSE diff --git a/src/libraries/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.cs b/src/libraries/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.cs index fec48c2c5c1be..47885edc44bda 100644 --- a/src/libraries/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.cs +++ b/src/libraries/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.cs @@ -5210,6 +5210,8 @@ internal X64() { } public static new bool IsSupported { get { throw null; } } } } + + [System.CLSCompliantAttribute(false)] public abstract partial class X86Base { internal X86Base() { } diff --git a/src/libraries/System.Runtime.Intrinsics/src/CompatibilitySuppressions.xml b/src/libraries/System.Runtime.Intrinsics/src/CompatibilitySuppressions.xml new file mode 100644 index 0000000000000..3f4ec97bc8d07 --- /dev/null +++ b/src/libraries/System.Runtime.Intrinsics/src/CompatibilitySuppressions.xml @@ -0,0 +1,76 @@ + + + + + CP0002 + M:System.Runtime.Intrinsics.X86.X86Base.DivRem(nuint lower, nint upper, nint divisor) + ref/net8.0/System.Runtime.Intrinsics.dll + lib/net8.0/System.Runtime.Intrinsics.dll + + + CP0002 + M:System.Runtime.Intrinsics.X86.X86Base.DivRem(nuint lower, nuint upper, nuint divisor) + ref/net8.0/System.Runtime.Intrinsics.dll + lib/net8.0/System.Runtime.Intrinsics.dll + + + CP0002 + M:System.Runtime.Intrinsics.X86.X86Base.DivRem(System.UInt32 lower, System.Int32 upper, System.Int32 divisor) + ref/net8.0/System.Runtime.Intrinsics.dll + lib/net8.0/System.Runtime.Intrinsics.dll + + + CP0002 + M:System.Runtime.Intrinsics.X86.X86Base.DivRem(System.UInt32 lower, System.UInt32 upper, System.UInt32 divisor) + ref/net8.0/System.Runtime.Intrinsics.dll + lib/net8.0/System.Runtime.Intrinsics.dll + + + CP0002 + M:System.Runtime.Intrinsics.X86.X86Base.DivRem(System.UInt32,System.Int32,System.Int32) + ref/net8.0/System.Runtime.Intrinsics.dll + lib/net8.0/System.Runtime.Intrinsics.dll + + + CP0002 + M:System.Runtime.Intrinsics.X86.X86Base.DivRem(System.UInt32,System.UInt32,System.UInt32) + ref/net8.0/System.Runtime.Intrinsics.dll + lib/net8.0/System.Runtime.Intrinsics.dll + + + CP0002 + M:System.Runtime.Intrinsics.X86.X86Base.DivRem(System.UIntPtr,System.IntPtr,System.IntPtr) + ref/net8.0/System.Runtime.Intrinsics.dll + lib/net8.0/System.Runtime.Intrinsics.dll + + + CP0002 + M:System.Runtime.Intrinsics.X86.X86Base.DivRem(System.UIntPtr,System.UIntPtr,System.UIntPtr) + ref/net8.0/System.Runtime.Intrinsics.dll + lib/net8.0/System.Runtime.Intrinsics.dll + + + CP0002 + M:System.Runtime.Intrinsics.X86.X86Base.X64.DivRem(System.UInt64 lower, System.Int64 upper, System.Int64 divisor) + ref/net8.0/System.Runtime.Intrinsics.dll + lib/net8.0/System.Runtime.Intrinsics.dll + + + CP0002 + M:System.Runtime.Intrinsics.X86.X86Base.X64.DivRem(System.UInt64 lower, System.UInt64 upper, System.UInt64 divisor) + ref/net8.0/System.Runtime.Intrinsics.dll + lib/net8.0/System.Runtime.Intrinsics.dll + + + CP0002 + M:System.Runtime.Intrinsics.X86.X86Base.X64.DivRem(System.UInt64,System.Int64,System.Int64) + ref/net8.0/System.Runtime.Intrinsics.dll + lib/net8.0/System.Runtime.Intrinsics.dll + + + CP0002 + M:System.Runtime.Intrinsics.X86.X86Base.X64.DivRem(System.UInt64,System.UInt64,System.UInt64) + ref/net8.0/System.Runtime.Intrinsics.dll + lib/net8.0/System.Runtime.Intrinsics.dll + + \ No newline at end of file diff --git a/src/tests/Common/GenerateHWIntrinsicTests/GenerateHWIntrinsicTests_X86.cs b/src/tests/Common/GenerateHWIntrinsicTests/GenerateHWIntrinsicTests_X86.cs index c20fca73cef70..52f7d523ea61d 100644 --- a/src/tests/Common/GenerateHWIntrinsicTests/GenerateHWIntrinsicTests_X86.cs +++ b/src/tests/Common/GenerateHWIntrinsicTests/GenerateHWIntrinsicTests_X86.cs @@ -1264,6 +1264,20 @@ ("ScalarTernOpBinResTest.template", new Dictionary { ["Isa"] = "Bmi2.X64", ["Method"] = "MultiplyNoFlags", ["RetBaseType"] = "UInt64", ["Op1BaseType"] = "UInt64", ["Op2BaseType"] = "UInt64", ["Op3BaseType"] = "UInt64", ["NextValueOp1"] = "UInt64.MaxValue", ["NextValueOp2"] = "UInt64.MaxValue", ["NextValueOp3"] = "0", ["ValidateResult"] = "ulong expectedHigher = 18446744073709551614, expectedLower = 1; isUnexpectedResult = (expectedHigher != higher) || (expectedLower != lower);" }), }; +(string templateFileName, Dictionary templateData)[] X86BaseInputs = new [] +{ + ("ScalarTernOpTupleBinRetTest.template", new Dictionary { ["Isa"] = "X86Base", ["Method"] = "DivRem", ["RetBaseType"] = "Int32", ["Op1BaseType"] = "UInt32", ["Op2BaseType"] = "Int32", ["Op3BaseType"] = "Int32", ["NextValueOp1"] = "UInt32.MaxValue", ["NextValueOp2"] = "-2", ["NextValueOp3"] = "-0x10001", ["ValidateResult"] = " int expectedQuotient = 0xFFFF; int expectedReminder = -2; isUnexpectedResult = (expectedQuotient != ret1) || (expectedReminder != ret2);" }), + ("ScalarTernOpTupleBinRetTest.template", new Dictionary { ["Isa"] = "X86Base", ["Method"] = "DivRem", ["RetBaseType"] = "UInt32", ["Op1BaseType"] = "UInt32", ["Op2BaseType"] = "UInt32", ["Op3BaseType"] = "UInt32", ["NextValueOp1"] = "1", ["NextValueOp2"] = "1", ["NextValueOp3"] = "0x80000000", ["ValidateResult"] = "uint expectedQuotient = 2; uint expectedReminder = 1; isUnexpectedResult = (expectedQuotient != ret1) || (expectedReminder != ret2);" }), + ("ScalarTernOpTupleBinRetTest.template", new Dictionary { ["Isa"] = "X86Base", ["Method"] = "DivRem", ["RetBaseType"] = "nint", ["Op1BaseType"] = "nuint", ["Op2BaseType"] = "nint", ["Op3BaseType"] = "nint", ["NextValueOp1"] = "nuint.MaxValue", ["NextValueOp2"] = "-2", ["NextValueOp3"] = "-((nint)1 << (IntPtr.Size * 4)) - 1", ["ValidateResult"] = " nint expectedQuotient = ((nint)1 << (IntPtr.Size * 4)) - 1; nint expectedReminder = -2; isUnexpectedResult = (expectedQuotient != ret1) || (expectedReminder != ret2);" }), + ("ScalarTernOpTupleBinRetTest.template", new Dictionary { ["Isa"] = "X86Base", ["Method"] = "DivRem", ["RetBaseType"] = "nuint", ["Op1BaseType"] = "nuint", ["Op2BaseType"] = "nuint", ["Op3BaseType"] = "nuint", ["NextValueOp1"] = "1", ["NextValueOp2"] = "1", ["NextValueOp3"] = "(nuint)nint.MinValue", ["ValidateResult"] = "nuint expectedQuotient = 2; nuint expectedReminder = 1; isUnexpectedResult = (expectedQuotient != ret1) || (expectedReminder != ret2);" }), +}; + +(string templateFileName, Dictionary templateData)[] X86BaseX64Inputs = new [] +{ + ("ScalarTernOpTupleBinRetTest.template", new Dictionary { ["Isa"] = "X86Base.X64", ["Method"] = "DivRem", ["RetBaseType"] = "Int64", ["Op1BaseType"] = "UInt64", ["Op2BaseType"] = "Int64", ["Op3BaseType"] = "Int64", ["NextValueOp1"] = "UInt64.MaxValue", ["NextValueOp2"] = "-2", ["NextValueOp3"] = "-0x100000001", ["ValidateResult"] = " long expectedQuotient = 0xFFFFFFFF; long expectedReminder = -2; isUnexpectedResult = (expectedQuotient != ret1) || (expectedReminder != ret2);" }), + ("ScalarTernOpTupleBinRetTest.template", new Dictionary { ["Isa"] = "X86Base.X64", ["Method"] = "DivRem", ["RetBaseType"] = "UInt64", ["Op1BaseType"] = "UInt64", ["Op2BaseType"] = "UInt64", ["Op3BaseType"] = "UInt64", ["NextValueOp1"] = "1", ["NextValueOp2"] = "1", ["NextValueOp3"] = "0x8000000000000000", ["ValidateResult"] = "ulong expectedQuotient = 2; ulong expectedReminder = 1; isUnexpectedResult = (expectedQuotient != ret1) || (expectedReminder != ret2);" }), +}; + Dictionary extraHelperFiles = new Dictionary { ["Sse2Verify"] = @"..\Sse2\Sse2Verify.cs", @@ -1374,6 +1388,11 @@ void ProcessInput(StreamWriter testListFile, string groupName, (string templateF testName += ".BinRes"; suffix += "BinRes"; } + else if (input.templateFileName == "ScalarTernOpTupleBinRetTest.template") + { + testName += ".Tuple3Op"; + suffix += "Tuple3Op"; + } var fileName = Path.Combine(outputDirectory, $"{testName}.cs"); @@ -1414,3 +1433,5 @@ void ProcessInput(StreamWriter testListFile, string groupName, (string templateF File.WriteAllText(fileName, template); } +ProcessInputs("X86Base", X86BaseInputs); +ProcessInputs("X86Base.X64", X86BaseX64Inputs); diff --git a/src/tests/JIT/HardwareIntrinsics/HardwareIntrinsics_r.csproj b/src/tests/JIT/HardwareIntrinsics/HardwareIntrinsics_r.csproj index d81af3a381450..9210387885675 100644 --- a/src/tests/JIT/HardwareIntrinsics/HardwareIntrinsics_r.csproj +++ b/src/tests/JIT/HardwareIntrinsics/HardwareIntrinsics_r.csproj @@ -15,7 +15,7 @@ - + diff --git a/src/tests/JIT/HardwareIntrinsics/HardwareIntrinsics_ro.csproj b/src/tests/JIT/HardwareIntrinsics/HardwareIntrinsics_ro.csproj index cec6dbb86c481..36d5b5608fede 100644 --- a/src/tests/JIT/HardwareIntrinsics/HardwareIntrinsics_ro.csproj +++ b/src/tests/JIT/HardwareIntrinsics/HardwareIntrinsics_ro.csproj @@ -16,7 +16,7 @@ - + diff --git a/src/tests/JIT/HardwareIntrinsics/X86/Shared/ScalarTernOpTupleBinRetTest.template b/src/tests/JIT/HardwareIntrinsics/X86/Shared/ScalarTernOpTupleBinRetTest.template new file mode 100644 index 0000000000000..e1e04ad6913cd --- /dev/null +++ b/src/tests/JIT/HardwareIntrinsics/X86/Shared/ScalarTernOpTupleBinRetTest.template @@ -0,0 +1,257 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +/****************************************************************************** + * This file is auto-generated from a template file by the GenerateTests.csx * + * script in tests\src\JIT\HardwareIntrinsics\X86\Shared. In order to make * + * changes, please update the corresponding template and run according to the * + * directions listed in the file. * + ******************************************************************************/ +extern alias CoreLib; +using X86Base = CoreLib::System.Runtime.Intrinsics.X86.X86Base; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; + +namespace JIT.HardwareIntrinsics.X86 +{ + public static partial class Program + { + private static void {Method}{RetBaseType}Tuple3Op() + { + var test = new ScalarTernOpTupleTest__{Method}{RetBaseType}(); + + if (test.IsSupported) + { + // Validates basic functionality works, using Unsafe.ReadUnaligned + test.RunBasicScenario_UnsafeRead(); + + // Validates calling via reflection works, using Unsafe.ReadUnaligned + test.RunReflectionScenario_UnsafeRead(); + + // Validates passing a static member works + test.RunClsVarScenario(); + + // Validates passing a local works, using Unsafe.ReadUnaligned + test.RunLclVarScenario_UnsafeRead(); + + // Validates passing the field of a local class works + test.RunClassLclFldScenario(); + + // Validates passing an instance member of a class works + test.RunClassFldScenario(); + + // Validates passing the field of a local struct works + test.RunStructLclFldScenario(); + + // Validates passing an instance member of a struct works + test.RunStructFldScenario(); + } + else + { + // Validates we throw on unsupported hardware + test.RunUnsupportedScenario(); + } + + if (!test.Succeeded) + { + throw new Exception("One or more scenarios did not complete as expected."); + } + } + } + + public sealed unsafe class ScalarTernOpTupleTest__{Method}{RetBaseType} + { + private struct TestStruct + { + public {Op1BaseType} _fld1; + public {Op2BaseType} _fld2; + public {Op3BaseType} _fld3; + + public static TestStruct Create() + { + var testStruct = new TestStruct(); + + testStruct._fld1 = {NextValueOp1}; + testStruct._fld2 = {NextValueOp2}; + testStruct._fld3 = {NextValueOp3}; + + return testStruct; + } + + public void RunStructFldScenario(ScalarTernOpTupleTest__{Method}{RetBaseType} testClass) + { + var result = {Isa}.{Method}(_fld1, _fld2, _fld3); + testClass.ValidateResult(_fld1, _fld2, _fld3, result); + } + } + + private static {Op1BaseType} _data1; + private static {Op2BaseType} _data2; + private static {Op3BaseType} _data3; + + private static {Op1BaseType} _clsVar1; + private static {Op2BaseType} _clsVar2; + private static {Op3BaseType} _clsVar3; + + private {Op1BaseType} _fld1; + private {Op2BaseType} _fld2; + private {Op3BaseType} _fld3; + + static ScalarTernOpTupleTest__{Method}{RetBaseType}() + { + _clsVar1 = {NextValueOp1}; + _clsVar2 = {NextValueOp2}; + _clsVar3 = {NextValueOp3}; + } + + public ScalarTernOpTupleTest__{Method}{RetBaseType}() + { + Succeeded = true; + + _fld1 = {NextValueOp1}; + _fld2 = {NextValueOp2}; + _fld3 = {NextValueOp3}; + + _data1 = {NextValueOp1}; + _data2 = {NextValueOp2}; + _data3 = {NextValueOp3}; + } + + public bool IsSupported => {Isa}.IsSupported; + + public bool Succeeded { get; set; } + + public void RunBasicScenario_UnsafeRead() + { + TestLibrary.TestFramework.BeginScenario(nameof(RunBasicScenario_UnsafeRead)); + + var result = {Isa}.{Method}( + Unsafe.ReadUnaligned<{Op1BaseType}>(ref Unsafe.As<{Op1BaseType}, byte>(ref _data1)), + Unsafe.ReadUnaligned<{Op2BaseType}>(ref Unsafe.As<{Op2BaseType}, byte>(ref _data2)), + Unsafe.ReadUnaligned<{Op3BaseType}>(ref Unsafe.As<{Op3BaseType}, byte>(ref _data3)) + ); + + ValidateResult(_data1, _data2, _data3, result); + } + + public void RunReflectionScenario_UnsafeRead() + { + TestLibrary.TestFramework.BeginScenario(nameof(RunReflectionScenario_UnsafeRead)); + + var result = typeof({Isa}).GetMethod(nameof({Isa}.{Method}), new Type[] { typeof({Op1BaseType}), typeof({Op2BaseType}), typeof({Op3BaseType}) }) + .Invoke(null, new object[] { + Unsafe.ReadUnaligned<{Op1BaseType}>(ref Unsafe.As<{Op1BaseType}, byte>(ref _data1)), + Unsafe.ReadUnaligned<{Op2BaseType}>(ref Unsafe.As<{Op2BaseType}, byte>(ref _data2)), + Unsafe.ReadUnaligned<{Op3BaseType}>(ref Unsafe.As<{Op3BaseType}, byte>(ref _data3)) + }); + + ValidateResult(_data1, _data2, _data3, (({RetBaseType}, {RetBaseType}))result); + } + + public void RunClsVarScenario() + { + TestLibrary.TestFramework.BeginScenario(nameof(RunClsVarScenario)); + + var result = {Isa}.{Method}( + _clsVar1, + _clsVar2, + _clsVar3 + ); + + ValidateResult(_clsVar1, _clsVar2, _clsVar3, result); + } + + public void RunLclVarScenario_UnsafeRead() + { + TestLibrary.TestFramework.BeginScenario(nameof(RunLclVarScenario_UnsafeRead)); + + var data1 = Unsafe.ReadUnaligned<{Op1BaseType}>(ref Unsafe.As<{Op1BaseType}, byte>(ref _data1)); + var data2 = Unsafe.ReadUnaligned<{Op2BaseType}>(ref Unsafe.As<{Op2BaseType}, byte>(ref _data2)); + var data3 = Unsafe.ReadUnaligned<{Op3BaseType}>(ref Unsafe.As<{Op3BaseType}, byte>(ref _data3)); + var result = {Isa}.{Method}(data1, data2, data3); + + ValidateResult(data1, data2, data3, result); + } + + public void RunClassLclFldScenario() + { + TestLibrary.TestFramework.BeginScenario(nameof(RunClassLclFldScenario)); + + var test = new ScalarTernOpTupleTest__{Method}{RetBaseType}(); + var result = {Isa}.{Method}(test._fld1, test._fld2, test._fld3); + + ValidateResult(test._fld1, test._fld2, test._fld3, result); + } + + public void RunClassFldScenario() + { + TestLibrary.TestFramework.BeginScenario(nameof(RunClassFldScenario)); + + var result = {Isa}.{Method}(_fld1, _fld2, _fld3); + ValidateResult(_fld1, _fld2, _fld3, result); + } + + public void RunStructLclFldScenario() + { + TestLibrary.TestFramework.BeginScenario(nameof(RunStructLclFldScenario)); + + var test = TestStruct.Create(); + var result = {Isa}.{Method}(test._fld1, test._fld2, test._fld3); + + ValidateResult(test._fld1, test._fld2, test._fld3, result); + } + + public void RunStructFldScenario() + { + TestLibrary.TestFramework.BeginScenario(nameof(RunStructFldScenario)); + + var test = TestStruct.Create(); + test.RunStructFldScenario(this); + } + + public void RunUnsupportedScenario() + { + TestLibrary.TestFramework.BeginScenario(nameof(RunUnsupportedScenario)); + + bool succeeded = false; + + try + { + RunBasicScenario_UnsafeRead(); + } + catch (PlatformNotSupportedException) + { + succeeded = true; + } + + if (!succeeded) + { + Succeeded = false; + } + } + + private void ValidateResult({Op1BaseType} op1, {Op2BaseType} op2, {Op3BaseType} op3, ({RetBaseType}, {RetBaseType}) result, [CallerMemberName] string method = "") + { + (var ret1, var ret2) = result; + var isUnexpectedResult = false; + + {ValidateResult} + + if (isUnexpectedResult) + { + TestLibrary.TestFramework.LogInformation($"{nameof({Isa})}.{nameof({Isa}.{Method})}<({RetBaseType}, {RetBaseType})>({Op1BaseType}, {Op2BaseType}, {Op3BaseType}): {Method} failed:"); + TestLibrary.TestFramework.LogInformation($" op1: {op1}"); + TestLibrary.TestFramework.LogInformation($" op2: {op2}"); + TestLibrary.TestFramework.LogInformation($" op3: {op3}"); + TestLibrary.TestFramework.LogInformation($" result1: {ret1}"); + TestLibrary.TestFramework.LogInformation($" result2: {ret2}"); + TestLibrary.TestFramework.LogInformation(string.Empty); + + Succeeded = false; + } + } + } +} diff --git a/src/tests/JIT/HardwareIntrinsics/X86/X86Base.X64/Program.X86Base.X64.cs b/src/tests/JIT/HardwareIntrinsics/X86/X86Base.X64/Program.X86Base.X64.cs new file mode 100644 index 0000000000000..40dc13b975f47 --- /dev/null +++ b/src/tests/JIT/HardwareIntrinsics/X86/X86Base.X64/Program.X86Base.X64.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +using System; +using System.Collections.Generic; + +[assembly:Xunit.ActiveIssue("https://github.com/dotnet/runtime/issues/75767", typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.IsMonoLLVMAOT))] +[assembly:Xunit.ActiveIssue("https://github.com/dotnet/runtime/issues/75767", typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.IsMonoLLVMFULLAOT))] +namespace JIT.HardwareIntrinsics.X86._X86Base.X64 +{ + public static partial class Program + { + static Program() + { + + } + } +} diff --git a/src/tests/JIT/HardwareIntrinsics/X86/X86Base.X64/X86Base.X64_r.csproj b/src/tests/JIT/HardwareIntrinsics/X86/X86Base.X64/X86Base.X64_r.csproj new file mode 100644 index 0000000000000..d4c72bc89146c --- /dev/null +++ b/src/tests/JIT/HardwareIntrinsics/X86/X86Base.X64/X86Base.X64_r.csproj @@ -0,0 +1,18 @@ + + + X86_X86Base.X64_r + false + true + + true + + + Embedded + + + + + + + + diff --git a/src/tests/JIT/HardwareIntrinsics/X86/X86Base.X64/X86Base.X64_ro.csproj b/src/tests/JIT/HardwareIntrinsics/X86/X86Base.X64/X86Base.X64_ro.csproj new file mode 100644 index 0000000000000..e0a62fa9bc562 --- /dev/null +++ b/src/tests/JIT/HardwareIntrinsics/X86/X86Base.X64/X86Base.X64_ro.csproj @@ -0,0 +1,18 @@ + + + X86_X86Base.X64_ro + false + true + + true + + + Embedded + True + + + + + + + diff --git a/src/tests/JIT/HardwareIntrinsics/X86/X86Base/DivRem.RefOnly.csproj b/src/tests/JIT/HardwareIntrinsics/X86/X86Base/DivRem.RefOnly.csproj new file mode 100644 index 0000000000000..1a70f52cd8874 --- /dev/null +++ b/src/tests/JIT/HardwareIntrinsics/X86/X86Base/DivRem.RefOnly.csproj @@ -0,0 +1,22 @@ + + + + true + Library + SharedLibrary + System.Private.CoreLib + 436 + 436 + true + + + + + + + \ No newline at end of file diff --git a/src/tests/JIT/HardwareIntrinsics/X86/X86Base/Program.X86Base.cs b/src/tests/JIT/HardwareIntrinsics/X86/X86Base/Program.X86Base.cs new file mode 100644 index 0000000000000..364a8aee4b047 --- /dev/null +++ b/src/tests/JIT/HardwareIntrinsics/X86/X86Base/Program.X86Base.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +using System; +using System.Collections.Generic; + +[assembly:Xunit.ActiveIssue("https://github.com/dotnet/runtime/issues/75767", typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.IsMonoLLVMAOT))] +[assembly:Xunit.ActiveIssue("https://github.com/dotnet/runtime/issues/75767", typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.IsMonoLLVMFULLAOT))] +namespace JIT.HardwareIntrinsics.X86._X86Base +{ + public static partial class Program + { + static Program() + { + + } + } +} diff --git a/src/tests/JIT/HardwareIntrinsics/X86/X86Base/X86Base_r.csproj b/src/tests/JIT/HardwareIntrinsics/X86/X86Base/X86Base_r.csproj new file mode 100644 index 0000000000000..f16999cb670d1 --- /dev/null +++ b/src/tests/JIT/HardwareIntrinsics/X86/X86Base/X86Base_r.csproj @@ -0,0 +1,18 @@ + + + X86_X86Base_r + false + true + + true + + + Embedded + + + + + + + + diff --git a/src/tests/JIT/HardwareIntrinsics/X86/X86Base/X86Base_ro.csproj b/src/tests/JIT/HardwareIntrinsics/X86/X86Base/X86Base_ro.csproj new file mode 100644 index 0000000000000..b09c5e72a5157 --- /dev/null +++ b/src/tests/JIT/HardwareIntrinsics/X86/X86Base/X86Base_ro.csproj @@ -0,0 +1,18 @@ + + + X86_X86Base_ro + false + true + + true + + + Embedded + True + + + + + + + diff --git a/src/tests/issues.targets b/src/tests/issues.targets index 9420fdc0eec52..6704c64474293 100644 --- a/src/tests/issues.targets +++ b/src/tests/issues.targets @@ -1417,6 +1417,15 @@ https://github.com/dotnet/runtime/issues/54185 + + https://github.com/dotnet/runtime/issues/75767 + + + https://github.com/dotnet/runtime/issues/75767 + + + https://github.com/dotnet/runtime/issues/75767 + Mono does not define out of range fp to int conversions @@ -2840,6 +2849,15 @@ expected failure: unsupported type with ByRefLike parameters currently fails at AOT compile time, not runtime + + https://github.com/dotnet/runtime/issues/75767 + + + https://github.com/dotnet/runtime/issues/75767 + + + https://github.com/dotnet/runtime/issues/75767 + https://github.com/dotnet/runtime/issues/70490 @@ -3149,6 +3167,15 @@ https://github.com/dotnet/runtime/issues/73454;https://github.com/dotnet/runtime/pull/61707#issuecomment-973122341 + + https://github.com/dotnet/runtime/issues/75767 + + + https://github.com/dotnet/runtime/issues/75767 + + + https://github.com/dotnet/runtime/issues/75767 + needs triage