Closed jkotas closed 1 year ago
Tagging subscribers to this area: @JulieLeeMSFT See info in area-owners.md if you want to be subscribed.
Author: | jkotas |
---|---|
Assignees: | - |
Labels: | `area-CodeGen-coreclr`, `untriaged` |
Milestone: | - |
Could this be caused by the use of Vector<T>
in string.Replace(char, char)
, and at runtime it's detected that AVX(2) is available, so the method gets JITed?
Cc @brianrob
crossgen with --singlemethodname Replace --singlemethodtypename System.String --singlemethodindex 3 --verbose
:
Info: Method `string.Replace(char,char)` was not compiled because `[S.P.CoreLib]System.Numerics.Vector`1<uint16>` requires runtime JIT
--instruction-set:avx2
fixes it
Could this be caused by the use of Vector
in string.Replace(char, char), and at runtime it's detected that AVX(2) is available, so the method gets JITed?
Yes, it is probably related. However, we should have enough flexibility in crossgen now to make it possible to precompile this successfully (as long as the app is running on current hardware at least).
Related: https://github.com/dotnet/designs/pull/173 (and https://github.com/dotnet/core/issues/7131) I assume we're going to default to Avx2 so it kinds of fixes itself
@trylek @dotnet/crossgen-contrib @richlander . Yeah we have been discussing compiling with avx2 by default.
As @EgorBo raises, it seems like the base question on why the R2R code cannot be used as-is is the basic question. If all non-AVX2 code is thrown out, then that R2R code is useless, right?
Ideally, the following is true:
As we all know, we are not doing this today. For this conversation, it seems like the first topic is the concern.
As @EgorBo raises, it seems like the base question on why the R2R code cannot be used as-is is the basic question. If all non-AVX2 code is thrown out, then that R2R code is useless, right?
Ideally, the following is true:
- All R2R code is used (not just in weird scenarios, but commonly).
- R2R code is optimized for modern hardware.
- R2R code includes high-value (currently) JIT-only optimizations, like guarded de-virtualization, at least for known high-value methods (call-sites or targets).
- The JIT optimizes high-value methods at runtime (tiered comp, dynamic PGO, ...).
As we all know, we are not doing this today. For this conversation, it seems like the first topic is the concern.
From my understanding the main problem that Vector<T>
behaves differently in R2R'd code and Tier1 because it's basically of different widths for them, consider the following example:
// This method is Jitted (uses AVX2)
void Method1()
{
Console.WriteLine(Method2(new Vector<long>(1)));
// Expected: {2,2,2,2}
// Actual: {2,2,1,1} or {2,2,0,0} or {2,2,garbage values}
}
// This method is R2R'd with SSE2
Vector<long> Method2(Vector<long> v)
{
return v * 2; // {v.long0 * 2, v.long1 * 2 }
}
So we don't allow this to happen and when we see Vector
if (Sse2.IsSupported)
{
}
else if (Vector.IsHardwareAccelerated)
{
Vector<T>
}
Right. That's my understanding, too. To my mind, our approach doesn't make sense. Why bother R2R-compiling methods that won't be used on most hardware? We should either compile those methods for the most common hardware (not SSE2) or not compile them at all.
non-trimmed path
trimmed? Do you mean tiered?
trimmed? Do you mean tiered?
Trimmed, but it's rather a wrong word here, consider the following example:
public static void Test1()
{
if (Sse2.IsSupported)
{
Console.WriteLine(Vector128.Create(2) * 2);
}
else
{
Console.WriteLine(new Vector<int>(2) * 2);
}
}
public static void Test2(bool cond)
{
if (cond && Sse2.IsSupported) // the only difference is `cond &&`
{
Console.WriteLine(Vector128.Create(2) * 2);
}
else
{
Console.WriteLine(new Vector<int>(2) * 2);
}
}
When we run crossgen for this snippet only Test1
is successfully precompiled because the code path with Vector<T>
is not even used as if (Sse2.IsSupported)
is always true. Test2
fails to precompile because it's not clear whether Vector<T>
path will be used or not - it depends on unknow (at compile time) cond
so we will have to always JIT Test2
on start.
Compiling [ConsoleApp23]Program..ctor()
Compiling [ConsoleApp23]Program.Test2(bool)
Info: Method `[ConsoleApp23]Program.Test2(bool)` was not compiled because `This function is using SIMD intrinsics, their size is machine specific` requires runtime JIT
Compiling [ConsoleApp23]Program.Test1()
Compiling [ConsoleApp23]Program.Main()
Processing 0 dependencies
Processing 1 dependencies
Moved to phase 1
Emitting R2R PE file:
There are few potentional solutions for the String.Replace
problem:
1) When we prejit code and meet if (Vector.IsHardwareAccelerated)
(if AVX2 is not enabled for crossgen) we replace it with false
-- should allow us to use the fallback user provided for their algorithm. The only problem that user's fallback might be throw new PlatformException("what kind of CPU is that? A potato? At least SSE2 or Neon is expected");
2) Duplicate code under if (Vector.IsHardwareAccelerated)
to use SSE and AVX in preJIT
3) Return true
for Vector.IsHardwareAccelerated
but use "software" fallbacks for all Vector<>
functions
4) Just address https://github.com/dotnet/designs/pull/173 and we will be fine as is - AVX2 will always be available for crossgen to emit (might be painful for e.g. Rosetta)
List of methods in System.Private.CoreLib.dll
which aren't prejitted due to missing AVX2 cap:
Info: Method `[S.P.CoreLib]System.Text.Unicode.Utf16Utility.GetPointerToFirstInvalidChar(char*,int32,int64&,int32&)` was not compiled because `[S.P.CoreLib]System.Numerics.Vector`1<uint16>` requires runtime JIT
Info: Method `[S.P.CoreLib]System.Text.Latin1Utility.WidenLatin1ToUtf16_Fallback(uint8*,char*,native uint)` was not compiled because `[S.P.CoreLib]System.Numerics.Vector`1<uint8>` requires runtime JIT
Info: Method `[S.P.CoreLib]System.Text.Latin1Utility.NarrowUtf16ToLatin1(char*,uint8*,native uint)` was not compiled because `[S.P.CoreLib]System.Numerics.Vector`1<uint16>` requires runtime JIT
Info: Method `[S.P.CoreLib]System.Text.Latin1Utility.GetIndexOfFirstNonLatin1Char_Default(char*,native uint)` was not compiled because `[S.P.CoreLib]System.Numerics.Vector`1<uint16>` requires runtime JIT
Info: Method `[S.P.CoreLib]System.Text.ASCIIUtility.WidenAsciiToUtf16(uint8*,char*,native uint)` was not compiled because `[S.P.CoreLib]System.Numerics.Vector`1<int8>` requires runtime JIT
Info: Method `[S.P.CoreLib]System.Text.ASCIIUtility.NarrowUtf16ToAscii(char*,uint8*,native uint)` was not compiled because `[S.P.CoreLib]System.Numerics.Vector`1<uint16>` requires runtime JIT
Info: Method `[S.P.CoreLib]System.Text.ASCIIUtility.GetIndexOfFirstNonAsciiChar_Default(char*,native uint)` was not compiled because `[S.P.CoreLib]System.Numerics.Vector`1<uint16>` requires runtime JIT
Info: Method `[S.P.CoreLib]System.SpanHelpers.Fill<__Canon>(__Canon&,native uint,__Canon)` was not compiled because `[S.P.CoreLib]System.Numerics.Vector`1<uint8>` requires runtime JIT
Info: Method `[S.P.CoreLib]System.SpanHelpers.LastIndexOfAny(uint8&,uint8,uint8,uint8,int32)` was not compiled because `[S.P.CoreLib]System.Numerics.Vector`1<uint8>` requires runtime JIT
Info: Method `[S.P.CoreLib]System.SpanHelpers.LastIndexOfAny(uint8&,uint8,uint8,int32)` was not compiled because `[S.P.CoreLib]System.Numerics.Vector`1<uint8>` requires runtime JIT
Info: Method `string.Replace(char,char)` was not compiled because `[S.P.CoreLib]System.Numerics.Vector`1<uint16>` requires runtime JIT
Info: Method `[S.P.CoreLib]System.SpanHelpers.Fill<char>(char&,native uint,char)` was not compiled because `[S.P.CoreLib]System.Numerics.Vector`1<uint8>` requires runtime JIT
Info: Method `[S.P.CoreLib]System.SpanHelpers.Fill<native int>(native int&,native uint,native int)` was not compiled because `[S.P.CoreLib]System.Numerics.Vector`1<uint8>` requires runtime JIT
Info: Method `[S.P.CoreLib]System.SpanHelpers.Fill<int32>(int32&,native uint,int32)` was not compiled because `[S.P.CoreLib]System.Numerics.Vector`1<uint8>` requires runtime JIT
Info: Method `[S.P.CoreLib]System.SpanHelpers.Fill<GuidResult>(GuidResult&,native uint,GuidResult)` was not compiled because `[S.P.CoreLib]System.Numerics.Vector`1<uint8>` requires runtime JIT
Info: Method `[S.P.CoreLib]System.SpanHelpers.Fill<float32>(float32&,native uint,float32)` was not compiled because `[S.P.CoreLib]System.Numerics.Vector`1<uint8>` requires runtime JIT
Info: Method `[S.P.CoreLib]System.SpanHelpers.Fill<uint16>(uint16&,native uint,uint16)` was not compiled because `[S.P.CoreLib]System.Numerics.Vector`1<uint8>` requires runtime JIT
Info: Method `[S.P.CoreLib]System.SpanHelpers.Fill<uint32>(uint32&,native uint,uint32)` was not compiled because `[S.P.CoreLib]System.Numerics.Vector`1<uint8>` requires runtime JIT
Info: Method `[S.P.CoreLib]System.SpanHelpers.Fill<int64>(int64&,native uint,int64)` was not compiled because `[S.P.CoreLib]System.Numerics.Vector`1<uint8>` requires runtime JIT
Info: Method `[S.P.CoreLib]System.SpanHelpers.Fill<uint64>(uint64&,native uint,uint64)` was not compiled because `[S.P.CoreLib]System.Numerics.Vector`1<uint8>` requires runtime JIT
Info: Method `[S.P.CoreLib]System.SpanHelpers.Fill<int16>(int16&,native uint,int16)` was not compiled because `[S.P.CoreLib]System.Numerics.Vector`1<uint8>` requires runtime JIT
Info: Method `[S.P.CoreLib]System.SpanHelpers.IndexOfValueType<int32>(int32&,int32,int32)` was not compiled because `[S.P.CoreLib]System.Numerics.Vector`1<int32>` requires runtime JIT
Info: Method `[S.P.CoreLib]System.SpanHelpers.IndexOfValueType<int64>(int64&,int64,int32)` was not compiled because `[S.P.CoreLib]System.Numerics.Vector`1<int64>` requires runtime JIT
Info: Method `[S.P.CoreLib]System.SpanHelpers.Fill<KeyValuePair`2<__Canon,__Canon>>(KeyValuePair`2<__Canon,__Canon>&,native uint,KeyValuePair`2<__Canon,__Canon>)` was not compiled because `[S.P.CoreLib]System.Numerics.Vector`1<uint8>` requires runtime JIT
Info: Method `[S.P.CoreLib]System.SpanHelpers.Fill<CalendarId>(CalendarId&,native uint,CalendarId)` was not compiled because `[S.P.CoreLib]System.Numerics.Vector`1<uint8>` requires runtime JIT
Info: Method `[S.P.CoreLib]System.SpanHelpers.Fill<float64>(float64&,native uint,float64)` was not compiled because `[S.P.CoreLib]System.Numerics.Vector`1<uint8>` requires runtime JIT
Info: Method `[S.P.CoreLib]System.SpanHelpers.Fill<KeyValuePair`2<SessionInfo,bool>>(KeyValuePair`2<SessionInfo,bool>&,native uint,KeyValuePair`2<SessionInfo,bool>)` was not compiled because `[S.P.CoreLib]System.Numerics.Vector`1<uint8>` requires runtime JIT
Info: Method `[S.P.CoreLib]System.SpanHelpers.Fill<KeyValuePair`2<int32,__Canon>>(KeyValuePair`2<int32,__Canon>&,native uint,KeyValuePair`2<int32,__Canon>)` was not compiled because `[S.P.CoreLib]System.Numerics.Vector`1<uint8>` requires runtime JIT
Info: Method `[S.P.CoreLib]System.SpanHelpers.Fill<SessionInfo>(SessionInfo&,native uint,SessionInfo)` was not compiled because `[S.P.CoreLib]System.Numerics.Vector`1<uint8>` requires runtime JIT
Option 4 seems the most attractive. It's simple. I think we should validate that it is unacceptable before looking at more exotic options.
@ivdiazsa has been doing startup tests with R2R+AVX2 vs retail (what we ship today). He's been able to demonstrate a win with R2R composite but not with our regular crossgen/R2R configuration. That seems odd to me. It's hard for me to imagine that JITing would be faster than no JITing. I'm guessing that there is something else at play. It wouldn't surprise me if there is something else in our system not expecting AVX2 code in our R2R images.
Also, the test that @ivdiazsa was using might not be the best one (TE Plaintext). He's going to switch to a different ASP.NET test that is known to rely more on SIMD. I'm not sure if he was collecting the diff in # of JITed methods with those configuration. That would also be interesting to know. Perhaps @EgorBo could pair with @ivdiazsa to double the brain power on this problem. You know, we could crunch more data with the same clock cycles.
Wouldn't option 4 mean blocking creating non AVX2 R2R images?
@ivdiazsa has been doing startup tests with R2R+AVX2 vs retail (what we ship today). He's been able to demonstrate a win with R2R composite but not with our regular crossgen/R2R configuration. @ivdiazsa was using might not be the best one (TE Plaintext).
I assume it was Platform-Plaintext? I've just tried it on our perflab (linux-citrine 28cores machine) and here is the results:
| application | nor2r | r2r | |
| --------------------- | ------------------------- | ------------------------- | ------- |
| Start Time (ms) | 517 | 193 | -62.67% |
| load | nor2r | r2r | |
| ---------------------- | ----------- | ----------- | ------- |
| First Request (ms) | 97 | 77 | -20.62% |
| Mean latency (ms) | 1.39 | 1.23 | -11.51% |
| Max latency (ms) | 81.27 | 68.26 | -16.01% |
nor2r
is DOTNET_ReadyToRun=0
(don't use R2R) and r2r
is /p:PublishReadyToRun=true
(I didn't even use Composite mode). R2R gives pretty nice start up bonus compared to only-jitting
Wouldn't option 4 mean blocking creating non AVX2 R2R images?
No. What led you to that conclusion?
There are three decision points:
We're exclusively talking about the last topic. The middle topic is also interesting. There is no discussion about the first topic.
I've just tried it on our perflab
That configuration is not what I intended nor what @ivdiazsa is testing.
There are two things to measure:
Ideally, the app is NOT R2R compiled but JITs. That's what most customer apps do. Measuring all R2R is also interesting but a SECONDARY scenario since most customers don't do that.
/p:PublishReadyToRun=true
-- This doesn't produce the second case above.
@richlander sorry, but I am still not sure I understand what is being tested. I fully realize that the most common case is when only BCL libs are prejitted (well, maybe a few 3rd party libs) and the rest is jitted - against what we test this mode? Against noR2R even for BCL?
The whole stack compiled with AVX2.
What does this mean exactly? VM/JIT compiled with mcpu=native
to use AVX2? Or all managed libs are prejitted into a giant Composite R2R?
I'm only talking about R2R not how we configure the native toolset.
AFAIK, the ability to specify AVX2 as the SIMD instruction set is not specific to composite. It was at one point, but I believe that's been resolved. That's certainly something to validate!
I'd expect that all the managed assemblies we have today are R2R compiled the same way as today (not composite) with the addition of a flag to affect SIMD instructions. This necessarily include NetCoreApp and ASP.NET Core frameworks.
We're very close to having container images (hopefully this coming week; it is Sunday here) that have this configuration. That will enable us to do A vs B perf testing much easier. It will also enable smart people inspect the configuration to validate that it is correct. It is VERY EASY to do this wrong. We've been working on this problem for months and have had a lot of problems.
@EgorBo @richlander is this still pending on more perf validation of non-composite AVX2 config?
I'm not sure. Do we have any data that we can share?
Neither am I, the root issue here can be fixed if we bump minimal baseline for R2R (https://github.com/dotnet/designs/pull/173) do we plan to do it in 7.0?
We're not making any more significant changes in 7.0, as you likely know.
We had a fair number of conversations about this topic over the last few months. Here's the basic context/problem:
Did I get that right? @ivdiazsa @mangod9 @davidwrighton @tannergooding
Btw, String.Replace
can be quickly fixed by adding Vector128 path (should also help with small inputs) cc @gfoidl
There are a relatively small number of SIMD-dependent methods whose R2R methods need to be rejected on AVX2-capable machines
IIRC, corelib is special and ignores and works under the presumption that we "do the right thing" and ensure all paths behave identically. While user code has the presumption that the two paths could differ in behavior and so will always throw out the R2R implementation if any path attempted to use an incompatible higher ISA. -- That is, SSE3, SSSE3, SSE4.1, and SSE4.2 are "encoding compatible with" SSE2 and so the if (Isa.IsSupported)
checks get emitted in R2R as actual dynamic checks. AVX, AVX2, FMA, BMI1, BMI2, and others are "encoding incompatible" and so the if (Isa.IsSupported)
checks will force R2R to throw out the code.
There are a few things to then keep in mind for corelib code:
ROSpan<T>
using it internallyVector<T>
will not have any R2R codex86-x64-v3
is the "level" that is the "baseline" for Azure, AWS, and more. It supports AVX/AVX2/BMI1/BMI2/FMA/LZCNT/MOVBE and has been around since ~2013 for Intel, ~2015 for AMD, and is reported by Steam Hardware Survey
to be in 87.78% of reporting hardware.
x86-x64-v2
is the "level" that most emulators (including the x64 on Arm64 emulators provided by Microsoft/Apple) support. It supports SSE3/SSSE3/SSE4.1/SSE4.2/POPCNT/CX16 and has been around since ~2008 for Intel, ~2009 for AMD, and is reported by Steam Hardware Survey
to be in 98.74% of reporting hardware.
x86-x64-v1
is the "level" we currently target. It supports SSE/SSE2/CMOV/CX8 and has been around since x64 was first introduced in 2003. For 32-bit it's been around since around 2000 and is reported by Steam Hardware Survey
to be in 100% of reporting hardware.
The discussion around targeting AVX/AVX2 is really one around targeting x86-x64-v3
. Since targeting it would only negatively impact startup for a likely minority of customers, I think its still worth considering making our baseline and if we, for whatever reason think that this is too much, the x86-x64-v2
is a reasonable alternative.
There is also a consideration that this impacts managed code but not native. Continuing to have the JIT, GC, and other native bits target a 20 year old baseline computer has some negative downsides with the main upside being that devs can "xcopy" their bits onto a flash drive and then run it on any computer.
Even Arm64 has the consideration that armv8.0-a
is "missing" some fairly important instructions (like the atomics
instruction set) and this is a major benefit that Apple M1 sees since it targets an armv8.5-a
baseline instead.
I expect we could see some reasonable perf gains if we experimented with allowing a "less-portable" version of the JIT/GC that targeted a higher baseline (for both x64 and Arm64). I expect that this would be a net benefit for a majority of cases with it being "worse" only for a small subset of users with hardware that more than 15 years old (at which point, they likely aren't running a modern OS and likely have many other considerations around general app usage).
Btw,
String.Replace
can be quickly fixed by adding Vector128 path (should also help with small inputs) cc @gfoidl
is this something which can be done in 7? Otherwise we could move this broader decision around avx2 to 8.
We'll take another run in .NET 8.
we have enabled avx2 for the new "composite" container images in 8. Is there any other specific work required here?
we have enabled avx2 for the new "composite" container images in 8. Is there any other specific work required here?
Just tried Console.WriteLine("Hello world".Replace('e', 'a'));
on the latest daily SDK:
1: JIT compiled System.Runtime.CompilerServices.CastHelpers:StelemRef(System.Array,long,System.Object) [Tier1, IL size=88, code size=93]
2: JIT compiled System.Runtime.CompilerServices.CastHelpers:LdelemaRef(System.Array,long,ulong) [Tier1, IL size=44, code size=44]
3: JIT compiled System.SpanHelpers:IndexOfNullCharacter(ulong) [Instrumented Tier0, IL size=805, code size=1160]
4: JIT compiled System.Guid:TryFormatCore[ushort](System.Span`1[ushort],byref,int) [Tier0, IL size=894, code size=892]
5: JIT compiled System.Guid:FormatGuidVector128Utf8(System.Guid,bool) [Tier0, IL size=331, code size=584]
6: JIT compiled System.HexConverter:AsciiToHexVector128(System.Runtime.Intrinsics.Vector128`1[ubyte],System.Runtime.Intrinsics.Vector128`1[ubyte]) [Tier0, IL size=78, code size=359]
7: JIT compiled System.Runtime.Intrinsics.Vector128:ShuffleUnsafe(System.Runtime.Intrinsics.Vector128`1[ubyte],System.Runtime.Intrinsics.Vector128`1[ubyte]) [Tier0, IL size=41, code size=50]
8: JIT compiled System.Number:UInt32ToDecChars[ushort](ulong,uint) [Instrumented Tier0, IL size=114, code size=315]
9: JIT compiled System.ArgumentOutOfRangeException:ThrowIfNegative[int](int,System.String) [Tier0, IL size=22, code size=50]
10: JIT compiled System.Text.Unicode.Utf16Utility:GetPointerToFirstInvalidChar(ulong,int,byref,byref) [Instrumented Tier0, IL size=994, code size=1296]
11: JIT compiled System.Text.Ascii:NarrowUtf16ToAscii(ulong,ulong,ulong) [Instrumented Tier0, IL size=491, code size=792]
12: JIT compiled System.SpanHelpers:IndexOfNullByte(ulong) [Instrumented Tier0, IL size=844, code size=1540]
13: JIT compiled System.PackedSpanHelpers:IndexOf[System.SpanHelpers+DontNegate`1[short]](byref,short,int) [Instrumented Tier0, IL size=698, code size=1911]
14: JIT compiled System.PackedSpanHelpers:PackSources(System.Runtime.Intrinsics.Vector256`1[short],System.Runtime.Intrinsics.Vector256`1[short]) [Tier0, IL size=13, code size=52]
15: JIT compiled System.PackedSpanHelpers:ComputeFirstIndexOverlapped(byref,byref,byref,System.Runtime.Intrinsics.Vector256`1[ubyte]) [Tier0, IL size=52, code size=124]
16: JIT compiled System.PackedSpanHelpers:FixUpPackedVector256Result(System.Runtime.Intrinsics.Vector256`1[ubyte]) [Tier0, IL size=22, code size=42]
17: JIT compiled System.SpanHelpers:LastIndexOfValueType[short,System.SpanHelpers+DontNegate`1[short]](byref,short,int) [Instrumented Tier0, IL size=963, code size=2111]
18: JIT compiled System.Text.Ascii:WidenAsciiToUtf16(ulong,ulong,ulong) [Instrumented Tier0, IL size=604, code size=1784]
19: JIT compiled (dynamicClass):InvokeStub_EventAttribute.set_Level(System.Object,System.Object,ulong) [FullOpts, IL size=25, code size=27]
20: JIT compiled (dynamicClass):InvokeStub_EventAttribute.set_Message(System.Object,System.Object,ulong) [FullOpts, IL size=25, code size=28]
21: JIT compiled (dynamicClass):InvokeStub_EventAttribute.set_Task(System.Object,System.Object,ulong) [FullOpts, IL size=25, code size=27]
22: JIT compiled (dynamicClass):InvokeStub_EventAttribute.set_Opcode(System.Object,System.Object,ulong) [FullOpts, IL size=25, code size=27]
23: JIT compiled (dynamicClass):InvokeStub_EventAttribute.set_Version(System.Object,System.Object,ulong) [FullOpts, IL size=25, code size=28]
24: JIT compiled (dynamicClass):InvokeStub_EventAttribute.set_Keywords(System.Object,System.Object,ulong) [FullOpts, IL size=25, code size=28]
25: JIT compiled System.Number:Int64ToHexChars[ushort](ulong,ulong,int,int) [Instrumented Tier0, IL size=67, code size=313]
26: JIT compiled System.SpanHelpers:IndexOfAnyInRangeUnsignedNumber[ushort,System.SpanHelpers+DontNegate`1[ushort]](byref,ushort,ushort,int) [Tier0, IL size=142, code size=190]
27: JIT compiled System.PackedSpanHelpers:IndexOfAnyInRange[System.SpanHelpers+DontNegate`1[short]](byref,short,short,int) [Instrumented Tier0, IL size=659, code size=1801]
28: JIT compiled Program:<Main>$(System.String[]) [Tier0, IL size=20, code size=57]
29: JIT compiled System.ArgumentOutOfRangeException:ThrowIfNegativeOrZero[int](int,System.String) [Tier0, IL size=36, code size=63]
Hallo world
I don't see string.Replace
but I remember we used to have 4 or 5 functions jitted for a hello world 🤔 (tracked in https://github.com/dotnet/runtime/issues/85791 so presumably @mangod9 this one can be closed)
System.ArgumentOutOfRangeException:ThrowIfNegativeOrZero
is being JITted.Surprised that
System.ArgumentOutOfRangeException:ThrowIfNegativeOrZero
is being JITted.public static void ThrowIfNegativeOrZero<T>(T value, [CallerArgumentExpression(nameof(value))] string? paramName = null) where T : INumberBase<T> { if (T.IsNegative(value) || T.IsZero(value)) ThrowNegativeOrZero(paramName, value); }
I guess it's the same SVM issue so presumably it will be fixed with https://github.com/dotnet/runtime/pull/87438
hmm, makes sense
Repro:
Console.WriteLine("Hello world".Replace('e', 'a'));
Actual result:
String.Replace
is JITed in the foregroundExpected result: R2R version of
String.Replace
is, no JITing ofString.Replace
observed.