You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Follow-up to #47920 (closed as resolved). The original "ineffective codegen" complaint is fixed — o is int or uint or long (M0) now produces the smallest, fastest code, using arm64 ccmp to combine the second and third type-id compares. Two related codegen-quality gaps remain:
1. Redundant null-check materialization when o != null && (o is X) appears
boolM1(objecto)=>o!=null&&(oisint or uint or long);
The leading o != null is semantically redundant (the isinst already returns false for null), but the JIT emits:
cmp x1, #0 ; <-- could be folded away
cset x0, ne
cbz w0, IG06
ldr x0, [x1] ; ...isinst chain identical to M0...
Equivalent C# without the leading null check (M0) emits just cbz x1, IG05 instead. ~8 bytes / 3 instructions wasted per call. The IL pattern is cgt.un(o, null); stloc V02; ldloc V02; brfalse. optOptimizeBools could simplify STORE V02 = (X != null); JTRUE V02 == 0 into JTRUE X == null (and forward-substitute to eliminate V02's first definition), but it doesn't, likely because V02 is reassigned later in flow with the isinst result.
2. ccmp combining only fires for the or-pattern, not for ||-chains or GetType()-equality chains
The ||-chain pattern is the obvious hand-written form; users with existing code should already get the same ccmp combining the or-pattern enjoys. Likewise the explicit GetType() == typeof(X) || ... chain (a common pattern from before is-patterns existed) should benefit.
Suggested next steps
Investigate generalizing the ccmp combine in arm64 lowering to handle short ||-chains of methodtable-equality comparisons, regardless of whether the source pattern came from is X or Y or Z, is X || is Y || is Z, or GetType() == typeof(X) || ....
Note
AI-assisted (Copilot CLI).
Follow-up to #47920 (closed as resolved). The original "ineffective codegen" complaint is fixed —
o is int or uint or long(M0) now produces the smallest, fastest code, using arm64ccmpto combine the second and third type-id compares. Two related codegen-quality gaps remain:1. Redundant null-check materialization when
o != null && (o is X)appearsThe leading
o != nullis semantically redundant (the isinst already returnsfalsefor null), but the JIT emits:Equivalent C# without the leading null check (M0) emits just
cbz x1, IG05instead. ~8 bytes / 3 instructions wasted per call. The IL pattern iscgt.un(o, null); stloc V02; ldloc V02; brfalse.optOptimizeBoolscould simplifySTORE V02 = (X != null); JTRUE V02 == 0intoJTRUE X == null(and forward-substitute to eliminate V02's first definition), but it doesn't, likely because V02 is reassigned later in flow with the isinst result.2.
ccmpcombining only fires for theor-pattern, not for||-chains orGetType()-equality chainsccmpo is int or uint or longo is int || o is uint || o is longcmp/beqo.GetType() == typeof(int) || o.GetType() == typeof(uint) || o.GetType() == typeof(long)cmp/beqThe
||-chain pattern is the obvious hand-written form; users with existing code should already get the sameccmpcombining theor-pattern enjoys. Likewise the explicitGetType() == typeof(X) || ...chain (a common pattern from beforeis-patterns existed) should benefit.Suggested next steps
ccmpcombine in arm64 lowering to handle short||-chains of methodtable-equality comparisons, regardless of whether the source pattern came fromis X or Y or Z,is X || is Y || is Z, orGetType() == typeof(X) || ....optOptimizeBools/ value-numbering to fold(X != null) && IsInst(X, T)→IsInst(X, T)(or equivalently fold the redundant null-materialization).Related: #47920 (closed).
cc @dotnet/jit-contrib