Skip to content

ARM64 SVE: mask conversion not always optimised away #108241

Closed

Description

@a74nh

With DOTNET_TieredCompilation=0

      [MethodImpl(MethodImplOptions.NoInlining)]
      public static unsafe int foo(ref int* src, int length)
      {
          Vector<int> total = new Vector<int>(0);
          Vector<int> pred = (Vector<int>)Sve.CreateWhileLessThanMask32Bit(0, length);
          Vector<int> vec = Sve.LoadVector(pred, src);
          total = Sve.ConditionalSelect(pred, Sve.Add(total, vec), total);
          return (int)Sve.AddAcross(total).ToScalar();
      }
G_M25815_IG01:  ;; offset=0x0000
            stp     fp, lr, [sp, #-0x20]!
            mov     fp, sp
            stp     xzr, xzr, [fp, #0x10]	// [V02 loc0], [V02 loc0+0x08]
						;; size=12 bbWeight=1 PerfScore 2.50
G_M25815_IG02:  ;; offset=0x000C
            str     xzr, [fp, #0x10]
            str     xzr, [fp, #0x18]
            mov     w2, wzr
            whilelt p0.s, w2, w1
            mov     z16.s, p0/z, #1
            ptrue   p0.s
            cmpne   p0.s, p0/z, z16.s, #0
            ldr     q16, [fp, #0x10]	// [V02 loc0]
            ldr     x0, [x0]
            ld1w    { z17.s }, p0/z, [x0]
            ldr     q18, [fp, #0x10]	// [V02 loc0]
            add     z16.s, z16.s, z17.s
            sel     z16.s, p0, z16.s, z18.s
            str     q16, [fp, #0x10]	// [V02 loc0]
            ldr     q16, [fp, #0x10]	// [V02 loc0]
            ptrue   p0.s
            saddv   d16, p0, z16.s
            umov    x0, v16.d[0]
						;; size=72 bbWeight=1 PerfScore 39.50
G_M25815_IG03:  ;; offset=0x0054
            ldp     fp, lr, [sp], #0x20
            ret     lr
						;; size=8 bbWeight=1 PerfScore 2.00

These three lines are not required. They are converting mask -> vector -> mask

            mov     z16.s, p0/z, #1
            ptrue   p0.s
            cmpne   p0.s, p0/z, z16.s, #0

I suspect this is because there are two uses of pred - in conditional select and load vector.

Activity

  1. ghost added
    area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI
    on Sep 25, 2024
  2. added
    arm-sveWork related to arm64 SVE/SVE2 support
    and removed
    area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI
    on Sep 25, 2024
  3. added
    area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI
    on Sep 25, 2024
  4. dotnet-policy-service commented on Sep 25, 2024

    @dotnet-policy-service
    Contributor

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

  5. a74nh commented on Sep 25, 2024

    @a74nh
    ContributorAuthor
          [MethodImpl(MethodImplOptions.NoInlining)]
          public static unsafe int foo(ref int* src, int length)
          {
              Vector<int> pred = (Vector<int>)Sve.CreateWhileLessThanMask32Bit(0, length);
              Vector<int> vec = Sve.LoadVector(pred, src);
              return (int)Sve.AddAcross(vec).ToScalar();
          }

    Reducing down and the conversion vanishes.

  6. tannergooding commented on Sep 25, 2024

    @tannergooding
    Member

    This is because Vector<int> pred produces a TYP_SIMD local and so that's what gets CSE'd.

    We still need to add a step to the JIT that identifies cases of STORELCL(TYP_SIMD, ConvertMaskToVector(x)) and transforms it instead to STORELCL(TYP_MASK, x) and replaces usages with ConvertMaskToVector(lcl); or something to that effect.

  7. tannergooding commented on Sep 25, 2024

    @tannergooding
    Member

    If you were to change the code to instead be:

          [MethodImpl(MethodImplOptions.NoInlining)]
          public static unsafe int foo(ref int* src, int length)
          {
              Vector<int> total = new Vector<int>(0);
              Vector<int> vec = Sve.LoadVector(Sve.CreateWhileLessThanMask32Bit(0, length).AsInt32(), src);
              total = Sve.ConditionalSelect(Sve.CreateWhileLessThanMask32Bit(0, length).AsInt32(), Sve.Add(total, vec), total);
              return (int)Sve.AddAcross(total).ToScalar();
          }

    Then I expect it would also be fixed, as the CreateWhileLessThanMask32Bit would be CSE'd instead and produce a TYP_MASK local.

    We should still add the right step to the JIT to ensure the right CSE happens, however.

  8. added this to the 10.0.0 milestone on Sep 25, 2024
  9. removed
    untriagedNew issue has not been triaged by the area owner
    on Sep 25, 2024
  10. a74nh commented on Sep 30, 2024

    @a74nh
    ContributorAuthor

    Also:

          i += Sve.GetActiveElementCount(pz, pz);

    becomes:

                ptrue   p2.b
                cmpne   p1.b, p2/z, z16.b, #0
                ptrue   p0.b
                cmpne   p0.b, p0/z, z16.b, #0
                cntp    x0, p1, p0.b
                add     x0, x0, x1
    

    Both args to the cntp should be using the same predicate instead of doing two conversions.

  11. tannergooding commented on Sep 30, 2024

    @tannergooding
    Member

    That's likely because SVE isn't using mask constants currently and so LSRA doesn't do the checks for "is constant already in a register"

  12. added
    Priority:1Work that is critical for the release, but we could probably ship without
    on Oct 22, 2024
  13. a74nh commented on Oct 23, 2024

    @a74nh
    ContributorAuthor

    This is because Vector<int> pred produces a TYP_SIMD local and so that's what gets CSE'd.

    We still need to add a step to the JIT that identifies cases of STORELCL(TYP_SIMD, ConvertMaskToVector(x)) and transforms it instead to STORELCL(TYP_MASK, x) and replaces usages with ConvertMaskToVector(lcl); or something to that effect.

    Switching the STORELCL is fairly easy.

    Finding the uses to remove the ConvertMaskToVector is a little more tricky, depending on when this is done.

    • I could do this in HIR, and use LocalsTreeList() to find all local uses, which means doing this between morph and build SSA.
    • Or I could do this in LIR, and use BlockRange().TryGetUse()
    • Or elsewhere?
  14. tannergooding commented on Oct 24, 2024

    @tannergooding
    Member

    I believe the general idea is that we want to do this as its own pass after local morph, as per #99608 (comment)

  15. a74nh commented on Oct 25, 2024

    @a74nh
    ContributorAuthor

    See Statement::LocalsTreeList. It allows to quickly check whether a statement contains a local you are interested in.

    @jakobbotsch, picking up on your suggestion in 99608.

    I'm using Statement::LocalsTreeList. This requires fgNodeThreading to be AllLocals.

    when I find the ConvertVectorToMask/ConvertMaskToVector I remove them by bashing the node to GT_NOP and rewiring the parent to the child.

    Then I need to fix up next/prev pointers. In previous PRs I did this using fgSetStmtSeq(). However, that requires fgNodeThreading to be AllTrees.

    Any suggestions for how I should be fixing that up?

  16. jakobbotsch commented on Oct 25, 2024

    @jakobbotsch
    Member

    fgSequenceLocals does the same as fgSetStmtSeq for locals linking.

  17. added
    in-prThere is an active PR which will close this issue when it is merged
    on Oct 28, 2024
  18. locked and limited conversation to collaborators on Dec 19, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Priority:1Work that is critical for the release, but we could probably ship withoutarea-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMIarm-sveWork related to arm64 SVE/SVE2 supportin-prThere is an active PR which will close this issue when it is merged

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions