[AArch64] Enable generation of FRINTNZ instructions
Richard Sandiford
richard.sandiford@arm.com
Fri Jan 14 10:37:35 GMT 2022
"Andre Vieira (lists)" <andre.simoesdiasvieira@arm.com> writes:
> diff --git a/gcc/doc/md.texi b/gcc/doc/md.texi
> index 19e89ae502bc2f51db64667b236c1cb669718b02..3b0e4e0875b4392ab6833568b207580ef597a98f 100644
> --- a/gcc/doc/md.texi
> +++ b/gcc/doc/md.texi
> @@ -6191,6 +6191,15 @@ operands; otherwise, it may not.
>
> This pattern is not allowed to @code{FAIL}.
>
> +@cindex @code{ftrunc@var{m}@var{n}2} instruction pattern
> +@item @samp{ftrunc@var{m}@var{n}2}
> +Truncate operand 1 to a @var{n} mode signed integer, towards zero, and store
> +the result in operand 0. Both operands have mode @var{m}, which is a scalar or
> +vector floating-point mode. An exception must be raised if operand 1 does not
> +fit in a @var{n} mode signed integer as it would have if the truncation
> +happened through separate floating point to integer conversion.
> +
> +
Nit: just one blank line here.
> @cindex @code{round@var{m}2} instruction pattern
> @item @samp{round@var{m}2}
> Round operand 1 to the nearest integer, rounding away from zero in the
> diff --git a/gcc/doc/sourcebuild.texi b/gcc/doc/sourcebuild.texi
> index 6095a35cd4565fdb7d758104e80fe6411230f758..a56bbb775572fa72379854f90a01ad543557e29a 100644
> --- a/gcc/doc/sourcebuild.texi
> +++ b/gcc/doc/sourcebuild.texi
> @@ -2286,6 +2286,10 @@ Like @code{aarch64_sve_hw}, but also test for an exact hardware vector length.
> @item aarch64_fjcvtzs_hw
> AArch64 target that is able to generate and execute armv8.3-a FJCVTZS
> instruction.
> +
> +@item aarch64_frintzx_ok
> +AArch64 target that is able to generate the Armv8.5-a FRINT32Z, FRINT64Z,
> +FRINT32X and FRINT64X instructions.
> @end table
>
> @subsubsection MIPS-specific attributes
> diff --git a/gcc/internal-fn.c b/gcc/internal-fn.c
> index b24102a5990bea4cbb102069f7a6df497fc81ebf..9047b486f41948059a7a7f1ccc4032410a369139 100644
> --- a/gcc/internal-fn.c
> +++ b/gcc/internal-fn.c
> @@ -130,6 +130,7 @@ init_internal_fns ()
> #define fold_left_direct { 1, 1, false }
> #define mask_fold_left_direct { 1, 1, false }
> #define check_ptrs_direct { 0, 0, false }
> +#define ftrunc_int_direct { 0, 1, true }
>
> const direct_internal_fn_info direct_internal_fn_array[IFN_LAST + 1] = {
> #define DEF_INTERNAL_FN(CODE, FLAGS, FNSPEC) not_direct,
> @@ -156,6 +157,29 @@ get_multi_vector_move (tree array_type, convert_optab optab)
> return convert_optab_handler (optab, imode, vmode);
> }
>
> +/* Expand FTRUNC_INT call STMT using optab OPTAB. */
> +
> +static void
> +expand_ftrunc_int_optab_fn (internal_fn, gcall *stmt, convert_optab optab)
> +{
> + class expand_operand ops[2];
> + tree lhs, float_type, int_type;
> + rtx target, op;
> +
> + lhs = gimple_call_lhs (stmt);
> + target = expand_normal (lhs);
> + op = expand_normal (gimple_call_arg (stmt, 0));
> +
> + float_type = TREE_TYPE (lhs);
> + int_type = element_type (gimple_call_arg (stmt, 1));
Sorry for the run-around, but now that we don't (need to) vectorise
the second argument, I think we can drop this element_type. That in
turn means that…
> +
> + create_output_operand (&ops[0], target, TYPE_MODE (float_type));
> + create_input_operand (&ops[1], op, TYPE_MODE (float_type));
> +
> + expand_insn (convert_optab_handler (optab, TYPE_MODE (float_type),
> + TYPE_MODE (int_type)), 2, ops);
> +}
> +
> /* Expand LOAD_LANES call STMT using optab OPTAB. */
>
> static void
> @@ -3747,6 +3771,15 @@ multi_vector_optab_supported_p (convert_optab optab, tree_pair types,
> != CODE_FOR_nothing);
> }
>
> +static bool
> +direct_ftrunc_int_optab_supported_p (convert_optab optab, tree_pair types,
> + optimization_type opt_type)
> +{
> + return (convert_optab_handler (optab, TYPE_MODE (types.first),
> + TYPE_MODE (element_type (types.second)),
> + opt_type) != CODE_FOR_nothing);
> +}
> +
…this can use convert_optab_supported_p.
> diff --git a/gcc/testsuite/gcc.target/aarch64/frintnz.c b/gcc/testsuite/gcc.target/aarch64/frintnz.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..008e1cf9f4a1b0148128c65c9ea0d1bb111467b7
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/aarch64/frintnz.c
> @@ -0,0 +1,91 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -march=armv8.5-a" } */
> +/* { dg-require-effective-target aarch64_frintnzx_ok } */
Is this just a cut-&-pasto from a run test? If not, why do we need both
this and the dg-options? It feels like one on its own should be enough,
with the dg-options being better.
The test looks OK without this line.
> +/* { dg-final { check-function-bodies "**" "" } } */
> +
> +/*
> +** f1:
> +** frint32z s0, s0
> +** ret
> +*/
> +float
> +f1 (float x)
> +{
> + int y = x;
> + return (float) y;
> +}
> +
> +/*
> +** f2:
> +** frint64z s0, s0
> +** ret
> +*/
> +float
> +f2 (float x)
> +{
> + long long int y = x;
> + return (float) y;
> +}
> +
> +/*
> +** f3:
> +** frint32z d0, d0
> +** ret
> +*/
> +double
> +f3 (double x)
> +{
> + int y = x;
> + return (double) y;
> +}
> +
> +/*
> +** f4:
> +** frint64z d0, d0
> +** ret
> +*/
> +double
> +f4 (double x)
> +{
> + long long int y = x;
> + return (double) y;
> +}
> +
> +float
> +f1_dont (float x)
> +{
> + unsigned int y = x;
> + return (float) y;
> +}
> +
> +float
> +f2_dont (float x)
> +{
> + unsigned long long int y = x;
> + return (float) y;
> +}
> +
> +double
> +f3_dont (double x)
> +{
> + unsigned int y = x;
> + return (double) y;
> +}
> +
> +double
> +f4_dont (double x)
> +{
> + unsigned long long int y = x;
> + return (double) y;
> +}
> +
> +double
> +f5_dont (double x)
> +{
> + signed short y = x;
> + return (double) y;
> +}
> +
> +/* Make sure the 'dont's don't generate any frintNz. */
> +/* { dg-final { scan-assembler-times {frint32z} 2 } } */
> +/* { dg-final { scan-assembler-times {frint64z} 2 } } */
> diff --git a/gcc/testsuite/gcc.target/aarch64/frintnz_vec.c b/gcc/testsuite/gcc.target/aarch64/frintnz_vec.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..801d65ea8325cb680691286aab42747f43b90687
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/aarch64/frintnz_vec.c
> @@ -0,0 +1,47 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O3 -march=armv8.5-a" } */
> +/* { dg-require-effective-target aarch64_frintnzx_ok } */
Same here.
> +/* { dg-final { check-function-bodies "**" "" } } */
> +
> +#define TEST(name,float_type,int_type) \
> +void \
> +name (float_type * __restrict__ x, float_type * __restrict__ y, int n) \
> +{ \
> + for (int i = 0; i < n; ++i) \
> + { \
> + int_type x_i = x[i]; \
> + y[i] = (float_type) x_i; \
> + } \
> +}
> +
> +/*
> +** f1:
> +** ...
> +** frint32z v[0-9]+\.4s, v[0-9]+\.4s
> +** ...
> +*/
> +TEST(f1, float, int)
> +
> +/*
> +** f2:
> +** ...
> +** frint64z v[0-9]+\.4s, v[0-9]+\.4s
> +** ...
> +*/
> +TEST(f2, float, long long)
> +
> +/*
> +** f3:
> +** ...
> +** frint32z v[0-9]+\.2d, v[0-9]+\.2d
> +** ...
> +*/
> +TEST(f3, double, int)
> +
> +/*
> +** f4:
> +** ...
> +** frint64z v[0-9]+\.2d, v[0-9]+\.2d
> +** ...
> +*/
> +TEST(f4, double, long long)
> […]
> diff --git a/gcc/tree-vect-stmts.c b/gcc/tree-vect-stmts.c
> index f2625a2ff4089739326ce11785f1b68678c07f0e..435f2f4f5aeb2ed4c503c7b6a97d375634ae4514 100644
> --- a/gcc/tree-vect-stmts.c
> +++ b/gcc/tree-vect-stmts.c
> @@ -1625,7 +1625,8 @@ vect_finish_stmt_generation (vec_info *vinfo,
>
> static internal_fn
> vectorizable_internal_function (combined_fn cfn, tree fndecl,
> - tree vectype_out, tree vectype_in)
> + tree vectype_out, tree vectype_in,
> + tree *vectypes)
Should be described in the comment above the function.
> {
> internal_fn ifn;
> if (internal_fn_p (cfn))
> @@ -1637,8 +1638,12 @@ vectorizable_internal_function (combined_fn cfn, tree fndecl,
> const direct_internal_fn_info &info = direct_internal_fn (ifn);
> if (info.vectorizable)
> {
> - tree type0 = (info.type0 < 0 ? vectype_out : vectype_in);
> - tree type1 = (info.type1 < 0 ? vectype_out : vectype_in);
> + tree type0 = (info.type0 < 0 ? vectype_out : vectypes[info.type0]);
> + if (!type0)
> + type0 = vectype_in;
> + tree type1 = (info.type1 < 0 ? vectype_out : vectypes[info.type1]);
> + if (!type1)
> + type1 = vectype_in;
> if (direct_internal_fn_supported_p (ifn, tree_pair (type0, type1),
> OPTIMIZE_FOR_SPEED))
> return ifn;
> @@ -3263,18 +3268,40 @@ vectorizable_call (vec_info *vinfo,
> rhs_type = unsigned_type_node;
> }
>
> - int mask_opno = -1;
> + /* The argument that is not of the same type as the others. */
> + int diff_opno = -1;
> + bool masked = false;
> if (internal_fn_p (cfn))
> - mask_opno = internal_fn_mask_index (as_internal_fn (cfn));
> + {
> + if (cfn == CFN_FTRUNC_INT)
> + /* For FTRUNC this represents the argument that carries the type of the
> + intermediate signed integer. */
> + diff_opno = 1;
> + else
> + {
> + /* For masked operations this represents the argument that carries the
> + mask. */
> + diff_opno = internal_fn_mask_index (as_internal_fn (cfn));
> + masked = diff_opno >= 0;
Nit: excess space after “>=”.
> + }
> + }
I think it would be better to add a new flag to direct_internal_fn_info
to say whether type1 is scalar, rather than check based on function code.
type1 would then provide the value of diff_opno above.
Also, I think diff_opno should be separate from mask_opno.
Maybe scalar_opno would be a better name.
This would probably be simpler if we used:
internal_fn ifn = associated_internal_fn (cfn, lhs_type);
before the loop (with lhs_type being new), then used ifn to get the
direct_internal_fn_info and passed ifn to vectorizable_internal_function.
> for (i = 0; i < nargs; i++)
> {
> - if ((int) i == mask_opno)
> + if ((int) i == diff_opno)
> {
> - if (!vect_check_scalar_mask (vinfo, stmt_info, slp_node, mask_opno,
> - &op, &slp_op[i], &dt[i], &vectypes[i]))
> - return false;
> - continue;
> + if (masked)
> + {
> + if (!vect_check_scalar_mask (vinfo, stmt_info, slp_node,
> + diff_opno, &op, &slp_op[i], &dt[i],
> + &vectypes[i]))
> + return false;
> + }
> + else
> + {
> + vectypes[i] = TREE_TYPE (gimple_call_arg (stmt, i));
> + continue;
> + }
> }
>
> if (!vect_is_simple_use (vinfo, stmt_info, slp_node,
> @@ -3286,27 +3313,30 @@ vectorizable_call (vec_info *vinfo,
> return false;
> }
>
> - /* We can only handle calls with arguments of the same type. */
> - if (rhs_type
> - && !types_compatible_p (rhs_type, TREE_TYPE (op)))
> + if ((int) i != diff_opno)
Is this ever false? It looks the continue above handles the other case.
> {
> - if (dump_enabled_p ())
> - dump_printf_loc (MSG_MISSED_OPTIMIZATION, vect_location,
> - "argument types differ.\n");
> - return false;
> - }
> - if (!rhs_type)
> - rhs_type = TREE_TYPE (op);
> + /* We can only handle calls with arguments of the same type. */
> + if (rhs_type
> + && !types_compatible_p (rhs_type, TREE_TYPE (op)))
> + {
> + if (dump_enabled_p ())
> + dump_printf_loc (MSG_MISSED_OPTIMIZATION, vect_location,
> + "argument types differ.\n");
> + return false;
> + }
> + if (!rhs_type)
> + rhs_type = TREE_TYPE (op);
>
> - if (!vectype_in)
> - vectype_in = vectypes[i];
> - else if (vectypes[i]
> - && !types_compatible_p (vectypes[i], vectype_in))
> - {
> - if (dump_enabled_p ())
> - dump_printf_loc (MSG_MISSED_OPTIMIZATION, vect_location,
> - "argument vector types differ.\n");
> - return false;
> + if (!vectype_in)
> + vectype_in = vectypes[i];
> + else if (vectypes[i]
> + && !types_compatible_p (vectypes[i], vectype_in))
> + {
> + if (dump_enabled_p ())
> + dump_printf_loc (MSG_MISSED_OPTIMIZATION, vect_location,
> + "argument vector types differ.\n");
> + return false;
> + }
> }
> }
> /* If all arguments are external or constant defs, infer the vector type
> @@ -3382,8 +3412,8 @@ vectorizable_call (vec_info *vinfo,
> || (modifier == NARROW
> && simple_integer_narrowing (vectype_out, vectype_in,
> &convert_code))))
> - ifn = vectorizable_internal_function (cfn, callee, vectype_out,
> - vectype_in);
> + ifn = vectorizable_internal_function (cfn, callee, vectype_out, vectype_in,
> + &vectypes[0]);
>
> /* If that fails, try asking for a target-specific built-in function. */
> if (ifn == IFN_LAST)
> @@ -3461,7 +3491,7 @@ vectorizable_call (vec_info *vinfo,
>
> if (loop_vinfo
> && LOOP_VINFO_CAN_USE_PARTIAL_VECTORS_P (loop_vinfo)
> - && (reduc_idx >= 0 || mask_opno >= 0))
> + && (reduc_idx >= 0 || masked))
> {
> if (reduc_idx >= 0
> && (cond_fn == IFN_LAST
> @@ -3481,8 +3511,8 @@ vectorizable_call (vec_info *vinfo,
> ? SLP_TREE_NUMBER_OF_VEC_STMTS (slp_node)
> : ncopies);
> tree scalar_mask = NULL_TREE;
> - if (mask_opno >= 0)
> - scalar_mask = gimple_call_arg (stmt_info->stmt, mask_opno);
> + if (masked)
> + scalar_mask = gimple_call_arg (stmt_info->stmt, diff_opno);
> vect_record_loop_mask (loop_vinfo, masks, nvectors,
> vectype_out, scalar_mask);
> }
> @@ -3547,7 +3577,7 @@ vectorizable_call (vec_info *vinfo,
> {
> /* We don't define any narrowing conditional functions
> at present. */
> - gcc_assert (mask_opno < 0);
> + gcc_assert (!masked);
> tree half_res = make_ssa_name (vectype_in);
> gcall *call
> = gimple_build_call_internal_vec (ifn, vargs);
> @@ -3567,16 +3597,16 @@ vectorizable_call (vec_info *vinfo,
> }
> else
> {
> - if (mask_opno >= 0 && masked_loop_p)
> + if (masked && masked_loop_p)
> {
> unsigned int vec_num = vec_oprnds0.length ();
> /* Always true for SLP. */
> gcc_assert (ncopies == 1);
> tree mask = vect_get_loop_mask (gsi, masks, vec_num,
> vectype_out, i);
> - vargs[mask_opno] = prepare_vec_mask
> + vargs[diff_opno] = prepare_vec_mask
> (loop_vinfo, TREE_TYPE (mask), mask,
> - vargs[mask_opno], gsi);
> + vargs[diff_opno], gsi);
> }
>
> gcall *call;
> @@ -3614,13 +3644,13 @@ vectorizable_call (vec_info *vinfo,
> if (masked_loop_p && reduc_idx >= 0)
> vargs[varg++] = vargs[reduc_idx + 1];
>
> - if (mask_opno >= 0 && masked_loop_p)
> + if (masked && masked_loop_p)
> {
> tree mask = vect_get_loop_mask (gsi, masks, ncopies,
> vectype_out, j);
> - vargs[mask_opno]
> + vargs[diff_opno]
> = prepare_vec_mask (loop_vinfo, TREE_TYPE (mask), mask,
> - vargs[mask_opno], gsi);
> + vargs[diff_opno], gsi);
> }
>
> gimple *new_stmt;
> @@ -3639,7 +3669,7 @@ vectorizable_call (vec_info *vinfo,
> {
> /* We don't define any narrowing conditional functions at
> present. */
> - gcc_assert (mask_opno < 0);
> + gcc_assert (!masked);
> tree half_res = make_ssa_name (vectype_in);
> gcall *call = gimple_build_call_internal_vec (ifn, vargs);
> gimple_call_set_lhs (call, half_res);
> @@ -3683,7 +3713,7 @@ vectorizable_call (vec_info *vinfo,
> {
> auto_vec<vec<tree> > vec_defs (nargs);
> /* We don't define any narrowing conditional functions at present. */
> - gcc_assert (mask_opno < 0);
> + gcc_assert (!masked);
> for (j = 0; j < ncopies; ++j)
> {
> /* Build argument list for the vectorized call. */
> diff --git a/gcc/tree.h b/gcc/tree.h
> index 318019c4dc5373271551f5d9a48dadb57a29d4a7..770d0ddfcc9a7acda01ed2fafa61eab0f1ba4cfa 100644
> --- a/gcc/tree.h
> +++ b/gcc/tree.h
> @@ -6558,4 +6558,12 @@ extern unsigned fndecl_dealloc_argno (tree);
> object or pointer. Otherwise return null. */
> extern tree get_attr_nonstring_decl (tree, tree * = NULL);
>
> +/* Return the type, or for a complex or vector type the type of its
> + elements. */
> +extern tree element_type (tree);
> +
> +/* Return the precision of the type, or for a complex or vector type the
> + precision of the type of its elements. */
> +extern unsigned int element_precision (const_tree);
> +
> #endif /* GCC_TREE_H */
> diff --git a/gcc/tree.c b/gcc/tree.c
> index d98b77db50b29b22dc9af1f98cd86044f62af019..81e66dd710ce6bc237f508655cfb437b40ec0bfa 100644
> --- a/gcc/tree.c
> +++ b/gcc/tree.c
> @@ -6646,11 +6646,11 @@ valid_constant_size_p (const_tree size, cst_size_error *perr /* = NULL */)
> return true;
> }
>
> -/* Return the precision of the type, or for a complex or vector type the
> - precision of the type of its elements. */
> +/* Return the type, or for a complex or vector type the type of its
> + elements. */
>
> -unsigned int
> -element_precision (const_tree type)
> +tree
> +element_type (tree type)
> {
> if (!TYPE_P (type))
> type = TREE_TYPE (type);
> @@ -6658,7 +6658,16 @@ element_precision (const_tree type)
> if (code == COMPLEX_TYPE || code == VECTOR_TYPE)
> type = TREE_TYPE (type);
>
> - return TYPE_PRECISION (type);
> + return const_cast<tree> (type);
The const_cast<> is redundant.
Sorry for not thinking about it before, but we should probably have
a test for the SLP case. E.g.:
for (int i = 0; i < n; i += 2)
{
int_type x_i0 = x[i];
int_type x_i1 = x[i + 1];
y[i] = (float_type) x_i1;
y[i + 1] = (float_type) x_i0;
}
(with a permute thrown in for good measure). This will make sure
that the (separate) SLP group matching code handles the call correctly.
Thanks,
Richard
More information about the Gcc-patches
mailing list