https://gcc.gnu.org/bugzilla/show_bug.cgi?id=128247

            Bug ID: 128247
           Summary: Strided broadcast of a single element may overwrite
                    other elements on RISC-V
           Product: gcc
           Version: 17.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: target
          Assignee: unassigned at gcc dot gnu.org
          Reporter: law at gcc dot gnu.org
  Target Milestone: ---

LLM review noted concerns with this pattern:

(define_expand "@pred_strided_broadcast<mode>"
  [(set (match_operand:V_VLS 0 "register_operand")
        (if_then_else:V_VLS
          (unspec:<VM>
            [(match_operand:<VM> 1 "strided_broadcast_mask_operand")
             (match_operand 4 "vector_length_operand")
             (match_operand 5 "const_int_operand")
             (match_operand 6 "const_int_operand")
             (match_operand 7 "const_int_operand")
             (reg:SI VL_REGNUM)
             (reg:SI VTYPE_REGNUM)] UNSPEC_VPREDICATE)
          (vec_duplicate:V_VLS
            (match_operand:<VEL> 3 "strided_broadcast_operand"))
          (match_operand:V_VLS 2 "vector_merge_operand")))]
  "TARGET_VECTOR"
{
  if (satisfies_constraint_Wb1 (operands[1]))
    {
      /* If we're asked to set a single element (like vmv.s.x but we
         need to go via memory here) and the tail policy is agnostic
         we can overwrite all elements.
         Thus, set the mask to broadcast.  */
      operands[1] = CONSTM1_RTX (<VM>mode);
      if (!satisfies_constraint_vu (operands[2])
          && GET_MODE_SIZE (<VEL>mode) > UNITS_PER_WORD)
        {
          /* Case 2: vmv.s.x (TU, x == memory) ==>
             vl = 0 or 1; + vlse.v (TU) in RV32 system  */
          /* In this case we must not overwrite the residual elements,
             so set the vector length to 0/1.  */
          operands[4] = riscv_vector::gen_avl_for_scalar_move (operands[4]);
        }
    }
})

In particular note the condition that adjusts operands[4].  Now consider this
LLM generated testcase:

/* { dg-do run { target { riscv_v && rv64 } } } */
/* { dg-options "-O2 -march=rv64gcv_zvfhmin -mabi=lp64d" } */
#include "riscv_vector.h"

/* Without Zvfh there is no vfmv.s.f, so the scalar move of an _Float16
   goes through the strided-broadcast fallback.  It must still write only
   element 0 and leave elements 1..vl-1 of the merge operand alone.  */

__attribute__ ((noinline, noclone)) vfloat16m1_t
f (vfloat16m1_t m, _Float16 x, size_t vl)
{
  return __riscv_vfmv_s_f_f16m1_tu (m, x, vl);
}

unsigned short Init[8], Out[8];
volatile unsigned short XB = 0x3c00;    /* 1.0 */
volatile size_t VL = 4;

int
main (void)
{
  for (int i = 0; i < 8; i++)
    Init[i] = 0x1111;
  unsigned short xb = XB;
  _Float16 x;
  __builtin_memcpy (&x, &xb, 2);
  vfloat16m1_t m
    = __riscv_vreinterpret_v_u16m1_f16m1 (__riscv_vle16_v_u16m1 (Init, 8));
  vfloat16m1_t r = f (m, x, VL);
  __riscv_vse16_v_u16m1 (Out, __riscv_vreinterpret_v_f16m1_u16m1 (r), 8);
  if (Out[0] != 0x3c00)
    __builtin_abort ();
  for (int i = 1; i < 8; i++)
    if (Out[i] != 0x1111)
      __builtin_abort ();
  __builtin_exit (0);
}

Generates this for "f":

f:
        fmv.x.s a5,fa0  # 26    [c=4 l=4]  *movhf_softfloat/5
        slli    a5,a5,16
        srai    a5,a5,16
        addi    sp,sp,-16       # 28    [c=4 l=4]  *adddi3/1
        addi    t0,sp,14        # 27    [c=4 l=4]  *adddi3/1
        sh      a5,14(sp)       # 16    [c=4 l=4]  *movhf_softfloat/3
        vsetvli zero,a0,e16,m1,tu,ma    # 39    [c=0 l=4] 
vsetvl_discard_resultdi
        vlse16.v        v8,0(t0),zero   # 12    [c=12 l=4] 
*pred_strided_broadcastrvvm1hf_zvfhmin/3
        addi    sp,sp,16        # 32    [c=4 l=4]  *adddi3/1
        jr      ra              # 33    [c=0 l=4]  simple_return_internal

Note the vsetvl gets VL from a0, which comes from the caller.  So even with the
tail undistubed policy we're writing more elements than we should AFAICT.

Reply via email to