]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
make get_operand virtual, move logical and folding into stmt
authorAndrew Macleod <amacleod@gcc.gnu.org>
Wed, 11 Apr 2018 21:16:31 +0000 (21:16 +0000)
committerAndrew Macleod <amacleod@gcc.gnu.org>
Wed, 11 Apr 2018 21:16:31 +0000 (21:16 +0000)
From-SVN: r259332

gcc/ssa-range-bb.c
gcc/ssa-range-bb.h
gcc/ssa-range-stmt.c
gcc/ssa-range-stmt.h
gcc/ssa-range.c
gcc/ssa-range.h

index bafee0f817b4c220b1b1014080f1347c5ed172bd..c9408ae4f6556d3626501cd80e5cfd83b0479471 100644 (file)
@@ -349,10 +349,38 @@ block_ranger::~block_ranger ()
   delete gori;
 }
 
+
+// Internally, the range operators all use boolen_type_node when comparisons
+// and such are made to create ranges for logical operations.
+// some languages, such as fortran, may use boolean types with different
+// precisions and these are incompatible.   This routine will look at the 
+// 2 ranges, and if there is a mismatch between the boolean types, will change
+// the range generated from the default node to the other type.
+//
+void
+block_ranger::normalize_bool_type (irange& r1, irange& r2)
+{
+  const_tree t1 = r1.get_type ();
+  const_tree t2 = r2.get_type ();
+  if (TREE_CODE (t1) != BOOLEAN_TYPE || TREE_CODE (t2) != BOOLEAN_TYPE)
+    return;
+
+  if (t1 == t2)
+    return;
+
+  /* If neither is boolean_type_node, assume they are compatible.  */
+  if (t1 == boolean_type_node)
+      r1.cast (t2);
+  else
+    if (t2 == boolean_type_node)
+      r2.cast (t1);
+}
+
 /* This routine will return what  is globally known about the range for an
    operand of any kind.  */
 bool
-block_ranger::get_operand_range (irange& r, tree op)
+block_ranger::get_operand_range (irange& r, tree op,
+                                gimple *s ATTRIBUTE_UNUSED)
 {
   /* This check allows unary operations to be handled without having to 
      make an explicit check for the existence of a second operand.  */
@@ -412,8 +440,8 @@ block_ranger::process_logical (range_stmt stmt, irange& r, tree name,
     }
   else
     {
-      ret = get_operand_range (op1_true, name);
-      ret &= get_operand_range (op1_false, name);
+      ret = get_operand_range (op1_true, name, stmt);
+      ret &= get_operand_range (op1_false, name, stmt);
     }
 
   /* If operand1 evaluated OK, move on to operand 2.  */
@@ -428,8 +456,8 @@ block_ranger::process_logical (range_stmt stmt, irange& r, tree name,
        }
       else
        {
-         ret &= get_operand_range (op2_true, name);
-         ret &= get_operand_range (op2_false, name);
+         ret &= get_operand_range (op2_true, name, stmt);
+         ret &= get_operand_range (op2_false, name, stmt);
        }
     }
 
@@ -446,7 +474,7 @@ bool
 block_ranger::get_range_from_stmt (range_stmt stmt, irange& r, tree name,
                                   const irange& lhs)
 {
-  irange op1_range, op2_range;
+  irange op1_range, op2_range, tmp_range;
   tree op1, op2;
   bool op1_in_chain, op2_in_chain;
 
@@ -466,7 +494,7 @@ block_ranger::get_range_from_stmt (range_stmt stmt, irange& r, tree name,
     { 
       if (!op2)
         return stmt.op1_irange (r, lhs);
-      if (get_operand_range (op2_range, op2))
+      if (get_operand_range (op2_range, op2, stmt))
        return stmt.op1_irange (r, lhs, op2_range);
       else
         return false;
@@ -474,7 +502,7 @@ block_ranger::get_range_from_stmt (range_stmt stmt, irange& r, tree name,
 
   if (op2 == name)
     {
-      if (get_operand_range (op1_range, op1))
+      if (get_operand_range (op1_range, op1, stmt))
        return stmt.op2_irange (r, lhs, op1_range);
       else
         return false;
@@ -493,51 +521,59 @@ block_ranger::get_range_from_stmt (range_stmt stmt, irange& r, tree name,
   if (!op1_in_chain && !op2_in_chain)
     return false;
 
+  /* Pick up an operand range for each argument.  */
+  if (!get_operand_range (op1_range, op1, stmt))
+    return false;
+  if (op2 && !get_operand_range (op2_range, op2, stmt))
+    return false;
+
   /* Can't resolve both sides at once, so take a guess at operand 1, calculate
      operand 2 and check if the guess at operand 1 was good.  */
   if (op1_in_chain && op2_in_chain)
     {
-      irange tmp_op1_range;
-      if (!get_operand_range (tmp_op1_range, op1))
-        return false;
-      if (!stmt.op2_irange (op2_range, lhs, tmp_op1_range))
+      // Get an op2_range based on the op1 range. 
+      if (!stmt.op2_irange (tmp_range, lhs, op1_range))
         return false;
+      // And combine it with the raw possibilty.
+      normalize_bool_type (op2_range, tmp_range);
+      op2_range.intersect (tmp_range);
       if (!get_range_from_stmt (SSA_NAME_DEF_STMT (op2), r, name, op2_range))
         return false;
-      if (!stmt.op1_irange (op1_range, lhs, op2_range))
+
+      // Now the same for operand 1
+      if (!stmt.op1_irange (tmp_range, lhs, op2_range))
         return false;
+      normalize_bool_type (op1_range, tmp_range);
+      op1_range.intersect (tmp_range);
       if (!get_range_from_stmt (SSA_NAME_DEF_STMT (op1), r, name, op1_range))
         return false;
-
-      /* If the guess is good, we're done. */
-      if (op1_range == tmp_op1_range)
-        return true;
-      /* Otherwise fall thru and calculate op2_range with this range.  */
+      // Do we need to possibly reevaluate op2?
+      return true;
     }
   else
     if (op1_in_chain)
       {
         if (!op2)
          {
-           if (!stmt.op1_irange (op1_range, lhs))
+           if (!stmt.op1_irange (tmp_range, lhs))
              return false;
          }
        else
          {
-           if (!get_operand_range (op2_range, op2))
-             return false;
-           if (!stmt.op1_irange (op1_range, lhs, op2_range))
+           if (!stmt.op1_irange (tmp_range, lhs, op2_range))
              return false;
          }
+       normalize_bool_type (op1_range, tmp_range);
+       op1_range.intersect (tmp_range);
        return get_range_from_stmt (SSA_NAME_DEF_STMT (op1), r, name,
                                    op1_range);
       }
     else
-      if (!get_operand_range (op1_range, op1))
-        return false;
 
-  if (!stmt.op2_irange (op2_range, lhs, op1_range))
+  if (!stmt.op2_irange (tmp_range, lhs, op1_range))
     return false;
+  normalize_bool_type (op2_range, tmp_range);
+  op2_range.intersect (tmp_range);
   return get_range_from_stmt (SSA_NAME_DEF_STMT (op2), r, name, op2_range);
 }
  
@@ -694,32 +730,6 @@ block_ranger::exercise (FILE *output)
 }
 
 
-
-bool
-block_ranger::range_on_stmt (irange& r, tree name, gimple *g)
-{
-  range_stmt rn (g);
-  irange lhs;
-
-  /* If we don't understand the stmt... */
-  if (!rn.valid())
-    return false;
-
-  /* If neither operand is what we are looking for, then return nothing.  */
-  if (rn.operand1 () != name && rn.operand2 () != name)
-    return false;
-
-  /* Only works if there is a LHS.  */
-  if (!gimple_get_lhs (g))
-    return false;
-
-  if (get_operand_range (lhs, gimple_get_lhs (g)))
-    return get_range_from_stmt (rn, r, name, lhs);
-
-  return false;
-}
-
-
 /* Attempt to evaluate the epression using whatever is globally known about
    the operands.  If it can be evaluated, TRUE is returned
    and the range is returned in R.  */
index e4895566f787f8515eeefec8679a4bd9acca71c4..81e71c3513a7df15a1542310d93f94146a37eecf 100644 (file)
@@ -39,7 +39,8 @@ class block_ranger
   bool get_range_from_stmt (range_stmt stmt, irange& r, tree name,
                            const irange& lhs);
 protected:
-  bool get_operand_range (irange& r, tree op);
+  virtual bool get_operand_range (irange& r, tree op, gimple *s = NULL);
+  void normalize_bool_type (irange& r1, irange& r2);
 public:
   block_ranger ();
   ~block_ranger ();
@@ -48,8 +49,6 @@ public:
   bool range_p (basic_block bb, tree name);
   /* What is the static calculated range of NAME on outgoing edge E.  */
   bool range_on_edge (irange& r, tree name, edge e);
-  /* What range does NAME have on entry to statement g.  */
-  bool range_on_stmt (irange& r, tree name, gimple *g);
   /* What infomation does stmt g provide about the lhs.  */
   bool range_of_def (irange& r, gimple *g);
   /* What does g provide about the lhs if NAME has RANGE_FOR_NAME.  */
index 3e6eccf29473f95dc5ace5045db0488231616150..7a4e0ce885a7632cc6c89bb9f500c686c4e3a190 100644 (file)
@@ -235,6 +235,11 @@ bool
 range_stmt::op1_irange (irange& r, const irange& lhs_range) const
 {  
   irange type_range;
+  if (lhs_range.empty_p ())
+    {
+      r.clear (TREE_TYPE (operand1 ()));
+      return true;
+    }
   type_range.set_range_for_type (TREE_TYPE (operand1 ()));
   return handler ()->op1_irange (r, lhs_range, type_range);
 }
@@ -248,6 +253,11 @@ range_stmt::op1_irange (irange& r, const irange& lhs_range,
                        const irange& op2_range) const
 {  
   gcc_assert (operand2 () != NULL);
+  if (op2_range.empty_p () || lhs_range.empty_p ())
+    {
+      r.clear (op2_range.get_type ());
+      return true;
+    }
   return handler ()->op1_irange (r, lhs_range, op2_range);
 }
 
@@ -259,6 +269,11 @@ bool
 range_stmt::op2_irange (irange& r, const irange& lhs_range,
                        const irange& op1_range) const
 {  
+  if (op1_range.empty_p () || lhs_range.empty_p ())
+    {
+      r.clear (op1_range.get_type ());
+      return true;
+    }
   return handler ()->op2_irange (r, lhs_range, op1_range);
 }
 
index e0d9bf7890679a3415a47d8a30cbb9b33a8bd2d7..dc8b1a338adf20cd4578519dd30faaa005840786 100644 (file)
@@ -43,30 +43,30 @@ private:
   gimple *g;
   void validate_stmt (gimple *s);
   class irange_operator *handler() const;
+  tree_code get_code () const;
 public:
   range_stmt ();
   range_stmt (gimple *stmt);
   range_stmt &operator= (gimple *stmt);
 
   bool valid () const;
-  tree_code get_code () const;
+  operator gimple *() const;
 
   tree operand1 () const;
   tree operand2 () const;
-
   tree ssa_operand1 () const;
   tree ssa_operand2 () const;
 
-  bool logical_expr_p () const;
-
   bool fold (irange& res, const irange& r1) const;
-  bool fold (irange& res, const irange& r1, const irange& r2) const;
   bool op1_irange (irange& r, const irange& lhs_range) const;
+
+  bool fold (irange& res, const irange& r1, const irange& r2) const;
   bool op1_irange (irange& r, const irange& lhs_range,
                   const irange& op2_range) const;
   bool op2_irange (irange& r, const irange& lhs_range,
                   const irange& op1_range) const;
 
+  bool logical_expr_p () const;
   bool fold_logical (irange& r, const irange& lhs, const irange& op1_true,
                     const irange& op1_false, const irange& op2_true,
                     const irange& op2_false) const;
@@ -167,4 +167,9 @@ range_stmt::handler () const
   return irange_op_handler (get_code ());
 }
 
+inline 
+range_stmt::operator gimple *() const
+{
+  return g;
+}
 #endif /* GCC_SSA_RANGE_STMT_H */
index 7a63089d0d3fabe803e384d848d3c7eae02ce1cb..61aa0c6d6acb2ec006f0934d24c6ba235e0cbfb6 100644 (file)
@@ -48,33 +48,6 @@ along with GCC; see the file COPYING3.  If not see
 #include "ssa-range-global.h"
 
 
-// Internally, the range operators all use boolen_type_node when comparisons
-// and such are made to create ranges for logical operations.
-// some languages, such as fortran, may use boolean types with different
-// precisions and these are incompatible.   This routine will look at the 
-// 2 ranges, and if there is a mismatch between the boolean types, will change
-// the range generated from the default node to the other type.
-//
-static void
-normalize_bool_type (irange& r1, irange& r2)
-{
-  const_tree t1 = r1.get_type ();
-  const_tree t2 = r2.get_type ();
-  if (TREE_CODE (t1) != BOOLEAN_TYPE || TREE_CODE (t2) != BOOLEAN_TYPE)
-    return;
-
-  if (t1 == t2)
-    return;
-
-  /* If neither is boolean_type_node, assume they are compatible.  */
-  if (t1 == boolean_type_node)
-      r1.cast (t2);
-  else
-    if (t2 == boolean_type_node)
-      r2.cast (t1);
-}
-
-
 class ssa_block_ranges
 {
 private:
@@ -416,12 +389,6 @@ path_ranger::process_phi (irange &r, gphi *phi)
   return true;
 }
 
-bool
-path_ranger::path_range_stmt (irange& r, tree name, gimple *g)
-{
-  return path_get_operand (r, name, gimple_bb (g));
-}
-
 
 bool
 path_ranger::path_range_stmt (irange& r, gimple *g)
@@ -511,15 +478,24 @@ path_ranger::path_get_operand (irange &r, tree name, basic_block bb)
       // This means NAME is defined in the same block, simply try to extract 
       // a range from that statement. 
       if (!path_range_stmt (r, s))
-       return get_operand_range (r, name);
+       return block_ranger::get_operand_range (r, name);
       return true;
     }
 
   if (path_range_entry (r, name, bb))
     return true;
-  return get_operand_range (r, name);
+  return block_ranger::get_operand_range (r, name);
 }
 
+bool
+path_ranger::get_operand_range (irange&r, tree op, gimple *s)
+{
+    
+  if (!s)
+    return block_ranger::get_operand_range (r, op, s);
+  else
+    return path_get_operand (r, op, gimple_bb (s));
+}
 
 bool
 path_ranger::path_fold_stmt (irange &r, range_stmt &rn, basic_block bb, edge e)
@@ -570,145 +546,145 @@ path_ranger::path_range_of_def (irange &r, gimple *g, edge e)
       gcc_assert (e->dest == bb);
       arg = gimple_phi_arg_def (phi, e->dest_idx);
       // Pick up anything simple we might know about the incoming edge. 
-      if (!range_on_edge (r, arg, e))
-       return get_operand_range (r, arg);
-      return true;
-    }
-
-  range_stmt rn(g);
-  if (!rn.valid())
-    return false; 
-  return path_fold_stmt (r, rn, bb, e);
-
-}
+       if (!range_on_edge (r, arg, e))
+         return get_operand_range (r, arg);
+       return true;
+      }
 
-// Attempt to evaluate NAME within the basic block it is defined as far
-// as possible. IF a PHI is encountered at the beginning of the block, either
-// fully evalaute it, or if E is provided, use just the value from that edge.
-bool
-path_ranger::path_range_of_def (irange &r, gimple *g)
-{
-  tree name;
-  basic_block bb = gimple_bb (g);
-  tree arg;
-  irange range_op1, range_op2;
+    range_stmt rn(g);
+    if (!rn.valid())
+      return false; 
+    return path_fold_stmt (r, rn, bb, e);
+
+  }
+
+  // Attempt to evaluate NAME within the basic block it is defined as far
+  // as possible. IF a PHI is encountered at the beginning of the block, either
+  // fully evalaute it, or if E is provided, use just the value from that edge.
+  bool
+  path_ranger::path_range_of_def (irange &r, gimple *g)
+  {
+    tree name;
+    basic_block bb = gimple_bb (g);
+    tree arg;
+    irange range_op1, range_op2;
+
+    // Note that since we are remaining within BB, we do not attempt to further
+    // evaluate any of the arguments of a PHI at this point.
+    // a recursive call could be made to evaluate any SSA_NAMEs on their
+    // repsective edgesin PATH form, but we leave that as something to look into
+    // later.  For the moment, just pick up any edge information since its cheap.
+    if (is_a <gphi *> (g))
+      {
+       gphi *phi = as_a <gphi *> (g);
+       tree phi_def = gimple_phi_result (phi);
+       irange tmp;
+       unsigned x;
+       edge e;
 
-  // Note that since we are remaining within BB, we do not attempt to further
-  // evaluate any of the arguments of a PHI at this point.
-  // a recursive call could be made to evaluate any SSA_NAMEs on their
-  // repsective edgesin PATH form, but we leave that as something to look into
-  // later.  For the moment, just pick up any edge information since its cheap.
-  if (is_a <gphi *> (g))
-    {
-      gphi *phi = as_a <gphi *> (g);
-      tree phi_def = gimple_phi_result (phi);
-      irange tmp;
-      unsigned x;
-      edge e;
+       if (!valid_irange_ssa (phi_def))
+         return false;
+       if (get_global_ssa_range (r, phi_def))
+         return true;
+
+       // Avoid infinite recursion by initializing global cache 
+       r.set_range (phi_def);
+       set_global_ssa_range (phi_def, r);
+
+       r.clear (TREE_TYPE (phi_def));
+       for (x = 0; x < gimple_phi_num_args (phi); x++)
+         {
+           arg = gimple_phi_arg_def (phi, x);
+           e = gimple_phi_arg_edge (phi, x);
+           if (!path_range_edge (range_op2, arg, e))
+             if (!get_operand_range (range_op2, arg))
+               return false;
+
+           normalize_bool_type (r, range_op2);
+           r.union_ (range_op2);
+           if (r.range_for_type_p ())
+             return true;
+         }
+
+       set_global_ssa_range (phi_def, r);
+       return true;
+      }
 
-      if (!valid_irange_ssa (phi_def))
-        return false;
-      if (get_global_ssa_range (r, phi_def))
-        return true;
+    range_stmt rn(g);
+    if (!rn.valid())
+      return false;
 
-      // Avoid infinite recursion by initializing global cache 
-      r.set_range (phi_def);
-      set_global_ssa_range (phi_def, r);
+    name = gimple_get_lhs (g);
 
-      r.clear (TREE_TYPE (phi_def));
-      for (x = 0; x < gimple_phi_num_args (phi); x++)
-        {
-         arg = gimple_phi_arg_def (phi, x);
-         e = gimple_phi_arg_edge (phi, x);
-         if (!path_range_edge (range_op2, arg, e))
-           if (!get_operand_range (range_op2, arg))
-             return false;
-
-         normalize_bool_type (r, range_op2);
-         r.union_ (range_op2);
-         if (r.range_for_type_p ())
-           return true;
-       }
+    /* If there is no LHS, then we are simply folding an expression.  */
+    if (!name)
+      return path_fold_stmt (r, rn, bb);
 
-      set_global_ssa_range (phi_def, r);
+    if (get_global_ssa_range (r, name))
       return true;
-    }
 
-  range_stmt rn(g);
-  if (!rn.valid())
-    return false;
-
-  name = gimple_get_lhs (g);
-
-  /* If there is no LHS, then we are simply folding an expression.  */
-  if (!name)
-    return path_fold_stmt (r, rn, bb);
-
-  if (get_global_ssa_range (r, name))
-    return true;
-
-  // avoid infinite recursion by initializing global cache 
-  r.set_range (name);
-  set_global_ssa_range (name, r);
-
-  bool res = path_fold_stmt (r, rn, bb);
-
-  if (res)
+    // avoid infinite recursion by initializing global cache 
+    r.set_range (name);
     set_global_ssa_range (name, r);
-  return res;
-}
-
-/* Calculate the known range for NAME on a path of basic blocks in
-   BBS.  If such a range exists, store it in R and return TRUE,
-   otherwise return FALSE.
-
-   DIR is FORWARD if BBS[0] is the definition and the last block is
-   the use.  DIR is REVERSE if the blocks are in reverse order.
 
-   If there is an edge leading into this path that we'd like to take
-   into account, such edge is START_EDGE.  Otherwise, START_EDGE is
-   set to NULL.  */
+    bool res = path_fold_stmt (r, rn, bb);
 
-bool
-path_ranger::path_range (irange &r, tree name, const vec<basic_block> &bbs,
-                        enum path_range_direction dir, edge start_edge)
-{
-  if (bbs.is_empty ())
-    return false;
-
-  /* If the first block defines NAME and it has meaningful range
-     information, use it, otherwise fall back to range for type.
-
-     Note: The first block may not always define NAME because we may
-     have pruned the paths such that the first block (bb1) is just the
-     first block that contains range info (bb99).  For example:
-
-     bb1:
-       x = 55;
-       ...
-       ...
-     bb99:
-       if (x > blah).
-  */
-  basic_block first_bb = dir == FORWARD ? bbs[0] : bbs[bbs.length () - 1];
-  gimple *def_stmt = SSA_NAME_DEF_STMT (name);
-  if (gimple_bb (def_stmt) == first_bb && start_edge)
-    {
-      if (!path_range_of_def (r, def_stmt, start_edge))
-       get_global_ssa_range (r, name);
-    }
-  else
-    get_global_ssa_range (r, name);
+    if (res)
+      set_global_ssa_range (name, r);
+    return res;
+  }
+
+  /* Calculate the known range for NAME on a path of basic blocks in
+     BBS.  If such a range exists, store it in R and return TRUE,
+     otherwise return FALSE.
+
+     DIR is FORWARD if BBS[0] is the definition and the last block is
+     the use.  DIR is REVERSE if the blocks are in reverse order.
+
+     If there is an edge leading into this path that we'd like to take
+     into account, such edge is START_EDGE.  Otherwise, START_EDGE is
+     set to NULL.  */
+
+  bool
+  path_ranger::path_range (irange &r, tree name, const vec<basic_block> &bbs,
+                          enum path_range_direction dir, edge start_edge)
+  {
+    if (bbs.is_empty ())
+      return false;
+
+    /* If the first block defines NAME and it has meaningful range
+       information, use it, otherwise fall back to range for type.
+
+       Note: The first block may not always define NAME because we may
+       have pruned the paths such that the first block (bb1) is just the
+       first block that contains range info (bb99).  For example:
+
+       bb1:
+        x = 55;
+        ...
+        ...
+       bb99:
+        if (x > blah).
+    */
+    basic_block first_bb = dir == FORWARD ? bbs[0] : bbs[bbs.length () - 1];
+    gimple *def_stmt = SSA_NAME_DEF_STMT (name);
+    if (gimple_bb (def_stmt) == first_bb && start_edge)
+      {
+       if (!path_range_of_def (r, def_stmt, start_edge))
+         get_global_ssa_range (r, name);
+      }
+    else
+      get_global_ssa_range (r, name);
 
-  if (dir == REVERSE)
-    return path_range_reverse (r, name, bbs);
+    if (dir == REVERSE)
+      return path_range_reverse (r, name, bbs);
 
-  for (unsigned i = 1; i < bbs.length (); ++i)
-    {
-      edge e = find_edge (bbs[i - 1], bbs[i]);
-      gcc_assert (e);
-      irange redge;
-      if (range_on_edge (redge, name, e))
+    for (unsigned i = 1; i < bbs.length (); ++i)
+      {
+       edge e = find_edge (bbs[i - 1], bbs[i]);
+       gcc_assert (e);
+       irange redge;
+       if (range_on_edge (redge, name, e))
        r.intersect (redge);
     }
 
index 98fb5801376aeade4acbb8d3e802a6f980c7b9a3..f505311f92823e693b6910b1c7df024645d1fde3 100644 (file)
@@ -37,7 +37,8 @@ private:
                       edge e = NULL);
   bool process_phi (irange &r, gphi *phi);
   bool path_get_operand (irange &r, tree name, basic_block bb);
-
+protected:
+  virtual bool get_operand_range (irange &r, tree op, gimple *s = NULL);
 public:
   enum path_range_direction { FORWARD, REVERSE };
   path_ranger ();
@@ -46,9 +47,7 @@ public:
   /* What is the known range of name from its DEF point to edge E.  */
   bool path_range_edge (irange& r, tree name, edge e);
   bool path_range_entry (irange& r, tree name, basic_block bb);
-  // Specifying NAME give the range of NAME before G is executed.
-  bool path_range_stmt (irange& r, tree name, gimple *g);
-  // no NAME, give the range of the LHS of the statement.
+  // Get ive the range of the LHS of the statement.
   bool path_range_stmt (irange& r, gimple *g);
 
   bool path_range (irange &r, tree name, const vec<basic_block> &bbs,