KEMBAR78
Faster "obj is T[]" for sealed T by EgorBo · Pull Request #75816 · dotnet/runtime · GitHub
Skip to content

Conversation

@EgorBo
Copy link
Member

@EgorBo EgorBo commented Sep 19, 2022

Let's see if my assumptions were correct. Closes #75815

// isinst
bool IsArrayOfStrings(object o) => o is string[];

// castclass
void CastToArrayOfString(IEnumerable<string> e) => Consume((string[])e);


[MethodImpl(MethodImplOptions.NoInlining)]
void Consume(string[] str) { }

codegen diff: https://www.diffchecker.com/IoRePQ7d

jit-diffs (409 methods affected)
PMI CodeSize Diffs for System.Private.CoreLib.dll, framework assemblies for  default jit

Summary of Code Size diffs:
(Lower is better)

Total bytes of base: 64751433
Total bytes of diff: 64754979
Total bytes of delta: 3546 (0.01 % of base)
Total relative delta: 11.82
    diff is a regression.
    relative diff is a regression.


Top file regressions (bytes):
         751 : ILCompiler.Reflection.ReadyToRun.dasm (0.33% of base)
         719 : FSharp.Core.dasm (0.02% of base)
         568 : System.Data.Common.dasm (0.03% of base)
         423 : System.Memory.dasm (0.15% of base)
         187 : System.Private.DataContractSerialization.dasm (0.02% of base)
         179 : System.Private.CoreLib.dasm (0.00% of base)
         131 : System.ComponentModel.Composition.dasm (0.04% of base)
         108 : System.Collections.dasm (0.02% of base)
          86 : System.DirectoryServices.AccountManagement.dasm (0.02% of base)
          69 : Microsoft.CSharp.dasm (0.02% of base)
          69 : System.Net.Http.dasm (0.01% of base)
          55 : System.Management.dasm (0.01% of base)
          53 : System.Diagnostics.PerformanceCounter.dasm (0.05% of base)
          41 : System.Net.WebSockets.Client.dasm (0.18% of base)
          39 : Microsoft.VisualBasic.Core.dasm (0.01% of base)
          38 : System.Diagnostics.Process.dasm (0.03% of base)
          33 : Microsoft.DotNet.XUnitExtensions.dasm (0.14% of base)
          31 : Newtonsoft.Json.dasm (0.00% of base)
          28 : System.Diagnostics.EventLog.dasm (0.02% of base)
          20 : System.Reflection.Context.dasm (0.04% of base)

Top file improvements (bytes):
         -86 : System.Collections.Concurrent.dasm (-0.02% of base)
         -48 : System.Private.Xml.dasm (-0.00% of base)
         -30 : xunit.execution.dotnet.dasm (-0.01% of base)
         -18 : System.Linq.dasm (-0.00% of base)
         -16 : Microsoft.Extensions.Primitives.dasm (-0.06% of base)
         -13 : Microsoft.Diagnostics.FastSerialization.dasm (-0.01% of base)
          -5 : System.DirectoryServices.Protocols.dasm (-0.00% of base)

39 total files with Code Size differences (7 improved, 32 regressed), 235 unchanged.

Top method regressions (bytes):
          71 ( 6.05% of base) : ILCompiler.Reflection.ReadyToRun.dasm - Internal.Pgo.PgoProcessor:<Merge>g__MergeInSchemaElem|9_0[System.Nullable`1[int],System.Nullable`1[int]](System.Collections.Generic.Dictionary`2[Internal.Pgo.PgoSchemaElem,Internal.Pgo.PgoSchemaElem],Internal.Pgo.PgoSchemaElem)
          67 ( 5.85% of base) : ILCompiler.Reflection.ReadyToRun.dasm - Internal.Pgo.PgoProcessor:<Merge>g__MergeInSchemaElem|9_0[ubyte,System.Nullable`1[int]](System.Collections.Generic.Dictionary`2[Internal.Pgo.PgoSchemaElem,Internal.Pgo.PgoSchemaElem],Internal.Pgo.PgoSchemaElem)
          66 ( 5.75% of base) : ILCompiler.Reflection.ReadyToRun.dasm - Internal.Pgo.PgoProcessor:<Merge>g__MergeInSchemaElem|9_0[long,System.Nullable`1[int]](System.Collections.Generic.Dictionary`2[Internal.Pgo.PgoSchemaElem,Internal.Pgo.PgoSchemaElem],Internal.Pgo.PgoSchemaElem)
          63 ( 5.48% of base) : ILCompiler.Reflection.ReadyToRun.dasm - Internal.Pgo.PgoProcessor:<Merge>g__MergeInSchemaElem|9_0[short,System.Nullable`1[int]](System.Collections.Generic.Dictionary`2[Internal.Pgo.PgoSchemaElem,Internal.Pgo.PgoSchemaElem],Internal.Pgo.PgoSchemaElem)
          61 ( 1.84% of base) : System.Management.dasm - System.Management.ManagementClassGenerator:ProcessPropertyQualifiers(System.Management.PropertyData,byref,byref,byref,bool,byref):System.String:this
          54 ( 4.54% of base) : ILCompiler.Reflection.ReadyToRun.dasm - Internal.Pgo.PgoProcessor:<Merge>g__MergeInSchemaElem|9_0[System.Numerics.Vector`1[float],System.Nullable`1[int]](System.Collections.Generic.Dictionary`2[Internal.Pgo.PgoSchemaElem,Internal.Pgo.PgoSchemaElem],Internal.Pgo.PgoSchemaElem)
          51 ( 7.98% of base) : FSharp.Core.dasm - Microsoft.FSharp.Primitives.Basics.Seq:tryLastV[System.Numerics.Vector`1[float]](System.Collections.Generic.IEnumerable`1[System.Numerics.Vector`1[float]]):Microsoft.FSharp.Core.FSharpValueOption`1[System.Numerics.Vector`1[float]]
          51 ( 1.63% of base) : System.Management.dasm - System.Management.PropertyData:MapValueToWmiValue(System.Object,byref,byref):System.Object
          46 ( 4.54% of base) : System.Collections.dasm - System.Collections.Generic.LinkedList`1[System.Numerics.Vector`1[float]]:OnDeserialization(System.Object):this
          45 ( 3.46% of base) : ILCompiler.Reflection.ReadyToRun.dasm - Internal.Pgo.PgoProcessor:EncodePgoData[System.Nullable`1[int],System.Nullable`1[int]](System.Collections.Generic.IEnumerable`1[Internal.Pgo.PgoSchemaElem],Internal.Pgo.IPgoEncodedValueEmitter`2[System.Nullable`1[int],System.Nullable`1[int]],bool)
          45 ( 6.46% of base) : FSharp.Core.dasm - Microsoft.FSharp.Primitives.Basics.Seq:tryLastV[System.Nullable`1[int]](System.Collections.Generic.IEnumerable`1[System.Nullable`1[int]]):Microsoft.FSharp.Core.FSharpValueOption`1[System.Nullable`1[int]]
          41 ( 3.58% of base) : ILCompiler.Reflection.ReadyToRun.dasm - Internal.Pgo.PgoProcessor:<Merge>g__MergeInSchemaElem|9_0[int,System.Nullable`1[int]](System.Collections.Generic.Dictionary`2[Internal.Pgo.PgoSchemaElem,Internal.Pgo.PgoSchemaElem],Internal.Pgo.PgoSchemaElem)
          38 ( 3.11% of base) : System.Diagnostics.Process.dasm - System.Diagnostics.PerformanceCounterLib:GetStringTable(bool):System.Collections.Generic.Dictionary`2[int,System.String]:this
          38 ( 2.72% of base) : System.Diagnostics.PerformanceCounter.dasm - System.Diagnostics.PerformanceCounterLib:GetStringTable(bool):System.Collections.Hashtable:this
          36 ( 7.79% of base) : FSharp.Core.dasm - Microsoft.FSharp.Core.CompilerServices.ArrayCollector`1[System.Nullable`1[int]]:AddMany(System.Collections.Generic.IEnumerable`1[System.Nullable`1[int]]):this
          36 ( 6.96% of base) : FSharp.Core.dasm - Microsoft.FSharp.Core.CompilerServices.ArrayCollector`1[System.Numerics.Vector`1[float]]:AddMany(System.Collections.Generic.IEnumerable`1[System.Numerics.Vector`1[float]]):this
          36 ( 2.57% of base) : System.Memory.dasm - System.Buffers.ReadOnlySequenceDebugView`1[System.Nullable`1[int]]:.ctor(System.Buffers.ReadOnlySequence`1[System.Nullable`1[int]]):this
          36 ( 2.57% of base) : System.Memory.dasm - System.Buffers.ReadOnlySequenceDebugView`1[System.Numerics.Vector`1[float]]:.ctor(System.Buffers.ReadOnlySequence`1[System.Numerics.Vector`1[float]]):this
          35 ( 2.09% of base) : ILCompiler.Reflection.ReadyToRun.dasm - <ParsePgoData>d__6`2[System.Numerics.Vector`1[float],System.Nullable`1[int]]:MoveNext():bool:this
          34 ( 2.06% of base) : ILCompiler.Reflection.ReadyToRun.dasm - <ParsePgoData>d__6`2[System.Nullable`1[int],System.Nullable`1[int]]:MoveNext():bool:this

Top method improvements (bytes):
        -260 (-9.24% of base) : System.Private.Xml.dasm - System.Xml.Serialization.XmlSchemaImporter:ImportChoiceGroup(System.Xml.Schema.XmlSchemaGroupBase,System.String,System.Xml.Serialization.CodeIdentifiers,System.Xml.Serialization.CodeIdentifiers,System.Xml.Serialization.INameScope,System.String,bool,byref,bool):System.Xml.Serialization.MemberMapping:this
         -85 (-5.34% of base) : System.Private.Xml.dasm - System.Xml.Serialization.XmlSchemaImporter:ImportTypeMembers(System.Xml.Schema.XmlSchemaType,System.String,System.String,System.Xml.Serialization.CodeIdentifiers,System.Xml.Serialization.CodeIdentifiers,System.Xml.Serialization.INameScope,byref,bool,bool):System.Xml.Serialization.MemberMapping[]:this
         -64 (-4.78% of base) : System.Management.dasm - System.Management.QualifierData:MapQualValueToWmiValue(System.Object):System.Object
         -25 (-37.88% of base) : System.Net.Http.dasm - System.Net.Http.Headers.HttpHeaders:GetEntriesArray():System.Net.Http.Headers.HeaderEntry[]:this
         -22 (-18.33% of base) : Microsoft.Extensions.Primitives.dasm - Microsoft.Extensions.Primitives.StringValues:IsNullOrEmpty(Microsoft.Extensions.Primitives.StringValues):bool
         -19 (-2.18% of base) : System.Net.Http.dasm - System.Net.Http.MultipartContent:SerializeHeadersToStream(System.IO.Stream,System.Net.Http.HttpContent,bool):this
         -15 (-28.85% of base) : FSharp.Core.dasm - Microsoft.FSharp.Core.ExtraTopLevelOperators:getArray[System.Nullable`1[int]](System.Collections.Generic.IEnumerable`1[System.Nullable`1[int]]):System.Nullable`1[int][]
         -15 (-28.85% of base) : FSharp.Core.dasm - Microsoft.FSharp.Core.ExtraTopLevelOperators:getArray[System.Numerics.Vector`1[float]](System.Collections.Generic.IEnumerable`1[System.Numerics.Vector`1[float]]):System.Numerics.Vector`1[float][]
         -15 (-28.85% of base) : System.ComponentModel.Composition.dasm - Microsoft.Internal.Collections.CollectionServices:AsArray[System.Nullable`1[int]](System.Collections.Generic.IEnumerable`1[System.Nullable`1[int]]):System.Nullable`1[int][]
         -15 (-28.85% of base) : System.ComponentModel.Composition.dasm - Microsoft.Internal.Collections.CollectionServices:AsArray[System.Numerics.Vector`1[float]](System.Collections.Generic.IEnumerable`1[System.Numerics.Vector`1[float]]):System.Numerics.Vector`1[float][]
         -15 (-28.85% of base) : xunit.execution.dotnet.dasm - Xunit.Sdk.CollectionExtensions:CastOrToArray[System.Nullable`1[int]](System.Collections.Generic.IEnumerable`1[System.Nullable`1[int]]):System.Nullable`1[int][]
         -15 (-28.85% of base) : xunit.execution.dotnet.dasm - Xunit.Sdk.CollectionExtensions:CastOrToArray[System.Numerics.Vector`1[float]](System.Collections.Generic.IEnumerable`1[System.Numerics.Vector`1[float]]):System.Numerics.Vector`1[float][]
         -12 (-0.38% of base) : System.Private.Xml.dasm - System.Xml.Serialization.XmlSerializationReader:ReadArray(System.String,System.String):System.Object:this
         -10 (-1.44% of base) : System.Collections.Concurrent.dasm - System.Collections.Concurrent.ConcurrentDictionary`2[double,System.Nullable`1[int]]:System.Collections.ICollection.CopyTo(System.Array,int):this
         -10 (-1.44% of base) : System.Collections.Concurrent.dasm - System.Collections.Concurrent.ConcurrentDictionary`2[int,System.Nullable`1[int]]:System.Collections.ICollection.CopyTo(System.Array,int):this
         -10 (-1.44% of base) : System.Collections.Concurrent.dasm - System.Collections.Concurrent.ConcurrentDictionary`2[long,System.Nullable`1[int]]:System.Collections.ICollection.CopyTo(System.Array,int):this
         -10 (-1.44% of base) : System.Collections.Concurrent.dasm - System.Collections.Concurrent.ConcurrentDictionary`2[short,System.Nullable`1[int]]:System.Collections.ICollection.CopyTo(System.Array,int):this
         -10 (-1.44% of base) : System.Collections.Concurrent.dasm - System.Collections.Concurrent.ConcurrentDictionary`2[System.Nullable`1[int],System.Nullable`1[int]]:System.Collections.ICollection.CopyTo(System.Array,int):this
         -10 (-1.44% of base) : System.Collections.Concurrent.dasm - System.Collections.Concurrent.ConcurrentDictionary`2[System.Numerics.Vector`1[float],System.Nullable`1[int]]:System.Collections.ICollection.CopyTo(System.Array,int):this
         -10 (-1.44% of base) : System.Collections.Concurrent.dasm - System.Collections.Concurrent.ConcurrentDictionary`2[ubyte,System.Nullable`1[int]]:System.Collections.ICollection.CopyTo(System.Array,int):this

Top method regressions (percentages):
          13 (29.55% of base) : System.Diagnostics.EventLog.dasm - System.Diagnostics.Eventing.Reader.EventLogConfiguration:get_ProviderNames():System.Collections.Generic.IEnumerable`1[System.String]:this
          15 (29.41% of base) : System.Private.Xml.dasm - <>c__DisplayClass57_0:<WriteAttribute>b__1(System.Object):System.String:this
          12 (27.91% of base) : System.Data.Common.dasm - System.Data.Common.SqlBinaryStorage:SetStorage(System.Object,System.Collections.BitArray):this
          12 (27.91% of base) : System.Data.Common.dasm - System.Data.Common.SqlBooleanStorage:SetStorage(System.Object,System.Collections.BitArray):this
          12 (27.91% of base) : System.Data.Common.dasm - System.Data.Common.SqlBytesStorage:SetStorage(System.Object,System.Collections.BitArray):this
          12 (27.91% of base) : System.Data.Common.dasm - System.Data.Common.SqlByteStorage:SetStorage(System.Object,System.Collections.BitArray):this
          12 (27.91% of base) : System.Data.Common.dasm - System.Data.Common.SqlCharsStorage:SetStorage(System.Object,System.Collections.BitArray):this
          12 (27.91% of base) : System.Data.Common.dasm - System.Data.Common.SqlDateTimeStorage:SetStorage(System.Object,System.Collections.BitArray):this
          12 (27.91% of base) : System.Data.Common.dasm - System.Data.Common.SqlDecimalStorage:SetStorage(System.Object,System.Collections.BitArray):this
          12 (27.91% of base) : System.Data.Common.dasm - System.Data.Common.SqlDoubleStorage:SetStorage(System.Object,System.Collections.BitArray):this
          12 (27.91% of base) : System.Data.Common.dasm - System.Data.Common.SqlGuidStorage:SetStorage(System.Object,System.Collections.BitArray):this
          12 (27.91% of base) : System.Data.Common.dasm - System.Data.Common.SqlInt16Storage:SetStorage(System.Object,System.Collections.BitArray):this
          12 (27.91% of base) : System.Data.Common.dasm - System.Data.Common.SqlInt32Storage:SetStorage(System.Object,System.Collections.BitArray):this
          12 (27.91% of base) : System.Data.Common.dasm - System.Data.Common.SqlInt64Storage:SetStorage(System.Object,System.Collections.BitArray):this
          12 (27.91% of base) : System.Data.Common.dasm - System.Data.Common.SqlMoneyStorage:SetStorage(System.Object,System.Collections.BitArray):this
          12 (27.91% of base) : System.Data.Common.dasm - System.Data.Common.SqlSingleStorage:SetStorage(System.Object,System.Collections.BitArray):this
          12 (27.91% of base) : System.Data.Common.dasm - System.Data.Common.SqlStringStorage:SetStorage(System.Object,System.Collections.BitArray):this
          12 (27.91% of base) : System.Data.Common.dasm - System.Data.Common.StringStorage:SetStorage(System.Object,System.Collections.BitArray):this
          31 (24.80% of base) : System.Private.DataContractSerialization.dasm - <>c__41`1[System.Nullable`1[int]]:<GetCollectionSetItemDelegate>b__41_0(System.Object,System.Object,int):System.Object:this
          13 (24.53% of base) : Microsoft.CodeAnalysis.CSharp.dasm - Microsoft.CodeAnalysis.CSharp.EnumExtensions:GetValues[System.Numerics.Vector`1[float]]():System.Numerics.Vector`1[float][]

Top method improvements (percentages):
         -25 (-37.88% of base) : System.Net.Http.dasm - System.Net.Http.Headers.HttpHeaders:GetEntriesArray():System.Net.Http.Headers.HeaderEntry[]:this
         -15 (-28.85% of base) : FSharp.Core.dasm - Microsoft.FSharp.Core.ExtraTopLevelOperators:getArray[System.Nullable`1[int]](System.Collections.Generic.IEnumerable`1[System.Nullable`1[int]]):System.Nullable`1[int][]
         -15 (-28.85% of base) : FSharp.Core.dasm - Microsoft.FSharp.Core.ExtraTopLevelOperators:getArray[System.Numerics.Vector`1[float]](System.Collections.Generic.IEnumerable`1[System.Numerics.Vector`1[float]]):System.Numerics.Vector`1[float][]
         -15 (-28.85% of base) : System.ComponentModel.Composition.dasm - Microsoft.Internal.Collections.CollectionServices:AsArray[System.Nullable`1[int]](System.Collections.Generic.IEnumerable`1[System.Nullable`1[int]]):System.Nullable`1[int][]
         -15 (-28.85% of base) : System.ComponentModel.Composition.dasm - Microsoft.Internal.Collections.CollectionServices:AsArray[System.Numerics.Vector`1[float]](System.Collections.Generic.IEnumerable`1[System.Numerics.Vector`1[float]]):System.Numerics.Vector`1[float][]
         -15 (-28.85% of base) : xunit.execution.dotnet.dasm - Xunit.Sdk.CollectionExtensions:CastOrToArray[System.Nullable`1[int]](System.Collections.Generic.IEnumerable`1[System.Nullable`1[int]]):System.Nullable`1[int][]
         -15 (-28.85% of base) : xunit.execution.dotnet.dasm - Xunit.Sdk.CollectionExtensions:CastOrToArray[System.Numerics.Vector`1[float]](System.Collections.Generic.IEnumerable`1[System.Numerics.Vector`1[float]]):System.Numerics.Vector`1[float][]
         -22 (-18.33% of base) : Microsoft.Extensions.Primitives.dasm - Microsoft.Extensions.Primitives.StringValues:IsNullOrEmpty(Microsoft.Extensions.Primitives.StringValues):bool
        -260 (-9.24% of base) : System.Private.Xml.dasm - System.Xml.Serialization.XmlSchemaImporter:ImportChoiceGroup(System.Xml.Schema.XmlSchemaGroupBase,System.String,System.Xml.Serialization.CodeIdentifiers,System.Xml.Serialization.CodeIdentifiers,System.Xml.Serialization.INameScope,System.String,bool,byref,bool):System.Xml.Serialization.MemberMapping:this
         -85 (-5.34% of base) : System.Private.Xml.dasm - System.Xml.Serialization.XmlSchemaImporter:ImportTypeMembers(System.Xml.Schema.XmlSchemaType,System.String,System.String,System.Xml.Serialization.CodeIdentifiers,System.Xml.Serialization.CodeIdentifiers,System.Xml.Serialization.INameScope,byref,bool,bool):System.Xml.Serialization.MemberMapping[]:this
          -2 (-5.26% of base) : System.Net.Http.dasm - System.Net.Http.Headers.HttpHeaders:get_EntriesAreLiveView():bool:this
          -5 (-4.95% of base) : System.Net.Http.dasm - Enumerator:.ctor(System.Object):this
          -5 (-4.90% of base) : System.Net.Http.dasm - System.Net.Http.Headers.HeaderStringValues:GetEnumerator():Enumerator:this
         -64 (-4.78% of base) : System.Management.dasm - System.Management.QualifierData:MapQualValueToWmiValue(System.Object):System.Object
          -7 (-3.93% of base) : System.Private.CoreLib.dasm - System.Collections.Concurrent.ConcurrentQueue`1[System.Nullable`1[int]]:System.Collections.ICollection.CopyTo(System.Array,int):this
          -7 (-3.93% of base) : System.Private.CoreLib.dasm - System.Collections.Concurrent.ConcurrentQueue`1[System.Numerics.Vector`1[float]]:System.Collections.ICollection.CopyTo(System.Array,int):this
          -6 (-3.64% of base) : System.Threading.Tasks.Dataflow.dasm - System.Threading.Tasks.Dataflow.TransformManyBlock`2[double,System.Nullable`1[int]]:StoreOutputItems(System.Collections.Generic.KeyValuePair`2[double,long],System.Collections.Generic.IEnumerable`1[System.Nullable`1[int]]):this
          -6 (-3.64% of base) : System.Threading.Tasks.Dataflow.dasm - System.Threading.Tasks.Dataflow.TransformManyBlock`2[int,System.Nullable`1[int]]:StoreOutputItems(System.Collections.Generic.KeyValuePair`2[int,long],System.Collections.Generic.IEnumerable`1[System.Nullable`1[int]]):this
          -6 (-3.64% of base) : System.Threading.Tasks.Dataflow.dasm - System.Threading.Tasks.Dataflow.TransformManyBlock`2[long,System.Nullable`1[int]]:StoreOutputItems(System.Collections.Generic.KeyValuePair`2[long,long],System.Collections.Generic.IEnumerable`1[System.Nullable`1[int]]):this
          -6 (-3.64% of base) : System.Threading.Tasks.Dataflow.dasm - System.Threading.Tasks.Dataflow.TransformManyBlock`2[short,System.Nullable`1[int]]:StoreOutputItems(System.Collections.Generic.KeyValuePair`2[short,long],System.Collections.Generic.IEnumerable`1[System.Nullable`1[int]]):this

409 total methods with Code Size differences (106 improved, 303 regressed), 391066 unchanged.

Mostly size regression coming from castclass expansion which is expected.

@ghost ghost added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Sep 19, 2022
@ghost ghost assigned EgorBo Sep 19, 2022
@ghost
Copy link

ghost commented Sep 19, 2022

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

Issue Details

Let's see if my assumptions were correct. Closes #75815

bool IsArrayOfStrings(object o) => o is string[]; // or e.g. IEnumerable<string> is string[]

codegen:

Author: EgorBo
Assignees: -
Labels:

area-CodeGen-coreclr

Milestone: -

@omariom
Copy link
Contributor

omariom commented Sep 19, 2022

🚀Finally!

@EgorBo
Copy link
Member Author

EgorBo commented Sep 19, 2022

Relaxed checks made NativeAOT unhappy, let me see why

@EgorBo
Copy link
Member Author

EgorBo commented Sep 19, 2022

Failed test: nativeaot_SmokeTests::_DynamicGenerics_DynamicGenerics_DynamicGenerics_cmd (nativeaot\\SmokeTests\\DynamicGenerics\\DynamicGenerics\\DynamicGenerics.cmd)
Unhandled Exception: EETypeRva:0x00496880(System.Diagnostics.DebugProvider+DebugAssertException): IsInstanceOfClass called with parameterized MethodTable
   at System.Diagnostics.DebugProvider.Fail(String, String) + 0x36
   at System.Diagnostics.Debug.Fail(String, String) + 0x21
   at System.Runtime.TypeCast.IsInstanceOfClass(MethodTable*, Object) + 0x48
   at System.Runtime.TypeCast.CheckCastClass(MethodTable*, Object) + 0x15
   at B282745.testMDArrayWith3Dimensions() + 0x2e8
   at CoreFXTestLibrary.Internal.Runner.RunTestMethod(TestInfo) + 0x195
   at CoreFXTestLibrary.Internal.Runner.RunTest(TestInfo) + 0x21
   at CoreFXTestLibrary.Internal.Runner.RunTests(TestInfo[], String[]) + 0x6d
   at EntryPointMain.Main(String[]) + 0x2416
   at DynamicGenerics!<BaseAddress>+0x3055ab

Perhaps this optimization is not valid for NativeAOT for MD arrays?

@jkotas
Copy link
Member

jkotas commented Sep 19, 2022

The optimization is valid for NativeAOT.

The problem seems to be that the codegen is violating the JIT helper contract. NativeAOT asserts. CoreCLR has a test hole (note that this is nativeaot smoke test that does not run on CoreCLR), or it happens to work and not crash, but it does not make it right.

@EgorBo
Copy link
Member Author

EgorBo commented Sep 19, 2022

The optimization is valid for NativeAOT.

The problem seems to be that the codegen is violating the JIT helper contract. NativeAOT asserts. CoreCLR has a test hole (note that this is nativeaot smoke test that does not run on CoreCLR), or it happens to work and not crash, but it does not make it right.

hm.. I don't see an API to find out whether a type is IsParameterizedType/IsDefType/IsTypeNodeShareable (e.g. via getClassAttrs)
Also, I use compareTypesForEquality(elementCls, elementCls) == TypeCompareState::Must to filter out shared types but apparently it passed for NativeAOT for shared type

@jkotas
Copy link
Member

jkotas commented Sep 19, 2022

hm.. I don't see an API to find out whether a type is IsParameterizedType/IsDefType/IsTypeNodeShareable (e.g. via getClassAttrs)

I am not sure why you would need this. I think you just need to overwriting the helper to CORINFO_HELP_CHKCASTCLASS_SPECIAL when we are dealing with array casts here:

const CorInfoHelpFunc specialHelper = CORINFO_HELP_CHKCASTCLASS_SPECIAL;

@EgorBo EgorBo marked this pull request as ready for review September 20, 2022 04:22
@EgorBo
Copy link
Member Author

EgorBo commented Sep 20, 2022

@jkotas thanks for suggestion, the code is cleaner now! @dotnet/jit-contrib PTAL

Copy link
Contributor

@kunalspathak kunalspathak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a question.

//
const CorInfoHelpFunc specialHelper = CORINFO_HELP_CHKCASTCLASS_SPECIAL;
const CorInfoHelpFunc specialHelper =
(helper == CORINFO_HELP_CHKCASTCLASS) ? CORINFO_HELP_CHKCASTCLASS_SPECIAL : helper;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What other helper we expect for isCastClass == true?

Copy link
Member Author

@EgorBo EgorBo Sep 20, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kunalspathak only CORINFO_HELP_CHKCASTCLASS that is replaced with CORINFO_HELP_CHKCASTCLASS_SPECIAL

other helpers ( CORINFO_HELP_CHKCASTARRAY and CORINFO_HELP_CHKCASTINTERFACE are expected to be untouched)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we assert(helper == CORINFO_HELP_CHKCASTARRAY)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CORINFO_HELP_CHKCASTINTERFACE wasn't handled previously (sounds like a bug) but it was only ever used with DOTNET_JitProfileCasts=1 which is 0 by default

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jkotas actually it looks like the check you asked me to delete is still needed, consider this example:

class ClassA { }
class ClassB : ClassA { }

internal class Program 
{

    [MethodImpl(MethodImplOptions.NoInlining)]
    public static void Main()
    {
        for (int i = 0; i < 100; i++)
        {
            CastToClassA(new ClassB());
            Thread.Sleep(10);
        }
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    static ClassA CastToClassA(object o) => (ClassA)o;
}

Current Tier1 codegen for CastToClassA is:

; Assembly listing for method CastToClassA
G_M33135_IG01:              
       4883EC28             sub      rsp, 40
G_M33135_IG02:              
       48894C2430           mov      gword ptr [rsp+30H], rcx
       488BC1               mov      rax, rcx
       4885C0               test     rax, rax
       741D                 je       SHORT G_M33135_IG05
G_M33135_IG03:              
       48BA18537F78FE7F0000 mov      rdx, 0x7FFE787F5318      ; ClassA
       483910               cmp      qword ptr [rax], rdx
       740E                 je       SHORT G_M33135_IG05
G_M33135_IG04:              
       488BCA               mov      rcx, rdx
       488B542430           mov      rdx, gword ptr [rsp+30H]
       FF152A75FBFF         call     [CORINFO_HELP_CHKCASTCLASS_SPECIAL]
G_M33135_IG05:              
       90                   nop      
G_M33135_IG06:              
       4883C428             add      rsp, 40
       C3                   ret

So jit inlined check for ClassA so it doesn't need to check for it inside the _SPECIAL helper. But as you can see from the call-site the check for ClassA fast path is not valid for given app in terms of performance. So with Dynamic PGO and DOTNET_JitProfileCasts=1 I get this:

; Assembly listing for method CastToClassA
G_M33135_IG01:
       4883EC28             sub      rsp, 40
G_M33135_IG02:              
       488BC1               mov      rax, rcx
       4885C0               test     rax, rax
       7422                 je       SHORT G_M33135_IG05
G_M33135_IG03:              
       48BA484F8078FE7F0000 mov      rdx, 0x7FFE78804F48      ; ClassB
       483910               cmp      qword ptr [rax], rdx
       7413                 je       SHORT G_M33135_IG05
G_M33135_IG04:              
       488BD1               mov      rdx, rcx
       48B9E04D8078FE7F0000 mov      rcx, 0x7FFE78804DE0      ; ClassA
       FF157A75FBFF         call     [CORINFO_HELP_CHKCASTCLASS_SPECIAL]
G_M33135_IG05:              
       90                   nop      
G_M33135_IG06:              
       4883C428             add      rsp, 40
       C3                   ret

Here we check for ClassB for fast path but we'll need the check for itself in the helper. Here is the codegen diff between two (PGO is on the right):

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As an optimization, we can introduce CORINFO_HELP_CHKCASTCLASS_SPECIAL_SLOW with the "am I the same type?" check

Copy link
Member Author

@EgorBo EgorBo Sep 20, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively, we can check for both in JIT inlined - but it doesn't sound right to me, if we have a profile saying that it's always ClassB why would we want to inline a check for ClassA?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The JIT should be using the regular CORINFO_HELP_CHKCASTCLASS when it decides to inline a check for a type from the middle of the type hierarchy.

CORINFO_HELP_CHKCASTCLASS_SPECIAL_SLOW with the "am I the same type?" check

It is exactly what CORINFO_HELP_CHKCASTCLASS does. We do not need a new helper.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, indeed 🙂

Copy link
Contributor

@kunalspathak kunalspathak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

GenTree* condFalse = gtClone(op1);
GenTree* condTrue;
if (isCastClass)
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason why we are not doing the PGO profiling for the CastAny helpers?

It would be nice to note that reason in impIsCastHelperMayHaveProfileData/impIsCastHelperMayHaveProfileData.

Copy link
Member Author

@EgorBo EgorBo Sep 20, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can look on enabling those separately if you don't mind - probably we can benefit from them a lot, will experiment. I assume they will cover a lot of generic code

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's fine. It is just something that I have run into while reviewing this change.

@EgorBo
Copy link
Member Author

EgorBo commented Sep 20, 2022

Hm something is off on CI - with Git fetch

@EgorBo EgorBo merged commit 4fae3c6 into dotnet:main Sep 21, 2022
@EgorBo EgorBo deleted the faster-isinst-array-sealed branch September 21, 2022 03:48
@DrewScoggins
Copy link
Member

DrewScoggins commented Sep 22, 2022

Improvements on Arm64: dotnet/perf-autofiling-issues#8663
Improvements and Regressions: dotnet/perf-autofiling-issues#8662

@ghost ghost locked as resolved and limited conversation to collaborators Oct 22, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

JIT: IEnumerable<string>isinst String[] should be inlined

5 participants