On Mon, 22 Jun 2026, Abhishek Kaushik wrote:

> Add optabs and internal functions for SVE2 SMLALB and UMLALB, and
> teach AArch64 instruction selection to use them for suitable IFN_FMA,
> IFN_COND_FMA and IFN_COND_ADD operations.
> 
> The instruction selection code recognizes multiply-add forms where both
> multiplicands are known to fit in half-width signed or unsigned elements.
> For predicated forms, it also rebuilds the selected multiplicand so that
> inactive lanes are zero when needed, allowing the final MLALB operation to
> preserve the original merge value.
> 
> Fixed-length SVE vectors are rejected for now because the new expanders use
> scalable SVE source modes.
> 
> Bootstrapped and regtested on aarch64-linux-gnu.

optabs need documenting in md.texi, internal functions need documenting
in ifn.texi

>       PR/target 122751
> 
> gcc/ChangeLog:
> 
>       * config/aarch64/aarch64-sve2.md (smlalb<mode>4): New expander.
>       (umlalb<mode>4): Likewise.
>       (@aarch64_sve_add_smlalb_partial<mode>): New insn.
>       (@aarch64_sve_add_umlalb_partial<mode>): Likewise.
>       * config/aarch64/aarch64.cc: Include internal-fn.h.
>       (aarch64_mlalb_vector_type_fits_half_p): New function.
>       (aarch64_mlalb_extend_p): Likewise.
>       (aarch64_mlalb_operand_fits_half_p): Likewise.
>       (aarch64_mlalb_zeroing_mask_load_p): Likewise.
>       (aarch64_mlalb_same_mode_accumulator_conversion_p): Likewise.
>       (aarch64_strip_mlalb_accumulator_conversions): Likewise.
>       (aarch64_cond_ifn_for_mlalb_arith): Likewise.
>       (aarch64_build_mlalb_zeroing_op): Likewise.
>       (aarch64_mlalb_source_element_type): Likewise.
>       (aarch64_build_mlalb_source_op): Likewise.
>       (aarch64_mlalb_mult_node_p): Likewise.
>       (aarch64_mlalb_candidate): New struct.
>       (aarch64_check_mlalb_candidate): New function.
>       (aarch64_replace_with_mlalb): Likewise.
>       (aarch64_try_cond_mlalb): Likewise.
>       (aarch64_try_fma_mlalb): Likewise.
>       (aarch64_instruction_selection): Try MLALB selection.
>       * config/aarch64/iterators.md (VHALF_EL): New mode attribute.
>       * internal-fn.def (SMLALB): New internal function.
>       (UMLALB): Likewise.
>       * optabs.def (smlalb_optab): New optab.
>       (umlalb_optab): Likewise.
> 
> gcc/testsuite/ChangeLog:
> 
>       * gcc.target/aarch64/sve2/mlalb-cond-add-selection.c: New test.
>       * gcc.target/aarch64/sve2/mlalb-cond-fma-selection.c: New test.
>       * gcc.target/aarch64/sve2/mlalb-fma-fixed-vector-length.c: New test.
>       * gcc.target/aarch64/sve2/mlalb-fma-selection.c: New test.
> ---
>  gcc/config/aarch64/aarch64-sve2.md            |  69 ++
>  gcc/config/aarch64/aarch64.cc                 | 634 ++++++++++++++++++
>  gcc/config/aarch64/iterators.md               |   5 +
>  gcc/internal-fn.def                           |   2 +
>  gcc/optabs.def                                |   2 +
>  .../aarch64/sve2/mlalb-cond-add-selection.c   | 276 ++++++++
>  .../aarch64/sve2/mlalb-cond-fma-selection.c   | 126 ++++
>  .../sve2/mlalb-fma-fixed-vector-length.c      |  16 +
>  .../aarch64/sve2/mlalb-fma-selection.c        | 134 ++++
>  9 files changed, 1264 insertions(+)
>  create mode 100644 
> gcc/testsuite/gcc.target/aarch64/sve2/mlalb-cond-add-selection.c
>  create mode 100644 
> gcc/testsuite/gcc.target/aarch64/sve2/mlalb-cond-fma-selection.c
>  create mode 100644 
> gcc/testsuite/gcc.target/aarch64/sve2/mlalb-fma-fixed-vector-length.c
>  create mode 100644 
> gcc/testsuite/gcc.target/aarch64/sve2/mlalb-fma-selection.c
> 
> diff --git a/gcc/config/aarch64/aarch64-sve2.md 
> b/gcc/config/aarch64/aarch64-sve2.md
> index 1d428619c07..6e1c302a946 100644
> --- a/gcc/config/aarch64/aarch64-sve2.md
> +++ b/gcc/config/aarch64/aarch64-sve2.md
> @@ -2696,6 +2696,75 @@
>  ;; - UMLSLT
>  ;; -------------------------------------------------------------------------
>  
> +;; Signed MLA bottom-half operation.
> +(define_expand "smlalb<mode>4"
> +  [(match_operand:SVE_FULL_HSDI 0 "register_operand")
> +   (match_operand:SVE_FULL_HSDI 1 "register_operand")
> +   (match_operand:<VHALF_EL> 2 "register_operand")
> +   (match_operand:<VHALF_EL> 3 "register_operand")]
> +  "TARGET_SVE2"
> +  {
> +    emit_insn (gen_aarch64_sve_add_smlalb_partial<mode>
> +              (operands[0], operands[1], operands[2], operands[3]));
> +    DONE;
> +  }
> +)
> +
> +;; Unsigned MLA bottom-half operation.
> +(define_expand "umlalb<mode>4"
> +  [(match_operand:SVE_FULL_HSDI 0 "register_operand")
> +   (match_operand:SVE_FULL_HSDI 1 "register_operand")
> +   (match_operand:<VHALF_EL> 2 "register_operand")
> +   (match_operand:<VHALF_EL> 3 "register_operand")]
> +  "TARGET_SVE2"
> +  {
> +    emit_insn (gen_aarch64_sve_add_umlalb_partial<mode>
> +              (operands[0], operands[1], operands[2], operands[3]));
> +    DONE;
> +  }
> +)
> +
> +(define_insn "@aarch64_sve_add_smlalb_partial<mode>"
> +  [(set (match_operand:SVE_FULL_HSDI 0 "register_operand" "=w,?&w")
> +     (plus:SVE_FULL_HSDI
> +       (unspec:SVE_FULL_HSDI
> +         [(match_operand:<VHALF_EL> 2 "register_operand" "w,w")
> +          (match_operand:<VHALF_EL> 3 "register_operand" "w,w")]
> +         UNSPEC_SMULLB)
> +       (match_operand:SVE_FULL_HSDI 1 "register_operand" "0,w")))]
> +  "TARGET_SVE2"
> +  {
> +    if (which_alternative == 0)
> +      return "smlalb\t%0.<Vetype>, %2.<Ventype>, "
> +          "%3.<Ventype>";
> +    return "movprfx\t%0, %1\;smlalb\t%0.<Vetype>, "
> +        "%2.<Ventype>, %3.<Ventype>";
> +  }
> +  [(set_attr "movprfx" "*,yes")
> +   (set_attr "sve_type" "sve_int_mul")]
> +)
> +
> +;; Unsigned MLA bottom-half operation with same-lane-count source operands.
> +(define_insn "@aarch64_sve_add_umlalb_partial<mode>"
> +  [(set (match_operand:SVE_FULL_HSDI 0 "register_operand" "=w,?&w")
> +     (plus:SVE_FULL_HSDI
> +       (unspec:SVE_FULL_HSDI
> +         [(match_operand:<VHALF_EL> 2 "register_operand" "w,w")
> +          (match_operand:<VHALF_EL> 3 "register_operand" "w,w")]
> +         UNSPEC_UMULLB)
> +       (match_operand:SVE_FULL_HSDI 1 "register_operand" "0,w")))]
> +  "TARGET_SVE2"
> +  {
> +    if (which_alternative == 0)
> +      return "umlalb\t%0.<Vetype>, %2.<Ventype>, "
> +          "%3.<Ventype>";
> +    return "movprfx\t%0, %1\;umlalb\t%0.<Vetype>, "
> +        "%2.<Ventype>, %3.<Ventype>";
> +  }
> +  [(set_attr "movprfx" "*,yes")
> +   (set_attr "sve_type" "sve_int_mul")]
> +)
> +
>  ;; Non-saturating MLA operations.
>  (define_insn "@aarch64_sve_add_<sve_int_op><mode>"
>    [(set (match_operand:SVE_FULL_HSDI 0 "register_operand")
> diff --git a/gcc/config/aarch64/aarch64.cc b/gcc/config/aarch64/aarch64.cc
> index c9403ed94e7..f094e026d12 100644
> --- a/gcc/config/aarch64/aarch64.cc
> +++ b/gcc/config/aarch64/aarch64.cc
> @@ -100,6 +100,7 @@
>  #include "ipa-fnsummary.h"
>  #include "hash-map.h"
>  #include "ifcvt.h"
> +#include "internal-fn.h"
>  #include "aarch64-sched-dispatch.h"
>  #include "aarch64-json-tunings-printer.h"
>  #include "aarch64-json-tunings-parser.h"
> @@ -2229,6 +2230,629 @@ aarch64_preferred_else_value (unsigned, tree, 
> unsigned int nops, tree *ops)
>    return nops == 3 ? ops[2] : ops[0];
>  }
>  
> +/* Return true if TYPE is a vector integer type whose elements are known
> +   to fit in a SOURCE_PRECISION-bit integer of the selected signedness.  */
> +
> +static bool
> +aarch64_mlalb_vector_type_fits_half_p (tree type,
> +                                    unsigned int source_precision,
> +                                    bool unsigned_p)
> +{
> +  if (!VECTOR_TYPE_P (type))
> +    return false;
> +
> +  tree element_type = TREE_TYPE (type);
> +  if (!INTEGRAL_TYPE_P (element_type))
> +    return false;
> +
> +  unsigned int precision = TYPE_PRECISION (element_type);
> +  if (unsigned_p)
> +    return TYPE_UNSIGNED (element_type) && precision <= source_precision;
> +
> +  return (precision < source_precision
> +       || (precision == source_precision && !TYPE_UNSIGNED (element_type)));
> +}
> +
> +/* Return true if ASSIGN widens or preserves vector integer element width.  
> */
> +
> +static bool
> +aarch64_mlalb_extend_p (gassign *assign)
> +{
> +  tree_code code = gimple_assign_rhs_code (assign);
> +  if (!CONVERT_EXPR_CODE_P (code))
> +    return false;
> +
> +  tree type = TREE_TYPE (gimple_assign_lhs (assign));
> +  tree inner_type = TREE_TYPE (gimple_assign_rhs1 (assign));
> +  if (!VECTOR_TYPE_P (type) || !VECTOR_TYPE_P (inner_type))
> +    return false;
> +
> +  tree element_type = TREE_TYPE (type);
> +  tree inner_element_type = TREE_TYPE (inner_type);
> +  if (!INTEGRAL_TYPE_P (element_type)
> +      || !INTEGRAL_TYPE_P (inner_element_type))
> +    return false;
> +
> +  unsigned int precision = TYPE_PRECISION (element_type);
> +  unsigned int inner_precision = TYPE_PRECISION (inner_element_type);
> +
> +  return precision >= inner_precision;
> +}
> +
> +/* Return true if OP is known locally to fit in a SOURCE_PRECISION-bit 
> integer
> +   element of the selected signedness.  When nonnull, set FITTING_OP to the
> +   operand that provides the half-width value.  */
> +
> +static bool
> +aarch64_mlalb_operand_fits_half_p (tree op, unsigned int source_precision,
> +                                 bool unsigned_p,
> +                                 tree *fitting_op = nullptr,
> +                                 unsigned int depth = 0)
> +{
> +  if (!op || depth > 20)
> +    return false;
> +
> +  tree type = TREE_TYPE (op);
> +  if (aarch64_mlalb_vector_type_fits_half_p (type, source_precision,
> +                                          unsigned_p))
> +    {
> +      if (fitting_op)
> +     *fitting_op = op;
> +      return true;
> +    }
> +
> +  if (TREE_CODE (op) != SSA_NAME)
> +    return false;
> +
> +  gassign *assign = dyn_cast<gassign *> (SSA_NAME_DEF_STMT (op));
> +  if (!assign || !aarch64_mlalb_extend_p (assign))
> +    return false;
> +
> +  return aarch64_mlalb_operand_fits_half_p (gimple_assign_rhs1 (assign),
> +                                         source_precision, unsigned_p,
> +                                         fitting_op, depth + 1);
> +}
> +
> +/* Return true if CALL is a zeroing MASK_LOAD under MASK.  */
> +
> +static bool
> +aarch64_mlalb_zeroing_mask_load_p (gcall *call, tree mask)
> +{
> +  if (!gimple_call_internal_p (call, IFN_MASK_LOAD))
> +    return false;
> +
> +  tree call_mask
> +    = gimple_call_arg (call, internal_fn_mask_index (IFN_MASK_LOAD));
> +  if (!operand_equal_p (call_mask, mask, 0))
> +    return false;
> +
> +  tree else_value
> +    = gimple_call_arg (call, internal_fn_else_index (IFN_MASK_LOAD));
> +  return initializer_zerop (else_value);
> +}
> +
> +/* Return true if converting INNER_TYPE to TYPE leaves the MLALB accumulator
> +   in the same vector mode.  */
> +
> +static bool
> +aarch64_mlalb_same_mode_accumulator_conversion_p (tree type, tree inner_type)
> +{
> +  if (!VECTOR_TYPE_P (type) || !VECTOR_TYPE_P (inner_type))
> +    return false;
> +
> +  tree element_type = TREE_TYPE (type);
> +  tree inner_element_type = TREE_TYPE (inner_type);
> +  if (!INTEGRAL_TYPE_P (element_type)
> +      || !INTEGRAL_TYPE_P (inner_element_type))
> +    return false;
> +
> +  return (known_eq (TYPE_VECTOR_SUBPARTS (type),
> +                 TYPE_VECTOR_SUBPARTS (inner_type))
> +       && (TYPE_PRECISION (element_type)
> +           == TYPE_PRECISION (inner_element_type)));
> +}
> +
> +/* Strip conversions from OP that do not change accumulator vector mode.  */
> +
> +static tree
> +aarch64_strip_mlalb_accumulator_conversions (tree op, unsigned int depth = 0)
> +{
> +  if (!op || depth > 20)
> +    return op;
> +
> +  tree inner = NULL_TREE;
> +  if (TREE_CODE (op) == SSA_NAME)
> +    {
> +      gassign *assign = dyn_cast<gassign *> (SSA_NAME_DEF_STMT (op));
> +      if (!assign)
> +     return op;
> +
> +      tree_code code = gimple_assign_rhs_code (assign);
> +      if (code == VIEW_CONVERT_EXPR)
> +     {
> +       inner = gimple_assign_rhs1 (assign);
> +       if (TREE_CODE (inner) == VIEW_CONVERT_EXPR)
> +         inner = TREE_OPERAND (inner, 0);
> +     }
> +      else if (CONVERT_EXPR_CODE_P (code))
> +     inner = gimple_assign_rhs1 (assign);
> +    }
> +
> +  if (inner
> +      && aarch64_mlalb_same_mode_accumulator_conversion_p (TREE_TYPE (op),
> +                                                         TREE_TYPE (inner)))
> +    return aarch64_strip_mlalb_accumulator_conversions (inner, depth + 1);
> +
> +  return op;
> +}
> +
> +/* Return the conditional form of CODE, or IFN_LAST if CODE isn't supported
> +   by this transformation.  */
> +
> +static internal_fn
> +aarch64_cond_ifn_for_mlalb_arith (tree_code code)
> +{
> +  switch (code)
> +    {
> +    case PLUS_EXPR:
> +      return IFN_COND_ADD;
> +    case MINUS_EXPR:
> +      return IFN_COND_SUB;
> +    default:
> +      return IFN_LAST;
> +    }
> +}
> +
> +/* Rebuild OP so that the part of the expression depending on a zeroing
> +   MASK_LOAD under MASK is also zero when MASK is false.  Set FOUND_P if OP
> +   already has, or could be rebuilt to have, that property.  If FORCE_ZEROING
> +   is true, insert a final conditional add with zero so that OP itself is 
> zero
> +   on inactive lanes.  */
> +
> +static void
> +aarch64_build_mlalb_zeroing_op (tree op, tree mask, gimple_stmt_iterator 
> *gsi,
> +                              tree &new_op, bool &found_p,
> +                              bool force_zeroing = false,
> +                              unsigned int depth = 0)
> +{
> +  new_op = op;
> +  found_p = false;
> +
> +  if (!op || depth > 20)
> +    return;
> +
> +  auto force_zero = [&] ()
> +    {
> +      tree type = TREE_TYPE (op);
> +      if (!direct_internal_fn_supported_p (IFN_COND_ADD, type,
> +                                            OPTIMIZE_FOR_SPEED))
> +     return;
> +      tree zero = build_zero_cst (type);
> +      gcall *cond_stmt = gimple_build_call_internal (IFN_COND_ADD, 4,
> +                                                  mask, op, zero, zero);
> +      gimple_call_set_nothrow (cond_stmt, true);
> +      new_op = make_ssa_name (type);
> +      gimple_call_set_lhs (cond_stmt, new_op);
> +      gsi_insert_before (gsi, cond_stmt, GSI_SAME_STMT);
> +      found_p = true;
> +    };
> +
> +  if (TREE_CODE (op) != SSA_NAME)
> +    {
> +      if (force_zeroing)
> +     force_zero ();
> +      return;
> +    }
> +
> +  gimple *def_stmt = SSA_NAME_DEF_STMT (op);
> +  if (gcall *call = dyn_cast<gcall *> (def_stmt))
> +    {
> +      if (aarch64_mlalb_zeroing_mask_load_p (call, mask))
> +     found_p = true;
> +      else if (force_zeroing)
> +     force_zero ();
> +      return;
> +    }
> +
> +  gassign *assign = dyn_cast<gassign *> (def_stmt);
> +  if (!assign)
> +    {
> +      if (force_zeroing)
> +     force_zero ();
> +      return;
> +    }
> +
> +  tree_code code = gimple_assign_rhs_code (assign);
> +  if (code == FIX_TRUNC_EXPR
> +      || code == VIEW_CONVERT_EXPR
> +      || CONVERT_EXPR_CODE_P (code))
> +    {
> +      tree inner;
> +      bool inner_found;
> +      tree rhs = gimple_assign_rhs1 (assign);
> +      if (code == VIEW_CONVERT_EXPR)
> +     rhs = TREE_OPERAND (rhs, 0);
> +
> +      aarch64_build_mlalb_zeroing_op (rhs, mask, gsi, inner, inner_found,
> +                                     force_zeroing,
> +                                     depth + 1);
> +
> +      if (inner_found)
> +     {
> +       tree type = TREE_TYPE (op);
> +       gimple *stmt;
> +       if (code == VIEW_CONVERT_EXPR)
> +         stmt = gimple_build_assign (make_ssa_name (type),
> +                                     fold_build1 (VIEW_CONVERT_EXPR, type,
> +                                                  inner));
> +       else
> +         stmt = gimple_build_assign (make_ssa_name (type), code, inner);
> +       gsi_insert_before (gsi, stmt, GSI_SAME_STMT);
> +       new_op = gimple_get_lhs (stmt);
> +       found_p = true;
> +       return;
> +     }
> +
> +      if (force_zeroing)
> +     force_zero ();
> +      return;
> +    }
> +
> +  internal_fn ifn = aarch64_cond_ifn_for_mlalb_arith (code);
> +  if (ifn != IFN_LAST
> +      && direct_internal_fn_supported_p (ifn, TREE_TYPE (op),
> +                                      OPTIMIZE_FOR_SPEED))
> +    {
> +      tree lhs;
> +      tree rhs;
> +      bool lhs_found;
> +      bool rhs_found;
> +      aarch64_build_mlalb_zeroing_op (gimple_assign_rhs1 (assign), mask,
> +                                           gsi, lhs, lhs_found,
> +                                           false, depth + 1);
> +      aarch64_build_mlalb_zeroing_op (gimple_assign_rhs2 (assign), mask,
> +                                           gsi, rhs, rhs_found,
> +                                           false, depth + 1);
> +
> +      if (lhs_found || rhs_found)
> +     {
> +       tree type = TREE_TYPE (op);
> +       if (!types_compatible_p (TREE_TYPE (lhs), type))
> +         {
> +           gimple *convert = gimple_build_assign (make_ssa_name (type),
> +                                                  NOP_EXPR, lhs);
> +           gsi_insert_before (gsi, convert, GSI_SAME_STMT);
> +           lhs = gimple_get_lhs (convert);
> +         }
> +
> +       if (!types_compatible_p (TREE_TYPE (rhs), type))
> +         {
> +           gimple *convert = gimple_build_assign (make_ssa_name (type),
> +                                                  NOP_EXPR, rhs);
> +           gsi_insert_before (gsi, convert, GSI_SAME_STMT);
> +           rhs = gimple_get_lhs (convert);
> +         }
> +
> +       /* Reuse an operand that is already zero on inactive lanes as
> +          the merge value.  This avoids reconstructing zeroing
> +          separately.  */
> +       tree merge = lhs_found ? lhs : rhs;
> +       gcall *cond_stmt = gimple_build_call_internal (ifn, 4, mask, lhs,
> +                                                  rhs, merge);
> +       gimple_call_set_nothrow (cond_stmt, true);
> +       new_op = make_ssa_name (type);
> +       gimple_call_set_lhs (cond_stmt, new_op);
> +       gsi_insert_before (gsi, cond_stmt, GSI_SAME_STMT);
> +       found_p = true;
> +       return;
> +     }
> +    }
> +
> +  if (force_zeroing)
> +    force_zero ();
> +}
> +
> +/* Return the half-width element type for an MLALB result element
> +   precision, or NULL_TREE if the precision is not supported.  */
> +
> +static tree
> +aarch64_mlalb_source_element_type (unsigned int result_precision,
> +                                bool unsigned_p)
> +{
> +  switch (result_precision)
> +    {
> +    case 16:
> +      return unsigned_p ? unsigned_char_type_node : signed_char_type_node;
> +    case 32:
> +      return unsigned_p ? short_unsigned_type_node : short_integer_type_node;
> +    case 64:
> +      return unsigned_p ? unsigned_type_node : integer_type_node;
> +    default:
> +      return NULL_TREE;
> +    }
> +}
> +
> +/* Convert OP to SOURCE_TYPE before GSI.  */
> +
> +static tree
> +aarch64_build_mlalb_source_op (tree op, tree source_type,
> +                             gimple_stmt_iterator *gsi)
> +{
> +  if (types_compatible_p (TREE_TYPE (op), source_type))
> +    return op;
> +
> +  tree lhs = make_ssa_name (source_type);
> +  tree op_type = TREE_TYPE (op);
> +  gimple *stmt;
> +  if (VECTOR_TYPE_P (op_type)
> +      && known_eq (TYPE_VECTOR_SUBPARTS (op_type),
> +                TYPE_VECTOR_SUBPARTS (source_type))
> +      && (TYPE_PRECISION (TREE_TYPE (op_type))
> +       == TYPE_PRECISION (TREE_TYPE (source_type))))
> +    stmt = gimple_build_assign (lhs, fold_build1 (VIEW_CONVERT_EXPR,
> +                                                       source_type, op));
> +  else
> +    stmt = gimple_build_assign (lhs, NOP_EXPR, op);
> +  gsi_insert_before (gsi, stmt, GSI_SAME_STMT);
> +  return lhs;
> +}
> +
> +static bool
> +aarch64_mlalb_mult_node_p (tree op, tree result_type,
> +                        tree &multiplicand0, tree &multiplicand1)
> +{
> +  unsigned int result_precision = TYPE_PRECISION (TREE_TYPE (result_type));
> +
> +  while (TREE_CODE (op) == SSA_NAME)
> +    {
> +      gimple *def_stmt = SSA_NAME_DEF_STMT (op);
> +      gassign *assign = dyn_cast<gassign *> (def_stmt);
> +      if (!assign)
> +     return false;
> +
> +      tree_code code = gimple_assign_rhs_code (assign);
> +      if (code == VIEW_CONVERT_EXPR)
> +     op = TREE_OPERAND (gimple_assign_rhs1 (assign), 0);
> +      else if (aarch64_mlalb_extend_p (assign))
> +     op = gimple_assign_rhs1 (assign);
> +      else if (code == MULT_EXPR)
> +     {
> +       tree mult_type = TREE_TYPE (gimple_assign_lhs (assign));
> +       if (!VECTOR_TYPE_P (mult_type))
> +         return false;
> +
> +       tree mult_element_type = TREE_TYPE (mult_type);
> +       unsigned int mult_precision = TYPE_PRECISION (mult_element_type);
> +       /* If the original product is narrower than the accumulator, MLALB
> +          computes more high product bits than the original expression kept.
> +          This is only equivalent when overflow of the original product is
> +          undefined; otherwise the original expression has defined wrapping
> +          semantics that MLALB would not preserve.  */
> +       if (mult_precision < result_precision
> +           && !TYPE_OVERFLOW_UNDEFINED (mult_element_type))
> +         return false;
> +
> +       multiplicand0 = gimple_assign_rhs1 (assign);
> +       multiplicand1 = gimple_assign_rhs2 (assign);
> +       return true;
> +     }
> +      else
> +     return false;
> +    }
> +
> +  return false;
> +}
> +
> +/* Information that is common to all MLALB candidate forms once the
> +   multiplicands and accumulator have been identified.  */
> +
> +struct aarch64_mlalb_candidate
> +{
> +  tree lhs;
> +  tree result_type;
> +  unsigned int source_precision;
> +  bool unsigned_p;
> +  internal_fn ifn;
> +};
> +
> +/* Check whether CALL can be replaced by an MLALB-like internal function 
> using
> +   MULTIPLICAND0 and MULTIPLICAND1.  Fill in CANDIDATE on success.  */
> +
> +static bool
> +aarch64_check_mlalb_candidate (gcall *call, tree &multiplicand0,
> +                               tree &multiplicand1,
> +                               aarch64_mlalb_candidate &candidate)
> +{
> +  candidate.lhs = gimple_call_lhs (call);
> +
> +  /* MLALB is an SVE2 instruction, so do not match unless the target can
> +     expand the replacement.  */
> +  if (!TARGET_SVE2)
> +    return false;
> +
> +  /* The result must be an SVE integer vector whose elements can be split 
> into
> +     bottom halves for MLALB.  */
> +  if (!candidate.lhs
> +      || !VECTOR_TYPE_P (TREE_TYPE (candidate.lhs))
> +      || !aarch64_sve_mode_p (TYPE_MODE (TREE_TYPE (candidate.lhs))))
> +    return false;
> +
> +  candidate.result_type = TREE_TYPE (candidate.lhs);
> +  /* The current MLALB optabs only have scalable SVE expanders.  Fixed-length
> +     SVE vector types can have a mode that maps to one of those expanders, 
> but
> +     building the half-width source type below would give fixed-length source
> +     operands, which the scalable expander cannot accept.  */
> +  if (TYPE_VECTOR_SUBPARTS (candidate.result_type).is_constant ())
> +    return false;
> +
> +  tree element_type = TREE_TYPE (candidate.result_type);
> +  if (!INTEGRAL_TYPE_P (element_type))
> +    return false;
> +
> +  unsigned int element_precision = TYPE_PRECISION (element_type);
> +
> +  if (!aarch64_mlalb_source_element_type (element_precision, false))
> +    return false;
> +
> +  unsigned int source_precision = element_precision / 2;
> +
> +  tree signed_multiplicand0;
> +  tree signed_multiplicand1;
> +  tree unsigned_multiplicand0;
> +  tree unsigned_multiplicand1;
> +
> +  if (aarch64_mlalb_operand_fits_half_p (multiplicand0, source_precision,
> +                                      false, &signed_multiplicand0)
> +      && aarch64_mlalb_operand_fits_half_p (multiplicand1, source_precision,
> +                                         false, &signed_multiplicand1))
> +    {
> +      candidate.unsigned_p = false;
> +      multiplicand0 = signed_multiplicand0;
> +      multiplicand1 = signed_multiplicand1;
> +    }
> +  else if (aarch64_mlalb_operand_fits_half_p (multiplicand0, 
> source_precision,
> +                                           true, &unsigned_multiplicand0)
> +        && aarch64_mlalb_operand_fits_half_p (multiplicand1,
> +                                          source_precision, true,
> +                                          &unsigned_multiplicand1))
> +    {
> +      candidate.unsigned_p = true;
> +      multiplicand0 = unsigned_multiplicand0;
> +      multiplicand1 = unsigned_multiplicand1;
> +    }
> +  else
> +    return false;
> +
> +  candidate.source_precision = source_precision;
> +  candidate.ifn = candidate.unsigned_p ? IFN_UMLALB : IFN_SMLALB;
> +  return direct_internal_fn_supported_p (candidate.ifn, 
> candidate.result_type,
> +                                               OPTIMIZE_FOR_SPEED);
> +}
> +
> +/* Replace the current statement with the MLALB operation described by
> +   CANDIDATE.  MULTIPLICAND0 and MULTIPLICAND1 are already known to fit in 
> the
> +   selected half-width source type; ACCUMULATOR is the value to preserve and
> +   accumulate into.  */
> +
> +static void
> +aarch64_replace_with_mlalb (gimple_stmt_iterator *gsi,
> +                           const aarch64_mlalb_candidate &candidate,
> +                           tree accumulator, tree multiplicand0,
> +                           tree multiplicand1)
> +{
> +  tree result_type = candidate.result_type;
> +  bool unsigned_p = candidate.unsigned_p;
> +
> +  /* The IFN expects the same-lane-count source mode used by the
> +     bottom-half instruction.  For example, a VNx4SI result uses
> +     VNx4HI sources, not the fully packed VNx8HI mode.  */
> +  tree result_element_type = TREE_TYPE (result_type);
> +  tree source_element_type
> +    = aarch64_mlalb_source_element_type (TYPE_PRECISION 
> (result_element_type),
> +                                       unsigned_p);
> +  tree source_type = build_vector_type (source_element_type,
> +                                     TYPE_VECTOR_SUBPARTS (result_type));
> +  multiplicand0 = aarch64_build_mlalb_source_op (multiplicand0, source_type,
> +                                              gsi);
> +  multiplicand1 = aarch64_build_mlalb_source_op (multiplicand1, source_type,
> +                                              gsi);
> +
> +  /* The accumulator must stay in the wide result mode.  Only strip casts 
> that
> +     keep that mode unchanged, such as signedness-only vector conversions.  
> */
> +  accumulator = aarch64_strip_mlalb_accumulator_conversions (accumulator);
> +
> +  gcall *mlalb = gimple_build_call_internal (candidate.ifn, 3, accumulator,
> +                                                   multiplicand0,
> +                                                   multiplicand1);
> +  gimple_call_set_lhs (mlalb, candidate.lhs);
> +  gimple_call_set_nothrow (mlalb, true);
> +  gsi_replace (gsi, mlalb, true);
> +}
> +
> +static bool
> +aarch64_try_cond_mlalb (gimple *stmt, gimple_stmt_iterator *gsi)
> +{
> +  gcall *call = as_a <gcall *> (stmt);
> +  tree mask, multiplicand0, multiplicand1, merge;
> +
> +  if (gimple_call_internal_fn (call) == IFN_COND_ADD)
> +    {
> +      mask = gimple_call_arg (call, 0);
> +      tree addend0 = gimple_call_arg (call, 1);
> +      tree addend1 = gimple_call_arg (call, 2);
> +      merge = gimple_call_arg (call, 3);
> +
> +      /* The conditional add must be a merging operation whose inactive lanes
> +      keep the accumulator.  */
> +      if (!operand_equal_p (merge, addend1))
> +     return false;
> +
> +      /* addend0 must be a multiplication node where each multiplicand fits 
> in
> +      half as many bits as the result.  */
> +      tree result_type = TREE_TYPE (gimple_call_lhs (call));
> +      if (!aarch64_mlalb_mult_node_p (addend0, result_type, multiplicand0,
> +                                   multiplicand1))
> +     return false;
> +    }
> +  else if (gimple_call_internal_fn (call) == IFN_COND_FMA)
> +    {
> +      mask = gimple_call_arg (call, 0);
> +      multiplicand0 = gimple_call_arg (call, 1);
> +      multiplicand1 = gimple_call_arg (call, 2);
> +      tree addend = gimple_call_arg (call, 3);
> +      merge = gimple_call_arg (call, 4);
> +
> +      /* The fused multiply-add must be a merging operation whose inactive
> +      lanes keep the accumulator.  */
> +      if (!operand_equal_p (addend, merge, 0))
> +     return false;
> +    }
> +  else
> +    gcc_unreachable ();
> +
> +  aarch64_mlalb_candidate candidate;
> +  if (!aarch64_check_mlalb_candidate (call, multiplicand0, multiplicand1,
> +                                             candidate))
> +    return false;
> +
> +  tree new_multiplicand0;
> +  tree new_multiplicand1 = multiplicand1;
> +  bool zeroing0;
> +  bool zeroing1;
> +  aarch64_build_mlalb_zeroing_op (multiplicand0, mask, gsi,
> +                             new_multiplicand0, zeroing0);
> +  if (!zeroing0)
> +    {
> +      aarch64_build_mlalb_zeroing_op (multiplicand1, mask, gsi,
> +                                   new_multiplicand1, zeroing1,
> +                                   true);
> +      if (!zeroing1)
> +     return false;
> +    }
> +
> +  aarch64_replace_with_mlalb (gsi, candidate, merge, new_multiplicand0,
> +                                     new_multiplicand1);
> +  return true;
> +}
> +
> +static bool
> +aarch64_try_fma_mlalb (gimple *stmt, gimple_stmt_iterator *gsi)
> +{
> +  gcall *call = as_a <gcall *> (stmt);
> +  tree multiplicand0 = gimple_call_arg (call, 0);
> +  tree multiplicand1 = gimple_call_arg (call, 1);
> +  tree addend = gimple_call_arg (call, 2);
> +
> +  aarch64_mlalb_candidate candidate;
> +  if (!aarch64_check_mlalb_candidate (call, multiplicand0, multiplicand1,
> +                                             candidate))
> +    return false;
> +
> +  aarch64_replace_with_mlalb (gsi, candidate, addend, multiplicand0,
> +                                     multiplicand1);
> +  return true;
> +}
> +
>  /* Implement TARGET_INSTRUCTION_SELECTION.  The target hook is used to
>     change generic sequences to a form AArch64 has an easier time expanding
>     instructions for.  It's not supposed to be used for generic rewriting that
> @@ -2238,6 +2862,16 @@ static bool
>  aarch64_instruction_selection (function * /* fun */, gimple_stmt_iterator 
> *gsi)
>  {
>    auto stmt = gsi_stmt (*gsi);
> +
> +  if ((gimple_call_internal_p (stmt, IFN_COND_FMA)
> +       || gimple_call_internal_p (stmt, IFN_COND_ADD))
> +      && aarch64_try_cond_mlalb (stmt, gsi))
> +    return true;
> +
> +  if (gimple_call_internal_p (stmt, IFN_FMA)
> +      && aarch64_try_fma_mlalb (stmt, gsi))
> +    return true;
> +
>    gassign *assign = dyn_cast<gassign *> (stmt);
>  
>    if (!assign)
> diff --git a/gcc/config/aarch64/iterators.md b/gcc/config/aarch64/iterators.md
> index 2d1522a348a..c1c92c4a9ce 100644
> --- a/gcc/config/aarch64/iterators.md
> +++ b/gcc/config/aarch64/iterators.md
> @@ -2139,6 +2139,11 @@
>                          (VNx2DI "VNx4SI") (VNx2DF "VNx4SF")
>                          (VNx8SI "VNx8HI") (VNx16SI "VNx16QI")
>                          (VNx8DI "VNx8HI")])
> +
> +;; Half-element modes with the same number of units.
> +(define_mode_attr VHALF_EL [(VNx8HI "VNx8QI")
> +                         (VNx4SI "VNx4HI")
> +                         (VNx2DI "VNx2SI")])
>  (define_mode_attr Vnarrow [(VNx8HI "vnx16qi")
>                          (VNx4SI "vnx8hi") (VNx4SF "vnx8hf")
>                          (VNx2DI "vnx4si") (VNx2DF "vnx4sf")
> diff --git a/gcc/internal-fn.def b/gcc/internal-fn.def
> index af9f92950c7..d79c140f86d 100644
> --- a/gcc/internal-fn.def
> +++ b/gcc/internal-fn.def
> @@ -471,6 +471,8 @@ DEF_INTERNAL_WIDENING_OPTAB_FN (VEC_WIDEN_ABD,
>                               first,
>                               vec_widen_sabd, vec_widen_uabd,
>                               binary)
> +DEF_INTERNAL_OPTAB_FN (SMLALB, ECF_CONST | ECF_NOTHROW, smlalb, ternary)
> +DEF_INTERNAL_OPTAB_FN (UMLALB, ECF_CONST | ECF_NOTHROW, umlalb, ternary)
>  DEF_INTERNAL_OPTAB_FN (VEC_FMADDSUB, ECF_CONST, vec_fmaddsub, ternary)
>  DEF_INTERNAL_OPTAB_FN (VEC_FMSUBADD, ECF_CONST, vec_fmsubadd, ternary)
>  
> diff --git a/gcc/optabs.def b/gcc/optabs.def
> index 7ccea18543f..a0225fe4b68 100644
> --- a/gcc/optabs.def
> +++ b/gcc/optabs.def
> @@ -476,6 +476,8 @@ OPTAB_D (vec_widen_smult_even_optab, 
> "vec_widen_smult_even_$a")
>  OPTAB_D (vec_widen_smult_hi_optab, "vec_widen_smult_hi_$a")
>  OPTAB_D (vec_widen_smult_lo_optab, "vec_widen_smult_lo_$a")
>  OPTAB_D (vec_widen_smult_odd_optab, "vec_widen_smult_odd_$a")
> +OPTAB_D (smlalb_optab, "smlalb$a4")
> +OPTAB_D (umlalb_optab, "umlalb$a4")
>  OPTAB_D (vec_widen_ssub_optab, "vec_widen_ssub_$a")
>  OPTAB_D (vec_widen_ssub_hi_optab, "vec_widen_ssub_hi_$a")
>  OPTAB_D (vec_widen_ssub_lo_optab, "vec_widen_ssub_lo_$a")
> diff --git a/gcc/testsuite/gcc.target/aarch64/sve2/mlalb-cond-add-selection.c 
> b/gcc/testsuite/gcc.target/aarch64/sve2/mlalb-cond-add-selection.c
> new file mode 100644
> index 00000000000..1209ca2e563
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/aarch64/sve2/mlalb-cond-add-selection.c
> @@ -0,0 +1,276 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -fdump-tree-vect -fdump-tree-isel" } */
> +
> +#include <stdint.h>
> +
> +/* Test COND_ADD selection of SMLALB for signed 8-bit multiplicands 
> accumulated
> +   into a 16-bit reduction.  */
> +short
> +cond_add_s8_to_s16_reduction (int8_t *a, int8_t *b, int len)
> +{
> +  if (len < 0 || len >= 128)
> +    return 0;
> +
> +  short x = 0;
> +  for (int8_t i = 0; i < len; ++i)
> +    x += (short) a[i] * (short) b[i];
> +
> +  return x;
> +}
> +
> +/* Test COND_ADD selection of UMLALB for unsigned 8-bit multiplicands
> +   accumulated into a 16-bit reduction.  */
> +unsigned short
> +cond_add_u8_to_u16_reduction (uint8_t *a, uint8_t *b, int len)
> +{
> +  if (len < 0 || len >= 128)
> +    return 0;
> +
> +  unsigned short x = 0;
> +  for (uint8_t i = 0; i < len; ++i)
> +    x += (unsigned short) a[i] * (unsigned short) b[i];
> +
> +  return x;
> +}
> +
> +/* Test COND_ADD selection of SMLALB for signed 16-bit multiplicands
> +   accumulated into a 32-bit reduction.  */
> +int
> +cond_add_s16_to_s32_reduction (int16_t *a, int16_t *b, int len)
> +{
> +  if (len < 0 || len >= 256)
> +    return 0;
> +
> +  int x = 0;
> +  for (int16_t i = 0; i < len; ++i)
> +    x += (int) a[i] * (int) b[i];
> +
> +  return x;
> +}
> +
> +/* Test COND_ADD selection of UMLALB for unsigned 16-bit multiplicands
> +   accumulated into a 32-bit reduction.  */
> +unsigned int
> +cond_add_u16_to_u32_reduction (uint16_t *a, uint16_t *b, int len)
> +{
> +  if (len < 0 || len >= 256)
> +    return 0;
> +
> +  unsigned int x = 0;
> +  for (uint16_t i = 0; i < len; ++i)
> +    x += (unsigned int) a[i] * (unsigned int) b[i];
> +
> +  return x;
> +}
> +
> +/* Test COND_ADD selection of SMLALB for signed 32-bit multiplicands
> +   accumulated into a 64-bit reduction.  */
> +long int
> +cond_add_s32_to_s64_reduction (int32_t *a, int32_t *b, int len)
> +{
> +  if (len < 0 || len >= 65536)
> +    return 0;
> +
> +  long int x = 0;
> +  for (int32_t i = 0; i < len; ++i)
> +    x += (long int) a[i] * (long int) b[i];
> +
> +  return x;
> +}
> +
> +/* Test COND_ADD selection of UMLALB for unsigned 32-bit multiplicands
> +   accumulated into a 64-bit reduction.  */
> +unsigned long int
> +cond_add_u32_to_u64_reduction (uint32_t *a, uint32_t *b, int len)
> +{
> +  if (len < 0 || len >= 65536)
> +    return 0;
> +
> +  unsigned long int x = 0;
> +  for (uint32_t i = 0; i < len; ++i)
> +    x += (unsigned long int) a[i] * (unsigned long int) b[i];
> +
> +  return x;
> +}
> +
> +/* Test that a 32-bit signed product widened to a 64-bit accumulator
> +   selects SMLALB, since signed overflow is undefined.  */
> +long
> +cond_add_s32_product_widened_to_s64 (int32_t *a, int32_t *b, int len)
> +{
> +  if (len < 0 || len >= 65536)
> +    return 0;
> +
> +  long x = 0;
> +  for (int32_t i = 0; i < len; ++i)
> +    {
> +      int32_t product = a[i] * b[i];
> +      x += (long) product;
> +    }
> +  return x;
> +}
> +
> +/* Test that a 32-bit unsigned product widened to a 64-bit accumulator is not
> +   selected, since UMLALB would compute the full 32x32 product.  */
> +unsigned long
> +cond_add_reject_u32_product_widened_to_u64 (uint32_t *a, uint32_t *b, int 
> len)
> +{
> +  if (len < 0 || len >= 65536)
> +    return 0;
> +
> +  unsigned long x = 0;
> +  for (uint32_t i = 0; i < len; ++i)
> +    {
> +      uint32_t product = a[i] * b[i];
> +      x += (unsigned long) product;
> +    }
> +  return x;
> +}
> +
> +/* Test a signed source reinterpretation that is still valid for SMLALB.  */
> +int
> +cond_add_signed_reinterpret_to_smlalb (uint16_t *buf, int len)
> +{
> +  if (len < 0 || len >= 256)
> +    return 0;
> +
> +  int x = 0;
> +  for (int16_t i = 0, y = 0; i < len; ++i, y = i * 10)
> +    {
> +      int16_t v = (int16_t) buf[i];
> +      x += y * (int) v;
> +    }
> +  return x;
> +}
> +
> +/* Test an unsigned source reinterpretation that selects UMLALB rather than
> +   SMLALB.  */
> +unsigned int
> +cond_add_unsigned_reinterpret_to_umlalb (int16_t *buf, int len)
> +{
> +  if (len < 0 || len >= 256)
> +    return 0;
> +
> +  unsigned int x = 0;
> +  for (uint16_t i = 0, y = 0; i < len; ++i, y = i * 10)
> +    {
> +      uint16_t v = (uint16_t) buf[i];
> +      x += (unsigned int) y * (unsigned int) v;
> +    }
> +  return x;
> +}
> +
> +/* Test that aarch64_mlalb_mult_node_p can walk through a VIEW_CONVERT_EXPR
> +   from a signed product to an unsigned accumulator type.  */
> +unsigned int
> +cond_add_signed_product_to_unsigned_accumulator (int16_t *a, int16_t *b,
> +                                                 int len)
> +{
> +  if (len < 0 || len >= 256)
> +    return 0;
> +
> +  unsigned int x = 0;
> +  for (int16_t i = 0; i < len; ++i)
> +    {
> +      int product = (int) a[i] * (int) b[i];
> +      x += (unsigned int) product;
> +    }
> +  return x;
> +}
> +
> +/* Test that an unsigned product reinterpreted to signed selects UMLALB 
> rather
> +   than SMLALB.  */
> +int
> +cond_add_unsigned_product_to_signed_accumulator (uint16_t *a, uint16_t *b,
> +                                                 int len)
> +{
> +  if (len < 0 || len >= 256)
> +    return 0;
> +
> +  int x = 0;
> +  for (int16_t i = 0; i < len; ++i)
> +    {
> +      unsigned int product = (unsigned int) a[i] * (unsigned int) b[i];
> +      x += (int) product;
> +    }
> +  return x;
> +}
> +
> +/* Test that truncating a product before accumulation is not selected.
> +   MLALB computes the full product, rather than the low half of a product.  
> */
> +int
> +cond_add_reject_truncated_product_low_half (int16_t *a, int16_t *b, int len)
> +{
> +  if (len < 0 || len >= 256)
> +    return 0;
> +
> +  int x = 0;
> +  for (int16_t i = 0; i < len; ++i)
> +    {
> +      int product = (int) a[i] * (int) b[i];
> +      x += (int16_t) product;
> +    }
> +  return x;
> +}
> +
> +/* Test COND_ADD selection of SMLALB when a signed IV square is widened
> +   to a 64-bit accumulator.  */
> +long
> +cond_add_signed_iv_square_s64 (int len)
> +{
> +  if (len <= 0 || len >= 255)
> +    return 0;
> +
> +  long res = 0;
> +  for (int i = 0, y = 0; i < len; i++, y = i * 10)
> +    res += y * y;
> +  return res;
> +}
> +
> +/* Test COND_ADD selection of SMLALB when a signed IV is widened to long
> +   before multiplication.  */
> +long
> +cond_add_signed_long_iv_square_s64 (int len)
> +{
> +  if (len <= 0 || len >= 255)
> +    return 0;
> +
> +  long res = 0;
> +  for (int i = 0, y = 0; i < len; i++, y = i * 10)
> +    res += (long) y * (long) y;
> +  return res;
> +}
> +
> +/* Test COND_ADD selection of UMLALB when an unsigned IV is widened to
> +   unsigned long before multiplication.  */
> +unsigned long
> +cond_add_unsigned_long_iv_square_u64 (unsigned len)
> +{
> +  if (len <= 0 || len >= 255)
> +    return 0;
> +
> +  unsigned long res = 0;
> +  for (unsigned i = 0, y = 0; i < len; i++, y = i * 10)
> +    res += (unsigned long) y * (unsigned long) y;
> +  return res;
> +}
> +
> +/* Test that an unsigned IV square widened to a 64-bit accumulator is not
> +   selected when the product itself is formed in 32 bits.  */
> +unsigned long
> +cond_add_reject_unsigned_iv_square_u64 (unsigned len)
> +{
> +  if (len <= 0 || len >= 255)
> +    return 0;
> +
> +  unsigned long res = 0;
> +  for (unsigned i = 0, y = 0; i < len; i++, y = i * 10)
> +    res += y * y;
> +  return res;
> +}
> +
> +/* { dg-final { scan-tree-dump-times {\.COND_ADD} 17 "vect" } } */
> +/* { dg-final { scan-tree-dump-times {\.SMLALB} 8 "isel" } } */
> +/* { dg-final { scan-tree-dump-times {\.UMLALB} 6 "isel" } } */
> +/* { dg-final { scan-assembler-times {\tsmlalb\t} 8 } } */
> +/* { dg-final { scan-assembler-times {\tumlalb\t} 6 } } */
> diff --git a/gcc/testsuite/gcc.target/aarch64/sve2/mlalb-cond-fma-selection.c 
> b/gcc/testsuite/gcc.target/aarch64/sve2/mlalb-cond-fma-selection.c
> new file mode 100644
> index 00000000000..a09d74a4450
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/aarch64/sve2/mlalb-cond-fma-selection.c
> @@ -0,0 +1,126 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -fdump-tree-widening_mul -fdump-tree-isel" } */
> +
> +#include <stdint.h>
> +
> +/* Test COND_FMA selection of SMLALB with a signed character load adjusted by
> +   a constant offset.  */
> +int
> +cond_fma_signed_char_digit_offset (char *buf, int len)
> +{
> +  if (len < 0 || len >= 256)
> +    return 0;
> +
> +  int x = 0;
> +  for (int16_t i = 0, y = 0; i < len; i++, y = i * 10)
> +    x += y * (buf[i] - '0');
> +  return x;
> +}
> +
> +/* Test COND_FMA selection of UMLALB when one unsigned multiplicand is built
> +   from an IV plus a constant.  */
> +uint32_t
> +cond_fma_unsigned_iv_plus_constant (uint8_t *buf, int len)
> +{
> +  if (len < 0 || len >= 256)
> +    return 0;
> +
> +  uint32_t x = 0;
> +  for (uint16_t i = 0, y = 0; i < len; i++, y = i * 10)
> +    x += (uint32_t) (y + 2) * (uint32_t) buf[i];
> +  return x;
> +}
> +
> +/* Test COND_FMA selection of SMLALB when one signed multiplicand is built
> +   from an IV minus a constant.  */
> +int32_t
> +cond_fma_signed_iv_minus_constant (int8_t *buf, int len)
> +{
> +  if (len < 0 || len >= 256)
> +    return 0;
> +
> +  int32_t x = 0;
> +  for (int16_t i = 0, y = 0; i < len; i++, y = i * 10)
> +    x += (int32_t) (y - 2) * (int32_t) buf[i];
> +  return x;
> +}
> +
> +/* Test COND_FMA selection of UMLALB for an unsigned short IV multiplied by 
> an
> +   unsigned short load.  */
> +unsigned
> +cond_fma_unsigned_short_load (uint16_t *buf, uint8_t len)
> +{
> +  unsigned x = 0;
> +  for (uint16_t i = 0, y = 0; i < len; i++, y = i * 10)
> +    x += buf[i] * y;
> +
> +  return x;
> +}
> +
> +/* Test COND_FMA selection of SMLALB when the loaded multiplicand is produced
> +   by float-to-int conversion.  */
> +long
> +cond_fma_float_to_int_load (float *buf, uint16_t len)
> +{
> +  long x = 0;
> +  for (int i = 0, y = 0; i < len; i++, y = i * 10)
> +    x += (long) y * (long) (int) buf[i];
> +  return x;
> +}
> +
> +/* Test COND_FMA selection of SMLALB when the loaded multiplicand is produced
> +   by double-to-int conversion.  */
> +long
> +cond_fma_double_to_int_load (double *buf, uint16_t len)
> +{
> +  long x = 0;
> +  for (int i = 0, y = 0; i < len; i++, y = i * 10)
> +    x += (long) y * (long) (int) buf[i];
> +  return x;
> +}
> +
> +/* Test COND_FMA selection of SMLALB when one signed character load is
> +   adjusted by a constant before multiplication.  */
> +int
> +cond_fma_two_signed_char_loads (char *__restrict buf1,
> +                                  char *__restrict buf2, uint8_t len)
> +{
> +  int x = 0;
> +  for (int i = 0; i < len; ++i)
> +    x += (buf1[i] - 2) * (buf2[i]);
> +  return x;
> +}
> +
> +/* Test COND_FMA selection of SMLALB when both multiplicands are the same
> +   signed IV.  */
> +int
> +cond_fma_signed_iv_square (int len)
> +{
> +  if (len <= 0 || len >= 255)
> +    return 0;
> +
> +  int res = 0;
> +  for (short i = 0, y = 0; i < len; i++, y = i * 10)
> +    res += y * y;
> +  return res;
> +}
> +
> +/* Test COND_FMA selection of UMLALB when both multiplicands are the same
> +   unsigned IV.  */
> +unsigned
> +cond_fma_unsigned_iv_square (unsigned len)
> +{
> +  if (len == 0 || len >= 255)
> +    return 0;
> +
> +  unsigned res = 0;
> +  for (unsigned short i = 0, y = 0; i < len; i++, y = i * 10)
> +    res += y * y;
> +  return res;
> +}
> +
> +/* { dg-final { scan-tree-dump-times {\.COND_FMA} 9 "widening_mul" } } */
> +/* { dg-final { scan-tree-dump-times {\.SMLALB} 6 "isel" } } */
> +/* { dg-final { scan-tree-dump-times {\.UMLALB} 3 "isel" } } */
> +/* { dg-final { scan-assembler-times {\tsmlalb\t} 6 } } */
> +/* { dg-final { scan-assembler-times {\tumlalb\t} 3 } } */
> diff --git 
> a/gcc/testsuite/gcc.target/aarch64/sve2/mlalb-fma-fixed-vector-length.c 
> b/gcc/testsuite/gcc.target/aarch64/sve2/mlalb-fma-fixed-vector-length.c
> new file mode 100644
> index 00000000000..8da14824ac2
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/aarch64/sve2/mlalb-fma-fixed-vector-length.c
> @@ -0,0 +1,16 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -msve-vector-bits=128 -fdump-tree-isel" } */
> +
> +typedef unsigned short uint16_t;
> +
> +/* Test that MLALB selection rejects fixed-length SVE result types until the
> +   optab supports the matching fixed-length half-width source operands.  */
> +void __attribute__ ((noinline, noclone))
> +bar_test (int *__restrict buf, uint16_t len, long *x)
> +{
> +  for (int i = 0, y = 0; i < len; i++, y = i * 10)
> +    x[i] += (long) y * (long) buf[i];
> +}
> +
> +/* { dg-final { scan-tree-dump-not {\.SMLALB} "isel" } } */
> +/* { dg-final { scan-assembler-not {\tsmlalb\t} } } */
> diff --git a/gcc/testsuite/gcc.target/aarch64/sve2/mlalb-fma-selection.c 
> b/gcc/testsuite/gcc.target/aarch64/sve2/mlalb-fma-selection.c
> new file mode 100644
> index 00000000000..d21968be204
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/aarch64/sve2/mlalb-fma-selection.c
> @@ -0,0 +1,134 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -fdump-tree-isel" } */
> +
> +#include <stdint.h>
> +
> +/* Test plain FMA selection of SMLALB with a signed character load adjusted 
> by
> +   a constant offset.  */
> +void
> +plain_fma_signed_char_digit_offset (char *__restrict buf, int8_t len, int *x)
> +{
> +  for (int16_t i = 0, y = 0; i < len; i++, y = i * 10)
> +    x[i] += (int) y * (buf[i] - '0');
> +}
> +
> +/* Test plain FMA selection of SMLALB for signed 32-bit multiplicands
> +   accumulated into a 64-bit memory operand.  */
> +void
> +plain_fma_s32_to_s64_memory_accumulator (int *__restrict buf, uint16_t len,
> +                                          long *x)
> +{
> +  for (int i = 0, y = 0; i < len; i++, y = i * 10)
> +    x[i] += (long) y * (long) buf[i];
> +}
> +
> +/* Test plain FMA selection of SMLALB when a float-to-int conversion
> +   feeds one multiplicand.  */
> +void
> +plain_fma_float_to_int_load (float *__restrict buf, uint16_t len, long *x)
> +{
> +  for (int i = 0, y = 0; i < len; i++, y = i * 10)
> +    x[i] += (long) y * (long) (int) buf[i];
> +}
> +
> +/* Test plain FMA selection of SMLALB when one multiplicand is built
> +   by subtracting a constant from a loaded value.  */
> +void
> +plain_fma_sub_loaded_source (int16_t *__restrict buf, int8_t len, int *x)
> +{
> +  for (int16_t i = 0, y = 0; i < len; i++, y = i * 10)
> +    {
> +      int16_t v = (int16_t) (buf[i] - 7);
> +      x[i] += (int) y * (int) v;
> +    }
> +}
> +
> +/* Test plain FMA selection of SMLALB when one multiplicand is built
> +   by multiplying a loaded value by a constant.  */
> +void
> +plain_fma_mul_loaded_source (int16_t *__restrict buf, int8_t len, int *x)
> +{
> +  for (int16_t i = 0, y = 0; i < len; i++, y = i * 10)
> +    {
> +      int16_t v = (int16_t) (buf[i] * 3);
> +      x[i] += (int) y * (int) v;
> +    }
> +}
> +
> +/* Test plain FMA selection of SMLALB when one multiplicand is built
> +   by adding a constant to a loaded value.  */
> +void
> +plain_fma_add_loaded_source (int16_t *__restrict buf, int8_t len, int *x)
> +{
> +  for (int16_t i = 0, y = 0; i < len; i++, y = i * 10)
> +    {
> +      int16_t v = (int16_t) (buf[i] + 7);
> +      x[i] += (int) y * (int) v;
> +    }
> +}
> +
> +/* Test plain FMA selection of UMLALB when one unsigned multiplicand is
> +   built by adding a constant to a loaded value.  */
> +void
> +plain_fma_unsigned_add_loaded_source (uint16_t *__restrict buf, int8_t len,
> +                                       unsigned int *x)
> +{
> +  for (uint16_t i = 0, y = 0; i < len; i++, y = i * 10)
> +    {
> +      uint16_t v = (uint16_t) (buf[i] + 7);
> +      x[i] += (unsigned int) y * (unsigned int) v;
> +    }
> +}
> +
> +/* Test plain FMA selection of SMLALB after stripping a signedness conversion
> +   around the accumulator.  */
> +void
> +plain_fma_accumulator_signedness_convert (int16_t *__restrict buf, int8_t 
> len,
> +                                          unsigned int *x)
> +{
> +  for (int16_t i = 0, y = 0; i < len; i++, y = i * 10)
> +    x[i] = (unsigned int) ((int) x[i] + (int) y * (int) buf[i]);
> +}
> +
> +/* Test plain FMA selection of UMLALB for an unsigned short IV multiplied by 
> an
> +   unsigned short load.  */
> +void
> +plain_fma_unsigned_short_load (uint16_t *__restrict buf, uint16_t len,
> +                               unsigned int *x)
> +{
> +  for (uint16_t i = 0, y = 0; i < len; i++, y = i * 10)
> +    x[i] += (unsigned int) y * (unsigned int) buf[i];
> +}
> +
> +/* Test the same accumulator signedness-conversion path with an explicitly
> +   named old value kept live in the loop body.  */
> +void
> +plain_fma_named_old_accumulator_convert (int16_t *__restrict buf, int8_t len,
> +                                         unsigned int *x)
> +{
> +  for (int16_t i = 0, y = 0; i < len; i++, y = i * 10)
> +    {
> +      int old = (int) x[i];
> +      x[i] = (unsigned int) (old + (int) y * (int) buf[i]);
> +    }
> +}
> +
> +/* Test that plain FMA selection still allows the old accumulator value to be
> +   used after the selected MLALB operation.  */
> +void
> +plain_fma_preserve_old_accumulator (int16_t *__restrict buf, int8_t len,
> +                                    int *__restrict x, int *__restrict old_x)
> +{
> +  for (int16_t i = 0, y = 0; i < len; i++, y = i * 10)
> +    {
> +      int old = x[i];
> +      x[i] = old + (int) y * (int) buf[i];
> +      old_x[i] = old;
> +    }
> +}
> +
> +/* { dg-final { scan-tree-dump-times {\.SMLALB} 9 "isel" } } */
> +/* { dg-final { scan-tree-dump-times {\.UMLALB} 2 "isel" } } */
> +/* { dg-final { scan-assembler-times {\tsmlalb\t} 9 } } */
> +/* { dg-final { scan-assembler-times {\tumlalb\t} 2 } } */
> +/* { dg-final { scan-assembler {\tmovprfx\t} } } */
> 

-- 
Richard Biener <[email protected]>
SUSE Software Solutions Germany GmbH,
Frankenstrasse 146, 90461 Nuernberg, Germany;
GF: Jochen Jaser, Andrew McDonald, Werner Knoblich; (HRB 36809, AG Nuernberg)

Reply via email to