Skip to content

JIT: (bug) [ARM64] FusedMultiplyAddScalar folds an unrelated GT_NEG through a bit-reinterpret, producing wrong results #134053

Description

@EgorBo

On arm64, Lowering::LowerHWIntrinsicFusedMultiplyAddScalar strips a GT_NEG under the operand of AdvSimd.FusedMultiplyAddScalar and compensates with fnmsub/fnmadd, without checking the type of the negated value. When AsDouble()/AsSingle() reinterprets the bits, the sign flip is applied to a completely different value and the result is silently wrong.

Minimal Repro

using System;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.Arm;

public class Program
{
    // Integer (two's complement) negation reinterpreted as double.
    [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
    public static double TestA(long bits)
    {
        Vector64<double> addend = Vector64.Create(-bits).AsDouble();
        return AdvSimd.FusedMultiplyAddScalar(addend, Vector64.Create(1.0), Vector64.Create(1.0)).ToScalar();
    }

    // Double negation flips bit 63; the float FMA consumes bits 0..31.
    [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
    public static float TestB(double d)
    {
        Vector64<float> addend = Vector64.Create(-d).AsSingle();
        return AdvSimd.FusedMultiplyAddScalar(addend, Vector64.Create(1.0f), Vector64.Create(1.0f)).ToScalar();
    }

    public static void Main()
    {
        long bits = BitConverter.DoubleToInt64Bits(3.0);
        double expectedA = BitConverter.Int64BitsToDouble(-bits) * 1.0 + 1.0;
        Console.WriteLine($"A: actual={TestA(bits)} expected={expectedA}");

        double d = BitConverter.Int64BitsToDouble(0x4008_0000_4000_0000);
        float lo = BitConverter.Int32BitsToSingle((int)BitConverter.DoubleToInt64Bits(-d));
        float expectedB = lo * 1.0f + 1.0f;
        Console.WriteLine($"B: actual={TestB(d)} expected={expectedB}");
    }
}

Expected

A: actual=-0.5 expected=-0.5
B: actual=3 expected=3

Actual

A: actual=-2 expected=-0.5
B: actual=-1 expected=3

Codegen for TestA contains no neg at all: mov v0.d[0], x0 / fnmsub d0, d16, d16, d0.
For TestB a float-width sign flip (bit 31) replaces the double negation (bit 63).

Notes

lowerarmarch.cpp, LowerHWIntrinsicFusedMultiplyAddScalar: the valueOp->OperIs(GT_NEG) check
removes the NEG with no type check, but AsDouble()/AsSingle() between Vector64.Create and the
FMA are pure bit reinterprets, so valueOp's type can differ from node->GetSimdBaseType() (case B)
or not be floating-point at all (case A).
Fix direction: also require varTypeIsFloating(valueOp) and valueOp->TypeGet() == node->GetSimdBaseType().
Deterministic; also reproduces on released .NET 10.0.9.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions