@@ -1842,6 +1842,157 @@ GenTree* Lowering::AddrGen(void* addr)
1842
1842
return AddrGen((ssize_t)addr);
1843
1843
}
1844
1844
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!\nOld 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("\nNew tree:\n");
1991
+ DISPTREERANGE(BlockRange(), storeBlk);
1992
+ *next = storeBlk;
1993
+ return true;
1994
+ }
1995
+
1845
1996
//------------------------------------------------------------------------
1846
1997
// LowerCallMemmove: Replace Buffer.Memmove(DST, SRC, CNS_SIZE) with a GT_STORE_BLK:
1847
1998
// Do the same for CORINFO_HELP_MEMCPY(DST, SRC, CNS_SIZE)
@@ -2221,11 +2372,32 @@ GenTree* Lowering::LowerCall(GenTree* node)
2221
2372
GenTree* nextNode = nullptr;
2222
2373
if (call->gtCallMoreFlags & GTF_CALL_M_SPECIAL_INTRINSIC)
2223
2374
{
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))
2227
2376
{
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;
2229
2401
}
2230
2402
}
2231
2403
@@ -2234,6 +2406,12 @@ GenTree* Lowering::LowerCall(GenTree* node)
2234
2406
{
2235
2407
return nextNode;
2236
2408
}
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
+ }
2237
2415
#endif
2238
2416
2239
2417
call->ClearOtherRegs();
0 commit comments