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
{{ message }}
Repository navigation
JIT: extend ccmp combining and fold redundant null-check for isinst / GetType chains #129055
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) || ....
Yeah problem here is with the extra stloc/ldloc, that local is used in the return block. Other IL (for something like o != null && (something_else) doesn't have the extra local, it just branches on the argument
To update, issue 1 is because an extra local that contains the result of the first null comparison that's live until the shared return. It is possible to remove the first CMP if we know precisely where its uses are though.
Issue 2-2 is oddly enough the opposite of issue 1 - that method has multiple returns that look similar, so optimize bools can't do the block-combining-into-or because the two blocks don't have the same branch target like in 2-1.
Activity
dotnet-policy-service commentedon Jun 5, 2026
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.
AndyAyersMS commentedon Jun 5, 2026
FYI @dhartglassMSFT some other smallish ARM64 CQ issues.
dhartglassMSFT commentedon Jun 12, 2026
Taking a look
dhartglassMSFT commentedon Jun 12, 2026
Yeah problem here is with the extra stloc/ldloc, that local is used in the return block. Other IL (for something like
o != null && (something_else)doesn't have the extra local, it just branches on the argumentdhartglassMSFT commentedon Jul 6, 2026
To update, issue 1 is because an extra local that contains the result of the first null comparison that's live until the shared return. It is possible to remove the first CMP if we know precisely where its uses are though.
Issue 2-2 is oddly enough the opposite of issue 1 - that method has multiple returns that look similar, so optimize bools can't do the block-combining-into-or because the two blocks don't have the same branch target like in 2-1.
2-3 have a fix for in pr.
dhartglassMSFT commentedon Aug 14, 2026
ongoing, but won't reach 11 deadline