Skip to content

JIT: extend ccmp combining and fold redundant null-check for isinst / GetType chains #129055

@AndyAyersMS

Description

@AndyAyersMS

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 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

bool M1(object o) => o != null && (o is int 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

Form Code size (arm64 .NET 11) Uses ccmp
o is int or uint or long 92 B yes (combines uint+long)
o is int || o is uint || o is long 108 B no — three separate cmp/beq
o.GetType() == typeof(int) || o.GetType() == typeof(uint) || o.GetType() == typeof(long) 92 B (without null check) / 108 B (with) no — three separate cmp/beq

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) || ....
  • Fix 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

Metadata

Metadata

Labels

arch-arm64area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMItenet-performancePerformance related issue

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions