From: Richard Sandiford <richard.sandiford@arm.com>
To: Robin Dapp <rdapp.gcc@gmail.com>
Cc: Jeff Law <jeffreyalaw@gmail.com>, gcc-patches <gcc-patches@gcc.gnu.org>
Subject: Re: [PATCH] fwprop: Allow UNARY_P and check register pressure.
Date: Wed, 06 Sep 2023 21:44:21 +0100 [thread overview]
Message-ID: <mpt4jk7qbfe.fsf@arm.com> (raw)
In-Reply-To: <d852ef8f-beba-f2fd-4f1c-7a3e57299dbf@gmail.com> (Robin Dapp's message of "Wed, 6 Sep 2023 13:22:12 +0200")
Robin Dapp <rdapp.gcc@gmail.com> writes:
> Hi Richard,
>
> I did some testing with the attached v2 that does not restrict to UNARY
> anymore. As feared ;) there is some more fallout that I'm detailing below.
>
> On Power there is one guality fail (pr43051-1.c) that I would take
> the liberty of ignoring for now.
>
> On x86 there are four fails:
>
> - cond_op_addsubmuldiv__Float16-2.c: assembler error
> unsupported masking for `vmovsh'. I guess that's a latent backend
> problem.
>
> - ifcvt-3.c, pr49095.c: Here we propagate into a compare. Before, we had
> (cmp (reg/CC) 0) and now we have (cmp (plus (reg1 reg2) 0).
> That looks like a costing problem and can hopefully solveable by making
> the second compare more expensive, preventing the propagation.
> i386 costing (or every costing?) is brittle so that could well break other
> things.
>
> - pr88873.c: This is interesting because even before this patch we
> propagated with different register classes (V2DF vs DI). With the patch
> we check the register pressure, find the class NO_REGS for V2DF and
> abort (because the patch assumes NO_REGS = high pressure). I'm thinking
> of keeping the old behavior for reg-reg propagations and only checking
> the pressure for more complex operations.
>
> aarch64 has the most fails:
>
> - One guality fail (same as Power).
> - shrn-combine-[123].c as before.
>
> - A class of (hopefully, I only checked some) similar cases where we
> propagate an unspec_whilelo into an unspec_ptest. Before we would only
> set a REG_EQUALS note.
> Before we managed to create a while_ultsivnx16bi_cc whereas now we have
> while_ultsivnx16bi and while_ultsivnx16bi_ptest that won't be combined.
> We create redundant whilelos and I'm not sure how to improve that. I
> guess a peephole is out of the question :)
>
> - pred-combine-and.c: Here the new propagation appears useful at first.
> We propagate a "vector mask and" into a while_ultsivnx4bi_ptest and the
> individual and registers remain live up to the propagation site (while
> being dead before the patch).
> With the registers dead, combine could create a single fcmgt before.
> Now it only manages a 2->2 combination because we still need the registers
> and end up with two fcmgts.
> The code is worse but this seems more bad luck than anything.
>
> - Addressing fails from before: I looked into these and suspect all of
> them are a similar.
> What happens is that we have a poly_int offset that we shift, negate
> and then add to x0. The result is used as load address.
> Before, we would pull (combine) the (plus x0 reg) into the load keeping
> the neg and shift.
> Now we propagate everything into a single (set (minus x0 offset)).
> The propagation itself seems worthwhile because we save one insn.
> However as we got rid of the base/offset split by lumping everything
> together, combine cannot pull the (plus) into the address load and
> we require an aarch64_split_add_offset. This will emit the longer
> sequence of ashiftl and subtract. The "base" address is x0 here so
> we cannot convert (minus x0 ...)) into neg.
> I didn't go through all of aarch64_split_add_offset. I suppose we
> could re-add the separation of base/offset there but that might be
> a loss when the result is not used as an address.
>
> Again, all in all no fatal problems but pretty annoying :) It's not much
> but just gradually worse than with just UNARY. Any idea on how/whether to
> continue?
Thanks for giving it a go. Can you post the latest version of the
regpressure patch too? The previous on-list version I could find
seems to be too old.
Thanks,
Richard
> Regards
> Robin
>
> gcc/ChangeLog:
>
> * fwprop.cc (fwprop_propagation::profitable_p): Add unary
> handling.
> (fwprop_propagation::update_register_pressure): New function.
> (fwprop_propagation::register_pressure_high_p): New function
> (reg_single_def_for_src_p): Look through unary expressions.
> (try_fwprop_subst_pattern): Check register pressure.
> (forward_propagate_into): Call new function.
> (fwprop_init): Init register pressure.
> (fwprop_done): Clean up register pressure.
> (fwprop_insn): Add comment.
>
> gcc/testsuite/ChangeLog:
>
> * gcc.target/riscv/rvv/autovec/binop/vadd-vx-fwprop.c: New test.
> ---
> gcc/fwprop.cc | 359 +++++++++++++++++-
> .../riscv/rvv/autovec/binop/vadd-vx-fwprop.c | 64 ++++
> 2 files changed, 419 insertions(+), 4 deletions(-)
> create mode 100644 gcc/testsuite/gcc.target/riscv/rvv/autovec/binop/vadd-vx-fwprop.c
>
> diff --git a/gcc/fwprop.cc b/gcc/fwprop.cc
> index 0707a234726..ce6f5a74b00 100644
> --- a/gcc/fwprop.cc
> +++ b/gcc/fwprop.cc
> @@ -36,6 +36,10 @@ along with GCC; see the file COPYING3. If not see
> #include "tree-pass.h"
> #include "rtl-iter.h"
> #include "target.h"
> +#include "dominance.h"
> +
> +#include "ira.h"
> +#include "regpressure.h"
>
> /* This pass does simple forward propagation and simplification when an
> operand of an insn can only come from a single def. This pass uses
> @@ -103,6 +107,10 @@ using namespace rtl_ssa;
>
> static int num_changes;
>
> +/* Keep track of which registers already increased the pressure to avoid double
> + booking. */
> +sbitmap pressure_accounted;
> +
> /* Do not try to replace constant addresses or addresses of local and
> argument slots. These MEM expressions are made only once and inserted
> in many instructions, as well as being used to control symbol table
> @@ -181,6 +189,8 @@ namespace
> bool changed_mem_p () const { return result_flags & CHANGED_MEM; }
> bool folded_to_constants_p () const;
> bool profitable_p () const;
> + bool register_pressure_high_p (rtx, rtx, rtx_insn *, rtx_insn *) const;
> + bool update_register_pressure (rtx, rtx, rtx_insn *, rtx_insn *) const;
>
> bool check_mem (int, rtx) final override;
> void note_simplification (int, uint16_t, rtx, rtx) final override;
> @@ -340,18 +350,272 @@ fwprop_propagation::profitable_p () const
> && !paradoxical_subreg_p (to))
> return true;
>
> - if (CONSTANT_P (to))
> + /* Disallow propagations into debug insns when it is not a reg-reg
> + propagation. Their costs are usually 0 and a cost comparison will
> + always be profitable no matter how much we complicate the pattern. */
> + if (DEBUG_INSN_P (insn))
> + return false;
> +
> + /* For more complex operations check each sub-rtx. */
> + subrtx_iterator::array_type array;
> + FOR_EACH_SUBRTX (iter, array, to, NONCONST)
> + {
> + const_rtx x = *iter;
> +
> + if (REG_P (x))
> + continue;
> +
> + else if (GET_CODE (x) == SUBREG
> + && REG_P (SUBREG_REG (x))
> + && !paradoxical_subreg_p (x))
> + continue;
> +
> + else if (rtx_equal_p (x, to))
> + continue;
> +
> + else
> + return false;
> + }
> +
> + return true;
> +}
> +
> +/* Check if the register pressure in any predecessor block of USE's block
> + until DEF's block is equal or higher than the number of hardregs in NU's
> + register class. */
> +bool
> +fwprop_propagation::register_pressure_high_p (rtx nu, rtx old, rtx_insn *def,
> + rtx_insn *use) const
> +{
> + enum reg_class nu_class, old_class;
> + int nu_nregs, old_nregs;
> + nu_class = regpressure_get_regno_pressure_class (REGNO (nu), &nu_nregs);
> + old_class
> + = regpressure_get_regno_pressure_class (REGNO (old), &old_nregs);
> +
> + if (nu_class == NO_REGS && old_class == NO_REGS)
> return true;
>
> - return false;
> This patch enables the forwarding of sources from more complex
> operations like unary (i.e. vec_duplicate) or binary. As this
> potentially involves replacing a register with a register of a different
> class the ira_hoist_pressure machinery is used to calculate the change
> in register pressure. If the propagation would increase the pressure
> beyond the number of hard regs, we don't perform it.
> + if (nu_class == old_class)
> + return false;
> +
> + basic_block bbfrom = BLOCK_FOR_INSN (def);
> + basic_block bbto = BLOCK_FOR_INSN (use);
> +
> + basic_block bb;
> +
> + sbitmap visited = sbitmap_alloc (last_basic_block_for_fn (cfun));
> + bitmap_clear (visited);
> + auto_vec<basic_block> q;
> + q.safe_push (bbto);
> +
> + bool high = false;
> +
> + while (!q.is_empty ())
> + {
> + bb = q.pop ();
> +
> + if (bitmap_bit_p (visited, bb->index))
> + continue;
> +
> + /* Nothing to do if the register to be replaced is not live
> + in this BB. */
> + if (bb != bbfrom && !regpressure_is_live_in (bb, REGNO (old)))
> + continue;
> +
> + /* Nothing to do if the replacement register is already live in
> + this BB. */
> + if (regpressure_is_live_in (bb, REGNO (nu)))
> + continue;
> +
> + bitmap_set_bit (visited, bb->index);
> +
> + int press = regpressure_get (bb, nu_class);
> + if (dump_file && (dump_flags & TDF_DETAILS))
> + fprintf (dump_file, "pressure for reg %d in bb %d: %d\n",
> + REGNO (nu), bb->index, press);
> +
> + if (press + nu_nregs > ira_class_hard_regs_num[nu_class])
> + {
> + high = true;
> + break;
> + }
> +
> + if (bb == bbfrom || bb == ENTRY_BLOCK_PTR_FOR_FN (cfun))
> + break;
> +
> + edge e;
> + edge_iterator ei;
> +
> + FOR_EACH_EDGE (e, ei, bb->preds)
> + {
> + basic_block pred = e->src;
> + q.safe_push (pred);
> + }
> + }
> +
> + sbitmap_free (visited);
> +
> + return high;
> }
>
> -/* Check that X has a single def. */
> +/* Update the register pressure when propagating from DEF to USE
> + replacing OLD with NU. Return TRUE if the pressure was updated. */
> +bool
> +fwprop_propagation::update_register_pressure (rtx nu, rtx old, rtx_insn *def,
> + rtx_insn *use) const
> +{
> + basic_block bb;
> +
> + enum reg_class nu_class, old_class;
> + int nu_nregs, old_nregs;
> + nu_class = regpressure_get_regno_pressure_class (REGNO (nu), &nu_nregs);
> + old_class = regpressure_get_regno_pressure_class (REGNO (old), &old_nregs);
> +
> + bool changed = false;
> +
> + if (nu_class == old_class)
> + return false;
> +
> + basic_block bbfrom = BLOCK_FOR_INSN (def);
> + basic_block bbto = BLOCK_FOR_INSN (use);
> +
> + sbitmap visited = sbitmap_alloc (last_basic_block_for_fn (cfun));
> + bitmap_clear (visited);
> + auto_vec<basic_block> q;
> + q.safe_push (bbto);
> +
> + /* Precompute the users. */
> + auto_vec<rtx_insn *> users;
> + df_ref duse;
> + bool should_decrease = true;
> + FOR_EACH_INSN_USE (duse, def)
> + {
> + rtx dreg = DF_REF_REAL_REG (duse);
> + if (REGNO (dreg) != REGNO (old))
> + continue;
> +
> + df_ref op_ref = DF_REG_USE_CHAIN (REGNO (dreg));
> + for (; op_ref; op_ref = DF_REF_NEXT_REG (op_ref))
> + {
> + if (!DF_REF_INSN_INFO (op_ref))
> + continue;
> +
> + rtx_insn *insn = DF_REF_INSN (op_ref);
> +
> + /* If there are other users in BBTO, never decrease the
> + pressure. */
> + if (BLOCK_FOR_INSN (insn) == bbto && insn != use)
> + {
> + should_decrease = false;
> + break;
> + }
> +
> + users.safe_push (insn);
> + }
> + }
> +
> + while (!q.is_empty ())
> + {
> + bb = q.pop ();
> +
> + if (bitmap_bit_p (visited, bb->index))
> + continue;
> +
> + /* Nothing to do if the register to be replaced is not live
> + in this BB. */
> + if (bb != bbfrom && !regpressure_is_live_in (bb, REGNO (old)))
> + continue;
> +
> + /* Nothing to do if the new register is already live in this BB. */
> + if (regpressure_is_live_in (bb, REGNO (nu)))
> + continue;
> +
> + bitmap_set_bit (visited, bb->index);
> +
> + if (regpressure_is_live_in (bb, REGNO (old)))
> + {
> + if (dump_file && (dump_flags & TDF_DETAILS))
> + fprintf (dump_file, "increasing pressure for "
> + "reg %d in bb %d by %d reg(s).\n",
> + REGNO (nu), bb->index, nu_nregs);
> + regpressure_set_live_in (bb, REGNO (nu));
> + regpressure_increase (bb, nu_class, nu_nregs);
> + changed = true;
> +
> + if (should_decrease)
> + {
> + rtx_insn *user;
> + int ix;
> + bool should_decrease_local = true;
> +
> + /* If this BB dominates no BB where OLD is used (except BBTO)
> + the register pressure is decreased. */
> + FOR_EACH_VEC_ELT(users, ix, user)
> + {
> + basic_block ubb = BLOCK_FOR_INSN (user);
> + if (ubb == bbto)
> + continue;
> + if (dominated_by_p (CDI_DOMINATORS, ubb, bb))
> + {
> + should_decrease_local = false;
> + break;
> + }
> + }
> +
> + if (should_decrease_local)
> + {
> + if (dump_file && (dump_flags & TDF_DETAILS))
> + fprintf (dump_file, "decreasing pressure for "
> + "reg %d in bb %d by %d reg(s).\n",
> + REGNO (old), bb->index, old_nregs);
> + regpressure_clear_live_in (bb, REGNO (old));
> + regpressure_decrease (bb, old_class, old_nregs);
> + }
> + }
> + }
> +
> + if (bb == bbfrom || bb == ENTRY_BLOCK_PTR_FOR_FN (cfun))
> + break;
> +
> + edge e;
> + edge_iterator ei;
> +
> + FOR_EACH_EDGE (e, ei, bb->preds)
> + {
> + basic_block pred = e->src;
> + q.safe_push (pred);
> + }
> + }
> +
> + sbitmap_free (visited);
> + return changed;
> +}
> +
> +/* Check that X has a single def. In case of more complex operations
> + check each sub-rtx. */
>
> static bool
> reg_single_def_p (rtx x)
> {
> - return REG_P (x) && crtl->ssa->single_dominating_def (REGNO (x));
> + if (REG_P (x))
> + return crtl->ssa->single_dominating_def (REGNO (x));
> +
> + subrtx_var_iterator::array_type array;
> + FOR_EACH_SUBRTX_VAR (iter, array, x, ALL)
> + {
> + rtx sub = *iter;
> +
> + if (SUBREG_P (x) && !contains_paradoxical_subreg_p (sub))
> + sub = SUBREG_REG (x);
> +
> + bool ok = REG_P (sub) && crtl->ssa->single_dominating_def (REGNO (sub));
> +
> + if (!ok)
> + return false;
> + }
> +
> + return true;
> }
>
> /* Try to substitute (set DEST SRC), which defines DEF, into note NOTE of
> @@ -490,6 +754,81 @@ try_fwprop_subst_pattern (obstack_watermark &attempt, insn_change &use_change,
> }
> }
>
> + /* Perform register-pressure checks for all sub-rtxs.
> + If FLAG_IRA_HOIST_PRESSURE is set only allow the propagation if the
> + register pressure is not high.
> + Otherwise only allow the propagation if DEF has only a single use. */
> + if (ok)
> + {
> + rtx_insn *def_rtl = def_insn->rtl ();
> +
> + subrtx_var_iterator::array_type array;
> + FOR_EACH_SUBRTX_VAR (iter, array, src, NONCONST)
> + {
> + rtx srcreg = *iter;
> + rtx dstreg = dest;
> +
> + /* For register pressure we do not care about subregs so strip
> + them. */
> + while (SUBREG_P (dstreg))
> + dstreg = SUBREG_REG (dstreg);
> +
> + while (SUBREG_P (srcreg))
> + srcreg = SUBREG_REG (srcreg);
> +
> + if (!REG_P (dstreg) || !REG_P (srcreg))
> + continue;
> +
> + /* Nothing to do for the same class. */
> + bool same_class = regpressure_same_class (dstreg, srcreg);
> + if (same_class)
> + {
> + if (flag_ira_hoist_pressure)
> + continue;
> + else
> + {
> + /* Check if this is the only use. If not do not
> + continue. */
> + for (use_info *use : def->all_uses ())
> + if (use->insn () != use_insn)
> + return false;
> + }
> + }
> +
> + /* Never allow different pressure classes when not considering
> + register pressure. */
> + if (!same_class && !flag_ira_hoist_pressure)
> + return false;
> +
> + if (!bitmap_bit_p (pressure_accounted, REGNO (srcreg)))
> + {
> + if (!prop.register_pressure_high_p (srcreg, dstreg, def_rtl,
> + use_rtl))
> + {
> + bool updated
> + = prop.update_register_pressure (srcreg, dstreg, def_rtl,
> + use_rtl);
> + if (updated)
> + bitmap_set_bit (pressure_accounted, REGNO (srcreg));
> + }
> + else
> + {
> + if (dump_file && (dump_flags & TDF_DETAILS))
> + fprintf (dump_file, "cannot propagate from insn %d into"
> + " insn %d: %s\n", def_insn->uid (),
> + use_insn->uid (),
> + "would increase register pressure beyond number"
> + " of hard regs");
> + ok = false;
> + }
> + }
> + else
> + if (dump_file && (dump_flags & TDF_DETAILS))
> + fprintf (dump_file, "pressure for reg %d has already"
> + " been accounted\n", REGNO (srcreg));
> + }
> + }
> +
> if (!ok)
> {
> /* The pattern didn't match, but if all uses of SRC folded to
> @@ -538,6 +877,7 @@ try_fwprop_subst_pattern (obstack_watermark &attempt, insn_change &use_change,
> confirm_change_group ();
> crtl->ssa->change_insn (use_change);
> num_changes++;
> +
> return true;
> }
>
> @@ -890,6 +1230,10 @@ fwprop_init (void)
>
> df_analyze ();
> crtl->ssa = new rtl_ssa::function_info (cfun);
> +
> + regpressure_init ();
> + pressure_accounted = sbitmap_alloc (DF_REG_SIZE (df));
> + bitmap_clear (pressure_accounted);
> }
>
> static void
> @@ -910,6 +1254,9 @@ fwprop_done (void)
> fprintf (dump_file,
> "\nNumber of successful forward propagations: %d\n\n",
> num_changes);
> +
> + regpressure_cleanup ();
> + sbitmap_free (pressure_accounted);
> }
>
> /* Try to optimize INSN, returning true if something changes.
> @@ -919,6 +1266,10 @@ fwprop_done (void)
> static bool
> fwprop_insn (insn_info *insn, bool fwprop_addr_p)
> {
> + /* TODO: when rejecting propagations due to register pressure
> + we would actually like to try the propagation with most
> + potential = uses first instead of going through them sequentially. */
> +
> for (use_info *use : insn->uses ())
> {
> if (use->is_mem ())
> diff --git a/gcc/testsuite/gcc.target/riscv/rvv/autovec/binop/vadd-vx-fwprop.c b/gcc/testsuite/gcc.target/riscv/rvv/autovec/binop/vadd-vx-fwprop.c
> new file mode 100644
> index 00000000000..0ed1bb80f73
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/riscv/rvv/autovec/binop/vadd-vx-fwprop.c
> @@ -0,0 +1,64 @@
> +/* { dg-do compile } */
> +/* { dg-additional-options "-O2 -march=rv64gcv -mabi=lp64d --param=riscv-autovec-preference=fixed-vlmax -fdump-rtl-fwprop1-details" } */
> +
> +/* Check that we propagate from vec_duplicates into vadd.vv, turning it into
> + vadd.vx. */
> +
> +#include <stdint-gcc.h>
> +
> +/* Define 26 variables so that we can perform two forward propagations until we
> + reach the hard reg limit of 28. */
> +uint64_t *restrict dst1;
> +uint64_t *restrict dst2;
> +uint64_t *restrict dst3;
> +uint64_t *restrict dst4;
> +uint64_t *restrict dst5;
> +uint64_t *restrict dst6;
> +uint64_t *restrict dst7;
> +uint64_t *restrict dst8;
> +uint64_t *restrict dst9;
> +uint64_t *restrict dst10;
> +uint64_t *restrict dst11;
> +uint64_t *restrict a1;
> +uint64_t *restrict a2;
> +uint64_t *restrict a3;
> +uint64_t *restrict a4;
> +uint64_t *restrict a5;
> +uint64_t *restrict a6;
> +uint64_t *restrict a7;
> +uint64_t *restrict a8;
> +uint64_t *restrict a9;
> +uint64_t *restrict a10;
> +uint64_t *restrict a11;
> +uint64_t b1;
> +uint64_t b2;
> +uint64_t b3;
> +uint64_t b4;
> +
> +void foo (int n)
> +{
> + __builtin_expect (n % 128, 0);
> + for (int i = 0; i < n; i++)
> + {
> + /* We expect b3 to be propagated into vadd here as well as in the other
> + 7 occurences (without increasing the pressure every time).
> + Then b2 is still supposed to be propagated but b1 is not even though
> + it would be better to do so. */
> + dst1[i] = a1[i] + b3;
> + dst2[i] = a2[i] + b3;
> + dst3[i] = a3[i] + b2;
> + dst4[i] = a4[i] + b1;
> + dst5[i] = a5[i] + b1;
> + dst6[i] = a6[i] + b3;
> + dst7[i] = a7[i] + b3;
> + dst8[i] = a8[i] + b3;
> + dst9[i] = a9[i] + b3;
> + dst10[i] = a10[i] + b3;
> + dst11[i] = a11[i] + b3;
> + }
> +}
> +
> +/* { dg-final { scan-rtl-dump-times "increasing pressure for reg" 2 "fwprop1" } } */
> +/* { dg-final { scan-rtl-dump-times "pressure for reg \[0-9\]+ has already been accounted" 7 "fwprop1" } } */
> +/* { dg-final { scan-rtl-dump-times "would increase register pressure beyond number of hard regs" 2 "fwprop1" } } */
> +/* { dg-final { scan-assembler-times "\tvadd\.vx" 9 } } */
next prev parent reply other threads:[~2023-09-06 20:44 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
2023-08-07 10:26 Robin Dapp
2023-08-24 14:06 ` Robin Dapp
2023-08-28 23:33 ` Jeff Law
2023-08-29 11:40 ` Richard Sandiford
2023-09-05 6:53 ` Robin Dapp
2023-09-05 8:38 ` Richard Sandiford
2023-09-05 8:45 ` Robin Dapp
2023-09-06 11:22 ` Robin Dapp
2023-09-06 20:44 ` Richard Sandiford [this message]
2023-09-07 7:56 ` Robin Dapp
2023-09-07 13:42 ` Richard Sandiford
2023-09-07 14:25 ` Robin Dapp
2023-09-26 16:24 ` Richard Sandiford
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=mpt4jk7qbfe.fsf@arm.com \
--to=richard.sandiford@arm.com \
--cc=gcc-patches@gcc.gnu.org \
--cc=jeffreyalaw@gmail.com \
--cc=rdapp.gcc@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).