Skip to content

Arm64: use FEAT_CSSC cnt/ctz for PopCount and TrailingZeroCount#130332

Merged
EgorBo merged 7 commits into
mainfrom
egorbo/arm64-cssc-popcnt-tzc
Jul 12, 2026
Merged

Arm64: use FEAT_CSSC cnt/ctz for PopCount and TrailingZeroCount#130332
EgorBo merged 7 commits into
mainfrom
egorbo/arm64-cssc-popcnt-tzc

Conversation

@EgorBo

@EgorBo EgorBo commented Jul 8, 2026

Copy link
Copy Markdown
Member

A quick PR to optimize PopCount and TrailingZeroCount with CSSC (armv8.9/armv9.4) that I found on my macbook m5.

static int TestPOPCNT(uint value) => BitOperations.PopCount(value);
static int TestPOPCNT64(ulong value) => BitOperations.PopCount(value);

static int TestTZC(uint value) => BitOperations.TrailingZeroCount(value);
static int TestTZC64(ulong value) => BitOperations.TrailingZeroCount(value);

Codegen diff:

 ; PopCount(uint)
-            movi    v16.8b, #0
-            ins     v16.s[0], w0
-            cnt     v16.8b, v16.8b
-            addv    b16, v16.8b
-            umov    w0, v16.s[0]
+            cnt     w0, w0

 ; PopCount(ulong)
-            ins     v16.d[0], x0
-            cnt     v16.8b, v16.8b
-            addv    b16, v16.8b
-            umov    x0, v16.d[0]
+            cnt     x0, x0

 ; TrailingZeroCount(uint)
-            rbit    w0, w0
-            clz     w0, w0
+            ctz     w0, w0

 ; TrailingZeroCount(ulong)
-            rbit    x0, x0
-            clz     x0, x0
+            ctz     x0, x0

I ran the entire runtime & libs tests locally on apple m5 + my own smoke tests.
Helix has Apple M4, but that doesn't have CSSC yet, so we'll get the full coverage only when we add Apple M5+ (or, presumably, Neoverse N4+/V4+)

PS: We can use more from CSSC: abs, smax

Benchmarks

Each method sums the operation over a 1024-element array of random values (base = current codegen, PR = FEAT_CSSC cnt/ctz):

[Benchmark] public int PopCount_UInt()  { int s = 0; foreach (uint  v in _u)  s += BitOperations.PopCount(v);          return s; }
[Benchmark] public int PopCount_ULong() { int s = 0; foreach (ulong v in _ul) s += BitOperations.PopCount(v);          return s; }
[Benchmark] public int TrailingZeroCount_UInt()  { int s = 0; foreach (uint  v in _u)  s += BitOperations.TrailingZeroCount(v); return s; }
[Benchmark] public int TrailingZeroCount_ULong() { int s = 0; foreach (ulong v in _ul) s += BitOperations.TrailingZeroCount(v); return s; }

Apple M5 Pro, macOS, --corerun base pr:

Method Base PR (CSSC) Ratio
PopCount(uint) 271.9 ns 247.5 ns 0.91
PopCount(ulong) 1,568.5 ns 247.7 ns 0.16
TrailingZeroCount(uint) 255.4 ns 246.3 ns 0.96
TrailingZeroCount(ulong) 255.8 ns 243.4 ns 0.95

The big PopCount(ulong) win comes from avoiding the GPR↔SIMD round-trip, which is expensive on Apple cores. Numbers may look different on server arm64 hardware.

Note

The benchmark harness and results table in this section were generated by GitHub Copilot.

Add support for the Arm64 FEAT_CSSC instruction set extension and use its
scalar `cnt` (population count) and `ctz` (count trailing zeros) instructions
to optimize BitOperations.PopCount and BitOperations.TrailingZeroCount.

When InstructionSet_Cssc is available:
* PopCount emits a single `cnt Rd, Rn` instead of the movi/ins/cnt.8b/addv/umov
  SIMD sequence (and no longer needs an internal SIMD register).
* TrailingZeroCount emits a single `ctz Rd, Rn` instead of `rbit` + `clz`.

The change follows the existing Rcpc2 instruction-set pattern: Cssc is added to
InstructionSetDesc.txt (R2R bit 93) and the generated ISA files, detected in the
minipal (Linux HWCAP2_CSSC, macOS sysctl hw.optional.arm.FEAT_CSSC), enabled in
the VM behind a new EnableArm64Cssc config, and wired through the AOT tools.
Windows is intentionally skipped as there is no official IsProcessorFeaturePresent
flag for CSSC yet.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 8, 2026 07:14
@github-actions github-actions Bot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jul 8, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR adds Arm64 FEAT_CSSC (ARMv8.8+) detection and wiring so the JIT can opportunistically use scalar cnt/ctz instructions to optimize BitOperations.PopCount and BitOperations.TrailingZeroCount on supported CPUs.

Changes:

  • Adds a new Arm64 ISA flag (Cssc) end-to-end (minipal detection → VM CPU flags → JIT ISA plumbing → R2R/ILCompiler metadata).
  • Teaches the Arm64 backend to emit scalar cnt/ctz when InstructionSet_Cssc is available; updates LSRA and emitter/unit tests accordingly.
  • Bumps the JIT/EE interface GUID to reflect the new ISA definition.

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/native/minipal/cpufeatures.h Adds ARM64IntrinsicConstants_Cssc bit to the minipal feature mask.
src/native/minipal/cpufeatures.c Detects FEAT_CSSC via Linux HWCAP2 and Apple sysctlbyname, and sets the Cssc feature bit.
src/coreclr/vm/codeman.cpp Enables InstructionSet_Cssc based on detected CPU features + config gate.
src/coreclr/tools/Common/JitInterface/ThunkGenerator/InstructionSetDesc.txt Adds Cssc to the instruction set definitions and assigns an R2R bit.
src/coreclr/tools/Common/JitInterface/CorInfoInstructionSet.cs Regenerates managed ISA enums/helpers to include Cssc.
src/coreclr/tools/Common/Internal/Runtime/ReadyToRunInstructionSetHelper.cs Maps InstructionSet.ARM64_Cssc to the new R2R instruction set value.
src/coreclr/tools/Common/Internal/Runtime/ReadyToRunInstructionSet.cs Adds ReadyToRunInstructionSet.Cssc = 93.
src/coreclr/tools/Common/InstructionSetHelpers.cs Includes cssc in the optimistic ARM64 instruction set support list.
src/coreclr/tools/Common/Compiler/HardwareIntrinsicHelpers.cs Keeps tool-side Arm64 intrinsic-constant flags in sync with minipal and adds cssc mapping.
src/coreclr/jit/lsraarm64.cpp Avoids requiring a SIMD temp register for PopCount when CSSC scalar cnt is available.
src/coreclr/jit/instrsarm64.h Adds scalar cnt (GPR form) and new scalar ctz instruction encodings.
src/coreclr/jit/hwintrinsic.cpp Extends ISA range table with a slot for Cssc.
src/coreclr/jit/emitarm64.cpp Allows emitting GPR cnt by falling through to the DR_2G encoding; adds support for ctz formatting.
src/coreclr/jit/codegenarmarch.cpp Uses scalar cnt/ctz when opportunistically depending on InstructionSet_Cssc.
src/coreclr/jit/codegenarm64test.cpp Adds emitter unit tests covering scalar cnt/ctz emission.
src/coreclr/inc/readytoruninstructionset.h Adds READYTORUN_INSTRUCTION_Cssc=93.
src/coreclr/inc/jiteeversionguid.h Updates the JIT/EE interface version GUID.
src/coreclr/inc/corinfoinstructionset.h Regenerates CORINFO instruction set enum/string mapping and adds R2R→CORINFO mapping for Cssc.
src/coreclr/inc/clrconfigvalues.h Adds EXTERNAL_EnableArm64Cssc config switch.

Comment thread src/native/minipal/cpufeatures.c
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Copilot Code Review

Holistic Assessment

Motivation: Justified — FEAT_CSSC provides scalar cnt and ctz instructions that replace multi-instruction sequences for PopCount and TrailingZeroCount on ARM64 hardware that supports it (e.g., Apple M5, future Arm Cobalt). The codegen improvement is unambiguous (5→1 instructions for PopCount, 2→1 for TZC).

Approach: The approach is correct — add CSSC as an internal-only instruction set (no public API surface), detect it at runtime via hwcap/sysctl, and use compOpportunisticallyDependsOn to opportunistically emit better instructions when available. This follows established patterns used by Rcpc, Rcpc2, and Dczva.

Summary: ✅ LGTM. The change is well-structured, follows existing conventions for internal instruction sets, instruction encodings are correct, and the emitter/LSRA/codegen changes are consistent with each other. No correctness, safety, or compatibility concerns found.


Detailed Findings

Detailed Findings

✅ Instruction Encoding Correctness

Verified the ARM64 instruction encodings against the FEAT_CSSC specification:

  • cnt (DR_2G): 0x5AC01C00sf:1 0101101011000000 000111 Rn Rd
  • ctz (DR_2G): 0x5AC01800sf:1 0101101011000000 000110 Rn Rd
  • cnt (DV_2M): 0x0E205800 — unchanged vector encoding ✓

The promotion of cnt from INST1 (vector-only) to INST2 (vector + general register) is correct, and ctz is correctly defined as INST1 (general register only, no vector form).

✅ Emitter Dispatch Logic

The emitIns_R_R function correctly dispatches INS_cnt:

  • Vector registers → DV_2M encoding (existing path, unchanged)
  • General registers → falls through to DR_2G encoding (new CSSC path)

The removed guard (if (ins == INS_cnt) { break; }) is now replaced by a comment and fallthrough to the shared DR_2G handler used by INS_ctz, INS_rev, and other two-register general instructions.

✅ LSRA Consistency

The LSRA change correctly avoids allocating a SIMD temp register for PopCount when CSSC is available, since the scalar cnt instruction operates entirely in general registers.

✅ Opportunistic ISA Usage

compOpportunisticallyDependsOn(InstructionSet_Cssc) is the correct API for optional runtime optimizations — the JIT emits CSSC instructions only on hardware confirmed to support them, and falls back to the existing multi-instruction sequence otherwise.

✅ InstructionSetDesc.txt & Generated Files

The CSSC entry follows the internal-only pattern (empty public name column) matching Rcpc/Rcpc2/Dczva. R2R bit 93 is correctly assigned as the next available. The JIT-EE version GUID update is expected since the instruction set enum changed.

✅ CPU Feature Detection

  • Linux: HWCAP2_CSSC = (1UL << 34) matches the kernel definition. hwCap2 is unsigned long (64-bit on ARM64). ✓
  • macOS: hw.optional.arm.FEAT_CSSC sysctl name is correct. ✓
  • Windows: TODO comment is appropriate — IsProcessorFeaturePresent doesn't have a CSSC flag yet.

✅ ReadyToRun & NativeAOT

CSSC is correctly added to the optimistic instruction set builder in InstructionSetHelpers.cs, enabling crossgen2/NativeAOT to emit CSSC code paths with runtime guards.

💡 Emitter Unit Test Coverage

The emitter unit tests in codegenarm64test.cpp encode INS_cnt and INS_ctz with general registers unconditionally (testing encoding correctness, not runtime availability). This is the expected pattern for emitter tests.

Note

This review was generated by GitHub Copilot.

Generated by Code Review for issue #130332 · ● 41.3M ·

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 8, 2026 07:27

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 19 out of 19 changed files in this pull request and generated no new comments.

@EgorBo

EgorBo commented Jul 8, 2026

Copy link
Copy Markdown
Member Author

@jkotas @dotnet/jit-contrib any opinion on this? we don't yet have a HW to test it on CI (the closest is Apple M4 in helix queue, but we need Apple M5 or later, or Neoverse V4/N4 (my speculation) or later), but from the other hand it's just two simple instructions, perhaps, for marketing purposes? 🙂

I have ran the full test suite locally.

@jkotas

jkotas commented Jul 8, 2026

Copy link
Copy Markdown
Member

By default, we should not be taking advantage of instruction extensions that we do not have sufficient automated test coverage for. Not worth the risk.

I do not have a problem with the code being added, but it should be off by default until we have sufficient automated test coverage.

Copilot AI review requested due to automatic review settings July 8, 2026 15:10
@EgorBo EgorBo had a problem deploying to copilot-pat-pool July 8, 2026 15:11 — with GitHub Actions Failure
@EgorBo

EgorBo commented Jul 8, 2026

Copy link
Copy Markdown
Member Author

By default, we should not be taking advantage of instruction extensions that we do not have sufficient automated test coverage for. Not worth the risk.

I do not have a problem with the code being added, but it should be off by default until we have sufficient automated test coverage.

Ok, I've disabled it by default (DOTNET_EnableArm64Cssc=0)

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 19 out of 19 changed files in this pull request and generated no new comments.

Comment thread src/coreclr/jit/codegenarm64test.cpp Outdated
Comment thread src/coreclr/jit/emitarm64.cpp

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 1 comment.

Comment thread src/coreclr/inc/clrconfigvalues.h
Add compIsaSupportedDebugOnly(Cssc) asserts in emitIns_R_R for the
general-register cnt/ctz encodings, plus a JitConfig.EnableArm64Cssc
toggle so the altjit ISA block can enable CSSC for codegen inspection
and emitter unit tests without CSSC hardware.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 9, 2026 09:00
@EgorBo EgorBo had a problem deploying to copilot-pat-pool July 9, 2026 09:00 — with GitHub Actions Failure

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 20 out of 20 changed files in this pull request and generated no new comments.

Copilot AI review requested due to automatic review settings July 10, 2026 22:40
@EgorBo EgorBo temporarily deployed to copilot-pat-pool July 10, 2026 22:40 — with GitHub Actions Inactive
@EgorBo EgorBo temporarily deployed to copilot-pat-pool July 10, 2026 22:40 — with GitHub Actions Inactive
@EgorBo EgorBo had a problem deploying to copilot-pat-pool July 10, 2026 22:41 — with GitHub Actions Failure
@EgorBo EgorBo temporarily deployed to copilot-pat-pool July 10, 2026 22:42 — with GitHub Actions Inactive
@EgorBo EgorBo temporarily deployed to copilot-pat-pool July 10, 2026 22:43 — with GitHub Actions Inactive
@EgorBo EgorBo temporarily deployed to copilot-pat-pool July 10, 2026 22:43 — with GitHub Actions Inactive

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 20 out of 20 changed files in this pull request and generated no new comments.

@EgorBo

EgorBo commented Jul 12, 2026

Copy link
Copy Markdown
Member Author

/ba-g wasm is heavily broken on ci

@EgorBo EgorBo enabled auto-merge (squash) July 12, 2026 12:03
@EgorBo EgorBo merged commit 0e7b627 into main Jul 12, 2026
184 of 192 checks passed
@EgorBo EgorBo deleted the egorbo/arm64-cssc-popcnt-tzc branch July 12, 2026 12:03
@dotnet-milestone-bot dotnet-milestone-bot Bot added this to the 11.0-preview7 milestone Jul 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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.

5 participants