@@ -1842,6 +1842,157 @@ GenTree* Lowering::AddrGen(void* addr)
18421842 return AddrGen ((ssize_t )addr);
18431843}
18441844
1845+ // LowerCallMemset: Replaces the following memset-like special intrinsics:
1846+ //
1847+ // SpanHelpers.Fill<T>(ref dstRef, CNS_SIZE, CNS_VALUE)
1848+ // CORINFO_HELP_MEMSET(ref dstRef, CNS_VALUE, CNS_SIZE)
1849+ // SpanHelpers.ClearWithoutReferences(ref dstRef, CNS_SIZE)
1850+ //
1851+ // with a GT_STORE_BLK node:
1852+ //
1853+ // * STORE_BLK struct<CNS_SIZE> (init) (Unroll)
1854+ // +--* LCL_VAR byref dstRef
1855+ // \--* CNS_INT int 0
1856+ //
1857+ // Arguments:
1858+ // tree - GenTreeCall node to replace with STORE_BLK
1859+ // next - [out] Next node to lower if this function returns true
1860+ //
1861+ // Return Value:
1862+ // false if no changes were made
1863+ //
1864+ bool Lowering::LowerCallMemset (GenTreeCall* call, GenTree** next)
1865+ {
1866+ assert (call->IsSpecialIntrinsic (comp, NI_System_SpanHelpers_Fill) ||
1867+ call->IsSpecialIntrinsic (comp, NI_System_SpanHelpers_ClearWithoutReferences) ||
1868+ call->IsHelperCall (comp, CORINFO_HELP_MEMSET));
1869+
1870+ JITDUMP (" Considering Memset-like call [%06d] for unrolling.. " , comp->dspTreeID (call))
1871+
1872+ if (comp->info .compHasNextCallRetAddr )
1873+ {
1874+ JITDUMP (" compHasNextCallRetAddr=true so we won't be able to remove the call - bail out.\n " );
1875+ return false ;
1876+ }
1877+
1878+ GenTree* dstRefArg = call->gtArgs .GetUserArgByIndex (0 )->GetNode ();
1879+ GenTree* lengthArg;
1880+ GenTree* valueArg;
1881+
1882+ // Fill<T>'s length is not in bytes, so we need to scale it depending on the signature
1883+ unsigned lengthScale;
1884+
1885+ if (call->IsSpecialIntrinsic (comp, NI_System_SpanHelpers_Fill))
1886+ {
1887+ // void SpanHelpers::Fill<T>(ref T refData, nuint numElements, T value)
1888+ //
1889+ assert (call->gtArgs .CountUserArgs () == 3 );
1890+ lengthArg = call->gtArgs .GetUserArgByIndex (1 )->GetNode ();
1891+ CallArg* valueCallArg = call->gtArgs .GetUserArgByIndex (2 );
1892+ valueArg = valueCallArg->GetNode ();
1893+
1894+ // Get that <T> from the signature
1895+ lengthScale = genTypeSize (valueCallArg->GetSignatureType ());
1896+ // NOTE: structs and TYP_REF will be ignored by the "Value is not a constant" check
1897+ // Some of those cases can be enabled in future, e.g. s
1898+ }
1899+ else if (call->IsHelperCall (comp, CORINFO_HELP_MEMSET))
1900+ {
1901+ // void CORINFO_HELP_MEMSET(ref T refData, byte value, nuint numElements)
1902+ //
1903+ assert (call->gtArgs .CountUserArgs () == 3 );
1904+ lengthArg = call->gtArgs .GetUserArgByIndex (2 )->GetNode ();
1905+ valueArg = call->gtArgs .GetUserArgByIndex (1 )->GetNode ();
1906+ lengthScale = 1 ; // it's always in bytes
1907+ }
1908+ else
1909+ {
1910+ // void SpanHelpers::ClearWithoutReferences(ref byte b, nuint byteLength)
1911+ //
1912+ assert (call->IsSpecialIntrinsic (comp, NI_System_SpanHelpers_ClearWithoutReferences));
1913+ assert (call->gtArgs .CountUserArgs () == 2 );
1914+
1915+ // Simple zeroing
1916+ lengthArg = call->gtArgs .GetUserArgByIndex (1 )->GetNode ();
1917+ valueArg = comp->gtNewZeroConNode (TYP_INT);
1918+ lengthScale = 1 ; // it's always in bytes
1919+ }
1920+
1921+ if (!lengthArg->IsIntegralConst ())
1922+ {
1923+ JITDUMP (" Length is not a constant - bail out.\n " );
1924+ return false ;
1925+ }
1926+
1927+ if (!valueArg->IsCnsIntOrI () || !valueArg->TypeIs (TYP_INT))
1928+ {
1929+ JITDUMP (" Value is not a constant - bail out.\n " );
1930+ return false ;
1931+ }
1932+
1933+ // If value is not zero, we can only unroll for single-byte values
1934+ if (!valueArg->IsIntegralConst (0 ) && (lengthScale != 1 ))
1935+ {
1936+ JITDUMP (" Value is not unroll-friendly - bail out.\n " );
1937+ return false ;
1938+ }
1939+
1940+ // Convert lenCns to bytes
1941+ ssize_t lenCns = lengthArg->AsIntCon ()->IconValue ();
1942+ if (CheckedOps::MulOverflows ((target_ssize_t )lenCns, (target_ssize_t )lengthScale, CheckedOps::Signed))
1943+ {
1944+ // lenCns overflows
1945+ JITDUMP (" lenCns * lengthScale overflows - bail out.\n " )
1946+ return false ;
1947+ }
1948+ lenCns *= (ssize_t )lengthScale;
1949+
1950+ // TODO-CQ: drop the whole thing in case of lenCns = 0
1951+ if ((lenCns <= 0 ) || (lenCns > (ssize_t )comp->getUnrollThreshold (Compiler::UnrollKind::Memset)))
1952+ {
1953+ JITDUMP (" Size is either 0 or too big to unroll - bail out.\n " )
1954+ return false ;
1955+ }
1956+
1957+ JITDUMP (" Accepted for unrolling!\n Old tree:\n " );
1958+ DISPTREERANGE (BlockRange (), call);
1959+
1960+ if (!valueArg->IsIntegralConst (0 ))
1961+ {
1962+ // Non-zero (byte) value, wrap value with GT_INIT_VAL
1963+ GenTree* initVal = valueArg;
1964+ valueArg = comp->gtNewOperNode (GT_INIT_VAL, TYP_INT, initVal);
1965+ BlockRange ().InsertAfter (initVal, valueArg);
1966+ }
1967+
1968+ GenTreeBlk* storeBlk =
1969+ comp->gtNewStoreBlkNode (comp->typGetBlkLayout ((unsigned )lenCns), dstRefArg, valueArg, GTF_IND_UNALIGNED);
1970+ storeBlk->gtBlkOpKind = GenTreeBlk::BlkOpKindUnroll;
1971+
1972+ // Insert/Remove trees into LIR
1973+ BlockRange ().InsertBefore (call, storeBlk);
1974+ if (call->IsSpecialIntrinsic (comp, NI_System_SpanHelpers_ClearWithoutReferences))
1975+ {
1976+ // Value didn't exist in LIR previously
1977+ BlockRange ().InsertBefore (storeBlk, valueArg);
1978+ }
1979+
1980+ // Remove the call and mark everything as unused ...
1981+ BlockRange ().Remove (call, true );
1982+ // ... except the args we're going to re-use
1983+ dstRefArg->ClearUnusedValue ();
1984+ valueArg->ClearUnusedValue ();
1985+ if (valueArg->OperIs (GT_INIT_VAL))
1986+ {
1987+ valueArg->gtGetOp1 ()->ClearUnusedValue ();
1988+ }
1989+
1990+ JITDUMP (" \n New tree:\n " );
1991+ DISPTREERANGE (BlockRange (), storeBlk);
1992+ *next = storeBlk;
1993+ return true ;
1994+ }
1995+
18451996// ------------------------------------------------------------------------
18461997// LowerCallMemmove: Replace Buffer.Memmove(DST, SRC, CNS_SIZE) with a GT_STORE_BLK:
18471998// Do the same for CORINFO_HELP_MEMCPY(DST, SRC, CNS_SIZE)
@@ -2221,11 +2372,32 @@ GenTree* Lowering::LowerCall(GenTree* node)
22212372 GenTree* nextNode = nullptr ;
22222373 if (call->gtCallMoreFlags & GTF_CALL_M_SPECIAL_INTRINSIC)
22232374 {
2224- NamedIntrinsic ni = comp->lookupNamedIntrinsic (call->gtCallMethHnd );
2225- if (((ni == NI_System_Buffer_Memmove) && LowerCallMemmove (call, &nextNode)) ||
2226- ((ni == NI_System_SpanHelpers_SequenceEqual) && LowerCallMemcmp (call, &nextNode)))
2375+ switch (comp->lookupNamedIntrinsic (call->gtCallMethHnd ))
22272376 {
2228- return nextNode;
2377+ case NI_System_Buffer_Memmove:
2378+ if (LowerCallMemmove (call, &nextNode))
2379+ {
2380+ return nextNode;
2381+ }
2382+ break ;
2383+
2384+ case NI_System_SpanHelpers_SequenceEqual:
2385+ if (LowerCallMemcmp (call, &nextNode))
2386+ {
2387+ return nextNode;
2388+ }
2389+ break ;
2390+
2391+ case NI_System_SpanHelpers_Fill:
2392+ case NI_System_SpanHelpers_ClearWithoutReferences:
2393+ if (LowerCallMemset (call, &nextNode))
2394+ {
2395+ return nextNode;
2396+ }
2397+ break ;
2398+
2399+ default :
2400+ break ;
22292401 }
22302402 }
22312403
@@ -2234,6 +2406,12 @@ GenTree* Lowering::LowerCall(GenTree* node)
22342406 {
22352407 return nextNode;
22362408 }
2409+
2410+ // Try to lower CORINFO_HELP_MEMSET to unrollable STORE_BLK
2411+ if (call->IsHelperCall (comp, CORINFO_HELP_MEMSET) && LowerCallMemset (call, &nextNode))
2412+ {
2413+ return nextNode;
2414+ }
22372415#endif
22382416
22392417 call->ClearOtherRegs ();
0 commit comments