diff --git a/eng/Versions.props b/eng/Versions.props index 47d338c5f21c4..a5d5d2971a27f 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -186,6 +186,8 @@ 3.12.0 4.5.0 6.0.0 + 5.0.0 + 7.0.2 13.0.3 1.0.2 2.0.4 diff --git a/src/coreclr/System.Private.CoreLib/src/System/Math.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Math.CoreCLR.cs index 5dd46b02d4231..1384da2c81d1f 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Math.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Math.CoreCLR.cs @@ -92,6 +92,7 @@ public static partial class Math [MethodImpl(MethodImplOptions.InternalCall)] public static extern double Sin(double a); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe (double Sin, double Cos) SinCos(double x) { double sin, cos; diff --git a/src/coreclr/System.Private.CoreLib/src/System/MathF.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/MathF.CoreCLR.cs index e9caae4c18a38..2bef2b8d3e175 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/MathF.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/MathF.CoreCLR.cs @@ -89,6 +89,7 @@ public static partial class MathF [MethodImpl(MethodImplOptions.InternalCall)] public static extern float Sin(float x); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe (float Sin, float Cos) SinCos(float x) { float sin, cos; diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 5dca9aa7f5e32..8ccaeec161f06 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -4562,8 +4562,7 @@ class Compiler GenTreeLclVarCommon* data, WCHAR* cns, int len, int dataOffset, StringComparison cmpMode); GenTreeStrCon* impGetStrConFromSpan(GenTree* span); - GenTree* impIntrinsic(GenTree* newobjThis, - CORINFO_CLASS_HANDLE clsHnd, + GenTree* impIntrinsic(CORINFO_CLASS_HANDLE clsHnd, CORINFO_METHOD_HANDLE method, CORINFO_SIG_INFO* sig, unsigned methodFlags, @@ -4628,7 +4627,6 @@ class Compiler CORINFO_CLASS_HANDLE clsHnd, CORINFO_METHOD_HANDLE method, CORINFO_SIG_INFO* sig, - GenTree* newobjThis, bool mustExpand); protected: @@ -4640,7 +4638,6 @@ class Compiler var_types retType, CorInfoType simdBaseJitType, unsigned simdSize, - GenTree* newobjThis, bool mustExpand); GenTree* impSpecialIntrinsic(NamedIntrinsic intrinsic, @@ -4653,10 +4650,7 @@ class Compiler unsigned simdSize, bool mustExpand); - GenTree* getArgForHWIntrinsic(var_types argType, - CORINFO_CLASS_HANDLE argClass, - bool expectAddr = false, - GenTree* newobjThis = nullptr); + GenTree* getArgForHWIntrinsic(var_types argType, CORINFO_CLASS_HANDLE argClass); GenTree* impNonConstFallback(NamedIntrinsic intrinsic, var_types simdType, CorInfoType simdBaseJitType); GenTree* addRangeCheckIfNeeded( NamedIntrinsic intrinsic, GenTree* immOp, bool mustExpand, int immLowerBound, int immUpperBound); diff --git a/src/coreclr/jit/fgbasic.cpp b/src/coreclr/jit/fgbasic.cpp index 4b3f0cc9afd2b..653ff015c3272 100644 --- a/src/coreclr/jit/fgbasic.cpp +++ b/src/coreclr/jit/fgbasic.cpp @@ -1395,15 +1395,10 @@ void Compiler::fgFindJumpTargets(const BYTE* codeAddr, IL_OFFSET codeSize, Fixed case NI_Vector64_CreateScalar: case NI_Vector64_CreateScalarUnsafe: #endif // TARGET_ARM64 - case NI_Vector2_Create: - case NI_Vector2_CreateBroadcast: - case NI_Vector3_Create: - case NI_Vector3_CreateBroadcast: - case NI_Vector3_CreateFromVector2: case NI_Vector128_Create: case NI_Vector128_CreateScalar: case NI_Vector128_CreateScalarUnsafe: - case NI_VectorT_CreateBroadcast: + case NI_VectorT_Create: #if defined(TARGET_XARCH) case NI_BMI1_TrailingZeroCount: case NI_BMI1_X64_TrailingZeroCount: diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index ec22b00d63639..9a899443294b5 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -27176,6 +27176,7 @@ GenTree* Compiler::gtNewSimdWithElementNode( assert(varTypeIsArithmetic(simdBaseType)); assert(op2->IsCnsIntOrI()); + assert(varTypeIsArithmetic(op3)); ssize_t imm8 = op2->AsIntCon()->IconValue(); ssize_t count = simdSize / genTypeSize(simdBaseType); diff --git a/src/coreclr/jit/hwintrinsic.cpp b/src/coreclr/jit/hwintrinsic.cpp index 26af49073c5d6..f2f04b4a820a6 100644 --- a/src/coreclr/jit/hwintrinsic.cpp +++ b/src/coreclr/jit/hwintrinsic.cpp @@ -745,16 +745,11 @@ bool HWIntrinsicInfo::isImmOp(NamedIntrinsic id, const GenTree* op) // Arguments: // argType -- the required type of argument // argClass -- the class handle of argType -// expectAddr -- if true indicates we are expecting type stack entry to be a TYP_BYREF. -// newobjThis -- For CEE_NEWOBJ, this is the temp grabbed for the allocated uninitialized object. // // Return Value: // the validated argument // -GenTree* Compiler::getArgForHWIntrinsic(var_types argType, - CORINFO_CLASS_HANDLE argClass, - bool expectAddr, - GenTree* newobjThis) +GenTree* Compiler::getArgForHWIntrinsic(var_types argType, CORINFO_CLASS_HANDLE argClass) { GenTree* arg = nullptr; @@ -768,31 +763,12 @@ GenTree* Compiler::getArgForHWIntrinsic(var_types argType, } assert(varTypeIsSIMD(argType)); - if (newobjThis == nullptr) - { - if (expectAddr) - { - arg = gtNewLoadValueNode(argType, impPopStack().val); - } - else - { - arg = impSIMDPopStack(); - } - assert(varTypeIsSIMDOrMask(arg)); - } - else - { - assert(newobjThis->IsLclVarAddr()); - arg = newobjThis; - - // push newobj result on type stack - unsigned lclNum = arg->AsLclVarCommon()->GetLclNum(); - impPushOnStack(gtNewLclvNode(lclNum, lvaGetRealType(lclNum)), verMakeTypeInfo(argClass)); - } + arg = impSIMDPopStack(); + assert(varTypeIsSIMDOrMask(arg)); } else { - assert(varTypeIsArithmetic(argType) || ((argType == TYP_BYREF) && (newobjThis == nullptr))); + assert(varTypeIsArithmetic(argType) || (argType == TYP_BYREF)); arg = impPopStack().val; assert(varTypeIsArithmetic(arg->TypeGet()) || ((argType == TYP_BYREF) && arg->TypeIs(TYP_BYREF))); diff --git a/src/coreclr/jit/hwintrinsicarm64.cpp b/src/coreclr/jit/hwintrinsicarm64.cpp index 3a481e2934517..5a32b4cbeee3d 100644 --- a/src/coreclr/jit/hwintrinsicarm64.cpp +++ b/src/coreclr/jit/hwintrinsicarm64.cpp @@ -721,6 +721,18 @@ GenTree* Compiler::impSpecialIntrinsic(NamedIntrinsic intrinsic, break; } + case NI_Vector128_AsVector128Unsafe: + { + assert(sig->numArgs == 1); + assert(retType == TYP_SIMD16); + assert(simdBaseJitType == CORINFO_TYPE_FLOAT); + assert((simdSize == 8) || (simdSize == 12)); + + op1 = impSIMDPopStack(); + retNode = gtNewSimdHWIntrinsicNode(retType, op1, NI_Vector128_AsVector128Unsafe, simdBaseJitType, simdSize); + break; + } + case NI_Vector64_op_BitwiseAnd: case NI_Vector128_op_BitwiseAnd: { diff --git a/src/coreclr/jit/hwintrinsicxarch.cpp b/src/coreclr/jit/hwintrinsicxarch.cpp index b44c9cf0785e7..5acac342b6211 100644 --- a/src/coreclr/jit/hwintrinsicxarch.cpp +++ b/src/coreclr/jit/hwintrinsicxarch.cpp @@ -1279,6 +1279,18 @@ GenTree* Compiler::impSpecialIntrinsic(NamedIntrinsic intrinsic, break; } + case NI_Vector128_AsVector128Unsafe: + { + assert(sig->numArgs == 1); + assert(retType == TYP_SIMD16); + assert(simdBaseJitType == CORINFO_TYPE_FLOAT); + assert((simdSize == 8) || (simdSize == 12)); + + op1 = impSIMDPopStack(); + retNode = gtNewSimdHWIntrinsicNode(retType, op1, NI_Vector128_AsVector128Unsafe, simdBaseJitType, simdSize); + break; + } + case NI_Vector256_AsVector: case NI_Vector256_AsVector256: { diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index 59a3fadf40f1e..c920ac70cfef5 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -242,7 +242,7 @@ var_types Compiler::impImportCall(OPCODE opcode, } #endif // FEATURE_READYTORUN - call = impIntrinsic(newobjThis, clsHnd, methHnd, sig, mflags, pResolvedToken, isReadonlyCall, isTailCall, + call = impIntrinsic(clsHnd, methHnd, sig, mflags, pResolvedToken, isReadonlyCall, isTailCall, opcode == CEE_CALLVIRT, pConstrainedResolvedToken, callInfo->thisTransform R2RARG(&entryPoint), &ni, &isSpecialIntrinsic); @@ -2818,7 +2818,6 @@ GenTree* Compiler::impCreateSpanIntrinsic(CORINFO_SIG_INFO* sig) // impIntrinsic: possibly expand intrinsic call into alternate IR sequence // // Arguments: -// newobjThis - for constructor calls, the tree for the newly allocated object // clsHnd - handle for the intrinsic method's class // method - handle for the intrinsic method // sig - signature of the intrinsic method @@ -2861,8 +2860,7 @@ GenTree* Compiler::impCreateSpanIntrinsic(CORINFO_SIG_INFO* sig) // identified as "must expand" if they are invoked from within their // own method bodies. // -GenTree* Compiler::impIntrinsic(GenTree* newobjThis, - CORINFO_CLASS_HANDLE clsHnd, +GenTree* Compiler::impIntrinsic(CORINFO_CLASS_HANDLE clsHnd, CORINFO_METHOD_HANDLE method, CORINFO_SIG_INFO* sig, unsigned methodFlags, @@ -3088,7 +3086,7 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, if (isIntrinsic) { - GenTree* hwintrinsic = impSimdAsHWIntrinsic(ni, clsHnd, method, sig, newobjThis, mustExpand); + GenTree* hwintrinsic = impSimdAsHWIntrinsic(ni, clsHnd, method, sig, mustExpand); if (hwintrinsic == nullptr) { diff --git a/src/coreclr/jit/lclmorph.cpp b/src/coreclr/jit/lclmorph.cpp index 0c005f1909a54..1774aea55c831 100644 --- a/src/coreclr/jit/lclmorph.cpp +++ b/src/coreclr/jit/lclmorph.cpp @@ -1307,9 +1307,10 @@ class LocalAddressVisitor final : public GenTreeVisitor break; #ifdef FEATURE_HW_INTRINSICS - // We have two cases we want to handle: - // 1. Vector2/3/4 and Quaternion where we have 4x float fields + // We have three cases we want to handle: + // 1. Vector2/3/4 and Quaternion where we have 2-4x float fields // 2. Plane where we have 1x Vector3 and 1x float field + // 3. Accesses of halves of larger SIMD types case IndirTransform::GetElement: { @@ -1321,24 +1322,29 @@ class LocalAddressVisitor final : public GenTreeVisitor { case TYP_FLOAT: { + // Handle case 1 or the float field of case 2 GenTree* indexNode = m_compiler->gtNewIconNode(offset / genTypeSize(elementType)); hwiNode = m_compiler->gtNewSimdGetElementNode(elementType, lclNode, indexNode, CORINFO_TYPE_FLOAT, genTypeSize(varDsc)); break; } + case TYP_SIMD12: { + // Handle the Vector3 field of case 2 assert(genTypeSize(varDsc) == 16); hwiNode = m_compiler->gtNewSimdHWIntrinsicNode(elementType, lclNode, NI_Vector128_AsVector3, CORINFO_TYPE_FLOAT, 16); break; } + case TYP_SIMD8: #if defined(FEATURE_SIMD) && defined(TARGET_XARCH) case TYP_SIMD16: case TYP_SIMD32: #endif { + // Handle case 3 assert(genTypeSize(elementType) * 2 == genTypeSize(varDsc)); if (offset == 0) { @@ -1374,29 +1380,44 @@ class LocalAddressVisitor final : public GenTreeVisitor { case TYP_FLOAT: { + // Handle case 1 or the float field of case 2 GenTree* indexNode = m_compiler->gtNewIconNode(offset / genTypeSize(elementType)); hwiNode = m_compiler->gtNewSimdWithElementNode(varDsc->TypeGet(), simdLclNode, indexNode, elementNode, CORINFO_TYPE_FLOAT, genTypeSize(varDsc)); break; } + case TYP_SIMD12: { + // Handle the Vector3 field of case 2 assert(varDsc->TypeGet() == TYP_SIMD16); - // We inverse the operands here and take elementNode as the main value and simdLclNode[3] as the - // new value. This gives us a new TYP_SIMD16 with all elements in the right spots - GenTree* indexNode = m_compiler->gtNewIconNode(3, TYP_INT); - hwiNode = m_compiler->gtNewSimdWithElementNode(TYP_SIMD16, elementNode, indexNode, simdLclNode, + // We effectively inverse the operands here and take elementNode as the main value and + // simdLclNode[3] as the new value. This gives us a new TYP_SIMD16 with all elements in the + // right spots + + elementNode = m_compiler->gtNewSimdHWIntrinsicNode(TYP_SIMD16, elementNode, + NI_Vector128_AsVector128Unsafe, + CORINFO_TYPE_FLOAT, 12); + + GenTree* indexNode1 = m_compiler->gtNewIconNode(3, TYP_INT); + simdLclNode = m_compiler->gtNewSimdGetElementNode(TYP_FLOAT, simdLclNode, indexNode1, + CORINFO_TYPE_FLOAT, 16); + + GenTree* indexNode2 = m_compiler->gtNewIconNode(3, TYP_INT); + hwiNode = m_compiler->gtNewSimdWithElementNode(TYP_SIMD16, elementNode, indexNode2, simdLclNode, CORINFO_TYPE_FLOAT, 16); break; } + case TYP_SIMD8: #if defined(FEATURE_SIMD) && defined(TARGET_XARCH) case TYP_SIMD16: case TYP_SIMD32: #endif { + // Handle case 3 assert(genTypeSize(elementType) * 2 == genTypeSize(varDsc)); if (offset == 0) { @@ -1412,6 +1433,7 @@ class LocalAddressVisitor final : public GenTreeVisitor break; } + default: unreached(); } @@ -1541,7 +1563,7 @@ class LocalAddressVisitor final : public GenTreeVisitor if (varTypeIsSIMD(varDsc)) { // We have three cases we want to handle: - // 1. Vector2/3/4 and Quaternion where we have 4x float fields + // 1. Vector2/3/4 and Quaternion where we have 2-4x float fields // 2. Plane where we have 1x Vector3 and 1x float field // 3. Accesses of halves of larger SIMD types diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index 2137874887e70..c7790fb6199d6 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -1752,18 +1752,23 @@ void Lowering::LowerArg(GenTreeCall* call, CallArg* callArg, bool late) } else if (arg->OperIs(GT_HWINTRINSIC)) { - GenTreeJitIntrinsic* jitIntrinsic = reinterpret_cast(arg); + GenTreeHWIntrinsic* hwintrinsic = arg->AsHWIntrinsic(); // For HWIntrinsic, there are some intrinsics like ExtractVector128 which have - // a gtType of TYP_SIMD16 but a SimdSize of 32, so we need to include that in - // the assert below. + // a gtType of TYP_SIMD16 but a SimdSize of 32, so we can't necessarily assert + // the simd size - assert((jitIntrinsic->GetSimdSize() == 12) || (jitIntrinsic->GetSimdSize() == 16) || - (jitIntrinsic->GetSimdSize() == 32) || (jitIntrinsic->GetSimdSize() == 64)); - - if (jitIntrinsic->GetSimdSize() == 12) + if (hwintrinsic->GetSimdSize() == 12) { - type = TYP_SIMD12; + if (hwintrinsic->GetHWIntrinsicId() != NI_Vector128_AsVector128Unsafe) + { + // Most nodes that have a simdSize of 12 are actually producing a TYP_SIMD12 + // and have been massaged to TYP_SIMD16 to match the actual product size. This + // is not the case for NI_Vector128_AsVector128Unsafe which is explicitly taking + // a TYP_SIMD12 and producing a TYP_SIMD16. + + type = TYP_SIMD12; + } } } } diff --git a/src/coreclr/jit/simdashwintrinsic.cpp b/src/coreclr/jit/simdashwintrinsic.cpp index b15a9988195e1..d04c5f10009b9 100644 --- a/src/coreclr/jit/simdashwintrinsic.cpp +++ b/src/coreclr/jit/simdashwintrinsic.cpp @@ -65,13 +65,11 @@ NamedIntrinsic SimdAsHWIntrinsicInfo::lookupId(Compiler* comp, return NI_Illegal; } - unsigned numArgs = sig->numArgs; - bool isInstanceMethod = false; + unsigned numArgs = sig->numArgs; if (sig->hasThis()) { - numArgs++; - isInstanceMethod = true; + return NI_Illegal; } if (classId == SimdAsHWIntrinsicClassId::Vector) @@ -79,8 +77,6 @@ NamedIntrinsic SimdAsHWIntrinsicInfo::lookupId(Compiler* comp, // We want to avoid doing anything that would unnecessarily trigger a recorded dependency against Vector // so we duplicate a few checks here to ensure this works smoothly for the static Vector class. - assert(!isInstanceMethod); - if (strcmp(methodName, "get_IsHardwareAccelerated") == 0) { return comp->IsBaselineSimdIsaSupported() ? NI_IsSupported_True : NI_IsSupported_False; @@ -128,11 +124,6 @@ NamedIntrinsic SimdAsHWIntrinsicInfo::lookupId(Compiler* comp, continue; } - if (isInstanceMethod != SimdAsHWIntrinsicInfo::IsInstanceMethod(intrinsicInfo.id)) - { - continue; - } - if (strcmp(methodName, intrinsicInfo.name) != 0) { continue; @@ -165,24 +156,6 @@ SimdAsHWIntrinsicClassId SimdAsHWIntrinsicInfo::lookupClassId(Compiler* comp, switch (className[0]) { - case 'P': - { - if (strcmp(className, "Plane") == 0) - { - return SimdAsHWIntrinsicClassId::Plane; - } - break; - } - - case 'Q': - { - if (strcmp(className, "Quaternion") == 0) - { - return SimdAsHWIntrinsicClassId::Quaternion; - } - break; - } - case 'V': { if (strncmp(className, "Vector", 6) != 0) @@ -196,18 +169,6 @@ SimdAsHWIntrinsicClassId SimdAsHWIntrinsicInfo::lookupClassId(Compiler* comp, { return SimdAsHWIntrinsicClassId::Vector; } - else if (strcmp(className, "2") == 0) - { - return SimdAsHWIntrinsicClassId::Vector2; - } - else if (strcmp(className, "3") == 0) - { - return SimdAsHWIntrinsicClassId::Vector3; - } - else if (strcmp(className, "4") == 0) - { - return SimdAsHWIntrinsicClassId::Vector4; - } else if (strcmp(className, "`1") == 0) { uint32_t vectorTByteLength = comp->getVectorTByteLength(); @@ -253,7 +214,6 @@ GenTree* Compiler::impSimdAsHWIntrinsic(NamedIntrinsic intrinsic, CORINFO_CLASS_HANDLE clsHnd, CORINFO_METHOD_HANDLE method, CORINFO_SIG_INFO* sig, - GenTree* newobjThis, bool mustExpand) { if (!IsBaselineSimdIsaSupported()) @@ -270,13 +230,12 @@ GenTree* Compiler::impSimdAsHWIntrinsic(NamedIntrinsic intrinsic, return nullptr; } - CORINFO_CLASS_HANDLE argClass = NO_CLASS_HANDLE; - var_types retType = genActualType(JITtype2varType(sig->retType)); - CorInfoType simdBaseJitType = CORINFO_TYPE_UNDEF; - var_types simdType = TYP_UNKNOWN; - unsigned simdSize = 0; - unsigned numArgs = sig->numArgs; - bool isInstanceMethod = false; + CORINFO_CLASS_HANDLE argClass = NO_CLASS_HANDLE; + var_types retType = genActualType(JITtype2varType(sig->retType)); + CorInfoType simdBaseJitType = CORINFO_TYPE_UNDEF; + var_types simdType = TYP_UNKNOWN; + unsigned simdSize = 0; + unsigned numArgs = sig->numArgs; // We want to resolve and populate the handle cache for this type even // if it isn't the basis for anything carried on the node. @@ -314,22 +273,10 @@ GenTree* Compiler::impSimdAsHWIntrinsic(NamedIntrinsic intrinsic, } } - if (sig->hasThis()) - { - assert(SimdAsHWIntrinsicInfo::IsInstanceMethod(intrinsic)); - numArgs++; - - isInstanceMethod = true; - argClass = clsHnd; + assert(!sig->hasThis()); - if (SimdAsHWIntrinsicInfo::BaseTypeFromThisArg(intrinsic)) - { - assert((simdBaseJitType == CORINFO_TYPE_UNDEF) || (simdBaseJitType == CORINFO_TYPE_VALUECLASS)); - simdBaseJitType = getBaseJitTypeAndSizeOfSIMDType(clsHnd, &simdSize); - } - } - else if ((clsHnd == m_simdHandleCache->VectorHandle) && (numArgs != 0) && - !SimdAsHWIntrinsicInfo::KeepBaseTypeFromRet(intrinsic)) + if ((clsHnd == m_simdHandleCache->VectorHandle) && (numArgs != 0) && + !SimdAsHWIntrinsicInfo::KeepBaseTypeFromRet(intrinsic)) { // We need to fixup the clsHnd in the case we are an intrinsic on Vector // The first argument will be the appropriate Vector handle to use @@ -362,18 +309,14 @@ GenTree* Compiler::impSimdAsHWIntrinsic(NamedIntrinsic intrinsic, return nullptr; } - if (SimdAsHWIntrinsicInfo::IsFloatingPointUsed(intrinsic)) - { - // Set `compFloatingPointUsed` to cover the scenario where an intrinsic - // is operating on SIMD fields, but where no SIMD local vars are in use. - compFloatingPointUsed = true; - } + // Set `compFloatingPointUsed` to cover the scenario where an intrinsic + // is operating on SIMD fields, but where no SIMD local vars are in use. + compFloatingPointUsed = true; if (hwIntrinsic == intrinsic) { // The SIMD intrinsic requires special handling outside the normal code path - return impSimdAsHWIntrinsicSpecial(intrinsic, clsHnd, sig, retType, simdBaseJitType, simdSize, newobjThis, - mustExpand); + return impSimdAsHWIntrinsicSpecial(intrinsic, clsHnd, sig, retType, simdBaseJitType, simdSize, mustExpand); } CORINFO_InstructionSet hwIntrinsicIsa = HWIntrinsicInfo::lookupIsa(hwIntrinsic); @@ -399,9 +342,8 @@ GenTree* Compiler::impSimdAsHWIntrinsic(NamedIntrinsic intrinsic, case 1: { - argType = isInstanceMethod ? simdType - : JITtype2varType(strip(info.compCompHnd->getArgType(sig, argList, &argClass))); - op1 = getArgForHWIntrinsic(argType, argClass, isInstanceMethod); + argType = JITtype2varType(strip(info.compCompHnd->getArgType(sig, argList, &argClass))); + op1 = getArgForHWIntrinsic(argType, argClass); return gtNewSimdAsHWIntrinsicNode(retType, op1, hwIntrinsic, simdBaseJitType, simdSize); } @@ -410,20 +352,18 @@ GenTree* Compiler::impSimdAsHWIntrinsic(NamedIntrinsic intrinsic, { if (SimdAsHWIntrinsicInfo::SpillSideEffectsOp1(intrinsic)) { - assert(newobjThis == nullptr); impSpillSideEffect(true, verCurrentState.esStackDepth - 2 DEBUGARG("Spilling op1 side effects for SimdAsHWIntrinsic")); } assert(!SimdAsHWIntrinsicInfo::SpillSideEffectsOp2(intrinsic)); - CORINFO_ARG_LIST_HANDLE arg2 = isInstanceMethod ? argList : info.compCompHnd->getArgNext(argList); + CORINFO_ARG_LIST_HANDLE arg2 = info.compCompHnd->getArgNext(argList); argType = JITtype2varType(strip(info.compCompHnd->getArgType(sig, arg2, &argClass))); op2 = getArgForHWIntrinsic(argType, argClass); - argType = isInstanceMethod ? simdType - : JITtype2varType(strip(info.compCompHnd->getArgType(sig, argList, &argClass))); - op1 = getArgForHWIntrinsic(argType, argClass, isInstanceMethod); + argType = JITtype2varType(strip(info.compCompHnd->getArgType(sig, argList, &argClass))); + op1 = getArgForHWIntrinsic(argType, argClass); return gtNewSimdAsHWIntrinsicNode(retType, op1, op2, hwIntrinsic, simdBaseJitType, simdSize); } @@ -455,7 +395,6 @@ GenTree* Compiler::impSimdAsHWIntrinsicSpecial(NamedIntrinsic intrinsic, var_types retType, CorInfoType simdBaseJitType, unsigned simdSize, - GenTree* newobjThis, bool mustExpand) { var_types simdBaseType = JitType2PreciseVarType(simdBaseJitType); @@ -478,17 +417,8 @@ GenTree* Compiler::impSimdAsHWIntrinsicSpecial(NamedIntrinsic intrinsic, GenTree* op4 = nullptr; GenTree* op5 = nullptr; - unsigned numArgs = sig->numArgs; - bool isInstanceMethod = false; - - if (sig->hasThis()) - { - assert(SimdAsHWIntrinsicInfo::IsInstanceMethod(intrinsic)); - numArgs++; - - isInstanceMethod = true; - argClass = clsHnd; - } + unsigned numArgs = sig->numArgs; + assert(!sig->hasThis()); #if defined(TARGET_XARCH) // We should have already exited early if SSE2 isn't supported @@ -544,8 +474,6 @@ GenTree* Compiler::impSimdAsHWIntrinsicSpecial(NamedIntrinsic intrinsic, break; } - case NI_Vector2_MultiplyAddEstimate: - case NI_Vector3_MultiplyAddEstimate: case NI_VectorT_MultiplyAddEstimate: { if (BlockNonDeterministicIntrinsics(mustExpand)) @@ -601,7 +529,7 @@ GenTree* Compiler::impSimdAsHWIntrinsicSpecial(NamedIntrinsic intrinsic, #endif // TARGET_XARCH #if defined(TARGET_X86) - case NI_VectorT_CreateBroadcast: + case NI_VectorT_Create: { if (varTypeIsLong(simdBaseType) && !impStackTop(0).val->IsIntegralConst()) { @@ -728,8 +656,6 @@ GenTree* Compiler::impSimdAsHWIntrinsicSpecial(NamedIntrinsic intrinsic, break; } - case NI_Vector2_WithElement: - case NI_Vector3_WithElement: case NI_VectorT_WithElement: { assert(sig->numArgs == 3); @@ -810,8 +736,6 @@ GenTree* Compiler::impSimdAsHWIntrinsicSpecial(NamedIntrinsic intrinsic, break; } - case NI_Vector2_WithElement: - case NI_Vector3_WithElement: case NI_VectorT_WithElement: { assert(numArgs == 3); @@ -849,8 +773,6 @@ GenTree* Compiler::impSimdAsHWIntrinsicSpecial(NamedIntrinsic intrinsic, } #endif // TARGET_XARCH - case NI_Vector2_FusedMultiplyAdd: - case NI_Vector3_FusedMultiplyAdd: case NI_VectorT_FusedMultiplyAdd: { bool isFmaAccelerated = false; @@ -917,8 +839,6 @@ GenTree* Compiler::impSimdAsHWIntrinsicSpecial(NamedIntrinsic intrinsic, { case 0: { - assert(newobjThis == nullptr); - switch (intrinsic) { case NI_VectorT_get_AllBitsSet: @@ -954,8 +874,6 @@ GenTree* Compiler::impSimdAsHWIntrinsicSpecial(NamedIntrinsic intrinsic, case 1: { - assert(newobjThis == nullptr); - isOpExplicit |= (intrinsic == NI_VectorT_op_Explicit); if (isOpExplicit) @@ -970,14 +888,11 @@ GenTree* Compiler::impSimdAsHWIntrinsicSpecial(NamedIntrinsic intrinsic, return op1; } - argType = isInstanceMethod ? simdType - : JITtype2varType(strip(info.compCompHnd->getArgType(sig, argList, &argClass))); - op1 = getArgForHWIntrinsic(argType, argClass, isInstanceMethod); + argType = JITtype2varType(strip(info.compCompHnd->getArgType(sig, argList, &argClass))); + op1 = getArgForHWIntrinsic(argType, argClass); switch (intrinsic) { - case NI_Vector2_Abs: - case NI_Vector3_Abs: case NI_VectorT_Abs: { return gtNewSimdAbsNode(retType, op1, simdBaseJitType, simdSize); @@ -1026,8 +941,6 @@ GenTree* Compiler::impSimdAsHWIntrinsicSpecial(NamedIntrinsic intrinsic, return gtNewSimdLoadNonTemporalNode(retType, op1, simdBaseJitType, simdSize); } - case NI_Vector2_op_UnaryNegation: - case NI_Vector3_op_UnaryNegation: case NI_VectorT_op_UnaryNegation: { return gtNewSimdUnOpNode(GT_NEG, retType, op1, simdBaseJitType, simdSize); @@ -1038,8 +951,6 @@ GenTree* Compiler::impSimdAsHWIntrinsicSpecial(NamedIntrinsic intrinsic, return gtNewSimdUnOpNode(GT_NOT, retType, op1, simdBaseJitType, simdSize); } - case NI_Vector2_Sqrt: - case NI_Vector3_Sqrt: case NI_VectorT_Sqrt: { return gtNewSimdSqrtNode(retType, op1, simdBaseJitType, simdSize); @@ -1257,7 +1168,7 @@ GenTree* Compiler::impSimdAsHWIntrinsicSpecial(NamedIntrinsic intrinsic, case 2: { - if (SimdAsHWIntrinsicInfo::SpillSideEffectsOp1(intrinsic) && (newobjThis == nullptr)) + if (SimdAsHWIntrinsicInfo::SpillSideEffectsOp1(intrinsic)) { impSpillSideEffect(true, verCurrentState.esStackDepth - 2 DEBUGARG("Spilling op1 side effects for SimdAsHWIntrinsic")); @@ -1265,30 +1176,16 @@ GenTree* Compiler::impSimdAsHWIntrinsicSpecial(NamedIntrinsic intrinsic, assert(!SimdAsHWIntrinsicInfo::SpillSideEffectsOp2(intrinsic)); - CORINFO_ARG_LIST_HANDLE arg2 = isInstanceMethod ? argList : info.compCompHnd->getArgNext(argList); + CORINFO_ARG_LIST_HANDLE arg2 = info.compCompHnd->getArgNext(argList); argType = JITtype2varType(strip(info.compCompHnd->getArgType(sig, arg2, &argClass))); op2 = getArgForHWIntrinsic(argType, argClass); - bool implicitConstructor = isInstanceMethod && (newobjThis == nullptr) && (retType == TYP_VOID); - - if (implicitConstructor) - { - op1 = getArgForHWIntrinsic(TYP_BYREF, argClass, isInstanceMethod, newobjThis); - } - else - { - argType = isInstanceMethod - ? simdType - : JITtype2varType(strip(info.compCompHnd->getArgType(sig, argList, &argClass))); + argType = JITtype2varType(strip(info.compCompHnd->getArgType(sig, argList, &argClass))); - op1 = getArgForHWIntrinsic(argType, (newobjThis != nullptr) ? clsHnd : argClass, isInstanceMethod, - newobjThis); - } + op1 = getArgForHWIntrinsic(argType, argClass); switch (intrinsic) { - case NI_Vector2_op_Addition: - case NI_Vector3_op_Addition: case NI_VectorT_op_Addition: { return gtNewSimdBinOpNode(GT_ADD, retType, op1, op2, simdBaseJitType, simdSize); @@ -1309,9 +1206,7 @@ GenTree* Compiler::impSimdAsHWIntrinsicSpecial(NamedIntrinsic intrinsic, return gtNewSimdBinOpNode(GT_OR, retType, op1, op2, simdBaseJitType, simdSize); } - case NI_Vector2_CreateBroadcast: - case NI_Vector3_CreateBroadcast: - case NI_VectorT_CreateBroadcast: + case NI_VectorT_Create: { assert(retType == TYP_VOID); @@ -1325,15 +1220,11 @@ GenTree* Compiler::impSimdAsHWIntrinsicSpecial(NamedIntrinsic intrinsic, return gtNewSimdCreateSequenceNode(simdType, op1, op2, simdBaseJitType, simdSize); } - case NI_Vector2_op_Division: - case NI_Vector3_op_Division: case NI_VectorT_op_Division: { return gtNewSimdBinOpNode(GT_DIV, retType, op1, op2, simdBaseJitType, simdSize); } - case NI_Vector2_Dot: - case NI_Vector3_Dot: case NI_VectorT_Dot: { op1 = gtNewSimdDotProdNode(simdType, op1, op2, simdBaseJitType, simdSize); @@ -1345,8 +1236,6 @@ GenTree* Compiler::impSimdAsHWIntrinsicSpecial(NamedIntrinsic intrinsic, return gtNewSimdCmpOpNode(GT_EQ, retType, op1, op2, simdBaseJitType, simdSize); } - case NI_Vector2_op_Equality: - case NI_Vector3_op_Equality: case NI_VectorT_op_Equality: { return gtNewSimdCmpOpAllNode(GT_EQ, retType, op1, op2, simdBaseJitType, simdSize); @@ -1362,8 +1251,6 @@ GenTree* Compiler::impSimdAsHWIntrinsicSpecial(NamedIntrinsic intrinsic, return gtNewSimdBinOpNode(GT_XOR, retType, op1, op2, simdBaseJitType, simdSize); } - case NI_Vector2_GetElement: - case NI_Vector3_GetElement: case NI_VectorT_GetElement: { return gtNewSimdGetElementNode(retType, op1, op2, simdBaseJitType, simdSize); @@ -1399,8 +1286,6 @@ GenTree* Compiler::impSimdAsHWIntrinsicSpecial(NamedIntrinsic intrinsic, return gtNewSimdCmpOpAnyNode(GT_GE, retType, op1, op2, simdBaseJitType, simdSize); } - case NI_Vector2_op_Inequality: - case NI_Vector3_op_Inequality: case NI_VectorT_op_Inequality: { return gtNewSimdCmpOpAnyNode(GT_NE, retType, op1, op2, simdBaseJitType, simdSize); @@ -1453,22 +1338,16 @@ GenTree* Compiler::impSimdAsHWIntrinsicSpecial(NamedIntrinsic intrinsic, return gtNewSimdLoadNode(retType, op1, simdBaseJitType, simdSize); } - case NI_Vector2_Max: - case NI_Vector3_Max: case NI_VectorT_Max: { return gtNewSimdMaxNode(retType, op1, op2, simdBaseJitType, simdSize); } - case NI_Vector2_Min: - case NI_Vector3_Min: case NI_VectorT_Min: { return gtNewSimdMinNode(retType, op1, op2, simdBaseJitType, simdSize); } - case NI_Vector2_op_Multiply: - case NI_Vector3_op_Multiply: case NI_VectorT_op_Multiply: { return gtNewSimdBinOpNode(GT_MUL, retType, op1, op2, simdBaseJitType, simdSize); @@ -1534,8 +1413,6 @@ GenTree* Compiler::impSimdAsHWIntrinsicSpecial(NamedIntrinsic intrinsic, return gtNewSimdStoreNonTemporalNode(op2, op1, simdBaseJitType, simdSize); } - case NI_Vector2_op_Subtraction: - case NI_Vector3_op_Subtraction: case NI_VectorT_op_Subtraction: { return gtNewSimdBinOpNode(GT_SUB, retType, op1, op2, simdBaseJitType, simdSize); @@ -1553,19 +1430,19 @@ GenTree* Compiler::impSimdAsHWIntrinsicSpecial(NamedIntrinsic intrinsic, case 3: { - if (SimdAsHWIntrinsicInfo::SpillSideEffectsOp1(intrinsic) && (newobjThis == nullptr)) + if (SimdAsHWIntrinsicInfo::SpillSideEffectsOp1(intrinsic)) { impSpillSideEffect(true, verCurrentState.esStackDepth - 3 DEBUGARG("Spilling op1 side effects for SimdAsHWIntrinsic")); } - if (SimdAsHWIntrinsicInfo::SpillSideEffectsOp2(intrinsic) && (newobjThis == nullptr)) + if (SimdAsHWIntrinsicInfo::SpillSideEffectsOp2(intrinsic)) { impSpillSideEffect(true, verCurrentState.esStackDepth - 2 DEBUGARG("Spilling op2 side effects for SimdAsHWIntrinsic")); } - CORINFO_ARG_LIST_HANDLE arg2 = isInstanceMethod ? argList : info.compCompHnd->getArgNext(argList); + 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))); @@ -1574,21 +1451,8 @@ GenTree* Compiler::impSimdAsHWIntrinsicSpecial(NamedIntrinsic intrinsic, argType = JITtype2varType(strip(info.compCompHnd->getArgType(sig, arg2, &argClass))); op2 = getArgForHWIntrinsic(argType, argClass); - bool implicitConstructor = isInstanceMethod && (newobjThis == nullptr) && (retType == TYP_VOID); - - if (implicitConstructor) - { - op1 = getArgForHWIntrinsic(TYP_BYREF, argClass, isInstanceMethod, newobjThis); - } - else - { - argType = isInstanceMethod - ? simdType - : JITtype2varType(strip(info.compCompHnd->getArgType(sig, argList, &argClass))); - - op1 = getArgForHWIntrinsic(argType, (newobjThis != nullptr) ? clsHnd : argClass, isInstanceMethod, - newobjThis); - } + argType = JITtype2varType(strip(info.compCompHnd->getArgType(sig, argList, &argClass))); + op1 = getArgForHWIntrinsic(argType, argClass); switch (intrinsic) { @@ -1597,55 +1461,11 @@ GenTree* Compiler::impSimdAsHWIntrinsicSpecial(NamedIntrinsic intrinsic, return gtNewSimdCndSelNode(retType, op1, op2, op3, simdBaseJitType, simdSize); } - case NI_Vector2_FusedMultiplyAdd: - case NI_Vector3_FusedMultiplyAdd: case NI_VectorT_FusedMultiplyAdd: { return gtNewSimdFmaNode(retType, op1, op2, op3, simdBaseJitType, simdSize); } - case NI_Vector2_Lerp: - case NI_Vector3_Lerp: - { - // We generate nodes equivalent to `(op1 * (1.0f - op3)) + (op2 * op3)` - // optimizing for xarch by doing a single broadcast and for arm64 by - // using multiply by scalar - - assert(simdBaseType == TYP_FLOAT); - -#if defined(TARGET_XARCH) - // op3 = broadcast(op3) - op3 = gtNewSimdCreateBroadcastNode(retType, op3, simdBaseJitType, simdSize); -#endif // TARGET_XARCH - - // clonedOp3 = op3 - GenTree* clonedOp3; - op3 = impCloneExpr(op3, &clonedOp3, CHECK_SPILL_ALL, nullptr DEBUGARG("Clone op3 for vector lerp")); - -#if defined(TARGET_XARCH) - // op3 = 1.0f - op3 - GenTree* oneCon = gtNewOneConNode(retType, simdBaseType); - op3 = gtNewSimdBinOpNode(GT_SUB, retType, oneCon, op3, simdBaseJitType, simdSize); -#elif defined(TARGET_ARM64) - // op3 = 1.0f - op3 - GenTree* oneCon = gtNewOneConNode(simdBaseType); - op3 = gtNewOperNode(GT_SUB, TYP_FLOAT, oneCon, op3); -#else -#error Unsupported platform -#endif - - // op1 *= op3 - op1 = gtNewSimdBinOpNode(GT_MUL, retType, op1, op3, simdBaseJitType, simdSize); - - // op2 *= clonedOp3 - op2 = gtNewSimdBinOpNode(GT_MUL, retType, op2, clonedOp3, simdBaseJitType, simdSize); - - // return op1 + op2 - return gtNewSimdBinOpNode(GT_ADD, retType, op1, op2, simdBaseJitType, simdSize); - } - - case NI_Vector2_MultiplyAddEstimate: - case NI_Vector3_MultiplyAddEstimate: case NI_VectorT_MultiplyAddEstimate: { bool isFmaAccelerated = false; @@ -1683,88 +1503,6 @@ GenTree* Compiler::impSimdAsHWIntrinsicSpecial(NamedIntrinsic intrinsic, return gtNewSimdStoreNode(op2, op1, simdBaseJitType, simdSize); } - case NI_Vector2_Create: - { - assert(retType == TYP_VOID); - assert(simdBaseType == TYP_FLOAT); - assert(simdSize == 8); - - if (op2->IsCnsFltOrDbl() && op3->IsCnsFltOrDbl()) - { - GenTreeVecCon* vecCon = gtNewVconNode(TYP_SIMD8); - - float cnsVal = 0; - - vecCon->gtSimdVal.f32[0] = static_cast(op2->AsDblCon()->DconValue()); - vecCon->gtSimdVal.f32[1] = static_cast(op3->AsDblCon()->DconValue()); - - copyBlkSrc = vecCon; - } - else if (areArgumentsContiguous(op2, op3)) - { - GenTree* op2Address = CreateAddressNodeForSimdHWIntrinsicCreate(op2, simdBaseType, 8); - copyBlkSrc = gtNewIndir(TYP_SIMD8, op2Address); - } - else - { -#if defined(TARGET_XARCH) - IntrinsicNodeBuilder nodeBuilder(getAllocator(CMK_ASTNode), 4); - - nodeBuilder.AddOperand(0, op2); - nodeBuilder.AddOperand(1, op3); - nodeBuilder.AddOperand(2, gtNewZeroConNode(TYP_FLOAT)); - nodeBuilder.AddOperand(3, gtNewZeroConNode(TYP_FLOAT)); - - copyBlkSrc = gtNewSimdHWIntrinsicNode(TYP_SIMD8, std::move(nodeBuilder), NI_Vector128_Create, - simdBaseJitType, 16); -#elif defined(TARGET_ARM64) - copyBlkSrc = - gtNewSimdHWIntrinsicNode(TYP_SIMD8, op2, op3, NI_Vector64_Create, simdBaseJitType, 8); -#else -#error Unsupported platform -#endif // !TARGET_XARCH && !TARGET_ARM64 - } - - copyBlkDst = op1; - break; - } - - case NI_Vector3_CreateFromVector2: - { - assert(retType == TYP_VOID); - assert(simdBaseType == TYP_FLOAT); - assert(simdSize == 12); - assert(simdType == TYP_SIMD12); - - // TODO-CQ: We should be able to check for contiguous args here after - // the relevant methods are updated to support more than just float - - if (op2->IsCnsVec() && op3->IsCnsFltOrDbl()) - { - GenTreeVecCon* vecCon = op2->AsVecCon(); - vecCon->gtType = simdType; - - vecCon->gtSimdVal.f32[2] = static_cast(op3->AsDblCon()->DconValue()); - copyBlkSrc = vecCon; - } - else - { - GenTree* idx = gtNewIconNode(2, TYP_INT); - - op2 = gtNewSimdHWIntrinsicNode(TYP_SIMD16, op2, NI_Vector128_AsVector128Unsafe, simdBaseJitType, - 12); - op2 = gtNewSimdWithElementNode(TYP_SIMD16, op2, idx, op3, simdBaseJitType, 16); - - copyBlkSrc = - gtNewSimdHWIntrinsicNode(TYP_SIMD12, op2, NI_Vector128_AsVector3, simdBaseJitType, 16); - } - - copyBlkDst = op1; - break; - } - - case NI_Vector2_WithElement: - case NI_Vector3_WithElement: case NI_VectorT_WithElement: { return gtNewSimdWithElementNode(retType, op1, op2, op3, simdBaseJitType, simdSize); @@ -1779,93 +1517,6 @@ GenTree* Compiler::impSimdAsHWIntrinsicSpecial(NamedIntrinsic intrinsic, } break; } - - case 4: - { - assert(isInstanceMethod); - assert(SimdAsHWIntrinsicInfo::SpillSideEffectsOp1(intrinsic)); - assert(!SimdAsHWIntrinsicInfo::SpillSideEffectsOp2(intrinsic)); - - if (newobjThis == nullptr) - { - impSpillSideEffect(true, verCurrentState.esStackDepth - - 4 DEBUGARG("Spilling op1 side effects for SimdAsHWIntrinsic")); - } - - CORINFO_ARG_LIST_HANDLE arg2 = argList; - CORINFO_ARG_LIST_HANDLE arg3 = info.compCompHnd->getArgNext(arg2); - CORINFO_ARG_LIST_HANDLE arg4 = info.compCompHnd->getArgNext(arg3); - - argType = JITtype2varType(strip(info.compCompHnd->getArgType(sig, arg4, &argClass))); - op4 = getArgForHWIntrinsic(argType, argClass); - - argType = JITtype2varType(strip(info.compCompHnd->getArgType(sig, arg3, &argClass))); - op3 = getArgForHWIntrinsic(argType, argClass); - - argType = JITtype2varType(strip(info.compCompHnd->getArgType(sig, arg2, &argClass))); - op2 = getArgForHWIntrinsic(argType, argClass); - - if ((newobjThis == nullptr) && (retType == TYP_VOID)) - { - op1 = getArgForHWIntrinsic(TYP_BYREF, argClass, isInstanceMethod, newobjThis); - } - else - { - op1 = getArgForHWIntrinsic(simdType, (newobjThis != nullptr) ? clsHnd : argClass, isInstanceMethod, - newobjThis); - } - - switch (intrinsic) - { - case NI_Vector3_Create: - { - assert(retType == TYP_VOID); - assert(simdBaseType == TYP_FLOAT); - assert(simdSize == 12); - - if (op2->IsCnsFltOrDbl() && op3->IsCnsFltOrDbl() && op4->IsCnsFltOrDbl()) - { - GenTreeVecCon* vecCon = gtNewVconNode(TYP_SIMD12); - - float cnsVal = 0; - - vecCon->gtSimdVal.f32[0] = static_cast(op2->AsDblCon()->DconValue()); - vecCon->gtSimdVal.f32[1] = static_cast(op3->AsDblCon()->DconValue()); - vecCon->gtSimdVal.f32[2] = static_cast(op4->AsDblCon()->DconValue()); - - copyBlkSrc = vecCon; - } - else if (areArgumentsContiguous(op2, op3) && areArgumentsContiguous(op3, op4)) - { - GenTree* op2Address = CreateAddressNodeForSimdHWIntrinsicCreate(op2, simdBaseType, 12); - copyBlkSrc = gtNewIndir(TYP_SIMD12, op2Address); - } - else - { - IntrinsicNodeBuilder nodeBuilder(getAllocator(CMK_ASTNode), 4); - - nodeBuilder.AddOperand(0, op2); - nodeBuilder.AddOperand(1, op3); - nodeBuilder.AddOperand(2, op4); - nodeBuilder.AddOperand(3, gtNewZeroConNode(TYP_FLOAT)); - - copyBlkSrc = gtNewSimdHWIntrinsicNode(TYP_SIMD12, std::move(nodeBuilder), NI_Vector128_Create, - simdBaseJitType, 16); - } - - copyBlkDst = op1; - break; - } - - default: - { - // Some platforms warn about unhandled switch cases - // We handle it more generally via the assert and nullptr return below. - break; - } - } - break; - } } if (copyBlkDst != nullptr) diff --git a/src/coreclr/jit/simdashwintrinsic.h b/src/coreclr/jit/simdashwintrinsic.h index d42fff256ab8e..77fc25ae74f4e 100644 --- a/src/coreclr/jit/simdashwintrinsic.h +++ b/src/coreclr/jit/simdashwintrinsic.h @@ -7,11 +7,6 @@ enum class SimdAsHWIntrinsicClassId { Unknown, - Plane, - Quaternion, - Vector2, - Vector3, - Vector4, Vector, VectorT, }; @@ -20,25 +15,14 @@ enum class SimdAsHWIntrinsicFlag : unsigned int { None = 0, - // Indicates compFloatingPointUsed does not need to be set. - NoFloatingPointUsed = 0x1, - - // Indicates the intrinsic is for an instance method. - InstanceMethod = 0x02, - - /* UnusedFlag = 0x04, */ - - // Base type should come from the this argument - BaseTypeFromThisArg = 0x08, - // For SIMDVectorHandle, keep the base type from the result type - KeepBaseTypeFromRet = 0x10, + KeepBaseTypeFromRet = 0x01, // Indicates that side effects need to be spilled for op1 - SpillSideEffectsOp1 = 0x20, + SpillSideEffectsOp1 = 0x02, // Indicates that side effects need to be spilled for op2 - SpillSideEffectsOp2 = 0x40, + SpillSideEffectsOp2 = 0x04, }; inline SimdAsHWIntrinsicFlag operator~(SimdAsHWIntrinsicFlag value) @@ -121,24 +105,6 @@ struct SimdAsHWIntrinsicInfo // Flags lookup - static bool IsFloatingPointUsed(NamedIntrinsic id) - { - SimdAsHWIntrinsicFlag flags = lookupFlags(id); - return (flags & SimdAsHWIntrinsicFlag::NoFloatingPointUsed) == SimdAsHWIntrinsicFlag::None; - } - - static bool IsInstanceMethod(NamedIntrinsic id) - { - SimdAsHWIntrinsicFlag flags = lookupFlags(id); - return (flags & SimdAsHWIntrinsicFlag::InstanceMethod) == SimdAsHWIntrinsicFlag::InstanceMethod; - } - - static bool BaseTypeFromThisArg(NamedIntrinsic id) - { - SimdAsHWIntrinsicFlag flags = lookupFlags(id); - return (flags & SimdAsHWIntrinsicFlag::BaseTypeFromThisArg) == SimdAsHWIntrinsicFlag::BaseTypeFromThisArg; - } - static bool KeepBaseTypeFromRet(NamedIntrinsic id) { SimdAsHWIntrinsicFlag flags = lookupFlags(id); diff --git a/src/coreclr/jit/simdashwintrinsiclistarm64.h b/src/coreclr/jit/simdashwintrinsiclistarm64.h index bcf8364e75d6f..efb3107930b03 100644 --- a/src/coreclr/jit/simdashwintrinsiclistarm64.h +++ b/src/coreclr/jit/simdashwintrinsiclistarm64.h @@ -32,57 +32,6 @@ * Each intrinsic has one or more flags with type of `enum SimdAsHWIntrinsicFlag` */ -// ************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************* -// ISA ID Name NumArg Instructions Flags -// {TYP_BYTE, TYP_UBYTE, TYP_SHORT, TYP_USHORT, TYP_INT, TYP_UINT, TYP_LONG, TYP_ULONG, TYP_FLOAT, TYP_DOUBLE} -// ************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************* -// Vector2 Intrinsics -SIMD_AS_HWINTRINSIC_ID(Vector2, Abs, 1, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector2_Abs, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_NM(Vector2, Create, ".ctor", 3, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector2_Create, NI_Illegal}, SimdAsHWIntrinsicFlag::InstanceMethod | SimdAsHWIntrinsicFlag::SpillSideEffectsOp1) -SIMD_AS_HWINTRINSIC_NM(Vector2, CreateBroadcast, ".ctor", 2, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector2_CreateBroadcast, NI_Illegal}, SimdAsHWIntrinsicFlag::InstanceMethod | SimdAsHWIntrinsicFlag::SpillSideEffectsOp1) -SIMD_AS_HWINTRINSIC_ID(Vector2, Dot, 2, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector2_Dot, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_ID(Vector2, FusedMultiplyAdd, 3, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector2_FusedMultiplyAdd, NI_Illegal}, SimdAsHWIntrinsicFlag::SpillSideEffectsOp1 | SimdAsHWIntrinsicFlag::SpillSideEffectsOp2) -SIMD_AS_HWINTRINSIC_ID(Vector2, GetElement, 2, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector2_GetElement, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_ID(Vector2, Lerp, 3, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector2_Lerp, NI_Illegal}, SimdAsHWIntrinsicFlag::SpillSideEffectsOp1 | SimdAsHWIntrinsicFlag::SpillSideEffectsOp2) -SIMD_AS_HWINTRINSIC_ID(Vector2, Max, 2, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector2_Max, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_ID(Vector2, Min, 2, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector2_Min, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_ID(Vector2, MultiplyAddEstimate, 3, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector2_MultiplyAddEstimate, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_ID(Vector2, op_Addition, 2, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector2_op_Addition, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_ID(Vector2, op_Division, 2, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector2_op_Division, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_ID(Vector2, op_Equality, 2, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector2_op_Equality, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_ID(Vector2, op_Inequality, 2, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector2_op_Inequality, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_ID(Vector2, op_Multiply, 2, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector2_op_Multiply, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_ID(Vector2, op_Subtraction, 2, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector2_op_Subtraction, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_ID(Vector2, op_UnaryNegation, 1, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector2_op_UnaryNegation, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_NM(Vector2, Sqrt, "SquareRoot", 1, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector2_Sqrt, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_ID(Vector2, WithElement, 3, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector2_WithElement, NI_Illegal}, SimdAsHWIntrinsicFlag::None) - -// ************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************* -// ISA ID Name NumArg Instructions Flags -// {TYP_BYTE, TYP_UBYTE, TYP_SHORT, TYP_USHORT, TYP_INT, TYP_UINT, TYP_LONG, TYP_ULONG, TYP_FLOAT, TYP_DOUBLE} -// ************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************* -// Vector3 Intrinsics -SIMD_AS_HWINTRINSIC_ID(Vector3, Abs, 1, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector3_Abs, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_NM(Vector3, Create, ".ctor", 4, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector3_Create, NI_Illegal}, SimdAsHWIntrinsicFlag::InstanceMethod | SimdAsHWIntrinsicFlag::SpillSideEffectsOp1) -SIMD_AS_HWINTRINSIC_NM(Vector3, CreateBroadcast, ".ctor", 2, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector3_CreateBroadcast, NI_Illegal}, SimdAsHWIntrinsicFlag::InstanceMethod | SimdAsHWIntrinsicFlag::SpillSideEffectsOp1) -SIMD_AS_HWINTRINSIC_NM(Vector3, CreateFromVector2, ".ctor", 3, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector3_CreateFromVector2, NI_Illegal}, SimdAsHWIntrinsicFlag::InstanceMethod | SimdAsHWIntrinsicFlag::SpillSideEffectsOp1 | SimdAsHWIntrinsicFlag::BaseTypeFromThisArg) -SIMD_AS_HWINTRINSIC_ID(Vector3, Dot, 2, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector3_Dot, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_ID(Vector3, FusedMultiplyAdd, 3, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector3_FusedMultiplyAdd, NI_Illegal}, SimdAsHWIntrinsicFlag::SpillSideEffectsOp1 | SimdAsHWIntrinsicFlag::SpillSideEffectsOp2) -SIMD_AS_HWINTRINSIC_ID(Vector3, GetElement, 2, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector3_GetElement, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_ID(Vector3, Lerp, 3, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector3_Lerp, NI_Illegal}, SimdAsHWIntrinsicFlag::SpillSideEffectsOp1 | SimdAsHWIntrinsicFlag::SpillSideEffectsOp2) -SIMD_AS_HWINTRINSIC_ID(Vector3, Max, 2, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector3_Max, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_ID(Vector3, Min, 2, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector3_Min, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_ID(Vector3, MultiplyAddEstimate, 3, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector3_MultiplyAddEstimate, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_ID(Vector3, op_Addition, 2, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector3_op_Addition, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_ID(Vector3, op_Division, 2, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector3_op_Division, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_ID(Vector3, op_Equality, 2, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector3_op_Equality, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_ID(Vector3, op_Inequality, 2, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector3_op_Inequality, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_ID(Vector3, op_Multiply, 2, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector3_op_Multiply, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_ID(Vector3, op_Subtraction, 2, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector3_op_Subtraction, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_ID(Vector3, op_UnaryNegation, 1, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector3_op_UnaryNegation, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_NM(Vector3, Sqrt, "SquareRoot", 1, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector3_Sqrt, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_ID(Vector3, WithElement, 3, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector3_WithElement, NI_Illegal}, SimdAsHWIntrinsicFlag::None) - // ************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************* // ISA ID Name NumArg Instructions Flags // {TYP_BYTE, TYP_UBYTE, TYP_SHORT, TYP_USHORT, TYP_INT, TYP_UINT, TYP_LONG, TYP_ULONG, TYP_FLOAT, TYP_DOUBLE} @@ -115,7 +64,7 @@ SIMD_AS_HWINTRINSIC_ID(VectorT, ConvertToUInt32, SIMD_AS_HWINTRINSIC_ID(VectorT, ConvertToUInt32Native, 1, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_VectorT_ConvertToUInt32Native, NI_Illegal}, SimdAsHWIntrinsicFlag::None) SIMD_AS_HWINTRINSIC_ID(VectorT, ConvertToUInt64, 1, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_VectorT_ConvertToUInt64}, SimdAsHWIntrinsicFlag::None) SIMD_AS_HWINTRINSIC_ID(VectorT, ConvertToUInt64Native, 1, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_VectorT_ConvertToUInt64Native}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_NM(VectorT, CreateBroadcast, ".ctor", 2, {NI_VectorT_CreateBroadcast, NI_VectorT_CreateBroadcast, NI_VectorT_CreateBroadcast, NI_VectorT_CreateBroadcast, NI_VectorT_CreateBroadcast, NI_VectorT_CreateBroadcast, NI_VectorT_CreateBroadcast, NI_VectorT_CreateBroadcast, NI_VectorT_CreateBroadcast, NI_VectorT_CreateBroadcast}, SimdAsHWIntrinsicFlag::InstanceMethod | SimdAsHWIntrinsicFlag::SpillSideEffectsOp1) +SIMD_AS_HWINTRINSIC_ID(VectorT, Create, 1, {NI_VectorT_Create, NI_VectorT_Create, NI_VectorT_Create, NI_VectorT_Create, NI_VectorT_Create, NI_VectorT_Create, NI_VectorT_Create, NI_VectorT_Create, NI_VectorT_Create, NI_VectorT_Create}, SimdAsHWIntrinsicFlag::None) SIMD_AS_HWINTRINSIC_ID(VectorT, CreateSequence, 2, {NI_VectorT_CreateSequence, NI_VectorT_CreateSequence, NI_VectorT_CreateSequence, NI_VectorT_CreateSequence, NI_VectorT_CreateSequence, NI_VectorT_CreateSequence, NI_VectorT_CreateSequence, NI_VectorT_CreateSequence, NI_VectorT_CreateSequence, NI_VectorT_CreateSequence}, SimdAsHWIntrinsicFlag::SpillSideEffectsOp1) SIMD_AS_HWINTRINSIC_ID(VectorT, Dot, 2, {NI_VectorT_Dot, NI_VectorT_Dot, NI_VectorT_Dot, NI_VectorT_Dot, NI_VectorT_Dot, NI_VectorT_Dot, NI_Illegal, NI_Illegal, NI_VectorT_Dot, NI_VectorT_Dot}, SimdAsHWIntrinsicFlag::None) SIMD_AS_HWINTRINSIC_ID(VectorT, Equals, 2, {NI_VectorT_Equals, NI_VectorT_Equals, NI_VectorT_Equals, NI_VectorT_Equals, NI_VectorT_Equals, NI_VectorT_Equals, NI_VectorT_Equals, NI_VectorT_Equals, NI_VectorT_Equals, NI_VectorT_Equals}, SimdAsHWIntrinsicFlag::None) diff --git a/src/coreclr/jit/simdashwintrinsiclistxarch.h b/src/coreclr/jit/simdashwintrinsiclistxarch.h index f8ccbeb90cb08..4074204e29ea4 100644 --- a/src/coreclr/jit/simdashwintrinsiclistxarch.h +++ b/src/coreclr/jit/simdashwintrinsiclistxarch.h @@ -32,57 +32,6 @@ * Each intrinsic has one or more flags with type of `enum SimdAsHWIntrinsicFlag` */ -// ************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************* -// ISA ID Name NumArg Instructions Flags -// {TYP_BYTE, TYP_UBYTE, TYP_SHORT, TYP_USHORT, TYP_INT, TYP_UINT, TYP_LONG, TYP_ULONG, TYP_FLOAT, TYP_DOUBLE} -// ************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************* -// Vector2 Intrinsics -SIMD_AS_HWINTRINSIC_ID(Vector2, Abs, 1, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector2_Abs, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_NM(Vector2, Create, ".ctor", 3, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector2_Create, NI_Illegal}, SimdAsHWIntrinsicFlag::InstanceMethod | SimdAsHWIntrinsicFlag::SpillSideEffectsOp1) -SIMD_AS_HWINTRINSIC_NM(Vector2, CreateBroadcast, ".ctor", 2, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector2_CreateBroadcast, NI_Illegal}, SimdAsHWIntrinsicFlag::InstanceMethod | SimdAsHWIntrinsicFlag::SpillSideEffectsOp1) -SIMD_AS_HWINTRINSIC_ID(Vector2, Dot, 2, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector2_Dot, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_ID(Vector2, FusedMultiplyAdd, 3, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector2_FusedMultiplyAdd, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_ID(Vector2, GetElement, 2, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector2_GetElement, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_ID(Vector2, Lerp, 3, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector2_Lerp, NI_Illegal}, SimdAsHWIntrinsicFlag::SpillSideEffectsOp1 | SimdAsHWIntrinsicFlag::SpillSideEffectsOp2) -SIMD_AS_HWINTRINSIC_ID(Vector2, Max, 2, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector2_Max, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_ID(Vector2, Min, 2, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector2_Min, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_ID(Vector2, MultiplyAddEstimate, 3, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector2_MultiplyAddEstimate, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_ID(Vector2, op_Addition, 2, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector2_op_Addition, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_ID(Vector2, op_Division, 2, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector2_op_Division, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_ID(Vector2, op_Equality, 2, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector2_op_Equality, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_ID(Vector2, op_Inequality, 2, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector2_op_Inequality, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_ID(Vector2, op_Multiply, 2, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector2_op_Multiply, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_ID(Vector2, op_Subtraction, 2, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector2_op_Subtraction, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_ID(Vector2, op_UnaryNegation, 1, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector2_op_UnaryNegation, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_NM(Vector2, Sqrt, "SquareRoot", 1, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector2_Sqrt, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_ID(Vector2, WithElement, 3, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector2_WithElement, NI_Illegal}, SimdAsHWIntrinsicFlag::None) - -// ************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************* -// ISA ID Name NumArg Instructions Flags -// {TYP_BYTE, TYP_UBYTE, TYP_SHORT, TYP_USHORT, TYP_INT, TYP_UINT, TYP_LONG, TYP_ULONG, TYP_FLOAT, TYP_DOUBLE} -// ************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************* -// Vector3 Intrinsics -SIMD_AS_HWINTRINSIC_ID(Vector3, Abs, 1, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector3_Abs, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_NM(Vector3, Create, ".ctor", 4, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector3_Create, NI_Illegal}, SimdAsHWIntrinsicFlag::InstanceMethod | SimdAsHWIntrinsicFlag::SpillSideEffectsOp1) -SIMD_AS_HWINTRINSIC_NM(Vector3, CreateBroadcast, ".ctor", 2, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector3_CreateBroadcast, NI_Illegal}, SimdAsHWIntrinsicFlag::InstanceMethod | SimdAsHWIntrinsicFlag::SpillSideEffectsOp1) -SIMD_AS_HWINTRINSIC_NM(Vector3, CreateFromVector2, ".ctor", 3, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector3_CreateFromVector2, NI_Illegal}, SimdAsHWIntrinsicFlag::InstanceMethod | SimdAsHWIntrinsicFlag::SpillSideEffectsOp1 | SimdAsHWIntrinsicFlag::BaseTypeFromThisArg) -SIMD_AS_HWINTRINSIC_ID(Vector3, Dot, 2, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector3_Dot, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_ID(Vector3, FusedMultiplyAdd, 3, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector3_FusedMultiplyAdd, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_ID(Vector3, GetElement, 2, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector3_GetElement, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_ID(Vector3, Lerp, 3, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector3_Lerp, NI_Illegal}, SimdAsHWIntrinsicFlag::SpillSideEffectsOp1 | SimdAsHWIntrinsicFlag::SpillSideEffectsOp2) -SIMD_AS_HWINTRINSIC_ID(Vector3, Max, 2, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector3_Max, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_ID(Vector3, Min, 2, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector3_Min, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_ID(Vector3, MultiplyAddEstimate, 3, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector3_MultiplyAddEstimate, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_ID(Vector3, op_Addition, 2, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector3_op_Addition, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_ID(Vector3, op_Division, 2, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector3_op_Division, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_ID(Vector3, op_Equality, 2, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector3_op_Equality, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_ID(Vector3, op_Inequality, 2, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector3_op_Inequality, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_ID(Vector3, op_Multiply, 2, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector3_op_Multiply, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_ID(Vector3, op_Subtraction, 2, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector3_op_Subtraction, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_ID(Vector3, op_UnaryNegation, 1, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector3_op_UnaryNegation, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_NM(Vector3, Sqrt, "SquareRoot", 1, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector3_Sqrt, NI_Illegal}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_ID(Vector3, WithElement, 3, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Vector3_WithElement, NI_Illegal}, SimdAsHWIntrinsicFlag::None) - // ************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************* // ISA ID Name NumArg Instructions Flags // {TYP_BYTE, TYP_UBYTE, TYP_SHORT, TYP_USHORT, TYP_INT, TYP_UINT, TYP_LONG, TYP_ULONG, TYP_FLOAT, TYP_DOUBLE} @@ -115,7 +64,7 @@ SIMD_AS_HWINTRINSIC_ID(VectorT, ConvertToUInt32, SIMD_AS_HWINTRINSIC_ID(VectorT, ConvertToUInt32Native, 1, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_VectorT_ConvertToUInt32Native, NI_Illegal}, SimdAsHWIntrinsicFlag::None) SIMD_AS_HWINTRINSIC_ID(VectorT, ConvertToUInt64, 1, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_VectorT_ConvertToUInt64}, SimdAsHWIntrinsicFlag::None) SIMD_AS_HWINTRINSIC_ID(VectorT, ConvertToUInt64Native, 1, {NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_Illegal, NI_VectorT_ConvertToUInt64Native}, SimdAsHWIntrinsicFlag::None) -SIMD_AS_HWINTRINSIC_NM(VectorT, CreateBroadcast, ".ctor", 2, {NI_VectorT_CreateBroadcast, NI_VectorT_CreateBroadcast, NI_VectorT_CreateBroadcast, NI_VectorT_CreateBroadcast, NI_VectorT_CreateBroadcast, NI_VectorT_CreateBroadcast, NI_VectorT_CreateBroadcast, NI_VectorT_CreateBroadcast, NI_VectorT_CreateBroadcast, NI_VectorT_CreateBroadcast}, SimdAsHWIntrinsicFlag::InstanceMethod | SimdAsHWIntrinsicFlag::SpillSideEffectsOp1) +SIMD_AS_HWINTRINSIC_ID(VectorT, Create, 1, {NI_VectorT_Create, NI_VectorT_Create, NI_VectorT_Create, NI_VectorT_Create, NI_VectorT_Create, NI_VectorT_Create, NI_VectorT_Create, NI_VectorT_Create, NI_VectorT_Create, NI_VectorT_Create}, SimdAsHWIntrinsicFlag::None) SIMD_AS_HWINTRINSIC_ID(VectorT, CreateSequence, 2, {NI_VectorT_CreateSequence, NI_VectorT_CreateSequence, NI_VectorT_CreateSequence, NI_VectorT_CreateSequence, NI_VectorT_CreateSequence, NI_VectorT_CreateSequence, NI_VectorT_CreateSequence, NI_VectorT_CreateSequence, NI_VectorT_CreateSequence, NI_VectorT_CreateSequence}, SimdAsHWIntrinsicFlag::SpillSideEffectsOp1) SIMD_AS_HWINTRINSIC_ID(VectorT, Dot, 2, {NI_Illegal, NI_Illegal, NI_VectorT_Dot, NI_VectorT_Dot, NI_VectorT_Dot, NI_VectorT_Dot, NI_Illegal, NI_Illegal, NI_VectorT_Dot, NI_VectorT_Dot}, SimdAsHWIntrinsicFlag::None) SIMD_AS_HWINTRINSIC_ID(VectorT, Equals, 2, {NI_VectorT_Equals, NI_VectorT_Equals, NI_VectorT_Equals, NI_VectorT_Equals, NI_VectorT_Equals, NI_VectorT_Equals, NI_VectorT_Equals, NI_VectorT_Equals, NI_VectorT_Equals, NI_VectorT_Equals}, SimdAsHWIntrinsicFlag::None) diff --git a/src/libraries/Common/src/System/Net/TlsStream.cs b/src/libraries/Common/src/System/Net/TlsStream.cs index 503253099aac1..28c344a659a31 100644 --- a/src/libraries/Common/src/System/Net/TlsStream.cs +++ b/src/libraries/Common/src/System/Net/TlsStream.cs @@ -8,6 +8,9 @@ using System.Threading; using System.Threading.Tasks; +#pragma warning disable SYSLIB0014 // ServicePointManager is obsolete +// This type is used by FtpWebRequest (already obsolete) and SmtpClient (discouraged). + namespace System.Net { internal sealed class TlsStream : NetworkStream diff --git a/src/libraries/System.Net.HttpListener/src/System/Net/Managed/HttpConnection.cs b/src/libraries/System.Net.HttpListener/src/System/Net/Managed/HttpConnection.cs index b9d99349dabb9..10e75797cfd4b 100644 --- a/src/libraries/System.Net.HttpListener/src/System/Net/Managed/HttpConnection.cs +++ b/src/libraries/System.Net.HttpListener/src/System/Net/Managed/HttpConnection.cs @@ -30,6 +30,7 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Net.Security; @@ -100,7 +101,11 @@ public HttpConnection(Socket sock, HttpEndPointListener epl, bool secure, X509Ce } _timer = new Timer(OnTimeout, null, Timeout.Infinite, Timeout.Infinite); + +#pragma warning disable SYSLIB0014 // ServicePointManager is obsolete _sslStream?.AuthenticateAsServer(_cert, true, (SslProtocols)ServicePointManager.SecurityProtocol, false); +#pragma warning restore SYSLIB0014 + Init(); } diff --git a/src/libraries/System.Net.Requests/ref/System.Net.Requests.cs b/src/libraries/System.Net.Requests/ref/System.Net.Requests.cs index cd52c0bfb0c6b..2e10e5fca4666 100644 --- a/src/libraries/System.Net.Requests/ref/System.Net.Requests.cs +++ b/src/libraries/System.Net.Requests/ref/System.Net.Requests.cs @@ -486,6 +486,7 @@ internal ServicePoint() { } public bool CloseConnectionGroup(string connectionGroupName) { throw null; } public void SetTcpKeepAlive(bool enabled, int keepAliveTime, int keepAliveInterval) { } } + [System.ObsoleteAttribute("WebRequest, HttpWebRequest, ServicePoint, and WebClient are obsolete. Use HttpClient instead. Settings on ServicePointManager no longer affect SslStream or HttpClient.", DiagnosticId = "SYSLIB0014", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")] public partial class ServicePointManager { internal ServicePointManager() { } @@ -504,11 +505,8 @@ internal ServicePointManager() { } public static System.Net.SecurityProtocolType SecurityProtocol { get { throw null; } set { } } public static System.Net.Security.RemoteCertificateValidationCallback? ServerCertificateValidationCallback { get { throw null; } set { } } public static bool UseNagleAlgorithm { get { throw null; } set { } } - [System.ObsoleteAttribute("WebRequest, HttpWebRequest, ServicePoint, and WebClient are obsolete. Use HttpClient instead.", DiagnosticId = "SYSLIB0014", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")] public static System.Net.ServicePoint FindServicePoint(string uriString, System.Net.IWebProxy? proxy) { throw null; } - [System.ObsoleteAttribute("WebRequest, HttpWebRequest, ServicePoint, and WebClient are obsolete. Use HttpClient instead.", DiagnosticId = "SYSLIB0014", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")] public static System.Net.ServicePoint FindServicePoint(System.Uri address) { throw null; } - [System.ObsoleteAttribute("WebRequest, HttpWebRequest, ServicePoint, and WebClient are obsolete. Use HttpClient instead.", DiagnosticId = "SYSLIB0014", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")] public static System.Net.ServicePoint FindServicePoint(System.Uri address, System.Net.IWebProxy? proxy) { throw null; } public static void SetTcpKeepAlive(bool enabled, int keepAliveTime, int keepAliveInterval) { } } diff --git a/src/libraries/System.Net.Requests/src/System/Net/FileWebRequest.cs b/src/libraries/System.Net.Requests/src/System/Net/FileWebRequest.cs index 85d9097da906b..a7368b0fdafd2 100644 --- a/src/libraries/System.Net.Requests/src/System/Net/FileWebRequest.cs +++ b/src/libraries/System.Net.Requests/src/System/Net/FileWebRequest.cs @@ -9,6 +9,8 @@ namespace System.Net { + // NOTE: While this class is not explicitly marked as obsolete, + // it effectively is by virtue of WebRequest.Create being obsolete. public class FileWebRequest : WebRequest, ISerializable { private readonly WebHeaderCollection _headers = new WebHeaderCollection(); diff --git a/src/libraries/System.Net.Requests/src/System/Net/FtpWebRequest.cs b/src/libraries/System.Net.Requests/src/System/Net/FtpWebRequest.cs index 4f55ea7b9048c..7694b7007fdaa 100644 --- a/src/libraries/System.Net.Requests/src/System/Net/FtpWebRequest.cs +++ b/src/libraries/System.Net.Requests/src/System/Net/FtpWebRequest.cs @@ -176,6 +176,9 @@ internal static FtpMethodInfo GetMethodInfo(string method) }; } + // NOTE: While this class is not explicitly marked as obsolete, + // it effectively is by virtue of WebRequest.Create being obsolete. + /// /// The FtpWebRequest class implements a basic FTP client interface. /// diff --git a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs index 7510495533be9..fbc32738ef21d 100644 --- a/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs +++ b/src/libraries/System.Net.Requests/src/System/Net/HttpWebRequest.cs @@ -26,6 +26,8 @@ namespace System.Net { public delegate void HttpContinueDelegate(int StatusCode, WebHeaderCollection httpHeaders); + // NOTE: While this class is not explicitly marked as obsolete, + // it effectively is by virtue of WebRequest.Create being obsolete. public class HttpWebRequest : WebRequest, ISerializable { private const int DefaultContinueTimeout = 350; // Current default value from .NET Desktop. diff --git a/src/libraries/System.Net.Requests/src/System/Net/ServicePoint/ServicePoint.cs b/src/libraries/System.Net.Requests/src/System/Net/ServicePoint/ServicePoint.cs index d48ac0760992c..fdf6964981b12 100644 --- a/src/libraries/System.Net.Requests/src/System/Net/ServicePoint/ServicePoint.cs +++ b/src/libraries/System.Net.Requests/src/System/Net/ServicePoint/ServicePoint.cs @@ -7,6 +7,9 @@ namespace System.Net { + // NOTE: While this class is not explicitly marked as obsolete, + // it effectively is by virtue of ServicePointManager being obsolete, + // where ServicePointManager is the only way to obtain ServicePoint instances. public class ServicePoint { private int _connectionLeaseTimeout = -1; diff --git a/src/libraries/System.Net.Requests/src/System/Net/ServicePoint/ServicePointManager.cs b/src/libraries/System.Net.Requests/src/System/Net/ServicePoint/ServicePointManager.cs index 8245e309efd93..be282f8401a92 100644 --- a/src/libraries/System.Net.Requests/src/System/Net/ServicePoint/ServicePointManager.cs +++ b/src/libraries/System.Net.Requests/src/System/Net/ServicePoint/ServicePointManager.cs @@ -9,6 +9,7 @@ namespace System.Net { + [Obsolete(Obsoletions.WebRequestMessage + " Settings on ServicePointManager no longer affect SslStream or HttpClient.", DiagnosticId = Obsoletions.WebRequestDiagId, UrlFormat = Obsoletions.SharedUrlFormat)] public class ServicePointManager { public const int DefaultNonPersistentConnectionLimit = 4; @@ -99,13 +100,10 @@ public static int DnsRefreshTimeout [UnsupportedOSPlatform("browser")] public static EncryptionPolicy EncryptionPolicy { get; } = EncryptionPolicy.RequireEncryption; - [Obsolete(Obsoletions.WebRequestMessage, DiagnosticId = Obsoletions.WebRequestDiagId, UrlFormat = Obsoletions.SharedUrlFormat)] public static ServicePoint FindServicePoint(Uri address) => FindServicePoint(address, null); - [Obsolete(Obsoletions.WebRequestMessage, DiagnosticId = Obsoletions.WebRequestDiagId, UrlFormat = Obsoletions.SharedUrlFormat)] public static ServicePoint FindServicePoint(string uriString, IWebProxy? proxy) => FindServicePoint(new Uri(uriString), proxy); - [Obsolete(Obsoletions.WebRequestMessage, DiagnosticId = Obsoletions.WebRequestDiagId, UrlFormat = Obsoletions.SharedUrlFormat)] public static ServicePoint FindServicePoint(Uri address, IWebProxy? proxy) { ArgumentNullException.ThrowIfNull(address); diff --git a/src/libraries/System.Net.Requests/src/System/Net/WebRequest.cs b/src/libraries/System.Net.Requests/src/System/Net/WebRequest.cs index 5442fdeef0fac..da55677ba160a 100644 --- a/src/libraries/System.Net.Requests/src/System/Net/WebRequest.cs +++ b/src/libraries/System.Net.Requests/src/System/Net/WebRequest.cs @@ -15,6 +15,8 @@ using System.Threading.Tasks; namespace System.Net { + // NOTE: While this class is not explicitly marked as obsolete, + // it effectively is by virtue of WebRequest.Create being obsolete. public abstract class WebRequest : MarshalByRefObject, ISerializable { internal sealed class WebRequestPrefixElement diff --git a/src/libraries/System.Numerics.Vectors/ref/System.Numerics.Vectors.cs b/src/libraries/System.Numerics.Vectors/ref/System.Numerics.Vectors.cs index 98b2db5e13365..13b94b02fd2cf 100644 --- a/src/libraries/System.Numerics.Vectors/ref/System.Numerics.Vectors.cs +++ b/src/libraries/System.Numerics.Vectors/ref/System.Numerics.Vectors.cs @@ -207,6 +207,16 @@ public static partial class Vector public static System.Numerics.Vector Add(System.Numerics.Vector left, System.Numerics.Vector right) { throw null; } public static System.Numerics.Vector AndNot(System.Numerics.Vector left, System.Numerics.Vector right) { throw null; } public static System.Numerics.Vector As(this System.Numerics.Vector vector) { throw null; } + public static System.Numerics.Plane AsPlane(this System.Numerics.Vector4 value) { throw null; } + public static System.Numerics.Quaternion AsQuaternion(this System.Numerics.Vector4 value) { throw null; } + public static System.Numerics.Vector2 AsVector2(this System.Numerics.Vector4 value) { throw null; } + public static System.Numerics.Vector3 AsVector3(this System.Numerics.Vector4 value) { throw null; } + public static System.Numerics.Vector4 AsVector4(this System.Numerics.Plane value) { throw null; } + public static System.Numerics.Vector4 AsVector4(this System.Numerics.Quaternion value) { throw null; } + public static System.Numerics.Vector4 AsVector4(this System.Numerics.Vector2 value) { throw null; } + public static System.Numerics.Vector4 AsVector4(this System.Numerics.Vector3 value) { throw null; } + public static System.Numerics.Vector4 AsVector4Unsafe(this System.Numerics.Vector2 value) { throw null; } + public static System.Numerics.Vector4 AsVector4Unsafe(this System.Numerics.Vector3 value) { throw null; } public static System.Numerics.Vector AsVectorByte(System.Numerics.Vector value) { throw null; } public static System.Numerics.Vector AsVectorDouble(System.Numerics.Vector value) { throw null; } public static System.Numerics.Vector AsVectorInt16(System.Numerics.Vector value) { throw null; } @@ -249,6 +259,8 @@ public static partial class Vector public static System.Numerics.Vector ConvertToUInt64(System.Numerics.Vector value) { throw null; } [System.CLSCompliantAttribute(false)] public static System.Numerics.Vector ConvertToUInt64Native(System.Numerics.Vector value) { throw null; } + public static System.Numerics.Vector Create(T value) { throw null; } + public static System.Numerics.Vector Create(System.ReadOnlySpan values) { throw null; } public static System.Numerics.Vector CreateSequence(T start, T step) { throw null; } public static System.Numerics.Vector Divide(System.Numerics.Vector left, System.Numerics.Vector right) { throw null; } public static System.Numerics.Vector Divide(System.Numerics.Vector left, T right) { throw null; } @@ -426,6 +438,9 @@ public partial struct Vector2 : System.IEquatable, Syst public static System.Numerics.Vector2 Abs(System.Numerics.Vector2 value) { throw null; } public static System.Numerics.Vector2 Add(System.Numerics.Vector2 left, System.Numerics.Vector2 right) { throw null; } public static System.Numerics.Vector2 Clamp(System.Numerics.Vector2 value1, System.Numerics.Vector2 min, System.Numerics.Vector2 max) { throw null; } + public static System.Numerics.Vector2 Create(float value) { throw null; } + public static System.Numerics.Vector2 Create(float x, float y) { throw null; } + public static System.Numerics.Vector2 Create(System.ReadOnlySpan values) { throw null; } public readonly void CopyTo(float[] array) { } public readonly void CopyTo(float[] array, int index) { } public readonly void CopyTo(System.Span destination) { } @@ -490,6 +505,10 @@ public partial struct Vector3 : System.IEquatable, Syst public static System.Numerics.Vector3 Abs(System.Numerics.Vector3 value) { throw null; } public static System.Numerics.Vector3 Add(System.Numerics.Vector3 left, System.Numerics.Vector3 right) { throw null; } public static System.Numerics.Vector3 Clamp(System.Numerics.Vector3 value1, System.Numerics.Vector3 min, System.Numerics.Vector3 max) { throw null; } + public static System.Numerics.Vector3 Create(float value) { throw null; } + public static System.Numerics.Vector3 Create(System.Numerics.Vector2 vector, float z) { throw null; } + public static System.Numerics.Vector3 Create(float x, float y, float z) { throw null; } + public static System.Numerics.Vector3 Create(System.ReadOnlySpan values) { throw null; } public readonly void CopyTo(float[] array) { } public readonly void CopyTo(float[] array, int index) { } public readonly void CopyTo(System.Span destination) { } @@ -556,6 +575,11 @@ public partial struct Vector4 : System.IEquatable, Syst public static System.Numerics.Vector4 Abs(System.Numerics.Vector4 value) { throw null; } public static System.Numerics.Vector4 Add(System.Numerics.Vector4 left, System.Numerics.Vector4 right) { throw null; } public static System.Numerics.Vector4 Clamp(System.Numerics.Vector4 value1, System.Numerics.Vector4 min, System.Numerics.Vector4 max) { throw null; } + public static System.Numerics.Vector4 Create(float value) { throw null; } + public static System.Numerics.Vector4 Create(System.Numerics.Vector2 vector, float z, float w) { throw null; } + public static System.Numerics.Vector4 Create(System.Numerics.Vector3 vector, float w) { throw null; } + public static System.Numerics.Vector4 Create(float x, float y, float z, float w) { throw null; } + public static System.Numerics.Vector4 Create(System.ReadOnlySpan values) { throw null; } public readonly void CopyTo(float[] array) { } public readonly void CopyTo(float[] array, int index) { } public readonly void CopyTo(System.Span destination) { } diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 4d68dc5729e26..77475d432d57a 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -599,6 +599,7 @@ + @@ -2794,4 +2795,4 @@ - \ No newline at end of file + diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Matrix3x2.Impl.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Matrix3x2.Impl.cs index 4fb270baa096b..7d270c17cc0de 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Matrix3x2.Impl.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Matrix3x2.Impl.cs @@ -32,7 +32,7 @@ internal struct Impl : IEquatable [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref Matrix3x2 AsM3x2() => ref Unsafe.As(ref this); - private const float RotationEpsilon = 0.001f * MathF.PI / 180f; // 0.1% of a degree + private const float RotationEpsilon = 0.001f * float.Pi / 180f; // 0.1% of a degree public Vector2 X; public Vector2 Y; @@ -43,9 +43,9 @@ public void Init(float m11, float m12, float m21, float m22, float m31, float m32) { - X = new Vector2(m11, m12); - Y = new Vector2(m21, m22); - Z = new Vector2(m31, m32); + X = Vector2.Create(m11, m12); + Y = Vector2.Create(m21, m22); + Z = Vector2.Create(m31, m32); } public static Impl Identity @@ -146,15 +146,15 @@ readonly get { Impl result; - result.X = new Vector2( + result.X = Vector2.Create( left.X.X * right.X.X + left.X.Y * right.Y.X, left.X.X * right.X.Y + left.X.Y * right.Y.Y ); - result.Y = new Vector2( + result.Y = Vector2.Create( left.Y.X * right.X.X + left.Y.Y * right.Y.X, left.Y.X * right.X.Y + left.Y.Y * right.Y.Y ); - result.Z = new Vector2( + result.Z = Vector2.Create( left.Z.X * right.X.X + left.Z.Y * right.Y.X + right.Z.X, left.Z.X * right.X.Y + left.Z.Y * right.Y.Y + right.Z.Y ); @@ -201,7 +201,7 @@ readonly get [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Impl CreateRotation(float radians) { - radians = MathF.IEEERemainder(radians, MathF.PI * 2); + radians = float.Ieee754Remainder(radians, float.Tau); float c; float s; @@ -212,19 +212,19 @@ public static Impl CreateRotation(float radians) c = 1; s = 0; } - else if (radians > MathF.PI / 2 - RotationEpsilon && radians < MathF.PI / 2 + RotationEpsilon) + else if (radians > float.Pi / 2 - RotationEpsilon && radians < float.Pi / 2 + RotationEpsilon) { // Exact case for 90 degree rotation. c = 0; s = 1; } - else if (radians < -MathF.PI + RotationEpsilon || radians > MathF.PI - RotationEpsilon) + else if (radians < -float.Pi + RotationEpsilon || radians > float.Pi - RotationEpsilon) { // Exact case for 180 degree rotation. c = -1; s = 0; } - else if (radians > -MathF.PI / 2 - RotationEpsilon && radians < -MathF.PI / 2 + RotationEpsilon) + else if (radians > -float.Pi / 2 - RotationEpsilon && radians < -float.Pi / 2 + RotationEpsilon) { // Exact case for 270 degree rotation. c = 0; @@ -233,8 +233,7 @@ public static Impl CreateRotation(float radians) else { // Arbitrary rotation. - c = MathF.Cos(radians); - s = MathF.Sin(radians); + (s, c) = float.SinCos(radians); } // [ c s ] @@ -243,8 +242,8 @@ public static Impl CreateRotation(float radians) Impl result; - result.X = new Vector2( c, s); - result.Y = new Vector2(-s, c); + result.X = Vector2.Create( c, s); + result.Y = Vector2.Create(-s, c); result.Z = Vector2.Zero; return result; @@ -253,7 +252,7 @@ public static Impl CreateRotation(float radians) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Impl CreateRotation(float radians, Vector2 centerPoint) { - radians = MathF.IEEERemainder(radians, MathF.PI * 2); + radians = float.Ieee754Remainder(radians, float.Tau); float c, s; @@ -263,19 +262,19 @@ public static Impl CreateRotation(float radians, Vector2 centerPoint) c = 1; s = 0; } - else if (radians > MathF.PI / 2 - RotationEpsilon && radians < MathF.PI / 2 + RotationEpsilon) + else if (radians > float.Pi / 2 - RotationEpsilon && radians < float.Pi / 2 + RotationEpsilon) { // Exact case for 90 degree rotation. c = 0; s = 1; } - else if (radians < -MathF.PI + RotationEpsilon || radians > MathF.PI - RotationEpsilon) + else if (radians < -float.Pi + RotationEpsilon || radians > float.Pi - RotationEpsilon) { // Exact case for 180 degree rotation. c = -1; s = 0; } - else if (radians > -MathF.PI / 2 - RotationEpsilon && radians < -MathF.PI / 2 + RotationEpsilon) + else if (radians > -float.Pi / 2 - RotationEpsilon && radians < -float.Pi / 2 + RotationEpsilon) { // Exact case for 270 degree rotation. c = 0; @@ -284,8 +283,7 @@ public static Impl CreateRotation(float radians, Vector2 centerPoint) else { // Arbitrary rotation. - c = MathF.Cos(radians); - s = MathF.Sin(radians); + (s, c) = float.SinCos(radians); } float x = centerPoint.X * (1 - c) + centerPoint.Y * s; @@ -297,9 +295,9 @@ public static Impl CreateRotation(float radians, Vector2 centerPoint) Impl result; - result.X = new Vector2( c, s); - result.Y = new Vector2(-s, c); - result.Z = new Vector2( x, y); + result.X = Vector2.Create( c, s); + result.Y = Vector2.Create(-s, c); + result.Z = Vector2.Create( x, y); return result; } @@ -309,8 +307,8 @@ public static Impl CreateScale(Vector2 scales) { Impl result; - result.X = new Vector2(scales.X, 0); - result.Y = new Vector2(0, scales.Y); + result.X = Vector2.CreateScalar(scales.X); + result.Y = Vector2.Create(0, scales.Y); result.Z = Vector2.Zero; return result; @@ -321,8 +319,8 @@ public static Impl CreateScale(float scaleX, float scaleY) { Impl result; - result.X = new Vector2(scaleX, 0); - result.Y = new Vector2(0, scaleY); + result.X = Vector2.CreateScalar(scaleX); + result.Y = Vector2.Create(0, scaleY); result.Z = Vector2.Zero; return result; @@ -333,9 +331,9 @@ public static Impl CreateScale(float scaleX, float scaleY, Vector2 centerPoint) { Impl result; - result.X = new Vector2(scaleX, 0); - result.Y = new Vector2(0, scaleY); - result.Z = centerPoint * (Vector2.One - new Vector2(scaleX, scaleY)); + result.X = Vector2.CreateScalar(scaleX); + result.Y = Vector2.Create(0, scaleY); + result.Z = centerPoint * (Vector2.One - Vector2.Create(scaleX, scaleY)); return result; } @@ -345,8 +343,8 @@ public static Impl CreateScale(Vector2 scales, Vector2 centerPoint) { Impl result; - result.X = new Vector2(scales.X, 0); - result.Y = new Vector2(0, scales.Y); + result.X = Vector2.CreateScalar(scales.X); + result.Y = Vector2.Create(0, scales.Y); result.Z = centerPoint * (Vector2.One - scales); return result; @@ -357,8 +355,8 @@ public static Impl CreateScale(float scale) { Impl result; - result.X = new Vector2(scale, 0); - result.Y = new Vector2(0, scale); + result.X = Vector2.CreateScalar(scale); + result.Y = Vector2.Create(0, scale); result.Z = Vector2.Zero; return result; @@ -369,9 +367,9 @@ public static Impl CreateScale(float scale, Vector2 centerPoint) { Impl result; - result.X = new Vector2(scale, 0); - result.Y = new Vector2(0, scale); - result.Z = centerPoint * (Vector2.One - new Vector2(scale)); + result.X = Vector2.CreateScalar(scale); + result.Y = Vector2.Create(0, scale); + result.Z = centerPoint * (Vector2.One - Vector2.Create(scale)); return result; } @@ -381,8 +379,8 @@ public static Impl CreateSkew(float radiansX, float radiansY) { Impl result; - result.X = new Vector2(1, MathF.Tan(radiansY)); - result.Y = new Vector2(MathF.Tan(radiansX), 1); + result.X = Vector2.Create(1, float.Tan(radiansY)); + result.Y = Vector2.Create(float.Tan(radiansX), 1); result.Z = Vector2.Zero; return result; @@ -391,17 +389,17 @@ public static Impl CreateSkew(float radiansX, float radiansY) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Impl CreateSkew(float radiansX, float radiansY, Vector2 centerPoint) { - float xTan = MathF.Tan(radiansX); - float yTan = MathF.Tan(radiansY); + float xTan = float.Tan(radiansX); + float yTan = float.Tan(radiansY); float tx = -centerPoint.Y * xTan; float ty = -centerPoint.X * yTan; Impl result; - result.X = new Vector2(1, yTan); - result.Y = new Vector2(xTan, 1); - result.Z = new Vector2(tx, ty); + result.X = Vector2.Create(1, yTan); + result.Y = Vector2.Create(xTan, 1); + result.Z = Vector2.Create(tx, ty); return result; } @@ -425,7 +423,7 @@ public static Impl CreateTranslation(float positionX, float positionY) result.X = Vector2.UnitX; result.Y = Vector2.UnitY; - result.Z = new Vector2(positionX, positionY); + result.Z = Vector2.Create(positionX, positionY); return result; } @@ -435,9 +433,9 @@ public static bool Invert(in Impl matrix, out Impl result) { float det = (matrix.X.X * matrix.Y.Y) - (matrix.Y.X * matrix.X.Y); - if (MathF.Abs(det) < float.Epsilon) + if (float.Abs(det) < float.Epsilon) { - Vector2 vNaN = new Vector2(float.NaN); + Vector2 vNaN = Vector2.Create(float.NaN); result.X = vNaN; result.Y = vNaN; @@ -448,15 +446,15 @@ public static bool Invert(in Impl matrix, out Impl result) float invDet = 1.0f / det; - result.X = new Vector2( + result.X = Vector2.Create( +matrix.Y.Y * invDet, -matrix.X.Y * invDet ); - result.Y = new Vector2( + result.Y = Vector2.Create( -matrix.Y.X * invDet, +matrix.X.X * invDet ); - result.Z = new Vector2( + result.Z = Vector2.Create( (matrix.Y.X * matrix.Z.Y - matrix.Z.X * matrix.Y.Y) * invDet, (matrix.Z.X * matrix.X.Y - matrix.X.X * matrix.Z.Y) * invDet ); diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Matrix4x4.Impl.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Matrix4x4.Impl.cs index 492eda1b452d0..79e96fd05dd5d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Matrix4x4.Impl.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Matrix4x4.Impl.cs @@ -38,7 +38,7 @@ internal struct Impl : IEquatable public ref Matrix4x4 AsM4x4() => ref Unsafe.As(ref this); private const float BillboardEpsilon = 1e-4f; - private const float BillboardMinAngle = 1.0f - (0.1f * (MathF.PI / 180.0f)); // 0.1 degrees + private const float BillboardMinAngle = 1.0f - (0.1f * (float.Pi / 180.0f)); // 0.1 degrees private const float DecomposeEpsilon = 0.0001f; public Vector4 X; @@ -52,19 +52,19 @@ public void Init(float m11, float m12, float m13, float m14, float m31, float m32, float m33, float m34, float m41, float m42, float m43, float m44) { - X = new Vector4(m11, m12, m13, m14); - Y = new Vector4(m21, m22, m23, m24); - Z = new Vector4(m31, m32, m33, m34); - W = new Vector4(m41, m42, m43, m44); + X = Vector4.Create(m11, m12, m13, m14); + Y = Vector4.Create(m21, m22, m23, m24); + Z = Vector4.Create(m31, m32, m33, m34); + W = Vector4.Create(m41, m42, m43, m44); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Init(in Matrix3x2.Impl value) { - X = new Vector4(value.X, 0, 0); - Y = new Vector4(value.Y, 0, 0); + X = Vector4.Create(value.X, 0, 0); + Y = Vector4.Create(value.Y, 0, 0); Z = Vector4.UnitZ; - W = new Vector4(value.Z, 0, 1); + W = Vector4.Create(value.Z, 0, 1); } public static Impl Identity @@ -122,12 +122,12 @@ public readonly bool IsIdentity public Vector3 Translation { [MethodImpl(MethodImplOptions.AggressiveInlining)] - readonly get => new Vector3(W.X, W.Y, W.Z); + readonly get => W.AsVector3(); [MethodImpl(MethodImplOptions.AggressiveInlining)] set { - W = new Vector4(value, W.W); + W = Vector4.Create(value, W.W); } } @@ -245,7 +245,7 @@ public static Impl CreateBillboard(in Vector3 objectPosition, in Vector3 cameraP } else { - axisZ = Vector3.Multiply(axisZ, 1.0f / MathF.Sqrt(norm)); + axisZ = Vector3.Multiply(axisZ, 1.0f / float.Sqrt(norm)); } Vector3 axisX = Vector3.Normalize(Vector3.Cross(cameraUpVector, axisZ)); @@ -253,10 +253,10 @@ public static Impl CreateBillboard(in Vector3 objectPosition, in Vector3 cameraP Impl result; - result.X = new Vector4(axisX, 0); - result.Y = new Vector4(axisY, 0); - result.Z = new Vector4(axisZ, 0); - result.W = new Vector4(objectPosition, 1); + result.X = axisX.AsVector4(); + result.Y = axisY.AsVector4(); + result.Z = axisZ.AsVector4();; + result.W = Vector4.Create(objectPosition, 1); return result; } @@ -274,7 +274,7 @@ public static Impl CreateConstrainedBillboard(in Vector3 objectPosition, in Vect } else { - faceDir = Vector3.Multiply(faceDir, (1.0f / MathF.Sqrt(norm))); + faceDir = Vector3.Multiply(faceDir, (1.0f / float.Sqrt(norm))); } Vector3 axisY = rotateAxis; @@ -282,16 +282,16 @@ public static Impl CreateConstrainedBillboard(in Vector3 objectPosition, in Vect // Treat the case when angle between faceDir and rotateAxis is too close to 0. float dot = Vector3.Dot(axisY, faceDir); - if (MathF.Abs(dot) > BillboardMinAngle) + if (float.Abs(dot) > BillboardMinAngle) { faceDir = objectForwardVector; // Make sure passed values are useful for compute. dot = Vector3.Dot(axisY, faceDir); - if (MathF.Abs(dot) > BillboardMinAngle) + if (float.Abs(dot) > BillboardMinAngle) { - faceDir = (MathF.Abs(axisY.Z) > BillboardMinAngle) ? Vector3.UnitX : new Vector3(0, 0, -1); + faceDir = (float.Abs(axisY.Z) > BillboardMinAngle) ? Vector3.UnitX : Vector3.Create(0, 0, -1); } } @@ -300,10 +300,10 @@ public static Impl CreateConstrainedBillboard(in Vector3 objectPosition, in Vect Impl result; - result.X = new Vector4(axisX, 0); - result.Y = new Vector4(axisY, 0); - result.Z = new Vector4(axisZ, 0); - result.W = new Vector4(objectPosition, 1); + result.X = axisX.AsVector4(); + result.Y = axisY.AsVector4(); + result.Z = axisZ.AsVector4(); + result.W = Vector4.Create(objectPosition, 1); return result; } @@ -341,8 +341,7 @@ public static Impl CreateFromAxisAngle(in Vector3 axis, float angle) float y = axis.Y; float z = axis.Z; - float sa = MathF.Sin(angle); - float ca = MathF.Cos(angle); + (float sa, float ca) = float.SinCos(angle); float xx = x * x; float yy = y * y; @@ -354,19 +353,19 @@ public static Impl CreateFromAxisAngle(in Vector3 axis, float angle) Impl result; - result.X = new Vector4( + result.X = Vector4.Create( xx + ca * (1.0f - xx), xy - ca * xy + sa * z, xz - ca * xz - sa * y, 0 ); - result.Y = new Vector4( + result.Y = Vector4.Create( xy - ca * xy - sa * z, yy + ca * (1.0f - yy), yz - ca * yz + sa * x, 0 ); - result.Z = new Vector4( + result.Z = Vector4.Create( xz - ca * xz + sa * y, yz - ca * yz - sa * x, zz + ca * (1.0f - zz), @@ -393,19 +392,19 @@ public static Impl CreateFromQuaternion(in Quaternion quaternion) Impl result; - result.X = new Vector4( + result.X = Vector4.Create( 1.0f - 2.0f * (yy + zz), 2.0f * (xy + wz), 2.0f * (xz - wy), 0 ); - result.Y = new Vector4( + result.Y = Vector4.Create( 2.0f * (xy - wz), 1.0f - 2.0f * (zz + xx), 2.0f * (yz + wx), 0 ); - result.Z = new Vector4( + result.Z = Vector4.Create( 2.0f * (xz + wy), 2.0f * (yz - wx), 1.0f - 2.0f * (yy + xx), @@ -433,25 +432,25 @@ public static Impl CreateLookTo(in Vector3 cameraPosition, in Vector3 cameraDire Impl result; - result.X = new Vector4( + result.X = Vector4.Create( axisX.X, axisY.X, axisZ.X, 0 ); - result.Y = new Vector4( + result.Y = Vector4.Create( axisX.Y, axisY.Y, axisZ.Y, 0 ); - result.Z = new Vector4( + result.Z = Vector4.Create( axisX.Z, axisY.Z, axisZ.Z, 0 ); - result.W = new Vector4( + result.W = Vector4.Create( Vector3.Dot(axisX, negativeCameraPosition), Vector3.Dot(axisY, negativeCameraPosition), Vector3.Dot(axisZ, negativeCameraPosition), @@ -471,25 +470,25 @@ public static Impl CreateLookToLeftHanded(in Vector3 cameraPosition, in Vector3 Impl result; - result.X = new Vector4( + result.X = Vector4.Create( axisX.X, axisY.X, axisZ.X, 0 ); - result.Y = new Vector4( + result.Y = Vector4.Create( axisX.Y, axisY.Y, axisZ.Y, 0 ); - result.Z = new Vector4( + result.Z = Vector4.Create( axisX.Z, axisY.Z, axisZ.Z, 0 ); - result.W = new Vector4( + result.W = Vector4.Create( Vector3.Dot(axisX, negativeCameraPosition), Vector3.Dot(axisY, negativeCameraPosition), Vector3.Dot(axisZ, negativeCameraPosition), @@ -506,10 +505,10 @@ public static Impl CreateOrthographic(float width, float height, float zNearPlan Impl result; - result.X = new Vector4(2.0f / width, 0, 0, 0); - result.Y = new Vector4(0, 2.0f / height, 0, 0); - result.Z = new Vector4(0, 0, range, 0); - result.W = new Vector4(0, 0, range * zNearPlane, 1); + result.X = Vector4.Create(2.0f / width, 0, 0, 0); + result.Y = Vector4.Create(0, 2.0f / height, 0, 0); + result.Z = Vector4.Create(0, 0, range, 0); + result.W = Vector4.Create(0, 0, range * zNearPlane, 1); return result; } @@ -521,10 +520,10 @@ public static Impl CreateOrthographicLeftHanded(float width, float height, float Impl result; - result.X = new Vector4(2.0f / width, 0, 0, 0); - result.Y = new Vector4(0, 2.0f / height, 0, 0); - result.Z = new Vector4(0, 0, range, 0); - result.W = new Vector4(0, 0, -range * zNearPlane, 1); + result.X = Vector4.Create(2.0f / width, 0, 0, 0); + result.Y = Vector4.Create(0, 2.0f / height, 0, 0); + result.Z = Vector4.Create(0, 0, range, 0); + result.W = Vector4.Create(0, 0, -range * zNearPlane, 1); return result; } @@ -538,10 +537,10 @@ public static Impl CreateOrthographicOffCenter(float left, float right, float bo Impl result; - result.X = new Vector4(reciprocalWidth + reciprocalWidth, 0, 0, 0); - result.Y = new Vector4(0, reciprocalHeight + reciprocalHeight, 0, 0); - result.Z = new Vector4(0, 0, range, 0); - result.W = new Vector4( + result.X = Vector4.Create(reciprocalWidth + reciprocalWidth, 0, 0, 0); + result.Y = Vector4.Create(0, reciprocalHeight + reciprocalHeight, 0, 0); + result.Z = Vector4.Create(0, 0, range, 0); + result.W = Vector4.Create( -(left + right) * reciprocalWidth, -(top + bottom) * reciprocalHeight, range * zNearPlane, @@ -560,10 +559,10 @@ public static Impl CreateOrthographicOffCenterLeftHanded(float left, float right Impl result; - result.X = new Vector4(reciprocalWidth + reciprocalWidth, 0, 0, 0); - result.Y = new Vector4(0, reciprocalHeight + reciprocalHeight, 0, 0); - result.Z = new Vector4(0, 0, range, 0); - result.W = new Vector4( + result.X = Vector4.Create(reciprocalWidth + reciprocalWidth, 0, 0, 0); + result.Y = Vector4.Create(0, reciprocalHeight + reciprocalHeight, 0, 0); + result.Z = Vector4.Create(0, 0, range, 0); + result.W = Vector4.Create( -(left + right) * reciprocalWidth, -(top + bottom) * reciprocalHeight, -range * zNearPlane, @@ -585,10 +584,10 @@ public static Impl CreatePerspective(float width, float height, float nearPlaneD Impl result; - result.X = new Vector4(dblNearPlaneDistance / width, 0, 0, 0); - result.Y = new Vector4(0, dblNearPlaneDistance / height, 0, 0); - result.Z = new Vector4(0, 0, range, -1.0f); - result.W = new Vector4(0, 0, range * nearPlaneDistance, 0); + result.X = Vector4.Create(dblNearPlaneDistance / width, 0, 0, 0); + result.Y = Vector4.Create(0, dblNearPlaneDistance / height, 0, 0); + result.Z = Vector4.Create(0, 0, range, -1.0f); + result.W = Vector4.Create(0, 0, range * nearPlaneDistance, 0); return result; } @@ -605,10 +604,10 @@ public static Impl CreatePerspectiveLeftHanded(float width, float height, float Impl result; - result.X = new Vector4(dblNearPlaneDistance / width, 0, 0, 0); - result.Y = new Vector4(0, dblNearPlaneDistance / height, 0, 0); - result.Z = new Vector4(0, 0, range, 1.0f); - result.W = new Vector4(0, 0, -range * nearPlaneDistance, 0); + result.X = Vector4.Create(dblNearPlaneDistance / width, 0, 0, 0); + result.Y = Vector4.Create(0, dblNearPlaneDistance / height, 0, 0); + result.Z = Vector4.Create(0, 0, range, 1.0f); + result.W = Vector4.Create(0, 0, -range * nearPlaneDistance, 0); return result; } @@ -623,16 +622,16 @@ public static Impl CreatePerspectiveFieldOfView(float fieldOfView, float aspectR ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(farPlaneDistance, 0.0f); ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(nearPlaneDistance, farPlaneDistance); - float height = 1.0f / MathF.Tan(fieldOfView * 0.5f); + float height = 1.0f / float.Tan(fieldOfView * 0.5f); float width = height / aspectRatio; float range = float.IsPositiveInfinity(farPlaneDistance) ? -1.0f : farPlaneDistance / (nearPlaneDistance - farPlaneDistance); Impl result; - result.X = new Vector4(width, 0, 0, 0); - result.Y = new Vector4(0, height, 0, 0); - result.Z = new Vector4(0, 0, range, -1.0f); - result.W = new Vector4(0, 0, range * nearPlaneDistance, 0); + result.X = Vector4.Create(width, 0, 0, 0); + result.Y = Vector4.Create(0, height, 0, 0); + result.Z = Vector4.Create(0, 0, range, -1.0f); + result.W = Vector4.Create(0, 0, range * nearPlaneDistance, 0); return result; } @@ -647,16 +646,16 @@ public static Impl CreatePerspectiveFieldOfViewLeftHanded(float fieldOfView, flo ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(farPlaneDistance, 0.0f); ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(nearPlaneDistance, farPlaneDistance); - float height = 1.0f / MathF.Tan(fieldOfView * 0.5f); + float height = 1.0f / float.Tan(fieldOfView * 0.5f); float width = height / aspectRatio; float range = float.IsPositiveInfinity(farPlaneDistance) ? 1.0f : farPlaneDistance / (farPlaneDistance - nearPlaneDistance); Impl result; - result.X = new Vector4(width, 0, 0, 0); - result.Y = new Vector4(0, height, 0, 0); - result.Z = new Vector4(0, 0, range, 1.0f); - result.W = new Vector4(0, 0, -range * nearPlaneDistance, 0); + result.X = Vector4.Create(width, 0, 0, 0); + result.Y = Vector4.Create(0, height, 0, 0); + result.Z = Vector4.Create(0, 0, range, 1.0f); + result.W = Vector4.Create(0, 0, -range * nearPlaneDistance, 0); return result; } @@ -675,15 +674,15 @@ public static Impl CreatePerspectiveOffCenter(float left, float right, float bot Impl result; - result.X = new Vector4(dblNearPlaneDistance * reciprocalWidth, 0, 0, 0); - result.Y = new Vector4(0, dblNearPlaneDistance * reciprocalHeight, 0, 0); - result.Z = new Vector4( + result.X = Vector4.Create(dblNearPlaneDistance * reciprocalWidth, 0, 0, 0); + result.Y = Vector4.Create(0, dblNearPlaneDistance * reciprocalHeight, 0, 0); + result.Z = Vector4.Create( (left + right) * reciprocalWidth, (top + bottom) * reciprocalHeight, range, -1.0f ); - result.W = new Vector4(0, 0, range * nearPlaneDistance, 0); + result.W = Vector4.Create(0, 0, range * nearPlaneDistance, 0); return result; } @@ -702,15 +701,15 @@ public static Impl CreatePerspectiveOffCenterLeftHanded(float left, float right, Impl result; - result.X = new Vector4(dblNearPlaneDistance * reciprocalWidth, 0, 0, 0); - result.Y = new Vector4(0, dblNearPlaneDistance * reciprocalHeight, 0, 0); - result.Z = new Vector4( + result.X = Vector4.Create(dblNearPlaneDistance * reciprocalWidth, 0, 0, 0); + result.Y = Vector4.Create(0, dblNearPlaneDistance * reciprocalHeight, 0, 0); + result.Z = Vector4.Create( -(left + right) * reciprocalWidth, -(top + bottom) * reciprocalHeight, range, 1.0f ); - result.W = new Vector4(0, 0, -range * nearPlaneDistance, 0); + result.W = Vector4.Create(0, 0, -range * nearPlaneDistance, 0); return result; } @@ -723,10 +722,10 @@ public static Impl CreateReflection(in Plane value) Impl result; - result.X = new Vector4(f * p.Normal.X, 0) + Vector4.UnitX; - result.Y = new Vector4(f * p.Normal.Y, 0) + Vector4.UnitY; - result.Z = new Vector4(f * p.Normal.Z, 0) + Vector4.UnitZ; - result.W = new Vector4(f * p.D, 1); + result.X = (f * p.Normal.X).AsVector4() + Vector4.UnitX; + result.Y = (f * p.Normal.Y).AsVector4() + Vector4.UnitY; + result.Z = (f * p.Normal.Z).AsVector4() + Vector4.UnitZ; + result.W = Vector4.Create(f * p.D, 1); return result; } @@ -734,8 +733,7 @@ public static Impl CreateReflection(in Plane value) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Impl CreateRotationX(float radians) { - float c = MathF.Cos(radians); - float s = MathF.Sin(radians); + (float s, float c) = float.SinCos(radians); // [ 1 0 0 0 ] // [ 0 c s 0 ] @@ -745,8 +743,8 @@ public static Impl CreateRotationX(float radians) Impl result; result.X = Vector4.UnitX; - result.Y = new Vector4(0, c, s, 0); - result.Z = new Vector4(0, -s, c, 0); + result.Y = Vector4.Create(0, c, s, 0); + result.Z = Vector4.Create(0, -s, c, 0); result.W = Vector4.UnitW; return result; @@ -755,8 +753,7 @@ public static Impl CreateRotationX(float radians) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Impl CreateRotationX(float radians, in Vector3 centerPoint) { - float c = MathF.Cos(radians); - float s = MathF.Sin(radians); + (float s, float c) = float.SinCos(radians); float y = centerPoint.Y * (1 - c) + centerPoint.Z * s; float z = centerPoint.Z * (1 - c) - centerPoint.Y * s; @@ -769,9 +766,9 @@ public static Impl CreateRotationX(float radians, in Vector3 centerPoint) Impl result; result.X = Vector4.UnitX; - result.Y = new Vector4(0, c, s, 0); - result.Z = new Vector4(0, -s, c, 0); - result.W = new Vector4(0, y, z, 1); + result.Y = Vector4.Create(0, c, s, 0); + result.Z = Vector4.Create(0, -s, c, 0); + result.W = Vector4.Create(0, y, z, 1); return result; } @@ -779,8 +776,7 @@ public static Impl CreateRotationX(float radians, in Vector3 centerPoint) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Impl CreateRotationY(float radians) { - float c = MathF.Cos(radians); - float s = MathF.Sin(radians); + (float s, float c) = float.SinCos(radians); // [ c 0 -s 0 ] // [ 0 1 0 0 ] @@ -789,9 +785,9 @@ public static Impl CreateRotationY(float radians) Impl result; - result.X = new Vector4(c, 0, -s, 0); + result.X = Vector4.Create(c, 0, -s, 0); result.Y = Vector4.UnitY; - result.Z = new Vector4(s, 0, c, 0); + result.Z = Vector4.Create(s, 0, c, 0); result.W = Vector4.UnitW; return result; @@ -800,8 +796,7 @@ public static Impl CreateRotationY(float radians) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Impl CreateRotationY(float radians, in Vector3 centerPoint) { - float c = MathF.Cos(radians); - float s = MathF.Sin(radians); + (float s, float c) = float.SinCos(radians); float x = centerPoint.X * (1 - c) - centerPoint.Z * s; float z = centerPoint.Z * (1 - c) + centerPoint.X * s; @@ -813,10 +808,10 @@ public static Impl CreateRotationY(float radians, in Vector3 centerPoint) Impl result; - result.X = new Vector4(c, 0, -s, 0); + result.X = Vector4.Create(c, 0, -s, 0); result.Y = Vector4.UnitY; - result.Z = new Vector4(s, 0, c, 0); - result.W = new Vector4(x, 0, z, 1); + result.Z = Vector4.Create(s, 0, c, 0); + result.W = Vector4.Create(x, 0, z, 1); return result; } @@ -824,9 +819,7 @@ public static Impl CreateRotationY(float radians, in Vector3 centerPoint) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Impl CreateRotationZ(float radians) { - - float c = MathF.Cos(radians); - float s = MathF.Sin(radians); + (float s, float c) = float.SinCos(radians); // [ c s 0 0 ] // [ -s c 0 0 ] @@ -835,8 +828,8 @@ public static Impl CreateRotationZ(float radians) Impl result; - result.X = new Vector4( c, s, 0, 0); - result.Y = new Vector4(-s, c, 0, 0); + result.X = Vector4.Create( c, s, 0, 0); + result.Y = Vector4.Create(-s, c, 0, 0); result.Z = Vector4.UnitZ; result.W = Vector4.UnitW; @@ -846,8 +839,7 @@ public static Impl CreateRotationZ(float radians) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Impl CreateRotationZ(float radians, in Vector3 centerPoint) { - float c = MathF.Cos(radians); - float s = MathF.Sin(radians); + (float s, float c) = float.SinCos(radians); float x = centerPoint.X * (1 - c) + centerPoint.Y * s; float y = centerPoint.Y * (1 - c) - centerPoint.X * s; @@ -859,10 +851,10 @@ public static Impl CreateRotationZ(float radians, in Vector3 centerPoint) Impl result; - result.X = new Vector4( c, s, 0, 0); - result.Y = new Vector4(-s, c, 0, 0); + result.X = Vector4.Create( c, s, 0, 0); + result.Y = Vector4.Create(-s, c, 0, 0); result.Z = Vector4.UnitZ; - result.W = new Vector4(x, y, 0, 1); + result.W = Vector4.Create(x, y, 0, 1); return result; } @@ -872,9 +864,9 @@ public static Impl CreateScale(float scaleX, float scaleY, float scaleZ) { Impl result; - result.X = new Vector4(scaleX, 0, 0, 0); - result.Y = new Vector4(0, scaleY, 0, 0); - result.Z = new Vector4(0, 0, scaleZ, 0); + result.X = Vector4.Create(scaleX, 0, 0, 0); + result.Y = Vector4.Create(0, scaleY, 0, 0); + result.Z = Vector4.Create(0, 0, scaleZ, 0); result.W = Vector4.UnitW; return result; @@ -885,10 +877,10 @@ public static Impl CreateScale(float scaleX, float scaleY, float scaleZ, in Vect { Impl result; - result.X = new Vector4(scaleX, 0, 0, 0); - result.Y = new Vector4(0, scaleY, 0, 0); - result.Z = new Vector4(0, 0, scaleZ, 0); - result.W = new Vector4(centerPoint * (Vector3.One - new Vector3(scaleX, scaleY, scaleZ)), 1); + result.X = Vector4.Create(scaleX, 0, 0, 0); + result.Y = Vector4.Create(0, scaleY, 0, 0); + result.Z = Vector4.Create(0, 0, scaleZ, 0); + result.W = Vector4.Create(centerPoint * (Vector3.One - Vector3.Create(scaleX, scaleY, scaleZ)), 1); return result; } @@ -898,9 +890,9 @@ public static Impl CreateScale(in Vector3 scales) { Impl result; - result.X = new Vector4(scales.X, 0, 0, 0); - result.Y = new Vector4(0, scales.Y, 0, 0); - result.Z = new Vector4(0, 0, scales.Z, 0); + result.X = Vector4.Create(scales.X, 0, 0, 0); + result.Y = Vector4.Create(0, scales.Y, 0, 0); + result.Z = Vector4.Create(0, 0, scales.Z, 0); result.W = Vector4.UnitW; return result; @@ -911,10 +903,10 @@ public static Impl CreateScale(in Vector3 scales, in Vector3 centerPoint) { Impl result; - result.X = new Vector4(scales.X, 0, 0, 0); - result.Y = new Vector4(0, scales.Y, 0, 0); - result.Z = new Vector4(0, 0, scales.Z, 0); - result.W = new Vector4(centerPoint * (Vector3.One - scales), 1); + result.X = Vector4.Create(scales.X, 0, 0, 0); + result.Y = Vector4.Create(0, scales.Y, 0, 0); + result.Z = Vector4.Create(0, 0, scales.Z, 0); + result.W = Vector4.Create(centerPoint * (Vector3.One - scales), 1); return result; } @@ -924,9 +916,9 @@ public static Impl CreateScale(float scale) { Impl result; - result.X = new Vector4(scale, 0, 0, 0); - result.Y = new Vector4(0, scale, 0, 0); - result.Z = new Vector4(0, 0, scale, 0); + result.X = Vector4.Create(scale, 0, 0, 0); + result.Y = Vector4.Create(0, scale, 0, 0); + result.Z = Vector4.Create(0, 0, scale, 0); result.W = Vector4.UnitW; return result; @@ -937,10 +929,10 @@ public static Impl CreateScale(float scale, in Vector3 centerPoint) { Impl result; - result.X = new Vector4(scale, 0, 0, 0); - result.Y = new Vector4(0, scale, 0, 0); - result.Z = new Vector4(0, 0, scale, 0); - result.W = new Vector4(centerPoint * (Vector3.One - new Vector3(scale)), 1); + result.X = Vector4.Create(scale, 0, 0, 0); + result.Y = Vector4.Create(0, scale, 0, 0); + result.Z = Vector4.Create(0, 0, scale, 0); + result.W = Vector4.Create(centerPoint * (Vector3.One - Vector3.Create(scale)), 1); return result; } @@ -955,10 +947,10 @@ public static Impl CreateShadow(in Vector3 lightDirection, in Plane plane) Impl result; - result.X = new Vector4(lightDirection * normal.X, 0) + new Vector4(dot, 0, 0, 0); - result.Y = new Vector4(lightDirection * normal.Y, 0) + new Vector4(0, dot, 0, 0); - result.Z = new Vector4(lightDirection * normal.Z, 0) + new Vector4(0, 0, dot, 0); - result.W = new Vector4(lightDirection * -p.D, dot); + result.X = (lightDirection * normal.X).AsVector4() + Vector4.CreateScalar(dot); + result.Y = (lightDirection * normal.Y).AsVector4() + Vector4.Create(0, dot, 0, 0); + result.Z = (lightDirection * normal.Z).AsVector4() + Vector4.Create(0, 0, dot, 0); + result.W = Vector4.Create(lightDirection * -p.D, dot); return result; } @@ -971,7 +963,7 @@ public static Impl CreateTranslation(in Vector3 position) result.X = Vector4.UnitX; result.Y = Vector4.UnitY; result.Z = Vector4.UnitZ; - result.W = new Vector4(position, 1); + result.W = Vector4.Create(position, 1); return result; } @@ -984,7 +976,7 @@ public static Impl CreateTranslation(float positionX, float positionY, float pos result.X = Vector4.UnitX; result.Y = Vector4.UnitY; result.Z = Vector4.UnitZ; - result.W = new Vector4(positionX, positionY, positionZ, 1); + result.W = Vector4.Create(positionX, positionY, positionZ, 1); return result; } @@ -996,13 +988,13 @@ public static Impl CreateViewport(float x, float y, float width, float height, f Impl result; // 4x SIMD fields to get a lot better codegen - result.W = new Vector4(width, height, 0f, 0f); - result.W *= new Vector4(0.5f, 0.5f, 0f, 0f); + result.W = Vector4.Create(width, height, 0f, 0f); + result.W *= Vector4.Create(0.5f, 0.5f, 0f, 0f); - result.X = new Vector4(result.W.X, 0f, 0f, 0f); - result.Y = new Vector4(0f, -result.W.Y, 0f, 0f); - result.Z = new Vector4(0f, 0f, minDepth - maxDepth, 0f); - result.W += new Vector4(x, y, minDepth, 1f); + result.X = Vector4.Create(result.W.X, 0f, 0f, 0f); + result.Y = Vector4.Create(0f, -result.W.Y, 0f, 0f); + result.Z = Vector4.Create(0f, 0f, minDepth - maxDepth, 0f); + result.W += Vector4.Create(x, y, minDepth, 1f); return result; } @@ -1013,13 +1005,13 @@ public static Impl CreateViewportLeftHanded(float x, float y, float width, float Impl result; // 4x SIMD fields to get a lot better codegen - result.W = new Vector4(width, height, 0f, 0f); - result.W *= new Vector4(0.5f, 0.5f, 0f, 0f); + result.W = Vector4.Create(width, height, 0f, 0f); + result.W *= Vector4.Create(0.5f, 0.5f, 0f, 0f); - result.X = new Vector4(result.W.X, 0f, 0f, 0f); - result.Y = new Vector4(0f, -result.W.Y, 0f, 0f); - result.Z = new Vector4(0f, 0f, maxDepth - minDepth, 0f); - result.W += new Vector4(x, y, minDepth, 1f); + result.X = Vector4.Create(result.W.X, 0f, 0f, 0f); + result.Y = Vector4.Create(0f, -result.W.Y, 0f, 0f); + result.Z = Vector4.Create(0f, 0f, maxDepth - minDepth, 0f); + result.W += Vector4.Create(x, y, minDepth, 1f); return result; } @@ -1033,10 +1025,10 @@ public static Impl CreateWorld(in Vector3 position, in Vector3 forward, in Vecto Impl result; - result.X = new Vector4(axisX, 0); - result.Y = new Vector4(axisY, 0); - result.Z = new Vector4(axisZ, 0); - result.W = new Vector4(position, 1); + result.X = axisX.AsVector4(); + result.Y = axisY.AsVector4(); + result.Z = axisZ.AsVector4(); + result.W = Vector4.Create(position, 1); return result; } @@ -1058,22 +1050,19 @@ public static unsafe bool Decompose(in Impl matrix, out Vector3 scale, out Quate CanonicalBasis canonicalBasis = default; Vector3* pCanonicalBasis = &canonicalBasis.Row0; - canonicalBasis.Row0 = new Vector3(1.0f, 0.0f, 0.0f); - canonicalBasis.Row1 = new Vector3(0.0f, 1.0f, 0.0f); - canonicalBasis.Row2 = new Vector3(0.0f, 0.0f, 1.0f); + canonicalBasis.Row0 = Vector3.UnitX; + canonicalBasis.Row1 = Vector3.UnitY; + canonicalBasis.Row2 = Vector3.UnitZ; - translation = new Vector3( - matrix.W.X, - matrix.W.Y, - matrix.W.Z); + translation = matrix.W.AsVector3(); pVectorBasis[0] = (Vector3*)&matTemp.X; pVectorBasis[1] = (Vector3*)&matTemp.Y; pVectorBasis[2] = (Vector3*)&matTemp.Z; - *(pVectorBasis[0]) = new Vector3(matrix.X.X, matrix.X.Y, matrix.X.Z); - *(pVectorBasis[1]) = new Vector3(matrix.Y.X, matrix.Y.Y, matrix.Y.Z); - *(pVectorBasis[2]) = new Vector3(matrix.Z.X, matrix.Z.Y, matrix.Z.Z); + *(pVectorBasis[0]) = matrix.X.AsVector3(); + *(pVectorBasis[1]) = matrix.Y.AsVector3(); + *(pVectorBasis[2]) = matrix.Z.AsVector3(); scale.X = pVectorBasis[0]->Length(); scale.Y = pVectorBasis[1]->Length(); @@ -1148,9 +1137,9 @@ public static unsafe bool Decompose(in Impl matrix, out Vector3 scale, out Quate uint cc; float fAbsX, fAbsY, fAbsZ; - fAbsX = MathF.Abs(pVectorBasis[a]->X); - fAbsY = MathF.Abs(pVectorBasis[a]->Y); - fAbsZ = MathF.Abs(pVectorBasis[a]->Z); + fAbsX = float.Abs(pVectorBasis[a]->X); + fAbsY = float.Abs(pVectorBasis[a]->Y); + fAbsZ = float.Abs(pVectorBasis[a]->Z); #region Ranking if (fAbsX < fAbsY) @@ -1387,9 +1376,9 @@ static bool SseImpl(in Impl matrix, out Impl result) float det = Vector4.Dot(C0.AsVector4(), row1.AsVector4()); // Check determinate is not zero - if (MathF.Abs(det) < float.Epsilon) + if (float.Abs(det) < float.Epsilon) { - Vector4 vNaN = new Vector4(float.NaN); + Vector4 vNaN = Vector4.Create(float.NaN); result.X = vNaN; result.Y = vNaN; @@ -1546,9 +1535,9 @@ static bool SoftwareFallback(in Impl matrix, out Impl result) float det = a * a11 + b * a12 + c * a13 + d * a14; - if (MathF.Abs(det) < float.Epsilon) + if (float.Abs(det) < float.Epsilon) { - Vector4 vNaN = new Vector4(float.NaN); + Vector4 vNaN = Vector4.Create(float.NaN); result.X = vNaN; result.Y = vNaN; @@ -1645,25 +1634,25 @@ public static Impl Transform(in Impl value, in Quaternion rotation) Impl result; - result.X = new Vector4( + result.X = Vector4.Create( value.X.X * q11 + value.X.Y * q21 + value.X.Z * q31, value.X.X * q12 + value.X.Y * q22 + value.X.Z * q32, value.X.X * q13 + value.X.Y * q23 + value.X.Z * q33, value.X.W ); - result.Y = new Vector4( + result.Y = Vector4.Create( value.Y.X * q11 + value.Y.Y * q21 + value.Y.Z * q31, value.Y.X * q12 + value.Y.Y * q22 + value.Y.Z * q32, value.Y.X * q13 + value.Y.Y * q23 + value.Y.Z * q33, value.Y.W ); - result.Z = new Vector4( + result.Z = Vector4.Create( value.Z.X * q11 + value.Z.Y * q21 + value.Z.Z * q31, value.Z.X * q12 + value.Z.Y * q22 + value.Z.Z * q32, value.Z.X * q13 + value.Z.Y * q23 + value.Z.Z * q33, value.Z.W ); - result.W = new Vector4( + result.W = Vector4.Create( value.W.X * q11 + value.W.Y * q21 + value.W.Z * q31, value.W.X * q12 + value.W.Y * q22 + value.W.Z * q32, value.W.X * q13 + value.W.Y * q23 + value.W.Z * q33, @@ -1717,10 +1706,10 @@ public static Impl Transpose(in Impl matrix) } else { - result.X = new Vector4(matrix.X.X, matrix.Y.X, matrix.Z.X, matrix.W.X); - result.Y = new Vector4(matrix.X.Y, matrix.Y.Y, matrix.Z.Y, matrix.W.Y); - result.Z = new Vector4(matrix.X.Z, matrix.Y.Z, matrix.Z.Z, matrix.W.Z); - result.W = new Vector4(matrix.X.W, matrix.Y.W, matrix.Z.W, matrix.W.W); + result.X = Vector4.Create(matrix.X.X, matrix.Y.X, matrix.Z.X, matrix.W.X); + result.Y = Vector4.Create(matrix.X.Y, matrix.Y.Y, matrix.Z.Y, matrix.W.Y); + result.Z = Vector4.Create(matrix.X.Z, matrix.Y.Z, matrix.Z.Z, matrix.W.Z); + result.W = Vector4.Create(matrix.X.W, matrix.Y.W, matrix.Z.W, matrix.W.W); } return result; diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Matrix4x4.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Matrix4x4.cs index 3834877ae44a8..55404a6008455 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Matrix4x4.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Matrix4x4.cs @@ -380,7 +380,7 @@ public static Matrix4x4 CreatePerspectiveLeftHanded(float width, float height, f /// The right-handed perspective projection matrix. /// is less than or equal to zero. /// -or- - /// is greater than or equal to . + /// is greater than or equal to . /// is less than or equal to zero. /// -or- /// is less than or equal to zero. @@ -397,7 +397,7 @@ public static Matrix4x4 CreatePerspectiveFieldOfView(float fieldOfView, float as /// The left-handed perspective projection matrix. /// is less than or equal to zero. /// -or- - /// is greater than or equal to . + /// is greater than or equal to . /// is less than or equal to zero. /// -or- /// is less than or equal to zero. diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Plane.Extensions.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Plane.Extensions.cs new file mode 100644 index 0000000000000..867e4d15222e6 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Plane.Extensions.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; + +namespace System.Numerics +{ + public static unsafe partial class Vector + { + /// Reinterprets a as a new . + /// The plane to reinterpret. + /// reinterpreted as a new . + [Intrinsic] + public static Vector4 AsVector4(this Plane value) + { +#if MONO + return Unsafe.As(ref value); +#else + return Unsafe.BitCast(value); +#endif + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Plane.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Plane.cs index 09d19a137f917..55f4522800316 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Plane.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Plane.cs @@ -30,7 +30,7 @@ public struct Plane : IEquatable [Intrinsic] public Plane(float x, float y, float z, float d) { - this = Vector128.Create(x, y, z, d).AsPlane(); + this = Create(x, y, z, d); } /// Creates a object from a specified normal and the distance along the normal from the origin. @@ -39,7 +39,7 @@ public Plane(float x, float y, float z, float d) [Intrinsic] public Plane(Vector3 normal, float d) { - this = new Vector4(normal, d).AsPlane(); + this = Create(normal, d); } /// Creates a object from a specified four-dimensional vector. @@ -50,6 +50,22 @@ public Plane(Vector4 value) this = value.AsPlane(); } + /// Creates a object from the X, Y, and Z components of its normal, and its distance from the origin on that normal. + /// The X component of the normal. + /// The Y component of the normal. + /// The Z component of the normal. + /// The distance of the plane along its normal from the origin. + /// A new object from the X, Y, and Z components of its normal, and its distance from the origin on that normal. + [Intrinsic] + internal static Plane Create(float x, float y, float z, float d) => Vector128.Create(x, y, z, d).AsPlane(); + + /// Creates a object from a specified normal and the distance along the normal from the origin. + /// The plane's normal vector. + /// The plane's distance from the origin along its normal vector.\ + /// A new object from a specified normal and the distance along the normal from the origin. + [Intrinsic] + internal static Plane Create(Vector3 normal, float d) => Vector4.Create(normal, d).AsPlane(); + /// Creates a object that contains three specified points. /// The first point defining the plane. /// The second point defining the plane. @@ -70,7 +86,7 @@ public static Plane CreateFromVertices(Vector3 point1, Vector3 point2, Vector3 p // D = - Dot(N, point1) float d = -Vector3.Dot(normal, point1); - return new Plane(normal, d); + return Create(normal, d); } else { @@ -89,16 +105,17 @@ public static Plane CreateFromVertices(Vector3 point1, Vector3 point2, Vector3 p // Normalize(N) float ls = nx * nx + ny * ny + nz * nz; - float invNorm = 1.0f / MathF.Sqrt(ls); + float invNorm = 1.0f / float.Sqrt(ls); - Vector3 normal = new Vector3( + Vector3 normal = Vector3.Create( nx * invNorm, ny * invNorm, nz * invNorm); - return new Plane( + return Create( normal, - -(normal.X * point1.X + normal.Y * point1.Y + normal.Z * point1.Z)); + -(normal.X * point1.X + normal.Y * point1.Y + normal.Z * point1.Z) + ); } } @@ -130,13 +147,13 @@ public static Plane Normalize(Plane value) { float normalLengthSquared = value.Normal.LengthSquared(); - if (MathF.Abs(normalLengthSquared - 1.0f) < NormalizeEpsilon) + if (float.Abs(normalLengthSquared - 1.0f) < NormalizeEpsilon) { // It already normalized, so we don't need to farther process. return value; } - return (value.AsVector128() / MathF.Sqrt(normalLengthSquared)).AsPlane(); + return (value.AsVector128() / float.Sqrt(normalLengthSquared)).AsPlane(); } /// Transforms a normalized plane by a 4x4 matrix. @@ -151,11 +168,12 @@ public static Plane Transform(Plane plane, Matrix4x4 matrix) float x = plane.Normal.X, y = plane.Normal.Y, z = plane.Normal.Z, w = plane.D; - return new Plane( + return Create( x * m.M11 + y * m.M12 + z * m.M13 + w * m.M14, x * m.M21 + y * m.M22 + z * m.M23 + w * m.M24, x * m.M31 + y * m.M32 + z * m.M33 + w * m.M34, - x * m.M41 + y * m.M42 + z * m.M43 + w * m.M44); + x * m.M41 + y * m.M42 + z * m.M43 + w * m.M44 + ); } /// Transforms a normalized plane by a Quaternion rotation. @@ -195,11 +213,12 @@ public static Plane Transform(Plane plane, Quaternion rotation) float x = plane.Normal.X, y = plane.Normal.Y, z = plane.Normal.Z; - return new Plane( + return Create( x * m11 + y * m21 + z * m31, x * m12 + y * m22 + z * m32, x * m13 + y * m23 + z * m33, - plane.D); + plane.D + ); } /// Returns a value that indicates whether two planes are equal. diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Quaternion.Extensions.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Quaternion.Extensions.cs index 42ac2e3d0dfc3..b42487e3f6303 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Quaternion.Extensions.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Quaternion.Extensions.cs @@ -9,43 +9,17 @@ namespace System.Numerics { public static unsafe partial class Vector { - /// Gets the element at the specified index. - /// The quaternion to get the element from. - /// The index of the element to get. - /// The value of the element at . - /// was less than zero or greater than the number of elements. + /// Reinterprets a as a new . + /// The quaternion to reinterpret. + /// reinterpreted as a new . [Intrinsic] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static float GetElement(this Quaternion quaternion, int index) + public static Vector4 AsVector4(this Quaternion value) { - return quaternion.AsVector128().GetElement(index); - } - - /// Creates a new with the element at the specified index set to the specified value and the remaining elements set to the same value as that in the given quaternion. - /// The quaternion to get the remaining elements from. - /// The index of the element to set. - /// The value to set the element to. - /// A with the value of the element at set to and the remaining elements set to the same value as that in . - /// was less than zero or greater than the number of elements. - [Intrinsic] - internal static Quaternion WithElement(this Quaternion quaternion, int index, float value) - { - return quaternion.AsVector128().WithElement(index, value).AsQuaternion(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static float GetElementUnsafe(in this Quaternion quaternion, int index) - { - Debug.Assert((index >= 0) && (index < Quaternion.Count)); - ref float address = ref Unsafe.AsRef(in quaternion.X); - return Unsafe.Add(ref address, index); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void SetElementUnsafe(ref this Quaternion quaternion, int index, float value) - { - Debug.Assert((index >= 0) && (index < Quaternion.Count)); - Unsafe.Add(ref quaternion.X, index) = value; +#if MONO + return Unsafe.As(ref value); +#else + return Unsafe.BitCast(value); +#endif } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Quaternion.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Quaternion.cs index 4ec7f536ed43f..b14a70e68106b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Quaternion.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Quaternion.cs @@ -38,7 +38,7 @@ public struct Quaternion : IEquatable [Intrinsic] public Quaternion(float x, float y, float z, float w) { - this = Vector128.Create(x, y, z, w).AsQuaternion(); + this = Create(x, y, z, w); } /// Creates a quaternion from the specified vector and rotation parts. @@ -47,7 +47,7 @@ public Quaternion(float x, float y, float z, float w) [Intrinsic] public Quaternion(Vector3 vectorPart, float scalarPart) { - this = new Vector4(vectorPart, scalarPart).AsQuaternion(); + this = Create(vectorPart, scalarPart); } /// Gets a quaternion that represents a zero. @@ -63,7 +63,7 @@ public static Quaternion Zero public static Quaternion Identity { [Intrinsic] - get => new Quaternion(0.0f, 0.0f, 0.0f, 1.0f); + get => Create(0.0f, 0.0f, 0.0f, 1.0f); } /// Gets or sets the element at the specified index. @@ -73,9 +73,13 @@ public static Quaternion Identity public float this[int index] { [Intrinsic] - readonly get => this.GetElement(index); + readonly get => this.AsVector128().GetElement(index); - set => this = this.WithElement(index, value); + [Intrinsic] + set + { + this = this.AsVector128().WithElement(index, value).AsQuaternion(); + } } /// Gets a value that indicates whether the current instance is the identity quaternion. @@ -161,10 +165,10 @@ public float this[int index] { if (Vector128.IsHardwareAccelerated) { - var left = value1.AsVector128(); - var right = value2.AsVector128(); + Vector128 left = value1.AsVector128(); + Vector128 right = value2.AsVector128(); - var result = right * left.GetElementUnsafe(3); + Vector128 result = right * left.GetElementUnsafe(3); result += (Vector128.Shuffle(right, Vector128.Create(3, 2, 1, 0)) * left.GetElementUnsafe(0)) * Vector128.Create(+1.0f, -1.0f, +1.0f, -1.0f); result += (Vector128.Shuffle(right, Vector128.Create(2, 3, 0, 1)) * left.GetElementUnsafe(1)) * Vector128.Create(+1.0f, +1.0f, -1.0f, -1.0f); result += (Vector128.Shuffle(right, Vector128.Create(1, 0, 3, 2)) * left.GetElementUnsafe(2)) * Vector128.Create(-1.0f, +1.0f, +1.0f, -1.0f); @@ -275,6 +279,22 @@ public static Quaternion Concatenate(Quaternion value1, Quaternion value2) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Quaternion Conjugate(Quaternion value) => (value.AsVector128() * Vector128.Create(-1.0f, -1.0f, -1.0f, 1.0f)).AsQuaternion(); + /// Creates a quaternion from the specified components. + /// The value to assign to the X component of the quaternion. + /// The value to assign to the Y component of the quaternion. + /// The value to assign to the Z component of the quaternion. + /// The value to assign to the W component of the quaternion. + /// A new quaternion created from the specified components.> + [Intrinsic] + internal static Quaternion Create(float x, float y, float z, float w) => Vector128.Create(x, y, z, w).AsQuaternion(); + + /// Creates a quaternion from the specified vector and rotation parts. + /// The vector part of the quaternion. + /// The rotation part of the quaternion. + /// A new quaternion created from the specified vector and rotation parts. + [Intrinsic] + internal static Quaternion Create(Vector3 vectorPart, float scalarPart) => Vector4.Create(vectorPart, scalarPart).AsQuaternion(); + /// Creates a quaternion from a unit vector and an angle to rotate around the vector. /// The unit vector to rotate around. /// The angle, in radians, to rotate around the vector. @@ -284,9 +304,7 @@ public static Quaternion CreateFromAxisAngle(Vector3 axis, float angle) { Quaternion ans; - float halfAngle = angle * 0.5f; - float s = MathF.Sin(halfAngle); - float c = MathF.Cos(halfAngle); + (float s, float c) = float.SinCos(angle * 0.5f); ans.X = axis.X * s; ans.Y = axis.Y * s; @@ -307,7 +325,7 @@ public static Quaternion CreateFromRotationMatrix(Matrix4x4 matrix) if (trace > 0.0f) { - float s = MathF.Sqrt(trace + 1.0f); + float s = float.Sqrt(trace + 1.0f); q.W = s * 0.5f; s = 0.5f / s; q.X = (matrix.M23 - matrix.M32) * s; @@ -318,7 +336,7 @@ public static Quaternion CreateFromRotationMatrix(Matrix4x4 matrix) { if (matrix.M11 >= matrix.M22 && matrix.M11 >= matrix.M33) { - float s = MathF.Sqrt(1.0f + matrix.M11 - matrix.M22 - matrix.M33); + float s = float.Sqrt(1.0f + matrix.M11 - matrix.M22 - matrix.M33); float invS = 0.5f / s; q.X = 0.5f * s; q.Y = (matrix.M12 + matrix.M21) * invS; @@ -327,7 +345,7 @@ public static Quaternion CreateFromRotationMatrix(Matrix4x4 matrix) } else if (matrix.M22 > matrix.M33) { - float s = MathF.Sqrt(1.0f + matrix.M22 - matrix.M11 - matrix.M33); + float s = float.Sqrt(1.0f + matrix.M22 - matrix.M11 - matrix.M33); float invS = 0.5f / s; q.X = (matrix.M21 + matrix.M12) * invS; q.Y = 0.5f * s; @@ -336,7 +354,7 @@ public static Quaternion CreateFromRotationMatrix(Matrix4x4 matrix) } else { - float s = MathF.Sqrt(1.0f + matrix.M33 - matrix.M11 - matrix.M22); + float s = float.Sqrt(1.0f + matrix.M33 - matrix.M11 - matrix.M22); float invS = 0.5f / s; q.X = (matrix.M31 + matrix.M13) * invS; q.Y = (matrix.M32 + matrix.M23) * invS; @@ -357,19 +375,9 @@ public static Quaternion CreateFromYawPitchRoll(float yaw, float pitch, float ro { // Roll first, about axis the object is facing, then // pitch upward, then yaw to face into the new heading - float sr, cr, sp, cp, sy, cy; - - float halfRoll = roll * 0.5f; - sr = MathF.Sin(halfRoll); - cr = MathF.Cos(halfRoll); - - float halfPitch = pitch * 0.5f; - sp = MathF.Sin(halfPitch); - cp = MathF.Cos(halfPitch); - - float halfYaw = yaw * 0.5f; - sy = MathF.Sin(halfYaw); - cy = MathF.Cos(halfYaw); + (float sr, float cr) = float.SinCos(roll * 0.5f); + (float sp, float cp) = float.SinCos(pitch * 0.5f); + (float sy, float cy) = float.SinCos(yaw * 0.5f); Quaternion result; @@ -441,7 +449,7 @@ public static Quaternion Lerp(Quaternion quaternion1, Quaternion quaternion2, fl // Normalize it. float ls = r.X * r.X + r.Y * r.Y + r.Z * r.Z + r.W * r.W; - float invNorm = 1.0f / MathF.Sqrt(ls); + float invNorm = 1.0f / float.Sqrt(ls); r.X *= invNorm; r.Y *= invNorm; @@ -507,13 +515,13 @@ public static Quaternion Slerp(Quaternion quaternion1, Quaternion quaternion2, f } else { - float omega = MathF.Acos(cosOmega); - float invSinOmega = 1 / MathF.Sin(omega); + float omega = float.Acos(cosOmega); + float invSinOmega = 1 / float.Sin(omega); - s1 = MathF.Sin((1.0f - t) * omega) * invSinOmega; + s1 = float.Sin((1.0f - t) * omega) * invSinOmega; s2 = (flip) - ? -MathF.Sin(t * omega) * invSinOmega - : MathF.Sin(t * omega) * invSinOmega; + ? -float.Sin(t * omega) * invSinOmega + : float.Sin(t * omega) * invSinOmega; } Quaternion ans; @@ -553,7 +561,7 @@ public static Quaternion Slerp(Quaternion quaternion1, Quaternion quaternion2, f /// Calculates the length of the quaternion. /// The computed length of the quaternion. [Intrinsic] - public readonly float Length() => MathF.Sqrt(LengthSquared()); + public readonly float Length() => float.Sqrt(LengthSquared()); /// Calculates the squared length of the quaternion. /// The length squared of the quaternion. diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector.cs index 536fd43e8cac7..0e8e2b6bf2bf6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; @@ -209,7 +210,7 @@ public static Vector As(this Vector vector) /// Computes the ceiling of each element in a vector. /// The vector that will have its ceiling computed. /// A vector whose elements are the ceiling of the elements in . - /// + /// [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector Ceiling(Vector value) @@ -228,7 +229,7 @@ public static Vector Ceiling(Vector value) /// Computes the ceiling of each element in a vector. /// The vector that will have its ceiling computed. /// A vector whose elements are the ceiling of the elements in . - /// + /// [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector Ceiling(Vector value) @@ -486,6 +487,72 @@ public static Vector ConvertToUInt64Native(Vector value) return result; } + /// Creates a new instance with all elements initialized to the specified value. + /// The type of the elements in the vector. + /// The value that all elements will be initialized to. + /// A new with all elements initialized to . + /// The type of () is not supported. + [Intrinsic] + public static Vector Create(T value) + { + Unsafe.SkipInit(out Vector result); + + for (int index = 0; index < Vector.Count; index++) + { + result.SetElementUnsafe(index, value); + } + + return result; + } + + /// Creates a new from a given readonly span. + /// The type of the elements in the vector. + /// The readonly span from which the vector is created. + /// A new with its elements set to the first elements from . + /// The length of is less than . + /// The type of () is not supported. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector Create(ReadOnlySpan values) + { + if (values.Length < Vector.Count) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.values); + } + return Unsafe.ReadUnaligned>(ref Unsafe.As(ref MemoryMarshal.GetReference(values))); + } + + /// Creates a new instance with the first element initialized to the specified value and the remaining elements initialized to zero. + /// The type of the elements in the vector. + /// The value that element 0 will be initialized to. + /// A new instance with the first element initialized to and the remaining elements initialized to zero. + /// The type of () is not supported. + [Intrinsic] + internal static unsafe Vector CreateScalar(T value) + { + Vector result = Vector.Zero; + result.SetElementUnsafe(0, value); + return result; + } + + /// Creates a new instance with the first element initialized to the specified value and the remaining elements left uninitialized. + /// The type of the elements in the vector. + /// The value that element 0 will be initialized to. + /// A new instance with the first element initialized to and the remaining elements left uninitialized. + /// The type of () is not supported. + [Intrinsic] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static Vector CreateScalarUnsafe(T value) + { + // This relies on us stripping the "init" flag from the ".locals" + // declaration to let the upper bits be uninitialized. + + ThrowHelper.ThrowForUnsupportedNumericsVectorBaseType(); + Unsafe.SkipInit(out Vector result); + + result.SetElementUnsafe(0, value); + return result; + } + /// Creates a new instance where the elements begin at a specified value and which are spaced apart according to another specified value. /// The type of the elements in the vector. /// The value that element 0 will be initialized to. @@ -493,7 +560,7 @@ public static Vector ConvertToUInt64Native(Vector value) /// A new instance with the first element initialized to and each subsequent element initialized to the the value of the previous element plus . [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector CreateSequence(T start, T step) => (Vector.Indices * step) + new Vector(start); + public static Vector CreateSequence(T start, T step) => (Vector.Indices * step) + Create(start); /// Divides two vectors to compute their quotient. /// The vector that will be divided by . @@ -598,7 +665,7 @@ public static bool EqualsAny(Vector left, Vector right) /// Computes the floor of each element in a vector. /// The vector that will have its floor computed. /// A vector whose elements are the floor of the elements in . - /// + /// [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector Floor(Vector value) @@ -617,7 +684,7 @@ public static Vector Floor(Vector value) /// Computes the floor of each element in a vector. /// The vector that will have its floor computed. /// A vector whose elements are the floor of the elements in . - /// + /// [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector Floor(Vector value) diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector2.Extensions.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector2.Extensions.cs index 5b57163ecad5f..21e3defd777a4 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector2.Extensions.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector2.Extensions.cs @@ -3,60 +3,22 @@ using System.Diagnostics; using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; namespace System.Numerics { public static unsafe partial class Vector { - /// Gets the element at the specified index. - /// The vector to get the element from. - /// The index of the element to get. - /// The value of the element at . - /// was less than zero or greater than the number of elements. + /// Reinterprets a to a new with the new elements zeroed. + /// The vector to reinterpret. + /// reinterpreted to a new with the new elements zeroed. [Intrinsic] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static float GetElement(this Vector2 vector, int index) - { - if ((uint)(index) >= (uint)(Vector2.Count)) - { - ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index); - } + public static Vector4 AsVector4(this Vector2 value) => value.AsVector128().AsVector4(); - return vector.GetElementUnsafe(index); - } - - /// Creates a new with the element at the specified index set to the specified value and the remaining elements set to the same value as that in the given vector. - /// The vector to get the remaining elements from. - /// The index of the element to set. - /// The value to set the element to. - /// A with the value of the element at set to and the remaining elements set to the same value as that in . - /// was less than zero or greater than the number of elements. + /// Reinterprets a to a new with the new elements undefined. + /// The vector to reinterpret. + /// reinterpreted to a new with the new elements undefined. [Intrinsic] - internal static Vector2 WithElement(this Vector2 vector, int index, float value) - { - if ((uint)(index) >= (uint)(Vector2.Count)) - { - ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index); - } - - Vector2 result = vector; - result.SetElementUnsafe(index, value); - return result; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static float GetElementUnsafe(in this Vector2 vector, int index) - { - Debug.Assert((index >= 0) && (index < Vector2.Count)); - ref float address = ref Unsafe.AsRef(in vector.X); - return Unsafe.Add(ref address, index); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void SetElementUnsafe(ref this Vector2 vector, int index, float value) - { - Debug.Assert((index >= 0) && (index < Vector2.Count)); - Unsafe.Add(ref vector.X, index) = value; - } + public static Vector4 AsVector4Unsafe(this Vector2 value) => value.AsVector128Unsafe().AsVector4(); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector2.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector2.cs index 931cbf5eca86b..7eb79bf3524a7 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector2.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector2.cs @@ -28,8 +28,9 @@ public partial struct Vector2 : IEquatable, IFormattable /// Creates a new object whose two elements have the same value. /// The value to assign to both elements. [Intrinsic] - public Vector2(float value) : this(value, value) + public Vector2(float value) { + this = Create(value); } /// Creates a vector whose elements have the specified values. @@ -38,20 +39,15 @@ public Vector2(float value) : this(value, value) [Intrinsic] public Vector2(float x, float y) { - X = x; - Y = y; + this = Create(x, y); } /// Constructs a vector from the given . The span must contain at least 2 elements. /// The span of elements to assign to the vector. + [Intrinsic] public Vector2(ReadOnlySpan values) { - if (values.Length < 2) - { - ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.values); - } - - this = Unsafe.ReadUnaligned(ref Unsafe.As(ref MemoryMarshal.GetReference(values))); + this = Create(values); } /// Returns a vector whose 2 elements are equal to zero. @@ -67,7 +63,7 @@ public static Vector2 Zero public static Vector2 One { [Intrinsic] - get => new Vector2(1.0f); + get => Create(1.0f); } /// Gets the vector (1,0). @@ -75,7 +71,7 @@ public static Vector2 One public static Vector2 UnitX { [Intrinsic] - get => new Vector2(1.0f, 0.0f); + get => CreateScalar(1.0f); } /// Gets the vector (0,1). @@ -83,7 +79,7 @@ public static Vector2 UnitX public static Vector2 UnitY { [Intrinsic] - get => new Vector2(0.0f, 1.0f); + get => Create(0.0f, 1.0f); } /// Gets or sets the element at the specified index. @@ -93,9 +89,24 @@ public static Vector2 UnitY public float this[int index] { [Intrinsic] - readonly get => this.GetElement(index); + readonly get + { + if ((uint)index >= Count) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index); + } + return this.AsVector128Unsafe().GetElement(index); + } - set => this = this.WithElement(index, value); + [Intrinsic] + set + { + if ((uint)index >= Count) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index); + } + this = this.AsVector128Unsafe().WithElement(index, value).AsVector2(); + } } /// Adds two vectors together. @@ -105,13 +116,7 @@ public float this[int index] /// The method defines the addition operation for objects. [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector2 operator +(Vector2 left, Vector2 right) - { - return new Vector2( - left.X + right.X, - left.Y + right.Y - ); - } + public static Vector2 operator +(Vector2 left, Vector2 right) => (left.AsVector128Unsafe() + right.AsVector128Unsafe()).AsVector2(); /// Divides the first vector by the second. /// The first vector. @@ -120,13 +125,7 @@ public float this[int index] /// The method defines the division operation for objects. [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector2 operator /(Vector2 left, Vector2 right) - { - return new Vector2( - left.X / right.X, - left.Y / right.Y - ); - } + public static Vector2 operator /(Vector2 left, Vector2 right) => (left.AsVector128Unsafe() / right.AsVector128Unsafe()).AsVector2(); /// Divides the specified vector by a specified scalar value. /// The vector. @@ -134,7 +133,7 @@ public float this[int index] /// The result of the division. /// The method defines the division operation for objects. [Intrinsic] - public static Vector2 operator /(Vector2 value1, float value2) => value1 / new Vector2(value2); + public static Vector2 operator /(Vector2 value1, float value2) => (value1.AsVector128Unsafe() / value2).AsVector2(); /// Returns a value that indicates whether each pair of elements in two specified vectors is equal. /// The first vector to compare. @@ -143,11 +142,7 @@ public float this[int index] /// Two objects are equal if each value in is equal to the corresponding value in . [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Vector2 left, Vector2 right) - { - return (left.X == right.X) - && (left.Y == right.Y); - } + public static bool operator ==(Vector2 left, Vector2 right) => left.AsVector128() == right.AsVector128(); /// Returns a value that indicates whether two specified vectors are not equal. /// The first vector to compare. @@ -163,13 +158,7 @@ public float this[int index] /// The method defines the multiplication operation for objects. [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector2 operator *(Vector2 left, Vector2 right) - { - return new Vector2( - left.X * right.X, - left.Y * right.Y - ); - } + public static Vector2 operator *(Vector2 left, Vector2 right) => (left.AsVector128Unsafe() * right.AsVector128Unsafe()).AsVector2(); /// Multiplies the specified vector by the specified scalar value. /// The vector. @@ -177,7 +166,7 @@ public float this[int index] /// The scaled vector. /// The method defines the multiplication operation for objects. [Intrinsic] - public static Vector2 operator *(Vector2 left, float right) => left * new Vector2(right); + public static Vector2 operator *(Vector2 left, float right) => (left.AsVector128Unsafe() * right).AsVector2(); /// Multiplies the scalar value by the specified vector. /// The vector. @@ -194,33 +183,21 @@ public float this[int index] /// The method defines the subtraction operation for objects. [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector2 operator -(Vector2 left, Vector2 right) - { - return new Vector2( - left.X - right.X, - left.Y - right.Y - ); - } + public static Vector2 operator -(Vector2 left, Vector2 right) => (left.AsVector128Unsafe() - right.AsVector128Unsafe()).AsVector2(); /// Negates the specified vector. /// The vector to negate. /// The negated vector. /// The method defines the unary negation operation for objects. [Intrinsic] - public static Vector2 operator -(Vector2 value) => Zero - value; + public static Vector2 operator -(Vector2 value) => (-value.AsVector128Unsafe()).AsVector2(); /// Returns a vector whose elements are the absolute values of each of the specified vector's elements. /// A vector. /// The absolute value vector. [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector2 Abs(Vector2 value) - { - return new Vector2( - MathF.Abs(value.X), - MathF.Abs(value.Y) - ); - } + public static Vector2 Abs(Vector2 value) => Vector128.Abs(value.AsVector128Unsafe()).AsVector2(); /// Adds two vectors together. /// The first vector to add. @@ -241,12 +218,49 @@ public static Vector2 Clamp(Vector2 value1, Vector2 min, Vector2 max) return Min(Max(value1, min), max); } + /// Creates a new object whose two elements have the same value. + /// The value to assign to all two elements. + /// A new whose two elements have the same value. + [Intrinsic] + public static Vector2 Create(float value) => Vector128.Create(value).AsVector2(); + + /// Creates a vector whose elements have the specified values. + /// The value to assign to the field. + /// The value to assign to the field. + /// A new whose elements have the specified values. + [Intrinsic] + public static Vector2 Create(float x, float y) => Vector128.Create(x, y, 0, 0).AsVector2(); + + /// Constructs a vector from the given . The span must contain at least 2 elements. + /// The span of elements to assign to the vector. + [Intrinsic] + public static Vector2 Create(ReadOnlySpan values) + { + if (values.Length < Count) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.values); + } + return Unsafe.ReadUnaligned(ref Unsafe.As(ref MemoryMarshal.GetReference(values))); + } + + /// Creates a vector with initialized to the specified value and the remaining elements initialized to zero. + /// The value to assign to the field. + /// A new with initialized and the remaining elements initialized to zero. + [Intrinsic] + internal static Vector2 CreateScalar(float x) => Vector128.CreateScalar(x).AsVector2(); + + /// Creates a vector with initialized to the specified value and the remaining elements left uninitialized. + /// The value to assign to the field. + /// A new with initialized and the remaining elements left uninitialized. + [Intrinsic] + internal static Vector2 CreateScalarUnsafe(float x) => Vector128.CreateScalarUnsafe(x).AsVector2(); + /// Computes the Euclidean distance between the two given points. /// The first point. /// The second point. /// The distance. [Intrinsic] - public static float Distance(Vector2 value1, Vector2 value2) => MathF.Sqrt(DistanceSquared(value1, value2)); + public static float Distance(Vector2 value1, Vector2 value2) => float.Sqrt(DistanceSquared(value1, value2)); /// Returns the Euclidean distance squared between two specified points. /// The first point. @@ -275,22 +289,12 @@ public static Vector2 Clamp(Vector2 value1, Vector2 min, Vector2 max) /// The dot product. [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static float Dot(Vector2 value1, Vector2 value2) - { - return (value1.X * value2.X) - + (value1.Y * value2.Y); - } + public static float Dot(Vector2 value1, Vector2 value2) => Vector128.Dot(value1.AsVector128(), value2.AsVector128()); /// [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector2 FusedMultiplyAdd(Vector2 left, Vector2 right, Vector2 addend) - { - return new Vector2( - float.FusedMultiplyAdd(left.X, right.X, addend.X), - float.FusedMultiplyAdd(left.Y, right.Y, addend.Y) - ); - } + public static Vector2 FusedMultiplyAdd(Vector2 left, Vector2 right, Vector2 addend) => Vector128.FusedMultiplyAdd(left.AsVector128Unsafe(), right.AsVector128Unsafe(), addend.AsVector128Unsafe()).AsVector2(); /// Performs a linear interpolation between two vectors based on the given weighting. /// The first vector. @@ -310,13 +314,7 @@ public static Vector2 FusedMultiplyAdd(Vector2 left, Vector2 right, Vector2 adde /// The maximized vector. [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector2 Max(Vector2 value1, Vector2 value2) - { - return new Vector2( - (value1.X > value2.X) ? value1.X : value2.X, - (value1.Y > value2.Y) ? value1.Y : value2.Y - ); - } + public static Vector2 Max(Vector2 value1, Vector2 value2) => Vector128.Max(value1.AsVector128Unsafe(), value2.AsVector128Unsafe()).AsVector2(); /// Returns a vector whose elements are the minimum of each of the pairs of elements in two specified vectors. /// The first vector. @@ -324,13 +322,7 @@ public static Vector2 Max(Vector2 value1, Vector2 value2) /// The minimized vector. [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector2 Min(Vector2 value1, Vector2 value2) - { - return new Vector2( - (value1.X < value2.X) ? value1.X : value2.X, - (value1.Y < value2.Y) ? value1.Y : value2.Y - ); - } + public static Vector2 Min(Vector2 value1, Vector2 value2) => Vector128.Min(value1.AsVector128Unsafe(), value2.AsVector128Unsafe()).AsVector2(); /// Returns a new vector whose values are the product of each pair of elements in two specified vectors. /// The first vector. @@ -356,13 +348,7 @@ public static Vector2 Min(Vector2 value1, Vector2 value2) /// [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector2 MultiplyAddEstimate(Vector2 left, Vector2 right, Vector2 addend) - { - return new Vector2( - float.MultiplyAddEstimate(left.X, right.X, addend.X), - float.MultiplyAddEstimate(left.Y, right.Y, addend.Y) - ); - } + public static Vector2 MultiplyAddEstimate(Vector2 left, Vector2 right, Vector2 addend) => Vector128.MultiplyAddEstimate(left.AsVector128Unsafe(), right.AsVector128Unsafe(), addend.AsVector128Unsafe()).AsVector2(); /// Negates a specified vector. /// The vector to negate. @@ -388,13 +374,7 @@ public static Vector2 MultiplyAddEstimate(Vector2 left, Vector2 right, Vector2 a /// The square root vector. [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector2 SquareRoot(Vector2 value) - { - return new Vector2( - MathF.Sqrt(value.X), - MathF.Sqrt(value.Y) - ); - } + public static Vector2 SquareRoot(Vector2 value) => Vector128.Sqrt(value.AsVector128Unsafe()).AsVector2(); /// Subtracts the second vector from the first. /// The first vector. @@ -444,7 +424,7 @@ public static Vector2 Transform(Vector2 value, Quaternion rotation) float yy2 = rotation.Y * y2; float zz2 = rotation.Z * z2; - return new Vector2( + return Create( value.X * (1.0f - yy2 - zz2) + value.Y * (xy2 - wz2), value.X * (xy2 + wz2) + value.Y * (1.0f - xx2 - zz2) ); @@ -569,28 +549,7 @@ public readonly bool TryCopyTo(Span destination) /// if the two vectors are equal; otherwise, . /// Two vectors are equal if their and elements are equal. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly bool Equals(Vector2 other) - { - // This function needs to account for floating-point equality around NaN - // and so must behave equivalently to the underlying float/double.Equals - - if (Vector64.IsHardwareAccelerated) - { - return Vector64.LoadUnsafe(ref Unsafe.AsRef(in X)).Equals(Vector64.LoadUnsafe(ref other.X)); - } - else if (Vector128.IsHardwareAccelerated) - { - return this.AsVector128().Equals(other.AsVector128()); - } - - return SoftwareFallback(in this, other); - - static bool SoftwareFallback(in Vector2 self, Vector2 other) - { - return self.X.Equals(other.X) - && self.Y.Equals(other.Y); - } - } + public readonly bool Equals(Vector2 other) => this.AsVector128().Equals(other.AsVector128()); /// Returns the hash code for this instance. /// The hash code. @@ -600,7 +559,7 @@ static bool SoftwareFallback(in Vector2 self, Vector2 other) /// The vector's length. /// [Intrinsic] - public readonly float Length() => MathF.Sqrt(LengthSquared()); + public readonly float Length() => float.Sqrt(LengthSquared()); /// Returns the length of the vector squared. /// The vector's length squared. diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector3.Extensions.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector3.Extensions.cs index 2eb006eeda745..ba7457ba8310a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector3.Extensions.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector3.Extensions.cs @@ -3,60 +3,22 @@ using System.Diagnostics; using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; namespace System.Numerics { public static unsafe partial class Vector { - /// Gets the element at the specified index. - /// The vector to get the element from. - /// The index of the element to get. - /// The value of the element at . - /// was less than zero or greater than the number of elements. + /// Converts a to a new with the new elements zeroed. + /// The vector to convert. + /// converted to a new with the new elements zeroed. [Intrinsic] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static float GetElement(this Vector3 vector, int index) - { - if ((uint)(index) >= (uint)(Vector3.Count)) - { - ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index); - } + public static Vector4 AsVector4(this Vector3 value) => value.AsVector128().AsVector4(); - return vector.GetElementUnsafe(index); - } - - /// Creates a new with the element at the specified index set to the specified value and the remaining elements set to the same value as that in the given vector. - /// The vector to get the remaining elements from. - /// The index of the element to set. - /// The value to set the element to. - /// A with the value of the element at set to and the remaining elements set to the same value as that in . - /// was less than zero or greater than the number of elements. + /// Converts a to a new with the new elements undefined. + /// The vector to convert. + /// converted to a new with the new elements undefined. [Intrinsic] - internal static Vector3 WithElement(this Vector3 vector, int index, float value) - { - if ((uint)(index) >= (uint)(Vector3.Count)) - { - ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index); - } - - Vector3 result = vector; - result.SetElementUnsafe(index, value); - return result; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static float GetElementUnsafe(in this Vector3 vector, int index) - { - Debug.Assert((index >= 0) && (index < Vector3.Count)); - ref float address = ref Unsafe.AsRef(in vector.X); - return Unsafe.Add(ref address, index); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void SetElementUnsafe(ref this Vector3 vector, int index, float value) - { - Debug.Assert((index >= 0) && (index < Vector3.Count)); - Unsafe.Add(ref vector.X, index) = value; - } + public static Vector4 AsVector4Unsafe(this Vector3 value) => value.AsVector128Unsafe().AsVector4(); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector3.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector3.cs index 7dc4994040eaa..29efe6d702831 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector3.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector3.cs @@ -31,16 +31,18 @@ public partial struct Vector3 : IEquatable, IFormattable /// Creates a new object whose three elements have the same value. /// The value to assign to all three elements. [Intrinsic] - public Vector3(float value) : this(value, value, value) + public Vector3(float value) { + this = Create(value); } /// Creates a new object from the specified object and the specified value. /// The vector with two elements. /// The additional value to assign to the field. [Intrinsic] - public Vector3(Vector2 value, float z) : this(value.X, value.Y, z) + public Vector3(Vector2 value, float z) { + this = Create(value, z); } /// Creates a vector whose elements have the specified values. @@ -50,21 +52,15 @@ public Vector3(Vector2 value, float z) : this(value.X, value.Y, z) [Intrinsic] public Vector3(float x, float y, float z) { - X = x; - Y = y; - Z = z; + this = Create(x, y, z); } /// Constructs a vector from the given . The span must contain at least 3 elements. /// The span of elements to assign to the vector. + [Intrinsic] public Vector3(ReadOnlySpan values) { - if (values.Length < 3) - { - ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.values); - } - - this = Unsafe.ReadUnaligned(ref Unsafe.As(ref MemoryMarshal.GetReference(values))); + this = Create(values); } /// Gets a vector whose 3 elements are equal to zero. @@ -80,7 +76,7 @@ public static Vector3 Zero public static Vector3 One { [Intrinsic] - get => new Vector3(1.0f); + get => Create(1.0f); } /// Gets the vector (1,0,0). @@ -88,7 +84,7 @@ public static Vector3 One public static Vector3 UnitX { [Intrinsic] - get => new Vector3(1.0f, 0.0f, 0.0f); + get => CreateScalar(1.0f); } /// Gets the vector (0,1,0). @@ -96,7 +92,7 @@ public static Vector3 UnitX public static Vector3 UnitY { [Intrinsic] - get => new Vector3(0.0f, 1.0f, 0.0f); + get => Create(0.0f, 1.0f, 0.0f); } /// Gets the vector (0,0,1). @@ -104,7 +100,7 @@ public static Vector3 UnitY public static Vector3 UnitZ { [Intrinsic] - get => new Vector3(0.0f, 0.0f, 1.0f); + get => Create(0.0f, 0.0f, 1.0f); } /// Gets or sets the element at the specified index. @@ -114,9 +110,24 @@ public static Vector3 UnitZ public float this[int index] { [Intrinsic] - readonly get => this.GetElement(index); + readonly get + { + if ((uint)index >= Count) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index); + } + return this.AsVector128Unsafe().GetElement(index); + } - set => this = this.WithElement(index, value); + [Intrinsic] + set + { + if ((uint)index >= Count) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index); + } + this = this.AsVector128Unsafe().WithElement(index, value).AsVector3(); + } } /// Adds two vectors together. @@ -126,14 +137,7 @@ public float this[int index] /// The method defines the addition operation for objects. [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector3 operator +(Vector3 left, Vector3 right) - { - return new Vector3( - left.X + right.X, - left.Y + right.Y, - left.Z + right.Z - ); - } + public static Vector3 operator +(Vector3 left, Vector3 right) => (left.AsVector128Unsafe() + right.AsVector128Unsafe()).AsVector3(); /// Divides the first vector by the second. /// The first vector. @@ -142,14 +146,7 @@ public float this[int index] /// The method defines the division operation for objects. [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector3 operator /(Vector3 left, Vector3 right) - { - return new Vector3( - left.X / right.X, - left.Y / right.Y, - left.Z / right.Z - ); - } + public static Vector3 operator /(Vector3 left, Vector3 right) => (left.AsVector128Unsafe() / right.AsVector128Unsafe()).AsVector3(); /// Divides the specified vector by a specified scalar value. /// The vector. @@ -157,7 +154,7 @@ public float this[int index] /// The result of the division. /// The method defines the division operation for objects. [Intrinsic] - public static Vector3 operator /(Vector3 value1, float value2) => value1 / new Vector3(value2); + public static Vector3 operator /(Vector3 value1, float value2) => (value1.AsVector128Unsafe() / value2).AsVector3(); /// Returns a value that indicates whether each pair of elements in two specified vectors is equal. /// The first vector to compare. @@ -166,12 +163,7 @@ public float this[int index] /// Two objects are equal if each element in is equal to the corresponding element in . [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Vector3 left, Vector3 right) - { - return (left.X == right.X) - && (left.Y == right.Y) - && (left.Z == right.Z); - } + public static bool operator ==(Vector3 left, Vector3 right) => left.AsVector128() == right.AsVector128(); /// Returns a value that indicates whether two specified vectors are not equal. /// The first vector to compare. @@ -187,14 +179,7 @@ public float this[int index] /// The method defines the multiplication operation for objects. [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector3 operator *(Vector3 left, Vector3 right) - { - return new Vector3( - left.X * right.X, - left.Y * right.Y, - left.Z * right.Z - ); - } + public static Vector3 operator *(Vector3 left, Vector3 right) => (left.AsVector128Unsafe() * right.AsVector128Unsafe()).AsVector3(); /// Multiplies the specified vector by the specified scalar value. /// The vector. @@ -202,7 +187,7 @@ public float this[int index] /// The scaled vector. /// The method defines the multiplication operation for objects. [Intrinsic] - public static Vector3 operator *(Vector3 left, float right) => left * new Vector3(right); + public static Vector3 operator *(Vector3 left, float right) => (left.AsVector128Unsafe() * right).AsVector3(); /// Multiplies the scalar value by the specified vector. /// The vector. @@ -219,35 +204,21 @@ public float this[int index] /// The method defines the subtraction operation for objects. [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector3 operator -(Vector3 left, Vector3 right) - { - return new Vector3( - left.X - right.X, - left.Y - right.Y, - left.Z - right.Z - ); - } + public static Vector3 operator -(Vector3 left, Vector3 right) => (left.AsVector128Unsafe() - right.AsVector128Unsafe()).AsVector3(); /// Negates the specified vector. /// The vector to negate. /// The negated vector. /// The method defines the unary negation operation for objects. [Intrinsic] - public static Vector3 operator -(Vector3 value) => Zero - value; + public static Vector3 operator -(Vector3 value) => (-value.AsVector128Unsafe()).AsVector3(); /// Returns a vector whose elements are the absolute values of each of the specified vector's elements. /// A vector. /// The absolute value vector. [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector3 Abs(Vector3 value) - { - return new Vector3( - MathF.Abs(value.X), - MathF.Abs(value.Y), - MathF.Abs(value.Z) - ); - } + public static Vector3 Abs(Vector3 value) => Vector128.Abs(value.AsVector128Unsafe()).AsVector3(); /// Adds two vectors together. /// The first vector to add. @@ -268,6 +239,56 @@ public static Vector3 Clamp(Vector3 value1, Vector3 min, Vector3 max) return Min(Max(value1, min), max); } + /// Creates a new object whose three elements have the same value. + /// The value to assign to all three elements. + /// A new whose three elements have the same value. + [Intrinsic] + public static Vector3 Create(float value) => Vector128.Create(value).AsVector3(); + + /// Creates a new object from the specified object and a Z and a W component. + /// The vector to use for the X and Y components. + /// The Z component. + /// A new from the specified object and a Z and a W component. + [Intrinsic] + public static Vector3 Create(Vector2 vector, float z) + { + return vector.AsVector128Unsafe() + .WithElement(2, z) + .AsVector3(); + } + + /// Creates a vector whose elements have the specified values. + /// The value to assign to the field. + /// The value to assign to the field. + /// The value to assign to the field. + /// A new whose elements have the specified values. + [Intrinsic] + public static Vector3 Create(float x, float y, float z) => Vector128.Create(x, y, z, 0).AsVector3(); + + /// Constructs a vector from the given . The span must contain at least 3 elements. + /// The span of elements to assign to the vector. + [Intrinsic] + public static Vector3 Create(ReadOnlySpan values) + { + if (values.Length < Count) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.values); + } + return Unsafe.ReadUnaligned(ref Unsafe.As(ref MemoryMarshal.GetReference(values))); + } + + /// Creates a vector with initialized to the specified value and the remaining elements initialized to zero. + /// The value to assign to the field. + /// A new with initialized and the remaining elements initialized to zero. + [Intrinsic] + internal static Vector3 CreateScalar(float x) => Vector128.CreateScalar(x).AsVector3(); + + /// Creates a vector with initialized to the specified value and the remaining elements left uninitialized. + /// The value to assign to the field. + /// A new with initialized and the remaining elements left uninitialized. + [Intrinsic] + internal static Vector3 CreateScalarUnsafe(float x) => Vector128.CreateScalarUnsafe(x).AsVector3(); + /// Computes the cross product of two vectors. /// The first vector. /// The second vector. @@ -275,7 +296,7 @@ public static Vector3 Clamp(Vector3 value1, Vector3 min, Vector3 max) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector3 Cross(Vector3 vector1, Vector3 vector2) { - return new Vector3( + return Create( (vector1.Y * vector2.Z) - (vector1.Z * vector2.Y), (vector1.Z * vector2.X) - (vector1.X * vector2.Z), (vector1.X * vector2.Y) - (vector1.Y * vector2.X) @@ -287,7 +308,7 @@ public static Vector3 Cross(Vector3 vector1, Vector3 vector2) /// The second point. /// The distance. [Intrinsic] - public static float Distance(Vector3 value1, Vector3 value2) => MathF.Sqrt(DistanceSquared(value1, value2)); + public static float Distance(Vector3 value1, Vector3 value2) => float.Sqrt(DistanceSquared(value1, value2)); /// Returns the Euclidean distance squared between two specified points. /// The first point. @@ -316,24 +337,12 @@ public static Vector3 Cross(Vector3 vector1, Vector3 vector2) /// The dot product. [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static float Dot(Vector3 vector1, Vector3 vector2) - { - return (vector1.X * vector2.X) - + (vector1.Y * vector2.Y) - + (vector1.Z * vector2.Z); - } + public static float Dot(Vector3 vector1, Vector3 vector2) => Vector128.Dot(vector1.AsVector128(), vector2.AsVector128()); /// [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector3 FusedMultiplyAdd(Vector3 left, Vector3 right, Vector3 addend) - { - return new Vector3( - float.FusedMultiplyAdd(left.X, right.X, addend.X), - float.FusedMultiplyAdd(left.Y, right.Y, addend.Y), - float.FusedMultiplyAdd(left.Z, right.Z, addend.Z) - ); - } + public static Vector3 FusedMultiplyAdd(Vector3 left, Vector3 right, Vector3 addend) => Vector128.FusedMultiplyAdd(left.AsVector128Unsafe(), right.AsVector128Unsafe(), addend.AsVector128Unsafe()).AsVector3(); /// Performs a linear interpolation between two vectors based on the given weighting. /// The first vector. @@ -350,14 +359,7 @@ public static Vector3 FusedMultiplyAdd(Vector3 left, Vector3 right, Vector3 adde /// The maximized vector. [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector3 Max(Vector3 value1, Vector3 value2) - { - return new Vector3( - (value1.X > value2.X) ? value1.X : value2.X, - (value1.Y > value2.Y) ? value1.Y : value2.Y, - (value1.Z > value2.Z) ? value1.Z : value2.Z - ); - } + public static Vector3 Max(Vector3 value1, Vector3 value2) => Vector128.Max(value1.AsVector128Unsafe(), value2.AsVector128Unsafe()).AsVector3(); /// Returns a vector whose elements are the minimum of each of the pairs of elements in two specified vectors. /// The first vector. @@ -365,14 +367,7 @@ public static Vector3 Max(Vector3 value1, Vector3 value2) /// The minimized vector. [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector3 Min(Vector3 value1, Vector3 value2) - { - return new Vector3( - (value1.X < value2.X) ? value1.X : value2.X, - (value1.Y < value2.Y) ? value1.Y : value2.Y, - (value1.Z < value2.Z) ? value1.Z : value2.Z - ); - } + public static Vector3 Min(Vector3 value1, Vector3 value2) => Vector128.Min(value1.AsVector128Unsafe(), value2.AsVector128Unsafe()).AsVector3(); /// Returns a new vector whose values are the product of each pair of elements in two specified vectors. /// The first vector. @@ -398,14 +393,7 @@ public static Vector3 Min(Vector3 value1, Vector3 value2) /// [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector3 MultiplyAddEstimate(Vector3 left, Vector3 right, Vector3 addend) - { - return new Vector3( - float.MultiplyAddEstimate(left.X, right.X, addend.X), - float.MultiplyAddEstimate(left.Y, right.Y, addend.Y), - float.MultiplyAddEstimate(left.Z, right.Z, addend.Z) - ); - } + public static Vector3 MultiplyAddEstimate(Vector3 left, Vector3 right, Vector3 addend) => Vector128.MultiplyAddEstimate(left.AsVector128Unsafe(), right.AsVector128Unsafe(), addend.AsVector128Unsafe()).AsVector3(); /// Negates a specified vector. /// The vector to negate. @@ -431,14 +419,7 @@ public static Vector3 MultiplyAddEstimate(Vector3 left, Vector3 right, Vector3 a /// The square root vector. [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector3 SquareRoot(Vector3 value) - { - return new Vector3( - MathF.Sqrt(value.X), - MathF.Sqrt(value.Y), - MathF.Sqrt(value.Z) - ); - } + public static Vector3 SquareRoot(Vector3 value) => Vector128.Sqrt(value.AsVector128Unsafe()).AsVector3(); /// Subtracts the second vector from the first. /// The first vector. @@ -475,7 +456,7 @@ public static Vector3 Transform(Vector3 value, Quaternion rotation) float yz2 = rotation.Y * z2; float zz2 = rotation.Z * z2; - return new Vector3( + return Create( value.X * (1.0f - yy2 - zz2) + value.Y * (xy2 - wz2) + value.Z * (xz2 + wy2), value.X * (xy2 + wz2) + value.Y * (1.0f - xx2 - zz2) + value.Z * (yz2 - wx2), value.X * (xz2 - wy2) + value.Y * (yz2 + wx2) + value.Z * (1.0f - xx2 - yy2) @@ -587,25 +568,7 @@ public readonly bool TryCopyTo(Span destination) /// if the two vectors are equal; otherwise, . /// Two vectors are equal if their , , and elements are equal. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly bool Equals(Vector3 other) - { - // This function needs to account for floating-point equality around NaN - // and so must behave equivalently to the underlying float/double.Equals - - if (Vector128.IsHardwareAccelerated) - { - return this.AsVector128().Equals(other.AsVector128()); - } - - return SoftwareFallback(in this, other); - - static bool SoftwareFallback(in Vector3 self, Vector3 other) - { - return self.X.Equals(other.X) - && self.Y.Equals(other.Y) - && self.Z.Equals(other.Z); - } - } + public readonly bool Equals(Vector3 other) => this.AsVector128().Equals(other.AsVector128()); /// Returns the hash code for this instance. /// The hash code. @@ -615,7 +578,7 @@ static bool SoftwareFallback(in Vector3 self, Vector3 other) /// The vector's length. /// [Intrinsic] - public readonly float Length() => MathF.Sqrt(LengthSquared()); + public readonly float Length() => float.Sqrt(LengthSquared()); /// Returns the length of the vector squared. /// The vector's length squared. diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector4.Extensions.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector4.Extensions.cs index 5e276425a0e53..8e243636d96e4 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector4.Extensions.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector4.Extensions.cs @@ -12,7 +12,8 @@ public static unsafe partial class Vector /// Reinterprets a as a new . /// The vector to reinterpret. /// reinterpreted as a new . - internal static Plane AsPlane(this Vector4 value) + [Intrinsic] + public static Plane AsPlane(this Vector4 value) { #if MONO return Unsafe.As(ref value); @@ -24,7 +25,8 @@ internal static Plane AsPlane(this Vector4 value) /// Reinterprets a as a new . /// The vector to reinterpret. /// reinterpreted as a new . - internal static Quaternion AsQuaternion(this Vector4 value) + [Intrinsic] + public static Quaternion AsQuaternion(this Vector4 value) { #if MONO return Unsafe.As(ref value); @@ -33,37 +35,16 @@ internal static Quaternion AsQuaternion(this Vector4 value) #endif } - /// Gets the element at the specified index. - /// The vector to get the element from. - /// The index of the element to get. - /// The value of the element at . - /// was less than zero or greater than the number of elements. + /// Reinterprets a as a new . + /// The vector to reinterpret. + /// reinterpreted as a new . [Intrinsic] - internal static float GetElement(this Vector4 vector, int index) => vector.AsVector128().GetElement(index); + public static Vector2 AsVector2(this Vector4 value) => value.AsVector128().AsVector2(); - /// Creates a new with the element at the specified index set to the specified value and the remaining elements set to the same value as that in the given vector. - /// The vector to get the remaining elements from. - /// The index of the element to set. - /// The value to set the element to. - /// A with the value of the element at set to and the remaining elements set to the same value as that in . - /// was less than zero or greater than the number of elements. + /// Reinterprets a as a new . + /// The vector to reinterpret. + /// reinterpreted as a new . [Intrinsic] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static Vector4 WithElement(this Vector4 vector, int index, float value) => vector.AsVector128().WithElement(index, value).AsVector4(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static float GetElementUnsafe(in this Vector4 vector, int index) - { - Debug.Assert((index >= 0) && (index < Vector4.Count)); - ref float address = ref Unsafe.AsRef(in vector.X); - return Unsafe.Add(ref address, index); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void SetElementUnsafe(ref this Vector4 vector, int index, float value) - { - Debug.Assert((index >= 0) && (index < Vector4.Count)); - Unsafe.Add(ref vector.X, index) = value; - } + public static Vector3 AsVector3(this Vector4 value) => value.AsVector128().AsVector3(); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector4.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector4.cs index f6893396cc4fc..65ae809ba6bc3 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector4.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector4.cs @@ -37,7 +37,7 @@ public partial struct Vector4 : IEquatable, IFormattable [Intrinsic] public Vector4(float value) { - this = Vector128.Create(value).AsVector4(); + this = Create(value); } /// Creates a new object from the specified object and a Z and a W component. @@ -47,10 +47,7 @@ public Vector4(float value) [Intrinsic] public Vector4(Vector2 value, float z, float w) { - this = value.AsVector128() - .WithElement(2, z) - .WithElement(3, w) - .AsVector4(); + this = Create(value, z, w); } /// Constructs a new object from the specified object and a W component. @@ -59,9 +56,7 @@ public Vector4(Vector2 value, float z, float w) [Intrinsic] public Vector4(Vector3 value, float w) { - this = value.AsVector128() - .WithElement(3, w) - .AsVector4(); + this = Create(value, w); } /// Creates a vector whose elements have the specified values. @@ -72,14 +67,15 @@ public Vector4(Vector3 value, float w) [Intrinsic] public Vector4(float x, float y, float z, float w) { - this = Vector128.Create(x, y, z, w).AsVector4(); + this = Create(x, y, z, w); } /// Constructs a vector from the given . The span must contain at least 4 elements. /// The span of elements to assign to the vector. + [Intrinsic] public Vector4(ReadOnlySpan values) { - this = Vector128.Create(values).AsVector4(); + this = Create(values); } /// Gets a vector whose 4 elements are equal to zero. @@ -96,7 +92,7 @@ public static Vector4 Zero public static Vector4 One { [Intrinsic] - get => new Vector4(1); + get => Create(1); } /// Gets the vector (1,0,0,0). @@ -104,7 +100,7 @@ public static Vector4 One public static Vector4 UnitX { [Intrinsic] - get => new Vector4(1.0f, 0.0f, 0.0f, 0.0f); + get => CreateScalar(1.0f); } /// Gets the vector (0,1,0,0). @@ -112,7 +108,7 @@ public static Vector4 UnitX public static Vector4 UnitY { [Intrinsic] - get => new Vector4(0.0f, 1.0f, 0.0f, 0.0f); + get => Create(0.0f, 1.0f, 0.0f, 0.0f); } /// Gets the vector (0,0,1,0). @@ -120,7 +116,7 @@ public static Vector4 UnitY public static Vector4 UnitZ { [Intrinsic] - get => new Vector4(0.0f, 0.0f, 1.0f, 0.0f); + get => Create(0.0f, 0.0f, 1.0f, 0.0f); } /// Gets the vector (0,0,0,1). @@ -128,7 +124,7 @@ public static Vector4 UnitZ public static Vector4 UnitW { [Intrinsic] - get => new Vector4(0.0f, 0.0f, 0.0f, 1.0f); + get => Create(0.0f, 0.0f, 0.0f, 1.0f); } /// Gets or sets the element at the specified index. @@ -138,9 +134,13 @@ public static Vector4 UnitW public float this[int index] { [Intrinsic] - readonly get => this.GetElement(index); + readonly get => this.AsVector128().GetElement(index); - set => this = this.WithElement(index, value); + [Intrinsic] + set + { + this = this.AsVector128().WithElement(index, value).AsVector4(); + } } /// Adds two vectors together. @@ -167,7 +167,7 @@ public float this[int index] /// The result of the division. /// The method defines the division operation for objects. [Intrinsic] - public static Vector4 operator /(Vector4 value1, float value2) => value1 / new Vector4(value2); + public static Vector4 operator /(Vector4 value1, float value2) => (value1.AsVector128() / value2).AsVector4(); /// Returns a value that indicates whether each pair of elements in two specified vectors is equal. /// The first vector to compare. @@ -200,7 +200,7 @@ public float this[int index] /// The scaled vector. /// The method defines the multiplication operation for objects. [Intrinsic] - public static Vector4 operator *(Vector4 left, float right) => left * new Vector4(right); + public static Vector4 operator *(Vector4 left, float right) => (left.AsVector128() * right).AsVector4(); /// Multiplies the scalar value by the specified vector. /// The vector. @@ -253,12 +253,70 @@ public static Vector4 Clamp(Vector4 value1, Vector4 min, Vector4 max) return Min(Max(value1, min), max); } + /// Creates a new object whose four elements have the same value. + /// The value to assign to all four elements. + /// A new whose four elements have the same value. + [Intrinsic] + public static Vector4 Create(float value) => Vector128.Create(value).AsVector4(); + + /// Creates a new object from the specified object and a Z and a W component. + /// The vector to use for the X and Y components. + /// The Z component. + /// The W component. + /// A new from the specified object and a Z and a W component. + [Intrinsic] + public static Vector4 Create(Vector2 vector, float z, float w) + { + return vector.AsVector128Unsafe() + .WithElement(2, z) + .WithElement(3, w) + .AsVector4(); + } + + /// Constructs a new object from the specified object and a W component. + /// The vector to use for the X, Y, and Z components. + /// The W component. + /// A new from the specified object and a W component. + [Intrinsic] + public static Vector4 Create(Vector3 vector, float w) + { + return vector.AsVector128Unsafe() + .WithElement(3, w) + .AsVector4(); + } + + /// Creates a vector whose elements have the specified values. + /// The value to assign to the field. + /// The value to assign to the field. + /// The value to assign to the field. + /// The value to assign to the field. + /// A new whose elements have the specified values. + [Intrinsic] + public static Vector4 Create(float x, float y, float z, float w) => Vector128.Create(x, y, z, w).AsVector4(); + + /// Constructs a vector from the given . The span must contain at least 4 elements. + /// The span of elements to assign to the vector. + [Intrinsic] + public static Vector4 Create(ReadOnlySpan values) => Vector128.Create(values).AsVector4(); + + /// Creates a vector with initialized to the specified value and the remaining elements initialized to zero. + /// The value to assign to the field. + /// A new with initialized and the remaining elements initialized to zero. + [Intrinsic] + internal static Vector4 CreateScalar(float x) => Vector128.CreateScalar(x).AsVector4(); + + /// Creates a vector with initialized to the specified value and the remaining elements left uninitialized. + /// The value to assign to the field. + /// A new with initialized and the remaining elements left uninitialized. + [Intrinsic] + internal static Vector4 CreateScalarUnsafe(float x) => Vector128.CreateScalarUnsafe(x).AsVector4(); + /// Computes the Euclidean distance between the two given points. /// The first point. /// The second point. /// The distance. [Intrinsic] - public static float Distance(Vector4 value1, Vector4 value2) => MathF.Sqrt(DistanceSquared(value1, value2)); + public static float Distance(Vector4 value1, Vector4 value2) => float.Sqrt(DistanceSquared(value1, value2)); /// Returns the Euclidean distance squared between two specified points. /// The first point. @@ -412,7 +470,7 @@ public static Vector4 Transform(Vector2 value, Quaternion rotation) float yz2 = rotation.Y * z2; float zz2 = rotation.Z * z2; - return new Vector4( + return Create( value.X * (1.0f - yy2 - zz2) + value.Y * (xy2 - wz2), value.X * (xy2 + wz2) + value.Y * (1.0f - xx2 - zz2), value.X * (xz2 - wy2) + value.Y * (yz2 + wx2), @@ -459,7 +517,7 @@ public static Vector4 Transform(Vector3 value, Quaternion rotation) float yz2 = rotation.Y * z2; float zz2 = rotation.Z * z2; - return new Vector4( + return Create( value.X * (1.0f - yy2 - zz2) + value.Y * (xy2 - wz2) + value.Z * (xz2 + wy2), value.X * (xy2 + wz2) + value.Y * (1.0f - xx2 - zz2) + value.Z * (yz2 - wx2), value.X * (xz2 - wy2) + value.Y * (yz2 + wx2) + value.Z * (1.0f - xx2 - yy2), @@ -506,7 +564,7 @@ public static Vector4 Transform(Vector4 value, Quaternion rotation) float yz2 = rotation.Y * z2; float zz2 = rotation.Z * z2; - return new Vector4( + return Create( value.X * (1.0f - yy2 - zz2) + value.Y * (xy2 - wz2) + value.Z * (xz2 + wy2), value.X * (xy2 + wz2) + value.Y * (1.0f - xx2 - zz2) + value.Z * (yz2 - wx2), value.X * (xz2 - wy2) + value.Y * (yz2 + wx2) + value.Z * (1.0f - xx2 - yy2), @@ -564,7 +622,7 @@ public static Vector4 Transform(Vector4 value, Quaternion rotation) /// The vector's length. /// [Intrinsic] - public readonly float Length() => MathF.Sqrt(LengthSquared()); + public readonly float Length() => float.Sqrt(LengthSquared()); /// Returns the length of the vector squared. /// The vector's length squared. diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector_1.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector_1.cs index 4009387a4f796..1c47d03b513cd 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector_1.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Vector_1.cs @@ -133,7 +133,7 @@ public Vector(Span values) : this((ReadOnlySpan)values) public static Vector AllBitsSet { [Intrinsic] - get => new Vector(Scalar.AllBitsSet); + get => Vector.Create(Scalar.AllBitsSet); } #pragma warning disable CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type ('T') @@ -195,7 +195,7 @@ public static bool IsSupported public static Vector One { [Intrinsic] - get => new Vector(Scalar.One); + get => Vector.Create(Scalar.One); } /// Gets a new with all elements initialized to zero. @@ -506,7 +506,7 @@ public T this[int index] /// The scalar to multiply with . /// The product of and . [Intrinsic] - public static Vector operator *(Vector value, T factor) => value * new Vector(factor); + public static Vector operator *(Vector value, T factor) => value * Vector.Create(factor); /// Multiplies a vector by a scalar to compute their product. /// The scalar to multiply with . diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector128.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector128.cs index ea2446090399d..c92fa6a706b73 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector128.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector128.cs @@ -173,6 +173,7 @@ public static Vector128 As(this Vector128 vector) /// Reinterprets a as a new . /// The vector to reinterpret. /// reinterpreted as a new . + [Intrinsic] internal static Plane AsPlane(this Vector128 value) { #if MONO @@ -185,6 +186,7 @@ internal static Plane AsPlane(this Vector128 value) /// Reinterprets a as a new . /// The vector to reinterpret. /// reinterpreted as a new . + [Intrinsic] internal static Quaternion AsQuaternion(this Vector128 value) { #if MONO @@ -264,17 +266,17 @@ internal static Vector128 AsVector128(this Quaternion value) #endif } - /// Reinterprets a as a new . + /// Reinterprets a as a new with the new elements zeroed. /// The vector to reinterpret. - /// reinterpreted as a new . + /// reinterpreted as a new with the new elements zeroed. [Intrinsic] - public static Vector128 AsVector128(this Vector2 value) => new Vector4(value.X, value.Y, 0.0f, 0.0f).AsVector128(); + public static Vector128 AsVector128(this Vector2 value) => Vector4.Create(value, 0, 0).AsVector128(); - /// Reinterprets a as a new . + /// Reinterprets a as a new with the new elements zeroed. /// The vector to reinterpret. - /// reinterpreted as a new . + /// reinterpreted as a new with the new elements zeroed. [Intrinsic] - public static Vector128 AsVector128(this Vector3 value) => new Vector4(value.X, value.Y, value.Z, 0.0f).AsVector128(); + public static Vector128 AsVector128(this Vector3 value) => Vector4.Create(value, 0).AsVector128(); /// Reinterprets a as a new . /// The vector to reinterpret. @@ -305,6 +307,34 @@ public static Vector128 AsVector128(this Vector value) return Unsafe.ReadUnaligned>(ref address); } + /// Reinterprets a as a new , leaving the new elements undefined. + /// The vector to reinterpret. + /// reinterpreted as a new . + [Intrinsic] + public static Vector128 AsVector128Unsafe(this Vector2 value) + { + // This relies on us stripping the "init" flag from the ".locals" + // declaration to let the upper bits be uninitialized. + + Unsafe.SkipInit(out Vector128 result); + Unsafe.WriteUnaligned(ref Unsafe.As, byte>(ref result), value); + return result; + } + + /// Reinterprets a as a new , leaving the new elements undefined. + /// The vector to reinterpret. + /// reinterpreted as a new . + [Intrinsic] + public static Vector128 AsVector128Unsafe(this Vector3 value) + { + // This relies on us stripping the "init" flag from the ".locals" + // declaration to let the upper bits be uninitialized. + + Unsafe.SkipInit(out Vector128 result); + Unsafe.WriteUnaligned(ref Unsafe.As, byte>(ref result), value); + return result; + } + /// Reinterprets a as a new . /// The vector to reinterpret. /// reinterpreted as a new . @@ -1109,6 +1139,14 @@ public static unsafe Vector128 Create(ulong e0, ulong e1) ); } + /// Creates a new instance with the lower and upper 64-bits initialized to a specified value. + /// The type of the elements in the vector. + /// The value that the lower and upper 64-bits will be initialized to. + /// A new with the lower and upper 64-bits initialized to . + /// The type of () is not supported. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 Create(Vector64 value) => Create(value, value); + /// Creates a new instance from two instances. /// The type of the elements in the vector. /// The value that the lower 64-bits will be initialized to. diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector256.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector256.cs index 4779812149be0..73d021ffc4447 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector256.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector256.cs @@ -1046,6 +1046,22 @@ public static Vector256 Create(ulong e0, ulong e1, ulong e2, ulong e3) ); } + /// Creates a new instance with all 64-bit parts initialized to a specified value. + /// The type of the elements in the vector. + /// The value that the 64-bit parts will be initialized to. + /// A new with the 64-bit parts initialized to . + /// The type of () is not supported. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Create(Vector64 value) => Create(Vector128.Create(value, value)); + + /// Creates a new instance with the lower and upper 128-bits initialized to a specified value. + /// The type of the elements in the vector. + /// The value that the lower and upper 128-bits will be initialized to. + /// A new with the lower and upper 128-bits initialized to . + /// The type of () is not supported. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Create(Vector128 value) => Create(value, value); + /// Creates a new instance from two instances. /// The type of the elements in the vector. /// The value that the lower 128-bits will be initialized to. diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector512.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector512.cs index 4e534e8ac3947..7a80c3c393783 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector512.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector512.cs @@ -1108,6 +1108,30 @@ public static Vector512 Create(ulong e0, ulong e1, ulong e2, ulong e3, ul ); } + /// Creates a new instance with all 64-bit parts initialized to a specified value. + /// The type of the elements in the vector. + /// The value that the 64-bit parts will be initialized to. + /// A new with the 64-bit parts initialized to . + /// The type of () is not supported. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Create(Vector64 value) => Create(Vector128.Create(value, value)); + + /// Creates a new instance with all 128-bit parts initialized to a specified value. + /// The type of the elements in the vector. + /// The value that the 128-bit parts will be initialized to. + /// A new with the 128-bit parts initialized to . + /// The type of () is not supported. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Create(Vector128 value) => Create(Vector256.Create(value, value)); + + /// Creates a new instance with the lower and upper 256-bits initialized to a specified value. + /// The type of the elements in the vector. + /// The value that the lower and upper 256-bits will be initialized to. + /// A new with the lower and upper 256-bits initialized to . + /// The type of () is not supported. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Create(Vector256 value) => Create(value, value); + /// Creates a new instance from two instances. /// The type of the elements in the vector. /// The value that the lower 256-bits will be initialized to. diff --git a/src/libraries/System.Private.CoreLib/src/System/SearchValues/IndexOfAnyAsciiSearcher.cs b/src/libraries/System.Private.CoreLib/src/System/SearchValues/IndexOfAnyAsciiSearcher.cs index deccdddb8fece..42e0fcc2a65e7 100644 --- a/src/libraries/System.Private.CoreLib/src/System/SearchValues/IndexOfAnyAsciiSearcher.cs +++ b/src/libraries/System.Private.CoreLib/src/System/SearchValues/IndexOfAnyAsciiSearcher.cs @@ -17,7 +17,7 @@ internal static class IndexOfAnyAsciiSearcher { public struct AsciiState(Vector128 bitmap, BitVector256 lookup) { - public Vector256 Bitmap = Vector256.Create(bitmap, bitmap); + public Vector256 Bitmap = Vector256.Create(bitmap); public BitVector256 Lookup = lookup; public readonly AsciiState CreateInverse() => @@ -26,8 +26,8 @@ public readonly AsciiState CreateInverse() => public struct AnyByteState(Vector128 bitmap0, Vector128 bitmap1, BitVector256 lookup) { - public Vector256 Bitmap0 = Vector256.Create(bitmap0, bitmap0); - public Vector256 Bitmap1 = Vector256.Create(bitmap1, bitmap1); + public Vector256 Bitmap0 = Vector256.Create(bitmap0); + public Vector256 Bitmap1 = Vector256.Create(bitmap1); public BitVector256 Lookup = lookup; } @@ -146,7 +146,7 @@ private static unsafe bool TryIndexOfAny(ref short searchSpace, int se { // Only initializing the bitmap here is okay as we can only get here if the search space is long enough // and we support vectorization, so the IndexOfAnyVectorized implementation will never touch state.Lookup. - state.Bitmap = Vector256.Create(state.Bitmap._lower, state.Bitmap._lower); + state.Bitmap = Vector256.Create(state.Bitmap.GetLower()); index = (Ssse3.IsSupported || PackedSimd.IsSupported) && needleContainsZero ? IndexOfAny(ref searchSpace, searchSpaceLength, ref state) @@ -173,7 +173,7 @@ private static unsafe bool TryLastIndexOfAny(ref short searchSpace, in { // Only initializing the bitmap here is okay as we can only get here if the search space is long enough // and we support vectorization, so the LastIndexOfAnyVectorized implementation will never touch state.Lookup. - state.Bitmap = Vector256.Create(state.Bitmap._lower, state.Bitmap._lower); + state.Bitmap = Vector256.Create(state.Bitmap.GetLower()); index = (Ssse3.IsSupported || PackedSimd.IsSupported) && needleContainsZero ? LastIndexOfAny(ref searchSpace, searchSpaceLength, ref state) diff --git a/src/libraries/System.Private.CoreLib/src/System/SearchValues/ProbabilisticMap.cs b/src/libraries/System.Private.CoreLib/src/System/SearchValues/ProbabilisticMap.cs index 3c93ad6cd46d4..3211c0c320f83 100644 --- a/src/libraries/System.Private.CoreLib/src/System/SearchValues/ProbabilisticMap.cs +++ b/src/libraries/System.Private.CoreLib/src/System/SearchValues/ProbabilisticMap.cs @@ -411,7 +411,7 @@ private static int IndexOfAnyVectorizedAvx512(ref char searchS if (searchSpaceLength > 32) { - Vector512 charMap512 = Vector512.Create(charMap256, charMap256); + Vector512 charMap512 = Vector512.Create(charMap256); if (searchSpaceLength > 64) { @@ -499,8 +499,8 @@ private static int IndexOfAnyVectorized(ref char searchSpace, if (Avx2.IsSupported && searchSpaceLength >= 32) #pragma warning restore IntrinsicsInSystemPrivateCoreLibAttributeNotSpecificEnough { - Vector256 charMapLower256 = Vector256.Create(charMapLower, charMapLower); - Vector256 charMapUpper256 = Vector256.Create(charMapUpper, charMapUpper); + Vector256 charMapLower256 = Vector256.Create(charMapLower); + Vector256 charMapUpper256 = Vector256.Create(charMapUpper); ref char lastStartVectorAvx2 = ref Unsafe.Subtract(ref searchSpaceEnd, 32); @@ -586,7 +586,7 @@ private static int LastIndexOfAnyVectorizedAvx512(ref char sea if (searchSpaceLength > 32) { - Vector512 charMap512 = Vector512.Create(charMap256, charMap256); + Vector512 charMap512 = Vector512.Create(charMap256); if (searchSpaceLength > 64) { @@ -676,8 +676,8 @@ private static int LastIndexOfAnyVectorized(ref char searchSpa if (Avx2.IsSupported && searchSpaceLength >= 32) #pragma warning restore IntrinsicsInSystemPrivateCoreLibAttributeNotSpecificEnough { - Vector256 charMapLower256 = Vector256.Create(charMapLower, charMapLower); - Vector256 charMapUpper256 = Vector256.Create(charMapUpper, charMapUpper); + Vector256 charMapLower256 = Vector256.Create(charMapLower); + Vector256 charMapUpper256 = Vector256.Create(charMapUpper); ref char lastStartVectorAvx2 = ref Unsafe.Add(ref searchSpace, 32); diff --git a/src/libraries/System.Private.CoreLib/src/System/SearchValues/Strings/Helpers/TeddyBucketizer.cs b/src/libraries/System.Private.CoreLib/src/System/SearchValues/Strings/Helpers/TeddyBucketizer.cs index c277e6a8f72af..666889e4383e2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/SearchValues/Strings/Helpers/TeddyBucketizer.cs +++ b/src/libraries/System.Private.CoreLib/src/System/SearchValues/Strings/Helpers/TeddyBucketizer.cs @@ -35,7 +35,7 @@ public static (Vector512 Low, Vector512 High) GenerateNonBucketizedF high.SetElementUnsafe(highNibble, (byte)(high.GetElementUnsafe(highNibble) | bit)); } - return (DuplicateTo512(low), DuplicateTo512(high)); + return (Vector512.Create(low), Vector512.Create(high)); } // We can have up to 8 buckets, and their positions are encoded by 1 bit each. @@ -69,14 +69,7 @@ public static (Vector512 Low, Vector512 High) GenerateBucketizedFing } } - return (DuplicateTo512(low), DuplicateTo512(high)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector512 DuplicateTo512(Vector128 vector) - { - Vector256 vector256 = Vector256.Create(vector, vector); - return Vector512.Create(vector256, vector256); + return (Vector512.Create(low), Vector512.Create(high)); } public static string[][] Bucketize(ReadOnlySpan values, int bucketCount, int n) diff --git a/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.T.cs b/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.T.cs index 9ef07d114da67..ced611ec012a9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.T.cs +++ b/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.T.cs @@ -65,7 +65,7 @@ public static unsafe void Fill(ref T refData, nuint numElements, T value) } else if (Vector.Count == 32) { - vector = Vector256.Create(vec128, vec128).AsVector(); + vector = Vector256.Create(vec128).AsVector(); } else { 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 a8be59ed29889..44e3fe8e05fe4 100644 --- a/src/libraries/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.cs +++ b/src/libraries/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.cs @@ -34,6 +34,8 @@ public static partial class Vector128 public static System.Runtime.Intrinsics.Vector128 AsVector128(this System.Numerics.Vector2 value) { throw null; } public static System.Runtime.Intrinsics.Vector128 AsVector128(this System.Numerics.Vector3 value) { throw null; } public static System.Runtime.Intrinsics.Vector128 AsVector128(this System.Numerics.Vector4 value) { throw null; } + public static System.Runtime.Intrinsics.Vector128 AsVector128Unsafe(this System.Numerics.Vector2 value) { throw null; } + public static System.Runtime.Intrinsics.Vector128 AsVector128Unsafe(this System.Numerics.Vector3 value) { throw null; } public static System.Runtime.Intrinsics.Vector128 AsVector128(this System.Numerics.Vector value) { throw null; } public static System.Numerics.Vector2 AsVector2(this System.Runtime.Intrinsics.Vector128 value) { throw null; } public static System.Numerics.Vector3 AsVector3(this System.Runtime.Intrinsics.Vector128 value) { throw null; } @@ -152,6 +154,7 @@ public static void CopyTo(this System.Runtime.Intrinsics.Vector128 vector, public static System.Runtime.Intrinsics.Vector128 CreateScalarUnsafe(T value) { throw null; } public static System.Runtime.Intrinsics.Vector128 CreateSequence(T start, T step) { throw null; } public static System.Runtime.Intrinsics.Vector128 Create(System.ReadOnlySpan values) { throw null; } + public static System.Runtime.Intrinsics.Vector128 Create(System.Runtime.Intrinsics.Vector64 value) { throw null; } public static System.Runtime.Intrinsics.Vector128 Create(System.Runtime.Intrinsics.Vector64 lower, System.Runtime.Intrinsics.Vector64 upper) { throw null; } public static System.Runtime.Intrinsics.Vector128 Create(T value) { throw null; } public static System.Runtime.Intrinsics.Vector128 Create(T[] values) { throw null; } @@ -497,6 +500,8 @@ public static void CopyTo(this System.Runtime.Intrinsics.Vector256 vector, public static System.Runtime.Intrinsics.Vector256 CreateScalarUnsafe(T value) { throw null; } public static System.Runtime.Intrinsics.Vector256 CreateSequence(T start, T step) { throw null; } public static System.Runtime.Intrinsics.Vector256 Create(System.ReadOnlySpan values) { throw null; } + public static System.Runtime.Intrinsics.Vector256 Create(System.Runtime.Intrinsics.Vector64 value) { throw null; } + public static System.Runtime.Intrinsics.Vector256 Create(System.Runtime.Intrinsics.Vector128 value) { throw null; } public static System.Runtime.Intrinsics.Vector256 Create(System.Runtime.Intrinsics.Vector128 lower, System.Runtime.Intrinsics.Vector128 upper) { throw null; } public static System.Runtime.Intrinsics.Vector256 Create(T value) { throw null; } public static System.Runtime.Intrinsics.Vector256 Create(T[] values) { throw null; } @@ -842,6 +847,9 @@ public static void CopyTo(this System.Runtime.Intrinsics.Vector512 vector, public static System.Runtime.Intrinsics.Vector512 CreateScalarUnsafe(T value) { throw null; } public static System.Runtime.Intrinsics.Vector512 CreateSequence(T start, T step) { throw null; } public static System.Runtime.Intrinsics.Vector512 Create(System.ReadOnlySpan values) { throw null; } + public static System.Runtime.Intrinsics.Vector512 Create(System.Runtime.Intrinsics.Vector64 value) { throw null; } + public static System.Runtime.Intrinsics.Vector512 Create(System.Runtime.Intrinsics.Vector128 value) { throw null; } + public static System.Runtime.Intrinsics.Vector512 Create(System.Runtime.Intrinsics.Vector256 value) { throw null; } public static System.Runtime.Intrinsics.Vector512 Create(System.Runtime.Intrinsics.Vector256 lower, System.Runtime.Intrinsics.Vector256 upper) { throw null; } public static System.Runtime.Intrinsics.Vector512 Create(T value) { throw null; } public static System.Runtime.Intrinsics.Vector512 Create(T[] values) { throw null; } diff --git a/src/libraries/System.Text.Json/ref/System.Text.Json.cs b/src/libraries/System.Text.Json/ref/System.Text.Json.cs index 2dbe6cad3e5eb..6905b8d1e5dd5 100644 --- a/src/libraries/System.Text.Json/ref/System.Text.Json.cs +++ b/src/libraries/System.Text.Json/ref/System.Text.Json.cs @@ -888,6 +888,29 @@ internal JsonValue() { } public abstract bool TryGetValue([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out T? value); } } +namespace System.Text.Json.Schema +{ + public static partial class JsonSchemaExporter + { + public static System.Text.Json.Nodes.JsonNode GetJsonSchemaAsNode(this System.Text.Json.JsonSerializerOptions options, System.Type type, System.Text.Json.Schema.JsonSchemaExporterOptions? exporterOptions = null) { throw null; } + public static System.Text.Json.Nodes.JsonNode GetJsonSchemaAsNode(this System.Text.Json.Serialization.Metadata.JsonTypeInfo typeInfo, System.Text.Json.Schema.JsonSchemaExporterOptions? exporterOptions = null) { throw null; } + } + public readonly partial struct JsonSchemaExporterContext + { + private readonly object _dummy; + private readonly int _dummyPrimitive; + public System.Text.Json.Serialization.Metadata.JsonPropertyInfo? PropertyInfo { get { throw null; } } + public System.ReadOnlySpan Path { get { throw null; } } + public System.Text.Json.Serialization.Metadata.JsonTypeInfo TypeInfo { get { throw null; } } + } + public sealed partial class JsonSchemaExporterOptions + { + public JsonSchemaExporterOptions() { } + public static System.Text.Json.Schema.JsonSchemaExporterOptions Default { get { throw null; } } + public System.Func? TransformSchemaNode { get { throw null; } init { } } + public bool TreatNullObliviousAsNonNullable { get { throw null; } init { } } + } +} namespace System.Text.Json.Serialization { public partial interface IJsonOnDeserialized diff --git a/src/libraries/System.Text.Json/src/Resources/Strings.resx b/src/libraries/System.Text.Json/src/Resources/Strings.resx index 19ebcbd47137a..4ff132432c214 100644 --- a/src/libraries/System.Text.Json/src/Resources/Strings.resx +++ b/src/libraries/System.Text.Json/src/Resources/Strings.resx @@ -752,4 +752,10 @@ NullabilityInfoContext is not supported in the current application because 'System.Reflection.NullabilityInfoContext.IsSupported' is set to false. Set the MSBuild Property 'NullabilityInfoContextSupport' to true in order to enable it. + + JSON schema generation is not supported for contracts using ReferenceHandler.Preserve. + + + The depth of the generated JSON schema exceeds the JsonSerializerOptions.MaxDepth setting. + diff --git a/src/libraries/System.Text.Json/src/System.Text.Json.csproj b/src/libraries/System.Text.Json/src/System.Text.Json.csproj index f7bbf8f386f1e..ac93199558164 100644 --- a/src/libraries/System.Text.Json/src/System.Text.Json.csproj +++ b/src/libraries/System.Text.Json/src/System.Text.Json.csproj @@ -100,6 +100,11 @@ The System.Text.Json library is built-in as part of the shared framework in .NET + + + + + diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Schema/JsonSchema.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Schema/JsonSchema.cs new file mode 100644 index 0000000000000..d261b374b390a --- /dev/null +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Schema/JsonSchema.cs @@ -0,0 +1,324 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Diagnostics; +using System.Text.Json.Nodes; + +namespace System.Text.Json.Schema +{ + internal sealed class JsonSchema + { + internal const string RefPropertyName = "$ref"; + internal const string CommentPropertyName = "$comment"; + internal const string TypePropertyName = "type"; + internal const string FormatPropertyName = "format"; + internal const string PatternPropertyName = "pattern"; + internal const string PropertiesPropertyName = "properties"; + internal const string RequiredPropertyName = "required"; + internal const string ItemsPropertyName = "items"; + internal const string AdditionalPropertiesPropertyName = "additionalProperties"; + internal const string EnumPropertyName = "enum"; + internal const string NotPropertyName = "not"; + internal const string AnyOfPropertyName = "anyOf"; + internal const string ConstPropertyName = "const"; + internal const string DefaultPropertyName = "default"; + internal const string MinLengthPropertyName = "minLength"; + internal const string MaxLengthPropertyName = "maxLength"; + + public static JsonSchema False { get; } = new(false); + public static JsonSchema True { get; } = new(true); + + public JsonSchema() { } + private JsonSchema(bool trueOrFalse) { _trueOrFalse = trueOrFalse; } + + public bool IsTrue => _trueOrFalse is true; + public bool IsFalse => _trueOrFalse is false; + private readonly bool? _trueOrFalse; + + public string? Ref { get => _ref; set { VerifyMutable(); _ref = value; } } + private string? _ref; + + public string? Comment { get => _comment; set { VerifyMutable(); _comment = value; } } + private string? _comment; + + public JsonSchemaType Type { get => _type; set { VerifyMutable(); _type = value; } } + private JsonSchemaType _type = JsonSchemaType.Any; + + public string? Format { get => _format; set { VerifyMutable(); _format = value; } } + private string? _format; + + public string? Pattern { get => _pattern; set { VerifyMutable(); _pattern = value; } } + private string? _pattern; + + public JsonNode? Constant { get => _constant; set { VerifyMutable(); _constant = value; } } + private JsonNode? _constant; + + public List>? Properties { get => _properties; set { VerifyMutable(); _properties = value; } } + private List>? _properties; + + public List? Required { get => _required; set { VerifyMutable(); _required = value; } } + private List? _required; + + public JsonSchema? Items { get => _items; set { VerifyMutable(); _items = value; } } + private JsonSchema? _items; + + public JsonSchema? AdditionalProperties { get => _additionalProperties; set { VerifyMutable(); _additionalProperties = value; } } + private JsonSchema? _additionalProperties; + + public JsonArray? Enum { get => _enum; set { VerifyMutable(); _enum = value; } } + private JsonArray? _enum; + + public JsonSchema? Not { get => _not; set { VerifyMutable(); _not = value; } } + private JsonSchema? _not; + + public List? AnyOf { get => _anyOf; set { VerifyMutable(); _anyOf = value; } } + private List? _anyOf; + + public bool HasDefaultValue { get => _hasDefaultValue; set { VerifyMutable(); _hasDefaultValue = value; } } + private bool _hasDefaultValue; + + public JsonNode? DefaultValue { get => _defaultValue; set { VerifyMutable(); _defaultValue = value; } } + private JsonNode? _defaultValue; + + public int? MinLength { get => _minLength; set { VerifyMutable(); _minLength = value; } } + private int? _minLength; + + public int? MaxLength { get => _maxLength; set { VerifyMutable(); _maxLength = value; } } + private int? _maxLength; + + public JsonSchemaExporterContext? ExporterContext { get; set; } + + public int KeywordCount + { + get + { + if (_trueOrFalse != null) + { + return 0; + } + + int count = 0; + Count(Ref != null); + Count(Comment != null); + Count(Type != JsonSchemaType.Any); + Count(Format != null); + Count(Pattern != null); + Count(Constant != null); + Count(Properties != null); + Count(Required != null); + Count(Items != null); + Count(AdditionalProperties != null); + Count(Enum != null); + Count(Not != null); + Count(AnyOf != null); + Count(HasDefaultValue); + Count(MinLength != null); + Count(MaxLength != null); + + return count; + + void Count(bool isKeywordSpecified) + { + count += isKeywordSpecified ? 1 : 0; + } + } + } + + public void MakeNullable() + { + if (_trueOrFalse != null) + { + return; + } + + if (Type != JsonSchemaType.Any) + { + Type |= JsonSchemaType.Null; + } + } + + public JsonNode ToJsonNode(JsonSchemaExporterOptions options) + { + if (_trueOrFalse is { } boolSchema) + { + return CompleteSchema((JsonNode)boolSchema); + } + + var objSchema = new JsonObject(); + + if (Ref != null) + { + objSchema.Add(RefPropertyName, Ref); + } + + if (Comment != null) + { + objSchema.Add(CommentPropertyName, Comment); + } + + if (MapSchemaType(Type) is JsonNode type) + { + objSchema.Add(TypePropertyName, type); + } + + if (Format != null) + { + objSchema.Add(FormatPropertyName, Format); + } + + if (Pattern != null) + { + objSchema.Add(PatternPropertyName, Pattern); + } + + if (Constant != null) + { + objSchema.Add(ConstPropertyName, Constant); + } + + if (Properties != null) + { + var properties = new JsonObject(); + foreach (KeyValuePair property in Properties) + { + properties.Add(property.Key, property.Value.ToJsonNode(options)); + } + + objSchema.Add(PropertiesPropertyName, properties); + } + + if (Required != null) + { + var requiredArray = new JsonArray(); + foreach (string requiredProperty in Required) + { + requiredArray.Add((JsonNode)requiredProperty); + } + + objSchema.Add(RequiredPropertyName, requiredArray); + } + + if (Items != null) + { + objSchema.Add(ItemsPropertyName, Items.ToJsonNode(options)); + } + + if (AdditionalProperties != null) + { + objSchema.Add(AdditionalPropertiesPropertyName, AdditionalProperties.ToJsonNode(options)); + } + + if (Enum != null) + { + objSchema.Add(EnumPropertyName, Enum); + } + + if (Not != null) + { + objSchema.Add(NotPropertyName, Not.ToJsonNode(options)); + } + + if (AnyOf != null) + { + JsonArray anyOfArray = []; + foreach (JsonSchema schema in AnyOf) + { + anyOfArray.Add(schema.ToJsonNode(options)); + } + + objSchema.Add(AnyOfPropertyName, anyOfArray); + } + + if (HasDefaultValue) + { + objSchema.Add(DefaultPropertyName, DefaultValue); + } + + if (MinLength is int minLength) + { + objSchema.Add(MinLengthPropertyName, (JsonNode)minLength); + } + + if (MaxLength is int maxLength) + { + objSchema.Add(MaxLengthPropertyName, (JsonNode)maxLength); + } + + return CompleteSchema(objSchema); + + JsonNode CompleteSchema(JsonNode schema) + { + if (ExporterContext is { } context) + { + Debug.Assert(options.TransformSchemaNode != null, "context should only be populated if a callback is present."); + // Apply any user-defined transformations to the schema. + return options.TransformSchemaNode(context, schema); + } + + return schema; + } + } + + private static ReadOnlySpan s_schemaValues => + [ + // NB the order of these values influences order of types in the rendered schema + JsonSchemaType.String, + JsonSchemaType.Integer, + JsonSchemaType.Number, + JsonSchemaType.Boolean, + JsonSchemaType.Array, + JsonSchemaType.Object, + JsonSchemaType.Null, + ]; + + private void VerifyMutable() + { + Debug.Assert(_trueOrFalse is null, "Schema is not mutable"); + if (_trueOrFalse is not null) + { + Throw(); + static void Throw() => throw new InvalidOperationException(); + } + } + + public static JsonNode? MapSchemaType(JsonSchemaType schemaType) + { + if (schemaType is JsonSchemaType.Any) + { + return null; + } + + if (ToIdentifier(schemaType) is string identifier) + { + return identifier; + } + + var array = new JsonArray(); + foreach (JsonSchemaType type in s_schemaValues) + { + if ((schemaType & type) != 0) + { + array.Add((JsonNode)ToIdentifier(type)!); + } + } + + return array; + + static string? ToIdentifier(JsonSchemaType schemaType) + { + return schemaType switch + { + JsonSchemaType.Null => "null", + JsonSchemaType.Boolean => "boolean", + JsonSchemaType.Integer => "integer", + JsonSchemaType.Number => "number", + JsonSchemaType.String => "string", + JsonSchemaType.Array => "array", + JsonSchemaType.Object => "object", + _ => null, + }; + } + } + } +} diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Schema/JsonSchemaExporter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Schema/JsonSchemaExporter.cs new file mode 100644 index 0000000000000..a4f9cd3594a47 --- /dev/null +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Schema/JsonSchemaExporter.cs @@ -0,0 +1,510 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; + +namespace System.Text.Json.Schema +{ + /// + /// Functionality for exporting JSON schema from serialization contracts defined in . + /// + public static class JsonSchemaExporter + { + /// + /// Gets the JSON schema for as a document. + /// + /// The options declaring the contract for the type. + /// The type for which to resolve a schema. + /// The options object governing the export operation. + /// A JSON object containing the schema for . + public static JsonNode GetJsonSchemaAsNode(this JsonSerializerOptions options, Type type, JsonSchemaExporterOptions? exporterOptions = null) + { + if (options is null) + { + ThrowHelper.ThrowArgumentNullException(nameof(options)); + } + + if (type is null) + { + ThrowHelper.ThrowArgumentNullException(nameof(type)); + } + + ValidateOptions(options); + JsonTypeInfo typeInfo = options.GetTypeInfoInternal(type); + return typeInfo.GetJsonSchemaAsNode(exporterOptions); + } + + /// + /// Gets the JSON schema for as a document. + /// + /// The contract from which to resolve the JSON schema. + /// The options object governing the export operation. + /// A JSON object containing the schema for . + public static JsonNode GetJsonSchemaAsNode(this JsonTypeInfo typeInfo, JsonSchemaExporterOptions? exporterOptions = null) + { + if (typeInfo is null) + { + ThrowHelper.ThrowArgumentNullException(nameof(typeInfo)); + } + + ValidateOptions(typeInfo.Options); + exporterOptions ??= JsonSchemaExporterOptions.Default; + + typeInfo.EnsureConfigured(); + GenerationState state = new(typeInfo.Options, exporterOptions); + JsonSchema schema = MapJsonSchemaCore(ref state, typeInfo); + return schema.ToJsonNode(exporterOptions); + } + + private static JsonSchema MapJsonSchemaCore( + ref GenerationState state, + JsonTypeInfo typeInfo, + JsonPropertyInfo? propertyInfo = null, + JsonConverter? customConverter = null, + JsonNumberHandling? customNumberHandling = null, + Type? parentPolymorphicType = null, + bool parentPolymorphicTypeContainsTypesWithoutDiscriminator = false, + bool parentPolymorphicTypeIsNonNullable = false, + KeyValuePair? typeDiscriminator = null, + bool cacheResult = true) + { + Debug.Assert(typeInfo.IsConfigured); + + if (cacheResult && state.TryPushType(typeInfo, propertyInfo, out string? existingJsonPointer)) + { + // We're generating the schema of a recursive type, return a reference pointing to the outermost schema. + return CompleteSchema(ref state, new JsonSchema { Ref = existingJsonPointer }); + } + + JsonConverter effectiveConverter = customConverter ?? typeInfo.Converter; + JsonNumberHandling effectiveNumberHandling = customNumberHandling ?? typeInfo.NumberHandling ?? typeInfo.Options.NumberHandling; + if (effectiveConverter.GetSchema(effectiveNumberHandling) is { } schema) + { + // A schema has been provided by the converter. + return CompleteSchema(ref state, schema); + } + + if (parentPolymorphicType is null && typeInfo.PolymorphismOptions is { DerivedTypes.Count: > 0 } polyOptions) + { + // This is the base type of a polymorphic type hierarchy. The schema for this type + // will include an "anyOf" property with the schemas for all derived types. + string typeDiscriminatorKey = polyOptions.TypeDiscriminatorPropertyName; + List derivedTypes = new(polyOptions.DerivedTypes); + + if (!typeInfo.Type.IsAbstract && !IsPolymorphicTypeThatSpecifiesItselfAsDerivedType(typeInfo)) + { + // For non-abstract base types that haven't been explicitly configured, + // add a trivial schema to the derived types since we should support it. + derivedTypes.Add(new JsonDerivedType(typeInfo.Type)); + } + + bool containsTypesWithoutDiscriminator = derivedTypes.Exists(static derivedTypes => derivedTypes.TypeDiscriminator is null); + JsonSchemaType schemaType = JsonSchemaType.Any; + List? anyOf = new(derivedTypes.Count); + + state.PushSchemaNode(JsonSchema.AnyOfPropertyName); + + foreach (JsonDerivedType derivedType in derivedTypes) + { + Debug.Assert(derivedType.TypeDiscriminator is null or int or string); + + KeyValuePair? derivedTypeDiscriminator = null; + if (derivedType.TypeDiscriminator is { } discriminatorValue) + { + JsonNode discriminatorNode = discriminatorValue switch + { + string stringId => (JsonNode)stringId, + _ => (JsonNode)(int)discriminatorValue, + }; + + JsonSchema discriminatorSchema = new() { Constant = discriminatorNode }; + derivedTypeDiscriminator = new(typeDiscriminatorKey, discriminatorSchema); + } + + JsonTypeInfo derivedTypeInfo = typeInfo.Options.GetTypeInfoInternal(derivedType.DerivedType); + + state.PushSchemaNode(anyOf.Count.ToString(CultureInfo.InvariantCulture)); + JsonSchema derivedSchema = MapJsonSchemaCore( + ref state, + derivedTypeInfo, + parentPolymorphicType: typeInfo.Type, + typeDiscriminator: derivedTypeDiscriminator, + parentPolymorphicTypeContainsTypesWithoutDiscriminator: containsTypesWithoutDiscriminator, + parentPolymorphicTypeIsNonNullable: propertyInfo is { IsGetNullable: false, IsSetNullable: false }, + cacheResult: false); + + state.PopSchemaNode(); + + // Determine if all derived schemas have the same type. + if (anyOf.Count == 0) + { + schemaType = derivedSchema.Type; + } + else if (schemaType != derivedSchema.Type) + { + schemaType = JsonSchemaType.Any; + } + + anyOf.Add(derivedSchema); + } + + state.PopSchemaNode(); + + if (schemaType is not JsonSchemaType.Any) + { + // If all derived types have the same schema type, we can simplify the schema + // by moving the type keyword to the base schema and removing it from the derived schemas. + foreach (JsonSchema derivedSchema in anyOf) + { + derivedSchema.Type = JsonSchemaType.Any; + + if (derivedSchema.KeywordCount == 0) + { + // if removing the type results in an empty schema, + // remove the anyOf array entirely since it's always true. + anyOf = null; + break; + } + } + } + + return CompleteSchema(ref state, new() + { + Type = schemaType, + AnyOf = anyOf, + // If all derived types have a discriminator, we can require it in the base schema. + Required = containsTypesWithoutDiscriminator ? null : [typeDiscriminatorKey] + }); + } + + if (effectiveConverter.NullableElementConverter is { } elementConverter) + { + JsonTypeInfo elementTypeInfo = typeInfo.Options.GetTypeInfo(elementConverter.Type!); + schema = MapJsonSchemaCore(ref state, elementTypeInfo, customConverter: elementConverter, cacheResult: false); + + if (schema.Enum != null) + { + Debug.Assert(elementTypeInfo.Type.IsEnum, "The enum keyword should only be populated by schemas for enum types."); + schema.Enum.Add(null); // Append null to the enum array. + } + + return CompleteSchema(ref state, schema); + } + + switch (typeInfo.Kind) + { + case JsonTypeInfoKind.Object: + List>? properties = null; + List? required = null; + JsonSchema? additionalProperties = null; + + if (typeInfo.UnmappedMemberHandling is JsonUnmappedMemberHandling.Disallow) + { + additionalProperties = JsonSchema.False; + } + + if (typeDiscriminator is { } typeDiscriminatorPair) + { + (properties ??= []).Add(typeDiscriminatorPair); + if (parentPolymorphicTypeContainsTypesWithoutDiscriminator) + { + // Require the discriminator here since it's not common to all derived types. + (required ??= []).Add(typeDiscriminatorPair.Key); + } + } + + state.PushSchemaNode(JsonSchema.PropertiesPropertyName); + foreach (JsonPropertyInfo property in typeInfo.Properties) + { + if (property is { Get: null, Set: null } or { IsExtensionData: true }) + { + continue; // Skip JsonIgnored properties and extension data + } + + state.PushSchemaNode(property.Name); + JsonSchema propertySchema = MapJsonSchemaCore( + ref state, + property.JsonTypeInfo, + propertyInfo: property, + customConverter: property.EffectiveConverter, + customNumberHandling: property.EffectiveNumberHandling); + + state.PopSchemaNode(); + + if (property.AssociatedParameter is { HasDefaultValue: true } parameterInfo) + { + propertySchema.DefaultValue = JsonSerializer.SerializeToNode(parameterInfo.DefaultValue, property.JsonTypeInfo); + propertySchema.HasDefaultValue = true; + } + + (properties ??= []).Add(new(property.Name, propertySchema)); + + // Mark as required if either the property is required or the associated constructor parameter is non-optional. + // While the latter implies the former in cases where the JsonSerializerOptions.RespectRequiredConstructorParameters + // setting has been enabled, for the case of the schema exporter we always mark non-optional constructor parameters as required. + if (property is { IsRequired: true } or { AssociatedParameter.IsRequiredParameter: true }) + { + (required ??= []).Add(property.Name); + } + } + + state.PopSchemaNode(); + return CompleteSchema(ref state, new() + { + Type = JsonSchemaType.Object, + Properties = properties, + Required = required, + AdditionalProperties = additionalProperties, + }); + + case JsonTypeInfoKind.Enumerable: + Debug.Assert(typeInfo.ElementTypeInfo != null); + + if (typeDiscriminator is null) + { + state.PushSchemaNode(JsonSchema.ItemsPropertyName); + JsonSchema items = MapJsonSchemaCore(ref state, typeInfo.ElementTypeInfo, customNumberHandling: effectiveNumberHandling); + state.PopSchemaNode(); + + return CompleteSchema(ref state, new() + { + Type = JsonSchemaType.Array, + Items = items.IsTrue ? null : items, + }); + } + else + { + // Polymorphic enumerable types are represented using a wrapping object: + // { "$type" : "discriminator", "$values" : [element1, element2, ...] } + // Which corresponds to the schema + // { "properties" : { "$type" : { "const" : "discriminator" }, "$values" : { "type" : "array", "items" : { ... } } } } + const string ValuesKeyword = JsonSerializer.ValuesPropertyName; + + state.PushSchemaNode(JsonSchema.PropertiesPropertyName); + state.PushSchemaNode(ValuesKeyword); + state.PushSchemaNode(JsonSchema.ItemsPropertyName); + + JsonSchema items = MapJsonSchemaCore(ref state, typeInfo.ElementTypeInfo, customNumberHandling: effectiveNumberHandling); + + state.PopSchemaNode(); + state.PopSchemaNode(); + state.PopSchemaNode(); + + return CompleteSchema(ref state, new() + { + Type = JsonSchemaType.Object, + Properties = + [ + typeDiscriminator.Value, + new(ValuesKeyword, + new JsonSchema() + { + Type = JsonSchemaType.Array, + Items = items.IsTrue ? null : items, + }), + ], + Required = parentPolymorphicTypeContainsTypesWithoutDiscriminator ? [typeDiscriminator.Value.Key] : null, + }); + } + + case JsonTypeInfoKind.Dictionary: + Debug.Assert(typeInfo.ElementTypeInfo != null); + + List>? dictProps = null; + List? dictRequired = null; + + if (typeDiscriminator is { } dictDiscriminator) + { + dictProps = [dictDiscriminator]; + if (parentPolymorphicTypeContainsTypesWithoutDiscriminator) + { + // Require the discriminator here since it's not common to all derived types. + dictRequired = [dictDiscriminator.Key]; + } + } + + state.PushSchemaNode(JsonSchema.AdditionalPropertiesPropertyName); + JsonSchema valueSchema = MapJsonSchemaCore(ref state, typeInfo.ElementTypeInfo, customNumberHandling: effectiveNumberHandling); + state.PopSchemaNode(); + + return CompleteSchema(ref state, new() + { + Type = JsonSchemaType.Object, + Properties = dictProps, + Required = dictRequired, + AdditionalProperties = valueSchema.IsTrue ? null : valueSchema, + }); + + default: + Debug.Assert(typeInfo.Kind is JsonTypeInfoKind.None); + // Return a `true` schema for types with user-defined converters. + return CompleteSchema(ref state, JsonSchema.True); + } + + JsonSchema CompleteSchema(ref GenerationState state, JsonSchema schema) + { + if (schema.Ref is null) + { + // A schema is marked as nullable if either + // 1. We have a schema for a property where either the getter or setter are marked as nullable. + // 2. We have a schema for a reference type, unless we're explicitly treating null-oblivious types as non-nullable. + bool isNullableSchema = propertyInfo != null + ? propertyInfo.IsGetNullable || propertyInfo.IsSetNullable + : typeInfo.CanBeNull && !parentPolymorphicTypeIsNonNullable && !state.ExporterOptions.TreatNullObliviousAsNonNullable; + + if (isNullableSchema) + { + schema.MakeNullable(); + } + + if (cacheResult) + { + state.PopGeneratedType(); + } + } + + if (state.ExporterOptions.TransformSchemaNode != null) + { + // Prime the schema for invocation by the JsonNode transformer. + schema.ExporterContext = state.CreateContext(typeInfo, propertyInfo); + } + + return schema; + } + } + + private static void ValidateOptions(JsonSerializerOptions options) + { + if (options.ReferenceHandler == ReferenceHandler.Preserve) + { + ThrowHelper.ThrowNotSupportedException_JsonSchemaExporterDoesNotSupportReferenceHandlerPreserve(); + } + + options.MakeReadOnly(); + } + + private static bool IsPolymorphicTypeThatSpecifiesItselfAsDerivedType(JsonTypeInfo typeInfo) + { + Debug.Assert(typeInfo.PolymorphismOptions is not null); + + foreach (JsonDerivedType derivedType in typeInfo.PolymorphismOptions.DerivedTypes) + { + if (derivedType.DerivedType == typeInfo.Type) + { + return true; + } + } + + return false; + } + + private readonly ref struct GenerationState(JsonSerializerOptions options, JsonSchemaExporterOptions exporterOptions) + { + private readonly List _currentPath = []; + private readonly List<(JsonTypeInfo typeInfo, JsonPropertyInfo? propertyInfo, int depth)> _generationStack = []; + + public int CurrentDepth => _currentPath.Count; + public JsonSerializerOptions Options { get; } = options; + public JsonSchemaExporterOptions ExporterOptions { get; } = exporterOptions; + + public void PushSchemaNode(string nodeId) + { + if (CurrentDepth == Options.EffectiveMaxDepth) + { + ThrowHelper.ThrowInvalidOperationException_JsonSchemaExporterDepthTooLarge(); + } + + _currentPath.Add(nodeId); + } + + public void PopSchemaNode() + { + Debug.Assert(CurrentDepth > 0); + _currentPath.RemoveAt(_currentPath.Count - 1); + } + + /// + /// Pushes the current type/property to the generation stack or returns a JSON pointer if the type is recursive. + /// + public bool TryPushType(JsonTypeInfo typeInfo, JsonPropertyInfo? propertyInfo, [NotNullWhen(true)] out string? existingJsonPointer) + { + foreach ((JsonTypeInfo otherTypeInfo, JsonPropertyInfo? otherPropertyInfo, int depth) in _generationStack) + { + if (typeInfo == otherTypeInfo && propertyInfo == otherPropertyInfo) + { + existingJsonPointer = FormatJsonPointer(_currentPath, depth); + return true; + } + } + + _generationStack.Add((typeInfo, propertyInfo, CurrentDepth)); + existingJsonPointer = null; + return false; + } + + public void PopGeneratedType() + { + Debug.Assert(_generationStack.Count > 0); + _generationStack.RemoveAt(_generationStack.Count - 1); + } + + public JsonSchemaExporterContext CreateContext(JsonTypeInfo typeInfo, JsonPropertyInfo? propertyInfo) + { + return new JsonSchemaExporterContext(typeInfo, propertyInfo, _currentPath.ToArray()); + } + + private static string FormatJsonPointer(List currentPathList, int depth) + { + Debug.Assert(0 <= depth && depth < currentPathList.Count); + + if (depth == 0) + { + return "#"; + } + + using ValueStringBuilder sb = new(initialCapacity: depth * 10); + sb.Append('#'); + + for (int i = 0; i < depth; i++) + { + ReadOnlySpan segment = currentPathList[i].AsSpan(); + sb.Append('/'); + + do + { + // Per RFC 6901 the characters '~' and '/' must be escaped. + int pos = segment.IndexOfAny('~', '/'); + if (pos < 0) + { + sb.Append(segment); + break; + } + + sb.Append(segment.Slice(0, pos)); + + if (segment[pos] == '~') + { + sb.Append("~0"); + } + else + { + Debug.Assert(segment[pos] == '/'); + sb.Append("~1"); + } + + segment = segment.Slice(pos + 1); + } + while (!segment.IsEmpty); + } + + return sb.ToString(); + } + } + } +} diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Schema/JsonSchemaExporterContext.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Schema/JsonSchemaExporterContext.cs new file mode 100644 index 0000000000000..f8143e347656c --- /dev/null +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Schema/JsonSchemaExporterContext.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text.Json.Serialization.Metadata; + +namespace System.Text.Json.Schema +{ + /// + /// Defines the context for the generated JSON schema for a particular node in a type graph. + /// + public readonly struct JsonSchemaExporterContext + { + private readonly string[] _path; + + internal JsonSchemaExporterContext(JsonTypeInfo typeInfo, JsonPropertyInfo? propertyInfo, string[] path) + { + TypeInfo = typeInfo; + PropertyInfo = propertyInfo; + _path = path; + } + + /// + /// The for the type being processed. + /// + public JsonTypeInfo TypeInfo { get; } + + /// + /// The if the schema is being generated for a property. + /// + public JsonPropertyInfo? PropertyInfo { get; } + + /// + /// The path to the current node in the generated JSON schema. + /// + public ReadOnlySpan Path => _path; + } +} diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Schema/JsonSchemaExporterOptions.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Schema/JsonSchemaExporterOptions.cs new file mode 100644 index 0000000000000..1567ad9aded1e --- /dev/null +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Schema/JsonSchemaExporterOptions.cs @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text.Json.Nodes; + +namespace System.Text.Json.Schema +{ + /// + /// Configures the behavior of the APIs. + /// + public sealed class JsonSchemaExporterOptions + { + /// + /// Gets the default configuration object used by . + /// + public static JsonSchemaExporterOptions Default { get; } = new(); + + /// + /// Determines whether non-nullable schemas should be generated for null oblivious reference types. + /// + /// + /// Defaults to . Due to restrictions in the run-time representation of nullable reference types + /// most occurences are null oblivious and are treated as nullable by the serializer. A notable exception to that rule + /// are nullability annotations of field, property and constructor parameters which are represented in the contract metadata. + /// + public bool TreatNullObliviousAsNonNullable { get; init; } + + /// + /// Defines a callback that is invoked for every schema that is generated within the type graph. + /// + public Func? TransformSchemaNode { get; init; } + } +} diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Schema/JsonSchemaType.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Schema/JsonSchemaType.cs new file mode 100644 index 0000000000000..f0b6865633226 --- /dev/null +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Schema/JsonSchemaType.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Text.Json.Schema +{ + [Flags] + internal enum JsonSchemaType + { + Any = 0, + Null = 1, + Boolean = 2, + Integer = 4, + Number = 8, + String = 16, + Object = 32, + Array = 64, + } +} diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/CastingConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/CastingConverter.cs index fabf97436bd26..a49efe33765b0 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/CastingConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/CastingConverter.cs @@ -3,7 +3,9 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Text.Json.Nodes; using System.Text.Json.Reflection; +using System.Text.Json.Schema; namespace System.Text.Json.Serialization.Converters { @@ -72,5 +74,8 @@ internal override T ReadNumberWithCustomHandling(ref Utf8JsonReader reader, Json internal override void WriteNumberWithCustomHandling(Utf8JsonWriter writer, T? value, JsonNumberHandling handling) => _sourceConverter.WriteNumberWithCustomHandlingAsObject(writer, value, handling); + + internal override JsonSchema? GetSchema(JsonNumberHandling numberHandling) + => _sourceConverter.GetSchema(numberHandling); } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonMetadataServicesConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonMetadataServicesConverter.cs index 7808ca49443de..6cb9f90741fa1 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonMetadataServicesConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonMetadataServicesConverter.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Text.Json.Nodes; +using System.Text.Json.Schema; using System.Text.Json.Serialization.Metadata; namespace System.Text.Json.Serialization.Converters @@ -65,5 +67,8 @@ internal override bool OnTryWrite(Utf8JsonWriter writer, T value, JsonSerializer internal override void ConfigureJsonTypeInfo(JsonTypeInfo jsonTypeInfo, JsonSerializerOptions options) => Converter.ConfigureJsonTypeInfo(jsonTypeInfo, options); + + internal override JsonSchema? GetSchema(JsonNumberHandling numberHandling) + => Converter.GetSchema(numberHandling); } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Node/JsonArrayConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Node/JsonArrayConverter.cs index 0410d22df1f38..f30821770a182 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Node/JsonArrayConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Node/JsonArrayConverter.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Text.Json.Nodes; +using System.Text.Json.Schema; namespace System.Text.Json.Serialization.Converters { @@ -38,5 +39,7 @@ public static JsonArray ReadList(ref Utf8JsonReader reader, JsonNodeOptions? opt JsonElement jElement = JsonElement.ParseValue(ref reader); return new JsonArray(jElement, options); } + + internal override JsonSchema? GetSchema(JsonNumberHandling _) => new() { Type = JsonSchemaType.Array }; } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Node/JsonNodeConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Node/JsonNodeConverter.cs index 89caeaa08f50e..3728ab26ad6f3 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Node/JsonNodeConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Node/JsonNodeConverter.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Text.Json.Nodes; +using System.Text.Json.Schema; using System.Text.Json.Serialization.Metadata; namespace System.Text.Json.Serialization.Converters @@ -78,5 +79,7 @@ public override void Write(Utf8JsonWriter writer, JsonNode? value, JsonSerialize return node; } + + internal override JsonSchema? GetSchema(JsonNumberHandling _) => JsonSchema.True; } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Node/JsonObjectConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Node/JsonObjectConverter.cs index 4455e92916eb0..44f81126d7a65 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Node/JsonObjectConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Node/JsonObjectConverter.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Text.Json.Nodes; +using System.Text.Json.Schema; using System.Text.Json.Serialization.Metadata; namespace System.Text.Json.Serialization.Converters @@ -64,5 +65,7 @@ public static JsonObject ReadObject(ref Utf8JsonReader reader, JsonNodeOptions? JsonObject jObject = new JsonObject(jElement, options); return jObject; } + + internal override JsonSchema? GetSchema(JsonNumberHandling _) => new() { Type = JsonSchemaType.Object }; } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Node/JsonValueConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Node/JsonValueConverter.cs index 6b09c360987dd..51253ddd70c82 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Node/JsonValueConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Node/JsonValueConverter.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Text.Json.Nodes; +using System.Text.Json.Schema; using System.Text.Json.Serialization.Metadata; namespace System.Text.Json.Serialization.Converters @@ -30,5 +31,7 @@ public override void Write(Utf8JsonWriter writer, JsonValue? value, JsonSerializ JsonValue value = new JsonValuePrimitive(element, JsonMetadataServices.JsonElementConverter, options.GetNodeOptions()); return value; } + + internal override JsonSchema? GetSchema(JsonNumberHandling _) => JsonSchema.True; } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectConverter.cs index 4511e150b37ee..bda21c258fbe0 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectConverter.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Text.Json.Nodes; +using System.Text.Json.Schema; using System.Text.Json.Serialization.Metadata; namespace System.Text.Json.Serialization.Converters @@ -145,5 +146,7 @@ internal override bool OnTryRead(ref Utf8JsonReader reader, Type typeToConvert, return true; } + + internal override JsonSchema? GetSchema(JsonNumberHandling _) => JsonSchema.True; } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/BooleanConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/BooleanConverter.cs index 340a2105db8a8..1d5dd93c3529c 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/BooleanConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/BooleanConverter.cs @@ -3,6 +3,8 @@ using System.Buffers.Text; using System.Diagnostics; +using System.Text.Json.Nodes; +using System.Text.Json.Schema; namespace System.Text.Json.Serialization.Converters { @@ -35,5 +37,7 @@ internal override void WriteAsPropertyNameCore(Utf8JsonWriter writer, bool value { writer.WritePropertyName(value); } + + internal override JsonSchema? GetSchema(JsonNumberHandling _) => new() { Type = JsonSchemaType.Boolean }; } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/ByteArrayConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/ByteArrayConverter.cs index 9b97a6d93e216..3f0f871c81941 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/ByteArrayConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/ByteArrayConverter.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Text.Json.Schema; + namespace System.Text.Json.Serialization.Converters { internal sealed class ByteArrayConverter : JsonConverter @@ -26,5 +28,7 @@ public override void Write(Utf8JsonWriter writer, byte[]? value, JsonSerializerO writer.WriteBase64StringValue(value); } } + + internal override JsonSchema? GetSchema(JsonNumberHandling _) => new() { Type = JsonSchemaType.String }; } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/ByteConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/ByteConverter.cs index 9df14e2060976..8e03344ab7e43 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/ByteConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/ByteConverter.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Text.Json.Nodes; +using System.Text.Json.Schema; namespace System.Text.Json.Serialization.Converters { @@ -54,5 +56,8 @@ internal override void WriteNumberWithCustomHandling(Utf8JsonWriter writer, byte writer.WriteNumberValue(value); } } + + internal override JsonSchema? GetSchema(JsonNumberHandling numberHandling) => + GetSchemaForNumericType(JsonSchemaType.Integer, numberHandling); } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/CharConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/CharConverter.cs index c102a0879e1ac..3bdca6204c6be 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/CharConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/CharConverter.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; -using System.Runtime.InteropServices; +using System.Text.Json.Schema; namespace System.Text.Json.Serialization.Converters { @@ -60,5 +60,8 @@ internal override void WriteAsPropertyNameCore(Utf8JsonWriter writer, char value #endif ); } + + internal override JsonSchema? GetSchema(JsonNumberHandling _) => + new() { Type = JsonSchemaType.String, MinLength = 1, MaxLength = 1 }; } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DateOnlyConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DateOnlyConverter.cs index 9917f47fcf8db..b780780fa4b89 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DateOnlyConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DateOnlyConverter.cs @@ -1,9 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Buffers; using System.Diagnostics; using System.Globalization; +using System.Text.Json.Schema; namespace System.Text.Json.Serialization.Converters { @@ -78,5 +78,7 @@ internal override void WriteAsPropertyNameCore(Utf8JsonWriter writer, DateOnly v Debug.Assert(formattedSuccessfully && charsWritten == FormatLength); writer.WritePropertyName(buffer); } + + internal override JsonSchema? GetSchema(JsonNumberHandling _) => new() { Type = JsonSchemaType.String, Format = "date" }; } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DateTimeConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DateTimeConverter.cs index 204d39551cea2..f1423c2167218 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DateTimeConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DateTimeConverter.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Text.Json.Schema; namespace System.Text.Json.Serialization.Converters { @@ -27,5 +28,7 @@ internal override void WriteAsPropertyNameCore(Utf8JsonWriter writer, DateTime v { writer.WritePropertyName(value); } + + internal override JsonSchema? GetSchema(JsonNumberHandling _) => new JsonSchema { Type = JsonSchemaType.String, Format = "date-time" }; } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DateTimeOffsetConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DateTimeOffsetConverter.cs index 6cfeaf4c38edc..f0a2f527102c0 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DateTimeOffsetConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DateTimeOffsetConverter.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Text.Json.Schema; namespace System.Text.Json.Serialization.Converters { @@ -27,5 +28,7 @@ internal override void WriteAsPropertyNameCore(Utf8JsonWriter writer, DateTimeOf { writer.WritePropertyName(value); } + + internal override JsonSchema? GetSchema(JsonNumberHandling _) => new JsonSchema { Type = JsonSchemaType.String, Format = "date-time" }; } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DecimalConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DecimalConverter.cs index 7b3ff9fade01d..fffbed44d0b94 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DecimalConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DecimalConverter.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Text.Json.Schema; namespace System.Text.Json.Serialization.Converters { @@ -55,5 +56,8 @@ internal override void WriteNumberWithCustomHandling(Utf8JsonWriter writer, deci writer.WriteNumberValue(value); } } + + internal override JsonSchema? GetSchema(JsonNumberHandling numberHandling) => + GetSchemaForNumericType(JsonSchemaType.Number, numberHandling); } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DoubleConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DoubleConverter.cs index 0c6a6c4e26cc6..52cb8f4c0de4e 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DoubleConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/DoubleConverter.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Text.Json.Nodes; +using System.Text.Json.Schema; namespace System.Text.Json.Serialization.Converters { @@ -65,5 +67,8 @@ internal override void WriteNumberWithCustomHandling(Utf8JsonWriter writer, doub writer.WriteNumberValue(value); } } + + internal override JsonSchema? GetSchema(JsonNumberHandling numberHandling) => + GetSchemaForNumericType(JsonSchemaType.Number, numberHandling, isIeeeFloatingPoint: true); } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/EnumConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/EnumConverter.cs index 5f9ddf1eee1b6..6ec71f05ddb38 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/EnumConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/EnumConverter.cs @@ -7,6 +7,8 @@ using System.Globalization; using System.Runtime.CompilerServices; using System.Text.Encodings.Web; +using System.Text.Json.Nodes; +using System.Text.Json.Schema; namespace System.Text.Json.Serialization.Converters { @@ -17,6 +19,7 @@ internal sealed class EnumConverter : JsonPrimitiveConverter // Odd type codes are conveniently signed types (for enum backing types). private static readonly bool s_isSignedEnum = ((int)s_enumTypeCode % 2) == 1; + private static readonly bool s_isFlagsEnum = typeof(T).IsDefined(typeof(FlagsAttribute), inherit: false); private const string ValueSeparator = ", "; @@ -403,6 +406,39 @@ private bool TryParseEnumCore( return success; } + internal override JsonSchema? GetSchema(JsonNumberHandling numberHandling) + { + if ((_converterOptions & EnumConverterOptions.AllowStrings) != 0) + { + // This explicitly ignores the integer component in converters configured as AllowNumbers | AllowStrings + // which is the default for JsonStringEnumConverter. This sacrifices some precision in the schema for simplicity. + + if (s_isFlagsEnum) + { + // Do not report enum values in case of flags. + return new() { Type = JsonSchemaType.String }; + } + + JsonNamingPolicy? namingPolicy = _namingPolicy; + JsonArray enumValues = []; +#if NET + string[] names = Enum.GetNames(); +#else + string[] names = Enum.GetNames(Type); +#endif + + for (int i = 0; i < names.Length; i++) + { + JsonNode name = FormatJsonName(names[i], namingPolicy); + enumValues.Add(name); + } + + return new() { Enum = enumValues }; + } + + return new() { Type = JsonSchemaType.Integer }; + } + private T ReadEnumUsingNamingPolicy(string? enumString) { if (_namingPolicy == null) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/GuidConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/GuidConverter.cs index c808bc6d730ba..cace588ea9be2 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/GuidConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/GuidConverter.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Text.Json.Nodes; +using System.Text.Json.Schema; namespace System.Text.Json.Serialization.Converters { @@ -27,5 +29,8 @@ internal override void WriteAsPropertyNameCore(Utf8JsonWriter writer, Guid value { writer.WritePropertyName(value); } + + internal override JsonSchema? GetSchema(JsonNumberHandling numberHandling) => + new() { Type = JsonSchemaType.String, Format = "uuid" }; } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/HalfConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/HalfConverter.cs index c2bee78a5fa67..458b00df093e5 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/HalfConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/HalfConverter.cs @@ -4,6 +4,8 @@ using System.Buffers; using System.Diagnostics; using System.Globalization; +using System.Text.Json.Nodes; +using System.Text.Json.Schema; namespace System.Text.Json.Serialization.Converters { @@ -143,6 +145,9 @@ internal override void WriteNumberWithCustomHandling(Utf8JsonWriter writer, Half } } + internal override JsonSchema? GetSchema(JsonNumberHandling numberHandling) => + GetSchemaForNumericType(JsonSchemaType.Number, numberHandling, isIeeeFloatingPoint: true); + private static bool TryGetFloatingPointConstant(ref Utf8JsonReader reader, out Half value) { Span buffer = stackalloc byte[MaxFormatLength]; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/Int128Converter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/Int128Converter.cs index da415abbb785c..3dd7d9d863cd3 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/Int128Converter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/Int128Converter.cs @@ -4,6 +4,8 @@ using System.Buffers; using System.Diagnostics; using System.Globalization; +using System.Text.Json.Nodes; +using System.Text.Json.Schema; namespace System.Text.Json.Serialization.Converters { @@ -128,6 +130,9 @@ internal override void WriteNumberWithCustomHandling(Utf8JsonWriter writer, Int1 } } + internal override JsonSchema? GetSchema(JsonNumberHandling numberHandling) => + GetSchemaForNumericType(JsonSchemaType.Integer, numberHandling); + // Int128.TryParse(ROS) is not available on .NET 7, only Int128.TryParse(ROS). private static bool TryParse( #if NET8_0_OR_GREATER diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/Int16Converter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/Int16Converter.cs index 73da887f71c92..cd5e897f1a996 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/Int16Converter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/Int16Converter.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Text.Json.Nodes; +using System.Text.Json.Schema; namespace System.Text.Json.Serialization.Converters { @@ -57,5 +59,8 @@ internal override void WriteNumberWithCustomHandling(Utf8JsonWriter writer, shor writer.WriteNumberValue((long)value); } } + + internal override JsonSchema? GetSchema(JsonNumberHandling numberHandling) => + GetSchemaForNumericType(JsonSchemaType.Integer, numberHandling); } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/Int32Converter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/Int32Converter.cs index a243b0d65f26e..fc29970367f94 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/Int32Converter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/Int32Converter.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Text.Json.Nodes; +using System.Text.Json.Schema; namespace System.Text.Json.Serialization.Converters { @@ -57,5 +59,8 @@ internal override void WriteNumberWithCustomHandling(Utf8JsonWriter writer, int writer.WriteNumberValue((long)value); } } + + internal override JsonSchema? GetSchema(JsonNumberHandling numberHandling) => + GetSchemaForNumericType(JsonSchemaType.Integer, numberHandling); } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/Int64Converter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/Int64Converter.cs index 7987daf7c6916..7eae46d7ba4bd 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/Int64Converter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/Int64Converter.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Text.Json.Nodes; +using System.Text.Json.Schema; namespace System.Text.Json.Serialization.Converters { @@ -55,5 +57,8 @@ internal override void WriteNumberWithCustomHandling(Utf8JsonWriter writer, long writer.WriteNumberValue(value); } } + + internal override JsonSchema? GetSchema(JsonNumberHandling numberHandling) => + GetSchemaForNumericType(JsonSchemaType.Integer, numberHandling); } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/JsonDocumentConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/JsonDocumentConverter.cs index 3b70388658ec3..fd964f09800f9 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/JsonDocumentConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/JsonDocumentConverter.cs @@ -1,6 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Text.Json.Schema; +using System.Text.Json.Nodes; + namespace System.Text.Json.Serialization.Converters { internal sealed class JsonDocumentConverter : JsonConverter @@ -22,5 +25,7 @@ public override void Write(Utf8JsonWriter writer, JsonDocument? value, JsonSeria value.WriteTo(writer); } + + internal override JsonSchema? GetSchema(JsonNumberHandling _) => JsonSchema.True; } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/JsonElementConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/JsonElementConverter.cs index f371fe920a23c..79e8ef4bf280d 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/JsonElementConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/JsonElementConverter.cs @@ -1,6 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Text.Json.Schema; +using System.Text.Json.Nodes; + namespace System.Text.Json.Serialization.Converters { internal sealed class JsonElementConverter : JsonConverter @@ -14,5 +17,7 @@ public override void Write(Utf8JsonWriter writer, JsonElement value, JsonSeriali { value.WriteTo(writer); } + + internal override JsonSchema? GetSchema(JsonNumberHandling _) => JsonSchema.True; } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/JsonPrimitiveConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/JsonPrimitiveConverter.cs index 5d1ca58b76ae1..d7330ce675423 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/JsonPrimitiveConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/JsonPrimitiveConverter.cs @@ -1,7 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Text.Json.Nodes; +using System.Text.Json.Schema; namespace System.Text.Json.Serialization.Converters { @@ -29,5 +32,40 @@ public sealed override T ReadAsPropertyName(ref Utf8JsonReader reader, Type type return ReadAsPropertyNameCore(ref reader, typeToConvert, options); } + + private protected static JsonSchema GetSchemaForNumericType(JsonSchemaType schemaType, JsonNumberHandling numberHandling, bool isIeeeFloatingPoint = false) + { + Debug.Assert(schemaType is JsonSchemaType.Integer or JsonSchemaType.Number); + Debug.Assert(!isIeeeFloatingPoint || schemaType is JsonSchemaType.Number); +#if NET + Debug.Assert(isIeeeFloatingPoint == (typeof(T) == typeof(double) || typeof(T) == typeof(float) || typeof(T) == typeof(Half))); +#endif + string? pattern = null; + + if ((numberHandling & (JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)) != 0) + { + pattern = schemaType is JsonSchemaType.Integer + ? @"^-?(?:0|[1-9]\d*)$" + : isIeeeFloatingPoint + ? @"^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$" + : @"^-?(?:0|[1-9]\d*)(?:\.\d+)?$"; + + schemaType |= JsonSchemaType.String; + } + + if (isIeeeFloatingPoint && (numberHandling & JsonNumberHandling.AllowNamedFloatingPointLiterals) != 0) + { + return new JsonSchema + { + AnyOf = + [ + new JsonSchema { Type = schemaType, Pattern = pattern }, + new JsonSchema { Enum = [(JsonNode)"NaN", (JsonNode)"Infinity", (JsonNode)"-Infinity"] }, + ] + }; + } + + return new JsonSchema { Type = schemaType, Pattern = pattern }; + } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/MemoryByteConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/MemoryByteConverter.cs index 0efa7e71a13ac..3d719375cba4b 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/MemoryByteConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/MemoryByteConverter.cs @@ -1,6 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Text.Json.Nodes; +using System.Text.Json.Schema; + namespace System.Text.Json.Serialization.Converters { internal sealed class MemoryByteConverter : JsonConverter> @@ -16,5 +19,7 @@ public override void Write(Utf8JsonWriter writer, Memory value, JsonSerial { writer.WriteBase64StringValue(value.Span); } + + internal override JsonSchema? GetSchema(JsonNumberHandling _) => new() { Type = JsonSchemaType.String }; } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/ReadOnlyMemoryByteConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/ReadOnlyMemoryByteConverter.cs index 48eddafefba20..5139026fc4cae 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/ReadOnlyMemoryByteConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/ReadOnlyMemoryByteConverter.cs @@ -1,6 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Text.Json.Nodes; +using System.Text.Json.Schema; + namespace System.Text.Json.Serialization.Converters { internal sealed class ReadOnlyMemoryByteConverter : JsonConverter> @@ -16,5 +19,7 @@ public override void Write(Utf8JsonWriter writer, ReadOnlyMemory value, Js { writer.WriteBase64StringValue(value.Span); } + + internal override JsonSchema? GetSchema(JsonNumberHandling _) => new() { Type = JsonSchemaType.String }; } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/SByteConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/SByteConverter.cs index f302fd6a04565..0a9b91708d34d 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/SByteConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/SByteConverter.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Text.Json.Nodes; +using System.Text.Json.Schema; namespace System.Text.Json.Serialization.Converters { @@ -55,5 +57,8 @@ internal override void WriteNumberWithCustomHandling(Utf8JsonWriter writer, sbyt writer.WriteNumberValue(value); } } + + internal override JsonSchema? GetSchema(JsonNumberHandling numberHandling) => + GetSchemaForNumericType(JsonSchemaType.Integer, numberHandling); } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/SingleConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/SingleConverter.cs index 4c6b2c5a80674..4d681a7b65709 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/SingleConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/SingleConverter.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Text.Json.Nodes; +using System.Text.Json.Schema; namespace System.Text.Json.Serialization.Converters { @@ -66,5 +68,8 @@ internal override void WriteNumberWithCustomHandling(Utf8JsonWriter writer, floa writer.WriteNumberValue(value); } } + + internal override JsonSchema? GetSchema(JsonNumberHandling numberHandling) => + GetSchemaForNumericType(JsonSchemaType.Number, numberHandling, isIeeeFloatingPoint: true); } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/StringConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/StringConverter.cs index 7a2ce41367c63..8a290109e7e36 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/StringConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/StringConverter.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Text.Json.Nodes; +using System.Text.Json.Schema; namespace System.Text.Json.Serialization.Converters { @@ -50,5 +52,7 @@ internal override void WriteAsPropertyNameCore(Utf8JsonWriter writer, string val writer.WritePropertyName(value); } + + internal override JsonSchema? GetSchema(JsonNumberHandling _) => new() { Type = JsonSchemaType.String }; } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeOnlyConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeOnlyConverter.cs index 29ee02876ac62..bd2c40c8a07ca 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeOnlyConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeOnlyConverter.cs @@ -3,6 +3,8 @@ using System.Buffers.Text; using System.Diagnostics; +using System.Text.Json.Nodes; +using System.Text.Json.Schema; namespace System.Text.Json.Serialization.Converters { @@ -93,5 +95,7 @@ internal override void WriteAsPropertyNameCore(Utf8JsonWriter writer, TimeOnly v writer.WritePropertyName(output.Slice(0, bytesWritten)); } + + internal override JsonSchema? GetSchema(JsonNumberHandling _) => new() { Type = JsonSchemaType.String, Format = "time" }; } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeSpanConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeSpanConverter.cs index 08ef78d3f6973..e5a23f36d1000 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeSpanConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/TimeSpanConverter.cs @@ -3,6 +3,8 @@ using System.Buffers.Text; using System.Diagnostics; +using System.Text.Json.Nodes; +using System.Text.Json.Schema; namespace System.Text.Json.Serialization.Converters { @@ -91,5 +93,12 @@ internal override void WriteAsPropertyNameCore(Utf8JsonWriter writer, TimeSpan v writer.WritePropertyName(output.Slice(0, bytesWritten)); } + + internal override JsonSchema? GetSchema(JsonNumberHandling _) => new() + { + Type = JsonSchemaType.String, + Comment = "Represents a System.TimeSpan value.", + Pattern = @"^-?(\d+\.)?\d{2}:\d{2}:\d{2}(\.\d{1,7})?$" + }; } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UInt128Converter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UInt128Converter.cs index d1dd1fc05fc6e..711ba307045cb 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UInt128Converter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UInt128Converter.cs @@ -4,6 +4,8 @@ using System.Buffers; using System.Diagnostics; using System.Globalization; +using System.Text.Json.Nodes; +using System.Text.Json.Schema; namespace System.Text.Json.Serialization.Converters { @@ -127,6 +129,9 @@ internal override void WriteNumberWithCustomHandling(Utf8JsonWriter writer, UInt } } + internal override JsonSchema? GetSchema(JsonNumberHandling numberHandling) => + GetSchemaForNumericType(JsonSchemaType.Integer, numberHandling); + // UInt128.TryParse(ROS) is not available on .NET 7, only UInt128.TryParse(ROS). private static bool TryParse( #if NET8_0_OR_GREATER diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UInt16Converter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UInt16Converter.cs index 16c717cb7d440..f459f0434350d 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UInt16Converter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UInt16Converter.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Text.Json.Nodes; +using System.Text.Json.Schema; namespace System.Text.Json.Serialization.Converters { @@ -57,5 +59,8 @@ internal override void WriteNumberWithCustomHandling(Utf8JsonWriter writer, usho writer.WriteNumberValue((long)value); } } + + internal override JsonSchema? GetSchema(JsonNumberHandling numberHandling) => + GetSchemaForNumericType(JsonSchemaType.Integer, numberHandling); } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UInt32Converter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UInt32Converter.cs index 9a8dccb9b80cf..b368e84638a54 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UInt32Converter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UInt32Converter.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Text.Json.Nodes; +using System.Text.Json.Schema; namespace System.Text.Json.Serialization.Converters { @@ -57,5 +59,8 @@ internal override void WriteNumberWithCustomHandling(Utf8JsonWriter writer, uint writer.WriteNumberValue((ulong)value); } } + + internal override JsonSchema? GetSchema(JsonNumberHandling numberHandling) => + GetSchemaForNumericType(JsonSchemaType.Integer, numberHandling); } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UInt64Converter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UInt64Converter.cs index 7b3290a478758..c42044f7e024c 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UInt64Converter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UInt64Converter.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Text.Json.Nodes; +using System.Text.Json.Schema; namespace System.Text.Json.Serialization.Converters { @@ -55,5 +57,8 @@ internal override void WriteNumberWithCustomHandling(Utf8JsonWriter writer, ulon writer.WriteNumberValue(value); } } + + internal override JsonSchema? GetSchema(JsonNumberHandling numberHandling) => + GetSchemaForNumericType(JsonSchemaType.Integer, numberHandling); } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UnsupportedTypeConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UnsupportedTypeConverter.cs index f0d37569c8b33..f80af7c92b6c7 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UnsupportedTypeConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UnsupportedTypeConverter.cs @@ -1,6 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Text.Json.Schema; +using System.Text.Json.Nodes; + namespace System.Text.Json.Serialization.Converters { internal sealed class UnsupportedTypeConverter : JsonConverter @@ -16,5 +19,8 @@ public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerial public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) => throw new NotSupportedException(ErrorMessage); + + internal override JsonSchema? GetSchema(JsonNumberHandling _) => + new JsonSchema { Comment = "Unsupported .NET type", Not = JsonSchema.True }; } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UriConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UriConverter.cs index 312ec7a6877a9..5c6bceef66bfc 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UriConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/UriConverter.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Text.Json.Nodes; +using System.Text.Json.Schema; namespace System.Text.Json.Serialization.Converters { @@ -50,5 +52,8 @@ internal override void WriteAsPropertyNameCore(Utf8JsonWriter writer, Uri value, writer.WritePropertyName(value.OriginalString); } + + internal override JsonSchema? GetSchema(JsonNumberHandling _) => + new() { Type = JsonSchemaType.String, Format = "uri" }; } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/VersionConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/VersionConverter.cs index 85e0d8f67398a..fffa210475cd6 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/VersionConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/VersionConverter.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Text.Json.Nodes; +using System.Text.Json.Schema; namespace System.Text.Json.Serialization.Converters { @@ -123,5 +125,13 @@ internal override void WriteAsPropertyNameCore(Utf8JsonWriter writer, Version va writer.WritePropertyName(value.ToString()); #endif } + + internal override JsonSchema? GetSchema(JsonNumberHandling _) => + new() + { + Type = JsonSchemaType.String, + Comment = "Represents a version string.", + Pattern = @"^\d+(\.\d+){1,3}$", + }; } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverter.cs index 8c61188630276..fafa21ad0485d 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverter.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Reflection; +using System.Text.Json.Schema; using System.Text.Json.Serialization.Converters; using System.Text.Json.Serialization.Metadata; @@ -200,6 +201,10 @@ internal static bool ShouldFlush(ref WriteStack state, Utf8JsonWriter writer) internal abstract void WriteAsPropertyNameCoreAsObject(Utf8JsonWriter writer, object? value, JsonSerializerOptions options, bool isWritingExtensionDataProperty); internal abstract void WriteNumberWithCustomHandlingAsObject(Utf8JsonWriter writer, object? value, JsonNumberHandling handling); + /// + /// Gets a schema from the type being converted + /// + internal virtual JsonSchema? GetSchema(JsonNumberHandling numberHandling) => null; // Whether a type (ConverterStrategy.Object) is deserialized using a parameterized constructor. internal virtual bool ConstructorIsParameterized { get; } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonParameterInfo.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonParameterInfo.cs index 4db70c6694e22..15702db934c73 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonParameterInfo.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonParameterInfo.cs @@ -123,9 +123,6 @@ public ICustomAttributeProvider? AttributeProvider internal JsonNumberHandling? NumberHandling => MatchingProperty.EffectiveNumberHandling; internal JsonTypeInfo JsonTypeInfo => MatchingProperty.JsonTypeInfo; internal bool ShouldDeserialize => !MatchingProperty.IsIgnored; - internal bool IsRequiredParameter => - Options.RespectRequiredConstructorParameters && - !HasDefaultValue && - !IsMemberInitializer; + internal bool IsRequiredParameter => !HasDefaultValue && !IsMemberInitializer; } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs index cb7975b38c99c..73f6633ff7b63 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs @@ -448,7 +448,9 @@ internal void Configure() if (IsRequired) { - if (!CanDeserialize && AssociatedParameter?.IsRequiredParameter != true) + if (!CanDeserialize && + !(AssociatedParameter?.IsRequiredParameter is true && + Options.RespectRequiredConstructorParameters)) { ThrowHelper.ThrowInvalidOperationException_JsonPropertyRequiredAndNotDeserializable(this); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfoOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfoOfT.cs index a0219eafa1622..1c62158aa85b7 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfoOfT.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfoOfT.cs @@ -120,8 +120,12 @@ internal override void AddJsonParameterInfo(JsonParameterInfoValues parameterInf AssociatedParameter = new JsonParameterInfo(parameterInfoValues, this); // Overwrite the nullability annotation of property setter with the parameter. _isSetNullable = parameterInfoValues.IsNullable; - // If the property has been associated with a non-optional parameter, mark it as required. - _isRequired |= AssociatedParameter.IsRequiredParameter; + + if (Options.RespectRequiredConstructorParameters) + { + // If the property has been associated with a non-optional parameter, mark it as required. + _isRequired |= AssociatedParameter.IsRequiredParameter; + } } internal new JsonConverter EffectiveConverter diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs index 41431ee40e3e1..147de20f3c5a6 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs @@ -360,6 +360,7 @@ public JsonPolymorphismOptions? PolymorphismOptions internal bool PropertyMetadataSerializationNotSupported { get; set; } internal bool IsNullable => Converter.NullableElementConverter is not null; + internal bool CanBeNull => PropertyInfoForTypeInfo.PropertyTypeCanBeNull; [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void ValidateCanBeUsedForPropertyMetadataSerialization() diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs index 5b16af8de3eec..b5e2f99947e32 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs @@ -936,5 +936,17 @@ public static void ThrowInvalidOperationException_PipeWriterDoesNotImplementUnfl { throw new InvalidOperationException(SR.Format(SR.PipeWriter_DoesNotImplementUnflushedBytes, pipeWriter.GetType().Name)); } + + [DoesNotReturn] + public static void ThrowNotSupportedException_JsonSchemaExporterDoesNotSupportReferenceHandlerPreserve() + { + throw new NotSupportedException(SR.JsonSchemaExporter_ReferenceHandlerPreserve_NotSupported); + } + + [DoesNotReturn] + public static void ThrowInvalidOperationException_JsonSchemaExporterDepthTooLarge() + { + throw new InvalidOperationException(SR.JsonSchemaExporter_DepthTooLarge); + } } } diff --git a/src/libraries/System.Text.Json/tests/Common/JsonSchemaExporterTests.TestTypes.cs b/src/libraries/System.Text.Json/tests/Common/JsonSchemaExporterTests.TestTypes.cs new file mode 100644 index 0000000000000..13de2b5a0f85a --- /dev/null +++ b/src/libraries/System.Text.Json/tests/Common/JsonSchemaExporterTests.TestTypes.cs @@ -0,0 +1,1477 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Reflection; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; +using System.Text.Json.Serialization.Tests; + +namespace System.Text.Json.Schema.Tests +{ + public abstract partial class JsonSchemaExporterTests : SerializerTests + { + public static IEnumerable GetTestData() => + from testCase in GetTestDataCore() + select new object[] { testCase }; + + public static IEnumerable GetTestDataUsingAllValues() => + from testCase in GetTestDataCore() + from expandedTestCase in testCase.GetTestDataForAllValues() + select new object[] { expandedTestCase }; + + public static IEnumerable GetTestDataCore() + { + // Primitives and built-in types + yield return new TestData( + Value: new(), + AdditionalValues: [null, 42, false, 3.14, 3.14M, new int[] { 1, 2, 3 }, new SimpleRecord(1, "str", false, 3.14)], + ExpectedJsonSchema: "true"); + + yield return new TestData(true, ExpectedJsonSchema: """{"type":"boolean"}"""); + yield return new TestData(42, ExpectedJsonSchema: """{"type":"integer"}"""); + yield return new TestData(42, ExpectedJsonSchema: """{"type":"integer"}"""); + yield return new TestData(42, ExpectedJsonSchema: """{"type":"integer"}"""); + yield return new TestData(42, ExpectedJsonSchema: """{"type":"integer"}"""); + yield return new TestData(42, ExpectedJsonSchema: """{"type":"integer"}"""); + yield return new TestData(42, ExpectedJsonSchema: """{"type":"integer"}"""); + yield return new TestData(42, ExpectedJsonSchema: """{"type":"integer"}"""); + yield return new TestData(42, ExpectedJsonSchema: """{"type":"integer"}"""); + yield return new TestData(1.2f, ExpectedJsonSchema: """{"type":"number"}"""); + yield return new TestData(3.14159d, ExpectedJsonSchema: """{"type":"number"}"""); + yield return new TestData(3.14159M, ExpectedJsonSchema: """{"type":"number"}"""); +#if NET + yield return new TestData(42, ExpectedJsonSchema: """{"type":"integer"}"""); + yield return new TestData(42, ExpectedJsonSchema: """{"type":"integer"}"""); + yield return new TestData((Half)3.141, ExpectedJsonSchema: """{"type":"number"}"""); +#endif + yield return new TestData("I am a string", ExpectedJsonSchema: """{"type":["string","null"]}"""); + yield return new TestData('c', ExpectedJsonSchema: """{"type":"string", "minLength":1, "maxLength":1 }"""); + yield return new TestData( + Value: [1, 2, 3], + AdditionalValues: [[]], + ExpectedJsonSchema: """{"type":["string","null"]}"""); + + yield return new TestData>(new byte[] { 1, 2, 3 }, ExpectedJsonSchema: """{"type":"string"}"""); + yield return new TestData>(new byte[] { 1, 2, 3 }, ExpectedJsonSchema: """{"type":"string"}"""); + yield return new TestData( + Value: new(2024, 06, 06, 21, 39, 42, DateTimeKind.Utc), + ExpectedJsonSchema: """{"type":"string","format":"date-time"}"""); + + yield return new TestData( + Value: new(new DateTime(2021, 1, 1), TimeSpan.Zero), + AdditionalValues: [DateTimeOffset.MinValue, DateTimeOffset.MaxValue], + ExpectedJsonSchema: """{"type":"string","format": "date-time"}"""); + + yield return new TestData( + Value: new(hours: 5, minutes: 13, seconds: 3), + AdditionalValues: [TimeSpan.MinValue, TimeSpan.MaxValue], + ExpectedJsonSchema: """{"$comment": "Represents a System.TimeSpan value.", "type":"string", "pattern": "^-?(\\d+\\.)?\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,7})?$"}"""); + +#if NET + yield return new TestData(new(2021, 1, 1), ExpectedJsonSchema: """{"type":"string","format": "date"}"""); + yield return new TestData(new(hour: 22, minute: 30, second: 33, millisecond: 100), ExpectedJsonSchema: """{"type":"string","format": "time"}"""); +#endif + yield return new TestData(Guid.Empty, ExpectedJsonSchema: """{"type":"string","format":"uuid"}"""); + yield return new TestData(new("http://example.com"), """{"type":["string","null"],"format":"uri"}"""); + yield return new TestData(new(1, 2, 3, 4), ExpectedJsonSchema: """{"$comment": "Represents a version string.", "type":["string","null"],"pattern":"^\\d+(\\.\\d+){1,3}$"}"""); + yield return new TestData(JsonDocument.Parse("""[{ "x" : 42 }]"""), ExpectedJsonSchema: "true"); + yield return new TestData(JsonDocument.Parse("""[{ "x" : 42 }]""").RootElement, ExpectedJsonSchema: "true"); + yield return new TestData(JsonNode.Parse("""[{ "x" : 42 }]"""), ExpectedJsonSchema: "true"); + yield return new TestData((JsonValue)42, ExpectedJsonSchema: "true"); + yield return new TestData(new() { ["x"] = 42 }, ExpectedJsonSchema: """{"type":["object","null"]}"""); + yield return new TestData([(JsonNode)1, (JsonNode)2, (JsonNode)3], ExpectedJsonSchema: """{"type":["array","null"]}"""); + + // Enum types + yield return new TestData(IntEnum.A, ExpectedJsonSchema: """{"type":"integer"}"""); + yield return new TestData(StringEnum.A, ExpectedJsonSchema: """{"enum":["A","B","C"]}"""); + yield return new TestData(FlagsStringEnum.A, ExpectedJsonSchema: """{"type":"string"}"""); + + // Nullable types + yield return new TestData(true, AdditionalValues: [null], ExpectedJsonSchema: """{"type":["boolean","null"]}"""); + yield return new TestData(42, AdditionalValues: [null], ExpectedJsonSchema: """{"type":["integer","null"]}"""); + yield return new TestData(3.14, AdditionalValues: [null], ExpectedJsonSchema: """{"type":["number","null"]}"""); + yield return new TestData(Guid.Empty, AdditionalValues: [null], ExpectedJsonSchema: """{"type":["string","null"],"format":"uuid"}"""); + yield return new TestData(JsonDocument.Parse("{}").RootElement, AdditionalValues: [null], ExpectedJsonSchema: "true"); + yield return new TestData(IntEnum.A, AdditionalValues: [null], ExpectedJsonSchema: """{"type":["integer","null"]}"""); + yield return new TestData(StringEnum.A, AdditionalValues: [null], ExpectedJsonSchema: """{"enum":["A","B","C",null]}"""); + yield return new TestData( + new(1, "two", true, 3.14), + AdditionalValues: [null], + ExpectedJsonSchema: """ + { + "type":["object","null"], + "properties": { + "X": {"type":"integer"}, + "Y": {"type":"string"}, + "Z": {"type":"boolean"}, + "W": {"type":"number"} + } + } + """); + + // User-defined POCOs + yield return new TestData( + Value: new() { String = "string", StringNullable = "string", Int = 42, Double = 3.14, Boolean = true }, + ExpectedJsonSchema: """ + { + "type": ["object","null"], + "properties": { + "String": { "type": "string" }, + "StringNullable": { "type": ["string", "null"] }, + "Int": { "type": "integer" }, + "Double": { "type": "number" }, + "Boolean": { "type": "boolean" } + } + } + """); + + yield return new TestData( + Value: new(1, "two", true, 3.14), + ExpectedJsonSchema: """ + { + "type": ["object","null"], + "properties": { + "X": { "type": "integer" }, + "Y": { "type": "string" }, + "Z": { "type": "boolean" }, + "W": { "type": "number" } + }, + "required": ["X","Y","Z","W"] + } + """); + + yield return new TestData( + Value: new(1, "two", true, 3.14), + ExpectedJsonSchema: """ + { + "type": "object", + "properties": { + "X": { "type": "integer" }, + "Y": { "type": "string" }, + "Z": { "type": "boolean" }, + "W": { "type": "number" } + } + } + """); + + yield return new TestData( + Value: new(1, "two", true, 3.14, StringEnum.A), + ExpectedJsonSchema: """ + { + "type": ["object","null"], + "properties": { + "X1": { "type": "integer" }, + "X2": { "type": "string" }, + "X3": { "type": "boolean" }, + "X4": { "type": "number" }, + "X5": { "enum": ["A", "B", "C"] }, + "Y1": { "type": "integer", "default": 42 }, + "Y2": { "type": "string", "default": "str" }, + "Y3": { "type": "boolean", "default": true }, + "Y4": { "type": "number", "default": 0 }, + "Y5": { "enum": ["A", "B", "C"], "default": "A" } + }, + "required": ["X1", "X2", "X3", "X4", "X5"] + } + """); + + yield return new TestData( + new() { X = "str1", Y = "str2", Z = 42 }, + ExpectedJsonSchema: """ + { + "type": ["object","null"], + "properties": { + "Y": { "type": "string" }, + "Z": { "type": "integer" }, + "X": { "type": "string" } + }, + "required": [ "Y", "Z", "X" ] + } + """); + + yield return new TestData(new() { X = 1, Y = 2 }, ExpectedJsonSchema: """{"type":["object","null"],"properties":{"X":{"type":"integer"}}}"""); + yield return new TestData( + Value: new() { IntegerProperty = 1, StringProperty = "str" }, + ExpectedJsonSchema: """ + { + "type": ["object","null"], + "properties": { + "int": { "type": "integer" }, + "str": { "type": ["string", "null"] } + } + } + """); + + yield return new TestData( + Value: new() { X = 1 }, + ExpectedJsonSchema: """ + { + "type": ["object","null"], + "properties": { "X": { "type": ["string","integer"], "pattern": "^-?(?:0|[1-9]\\d*)$" } } + } + """); + + yield return new TestData( + Value: new() { + IntegerReadingFromString = 3, + DoubleReadingFromString = 3.14, + DecimalReadingFromString = 3.14M, + IntegerWritingAsString = 3, + DoubleWritingAsString = 3.14, + DecimalWritingAsString = 3.14M, + IntegerAllowingFloatingPointLiterals = 3, + DoubleAllowingFloatingPointLiterals = 3.14, + DecimalAllowingFloatingPointLiterals = 3.14M, + IntegerAllowingFloatingPointLiteralsAndReadingFromString = 3, + DoubleAllowingFloatingPointLiteralsAndReadingFromString = 3.14, + DecimalAllowingFloatingPointLiteralsAndReadingFromString = 3.14M, + }, + AdditionalValues: [ + new() { DoubleAllowingFloatingPointLiterals = double.NaN, }, + new() { DoubleAllowingFloatingPointLiterals = double.PositiveInfinity }, + new() { DoubleAllowingFloatingPointLiterals = double.NegativeInfinity }, + ], + ExpectedJsonSchema: """ + { + "type": ["object","null"], + "properties": { + "IntegerReadingFromString": { "type": ["string","integer"], "pattern": "^-?(?:0|[1-9]\\d*)$" }, + "DoubleReadingFromString": { "type": ["string","number"], "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?$" }, + "DecimalReadingFromString": { "type": ["string","number"], "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$" }, + "IntegerWritingAsString": { "type": ["string","integer"], "pattern": "^-?(?:0|[1-9]\\d*)$" }, + "DoubleWritingAsString": { "type": ["string","number"], "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?$" }, + "DecimalWritingAsString": { "type": ["string","number"], "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$" }, + "IntegerAllowingFloatingPointLiterals": { "type": "integer" }, + "DoubleAllowingFloatingPointLiterals": { + "anyOf": [ + { "type": "number" }, + { "enum": ["NaN", "Infinity", "-Infinity"] } + ] + }, + "DecimalAllowingFloatingPointLiterals": { "type": "number" }, + "IntegerAllowingFloatingPointLiteralsAndReadingFromString": { "type": ["string","integer"], "pattern": "^-?(?:0|[1-9]\\d*)$" }, + "DoubleAllowingFloatingPointLiteralsAndReadingFromString": { + "anyOf": [ + { "type": ["string","number"], "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?$" }, + { "enum": ["NaN", "Infinity", "-Infinity"] } + ] + }, + "DecimalAllowingFloatingPointLiteralsAndReadingFromString": { "type": ["string","number"], "pattern": "^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$" } + } + } + """); + + yield return new TestData( + Value: new() { Value = 1, Next = new() { Value = 2, Next = new() { Value = 3 } } }, + AdditionalValues: [new() { Value = 1, Next = null }], + ExpectedJsonSchema: """ + { + "type": ["object","null"], + "properties": { + "Value": { "type": "integer" }, + "Next": { + "type": ["object", "null"], + "properties": { + "Value": { "type": "integer" }, + "Next": { "$ref": "#/properties/Next" } + } + } + } + } + """); + + // Same as above with non-nullable reference type handling + yield return new TestData( + Value: new() { Value = 1, Next = new() { Value = 2, Next = new() { Value = 3 } } }, + AdditionalValues: [new() { Value = 1, Next = null }], + ExpectedJsonSchema: """ + { + "type": "object", + "properties": { + "Value": { "type": "integer" }, + "Next": { + "type": ["object", "null"], + "properties": { + "Value": { "type": "integer" }, + "Next": { "$ref": "#/properties/Next" } + } + } + } + } + """, + Options: new() { TreatNullObliviousAsNonNullable = true }); + + // Same as above but using an anchor-based reference scheme + yield return new TestData( + Value: new() { Value = 1, Next = new() { Value = 2, Next = new() { Value = 3 } } }, + AdditionalValues: [new() { Value = 1, Next = null }], + ExpectedJsonSchema: """ + { + "$anchor" : "PocoWithRecursiveMembers", + "type": ["object","null"], + "properties": { + "Value": { "type": "integer" }, + "Next": { + "$anchor" : "PocoWithRecursiveMembers_Next", + "type": ["object", "null"], + "properties": { + "Value": { "type": "integer" }, + "Next": { "$ref": "#PocoWithRecursiveMembers_Next" } + } + } + } + } + """, + Options: new JsonSchemaExporterOptions + { + TransformSchemaNode = static (ctx, schema) => + { + if (ctx.TypeInfo.Kind is JsonTypeInfoKind.None || schema is not JsonObject schemaObj) + { + return schema; + } + + string anchorName = ctx.PropertyInfo is { } property + ? ctx.TypeInfo.Type.Name + "_" + property.Name + : ctx.TypeInfo.Type.Name; + + if (schemaObj.ContainsKey("$ref")) + { + schemaObj["$ref"] = "#" + anchorName; + } + else + { + schemaObj["$anchor"] = anchorName; + } + + return schemaObj; + } + }); + + // Same as above but using an id-based reference scheme + yield return new TestData( + Value: new() { Value = 1, Next = new() { Value = 2, Next = new() { Value = 3 } } }, + AdditionalValues: [new() { Value = 1, Next = null }], + ExpectedJsonSchema: """ + { + "$id" : "https://example.com/schema/pocowithrecursivemembers.json", + "type": ["object","null"], + "properties": { + "Value": { "type": "integer" }, + "Next": { + "$id" : "https://example.com/schema/pocowithrecursivemembers/next.json", + "type": ["object", "null"], + "properties": { + "Value": { "type": "integer" }, + "Next": { "$ref": "https://example.com/schema/pocowithrecursivemembers/next.json" } + } + } + } + } + """, + Options: new JsonSchemaExporterOptions + { + TransformSchemaNode = static (ctx, schema) => + { + if (ctx.TypeInfo.Kind is JsonTypeInfoKind.None || schema is not JsonObject schemaObj) + { + return schema; + } + + string idPath = ctx.PropertyInfo is { } property + ? ctx.TypeInfo.Type.Name.ToLower() + "/" + property.Name.ToLower() + : ctx.TypeInfo.Type.Name.ToLower(); + + string idUrl = "https://example.com/schema/" + idPath + ".json"; + + if (schemaObj.ContainsKey("$ref")) + { + schemaObj["$ref"] = idUrl; + } + else + { + schemaObj["$id"] = idUrl; + } + + return schemaObj; + } + }); + + yield return new TestData( + Value: new() { Children = [new(), new() { Children = [] }] }, + ExpectedJsonSchema: """ + { + "type": ["object","null"], + "properties": { + "Children": { + "type": "array", + "items": { "$ref" : "#" } + } + } + } + """); + + // Same as above but with non-nullable reference type handling + yield return new TestData( + Value: new() { Children = [new(), new() { Children = [] }] }, + ExpectedJsonSchema: """ + { + "type": "object", + "properties": { + "Children": { + "type": "array", + "items": { "$ref" : "#" } + } + } + } + """, + Options: new() { TreatNullObliviousAsNonNullable = true }); + + yield return new TestData( + Value: new() { Children = new() { ["key1"] = new(), ["key2"] = new() { Children = new() { ["key3"] = new() } } } }, + ExpectedJsonSchema: """ + { + "type": ["object","null"], + "properties": { + "Children": { + "type": "object", + "additionalProperties": { "$ref" : "#" } + } + } + } + """); + + // Same as above but with non-nullable reference type handling + yield return new TestData( + Value: new() { Children = new() { ["key1"] = new(), ["key2"] = new() { Children = new() { ["key3"] = new() } } } }, + ExpectedJsonSchema: """ + { + "type": "object", + "properties": { + "Children": { + "type": "object", + "additionalProperties": { "$ref" : "#" } + } + } + } + """, + Options: new() { TreatNullObliviousAsNonNullable = true }); + + yield return new TestData( + Value: new() { X = 42 }, + ExpectedJsonSchema: """ + { + "description": "The type description", + "type": ["object","null"], + "properties": { + "X": { + "description": "The property description", + "type": "integer" + } + } + } + """, + Options: new() + { + TransformSchemaNode = static (ctx, schema) => + { + if (schema is not JsonObject schemaObj || schemaObj.ContainsKey("$ref")) + { + return schema; + } + + DescriptionAttribute? descriptionAttribute = + GetCustomAttribute(ctx.PropertyInfo?.AttributeProvider) ?? + GetCustomAttribute(ctx.PropertyInfo?.AssociatedParameter.AttributeProvider) ?? + GetCustomAttribute(ctx.TypeInfo.Type); + + if (descriptionAttribute != null) + { + schemaObj["description"] = (JsonNode)descriptionAttribute.Description; + } + + return schemaObj; + } + }); + + yield return new TestData(new() { Value = 42 }, ExpectedJsonSchema: "true"); + yield return new TestData(new() { Value = 42 }, ExpectedJsonSchema: """{"type":["object", "null"],"properties":{"Value":true}}"""); + yield return new TestData( + Value: new() + { + IntEnum = IntEnum.A, + StringEnum = StringEnum.B, + IntEnumUsingStringConverter = IntEnum.A, + NullableIntEnumUsingStringConverter = IntEnum.B, + StringEnumUsingIntConverter = StringEnum.A, + NullableStringEnumUsingIntConverter = StringEnum.B + }, + AdditionalValues: [ + new() + { + IntEnum = (IntEnum)int.MaxValue, + StringEnum = StringEnum.A, + IntEnumUsingStringConverter = IntEnum.A, + NullableIntEnumUsingStringConverter = null, + StringEnumUsingIntConverter = (StringEnum)int.MaxValue, + NullableStringEnumUsingIntConverter = null + }, + ], + ExpectedJsonSchema: """ + { + "type": ["object","null"], + "properties": { + "IntEnum": { "type": "integer" }, + "StringEnum": { "enum": [ "A", "B", "C" ] }, + "IntEnumUsingStringConverter": { "enum": [ "A", "B", "C" ] }, + "NullableIntEnumUsingStringConverter": { "enum": [ "A", "B", "C", null ] }, + "StringEnumUsingIntConverter": { "type": "integer" }, + "NullableStringEnumUsingIntConverter": { "type": [ "integer", "null" ] } + } + } + """); + + // Same but using a callback to insert a type keyword for string enums. + yield return new TestData( + Value: new() + { + IntEnum = IntEnum.A, + StringEnum = StringEnum.B, + IntEnumUsingStringConverter = IntEnum.A, + NullableIntEnumUsingStringConverter = IntEnum.B, + StringEnumUsingIntConverter = StringEnum.A, + NullableStringEnumUsingIntConverter = StringEnum.B + }, + ExpectedJsonSchema: """ + { + "type": ["object","null"], + "properties": { + "IntEnum": { "type": "integer" }, + "StringEnum": { "type" : "string", "enum": [ "A", "B", "C" ] }, + "IntEnumUsingStringConverter": { "type" : "string", "enum": [ "A", "B", "C" ] }, + "NullableIntEnumUsingStringConverter": { "type" : ["string","null"], "enum": [ "A", "B", "C", null ] }, + "StringEnumUsingIntConverter": { "type": "integer" }, + "NullableStringEnumUsingIntConverter": { "type": [ "integer", "null" ] } + } + } + """, + Options: new() + { + TransformSchemaNode = static (ctx, schema) => + { + if (schema is not JsonObject schemaObj) + { + return schema; + } + + Type type = ctx.TypeInfo.Type; + + if (schemaObj.ContainsKey("enum")) + { + if (ctx.TypeInfo.Type.IsEnum) + { + schemaObj.Add("type", "string"); + } + else if (Nullable.GetUnderlyingType(type)?.IsEnum is true) + { + schemaObj.Add("type", new JsonArray { (JsonNode)"string", (JsonNode)"null" }); + } + } + + return schemaObj; + } + }); + + var recordStruct = new SimpleRecordStruct(42, "str", true, 3.14); + yield return new TestData( + Value: new() { Struct = recordStruct, NullableStruct = null }, + AdditionalValues: [new() { Struct = recordStruct, NullableStruct = recordStruct }], + ExpectedJsonSchema: """ + { + "type": ["object","null"], + "properties": { + "Struct": { + "type": "object", + "properties": { + "X": {"type":"integer"}, + "Y": {"type":"string"}, + "Z": {"type":"boolean"}, + "W": {"type":"number"} + } + }, + "NullableStruct": { + "type": ["object","null"], + "properties": { + "X": {"type":"integer"}, + "Y": {"type":"string"}, + "Z": {"type":"boolean"}, + "W": {"type":"number"} + } + } + } + } + """); + + yield return new TestData( + Value: new() { NullableStruct = null, Struct = recordStruct }, + AdditionalValues: [new() { NullableStruct = recordStruct, Struct = recordStruct }], + ExpectedJsonSchema: """ + { + "type": ["object","null"], + "properties": { + "NullableStruct": { + "type": ["object","null"], + "properties": { + "X": {"type":"integer"}, + "Y": {"type":"string"}, + "Z": {"type":"boolean"}, + "W": {"type":"number"} + } + }, + "Struct": { + "type": "object", + "properties": { + "X": {"type":"integer"}, + "Y": {"type":"string"}, + "Z": {"type":"boolean"}, + "W": {"type":"number"} + } + } + } + } + """); + + yield return new TestData( + Value: new() { Name = "name", ExtensionData = new() { ["x"] = 42 } }, + ExpectedJsonSchema: """ + { + "type": ["object","null"], + "properties": { + "Name": { "type": ["string", "null"] } + } + } + """); + + yield return new TestData( + Value: new() { Name = "name", Age = 42 }, + ExpectedJsonSchema: """ + { + "type": ["object","null"], + "properties": { + "Name": {"type": ["string", "null"]}, + "Age": {"type":"integer"} + }, + "additionalProperties": false + } + """); + + yield return new TestData( + Value: new() { MaybeNull = null!, AllowNull = null, NotNull = null, DisallowNull = null!, NotNullDisallowNull = "str" }, + ExpectedJsonSchema: """ + { + "type": ["object","null"], + "properties": { + "MaybeNull": {"type":["string","null"]}, + "AllowNull": {"type":["string","null"]}, + "NotNull": {"type":["string","null"]}, + "DisallowNull": {"type":["string","null"]}, + "NotNullDisallowNull": {"type":"string"} + } + } + """); + + yield return new TestData( + Value: new(allowNull: null, disallowNull: "str"), + ExpectedJsonSchema: """ + { + "type": ["object","null"], + "properties": { + "AllowNull": {"type":["string","null"]}, + "DisallowNull": {"type":"string"} + }, + "required": ["AllowNull", "DisallowNull"] + } + """); + + yield return new TestData( + Value: new(null), + ExpectedJsonSchema: """ + { + "type": ["object","null"], + "properties": { + "Value": {"type":["string","null"]} + }, + "required": ["Value"] + } + """); + + yield return new TestData( + Value: new(), + ExpectedJsonSchema: """ + { + "type": ["object","null"], + "properties": { + "X1": {"type":"string", "default": "str" }, + "X2": {"type":"integer", "default": 42 }, + "X3": {"type":"boolean", "default": true }, + "X4": {"type":"number", "default": 0 }, + "X5": {"enum":["A","B","C"], "default": "A" }, + "X6": {"type":["string","null"], "default": "str" }, + "X7": {"type":["integer","null"], "default": 42 }, + "X8": {"type":["boolean","null"], "default": true }, + "X9": {"type":["number","null"], "default": 0 }, + "X10": {"enum":["A","B","C", null], "default": "A" } + } + } + """); + + yield return new TestData>( + Value: new(null!), + ExpectedJsonSchema: """ + { + "type": ["object","null"], + "properties": { + "Value": {"type":["string","null"]} + }, + "required": ["Value"] + } + """); + + yield return new TestData( + Value: new PocoWithPolymorphism.DerivedPocoStringDiscriminator { BaseValue = 42, DerivedValue = "derived" }, + AdditionalValues: [ + new PocoWithPolymorphism.DerivedPocoNoDiscriminator { BaseValue = 42, DerivedValue = "derived" }, + new PocoWithPolymorphism.DerivedPocoIntDiscriminator { BaseValue = 42, DerivedValue = "derived" }, + new PocoWithPolymorphism.DerivedCollectionNoDiscriminator { BaseValue = 42 }, + new PocoWithPolymorphism.DerivedCollection { BaseValue = 42 }, + new PocoWithPolymorphism.DerivedDictionaryNoDiscriminator { BaseValue = 42 }, + new PocoWithPolymorphism.DerivedDictionary { BaseValue = 42 }, + ], + + ExpectedJsonSchema: """ + { + "anyOf": [ + { + "type": ["object","null"], + "properties": { + "BaseValue": {"type":"integer"}, + "DerivedValue": {"type":["string", "null"]} + } + }, + { + "type": ["object","null"], + "properties": { + "$type": {"const":"derivedPoco"}, + "BaseValue": {"type":"integer"}, + "DerivedValue": {"type":["string", "null"]} + }, + "required": ["$type"] + }, + { + "type": ["object","null"], + "properties": { + "$type": {"const":42}, + "BaseValue": {"type":"integer"}, + "DerivedValue": {"type":["string", "null"]} + }, + "required": ["$type"] + }, + { + "type": ["array","null"], + "items": {"type":"integer"} + }, + { + "type": ["object","null"], + "properties": { + "$type": {"const":"derivedCollection"}, + "$values": { + "type": "array", + "items": {"type":"integer"} + } + }, + "required": ["$type"] + }, + { + "type": ["object","null"], + "additionalProperties":{"type": "integer"} + }, + { + "type": ["object","null"], + "properties": { + "$type": {"const":"derivedDictionary"} + }, + "additionalProperties":{"type": "integer"}, + "required": ["$type"] + } + ] + } + """); + + yield return new TestData( + Value: new NonAbstractClassWithSingleDerivedType(), + AdditionalValues: [new NonAbstractClassWithSingleDerivedType.Derived()], + ExpectedJsonSchema: """{"type":["object","null"]}"""); + + yield return new TestData( + Value: new DiscriminatedUnion.Left("value"), + AdditionalValues: [new DiscriminatedUnion.Right(42)], + ExpectedJsonSchema: """ + { + "type": ["object","null"], + "required": ["case"], + "anyOf": [ + { + "properties": { + "case": {"const":"left"}, + "value": {"type":"string"} + }, + "required": ["value"] + }, + { + "properties": { + "case": {"const":"right"}, + "value": {"type":"integer"} + }, + "required": ["value"] + } + ] + } + """); + + yield return new TestData( + Value: new(), + ExpectedJsonSchema: """ + { + "type": ["object","null"], + "properties": { + "PolymorphicValue": { + "anyOf": [ + { + "type": "object", + "properties": { + "BaseValue": {"type":"integer"}, + "DerivedValue": {"type":["string", "null"]} + } + }, + { + "type": "object", + "properties": { + "$type": {"const":"derivedPoco"}, + "BaseValue": {"type":"integer"}, + "DerivedValue": {"type":["string","null"]} + }, + "required": ["$type"] + }, + { + "type": "object", + "properties": { + "$type": {"const":42}, + "BaseValue": {"type":"integer"}, + "DerivedValue": {"type":["string", "null"]} + }, + "required": ["$type"] + }, + { + "type": "array", + "items": {"type":"integer"} + }, + { + "type": "object", + "properties": { + "$type": {"const":"derivedCollection"}, + "$values": { + "type": "array", + "items": {"type":"integer"} + } + }, + "required": ["$type"] + }, + { + "type": "object", + "additionalProperties":{"type": "integer"} + }, + { + "type": "object", + "properties": { + "$type": {"const":"derivedDictionary"} + }, + "additionalProperties":{"type": "integer"}, + "required": ["$type"] + } + ] + }, + "DiscriminatedUnion":{ + "type": "object", + "required": ["case"], + "anyOf": [ + { + "properties": { + "case": {"const":"left"}, + "value": {"type":"string"} + }, + "required": ["value"] + }, + { + "properties": { + "case": {"const":"right"}, + "value": {"type":"integer"} + }, + "required": ["value"] + } + ] + }, + "DerivedValue1": { + "type": "object", + "properties": { + "BaseValue": {"type":"integer"}, + "DerivedValue": {"type":["string", "null"]} + } + }, + "DerivedValue2": { + "type": "object", + "properties": { + "BaseValue": {"type":"integer"}, + "DerivedValue": {"type":["string", "null"]} + } + } + } + } + """); + + yield return new TestData( + Value: new("string", -1), + ExpectedJsonSchema: """ + { + "type": ["object","null"], + "properties": { + "StringValue": {"type":"string","pattern":"\\w+"}, + "IntValue": {"type":"integer","default":42} + }, + "required": ["StringValue","IntValue"] + } + """, + Options: new() + { + TransformSchemaNode = static (ctx, schema) => + { + if (ctx.PropertyInfo is null || schema is not JsonObject schemaObj || schemaObj.ContainsKey("$ref")) + { + return schema; + } + + DefaultValueAttribute? defaultValueAttr = + GetCustomAttribute(ctx.PropertyInfo?.AttributeProvider) ?? + GetCustomAttribute(ctx.PropertyInfo?.AssociatedParameter?.AttributeProvider); + + if (defaultValueAttr != null) + { + schemaObj["default"] = JsonSerializer.SerializeToNode(defaultValueAttr.Value, ctx.TypeInfo); + } + + RegularExpressionAttribute? regexAttr = + GetCustomAttribute(ctx.PropertyInfo?.AttributeProvider) ?? + GetCustomAttribute(ctx.PropertyInfo?.AssociatedParameter?.AttributeProvider); + + if (regexAttr != null) + { + schemaObj["pattern"] = regexAttr.Pattern; + } + + return schemaObj; + } + }); + + yield return new TestData( + Value: new ClassWithJsonPointerEscapablePropertyNames { Value = new() }, + ExpectedJsonSchema: """ + { + "type": ["object","null"], + "properties": { + "~/path/to/value": { + "type": "object", + "properties": { + "Value" : {"type":"integer"}, + "Next": { + "type": ["object","null"], + "properties": { + "Value" : {"type":"integer"}, + "Next": {"$ref":"#/properties/~0~1path~1to~1value/properties/Next"} + } + } + } + } + } + } + """); + + // Collection types + yield return new TestData([1, 2, 3], ExpectedJsonSchema: """{"type":["array","null"],"items":{"type":"integer"}}"""); + yield return new TestData>([false, true, false], ExpectedJsonSchema: """{"type":["array","null"],"items":{"type":"boolean"}}"""); + yield return new TestData>(["one", "two", "three"], ExpectedJsonSchema: """{"type":["array","null"],"items":{"type":["string","null"]}}"""); + yield return new TestData>(new([1.1, 2.2, 3.3]), ExpectedJsonSchema: """{"type":["array","null"],"items":{"type":"number"}}"""); + yield return new TestData>(new(['x', '2', '+']), ExpectedJsonSchema: """{"type":["array","null"],"items":{"type":"string","minLength":1,"maxLength":1}}"""); + yield return new TestData>([1, 2, 3], ExpectedJsonSchema: """{"type":"array","items":{"type":"integer"}}"""); + yield return new TestData>(["one", "two", "three"], ExpectedJsonSchema: """{"type":["array","null"],"items":{"type":["string","null"]}}"""); + yield return new TestData>([false, false, true], ExpectedJsonSchema: """{"type":["array","null"],"items":{"type":"boolean"}}"""); + yield return new TestData([1, "two", 3.14], ExpectedJsonSchema: """{"type":["array","null"]}"""); + yield return new TestData([1, "two", 3.14], ExpectedJsonSchema: """{"type":["array","null"]}"""); + + // Dictionary types + yield return new TestData>( + Value: new() { ["one"] = 1, ["two"] = 2, ["three"] = 3 }, + ExpectedJsonSchema: """{"type":["object","null"],"additionalProperties":{"type": "integer"}}"""); + + yield return new TestData>( + Value: new([new("one", 1), new("two", 2), new("three", 3)]), + ExpectedJsonSchema: """{"type":"object","additionalProperties":{"type": "integer"}}"""); + + yield return new TestData>( + Value: new() { [1] = "one", [2] = "two", [3] = "three" }, + ExpectedJsonSchema: """{"type":["object","null"],"additionalProperties":{"type": ["string","null"]}}"""); + + yield return new TestData>( + Value: new() + { + ["one"] = new() { String = "string", StringNullable = "string", Int = 42, Double = 3.14, Boolean = true }, + ["two"] = new() { String = "string", StringNullable = null, Int = 42, Double = 3.14, Boolean = true }, + ["three"] = new() { String = "string", StringNullable = null, Int = 42, Double = 3.14, Boolean = true }, + }, + ExpectedJsonSchema: """ + { + "type": ["object","null"], + "additionalProperties": { + "type": ["object","null"], + "properties": { + "String": { "type": "string" }, + "StringNullable": { "type": ["string","null"] }, + "Int": { "type": "integer" }, + "Double": { "type": "number" }, + "Boolean": { "type": "boolean" } + } + } + } + """); + + yield return new TestData>( + Value: new() { ["one"] = 1, ["two"] = "two", ["three"] = 3.14 }, + ExpectedJsonSchema: """{"type":["object","null"]}"""); + + yield return new TestData( + Value: new() { ["one"] = 1, ["two"] = "two", ["three"] = 3.14 }, + ExpectedJsonSchema: """{"type":["object","null"]}"""); + } + + public enum IntEnum { A, B, C }; + + [JsonConverter(typeof(JsonStringEnumConverter))] + public enum StringEnum { A, B, C }; + + [Flags, JsonConverter(typeof(JsonStringEnumConverter))] + public enum FlagsStringEnum { A = 1, B = 2, C = 4 }; + + public class SimplePoco + { + public string String { get; set; } = "default"; + public string? StringNullable { get; set; } + + public int Int { get; set; } + public double Double { get; set; } + public bool Boolean { get; set; } + } + + public record SimpleRecord(int X, string Y, bool Z, double W); + public record struct SimpleRecordStruct(int X, string Y, bool Z, double W); + + public record RecordWithOptionalParameters( + int X1, string X2, bool X3, double X4, StringEnum X5, + int Y1 = 42, string Y2 = "str", bool Y3 = true, double Y4 = 0, StringEnum Y5 = StringEnum.A); + + public class PocoWithRequiredMembers + { + [JsonInclude] + public required string X; + + public required string Y { get; set; } + + [JsonRequired] + public int Z { get; set; } + } + + public class PocoWithIgnoredMembers + { + public int X { get; set; } + + [JsonIgnore] + public int Y { get; set; } + } + + public class PocoWithCustomNaming + { + [JsonPropertyName("int")] + public int IntegerProperty { get; set; } + + [JsonPropertyName("str")] + public string? StringProperty { get; set; } + } + + [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)] + public class PocoWithCustomNumberHandling + { + public int X { get; set; } + } + + public class PocoWithCustomNumberHandlingOnProperties + { + [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)] + public int IntegerReadingFromString { get; set; } + + [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)] + public double DoubleReadingFromString { get; set; } + + [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)] + public decimal DecimalReadingFromString { get; set; } + + [JsonNumberHandling(JsonNumberHandling.WriteAsString)] + public int IntegerWritingAsString { get; set; } + + [JsonNumberHandling(JsonNumberHandling.WriteAsString)] + public double DoubleWritingAsString { get; set; } + + [JsonNumberHandling(JsonNumberHandling.WriteAsString)] + public decimal DecimalWritingAsString { get; set; } + + [JsonNumberHandling(JsonNumberHandling.AllowNamedFloatingPointLiterals)] + public int IntegerAllowingFloatingPointLiterals { get; set; } + + [JsonNumberHandling(JsonNumberHandling.AllowNamedFloatingPointLiterals)] + public double DoubleAllowingFloatingPointLiterals { get; set; } + + [JsonNumberHandling(JsonNumberHandling.AllowNamedFloatingPointLiterals)] + public decimal DecimalAllowingFloatingPointLiterals { get; set; } + + [JsonNumberHandling(JsonNumberHandling.AllowNamedFloatingPointLiterals | JsonNumberHandling.AllowReadingFromString)] + public int IntegerAllowingFloatingPointLiteralsAndReadingFromString { get; set; } + + [JsonNumberHandling(JsonNumberHandling.AllowNamedFloatingPointLiterals | JsonNumberHandling.AllowReadingFromString)] + public double DoubleAllowingFloatingPointLiteralsAndReadingFromString { get; set; } + + [JsonNumberHandling(JsonNumberHandling.AllowNamedFloatingPointLiterals | JsonNumberHandling.AllowReadingFromString)] + public decimal DecimalAllowingFloatingPointLiteralsAndReadingFromString { get; set; } + } + + public class PocoWithRecursiveMembers + { + public int Value { get; init; } + public PocoWithRecursiveMembers? Next { get; init; } + } + + public class PocoWithRecursiveCollectionElement + { + public List Children { get; init; } = new(); + } + + public class PocoWithRecursiveDictionaryValue + { + public Dictionary Children { get; init; } = new(); + } + + [Description("The type description")] + public class PocoWithDescription + { + [Description("The property description")] + public int X { get; set; } + } + + [JsonConverter(typeof(CustomConverter))] + public class PocoWithCustomConverter + { + public int Value { get; set; } + + public class CustomConverter : JsonConverter + { + public override PocoWithCustomConverter Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => + new PocoWithCustomConverter { Value = reader.GetInt32() }; + + public override void Write(Utf8JsonWriter writer, PocoWithCustomConverter value, JsonSerializerOptions options) => + writer.WriteNumberValue(value.Value); + } + } + + public class PocoWithCustomPropertyConverter + { + [JsonConverter(typeof(CustomConverter))] + public int Value { get; set; } + + public class CustomConverter : JsonConverter + { + public override int Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + => int.Parse(reader.GetString()!); + + public override void Write(Utf8JsonWriter writer, int value, JsonSerializerOptions options) + => writer.WriteStringValue(value.ToString()); + } + } + + public class PocoWithEnums + { + public IntEnum IntEnum { get; init; } + public StringEnum StringEnum { get; init; } + + [JsonConverter(typeof(JsonStringEnumConverter))] + public IntEnum IntEnumUsingStringConverter { get; set; } + + [JsonConverter(typeof(JsonStringEnumConverter))] + public IntEnum? NullableIntEnumUsingStringConverter { get; set; } + + [JsonConverter(typeof(JsonNumberEnumConverter))] + public StringEnum StringEnumUsingIntConverter { get; set; } + + [JsonConverter(typeof(JsonNumberEnumConverter))] + public StringEnum? NullableStringEnumUsingIntConverter { get; set; } + } + + public class PocoWithStructFollowedByNullableStruct + { + public SimpleRecordStruct? NullableStruct { get; set; } + public SimpleRecordStruct Struct { get; set; } + } + + public class PocoWithNullableStructFollowedByStruct + { + public SimpleRecordStruct? NullableStruct { get; set; } + public SimpleRecordStruct Struct { get; set; } + } + + public class PocoWithExtensionDataProperty + { + public string? Name { get; set; } + + [JsonExtensionData] + public Dictionary? ExtensionData { get; set; } + } + + [JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Disallow)] + public class PocoDisallowingUnmappedMembers + { + public string? Name { get; set; } + public int Age { get; set; } + } + + public class PocoWithNullableAnnotationAttributes + { + [MaybeNull] + public string MaybeNull { get; set; } + + [AllowNull] + public string AllowNull { get; set; } + + [NotNull] + public string? NotNull { get; set; } + + [DisallowNull] + public string? DisallowNull { get; set; } + + [NotNull, DisallowNull] + public string? NotNullDisallowNull { get; set; } = ""; + } + + public class PocoWithNullableAnnotationAttributesOnConstructorParams([AllowNull] string allowNull, [DisallowNull] string? disallowNull) + { + public string AllowNull { get; } = allowNull!; + public string DisallowNull { get; } = disallowNull; + } + + public class PocoWithNullableConstructorParameter(string? value) + { + public string Value { get; } = value!; + } + + public class PocoWithOptionalConstructorParams( + string x1 = "str", int x2 = 42, bool x3 = true, double x4 = 0, StringEnum x5 = StringEnum.A, + string? x6 = "str", int? x7 = 42, bool? x8 = true, double? x9 = 0, StringEnum? x10 = StringEnum.A) + { + public string X1 { get; } = x1; + public int X2 { get; } = x2; + public bool X3 { get; } = x3; + public double X4 { get; } = x4; + public StringEnum X5 { get; } = x5; + + public string? X6 { get; } = x6; + public int? X7 { get; } = x7; + public bool? X8 { get; } = x8; + public double? X9 { get; } = x9; + public StringEnum? X10 { get; } = x10; + } + + // Regression test for https://github.com/dotnet/runtime/issues/92487 + public class GenericPocoWithNullableConstructorParameter(T value) + { + [NotNull] + public T Value { get; } = value!; + } + + [JsonDerivedType(typeof(DerivedPocoNoDiscriminator))] + [JsonDerivedType(typeof(DerivedPocoStringDiscriminator), "derivedPoco")] + [JsonDerivedType(typeof(DerivedPocoIntDiscriminator), 42)] + [JsonDerivedType(typeof(DerivedCollectionNoDiscriminator))] + [JsonDerivedType(typeof(DerivedCollection), "derivedCollection")] + [JsonDerivedType(typeof(DerivedDictionaryNoDiscriminator))] + [JsonDerivedType(typeof(DerivedDictionary), "derivedDictionary")] + public abstract class PocoWithPolymorphism + { + public int BaseValue { get; set; } + + public class DerivedPocoNoDiscriminator : PocoWithPolymorphism + { + public string? DerivedValue { get; set; } + } + + public class DerivedPocoStringDiscriminator : PocoWithPolymorphism + { + public string? DerivedValue { get; set; } + } + + public class DerivedPocoIntDiscriminator : PocoWithPolymorphism + { + public string? DerivedValue { get; set; } + } + + public class DerivedCollection : PocoWithPolymorphism, IEnumerable + { + public IEnumerator GetEnumerator() => Enumerable.Repeat(BaseValue, 1).GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + + public class DerivedCollectionNoDiscriminator : DerivedCollection; + + public class DerivedDictionary : PocoWithPolymorphism, IReadOnlyDictionary + { + public int this[string key] => key == nameof(BaseValue) ? BaseValue : throw new KeyNotFoundException(); + public IEnumerable Keys => [nameof(BaseValue)]; + public IEnumerable Values => [BaseValue]; + public int Count => 1; + public bool ContainsKey(string key) => key == nameof(BaseValue); + public bool TryGetValue(string key, out int value) => key == nameof(BaseValue) ? (value = BaseValue) == BaseValue : (value = 0) == 0; + public IEnumerator> GetEnumerator() => Enumerable.Repeat(new KeyValuePair(nameof(BaseValue), BaseValue), 1).GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + + public class DerivedDictionaryNoDiscriminator : DerivedDictionary; + } + + [JsonDerivedType(typeof(Derived))] + public class NonAbstractClassWithSingleDerivedType + { + public class Derived : NonAbstractClassWithSingleDerivedType; + } + + [JsonPolymorphic(TypeDiscriminatorPropertyName = "case")] + [JsonDerivedType(typeof(Left), "left")] + [JsonDerivedType(typeof(Right), "right")] + public abstract record DiscriminatedUnion + { + public record Left(string value) : DiscriminatedUnion; + public record Right(int value) : DiscriminatedUnion; + } + + public class PocoCombiningPolymorphicTypeAndDerivedTypes + { + public PocoWithPolymorphism PolymorphicValue { get; set; } = new PocoWithPolymorphism.DerivedPocoNoDiscriminator { DerivedValue = "derived" }; + public DiscriminatedUnion DiscriminatedUnion { get; set; } = new DiscriminatedUnion.Left("value"); + public PocoWithPolymorphism.DerivedPocoNoDiscriminator DerivedValue1 { get; set; } = new() { DerivedValue = "derived" }; + public PocoWithPolymorphism.DerivedPocoStringDiscriminator DerivedValue2 { get; set; } = new() { DerivedValue = "derived" }; + } + + public class ClassWithComponentModelAttributes + { + public ClassWithComponentModelAttributes(string stringValue, [DefaultValue(42)] int intValue) + { + StringValue = stringValue; + IntValue = intValue; + } + + [RegularExpression(@"\w+")] + public string StringValue { get; } + + public int IntValue { get; } + } + + public class ClassWithJsonPointerEscapablePropertyNames + { + [JsonPropertyName("~/path/to/value")] + public PocoWithRecursiveMembers Value { get; set; } + } + + public readonly struct StructDictionary(IEnumerable> values) + : IReadOnlyDictionary + where TKey : notnull + { + private readonly IReadOnlyDictionary _dictionary = values.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + public TValue this[TKey key] => _dictionary[key]; + public IEnumerable Keys => _dictionary.Keys; + public IEnumerable Values => _dictionary.Values; + public int Count => _dictionary.Count; + public bool ContainsKey(TKey key) => _dictionary.ContainsKey(key); + public IEnumerator> GetEnumerator() => _dictionary.GetEnumerator(); +#if NET + public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) => _dictionary.TryGetValue(key, out value); +#else + public bool TryGetValue(TKey key, out TValue value) => _dictionary.TryGetValue(key, out value); +#endif + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_dictionary).GetEnumerator(); + } + + public record TestData( + T? Value, + string ExpectedJsonSchema, + IEnumerable? AdditionalValues = null, + JsonSchemaExporterOptions? Options = null) + : ITestData + { + public Type Type => typeof(T); + object? ITestData.Value => Value; + + IEnumerable ITestData.GetTestDataForAllValues() + { + yield return this; + + if (default(T) is null && Options?.TreatNullObliviousAsNonNullable != true) + { + yield return this with { Value = default, AdditionalValues = null }; + } + + if (AdditionalValues != null) + { + foreach (T? value in AdditionalValues) + { + yield return this with { Value = value, AdditionalValues = null }; + } + } + } + } + + public interface ITestData + { + Type Type { get; } + + object? Value { get; } + + string ExpectedJsonSchema { get; } + + JsonSchemaExporterOptions? Options { get; } + + IEnumerable GetTestDataForAllValues(); + } + + private static TAttribute? GetCustomAttribute(ICustomAttributeProvider? provider, bool inherit = false) where TAttribute : Attribute + => provider?.GetCustomAttributes(typeof(TAttribute), inherit).FirstOrDefault() as TAttribute; + } +} diff --git a/src/libraries/System.Text.Json/tests/Common/JsonSchemaExporterTests.cs b/src/libraries/System.Text.Json/tests/Common/JsonSchemaExporterTests.cs new file mode 100644 index 0000000000000..042e78ad2bd6e --- /dev/null +++ b/src/libraries/System.Text.Json/tests/Common/JsonSchemaExporterTests.cs @@ -0,0 +1,209 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.Serialization; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; +using System.Text.Json.Serialization.Tests; +using Json.Schema; +using Xunit; +using Xunit.Sdk; + +namespace System.Text.Json.Schema.Tests +{ + public abstract partial class JsonSchemaExporterTests : SerializerTests + { + private readonly JsonSerializerOptions _indentedOptions; + + protected JsonSchemaExporterTests(JsonSerializerWrapper serializer) : base(serializer) + { + _indentedOptions = new(serializer.DefaultOptions) { WriteIndented = true }; + } + + [Theory] + [MemberData(nameof(GetTestData))] + public void TestTypes_GeneratesExpectedJsonSchema(ITestData testData) + { + JsonNode schema = Serializer.DefaultOptions.GetJsonSchemaAsNode(testData.Type, testData.Options); + AssertValidJsonSchema(testData.Type, testData.ExpectedJsonSchema, schema); + } + + [Theory] + [MemberData(nameof(GetTestDataUsingAllValues))] + public void TestTypes_SerializedValueMatchesGeneratedSchema(ITestData testData) + { + JsonNode schema = Serializer.DefaultOptions.GetJsonSchemaAsNode(testData.Type, testData.Options); + JsonNode? instance = JsonSerializer.SerializeToNode(testData.Value, testData.Type, Serializer.DefaultOptions); + AssertDocumentMatchesSchema(schema, instance); + } + + [Theory] + [InlineData(typeof(string), "string")] + [InlineData(typeof(int[]), "array")] + [InlineData(typeof(Dictionary), "object")] + [InlineData(typeof(SimplePoco), "object")] + public void TreatNullObliviousAsNonNullable_False_MarksReferenceTypesAsNullable(Type referenceType, string expectedType) + { + Assert.True(!referenceType.IsValueType); + var config = new JsonSchemaExporterOptions { TreatNullObliviousAsNonNullable = false }; + JsonNode schema = Serializer.DefaultOptions.GetJsonSchemaAsNode(referenceType, config); + JsonArray arr = Assert.IsType(schema["type"]); + Assert.Equal([expectedType, "null"], arr.Select(e => (string)e!)); + } + + [Theory] + [InlineData(typeof(string), "string")] + [InlineData(typeof(int[]), "array")] + [InlineData(typeof(Dictionary), "object")] + [InlineData(typeof(SimplePoco), "object")] + public void TreatNullObliviousAsNonNullable_True_MarksReferenceTypesAsNonNullable(Type referenceType, string expectedType) + { + Assert.True(!referenceType.IsValueType); + var config = new JsonSchemaExporterOptions { TreatNullObliviousAsNonNullable = true }; + JsonNode schema = Serializer.DefaultOptions.GetJsonSchemaAsNode(referenceType, config); + Assert.Equal(expectedType, (string)schema["type"]!); + } + + [Theory] + [InlineData(typeof(Type))] + [InlineData(typeof(MethodInfo))] + [InlineData(typeof(UIntPtr))] + [InlineData(typeof(MemberInfo))] + [InlineData(typeof(SerializationInfo))] + [InlineData(typeof(Func))] + public void UnsupportedType_ReturnsExpectedSchema(Type type) + { + JsonNode schema = Serializer.DefaultOptions.GetJsonSchemaAsNode(type); + Assert.Equal(""""{"$comment":"Unsupported .NET type","not":true}"""", schema.ToJsonString()); + } + + [Fact] + public void TypeWithDisallowUnmappedMembers_AdditionalPropertiesFailValidation() + { + JsonNode schema = Serializer.DefaultOptions.GetJsonSchemaAsNode(typeof(PocoDisallowingUnmappedMembers)); + JsonNode? jsonWithUnmappedProperties = JsonNode.Parse("""{ "UnmappedProperty" : {} }"""); + AssertDoesNotMatchSchema(schema, jsonWithUnmappedProperties); + } + + [Fact] + public void GetJsonSchemaAsNode_NullInputs_ThrowsArgumentNullException() + { + Assert.Throws(() => ((JsonSerializerOptions)null!).GetJsonSchemaAsNode(typeof(int))); + Assert.Throws(() => Serializer.DefaultOptions.GetJsonSchemaAsNode((Type)null!)); + Assert.Throws(() => ((JsonTypeInfo)null!).GetJsonSchemaAsNode()); + } + + [Fact] + public void GetJsonSchemaAsNode_NoResolver_ThrowInvalidOperationException() + { + var options = new JsonSerializerOptions(); + Assert.Throws(() => options.GetJsonSchemaAsNode(typeof(int))); + } + + [Fact] + public void JsonSerializerOptions_SmallMaxDepth_ThrowsInvalidOperationException() + { + var options = new JsonSerializerOptions(Serializer.DefaultOptions) { MaxDepth = 1 }; + var ex = Assert.Throws(() => options.GetJsonSchemaAsNode(typeof(PocoWithRecursiveMembers))); + Assert.Contains("depth", ex.Message); + } + + [Fact] + public void ReferenceHandlePreserve_Enabled_ThrowsNotSupportedException() + { + var options = new JsonSerializerOptions(Serializer.DefaultOptions) { ReferenceHandler = ReferenceHandler.Preserve }; + options.MakeReadOnly(); + + var ex = Assert.Throws(() => options.GetJsonSchemaAsNode(typeof(SimplePoco))); + Assert.Contains("ReferenceHandler.Preserve", ex.Message); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void JsonSchemaExporterOptions_DefaultSettings(bool useSingleton) + { + JsonSchemaExporterOptions options = useSingleton ? JsonSchemaExporterOptions.Default : new(); + + Assert.False(options.TreatNullObliviousAsNonNullable); + Assert.Null(options.TransformSchemaNode); + } + + [Fact] + public void JsonSchemaExporterOptions_Default_IsSame() + { + Assert.Same(JsonSchemaExporterOptions.Default, JsonSchemaExporterOptions.Default); + } + + protected void AssertValidJsonSchema(Type type, string expectedJsonSchema, JsonNode actualJsonSchema) + { + JsonNode? expectedJsonSchemaNode = JsonNode.Parse(expectedJsonSchema, documentOptions: new() { CommentHandling = JsonCommentHandling.Skip, AllowTrailingCommas = true }); + + if (!JsonNode.DeepEquals(expectedJsonSchemaNode, actualJsonSchema)) + { + throw new XunitException($""" + Generated schema does not match the expected specification. + Expected: + {FormatJson(expectedJsonSchemaNode)} + Actual: + {FormatJson(actualJsonSchema)} + """); + } + } + + protected void AssertDocumentMatchesSchema(JsonNode schema, JsonNode? instance) + { + EvaluationResults results = EvaluateSchemaCore(schema, instance); + if (!results.IsValid) + { + IEnumerable errors = results.Details + .Where(d => d.HasErrors) + .SelectMany(d => d.Errors!.Select(error => $"Path:${d.InstanceLocation} {error.Key}:{error.Value}")); + + throw new XunitException($""" + Instance JSON document does not match the specified schema. + Schema: + {FormatJson(schema)} + Instance: + {FormatJson(instance)} + Errors: + {string.Join(Environment.NewLine, errors)} + """); + } + } + + protected void AssertDoesNotMatchSchema(JsonNode schema, JsonNode? instance) + { + EvaluationResults results = EvaluateSchemaCore(schema, instance); + if (results.IsValid) + { + throw new XunitException($""" + Instance JSON document matches the specified schema. + Schema: + {FormatJson(schema)} + Instance: + {FormatJson(instance)} + """); + } + } + + private EvaluationResults EvaluateSchemaCore(JsonNode schema, JsonNode? instance) + { + JsonSchema jsonSchema = JsonSchema.FromText(schema.ToJsonString()); + return jsonSchema.Evaluate(instance, s_evaluationOptions); + } + + private static readonly EvaluationOptions s_evaluationOptions = new() + { + OutputFormat = OutputFormat.List, + RequireFormatValidation = true, + }; + + private string FormatJson(JsonNode? node) => + JsonSerializer.Serialize(node, _indentedOptions); + } +} diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/JsonSchemaExporterTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/JsonSchemaExporterTests.cs new file mode 100644 index 0000000000000..f188c0f241909 --- /dev/null +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/JsonSchemaExporterTests.cs @@ -0,0 +1,128 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Reflection; +using System.Runtime.Serialization; +using System.Text.Json.Nodes; +using System.Text.Json.Schema.Tests; +using System.Text.Json.Serialization; + +namespace System.Text.Json.SourceGeneration.Tests +{ + public sealed partial class JsonSchemaExporterTests_SourceGen() + : JsonSchemaExporterTests(new StringSerializerWrapper(TestTypesContext.Default)) + { + [JsonSerializable(typeof(object))] + [JsonSerializable(typeof(bool))] + [JsonSerializable(typeof(byte))] + [JsonSerializable(typeof(ushort))] + [JsonSerializable(typeof(uint))] + [JsonSerializable(typeof(ulong))] + [JsonSerializable(typeof(sbyte))] + [JsonSerializable(typeof(short))] + [JsonSerializable(typeof(int))] + [JsonSerializable(typeof(long))] + [JsonSerializable(typeof(float))] + [JsonSerializable(typeof(double))] + [JsonSerializable(typeof(decimal))] +#if NETCOREAPP + [JsonSerializable(typeof(UInt128))] + [JsonSerializable(typeof(Int128))] + [JsonSerializable(typeof(Half))] +#endif + [JsonSerializable(typeof(string))] + [JsonSerializable(typeof(char))] + [JsonSerializable(typeof(byte[]))] + [JsonSerializable(typeof(Memory))] + [JsonSerializable(typeof(ReadOnlyMemory))] + [JsonSerializable(typeof(DateTime))] + [JsonSerializable(typeof(DateTimeOffset))] + [JsonSerializable(typeof(TimeSpan))] +#if NETCOREAPP + [JsonSerializable(typeof(DateOnly))] + [JsonSerializable(typeof(TimeOnly))] +#endif + [JsonSerializable(typeof(Guid))] + [JsonSerializable(typeof(Uri))] + [JsonSerializable(typeof(Version))] + [JsonSerializable(typeof(JsonDocument))] + [JsonSerializable(typeof(JsonElement))] + [JsonSerializable(typeof(JsonNode))] + [JsonSerializable(typeof(JsonValue))] + [JsonSerializable(typeof(JsonObject))] + [JsonSerializable(typeof(JsonArray))] + // Unsupported types + [JsonSerializable(typeof(Type))] + [JsonSerializable(typeof(MethodInfo))] + [JsonSerializable(typeof(UIntPtr))] + [JsonSerializable(typeof(MemberInfo))] + [JsonSerializable(typeof(SerializationInfo))] + [JsonSerializable(typeof(Func))] + // Enum types + [JsonSerializable(typeof(IntEnum))] + [JsonSerializable(typeof(StringEnum))] + [JsonSerializable(typeof(FlagsStringEnum))] + // Nullable types + [JsonSerializable(typeof(bool?))] + [JsonSerializable(typeof(int?))] + [JsonSerializable(typeof(double?))] + [JsonSerializable(typeof(Guid?))] + [JsonSerializable(typeof(JsonElement?))] + [JsonSerializable(typeof(IntEnum?))] + [JsonSerializable(typeof(StringEnum?))] + [JsonSerializable(typeof(SimpleRecordStruct?))] + // User-defined POCOs + [JsonSerializable(typeof(SimplePoco))] + [JsonSerializable(typeof(SimpleRecord))] + [JsonSerializable(typeof(SimpleRecordStruct))] + [JsonSerializable(typeof(RecordWithOptionalParameters))] + [JsonSerializable(typeof(PocoWithRequiredMembers))] + [JsonSerializable(typeof(PocoWithIgnoredMembers))] + [JsonSerializable(typeof(PocoWithCustomNaming))] + [JsonSerializable(typeof(PocoWithCustomNumberHandling))] + [JsonSerializable(typeof(PocoWithCustomNumberHandlingOnProperties))] + [JsonSerializable(typeof(PocoWithRecursiveMembers))] + [JsonSerializable(typeof(PocoWithRecursiveCollectionElement))] + [JsonSerializable(typeof(PocoWithRecursiveDictionaryValue))] + [JsonSerializable(typeof(PocoWithDescription))] + [JsonSerializable(typeof(PocoWithCustomConverter))] + [JsonSerializable(typeof(PocoWithCustomPropertyConverter))] + [JsonSerializable(typeof(PocoWithEnums))] + [JsonSerializable(typeof(PocoWithStructFollowedByNullableStruct))] + [JsonSerializable(typeof(PocoWithNullableStructFollowedByStruct))] + [JsonSerializable(typeof(PocoWithExtensionDataProperty))] + [JsonSerializable(typeof(PocoDisallowingUnmappedMembers))] + [JsonSerializable(typeof(PocoWithNullableAnnotationAttributes))] + [JsonSerializable(typeof(PocoWithNullableAnnotationAttributesOnConstructorParams))] + [JsonSerializable(typeof(PocoWithNullableConstructorParameter))] + [JsonSerializable(typeof(PocoWithOptionalConstructorParams))] + [JsonSerializable(typeof(GenericPocoWithNullableConstructorParameter))] + [JsonSerializable(typeof(PocoWithPolymorphism))] + [JsonSerializable(typeof(DiscriminatedUnion))] + [JsonSerializable(typeof(NonAbstractClassWithSingleDerivedType))] + [JsonSerializable(typeof(PocoCombiningPolymorphicTypeAndDerivedTypes))] + [JsonSerializable(typeof(ClassWithComponentModelAttributes))] + [JsonSerializable(typeof(ClassWithJsonPointerEscapablePropertyNames))] + // Collection types + [JsonSerializable(typeof(int[]))] + [JsonSerializable(typeof(List))] + [JsonSerializable(typeof(HashSet))] + [JsonSerializable(typeof(Queue))] + [JsonSerializable(typeof(Stack))] + [JsonSerializable(typeof(ImmutableArray))] + [JsonSerializable(typeof(ImmutableList))] + [JsonSerializable(typeof(ImmutableQueue))] + [JsonSerializable(typeof(object[]))] + [JsonSerializable(typeof(System.Collections.ArrayList))] + [JsonSerializable(typeof(Dictionary))] + [JsonSerializable(typeof(SortedDictionary))] + [JsonSerializable(typeof(Dictionary))] + [JsonSerializable(typeof(Dictionary))] + [JsonSerializable(typeof(Hashtable))] + [JsonSerializable(typeof(StructDictionary))] + public partial class TestTypesContext : JsonSerializerContext; + } +} diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.targets b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.targets index 7de497308e6ba..eede2cd52eeb0 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.targets +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.targets @@ -83,6 +83,8 @@ + + @@ -122,6 +124,7 @@ + @@ -144,6 +147,11 @@ + + + + + diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/JsonSchemaExporterTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/JsonSchemaExporterTests.cs new file mode 100644 index 0000000000000..373c47b4fa604 --- /dev/null +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/JsonSchemaExporterTests.cs @@ -0,0 +1,9 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text.Json.Serialization.Tests; + +namespace System.Text.Json.Schema.Tests +{ + public sealed class JsonSchemaExporterTests_Reflection() : JsonSchemaExporterTests(JsonSerializerWrapper.StringSerializer); +} diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj index 72a7a32210a31..5a69d16fe4093 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj @@ -67,6 +67,8 @@ + + @@ -175,6 +177,7 @@ + @@ -311,9 +314,14 @@ + + + + +