diff --git a/src/coreclr/System.Private.CoreLib/src/System/String.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/String.CoreCLR.cs index 002d59ec44e406..bc122af03ff943 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/String.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/String.CoreCLR.cs @@ -11,11 +11,13 @@ namespace System { public partial class String { + [Intrinsic] [MethodImpl(MethodImplOptions.InternalCall)] [RequiresUnsafe] internal static extern unsafe string FastAllocateString(MethodTable *pMT, nint length); [DebuggerHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static unsafe string FastAllocateString(nint length) { return FastAllocateString(TypeHandle.TypeHandleOf().AsMethodTable(), length); diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index 1b846460e89c8b..376ad6daf52f05 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -2086,6 +2086,10 @@ GenTree* Compiler::getArrayLengthFromAllocation(GenTree* tree) assert((arrayLength == nullptr) || ((optMethodFlags & OMF_HAS_NEWARRAY) != 0)); } + else if (call->IsSpecialIntrinsic(this, NI_System_String_FastAllocateString)) + { + arrayLength = call->gtArgs.GetUserArgByIndex(1)->GetNode(); + } } if (arrayLength != nullptr) diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index 6ef07513ee4b10..a7e7473ac475dc 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -4029,6 +4029,7 @@ GenTree* Compiler::impIntrinsic(CORINFO_CLASS_HANDLE clsHnd, } case NI_System_ArgumentNullException_ThrowIfNull: + case NI_System_String_FastAllocateString: isSpecial = true; break; @@ -10727,6 +10728,10 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method) { result = NI_System_String_Equals; } + else if (strcmp(methodName, "FastAllocateString") == 0) + { + result = NI_System_String_FastAllocateString; + } else if (strcmp(methodName, "get_Chars") == 0) { result = NI_System_String_get_Chars; diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index a48e09d08bb82e..11822d9cb7b1d2 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -4377,8 +4377,14 @@ GenTree* Compiler::fgMorphPotentialTailCall(GenTreeCall* call) if (call->IsSpecialIntrinsic()) { - failTailCall("Might turn into an intrinsic"); - return nullptr; + // FastAllocateString is marked as a special intrinsic only to give the JIT + // hints about its return value (non-null, length tracked by VN). It is not + // re-expanded, so it is safe to tail call. + if (!call->IsSpecialIntrinsic(this, NI_System_String_FastAllocateString)) + { + failTailCall("Might turn into an intrinsic"); + return nullptr; + } } #ifdef TARGET_ARM diff --git a/src/coreclr/jit/namedintrinsiclist.h b/src/coreclr/jit/namedintrinsiclist.h index 0a0bd8805859c3..81141e9340e712 100644 --- a/src/coreclr/jit/namedintrinsiclist.h +++ b/src/coreclr/jit/namedintrinsiclist.h @@ -139,6 +139,7 @@ enum NamedIntrinsic : unsigned short NI_System_Runtime_InteropService_MemoryMarshal_GetArrayDataReference, NI_System_String_Equals, + NI_System_String_FastAllocateString, NI_System_String_get_Chars, NI_System_String_get_Length, NI_System_String_op_Implicit, diff --git a/src/coreclr/jit/valuenum.cpp b/src/coreclr/jit/valuenum.cpp index 6570e0c5965162..36f3c6e6192396 100644 --- a/src/coreclr/jit/valuenum.cpp +++ b/src/coreclr/jit/valuenum.cpp @@ -2564,8 +2564,10 @@ ValueNum ValueNumStore::VNForFunc(var_types typ, VNFunc func, ValueNum arg0VN) } // Case 4: ARR_LENGTH(new T[(long)size]) -> size + // ARR_LENGTH(String.FastAllocateString(pMT, (long)size)) -> size VNFuncApp newArrFuncApp; - if (GetVNFunc(arg0VN, &newArrFuncApp) && (newArrFuncApp.m_func == VNF_JitNewArr)) + if (GetVNFunc(arg0VN, &newArrFuncApp) && + ((newArrFuncApp.m_func == VNF_JitNewArr) || (newArrFuncApp.m_func == VNF_StrFastAllocate))) { ValueNum actualSizeVN = VNIgnoreIntToLongCast(newArrFuncApp.m_args[1]); if (TypeOfVN(actualSizeVN) == TYP_INT) @@ -14112,6 +14114,7 @@ void Compiler::fgValueNumberHelperCallFunc(GenTreeCall* call, VNFunc vnf, ValueN } break; + case VNF_StrFastAllocate: case VNF_JitNewArr: case VNF_JitNewLclArr: { @@ -14344,6 +14347,13 @@ bool Compiler::fgValueNumberSpecialIntrinsic(GenTreeCall* call) switch (lookupNamedIntrinsic(call->gtCallMethHnd)) { + case NI_System_String_FastAllocateString: + { + assert(call->gtArgs.CountUserArgs() == 2); + fgValueNumberHelperCallFunc(call, VNF_StrFastAllocate, ValueNumStore::VNPForEmptyExcSet()); + return true; + } + case NI_System_Type_GetTypeFromHandle: { // Optimize Type.GetTypeFromHandle(TypeHandleToRuntimeTypeHandle(clsHandle)) to a frozen handle. diff --git a/src/coreclr/jit/valuenumfuncs.h b/src/coreclr/jit/valuenumfuncs.h index 094153d578df1f..28a494b95b753d 100644 --- a/src/coreclr/jit/valuenumfuncs.h +++ b/src/coreclr/jit/valuenumfuncs.h @@ -162,6 +162,7 @@ ValueNumFuncDef(JitNewMdArr, 4, false, true) ValueNumFuncDef(JitReadyToRunNew, 2, false, true) ValueNumFuncDef(JitReadyToRunNewArr, 3, false, true) ValueNumFuncDef(JitReadyToRunNewLclArr, 3, false, true) +ValueNumFuncDef(StrFastAllocate, 3, false, true) // Args: 0: MethodTable, 1: length, 2: unique VN. ValueNumFuncDef(Box, 3, false, true) ValueNumFuncDef(BoxNullable, 3, false, false)