dotnet / dotnet/runtime

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

Open
#129,055 6 comments 0 reactions 1 assignee Claimed by @dhartglassMSFT View on GitHub
arch-arm64 area-CodeGen-coreclr tenet-performance
Dominant language
C#
Stars
18.3k
Forks
5.6k
PR merge metrics
PR metrics pending

Description

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

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

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.