Skip to content

JIT: Assertion failed 'canRepairTargetOverlap || (targetReg != intrin.op2->GetRegNum()) || genIsSameLocalVar(rmwOp, intrin.op2)' during 'Generate code' #130247

Description

@jakobbotsch

The following example, found by Fuzzlyn, causes a JIT assertion failure on arm64 (SVE):

// Generated by Fuzzlyn v3.3 on 2026-07-05 17:04:37
// Run on Arm64 Linux
// Seed: 4638962307177550988-vectort,vector64,vector128,armadvsimd,armadvsimdarm64,armaes,armarmbase,armarmbasearm64,armcrc32,armcrc32arm64,armdp,armrdm,armrdmarm64,armsha1,armsha256,armsve,armsve2
// Reduced from 44.1 KiB to 1.2 KiB in 00:00:31
// Hits JIT assert for Release:
// Assertion failed 'canRepairTargetOverlap || (targetReg != intrin.op2->GetRegNum()) || genIsSameLocalVar(rmwOp, intrin.op2)' in 'Program:M0()' during 'Generate code' (IL size 138; hash 0xaf50ff37; FullOpts)
// 
//     File: /__w/1/s/src/coreclr/jit/hwintrinsiccodegenarm64.cpp Line: 874
// 
using System.Numerics;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.Arm;

public class Program
{
    public static void Main()
    {
        M0();
    }

    public static void M0()
    {
        var vr1 = Vector.Create<long>(0);
        var vr3 = Vector128.CreateScalar(2714202814947371637L).AsVector();
        var vr7 = (Vector<short>)Vector128.CreateScalar(0).AsVector();
        var vr8 = Vector.Create<short>(0);
        var vr6 = Sve.CreateBreakPropagateMask(vr7, vr8);
        var vr11 = (short)0;
        var vr10 = Vector128.CreateScalar(vr11).AsVector();
        var vr13 = (short)0;
        var vr12 = Vector128.CreateScalar(vr13).AsVector();
        var vr15 = (short)0;
        var vr14 = Vector128.CreateScalar(vr15).AsVector();
        var vr9 = Sve.CreateBreakBeforePropagateMask(vr10, vr12, vr14);
        var vr5 = Sve.And(vr6, vr9);
        var vr4 = Sve.AddAcross(vr5);
        var vr2 = Sve.AddSaturate(vr3, vr4);
        if (Sve.TestLastTrue(vr1, vr2))
        {
            var vr0 = new int[, ]
            {
                {
                    0
                }
            };
        }
    }
}

The assertion:

Assertion failed 'canRepairTargetOverlap || (targetReg != intrin.op2->GetRegNum()) || genIsSameLocalVar(rmwOp, intrin.op2)' in 'Program:M0()' during 'Generate code' (IL size 138; hash 0xaf50ff37; FullOpts)

    File: /__w/1/s/src/coreclr/jit/hwintrinsiccodegenarm64.cpp Line: 874
Analysis of jitdump (AI-generated)

[!NOTE]
The following analysis was generated by AI (GitHub Copilot) and may contain inaccuracies.

Analysis (AI-generated)

Assertion: canRepairTargetOverlap || (targetReg != intrin.op2->GetRegNum()) || genIsSameLocalVar(rmwOp, intrin.op2)
Location: src/coreclr/jit/hwintrinsiccodegenarm64.cpp:874 (CodeGen::checkRMWRegisters)
Phase: Generate code (FullOpts), target arm64/SVE.

What the JIT is compiling

The reduced method computes a chain of SVE predicate operations. After
morph/lowering the relevant tree in BB01 is:

[000041] HWINTRINSIC mask And_Predicates            (RMW flag set)
   op1 = [000015] CreateBreakPropagateMask
   op2 = [000031] CreateBreakBeforePropagateMask

And_Predicates is wrapped in a ConditionalSelect(ptrue, And_Predicates(a, b), zero)
pattern, so it is generated through the embedded-masked path
(genEmbeddedMaskedHWIntrinsic).

Register state at the failure

From the JitDump codegen trace right before the assert:

  • Target register of the operation: p0 (t86 ConditionalSelect REG p0)
  • op1 (CreateBreakPropagateMask) is materialized in p2
  • op2 (CreateBreakBeforePropagateMask, [000031]) is materialized in p0

So targetReg == op2Reg == p0, while rmwReg == p1/p2 != targetReg.

Why the assert fires

genEmbeddedMaskedHWIntrinsic unconditionally calls checkRMWRegisters(intrinEmbMask, targetReg).
For And_Predicates (2 operands, not an FMA and not an explicit-masked
operation) checkRMWRegisters falls into the default branch and selects
rmwOp = op1. Then, because targetReg != rmwReg, it validates each other
operand with:

case 2:
    if (rmwReg != intrin.op2->GetRegNum())
        assert(canRepairTargetOverlap || (targetReg != intrin.op2->GetRegNum())
               || genIsSameLocalVar(rmwOp, intrin.op2));

Here targetReg (p0) == op2Reg (p0), genIsSameLocalVar is false, and
canRepairTargetOverlap is only true for NI_Sve_MultiplyAddRotateComplex, so
the assertion fails.

Root cause

The mismatch is between LSRA and codegen over And_Predicates:

  • On arm64, GenTree::isRMWHWIntrinsic returns true for any
    IsEmbeddedMaskedOperation/IsOptionalEmbeddedMaskedOperation
    (gentree.cpp:22443). And_Predicates carries
    HW_Flag_EmbeddedMaskedOperation (hwintrinsiclistarm64sve.h:568), so it is
    treated as a destructive RMW operation whose destructive operand
    checkRMWRegisters assumes to be op1.
  • However, the emitted instruction is the SVE all-predicate AND
    AND Pd.B, Pg/Z, Pn.B, Pm.B, which is not destructive — the destination
    predicate may legally alias either source predicate. LSRA correctly allowed
    the target to reuse op2's register (p0).

Codegen's checkRMWRegisters does not model this: it insists the target may
only overlap the single fixed rmwOp (op1). When the allocator legitimately
places op2 in the target register, the debug validation trips even though the
final encoding would be correct.

In short: And_Predicates (and likely other non-destructive predicate
embedded-masked operations) is over-classified as an op1-destructive RMW
intrinsic, so checkRMWRegisters rejects a valid target/op2 register overlap
that LSRA produced. Either checkRMWRegisters must recognize that predicate
operations allow the target to alias any source operand, or the RMW/destructive
operand modeling for these predicate intrinsics must be made consistent between
LSRA and codegen.

Attached details.zip file that includes the repro.mc and jitdump.txt files for this example.

cc @dotnet/jit-contrib

details.zip

Metadata

Metadata

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMIarm-sveWork related to arm64 SVE/SVE2 supportblocking-clean-ci-optionalBlocking optional rolling runs

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions