public inbox for gcc-cvs@sourceware.org
help / color / mirror / Atom feed
* [gcc(refs/users/aoliva/heads/strub)] more tests, red zones, and deferred strubbing
@ 2021-08-05 21:32 Alexandre Oliva
  0 siblings, 0 replies; 6+ messages in thread
From: Alexandre Oliva @ 2021-08-05 21:32 UTC (permalink / raw)
  To: gcc-cvs

https://gcc.gnu.org/g:e811a5b461a93219a3e84b7b50efc99ebc52f76d

commit e811a5b461a93219a3e84b7b50efc99ebc52f76d
Author: Alexandre Oliva <oliva@adacore.com>
Date:   Thu Aug 5 18:32:13 2021 -0300

    more tests, red zones, and deferred strubbing

Diff:
---
 gcc/builtins.c                                     |  86 +++-
 gcc/ipa-split.c                                    |   7 +
 gcc/ipa-strub.c                                    | 434 +++++++++++++--------
 gcc/ipa-strub.h                                    |   7 +
 gcc/testsuite/c-c++-common/strub-O2.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O2fni.c           |   6 +-
 gcc/testsuite/c-c++-common/strub-O3fni.c           |   4 +-
 gcc/testsuite/c-c++-common/strub-Og.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-Os.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-all1.c            |   4 +-
 gcc/testsuite/c-c++-common/strub-apply1.c          |  15 +
 gcc/testsuite/c-c++-common/strub-apply2.c          |  12 +
 gcc/testsuite/c-c++-common/strub-apply3.c          |   8 +
 gcc/testsuite/c-c++-common/strub-apply4.c          |  21 +
 gcc/testsuite/c-c++-common/strub-at-calls1.c       |   4 +-
 gcc/testsuite/c-c++-common/strub-default1.c        |   8 +-
 gcc/testsuite/c-c++-common/strub-defer-O1.c        |   7 +
 gcc/testsuite/c-c++-common/strub-defer-O2.c        |   8 +
 gcc/testsuite/c-c++-common/strub-defer-O3.c        |  93 +++++
 gcc/testsuite/c-c++-common/strub-defer-Os.c        |   7 +
 gcc/testsuite/c-c++-common/strub-internal1.c       |   4 +-
 gcc/testsuite/c-c++-common/strub-parms1.c          |  48 +++
 gcc/testsuite/c-c++-common/strub-parms2.c          |  36 ++
 gcc/testsuite/c-c++-common/strub-parms3.c          |  58 +++
 .../c-c++-common/torture/strub-callable1.c         |  13 +
 .../c-c++-common/torture/strub-callable2.c         | 264 +++++++++++++
 gcc/testsuite/c-c++-common/torture/strub-const1.c  |  18 +
 gcc/testsuite/c-c++-common/torture/strub-const2.c  |  22 ++
 gcc/testsuite/c-c++-common/torture/strub-const3.c  |  13 +
 gcc/testsuite/c-c++-common/torture/strub-const4.c  |  17 +
 gcc/testsuite/c-c++-common/torture/strub-data5.c   |   6 +-
 gcc/testsuite/c-c++-common/torture/strub-pure1.c   |  18 +
 gcc/testsuite/c-c++-common/torture/strub-pure2.c   |  22 ++
 gcc/testsuite/c-c++-common/torture/strub-pure3.c   |  13 +
 gcc/testsuite/c-c++-common/torture/strub-pure4.c   |  17 +
 gcc/testsuite/c-c++-common/torture/strub-run1.c    |  85 ++++
 gcc/testsuite/c-c++-common/torture/strub-run2.c    |  75 ++++
 gcc/testsuite/c-c++-common/torture/strub-run3.c    |  75 ++++
 .../g++.dg/{wrappers/strub1.C => strub-run1.C}     |   1 +
 gcc/testsuite/g++.dg/wrappers/strub2.C             |  22 --
 gcc/testsuite/g++.dg/wrappers/strub3.C             |  22 --
 gcc/testsuite/g++.dg/wrappers/strub4.C             |  18 -
 42 files changed, 1362 insertions(+), 242 deletions(-)

diff --git a/gcc/builtins.c b/gcc/builtins.c
index f387d93974f..0341a0923b2 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -70,6 +70,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "gimple-fold.h"
 #include "intl.h"
 #include "file-prefix-map.h" /* remap_macro_filename()  */
+#include "ipa-strub.h" /* strub_watermark_parm()  */
 #include "gomp-constants.h"
 #include "omp-general.h"
 #include "tree-dfa.h"
@@ -7926,7 +7927,23 @@ expand_builtin_strub_enter (tree exp)
   if (optimize < 1 || flag_no_inline)
     return NULL_RTX;
 
-  rtx stktop = expand_builtin_stack_address ();
+  rtx stktop = NULL_RTX;
+
+#if 1 || defined RED_ZONE_SIZE
+  if (tree wmptr = (optimize
+		    ? strub_watermark_parm (current_function_decl)
+		    : NULL_TREE))
+    {
+      tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+      tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+				 build_int_cst (TREE_TYPE (wmptr), 0));
+      rtx wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+      stktop = force_reg (ptr_mode, wmark);
+    }
+#endif
+
+  if (!stktop)
+    stktop = expand_builtin_stack_address ();
 
   tree wmptr = CALL_EXPR_ARG (exp, 0);
   tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
@@ -7952,6 +7969,52 @@ expand_builtin_strub_update (tree exp)
 
   rtx stktop = expand_builtin_stack_address ();
 
+#ifdef RED_ZONE_SIZE
+  /* Here's how the strub enter, update and leave functions deal with red zones.
+
+     If it weren't for red zones, update, called from within a strub context,
+     would bump the watermark to the top of the stack.  Enter and leave, running
+     in the caller, would use the caller's top of stack address both to
+     initialize the watermark passed to the callee, and to start strubbing the
+     stack afterwards.
+
+     Ideally, we'd update the watermark so as to cover the used amount of red
+     zone, and strub starting at the caller's other end of the (presumably
+     unused) red zone.  Normally, only leaf functions use the red zone, but at
+     this point we can't tell whether a function is a leaf, nor can we tell how
+     much of the red zone it uses.  Furthermore, some strub contexts may have
+     been inlined so that update and leave are called from the same stack frame,
+     and the strub builtins may all have been inlined, turning a strub function
+     into a leaf.
+
+     So cleaning the range from the caller's stack pointer (one end of the red
+     zone) to the (potentially inlined) callee's (other end of the) red zone
+     could scribble over the caller's own red zone.
+
+     We avoid this possibility by arranging for callers that are strub contexts
+     to use their own watermark as the strub starting point.  So, if A calls B,
+     and B calls C, B will tell A to strub up to the end of B's red zone, and
+     will strub itself only the part of C's stack frame and red zone that
+     doesn't overlap with B's.  With that, we don't need to know who's leaf and
+     who isn't: inlined calls will shrink their strub window to zero, each
+     remaining call will strub some portion of the stack, and eventually the
+     strub context will return to a caller that isn't a strub context itself,
+     that will therefore use its own stack pointer as the strub starting point.
+     It's not a leaf, because strub contexts can't be inlined into non-strub
+     contexts, so it doesn't use the red zone, and it will therefore correctly
+     strub up the callee's stack frame up to the end of the callee's red zone.
+     Neat!  */
+  if (true /* (flags_from_decl_or_type (current_function_decl) & ECF_LEAF) */)
+    {
+      poly_int64 red_zone_size = RED_ZONE_SIZE;
+#if STACK_GROWS_DOWNWARD
+      red_zone_size = -red_zone_size;
+#endif
+      stktop = plus_constant (ptr_mode, stktop, red_zone_size);
+      stktop = force_reg (ptr_mode, stktop);
+    }
+#endif
+
   tree wmptr = CALL_EXPR_ARG (exp, 0);
   tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
   tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
@@ -7982,7 +8045,23 @@ expand_builtin_strub_leave (tree exp)
   if (optimize < 2 || flag_no_inline)
     return NULL_RTX;
 
-  rtx stktop = expand_builtin_stack_address ();
+  rtx stktop = NULL_RTX;
+
+#if 1 || defined RED_ZONE_SIZE
+  if (tree wmptr = (optimize
+		    ? strub_watermark_parm (current_function_decl)
+		    : NULL_TREE))
+    {
+      tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+      tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+				 build_int_cst (TREE_TYPE (wmptr), 0));
+      rtx wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+      stktop = force_reg (ptr_mode, wmark);
+    }
+#endif
+
+  if (!stktop)
+    stktop = expand_builtin_stack_address ();
 
   tree wmptr = CALL_EXPR_ARG (exp, 0);
   tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
@@ -8000,6 +8079,9 @@ expand_builtin_strub_leave (tree exp)
   rtx end = stktop;
 #endif
 
+  /* We're going to modify it, so make sure it's not e.g. the stack pointer.  */
+  base = copy_to_reg (base);
+
   rtx_code_label *done = gen_label_rtx ();
   do_compare_rtx_and_jump (base, end, LT, STACK_UNSIGNED,
 			   ptr_mode, NULL_RTX, done, NULL,
diff --git a/gcc/ipa-split.c b/gcc/ipa-split.c
index 5e918ee3fbf..e4ebae472f5 100644
--- a/gcc/ipa-split.c
+++ b/gcc/ipa-split.c
@@ -104,6 +104,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "ipa-fnsummary.h"
 #include "cfgloop.h"
 #include "attribs.h"
+#include "ipa-strub.h"
 
 /* Per basic block info.  */
 
@@ -1791,6 +1792,12 @@ execute_split_functions (void)
 		 "section.\n");
       return 0;
     }
+  if (!strub_splittable_p (node))
+    {
+      if (dump_file)
+	fprintf (dump_file, "Not splitting: function is a strub context.\n");
+      return 0;
+    }
 
   /* We enforce splitting after loop headers when profile info is not
      available.  */
diff --git a/gcc/ipa-strub.c b/gcc/ipa-strub.c
index e302f0ec1c5..05c91a6485c 100644
--- a/gcc/ipa-strub.c
+++ b/gcc/ipa-strub.c
@@ -210,10 +210,16 @@ get_strub_mode_from_attr (tree strub_attr, bool var_p = false)
   return mode;
 }
 
+static enum strub_mode
+get_strub_mode_from_decl (tree fndecl)
+{
+  return get_strub_mode_from_attr (get_strub_attr_from_decl (fndecl));
+}
+
 static enum strub_mode
 get_strub_mode (cgraph_node *node)
 {
-  return get_strub_mode_from_attr (get_strub_attr_from_decl (node->decl));
+  return get_strub_mode_from_decl (node->decl);
 }
 
 static enum strub_mode
@@ -263,7 +269,7 @@ calls_builtin_apply_args_p (cgraph_node *node, bool report = false)
 	break;
 
       sorry_at (gimple_location (e->call_stmt),
-		"at-calls strub does not support call to %qD",
+		"at-calls %<strub%> does not support call to %qD",
 		cdecl);
     }
 
@@ -292,7 +298,8 @@ can_strub_p (cgraph_node *node, bool report = false)
 	return result;
 
       sorry_at (DECL_SOURCE_LOCATION (node->decl),
-		"%qD is not eligible for strub because of attribute %<noipa%>",
+		"%qD is not eligible for %<strub%>"
+		" because of attribute %<noipa%>",
 		node->decl);
     }
 
@@ -352,7 +359,7 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 	return result;
 
       sorry_at (DECL_SOURCE_LOCATION (node->decl),
-		"%qD is not eligible for internal strub"
+		"%qD is not eligible for internal %<strub%>"
 		" because of attribute %<noclone%>",
 		node->decl);
     }
@@ -372,7 +379,7 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 	return result;
 
       sorry_at (gimple_location (e->call_stmt),
-		"internal strub does not support call to %qD",
+		"internal %<strub%> does not support call to %qD",
 		cdecl);
     }
 
@@ -411,7 +418,7 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 	      return result;
 
 	    sorry_at (gimple_location (label_stmt),
-		      "internal strub does not support user labels");
+		      "internal %<strub%> does not support user labels");
 	  }
     }
 
@@ -425,16 +432,16 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 	return result;
 
       sorry_at (DECL_SOURCE_LOCATION (node->decl),
-		"%qD has too many arguments for internal strub",
+		"%qD has too many arguments for internal %<strub%>",
 		node->decl);
     }
 
-  if (result)
-    /* Since we're not changing the function identity proper, just
-       moving its full implementation, we *could* disable
-       fun->cannot_be_copied_reason and/or temporarily drop a noclone
-       attribute.  */
-    gcc_checking_assert (tree_versionable_function_p (node->decl));
+  /* Since we're not changing the function identity proper, just
+     moving its full implementation, we *could* disable
+     fun->cannot_be_copied_reason and/or temporarily drop a noclone
+     attribute.  */
+  gcc_checking_assert (!result || !node->has_gimple_body_p ()
+		       || tree_versionable_function_p (node->decl));
 
   return result;
 }
@@ -489,8 +496,22 @@ strub_callable_builtin_p (cgraph_node *node)
     case BUILT_IN_NONE:
       gcc_unreachable ();
 
-      /* ??? Make all builtins callable.  We wish to make any builtin call the
-	 compiler might introduce on its own callable.  Anything that is
+      /* This temporarily allocates stack for the call, and we can't reasonably
+	 update the watermark for that.  Besides, we don't check the actual call
+	 target, nor its signature, and it seems to be overkill to as much as
+	 try to do so.  */
+    case BUILT_IN_APPLY:
+      return false;
+
+      /* Conversely, this shouldn't be called from within strub contexts, since
+	 the caller may have had its signature modified.  STRUB_INTERNAL is ok,
+	 the call will remain in the STRUB_WRAPPER, and removed from the
+	 STRUB_WRAPPED clone.  */
+    case BUILT_IN_APPLY_ARGS:
+      return false;
+
+      /* ??? Make all other builtins callable.  We wish to make any builtin call
+	 the compiler might introduce on its own callable.  Anything that is
 	 predictable enough as to be known not to allow stack data that should
 	 be strubbed to unintentionally escape to non-strub contexts can be
 	 allowed, and pretty much every builtin appears to fit this description.
@@ -698,7 +719,8 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
 	{
 	  gcc_checking_assert (analyze_body);
 	  error_at (DECL_SOURCE_LOCATION (node->decl),
-		    "%qD requires strub, but no viable strub mode was found",
+		    "%qD requires %<strub%>,"
+		    " but no viable %<strub%> mode was found",
 		    node->decl);
 	  break;
 	}
@@ -751,7 +773,7 @@ set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
 	       && mode == STRUB_INLINABLE))
 	{
 	  error_at (DECL_SOURCE_LOCATION (node->decl),
-		    "strub mode %i selected for %qD, when %i was requested",
+		    "%<strub%> mode %i selected for %qD, when %i was requested",
 		    (int) mode, node->decl,
 		    (int) get_strub_mode_from_attr (attr));
 	  if (node->alias)
@@ -979,7 +1001,7 @@ verify_strub ()
 	if (callee_mode == STRUB_DISABLED
 	    || callee_mode == STRUB_INTERNAL)
 	  error_at (gimple_location (e->call_stmt),
-		    "indirect non-strub call in strub context %qD",
+		    "indirect non-%<strub%> call in %<strub%> context %qD",
 		    node->decl);
       }
 
@@ -987,9 +1009,22 @@ verify_strub ()
       {
 	gcc_checking_assert (!e->indirect_unknown_callee);
 	if (!strub_callable_from_p (e->callee, node))
-	  error_at (gimple_location (e->call_stmt),
-		    "calling non-strub %qD in strub context %qD",
-		    e->callee->decl, node->decl);
+	  {
+	    if (get_strub_mode (e->callee) == STRUB_INLINABLE)
+	      error_at (gimple_location (e->call_stmt),
+			"calling %<always_inline%> %<strub%> %qD"
+			" in non-%<strub%> context %qD",
+			e->callee->decl, node->decl);
+	    else if (fndecl_built_in_p (e->callee->decl, BUILT_IN_APPLY_ARGS)
+		     && get_strub_mode (node) == STRUB_INTERNAL)
+	      /* This is ok, it will be kept in the STRUB_WRAPPER, and removed
+		 from the STRUB_WRAPPED's strub context.  */
+	      continue;
+	    else
+	      error_at (gimple_location (e->call_stmt),
+			"calling non-%<strub%> %qD in %<strub%> context %qD",
+			e->callee->decl, node->decl);
+	  }
       }
   }
 
@@ -1067,27 +1102,25 @@ public:
 
   /* Use a distinct ptr_type_node to denote the watermark, so that we can
      recognize it in arg lists and avoid modifying types twice.  */
-  DEF_TYPE (wmt, build_distinct_type_copy (ptr_type_node))
+  DEF_TYPE (wmt, build_variant_type_copy (ptr_type_node))
 
-  DEF_TYPE (pwmt, build_pointer_type (get_wmt ()))
+  DEF_TYPE (pwmt, build_reference_type (get_wmt ()))
 
   DEF_TYPE (qpwmt,
 	    build_qualified_type (get_pwmt (),
 				  TYPE_QUAL_RESTRICT
-				  | TYPE_QUAL_CONST))
+				  /* | TYPE_QUAL_CONST */))
 
-  DEF_TYPE (pptr, build_pointer_type (ptr_type_node))
-
-  DEF_TYPE (qpptr,
-	    build_qualified_type (get_pptr (),
+  DEF_TYPE (qptr,
+	    build_qualified_type (ptr_type_node,
 				  TYPE_QUAL_RESTRICT
 				  | TYPE_QUAL_CONST))
 
   DEF_TYPE (qpvalst,
-	    build_qualified_type (build_pointer_type
+	    build_qualified_type (build_reference_type
 				  (va_list_type_node),
 				  TYPE_QUAL_RESTRICT
-				  | TYPE_QUAL_CONST))
+				  /* | TYPE_QUAL_CONST */))
 
 #undef DEF_TYPE
 
@@ -1159,12 +1192,13 @@ public:
 
   DEF_IDENT (watermark_ptr)
   DEF_IDENT (va_list_ptr)
-  DEF_IDENT (apply_args_ptr)
+  DEF_IDENT (apply_args)
 
 #undef DEF_IDENT
 
   static inline int adjust_at_calls_type (tree);
   static inline void adjust_at_calls_call (cgraph_edge *, int);
+  static inline void adjust_at_calls_calls (cgraph_node *);
 
   static inline gimple_seq
   call_update_watermark (tree wmptr, cgraph_node *node, profile_count count,
@@ -1324,6 +1358,9 @@ add_call_edges_for_seq (gimple_seq seq, profile_count count)
 static void
 gsi_insert_finally_seq_after_call (gimple_stmt_iterator gsi, gimple_seq seq)
 {
+  if (!seq)
+    return;
+
   gimple *stmt = gsi_stmt (gsi);
 
   gcall *call = is_a <gcall *> (stmt) ? as_a <gcall *> (stmt) : NULL;
@@ -1493,6 +1530,64 @@ ipa_strub_set_mode_for_new_functions ()
   last_cgraph_order = symtab->order;
 }
 
+/* Return FALSE if NODE is a strub context, and TRUE otherwise.  */
+bool
+strub_splittable_p (cgraph_node *node)
+{
+  switch (get_strub_mode (node))
+    {
+    case STRUB_WRAPPED:
+    case STRUB_AT_CALLS:
+    case STRUB_AT_CALLS_OPT:
+    case STRUB_INLINABLE:
+    case STRUB_INTERNAL:
+    case STRUB_WRAPPER:
+      return false;
+
+    case STRUB_CALLABLE:
+    case STRUB_DISABLED:
+      break;
+
+    default:
+      gcc_unreachable ();
+    }
+
+  return true;
+}
+
+/* Return the PARM_DECL of the incoming watermark pointer, if there is one.  */
+tree
+strub_watermark_parm (tree fndecl)
+{
+  switch (get_strub_mode_from_decl (fndecl))
+    {
+    case STRUB_WRAPPED:
+    case STRUB_AT_CALLS:
+    case STRUB_AT_CALLS_OPT:
+      break;
+
+    case STRUB_INTERNAL:
+    case STRUB_WRAPPER:
+    case STRUB_CALLABLE:
+    case STRUB_DISABLED:
+    case STRUB_INLINABLE:
+      return NULL_TREE;
+
+    default:
+      gcc_unreachable ();
+    }
+
+  for (tree parm = DECL_ARGUMENTS (fndecl); parm; parm = DECL_CHAIN (parm))
+    if (TREE_TYPE (parm) == pass_ipa_strub::get_qpwmt ())
+      {
+	gcc_checking_assert (DECL_NAME (parm)
+			     == pass_ipa_strub::get_watermark_ptr ());
+	return parm;
+      }
+
+  gcc_unreachable ();
+}
+
 /* Adjust a STRUB_AT_CALLS function TYPE, adding a watermark pointer if it
    hasn't been added yet.  Return the named argument count.  */
 int
@@ -1507,7 +1602,7 @@ pass_ipa_strub::adjust_at_calls_type (tree type)
   tree qpwmptrt = get_qpwmt ();
   while (*tlist && TREE_VALUE (*tlist) != void_type_node)
     {
-      /* The type has alreayd been adjusted.  */
+      /* The type has already been adjusted.  */
       if (TREE_VALUE (*tlist) == qpwmptrt)
 	return named_args;
       named_args++;
@@ -1581,22 +1676,30 @@ pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
 			 && (TREE_TYPE (gimple_call_arg (ocall, named_args))
 			     == get_pwmt ())));
 
-  /* ??? If it's a (tail?) call within a strub context, maybe pass on
-     the strub watermark instead of wrapping the call.  */
-
-  /* Initialize the watermark before the call.  */
-  tree swm = create_tmp_var (get_wmt (), ".strub.watermark");
-  TREE_ADDRESSABLE (swm) = true;
-  tree swmp = build1 (ADDR_EXPR, get_pwmt (), swm);
+  /* If we're already within a strub context, pass on the incoming watermark
+     pointer, and omit the enter and leave calls around the modified call.  */
+  tree swmp = ((optimize_size || optimize > 2)
+	       ? strub_watermark_parm (e->caller->decl)
+	       : NULL_TREE);
+  bool omit_own_watermark = swmp;
+  tree swm = NULL_TREE;
+  if (!omit_own_watermark)
+    {
+      swm = create_tmp_var (get_wmt (), ".strub.watermark");
+      TREE_ADDRESSABLE (swm) = true;
+      swmp = build1 (ADDR_EXPR, get_pwmt (), swm);
 
-  tree enter = get_enter ();
-  gcall *stptr = gimple_build_call (enter, 1,
-				    unshare_expr (swmp));
-  gsi_insert_before (&gsi, stptr, GSI_SAME_STMT);
+      /* Initialize the watermark before the call.  */
+      tree enter = get_enter ();
+      gcall *stptr = gimple_build_call (enter, 1,
+					unshare_expr (swmp));
+      gsi_insert_before (&gsi, stptr, GSI_SAME_STMT);
 #if !IMPLICIT_CGRAPH_EDGES
-  e->caller->create_edge (cgraph_node::get_create (enter),
-			  stptr, gsi_bb (gsi)->count, false);
+      e->caller->create_edge (cgraph_node::get_create (enter),
+			      stptr, gsi_bb (gsi)->count, false);
 #endif
+    }
+
 
   /* Replace the call with one that passes the swmp argument first.  */
   gcall *wrcall;
@@ -1661,59 +1764,114 @@ pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
   /* Insert the strub code after the call.  */
   gimple_seq seq = NULL;
 
-  {
 #if !ATTR_FNSPEC_DECONST_WATERMARK
-    /* If the call will be assumed to not modify or even read the
-       watermark, make it read and modified ourselves.  */
-    if ((gimple_call_flags (wrcall)
-	 & (ECF_CONST | ECF_PURE | ECF_NOVOPS)))
-      {
-	vec<tree, va_gc> *inputs = NULL;
-	vec<tree, va_gc> *outputs = NULL;
-	vec_safe_push (outputs,
-		       build_tree_list
-		       (build_tree_list
-			(NULL_TREE, build_string (2, "=m")),
-			swm));
-	vec_safe_push (inputs,
-		       build_tree_list
-		       (build_tree_list
-			(NULL_TREE, build_string (1, "m")),
-			swm));
-	gasm *forcemod = gimple_build_asm_vec ("", inputs, outputs,
-					       NULL, NULL);
-	gimple_seq_add_stmt (&seq, forcemod);
-
-	/* If the call will be assumed to not even read the watermark,
-	   make sure it is already in memory before the call.  */
-	if ((gimple_call_flags (wrcall) & ECF_CONST))
-	  {
-	    vec<tree, va_gc> *inputs = NULL;
-	    vec_safe_push (inputs,
-			   build_tree_list
-			   (build_tree_list
-			    (NULL_TREE, build_string (1, "m")),
-			    swm));
-	    gasm *force_store = gimple_build_asm_vec ("", inputs, NULL,
-						      NULL, NULL);
-	    gsi_insert_before (&gsi, force_store, GSI_SAME_STMT);
-	  }
-      }
+  /* If the call will be assumed to not modify or even read the
+     watermark, make it read and modified ourselves.  */
+  if ((gimple_call_flags (wrcall)
+       & (ECF_CONST | ECF_PURE | ECF_NOVOPS)))
+    {
+      if (!swm)
+	swm = build2 (MEM_REF,
+		      TREE_TYPE (TREE_TYPE (swmp)),
+		      swmp,
+		      build_int_cst (TREE_TYPE (swmp), 0));
+
+      vec<tree, va_gc> *inputs = NULL;
+      vec<tree, va_gc> *outputs = NULL;
+      vec_safe_push (outputs,
+		     build_tree_list
+		     (build_tree_list
+		      (NULL_TREE, build_string (2, "=m")),
+		      unshare_expr (swm)));
+      vec_safe_push (inputs,
+		     build_tree_list
+		     (build_tree_list
+		      (NULL_TREE, build_string (1, "m")),
+		      unshare_expr (swm)));
+      gasm *forcemod = gimple_build_asm_vec ("", inputs, outputs,
+					     NULL, NULL);
+      gimple_seq_add_stmt (&seq, forcemod);
+
+      /* If the call will be assumed to not even read the watermark,
+	 make sure it is already in memory before the call.  */
+      if ((gimple_call_flags (wrcall) & ECF_CONST))
+	{
+	  vec<tree, va_gc> *inputs = NULL;
+	  vec_safe_push (inputs,
+			 build_tree_list
+			 (build_tree_list
+			  (NULL_TREE, build_string (1, "m")),
+			  unshare_expr (swm)));
+	  gasm *force_store = gimple_build_asm_vec ("", inputs, NULL,
+						    NULL, NULL);
+	  gsi_insert_before (&gsi, force_store, GSI_SAME_STMT);
+	}
+    }
 #endif
 
-    gcall *sleave = gimple_build_call (get_leave (), 1,
-				       unshare_expr (swmp));
-    gimple_seq_add_stmt (&seq, sleave);
-
-    gassign *clobber = gimple_build_assign (swm,
-					    build_clobber
-					    (TREE_TYPE (swm)));
-    gimple_seq_add_stmt (&seq, clobber);
-  }
+  if (!omit_own_watermark)
+    {
+      gcall *sleave = gimple_build_call (get_leave (), 1,
+					 unshare_expr (swmp));
+      gimple_seq_add_stmt (&seq, sleave);
+
+      gassign *clobber = gimple_build_assign (swm,
+					      build_clobber
+					      (TREE_TYPE (swm)));
+      gimple_seq_add_stmt (&seq, clobber);
+    }
 
   gsi_insert_finally_seq_after_call (gsi, seq);
 }
 
+void
+pass_ipa_strub::adjust_at_calls_calls (cgraph_node *node)
+{
+  /* Adjust unknown-callee indirect calls with STRUB_AT_CALLS types within
+     onode.  */
+  if (node->indirect_calls)
+    {
+      push_cfun (DECL_STRUCT_FUNCTION (node->decl));
+      for (cgraph_edge *e = node->indirect_calls; e; e = e->next_callee)
+	{
+	  gcc_checking_assert (e->indirect_unknown_callee);
+
+	  tree callee_fntype = gimple_call_fntype (e->call_stmt);
+	  enum strub_mode callee_mode
+	    = get_strub_mode_from_type (callee_fntype);
+
+	  if (callee_mode != STRUB_AT_CALLS
+	      && callee_mode != STRUB_AT_CALLS_OPT)
+	    continue;
+
+	  int named_args = adjust_at_calls_type (callee_fntype);
+
+	  adjust_at_calls_call (e, named_args);
+	}
+      pop_cfun ();
+    }
+
+  if (node->callees)
+    {
+      push_cfun (DECL_STRUCT_FUNCTION (node->decl));
+      for (cgraph_edge *e = node->callees; e; e = e->next_callee)
+	{
+	  gcc_checking_assert (!e->indirect_unknown_callee);
+
+	  enum strub_mode callee_mode = get_strub_mode (e->callee);
+
+	  if (callee_mode != STRUB_AT_CALLS
+	      && callee_mode != STRUB_AT_CALLS_OPT)
+	    continue;
+
+	  int named_args = adjust_at_calls_type (TREE_TYPE (e->callee->decl));
+
+	  adjust_at_calls_call (e, named_args);
+	}
+      pop_cfun ();
+    }
+}
+
 unsigned int
 pass_ipa_strub_mode::execute (function *)
 {
@@ -1849,54 +2007,13 @@ pass_ipa_strub::execute (function *)
     if (!onode->has_gimple_body_p ())
       continue;
 
-    /* Adjust unknown-callee indirect calls with STRUB_AT_CALLS types within
-       onode.  */
-    if (onode->indirect_calls)
-      {
-	push_cfun (DECL_STRUCT_FUNCTION (onode->decl));
-	for (cgraph_edge *e = onode->indirect_calls; e; e = e->next_callee)
-	  {
-	    gcc_checking_assert (e->indirect_unknown_callee);
-
-	    tree callee_fntype = gimple_call_fntype (e->call_stmt);
-	    enum strub_mode callee_mode
-	      = get_strub_mode_from_type (callee_fntype);
-
-	    if (callee_mode != STRUB_AT_CALLS
-		&& callee_mode != STRUB_AT_CALLS_OPT)
-	      continue;
-
-	    int named_args = adjust_at_calls_type (callee_fntype);
-
-	    adjust_at_calls_call (e, named_args);
-	  }
-	pop_cfun ();
-      }
-
-    if (onode->callees)
-      {
-	push_cfun (DECL_STRUCT_FUNCTION (onode->decl));
-	for (cgraph_edge *e = onode->callees; e; e = e->next_callee)
-	  {
-	    gcc_checking_assert (!e->indirect_unknown_callee);
-
-	    enum strub_mode callee_mode = get_strub_mode (e->callee);
-
-	    if (callee_mode != STRUB_AT_CALLS
-		&& callee_mode != STRUB_AT_CALLS_OPT)
-	      continue;
-
-	    int named_args = adjust_at_calls_type (TREE_TYPE (e->callee->decl));
-
-	    adjust_at_calls_call (e, named_args);
-	  }
-	pop_cfun ();
-      }
-
     enum strub_mode mode = get_strub_mode (onode);
 
     if (mode != STRUB_INTERNAL)
-      continue;
+      {
+	adjust_at_calls_calls (onode);
+	continue;
+      }
 
 #if 0
     /* Hmm, this is an i386-specific attribute.  Do we need machine-specific
@@ -1909,7 +2026,7 @@ pass_ipa_strub::execute (function *)
     if (!DECL_STRUCT_FUNCTION (onode->decl))
       {
 	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"not splitting struct-less function %qD for stack scrubbing",
+		"not splitting struct-less function %qD for %<strub%>",
 		onode->decl);
 	continue;
       }
@@ -1917,7 +2034,7 @@ pass_ipa_strub::execute (function *)
     if (!onode->lowered)
       {
 	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"not splitting non-lowered function %qD for stack scrubbing",
+		"not splitting non-lowered function %qD for %<strub%>",
 		onode->decl);
 	continue;
       }
@@ -1929,7 +2046,7 @@ pass_ipa_strub::execute (function *)
     if (!tree_versionable_function_p (onode->decl))
       {
 	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"%qD cannot be split for stack scrubbing",
+		"%qD cannot be split for %<strub%>",
 		onode->decl);
 	continue;
       }
@@ -1956,7 +2073,7 @@ pass_ipa_strub::execute (function *)
 	{
 	  ipa_adjusted_param aaadj = {};
 	  aaadj.op = IPA_PARAM_OP_NEW;
-	  aaadj.type = get_qpptr ();
+	  aaadj.type = get_qptr ();
 	  vec_safe_push (nparms, aaadj);
 	}
 
@@ -1982,7 +2099,7 @@ pass_ipa_strub::execute (function *)
     if (!nnode)
       {
 	error_at (DECL_SOURCE_LOCATION (onode->decl),
-		  "failed to split %qD for stack scrubbing",
+		  "failed to split %qD for %<strub%>",
 		  onode->decl);
 	continue;
       }
@@ -1996,6 +2113,8 @@ pass_ipa_strub::execute (function *)
     set_strub_mode_to (onode, STRUB_WRAPPER);
     set_strub_mode_to (nnode, STRUB_WRAPPED);
 
+    adjust_at_calls_calls (nnode);
+
     /* Decide which of the wrapped function's parms we want to turn into
        references to the argument passed to the wrapper.  In general, we want to
        copy small arguments, and avoid copying large ones.  Variable-sized array
@@ -2181,8 +2300,8 @@ pass_ipa_strub::execute (function *)
 	     only of reading because const/pure.  */
 	  if (apply_args)
 	    {
-	      nspec[curlen++] = (no_writes_p ? 'r' : '.');
-	      nspec[curlen++] = (no_writes_p ? 't' : ' ');
+	      nspec[curlen++] = 'r';
+	      nspec[curlen++] = ' ';
 	    }
 	  if (is_stdarg)
 	    {
@@ -2485,24 +2604,24 @@ pass_ipa_strub::execute (function *)
     }
 
     {
-      tree aaptr = NULL_TREE;
+      tree aaval = NULL_TREE;
       tree vaptr = NULL_TREE;
       tree wmptr = NULL_TREE;
       for (tree arg = DECL_ARGUMENTS (nnode->decl); arg; arg = DECL_CHAIN (arg))
 	{
-	  aaptr = vaptr;
+	  aaval = vaptr;
 	  vaptr = wmptr;
 	  wmptr = arg;
 	}
 
       if (!apply_args)
-	aaptr = NULL_TREE;
+	aaval = NULL_TREE;
       /* The trailing args are [apply_args], [va_list_ptr], and
 	 watermark.  If we don't have a va_list_ptr, the penultimate
 	 argument is apply_args.
        */
       else if (!is_stdarg)
-	aaptr = vaptr;
+	aaval = vaptr;
 
       if (!is_stdarg)
 	vaptr = NULL_TREE;
@@ -2522,10 +2641,10 @@ pass_ipa_strub::execute (function *)
 
       if (apply_args)
 	{
-	  DECL_NAME (aaptr) = get_apply_args_ptr ();
-	  DECL_ARTIFICIAL (aaptr) = 1;
-	  DECL_IGNORED_P (aaptr) = 1;
-	  TREE_USED (aaptr) = 1;
+	  DECL_NAME (aaval) = get_apply_args ();
+	  DECL_ARTIFICIAL (aaval) = 1;
+	  DECL_IGNORED_P (aaval) = 1;
+	  TREE_USED (aaval) = 1;
 	}
 
       push_cfun (DECL_STRUCT_FUNCTION (nnode->decl));
@@ -2582,9 +2701,10 @@ pass_ipa_strub::execute (function *)
 	    else if (fndecl && is_stdarg
 		     && fndecl_built_in_p (fndecl, BUILT_IN_VA_START))
 	      {
-		if (builtin_decl_explicit (BUILT_IN_VA_START) != fndecl)
-		  sorry_at (gimple_location (call),
-			    "nonstandard stdarg conventions");
+		/* Using a non-default stdarg ABI makes the function ineligible
+		   for internal strub.  */
+		gcc_checking_assert (builtin_decl_explicit (BUILT_IN_VA_START)
+				     == fndecl);
 		tree bvacopy = builtin_decl_explicit (BUILT_IN_VA_COPY);
 		gimple_call_set_fndecl (call, bvacopy);
 		tree arg = vaptr;
@@ -2607,7 +2727,9 @@ pass_ipa_strub::execute (function *)
 		     && fndecl_built_in_p (fndecl, BUILT_IN_APPLY_ARGS))
 	      {
 		tree lhs = gimple_call_lhs (call);
-		gassign *assign = gimple_build_assign (lhs, aaptr);
+		gimple *assign = (lhs
+				  ? gimple_build_assign (lhs, aaval)
+				  : gimple_build_nop ());
 		gsi_replace (&gsi, assign, true);
 		cgraph_edge::remove (e);
 	      }
@@ -2650,7 +2772,7 @@ pass_ipa_strub::execute (function *)
 
       if (apply_args)
 	{
-	  tree aalst = create_tmp_var (ptr_type_node, ".strub.appargs");
+	  tree aalst = create_tmp_var (ptr_type_node, ".strub.apply_args");
 	  tree bappargs = builtin_decl_explicit (BUILT_IN_APPLY_ARGS);
 	  gcall *appargs = gimple_build_call (bappargs, 0);
 	  gimple_call_set_lhs (appargs, aalst);
diff --git a/gcc/ipa-strub.h b/gcc/ipa-strub.h
index cadbca5002a..7403c139d8c 100644
--- a/gcc/ipa-strub.h
+++ b/gcc/ipa-strub.h
@@ -23,3 +23,10 @@ along with GCC; see the file COPYING3.  If not see
    doesn't have to be called directly by CALLER, but the returned
    value says nothing about intervening functions.  */
 extern bool strub_inlinable_p (cgraph_node *callee, cgraph_node *caller);
+
+/* Return FALSE if NODE is a strub context, and TRUE otherwise.  */
+extern bool strub_splittable_p (cgraph_node *node);
+
+/* Locate and return the watermark_ptr parameter for FNDECL.  If FNDECL is not a
+   strub context, return NULL.  */
+extern tree strub_watermark_parm (tree fndecl);
diff --git a/gcc/testsuite/c-c++-common/strub-O2.c b/gcc/testsuite/c-c++-common/strub-O2.c
index 9cc39e763b1..7848c46d179 100644
--- a/gcc/testsuite/c-c++-common/strub-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-O2.c
@@ -13,4 +13,4 @@ int f() {
 /* { dg-final { scan-rtl-dump-not "strub_enter" "expand" } } */
 /* { dg-final { scan-rtl-dump-not "strub_update" "expand" } } */
 /* { dg-final { scan-rtl-dump "strub_leave" "expand" } } */
-/* { dg-final { scan-rtl-dump "\n\[(\]call_insn\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
+/* { dg-final { scan-rtl-dump "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-O2fni.c b/gcc/testsuite/c-c++-common/strub-O2fni.c
index 51cae845d5f..85a8f76785e 100644
--- a/gcc/testsuite/c-c++-common/strub-O2fni.c
+++ b/gcc/testsuite/c-c++-common/strub-O2fni.c
@@ -1,7 +1,7 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fdump-rtl-expand -fno-inline-functions" } */
+/* { dg-options "-O2 -fstrub=default -fdump-rtl-expand -fno-inline" } */
 
-/* With -fno-inline-functions, none of the strub builtins are inlined.  */
+/* With -fno-inline, none of the strub builtins are inlined.  */
 
 int __attribute__ ((__strub__)) var;
 
@@ -12,4 +12,4 @@ int f() {
 /* { dg-final { scan-rtl-dump "strub_enter" "expand" } } */
 /* { dg-final { scan-rtl-dump "strub_update" "expand" } } */
 /* { dg-final { scan-rtl-dump "strub_leave" "expand" } } */
-/* { dg-final { scan-rtl-dump-not "\n\[(\]call_insn\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
+/* { dg-final { scan-rtl-dump-not "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-O3fni.c b/gcc/testsuite/c-c++-common/strub-O3fni.c
index 8f67b613be8..a2eedfd96b2 100644
--- a/gcc/testsuite/c-c++-common/strub-O3fni.c
+++ b/gcc/testsuite/c-c++-common/strub-O3fni.c
@@ -1,7 +1,7 @@
 /* { dg-do compile } */
 /* { dg-options "-O3 -fstrub=default -fdump-rtl-expand -fno-inline" } */
 
-/* With -fno-inline-functions, none of the strub builtins are inlined.  */
+/* With -fno-inline, none of the strub builtins are inlined.  */
 
 int __attribute__ ((__strub__)) var;
 
@@ -12,4 +12,4 @@ int f() {
 /* { dg-final { scan-rtl-dump "strub_enter" "expand" } } */
 /* { dg-final { scan-rtl-dump "strub_update" "expand" } } */
 /* { dg-final { scan-rtl-dump "strub_leave" "expand" } } */
-/* { dg-final { scan-rtl-dump-not "\n\[(\]call_insn\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
+/* { dg-final { scan-rtl-dump-not "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-Og.c b/gcc/testsuite/c-c++-common/strub-Og.c
index 6f60349573f..e5cb1f60541 100644
--- a/gcc/testsuite/c-c++-common/strub-Og.c
+++ b/gcc/testsuite/c-c++-common/strub-Og.c
@@ -13,4 +13,4 @@ int f() {
 /* { dg-final { scan-rtl-dump-not "strub_enter" "expand" } } */
 /* { dg-final { scan-rtl-dump "strub_update" "expand" } } */
 /* { dg-final { scan-rtl-dump "strub_leave" "expand" } } */
-/* { dg-final { scan-rtl-dump-not "\n\[(\]call_insn\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
+/* { dg-final { scan-rtl-dump-not "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-Os.c b/gcc/testsuite/c-c++-common/strub-Os.c
index 5d1c08a7528..194aacc2c05 100644
--- a/gcc/testsuite/c-c++-common/strub-Os.c
+++ b/gcc/testsuite/c-c++-common/strub-Os.c
@@ -15,4 +15,4 @@ int f() {
 /* { dg-final { scan-rtl-dump-not "strub_enter" "expand" } } */
 /* { dg-final { scan-rtl-dump-not "strub_update" "expand" } } */
 /* { dg-final { scan-rtl-dump "strub_leave" "expand" } } */
-/* { dg-final { scan-rtl-dump-not "\n\[(\]call_insn\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
+/* { dg-final { scan-rtl-dump-not "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-all1.c b/gcc/testsuite/c-c++-common/strub-all1.c
index 54daf84656c..46e84bf6560 100644
--- a/gcc/testsuite/c-c++-common/strub-all1.c
+++ b/gcc/testsuite/c-c++-common/strub-all1.c
@@ -3,9 +3,9 @@
 
 /* h becomes STRUB_CALLABLE, rather than STRUB_INLINABLE, because of the
    strub-enabling -fstrub flag, and gets inlined before pass_ipa_strub.  */
-static void
+static inline void
 __attribute__ ((__always_inline__))
-h() {  /* { dg-warning "might not be inlinable" } */
+h() {
 }
 
 /* g becomes STRUB_AT_CALLS, because of the flag.  */
diff --git a/gcc/testsuite/c-c++-common/strub-apply1.c b/gcc/testsuite/c-c++-common/strub-apply1.c
new file mode 100644
index 00000000000..f180b17f30e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply1.c
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default" } */
+
+void __attribute__ ((__strub__ (3)))
+apply_function (void *args)
+{
+  __builtin_apply (0, args, 0);
+}
+
+void __attribute__ ((__strub__ (2)))
+apply_args (int i, int j, double d)
+{
+  void *args = __builtin_apply_args ();
+  apply_function (args);
+}
diff --git a/gcc/testsuite/c-c++-common/strub-apply2.c b/gcc/testsuite/c-c++-common/strub-apply2.c
new file mode 100644
index 00000000000..379a54b73b7
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply2.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default" } */
+
+extern void __attribute__ ((__strub__))
+apply_function (void *args);
+
+void __attribute__ ((__strub__))
+apply_args (int i, int j, double d) /* { dg-error "selected" } */
+{
+  void *args = __builtin_apply_args (); /* { dg-message "does not support" } */
+  apply_function (args);
+}
diff --git a/gcc/testsuite/c-c++-common/strub-apply3.c b/gcc/testsuite/c-c++-common/strub-apply3.c
new file mode 100644
index 00000000000..9b4786be698
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply3.c
@@ -0,0 +1,8 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default" } */
+
+void __attribute__ ((__strub__))
+apply_function (void *args)
+{
+  __builtin_apply (0, args, 0); /* { dg-error "in .strub. context" } */
+}
diff --git a/gcc/testsuite/c-c++-common/strub-apply4.c b/gcc/testsuite/c-c++-common/strub-apply4.c
new file mode 100644
index 00000000000..409f747743e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply4.c
@@ -0,0 +1,21 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fstrub=default -fdump-ipa-strubm" } */
+
+/* Check that implicit enabling of strub mode selects internal strub when the
+   function uses __builtin_apply_args, that prevents the optimization to
+   at-calls mode.  */
+
+int __attribute__ ((__strub__)) var;
+
+static inline void
+apply_args (int i, int j, double d)
+{
+  var++;
+  __builtin_apply_args ();
+}
+
+void f() {
+  apply_args (1, 2, 3);
+}
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 1 "strubm" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-at-calls1.c b/gcc/testsuite/c-c++-common/strub-at-calls1.c
index 0d1b9fce833..d964b07ae5d 100644
--- a/gcc/testsuite/c-c++-common/strub-at-calls1.c
+++ b/gcc/testsuite/c-c++-common/strub-at-calls1.c
@@ -3,9 +3,9 @@
 
 /* h becomes STRUB_CALLABLE, rather than STRUB_INLINABLE, because of the
    strub-enabling -fstrub flag, and gets inlined before pass_ipa_strub.  */
-static void
+static inline void
 __attribute__ ((__always_inline__))
-h() {  /* { dg-warning "might not be inlinable" } */
+h() {
 }
 
 /* g becomes STRUB_AT_CALLS, because of the flag.  */
diff --git a/gcc/testsuite/c-c++-common/strub-default1.c b/gcc/testsuite/c-c++-common/strub-default1.c
index a1e1803aadc..d579ec62f55 100644
--- a/gcc/testsuite/c-c++-common/strub-default1.c
+++ b/gcc/testsuite/c-c++-common/strub-default1.c
@@ -3,12 +3,12 @@
 
 static int __attribute__ ((__strub__)) var;
 
-/* h becomes STRUB_STRUB_INLINABLE, because of the use of the strub variable,
+/* h becomes STRUB_INLINABLE, because of the use of the strub variable,
    and the always_inline flag.  It would get inlined before pass_ipa_strub, if
    it weren't for the error.  */
-static void
+static inline void
 __attribute__ ((__always_inline__))
-h() {  /* { dg-warning "might not be inlinable" } */
+h() {
   var++;
 }
 
@@ -34,7 +34,7 @@ f() {
 /* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 1 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-3\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]-3\[)\]" 1 "strub" } } */
 /* { dg-final { scan-ipa-dump-times "strub \[(\]-4\[)\]" 1 "strub" } } */
 /* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 1 "strub" } } */
 /* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O1.c b/gcc/testsuite/c-c++-common/strub-defer-O1.c
new file mode 100644
index 00000000000..7b04eea35d9
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-O1.c
@@ -0,0 +1,7 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default -O1" } */
+
+/* Check that a strub function called by another strub function does NOT defer
+   the strubbing to its caller at -O1.  */
+
+#include "strub-defer-O2.c"
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O2.c b/gcc/testsuite/c-c++-common/strub-defer-O2.c
new file mode 100644
index 00000000000..67d96419a5e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-O2.c
@@ -0,0 +1,8 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default -O2" } */
+
+/* Check that a strub function called by another strub function does NOT defer
+   the strubbing to its caller at -O2.  */
+
+#define EXPECT_DEFERRAL !
+#include "strub-defer-O3.c"
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O3.c b/gcc/testsuite/c-c++-common/strub-defer-O3.c
new file mode 100644
index 00000000000..34828d2711e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-O3.c
@@ -0,0 +1,93 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default -O3" } */
+
+/* Check that a strub function called by another strub function defers the
+   strubbing to its caller at -O3.  */
+
+#ifndef EXPECT_DEFERRAL
+# define EXPECT_DEFERRAL
+#endif
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+static inline __attribute__ ((__always_inline__, __strub__ (3)))
+char *
+leak_string (void)
+{
+  /* We use this variable to avoid any stack red zone.  Stack scrubbing covers
+     it, but __builtin_stack_address, that we take as a reference, doesn't, so
+     if e.g. callable() were to store the string in the red zone, we wouldn't
+     find it because it would be outside the range we searched.  */
+  typedef void __attribute__ ((__strub__ (3))) callable_t (char *);
+  callable_t *f = 0;
+
+  char s[sizeof (test_string)];
+  __builtin_strcpy (s, test_string);
+  asm ("" : "+m" (s), "+r" (f));
+
+  if (__builtin_expect (!f, 1))
+    return (char*)__builtin_stack_address ();
+
+  f (s);
+  return 0;
+}
+
+static inline __attribute__ ((__always_inline__, __strub__ (3)))
+int
+look_for_string (char *e)
+{
+  char *p = (char*)__builtin_stack_address ();
+
+  if (p == e)
+    __builtin_abort ();
+
+  if (p > e)
+    {
+      char *q = p;
+      p = e;
+      e = q;
+    }
+
+  for (char *re = e - sizeof (test_string); p < re; p++)
+    for (int i = 0; p[i] == test_string[i]; i++)
+      if (i == sizeof (test_string) - 1)
+	return i;
+
+  return 0;
+}
+
+static __attribute__ ((__strub__ (1), __noinline__, __noclone__))
+char *
+at_calls ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ (1)))
+char *
+deferred_at_calls ()
+{
+  char *ret = at_calls ();
+  if (EXPECT_DEFERRAL !look_for_string (ret))
+    __builtin_abort ();
+  return ret;
+}
+
+static __attribute__ ((__strub__ (2)))
+char *
+deferred_internal ()
+{
+  char *ret = at_calls ();
+  if (EXPECT_DEFERRAL !look_for_string (ret))
+    __builtin_abort ();
+  return ret;
+}
+
+int main ()
+{
+  if (look_for_string (deferred_at_calls ()))
+    __builtin_abort ();
+  if (look_for_string (deferred_internal ()))
+    __builtin_abort ();
+  __builtin_exit (0);
+}
diff --git a/gcc/testsuite/c-c++-common/strub-defer-Os.c b/gcc/testsuite/c-c++-common/strub-defer-Os.c
new file mode 100644
index 00000000000..b273660aea1
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-Os.c
@@ -0,0 +1,7 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default -Os" } */
+
+/* Check that a strub function called by another strub function defers the
+   strubbing to its caller at -Os.  */
+
+#include "strub-defer-O3.c"
diff --git a/gcc/testsuite/c-c++-common/strub-internal1.c b/gcc/testsuite/c-c++-common/strub-internal1.c
index b9bd787df0a..a74658c9ac9 100644
--- a/gcc/testsuite/c-c++-common/strub-internal1.c
+++ b/gcc/testsuite/c-c++-common/strub-internal1.c
@@ -3,9 +3,9 @@
 
 /* h becomes STRUB_CALLABLE, rather than STRUB_INLINABLE, because of the
    strub-enabling -fstrub flag, and gets inlined before pass_ipa_strub.  */
-static void
+static inline void
 __attribute__ ((__always_inline__))
-h() {  /* { dg-warning "might not be inlinable" } */
+h() {
 }
 
 /* g becomes STRUB_INTERNAL because of the flag, and gets split into
diff --git a/gcc/testsuite/c-c++-common/strub-parms1.c b/gcc/testsuite/c-c++-common/strub-parms1.c
new file mode 100644
index 00000000000..0422ccc7f7d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-parms1.c
@@ -0,0 +1,48 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+#include <stdarg.h>
+
+void __attribute__ ((__strub__ (2)))
+small_args (int i, long long l, void *p, void **q, double d, char c)
+{
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid small_args.strub.\[0-9\]* \[(\]int i, long long int l, void \\* p, void \\* \\* q, double d, char c, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " small_args.strub.\[0-9\]* \[(\]\[^&\]*&.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+
+
+struct large_arg {
+  int x[128];
+};
+
+void __attribute__ ((__strub__ (2)))
+large_byref_arg (struct large_arg la)
+{
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid large_byref_arg.strub.\[0-9\]* \[(\]struct large_arg & la, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " large_byref_arg.strub.\[0-9\]* \[(\]&\[^&\]*&.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+
+void __attribute__ ((__strub__ (2)))
+std_arg (int i, ...)
+{
+  va_list vl;
+  va_start (vl, i);
+  va_end (vl);
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid std_arg.strub.\[0-9\]* \[(\]int i, \[^&,\]* &\[^&,\]*.strub.va_list_ptr, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " std_arg.strub.\[0-9\]* \[(\]\[^&\]*&.strub.va_list.\[0-9\]*, &.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_start \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_copy \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_end \\(" 2 "strub" } } */
+
+void __attribute__ ((__strub__ (2)))
+apply_args (int i, int j, double d)
+{
+  __builtin_apply_args ();
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid apply_args.strub.\[0-9\]* \[(\]int i, int j, double d, void \\*\[^&,\]*.strub.apply_args, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " apply_args.strub.\[0-9\]* \[(\]\[^&\]*.strub.apply_args.\[0-9\]*_\[0-9\]*, &.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-parms2.c b/gcc/testsuite/c-c++-common/strub-parms2.c
new file mode 100644
index 00000000000..aaf02a2cd46
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-parms2.c
@@ -0,0 +1,36 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+#include <stdarg.h>
+
+void __attribute__ ((__strub__ (1)))
+small_args (int i, long long l, void *p, void **q, double d, char c)
+{
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid small_args \[(\]int i, long long int l, void \\* p, void \\* \\* q, double d, char c, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+
+
+struct large_arg {
+  int x[128];
+};
+
+void __attribute__ ((__strub__ (1)))
+large_byref_arg (struct large_arg la)
+{
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid large_byref_arg \[(\]struct large_arg la, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+
+void __attribute__ ((__strub__ (1)))
+std_arg (int i, ...)
+{
+  va_list vl;
+  va_start (vl, i);
+  va_end (vl);
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid std_arg \[(\]int i, void \\* &\[^&,\]*.strub.watermark_ptr\[, .]*\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_start \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-not "va_copy \\(" "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_end \\(" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-parms3.c b/gcc/testsuite/c-c++-common/strub-parms3.c
new file mode 100644
index 00000000000..2846098160d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-parms3.c
@@ -0,0 +1,58 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that uses of a strub variable implicitly enables internal strub for
+   publicly-visible functions, and causes the same transformations to their
+   signatures as those in strub-parms1.c.  */
+
+#include <stdarg.h>
+
+int __attribute__ ((__strub__)) var;
+
+void
+small_args (int i, long long l, void *p, void **q, double d, char c)
+{
+  var++;
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid small_args.strub.\[0-9\]* \[(\]int i, long long int l, void \\* p, void \\* \\* q, double d, char c, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " small_args.strub.\[0-9\]* \[(\]\[^&\]*&.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+
+
+struct large_arg {
+  int x[128];
+};
+
+void
+large_byref_arg (struct large_arg la)
+{
+  var++;
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid large_byref_arg.strub.\[0-9\]* \[(\]struct large_arg & la, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " large_byref_arg.strub.\[0-9\]* \[(\]&\[^&\]*&.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+
+void
+std_arg (int i, ...)
+{
+  va_list vl;
+  va_start (vl, i);
+  var++;
+  va_end (vl);
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid std_arg.strub.\[0-9\]* \[(\]int i, \[^&,\]* &\[^&,\]*.strub.va_list_ptr, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " std_arg.strub.\[0-9\]* \[(\]\[^&\]*&.strub.va_list.\[0-9\]*, &.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_start \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_copy \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_end \\(" 2 "strub" } } */
+
+void
+apply_args (int i, int j, double d)
+{
+  var++;
+  __builtin_apply_args ();
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid apply_args.strub.\[0-9\]* \[(\]int i, int j, double d, void \\*\[^&,\]*.strub.apply_args, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " apply_args.strub.\[0-9\]* \[(\]\[^&\]*.strub.apply_args.\[0-9\]*_\[0-9\]*, &.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable1.c b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
new file mode 100644
index 00000000000..45965f275c9
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub -fno-inline" } */
+
+/* Check that strub and non-strub functions can be called from non-strub
+   contexts, and that strub and callable functions can be called from strub
+   contexts.  */
+
+#define OMIT_IMPERMISSIBLE_CALLS 1
+#include "strub-callable2.c"
+
+/* { dg-final { scan-ipa-dump-times "strub_enter" 45 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_update" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_leave" 45 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable2.c b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
new file mode 100644
index 00000000000..38935e3270b
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
@@ -0,0 +1,264 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default" } */
+
+/* Check that impermissible (cross-strub-context) calls are reported.  */
+
+extern int __attribute__ ((__strub__ (3))) xcallable (void);
+extern int __attribute__ ((__strub__ (2))) xinternal (void);
+extern int __attribute__ ((__strub__ (1))) xat_calls (void);
+extern int __attribute__ ((__strub__ (0))) xdisabled (void);
+
+int __attribute__ ((__strub__ (3))) callable (void);
+int __attribute__ ((__strub__ (2))) internal (void);
+int __attribute__ ((__strub__ (1))) at_calls (void);
+int __attribute__ ((__strub__ (0))) disabled (void);
+
+int __attribute__ ((__strub__)) var;
+int var_user (void);
+
+static inline int __attribute__ ((__always_inline__, __strub__ (3)))
+icallable (void);
+static inline int __attribute__ ((__always_inline__, __strub__ (2)))
+iinternal (void);
+static inline int __attribute__ ((__always_inline__, __strub__ (1)))
+iat_calls (void);
+static inline int __attribute__ ((__always_inline__, __strub__ (0)))
+idisabled (void);
+static inline int __attribute__ ((__always_inline__))
+ivar_user (void);
+
+static inline int __attribute__ ((__always_inline__, __strub__ (3)))
+i_callable (void) { return 0; }
+static inline int __attribute__ ((__always_inline__, __strub__ (2)))
+i_internal (void) { return var; }
+static inline int __attribute__ ((__always_inline__, __strub__ (1)))
+i_at_calls (void) { return var; }
+static inline int __attribute__ ((__always_inline__, __strub__ (0)))
+i_disabled (void) { return 0; }
+static inline int __attribute__ ((__always_inline__))
+i_var_user (void) { return var; }
+
+#define CALLS_GOOD_FOR_STRUB_CONTEXT(ISEP)	\
+  do {						\
+    ret += i ## ISEP ## at_calls ();		\
+    ret += i ## ISEP ## internal ();		\
+    ret += i ## ISEP ## var_user ();		\
+  } while (0)
+
+#define CALLS_GOOD_FOR_NONSTRUB_CONTEXT(ISEP)	\
+  do {						\
+    ret += internal ();				\
+    ret += disabled ();				\
+    ret += var_user ();				\
+						\
+    ret += i ## ISEP ## disabled ();		\
+						\
+    ret += xinternal ();			\
+    ret += xdisabled ();			\
+  } while (0)
+
+#define CALLS_GOOD_FOR_EITHER_CONTEXT(ISEP)	\
+  do {						\
+    ret += i ## ISEP ## callable ();		\
+						\
+    ret += callable ();				\
+    ret += at_calls ();				\
+						\
+    ret += xat_calls ();			\
+    ret += xcallable ();			\
+  } while (0)
+
+/* Not a strub context, so it can call anything.
+   Explicitly declared as callable even from within strub contexts.  */
+int __attribute__ ((__strub__ (3)))
+callable (void) {
+  int ret = 0;
+
+  /* CALLS_GOOD_FOR_STRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += iat_calls (); /* { dg-error "in non-.strub. context" } */
+    ret += iinternal (); /* { dg-error "in non-.strub. context" } */
+    ret += ivar_user (); /* { dg-error "in non-.strub. context" } */
+#endif
+  CALLS_GOOD_FOR_EITHER_CONTEXT();
+  CALLS_GOOD_FOR_NONSTRUB_CONTEXT();
+
+  return ret;
+}
+
+/* Internal strubbing means the body is a strub context, so it can only call
+   strub functions, and it's not itself callable from strub functions.  */
+int __attribute__ ((__strub__ (2)))
+internal (void) {
+  int ret = var;
+
+  CALLS_GOOD_FOR_STRUB_CONTEXT();
+  CALLS_GOOD_FOR_EITHER_CONTEXT();
+  /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += internal (); /* { dg-error "in .strub. context" } */
+    ret += disabled (); /* { dg-error "in .strub. context" } */
+    ret += var_user (); /* { dg-error "in .strub. context" } */
+
+    ret += idisabled (); /* { dg-error "in .strub. context" } */
+
+    ret += xinternal (); /* { dg-error "in .strub. context" } */
+    ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+  return ret;
+}
+
+int __attribute__ ((__strub__ (1)))
+at_calls (void) {
+  int ret = var;
+
+  CALLS_GOOD_FOR_STRUB_CONTEXT();
+  CALLS_GOOD_FOR_EITHER_CONTEXT();
+  /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += internal (); /* { dg-error "in .strub. context" } */
+    ret += disabled (); /* { dg-error "in .strub. context" } */
+    ret += var_user (); /* { dg-error "in .strub. context" } */
+
+    ret += idisabled (); /* { dg-error "in .strub. context" } */
+
+    ret += xinternal (); /* { dg-error "in .strub. context" } */
+    ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+  return ret;
+}
+
+int __attribute__ ((__strub__ (0)))
+disabled () {
+  int ret = 0;
+
+  /* CALLS_GOOD_FOR_STRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += iat_calls (); /* { dg-error "in non-.strub. context" } */
+    ret += iinternal (); /* { dg-error "in non-.strub. context" } */
+    ret += ivar_user (); /* { dg-error "in non-.strub. context" } */
+#endif
+  CALLS_GOOD_FOR_EITHER_CONTEXT();
+  CALLS_GOOD_FOR_NONSTRUB_CONTEXT();
+
+  return ret;
+}  
+
+int
+var_user (void) {
+  int ret = var;
+
+  CALLS_GOOD_FOR_STRUB_CONTEXT();
+  CALLS_GOOD_FOR_EITHER_CONTEXT();
+  /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += internal (); /* { dg-error "in .strub. context" } */
+    ret += disabled (); /* { dg-error "in .strub. context" } */
+    ret += var_user (); /* { dg-error "in .strub. context" } */
+
+    ret += idisabled (); /* { dg-error "in .strub. context" } */
+
+    ret += xinternal (); /* { dg-error "in .strub. context" } */
+    ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+  return ret;
+}
+
+int
+icallable (void)
+{
+  int ret = 0;
+
+  /* CALLS_GOOD_FOR_STRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += i_at_calls (); /* { dg-error "in non-.strub. context" } */
+    ret += i_internal (); /* { dg-error "in non-.strub. context" } */
+    ret += i_var_user (); /* { dg-error "in non-.strub. context" } */
+#endif
+  CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+  CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_);
+
+  return ret;
+}
+
+int
+iinternal (void) {
+  int ret = var;
+
+  CALLS_GOOD_FOR_STRUB_CONTEXT(_);
+  CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+  /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += internal (); /* { dg-error "in .strub. context" } */
+    ret += disabled (); /* { dg-error "in .strub. context" } */
+    ret += var_user (); /* { dg-error "in .strub. context" } */
+
+    ret += i_disabled (); /* { dg-error "in .strub. context" } */
+
+    ret += xinternal (); /* { dg-error "in .strub. context" } */
+    ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+  return ret;
+}
+
+int
+iat_calls (void) {
+  int ret = var;
+
+  CALLS_GOOD_FOR_STRUB_CONTEXT(_);
+  CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+  /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += internal (); /* { dg-error "in .strub. context" } */
+    ret += disabled (); /* { dg-error "in .strub. context" } */
+    ret += var_user (); /* { dg-error "in .strub. context" } */
+
+    ret += i_disabled (); /* { dg-error "in .strub. context" } */
+
+    ret += xinternal (); /* { dg-error "in .strub. context" } */
+    ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+  return ret;
+}
+
+int
+idisabled () {
+  int ret = 0;
+
+  /* CALLS_GOOD_FOR_STRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += i_at_calls (); /* { dg-error "in non-.strub. context" } */
+    ret += i_internal (); /* { dg-error "in non-.strub. context" } */
+    ret += i_var_user (); /* { dg-error "in non-.strub. context" } */
+#endif
+  CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+  CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_);
+
+  return ret;
+}  
+
+int
+ivar_user (void) {
+  int ret = var;
+
+  CALLS_GOOD_FOR_STRUB_CONTEXT(_);
+  CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+  /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += internal (); /* { dg-error "in .strub. context" } */
+    ret += disabled (); /* { dg-error "in .strub. context" } */
+    ret += var_user (); /* { dg-error "in .strub. context" } */
+
+    ret += i_disabled (); /* { dg-error "in .strub. context" } */
+
+    ret += xinternal (); /* { dg-error "in .strub. context" } */
+    ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+  return ret;
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const1.c b/gcc/testsuite/c-c++-common/torture/strub-const1.c
new file mode 100644
index 00000000000..100fb0c59a9
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const1.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub const function call, we issue an asm statement
+   to make sure the watermark passed to it is held in memory before the call,
+   and another to make sure it is not assumed to be unchanged.  */
+
+int __attribute__ ((__strub__, __const__))
+f() {
+  return 0;
+}
+
+int
+g() {
+  return f();
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const2.c b/gcc/testsuite/c-c++-common/torture/strub-const2.c
new file mode 100644
index 00000000000..9e818ac9748
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const2.c
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-const function call, we issue an
+   asm statement to make sure the watermark passed to it is held in memory
+   before the call, and another to make sure it is not assumed to be
+   unchanged.  */
+
+int __attribute__ ((__strub__))
+#if ! __OPTIMIZE__
+__attribute__ ((__const__))
+#endif
+f() {
+  return 0;
+}
+
+int
+g() {
+  return f();
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const3.c b/gcc/testsuite/c-c++-common/torture/strub-const3.c
new file mode 100644
index 00000000000..d40e8aa45cb
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const3.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub const wrapping call, we issue an asm statement
+   to make sure the watermark passed to it is held in memory before the call,
+   and another to make sure it is not assumed to be unchanged.  */
+
+int __attribute__ ((__strub__ (2), __const__))
+f() {
+  return 0;
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const4.c b/gcc/testsuite/c-c++-common/torture/strub-const4.c
new file mode 100644
index 00000000000..d4cbdaf10f3
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const4.c
@@ -0,0 +1,17 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-const wrapping call, we issue an
+   asm statement to make sure the watermark passed to it is held in memory
+   before the call, and another to make sure it is not assumed to be
+   unchanged.  */
+
+int __attribute__ ((__strub__ (2)))
+#if ! __OPTIMIZE__
+__attribute__ ((__const__))
+#endif
+f() {
+  return 0;
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data5.c b/gcc/testsuite/c-c++-common/torture/strub-data5.c
index 251790d4bbb..07e25af9c53 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data5.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data5.c
@@ -1,15 +1,13 @@
 /* { dg-do compile } */
 /* { dg-options "-fstrub=default -Werror" } */
 
-/* The pointer itself is a strub variable, that would enable internal strubbing
-   if its value was used.  Here, it's only overwritten, so no strub.  */
 typedef int __attribute__ ((__strub__)) strub_int;
 strub_int *ptr;
 
 int *f () {
-  return ptr; /* { dg-warn "incompatible" } */
+  return ptr; /* { dg-warning "incompatible" } */
 }
 
 strub_int *g () {
-  return f (); /* { dg-warn "incompatible" } */
+  return f (); /* { dg-warning "incompatible" } */
 }
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure1.c b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
new file mode 100644
index 00000000000..cb223da6efc
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub pure function call, we issue an asm statement
+   to make sure the watermark passed to it is not assumed to be unchanged.  */
+
+int __attribute__ ((__strub__, __pure__))
+f() {
+  static int i; /* Stop it from being detected as const.  */
+  return i;
+}
+
+int
+g() {
+  return f();
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure2.c b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
new file mode 100644
index 00000000000..67d1434b1f8
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-pure function call, we issue an asm
+   statement to make sure the watermark passed to it is not assumed to be
+   unchanged.  */
+
+int __attribute__ ((__strub__))
+#if ! __OPTIMIZE__ /* At -O0, implicit pure detection doesn't run.  */
+__attribute__ ((__pure__))
+#endif
+f() {
+  static int i; /* Stop it from being detected as const.  */
+  return i;
+}
+
+int
+g() {
+  return f();
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure3.c b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
new file mode 100644
index 00000000000..59f02ea901f
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub pure wrapping call, we issue an asm statement
+   to make sure the watermark passed to it is not assumed to be unchanged.  */
+
+int __attribute__ ((__strub__ (2), __pure__))
+f() {
+  static int i; /* Stop it from being detected as const.  */
+  return i;
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure4.c b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
new file mode 100644
index 00000000000..973e909217d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
@@ -0,0 +1,17 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-pure wrapping call, we issue an asm
+   statement to make sure the watermark passed to it is not assumed to be
+   unchanged.  */
+
+int __attribute__ ((__strub__ (2)))
+#if ! __OPTIMIZE__ /* At -O0, implicit pure detection doesn't run.  */
+__attribute__ ((__pure__))
+#endif
+f() {
+  static int i; /* Stop it from being detected as const.  */
+  return i;
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run1.c b/gcc/testsuite/c-c++-common/torture/strub-run1.c
new file mode 100644
index 00000000000..828a4cc2998
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run1.c
@@ -0,0 +1,85 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default" } */
+
+/* Check that a non-strub function leaves a string behind in the stack, and that
+   equivalent strub functions don't.  */
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+static inline __attribute__ ((__always_inline__, __strub__ (3)))
+char *
+leak_string (void)
+{
+  /* We use this variable to avoid any stack red zone.  Stack scrubbing covers
+     it, but __builtin_stack_address, that we take as a reference, doesn't, so
+     if e.g. callable() were to store the string in the red zone, we wouldn't
+     find it because it would be outside the range we searched.  */
+  typedef void __attribute__ ((__strub__ (3))) callable_t (char *);
+  callable_t *f = 0;
+
+  char s[sizeof (test_string)];
+  __builtin_strcpy (s, test_string);
+  asm ("" : "+m" (s), "+r" (f));
+
+  if (__builtin_expect (!f, 1))
+    return __builtin_stack_address ();
+
+  f (s);
+  return 0;
+}
+
+static inline __attribute__ ((__always_inline__))
+int
+look_for_string (char *e)
+{
+  char *p = __builtin_stack_address ();
+
+  if (p == e)
+    __builtin_abort ();
+
+  if (p > e)
+    {
+      char *q = p;
+      p = e;
+      e = q;
+    }
+
+  for (char *re = e - sizeof (test_string); p < re; p++)
+    for (int i = 0; p[i] == test_string[i]; i++)
+      if (i == sizeof (test_string) - 1)
+	return i;
+
+  return 0;
+}
+
+static __attribute__ ((__noinline__, __noclone__))
+char *
+callable ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ (1)))
+char *
+at_calls ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ (2)))
+char *
+internal ()
+{
+  return leak_string ();
+}
+
+int main ()
+{
+  if (!look_for_string (callable ()))
+    __builtin_abort ();
+  if (look_for_string (at_calls ()))
+    __builtin_abort ();
+  if (look_for_string (internal ()))
+    __builtin_abort ();
+  __builtin_exit (0);
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run2.c b/gcc/testsuite/c-c++-common/torture/strub-run2.c
new file mode 100644
index 00000000000..5794b694b2d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run2.c
@@ -0,0 +1,75 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default" } */
+
+/* Check that a non-strub function leaves a string behind in the stack, and that
+   equivalent strub functions don't.  */
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+static inline __attribute__ ((__always_inline__, __strub__ (3)))
+char *
+leak_string (void)
+{
+  int len = sizeof (test_string);
+  asm ("" : "+rm" (len));
+  char s[len];
+  __builtin_strcpy (s, test_string);
+  asm ("" : "+m" (s));
+  return __builtin_stack_address ();
+}
+
+static inline __attribute__ ((__always_inline__))
+int
+look_for_string (char *e)
+{
+  char *p = __builtin_stack_address ();
+
+  if (p == e)
+    __builtin_abort ();
+
+  if (p > e)
+    {
+      char *q = p;
+      p = e;
+      e = q;
+    }
+
+  for (char *re = e - sizeof (test_string); p < re; p++)
+    for (int i = 0; p[i] == test_string[i]; i++)
+      if (i == sizeof (test_string) - 1)
+	return i;
+
+  return 0;
+}
+
+static __attribute__ ((__noinline__, __noclone__))
+char *
+callable ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ (1)))
+char *
+at_calls ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ (2)))
+char *
+internal ()
+{
+  return leak_string ();
+}
+
+int main ()
+{
+  if (!look_for_string (callable ()))
+    __builtin_abort ();
+  if (look_for_string (at_calls ()))
+    __builtin_abort ();
+  if (look_for_string (internal ()))
+    __builtin_abort ();
+  __builtin_exit (0);
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run3.c b/gcc/testsuite/c-c++-common/torture/strub-run3.c
new file mode 100644
index 00000000000..7da79055959
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run3.c
@@ -0,0 +1,75 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default" } */
+/* { dg-require-effective-target alloca } */
+
+/* Check that a non-strub function leaves a string behind in the stack, and that
+   equivalent strub functions don't.  */
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+static inline __attribute__ ((__always_inline__, __strub__ (3)))
+char *
+leak_string (void)
+{
+  int len = sizeof (test_string);
+  char *s = __builtin_alloca (len);
+  __builtin_strcpy (s, test_string);
+  asm ("" : "+m" (s));
+  return __builtin_stack_address ();
+}
+
+static inline __attribute__ ((__always_inline__))
+int
+look_for_string (char *e)
+{
+  char *p = __builtin_stack_address ();
+
+  if (p == e)
+    __builtin_abort ();
+
+  if (p > e)
+    {
+      char *q = p;
+      p = e;
+      e = q;
+    }
+
+  for (char *re = e - sizeof (test_string); p < re; p++)
+    for (int i = 0; p[i] == test_string[i]; i++)
+      if (i == sizeof (test_string) - 1)
+	return i;
+
+  return 0;
+}
+
+static __attribute__ ((__noinline__, __noclone__))
+char *
+callable ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ (1)))
+char *
+at_calls ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ (2)))
+char *
+internal ()
+{
+  return leak_string ();
+}
+
+int main ()
+{
+  if (!look_for_string (callable ()))
+    __builtin_abort ();
+  if (look_for_string (at_calls ()))
+    __builtin_abort ();
+  if (look_for_string (internal ()))
+    __builtin_abort ();
+  __builtin_exit (0);
+}
diff --git a/gcc/testsuite/g++.dg/wrappers/strub1.C b/gcc/testsuite/g++.dg/strub-run1.C
similarity index 90%
rename from gcc/testsuite/g++.dg/wrappers/strub1.C
rename to gcc/testsuite/g++.dg/strub-run1.C
index a474a929649..754291eaa01 100644
--- a/gcc/testsuite/g++.dg/wrappers/strub1.C
+++ b/gcc/testsuite/g++.dg/strub-run1.C
@@ -1,4 +1,5 @@
 // { dg-do run }
+// { dg-options "-fstrub=internal" }
 
 // Check that we don't get extra copies.
 
diff --git a/gcc/testsuite/g++.dg/wrappers/strub2.C b/gcc/testsuite/g++.dg/wrappers/strub2.C
deleted file mode 100644
index 25a62166448..00000000000
--- a/gcc/testsuite/g++.dg/wrappers/strub2.C
+++ /dev/null
@@ -1,22 +0,0 @@
-// { dg-do run }
-
-// This doesn't really test anything yet.  We should mark the
-// variables as requiring strubbing, and somehow check that the
-// wrapped functions take the parameter by reference.
-
-struct T {
-  char d[32 * sizeof(void*)];
-};
-
-T foo (T q) { asm ("" : : "m"(q)); return q; }
-T bar (T p) { return foo (p); }
-
-T tmp;
-T tmp2;
-
-int main () {
-  __builtin_memset (&tmp, 0x55, sizeof (tmp));
-  tmp2 = bar (tmp);
-  if (__builtin_memcmp (&tmp, &tmp2, sizeof (tmp)))
-    __builtin_abort ();
-}
diff --git a/gcc/testsuite/g++.dg/wrappers/strub3.C b/gcc/testsuite/g++.dg/wrappers/strub3.C
deleted file mode 100644
index e1b51cd0399..00000000000
--- a/gcc/testsuite/g++.dg/wrappers/strub3.C
+++ /dev/null
@@ -1,22 +0,0 @@
-// { dg-do run }
-
-// This doesn't really test anything yet.  We should mark the
-// variables as requiring strubbing, and somehow check that the
-// wrapped functions take the parameter by reference.
-
-struct T {
-  char d[32 * sizeof(void*)];
-};
-
-static T foo (T q) { asm ("" : : "m"(q)); return q; }
-static T bar (T p) { return foo (p); }
-
-T tmp;
-T tmp2;
-
-int main () {
-  __builtin_memset (&tmp, 0x55, sizeof (tmp));
-  tmp2 = bar (tmp);
-  if (__builtin_memcmp (&tmp, &tmp2, sizeof (tmp)))
-    __builtin_abort ();
-}
diff --git a/gcc/testsuite/g++.dg/wrappers/strub4.C b/gcc/testsuite/g++.dg/wrappers/strub4.C
deleted file mode 100644
index d021fca88e4..00000000000
--- a/gcc/testsuite/g++.dg/wrappers/strub4.C
+++ /dev/null
@@ -1,18 +0,0 @@
-// { dg-do run }
-
-namespace
-{
-  class foo
-  {
-  public:
-    foo();
-  };
-
-  foo::foo() {}
-
-  foo bar;
-}
-
-int main()
-{
-}


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [gcc(refs/users/aoliva/heads/strub)] more tests, red zones, and deferred strubbing
@ 2021-08-05 18:05 Alexandre Oliva
  0 siblings, 0 replies; 6+ messages in thread
From: Alexandre Oliva @ 2021-08-05 18:05 UTC (permalink / raw)
  To: gcc-cvs

https://gcc.gnu.org/g:189da193559d4dcd74a29439dd247d019464798e

commit 189da193559d4dcd74a29439dd247d019464798e
Author: Alexandre Oliva <oliva@adacore.com>
Date:   Thu Aug 5 11:03:49 2021 -0300

    more tests, red zones, and deferred strubbing

Diff:
---
 gcc/builtins.c                                     |  86 ++++-
 gcc/ipa-strub.c                                    | 409 +++++++++++++--------
 gcc/ipa-strub.h                                    |   1 +
 gcc/testsuite/c-c++-common/strub-O2.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O2fni.c           |   6 +-
 gcc/testsuite/c-c++-common/strub-O3fni.c           |   4 +-
 gcc/testsuite/c-c++-common/strub-Og.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-Os.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-all1.c            |   4 +-
 gcc/testsuite/c-c++-common/strub-apply1.c          |  15 +
 gcc/testsuite/c-c++-common/strub-apply2.c          |  12 +
 gcc/testsuite/c-c++-common/strub-apply3.c          |   8 +
 gcc/testsuite/c-c++-common/strub-apply4.c          |  21 ++
 gcc/testsuite/c-c++-common/strub-at-calls1.c       |   4 +-
 gcc/testsuite/c-c++-common/strub-default1.c        |   8 +-
 gcc/testsuite/c-c++-common/strub-defer-O1.c        |   7 +
 gcc/testsuite/c-c++-common/strub-defer-O2.c        |   8 +
 gcc/testsuite/c-c++-common/strub-defer-O3.c        |  93 +++++
 gcc/testsuite/c-c++-common/strub-defer-Os.c        |   7 +
 gcc/testsuite/c-c++-common/strub-internal1.c       |   4 +-
 gcc/testsuite/c-c++-common/strub-parms1.c          |  48 +++
 gcc/testsuite/c-c++-common/strub-parms2.c          |  36 ++
 gcc/testsuite/c-c++-common/strub-parms3.c          |  58 +++
 .../c-c++-common/torture/strub-callable1.c         |  13 +
 .../c-c++-common/torture/strub-callable2.c         | 264 +++++++++++++
 gcc/testsuite/c-c++-common/torture/strub-const1.c  |  18 +
 gcc/testsuite/c-c++-common/torture/strub-const2.c  |  22 ++
 gcc/testsuite/c-c++-common/torture/strub-const3.c  |  13 +
 gcc/testsuite/c-c++-common/torture/strub-const4.c  |  17 +
 gcc/testsuite/c-c++-common/torture/strub-data5.c   |   6 +-
 gcc/testsuite/c-c++-common/torture/strub-pure1.c   |  18 +
 gcc/testsuite/c-c++-common/torture/strub-pure2.c   |  22 ++
 gcc/testsuite/c-c++-common/torture/strub-pure3.c   |  13 +
 gcc/testsuite/c-c++-common/torture/strub-pure4.c   |  17 +
 gcc/testsuite/c-c++-common/torture/strub-run1.c    |  85 +++++
 gcc/testsuite/c-c++-common/torture/strub-run2.c    |  75 ++++
 gcc/testsuite/c-c++-common/torture/strub-run3.c    |  75 ++++
 .../g++.dg/{wrappers/strub1.C => strub-run1.C}     |   1 +
 gcc/testsuite/g++.dg/wrappers/strub2.C             |  22 --
 gcc/testsuite/g++.dg/wrappers/strub3.C             |  22 --
 gcc/testsuite/g++.dg/wrappers/strub4.C             |  18 -
 41 files changed, 1324 insertions(+), 242 deletions(-)

diff --git a/gcc/builtins.c b/gcc/builtins.c
index f387d93974f..87b17c75ee3 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -70,6 +70,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "gimple-fold.h"
 #include "intl.h"
 #include "file-prefix-map.h" /* remap_macro_filename()  */
+#include "ipa-strub.h" /* find_watermark_parm()  */
 #include "gomp-constants.h"
 #include "omp-general.h"
 #include "tree-dfa.h"
@@ -7926,7 +7927,23 @@ expand_builtin_strub_enter (tree exp)
   if (optimize < 1 || flag_no_inline)
     return NULL_RTX;
 
-  rtx stktop = expand_builtin_stack_address ();
+  rtx stktop = NULL_RTX;
+
+#if 1 || defined RED_ZONE_SIZE
+  if (tree wmptr = (optimize
+		    ? find_watermark_parm (current_function_decl)
+		    : NULL_TREE))
+    {
+      tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+      tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+				 build_int_cst (TREE_TYPE (wmptr), 0));
+      rtx wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+      stktop = force_reg (ptr_mode, wmark);
+    }
+#endif
+
+  if (!stktop)
+    stktop = expand_builtin_stack_address ();
 
   tree wmptr = CALL_EXPR_ARG (exp, 0);
   tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
@@ -7952,6 +7969,52 @@ expand_builtin_strub_update (tree exp)
 
   rtx stktop = expand_builtin_stack_address ();
 
+#ifdef RED_ZONE_SIZE
+  /* Here's how the strub enter, update and leave functions deal with red zones.
+
+     If it weren't for red zones, update, called from within a strub context,
+     would bump the watermark to the top of the stack.  Enter and leave, running
+     in the caller, would use the caller's top of stack address both to
+     initialize the watermark passed to the callee, and to start strubbing the
+     stack afterwards.
+
+     Ideally, we'd update the watermark so as to cover the used amount of red
+     zone, and strub starting at the caller's other end of the (presumably
+     unused) red zone.  Normally, only leaf functions use the red zone, but at
+     this point we can't tell whether a function is a leaf, nor can we tell how
+     much of the red zone it uses.  Furthermore, some strub contexts may have
+     been inlined so that update and leave are called from the same stack frame,
+     and the strub builtins may all have been inlined, turning a strub function
+     into a leaf.
+
+     So cleaning the range from the caller's stack pointer (one end of the red
+     zone) to the (potentially inlined) callee's (other end of the) red zone
+     could scribble over the caller's own red zone.
+
+     We avoid this possibility by arranging for callers that are strub contexts
+     to use their own watermark as the strub starting point.  So, if A calls B,
+     and B calls C, B will tell A to strub up to the end of B's red zone, and
+     will strub itself only the part of C's stack frame and red zone that
+     doesn't overlap with B's.  With that, we don't need to know who's leaf and
+     who isn't: inlined calls will shrink their strub window to zero, each
+     remaining call will strub some portion of the stack, and eventually the
+     strub context will return to a caller that isn't a strub context itself,
+     that will therefore use its own stack pointer as the strub starting point.
+     It's not a leaf, because strub contexts can't be inlined into non-strub
+     contexts, so it doesn't use the red zone, and it will therefore correctly
+     strub up the callee's stack frame up to the end of the callee's red zone.
+     Neat!  */
+  if (true /* (flags_from_decl_or_type (current_function_decl) & ECF_LEAF) */)
+    {
+      poly_int64 red_zone_size = RED_ZONE_SIZE;
+#if STACK_GROWS_DOWNWARD
+      red_zone_size = -red_zone_size;
+#endif
+      stktop = plus_constant (ptr_mode, stktop, red_zone_size);
+      stktop = force_reg (ptr_mode, stktop);
+    }
+#endif
+
   tree wmptr = CALL_EXPR_ARG (exp, 0);
   tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
   tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
@@ -7982,7 +8045,23 @@ expand_builtin_strub_leave (tree exp)
   if (optimize < 2 || flag_no_inline)
     return NULL_RTX;
 
-  rtx stktop = expand_builtin_stack_address ();
+  rtx stktop = NULL_RTX;
+
+#if 1 || defined RED_ZONE_SIZE
+  if (tree wmptr = (optimize
+		    ? find_watermark_parm (current_function_decl)
+		    : NULL_TREE))
+    {
+      tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+      tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+				 build_int_cst (TREE_TYPE (wmptr), 0));
+      rtx wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+      stktop = force_reg (ptr_mode, wmark);
+    }
+#endif
+
+  if (!stktop)
+    stktop = expand_builtin_stack_address ();
 
   tree wmptr = CALL_EXPR_ARG (exp, 0);
   tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
@@ -8000,6 +8079,9 @@ expand_builtin_strub_leave (tree exp)
   rtx end = stktop;
 #endif
 
+  /* We're going to modify it, so make sure it's not e.g. the stack pointer.  */
+  base = copy_to_reg (base);
+
   rtx_code_label *done = gen_label_rtx ();
   do_compare_rtx_and_jump (base, end, LT, STACK_UNSIGNED,
 			   ptr_mode, NULL_RTX, done, NULL,
diff --git a/gcc/ipa-strub.c b/gcc/ipa-strub.c
index e302f0ec1c5..9aef0de577d 100644
--- a/gcc/ipa-strub.c
+++ b/gcc/ipa-strub.c
@@ -210,10 +210,16 @@ get_strub_mode_from_attr (tree strub_attr, bool var_p = false)
   return mode;
 }
 
+static enum strub_mode
+get_strub_mode_from_decl (tree fndecl)
+{
+  return get_strub_mode_from_attr (get_strub_attr_from_decl (fndecl));
+}
+
 static enum strub_mode
 get_strub_mode (cgraph_node *node)
 {
-  return get_strub_mode_from_attr (get_strub_attr_from_decl (node->decl));
+  return get_strub_mode_from_decl (node->decl);
 }
 
 static enum strub_mode
@@ -263,7 +269,7 @@ calls_builtin_apply_args_p (cgraph_node *node, bool report = false)
 	break;
 
       sorry_at (gimple_location (e->call_stmt),
-		"at-calls strub does not support call to %qD",
+		"at-calls %<strub%> does not support call to %qD",
 		cdecl);
     }
 
@@ -292,7 +298,8 @@ can_strub_p (cgraph_node *node, bool report = false)
 	return result;
 
       sorry_at (DECL_SOURCE_LOCATION (node->decl),
-		"%qD is not eligible for strub because of attribute %<noipa%>",
+		"%qD is not eligible for %<strub%>"
+		" because of attribute %<noipa%>",
 		node->decl);
     }
 
@@ -352,7 +359,7 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 	return result;
 
       sorry_at (DECL_SOURCE_LOCATION (node->decl),
-		"%qD is not eligible for internal strub"
+		"%qD is not eligible for internal %<strub%>"
 		" because of attribute %<noclone%>",
 		node->decl);
     }
@@ -372,7 +379,7 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 	return result;
 
       sorry_at (gimple_location (e->call_stmt),
-		"internal strub does not support call to %qD",
+		"internal %<strub%> does not support call to %qD",
 		cdecl);
     }
 
@@ -411,7 +418,7 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 	      return result;
 
 	    sorry_at (gimple_location (label_stmt),
-		      "internal strub does not support user labels");
+		      "internal %<strub%> does not support user labels");
 	  }
     }
 
@@ -425,16 +432,16 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 	return result;
 
       sorry_at (DECL_SOURCE_LOCATION (node->decl),
-		"%qD has too many arguments for internal strub",
+		"%qD has too many arguments for internal %<strub%>",
 		node->decl);
     }
 
-  if (result)
-    /* Since we're not changing the function identity proper, just
-       moving its full implementation, we *could* disable
-       fun->cannot_be_copied_reason and/or temporarily drop a noclone
-       attribute.  */
-    gcc_checking_assert (tree_versionable_function_p (node->decl));
+  /* Since we're not changing the function identity proper, just
+     moving its full implementation, we *could* disable
+     fun->cannot_be_copied_reason and/or temporarily drop a noclone
+     attribute.  */
+  gcc_checking_assert (!result || !node->has_gimple_body_p ()
+		       || tree_versionable_function_p (node->decl));
 
   return result;
 }
@@ -489,8 +496,22 @@ strub_callable_builtin_p (cgraph_node *node)
     case BUILT_IN_NONE:
       gcc_unreachable ();
 
-      /* ??? Make all builtins callable.  We wish to make any builtin call the
-	 compiler might introduce on its own callable.  Anything that is
+      /* This temporarily allocates stack for the call, and we can't reasonably
+	 update the watermark for that.  Besides, we don't check the actual call
+	 target, nor its signature, and it seems to be overkill to as much as
+	 try to do so.  */
+    case BUILT_IN_APPLY:
+      return false;
+
+      /* Conversely, this shouldn't be called from within strub contexts, since
+	 the caller may have had its signature modified.  STRUB_INTERNAL is ok,
+	 the call will remain in the STRUB_WRAPPER, and removed from the
+	 STRUB_WRAPPED clone.  */
+    case BUILT_IN_APPLY_ARGS:
+      return false;
+
+      /* ??? Make all other builtins callable.  We wish to make any builtin call
+	 the compiler might introduce on its own callable.  Anything that is
 	 predictable enough as to be known not to allow stack data that should
 	 be strubbed to unintentionally escape to non-strub contexts can be
 	 allowed, and pretty much every builtin appears to fit this description.
@@ -698,7 +719,8 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
 	{
 	  gcc_checking_assert (analyze_body);
 	  error_at (DECL_SOURCE_LOCATION (node->decl),
-		    "%qD requires strub, but no viable strub mode was found",
+		    "%qD requires %<strub%>,"
+		    " but no viable %<strub%> mode was found",
 		    node->decl);
 	  break;
 	}
@@ -751,7 +773,7 @@ set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
 	       && mode == STRUB_INLINABLE))
 	{
 	  error_at (DECL_SOURCE_LOCATION (node->decl),
-		    "strub mode %i selected for %qD, when %i was requested",
+		    "%<strub%> mode %i selected for %qD, when %i was requested",
 		    (int) mode, node->decl,
 		    (int) get_strub_mode_from_attr (attr));
 	  if (node->alias)
@@ -979,7 +1001,7 @@ verify_strub ()
 	if (callee_mode == STRUB_DISABLED
 	    || callee_mode == STRUB_INTERNAL)
 	  error_at (gimple_location (e->call_stmt),
-		    "indirect non-strub call in strub context %qD",
+		    "indirect non-%<strub%> call in %<strub%> context %qD",
 		    node->decl);
       }
 
@@ -987,9 +1009,22 @@ verify_strub ()
       {
 	gcc_checking_assert (!e->indirect_unknown_callee);
 	if (!strub_callable_from_p (e->callee, node))
-	  error_at (gimple_location (e->call_stmt),
-		    "calling non-strub %qD in strub context %qD",
-		    e->callee->decl, node->decl);
+	  {
+	    if (get_strub_mode (e->callee) == STRUB_INLINABLE)
+	      error_at (gimple_location (e->call_stmt),
+			"calling %<always_inline%> %<strub%> %qD"
+			" in non-%<strub%> context %qD",
+			e->callee->decl, node->decl);
+	    else if (fndecl_built_in_p (e->callee->decl, BUILT_IN_APPLY_ARGS)
+		     && get_strub_mode (node) == STRUB_INTERNAL)
+	      /* This is ok, it will be kept in the STRUB_WRAPPER, and removed
+		 from the STRUB_WRAPPED's strub context.  */
+	      continue;
+	    else
+	      error_at (gimple_location (e->call_stmt),
+			"calling non-%<strub%> %qD in %<strub%> context %qD",
+			e->callee->decl, node->decl);
+	  }
       }
   }
 
@@ -1067,27 +1102,25 @@ public:
 
   /* Use a distinct ptr_type_node to denote the watermark, so that we can
      recognize it in arg lists and avoid modifying types twice.  */
-  DEF_TYPE (wmt, build_distinct_type_copy (ptr_type_node))
+  DEF_TYPE (wmt, build_variant_type_copy (ptr_type_node))
 
-  DEF_TYPE (pwmt, build_pointer_type (get_wmt ()))
+  DEF_TYPE (pwmt, build_reference_type (get_wmt ()))
 
   DEF_TYPE (qpwmt,
 	    build_qualified_type (get_pwmt (),
 				  TYPE_QUAL_RESTRICT
-				  | TYPE_QUAL_CONST))
-
-  DEF_TYPE (pptr, build_pointer_type (ptr_type_node))
+				  /* | TYPE_QUAL_CONST */))
 
-  DEF_TYPE (qpptr,
-	    build_qualified_type (get_pptr (),
+  DEF_TYPE (qptr,
+	    build_qualified_type (ptr_type_node,
 				  TYPE_QUAL_RESTRICT
 				  | TYPE_QUAL_CONST))
 
   DEF_TYPE (qpvalst,
-	    build_qualified_type (build_pointer_type
+	    build_qualified_type (build_reference_type
 				  (va_list_type_node),
 				  TYPE_QUAL_RESTRICT
-				  | TYPE_QUAL_CONST))
+				  /* | TYPE_QUAL_CONST */))
 
 #undef DEF_TYPE
 
@@ -1159,12 +1192,13 @@ public:
 
   DEF_IDENT (watermark_ptr)
   DEF_IDENT (va_list_ptr)
-  DEF_IDENT (apply_args_ptr)
+  DEF_IDENT (apply_args)
 
 #undef DEF_IDENT
 
   static inline int adjust_at_calls_type (tree);
   static inline void adjust_at_calls_call (cgraph_edge *, int);
+  static inline void adjust_at_calls_calls (cgraph_node *);
 
   static inline gimple_seq
   call_update_watermark (tree wmptr, cgraph_node *node, profile_count count,
@@ -1324,6 +1358,9 @@ add_call_edges_for_seq (gimple_seq seq, profile_count count)
 static void
 gsi_insert_finally_seq_after_call (gimple_stmt_iterator gsi, gimple_seq seq)
 {
+  if (!seq)
+    return;
+
   gimple *stmt = gsi_stmt (gsi);
 
   gcall *call = is_a <gcall *> (stmt) ? as_a <gcall *> (stmt) : NULL;
@@ -1493,6 +1530,39 @@ ipa_strub_set_mode_for_new_functions ()
   last_cgraph_order = symtab->order;
 }
 
+/* Return the PARM_DECL of the incoming watermark pointer, if there is one.  */
+tree
+find_watermark_parm (tree fndecl)
+{
+  switch (get_strub_mode_from_decl (fndecl))
+    {
+    case STRUB_WRAPPED:
+    case STRUB_AT_CALLS:
+    case STRUB_AT_CALLS_OPT:
+      break;
+
+    case STRUB_INTERNAL:
+    case STRUB_WRAPPER:
+    case STRUB_CALLABLE:
+    case STRUB_DISABLED:
+    case STRUB_INLINABLE:
+      return NULL_TREE;
+
+    default:
+      gcc_unreachable ();
+    }
+
+  for (tree parm = DECL_ARGUMENTS (fndecl); parm; parm = DECL_CHAIN (parm))
+    if (TREE_TYPE (parm) == pass_ipa_strub::get_qpwmt ())
+      {
+	gcc_checking_assert (DECL_NAME (parm)
+			     == pass_ipa_strub::get_watermark_ptr ());
+	return parm;
+      }
+
+  gcc_unreachable ();
+}
+
 /* Adjust a STRUB_AT_CALLS function TYPE, adding a watermark pointer if it
    hasn't been added yet.  Return the named argument count.  */
 int
@@ -1507,7 +1577,7 @@ pass_ipa_strub::adjust_at_calls_type (tree type)
   tree qpwmptrt = get_qpwmt ();
   while (*tlist && TREE_VALUE (*tlist) != void_type_node)
     {
-      /* The type has alreayd been adjusted.  */
+      /* The type has already been adjusted.  */
       if (TREE_VALUE (*tlist) == qpwmptrt)
 	return named_args;
       named_args++;
@@ -1581,22 +1651,30 @@ pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
 			 && (TREE_TYPE (gimple_call_arg (ocall, named_args))
 			     == get_pwmt ())));
 
-  /* ??? If it's a (tail?) call within a strub context, maybe pass on
-     the strub watermark instead of wrapping the call.  */
-
-  /* Initialize the watermark before the call.  */
-  tree swm = create_tmp_var (get_wmt (), ".strub.watermark");
-  TREE_ADDRESSABLE (swm) = true;
-  tree swmp = build1 (ADDR_EXPR, get_pwmt (), swm);
+  /* If we're already within a strub context, pass on the incoming watermark
+     pointer, and omit the enter and leave calls around the modified call.  */
+  tree swmp = ((optimize_size || optimize > 2)
+	       ? find_watermark_parm (e->caller->decl)
+	       : NULL_TREE);
+  bool omit_own_watermark = swmp;
+  tree swm = NULL_TREE;
+  if (!omit_own_watermark)
+    {
+      swm = create_tmp_var (get_wmt (), ".strub.watermark");
+      TREE_ADDRESSABLE (swm) = true;
+      swmp = build1 (ADDR_EXPR, get_pwmt (), swm);
 
-  tree enter = get_enter ();
-  gcall *stptr = gimple_build_call (enter, 1,
-				    unshare_expr (swmp));
-  gsi_insert_before (&gsi, stptr, GSI_SAME_STMT);
+      /* Initialize the watermark before the call.  */
+      tree enter = get_enter ();
+      gcall *stptr = gimple_build_call (enter, 1,
+					unshare_expr (swmp));
+      gsi_insert_before (&gsi, stptr, GSI_SAME_STMT);
 #if !IMPLICIT_CGRAPH_EDGES
-  e->caller->create_edge (cgraph_node::get_create (enter),
-			  stptr, gsi_bb (gsi)->count, false);
+      e->caller->create_edge (cgraph_node::get_create (enter),
+			      stptr, gsi_bb (gsi)->count, false);
 #endif
+    }
+
 
   /* Replace the call with one that passes the swmp argument first.  */
   gcall *wrcall;
@@ -1661,59 +1739,114 @@ pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
   /* Insert the strub code after the call.  */
   gimple_seq seq = NULL;
 
-  {
 #if !ATTR_FNSPEC_DECONST_WATERMARK
-    /* If the call will be assumed to not modify or even read the
-       watermark, make it read and modified ourselves.  */
-    if ((gimple_call_flags (wrcall)
-	 & (ECF_CONST | ECF_PURE | ECF_NOVOPS)))
-      {
-	vec<tree, va_gc> *inputs = NULL;
-	vec<tree, va_gc> *outputs = NULL;
-	vec_safe_push (outputs,
-		       build_tree_list
-		       (build_tree_list
-			(NULL_TREE, build_string (2, "=m")),
-			swm));
-	vec_safe_push (inputs,
-		       build_tree_list
-		       (build_tree_list
-			(NULL_TREE, build_string (1, "m")),
-			swm));
-	gasm *forcemod = gimple_build_asm_vec ("", inputs, outputs,
-					       NULL, NULL);
-	gimple_seq_add_stmt (&seq, forcemod);
-
-	/* If the call will be assumed to not even read the watermark,
-	   make sure it is already in memory before the call.  */
-	if ((gimple_call_flags (wrcall) & ECF_CONST))
-	  {
-	    vec<tree, va_gc> *inputs = NULL;
-	    vec_safe_push (inputs,
-			   build_tree_list
-			   (build_tree_list
-			    (NULL_TREE, build_string (1, "m")),
-			    swm));
-	    gasm *force_store = gimple_build_asm_vec ("", inputs, NULL,
-						      NULL, NULL);
-	    gsi_insert_before (&gsi, force_store, GSI_SAME_STMT);
-	  }
-      }
+  /* If the call will be assumed to not modify or even read the
+     watermark, make it read and modified ourselves.  */
+  if ((gimple_call_flags (wrcall)
+       & (ECF_CONST | ECF_PURE | ECF_NOVOPS)))
+    {
+      if (!swm)
+	swm = build2 (MEM_REF,
+		      TREE_TYPE (TREE_TYPE (swmp)),
+		      swmp,
+		      build_int_cst (TREE_TYPE (swmp), 0));
+
+      vec<tree, va_gc> *inputs = NULL;
+      vec<tree, va_gc> *outputs = NULL;
+      vec_safe_push (outputs,
+		     build_tree_list
+		     (build_tree_list
+		      (NULL_TREE, build_string (2, "=m")),
+		      unshare_expr (swm)));
+      vec_safe_push (inputs,
+		     build_tree_list
+		     (build_tree_list
+		      (NULL_TREE, build_string (1, "m")),
+		      unshare_expr (swm)));
+      gasm *forcemod = gimple_build_asm_vec ("", inputs, outputs,
+					     NULL, NULL);
+      gimple_seq_add_stmt (&seq, forcemod);
+
+      /* If the call will be assumed to not even read the watermark,
+	 make sure it is already in memory before the call.  */
+      if ((gimple_call_flags (wrcall) & ECF_CONST))
+	{
+	  vec<tree, va_gc> *inputs = NULL;
+	  vec_safe_push (inputs,
+			 build_tree_list
+			 (build_tree_list
+			  (NULL_TREE, build_string (1, "m")),
+			  unshare_expr (swm)));
+	  gasm *force_store = gimple_build_asm_vec ("", inputs, NULL,
+						    NULL, NULL);
+	  gsi_insert_before (&gsi, force_store, GSI_SAME_STMT);
+	}
+    }
 #endif
 
-    gcall *sleave = gimple_build_call (get_leave (), 1,
-				       unshare_expr (swmp));
-    gimple_seq_add_stmt (&seq, sleave);
-
-    gassign *clobber = gimple_build_assign (swm,
-					    build_clobber
-					    (TREE_TYPE (swm)));
-    gimple_seq_add_stmt (&seq, clobber);
-  }
+  if (!omit_own_watermark)
+    {
+      gcall *sleave = gimple_build_call (get_leave (), 1,
+					 unshare_expr (swmp));
+      gimple_seq_add_stmt (&seq, sleave);
+
+      gassign *clobber = gimple_build_assign (swm,
+					      build_clobber
+					      (TREE_TYPE (swm)));
+      gimple_seq_add_stmt (&seq, clobber);
+    }
 
   gsi_insert_finally_seq_after_call (gsi, seq);
 }
 
+void
+pass_ipa_strub::adjust_at_calls_calls (cgraph_node *node)
+{
+  /* Adjust unknown-callee indirect calls with STRUB_AT_CALLS types within
+     onode.  */
+  if (node->indirect_calls)
+    {
+      push_cfun (DECL_STRUCT_FUNCTION (node->decl));
+      for (cgraph_edge *e = node->indirect_calls; e; e = e->next_callee)
+	{
+	  gcc_checking_assert (e->indirect_unknown_callee);
+
+	  tree callee_fntype = gimple_call_fntype (e->call_stmt);
+	  enum strub_mode callee_mode
+	    = get_strub_mode_from_type (callee_fntype);
+
+	  if (callee_mode != STRUB_AT_CALLS
+	      && callee_mode != STRUB_AT_CALLS_OPT)
+	    continue;
+
+	  int named_args = adjust_at_calls_type (callee_fntype);
+
+	  adjust_at_calls_call (e, named_args);
+	}
+      pop_cfun ();
+    }
+
+  if (node->callees)
+    {
+      push_cfun (DECL_STRUCT_FUNCTION (node->decl));
+      for (cgraph_edge *e = node->callees; e; e = e->next_callee)
+	{
+	  gcc_checking_assert (!e->indirect_unknown_callee);
+
+	  enum strub_mode callee_mode = get_strub_mode (e->callee);
+
+	  if (callee_mode != STRUB_AT_CALLS
+	      && callee_mode != STRUB_AT_CALLS_OPT)
+	    continue;
+
+	  int named_args = adjust_at_calls_type (TREE_TYPE (e->callee->decl));
+
+	  adjust_at_calls_call (e, named_args);
+	}
+      pop_cfun ();
+    }
+}
+
 unsigned int
 pass_ipa_strub_mode::execute (function *)
 {
@@ -1849,54 +1982,13 @@ pass_ipa_strub::execute (function *)
     if (!onode->has_gimple_body_p ())
       continue;
 
-    /* Adjust unknown-callee indirect calls with STRUB_AT_CALLS types within
-       onode.  */
-    if (onode->indirect_calls)
-      {
-	push_cfun (DECL_STRUCT_FUNCTION (onode->decl));
-	for (cgraph_edge *e = onode->indirect_calls; e; e = e->next_callee)
-	  {
-	    gcc_checking_assert (e->indirect_unknown_callee);
-
-	    tree callee_fntype = gimple_call_fntype (e->call_stmt);
-	    enum strub_mode callee_mode
-	      = get_strub_mode_from_type (callee_fntype);
-
-	    if (callee_mode != STRUB_AT_CALLS
-		&& callee_mode != STRUB_AT_CALLS_OPT)
-	      continue;
-
-	    int named_args = adjust_at_calls_type (callee_fntype);
-
-	    adjust_at_calls_call (e, named_args);
-	  }
-	pop_cfun ();
-      }
-
-    if (onode->callees)
-      {
-	push_cfun (DECL_STRUCT_FUNCTION (onode->decl));
-	for (cgraph_edge *e = onode->callees; e; e = e->next_callee)
-	  {
-	    gcc_checking_assert (!e->indirect_unknown_callee);
-
-	    enum strub_mode callee_mode = get_strub_mode (e->callee);
-
-	    if (callee_mode != STRUB_AT_CALLS
-		&& callee_mode != STRUB_AT_CALLS_OPT)
-	      continue;
-
-	    int named_args = adjust_at_calls_type (TREE_TYPE (e->callee->decl));
-
-	    adjust_at_calls_call (e, named_args);
-	  }
-	pop_cfun ();
-      }
-
     enum strub_mode mode = get_strub_mode (onode);
 
     if (mode != STRUB_INTERNAL)
-      continue;
+      {
+	adjust_at_calls_calls (onode);
+	continue;
+      }
 
 #if 0
     /* Hmm, this is an i386-specific attribute.  Do we need machine-specific
@@ -1909,7 +2001,7 @@ pass_ipa_strub::execute (function *)
     if (!DECL_STRUCT_FUNCTION (onode->decl))
       {
 	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"not splitting struct-less function %qD for stack scrubbing",
+		"not splitting struct-less function %qD for %<strub%>",
 		onode->decl);
 	continue;
       }
@@ -1917,7 +2009,7 @@ pass_ipa_strub::execute (function *)
     if (!onode->lowered)
       {
 	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"not splitting non-lowered function %qD for stack scrubbing",
+		"not splitting non-lowered function %qD for %<strub%>",
 		onode->decl);
 	continue;
       }
@@ -1929,7 +2021,7 @@ pass_ipa_strub::execute (function *)
     if (!tree_versionable_function_p (onode->decl))
       {
 	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"%qD cannot be split for stack scrubbing",
+		"%qD cannot be split for %<strub%>",
 		onode->decl);
 	continue;
       }
@@ -1956,7 +2048,7 @@ pass_ipa_strub::execute (function *)
 	{
 	  ipa_adjusted_param aaadj = {};
 	  aaadj.op = IPA_PARAM_OP_NEW;
-	  aaadj.type = get_qpptr ();
+	  aaadj.type = get_qptr ();
 	  vec_safe_push (nparms, aaadj);
 	}
 
@@ -1982,7 +2074,7 @@ pass_ipa_strub::execute (function *)
     if (!nnode)
       {
 	error_at (DECL_SOURCE_LOCATION (onode->decl),
-		  "failed to split %qD for stack scrubbing",
+		  "failed to split %qD for %<strub%>",
 		  onode->decl);
 	continue;
       }
@@ -1996,6 +2088,8 @@ pass_ipa_strub::execute (function *)
     set_strub_mode_to (onode, STRUB_WRAPPER);
     set_strub_mode_to (nnode, STRUB_WRAPPED);
 
+    adjust_at_calls_calls (nnode);
+
     /* Decide which of the wrapped function's parms we want to turn into
        references to the argument passed to the wrapper.  In general, we want to
        copy small arguments, and avoid copying large ones.  Variable-sized array
@@ -2181,8 +2275,8 @@ pass_ipa_strub::execute (function *)
 	     only of reading because const/pure.  */
 	  if (apply_args)
 	    {
-	      nspec[curlen++] = (no_writes_p ? 'r' : '.');
-	      nspec[curlen++] = (no_writes_p ? 't' : ' ');
+	      nspec[curlen++] = 'r';
+	      nspec[curlen++] = ' ';
 	    }
 	  if (is_stdarg)
 	    {
@@ -2485,24 +2579,24 @@ pass_ipa_strub::execute (function *)
     }
 
     {
-      tree aaptr = NULL_TREE;
+      tree aaval = NULL_TREE;
       tree vaptr = NULL_TREE;
       tree wmptr = NULL_TREE;
       for (tree arg = DECL_ARGUMENTS (nnode->decl); arg; arg = DECL_CHAIN (arg))
 	{
-	  aaptr = vaptr;
+	  aaval = vaptr;
 	  vaptr = wmptr;
 	  wmptr = arg;
 	}
 
       if (!apply_args)
-	aaptr = NULL_TREE;
+	aaval = NULL_TREE;
       /* The trailing args are [apply_args], [va_list_ptr], and
 	 watermark.  If we don't have a va_list_ptr, the penultimate
 	 argument is apply_args.
        */
       else if (!is_stdarg)
-	aaptr = vaptr;
+	aaval = vaptr;
 
       if (!is_stdarg)
 	vaptr = NULL_TREE;
@@ -2522,10 +2616,10 @@ pass_ipa_strub::execute (function *)
 
       if (apply_args)
 	{
-	  DECL_NAME (aaptr) = get_apply_args_ptr ();
-	  DECL_ARTIFICIAL (aaptr) = 1;
-	  DECL_IGNORED_P (aaptr) = 1;
-	  TREE_USED (aaptr) = 1;
+	  DECL_NAME (aaval) = get_apply_args ();
+	  DECL_ARTIFICIAL (aaval) = 1;
+	  DECL_IGNORED_P (aaval) = 1;
+	  TREE_USED (aaval) = 1;
 	}
 
       push_cfun (DECL_STRUCT_FUNCTION (nnode->decl));
@@ -2582,9 +2676,10 @@ pass_ipa_strub::execute (function *)
 	    else if (fndecl && is_stdarg
 		     && fndecl_built_in_p (fndecl, BUILT_IN_VA_START))
 	      {
-		if (builtin_decl_explicit (BUILT_IN_VA_START) != fndecl)
-		  sorry_at (gimple_location (call),
-			    "nonstandard stdarg conventions");
+		/* Using a non-default stdarg ABI makes the function ineligible
+		   for internal strub.  */
+		gcc_checking_assert (builtin_decl_explicit (BUILT_IN_VA_START)
+				     == fndecl);
 		tree bvacopy = builtin_decl_explicit (BUILT_IN_VA_COPY);
 		gimple_call_set_fndecl (call, bvacopy);
 		tree arg = vaptr;
@@ -2607,7 +2702,9 @@ pass_ipa_strub::execute (function *)
 		     && fndecl_built_in_p (fndecl, BUILT_IN_APPLY_ARGS))
 	      {
 		tree lhs = gimple_call_lhs (call);
-		gassign *assign = gimple_build_assign (lhs, aaptr);
+		gimple *assign = (lhs
+				  ? gimple_build_assign (lhs, aaval)
+				  : gimple_build_nop ());
 		gsi_replace (&gsi, assign, true);
 		cgraph_edge::remove (e);
 	      }
@@ -2650,7 +2747,7 @@ pass_ipa_strub::execute (function *)
 
       if (apply_args)
 	{
-	  tree aalst = create_tmp_var (ptr_type_node, ".strub.appargs");
+	  tree aalst = create_tmp_var (ptr_type_node, ".strub.apply_args");
 	  tree bappargs = builtin_decl_explicit (BUILT_IN_APPLY_ARGS);
 	  gcall *appargs = gimple_build_call (bappargs, 0);
 	  gimple_call_set_lhs (appargs, aalst);
diff --git a/gcc/ipa-strub.h b/gcc/ipa-strub.h
index cadbca5002a..ea8ee857e8e 100644
--- a/gcc/ipa-strub.h
+++ b/gcc/ipa-strub.h
@@ -23,3 +23,4 @@ along with GCC; see the file COPYING3.  If not see
    doesn't have to be called directly by CALLER, but the returned
    value says nothing about intervening functions.  */
 extern bool strub_inlinable_p (cgraph_node *callee, cgraph_node *caller);
+extern tree find_watermark_parm (tree fndecl);
diff --git a/gcc/testsuite/c-c++-common/strub-O2.c b/gcc/testsuite/c-c++-common/strub-O2.c
index 9cc39e763b1..7848c46d179 100644
--- a/gcc/testsuite/c-c++-common/strub-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-O2.c
@@ -13,4 +13,4 @@ int f() {
 /* { dg-final { scan-rtl-dump-not "strub_enter" "expand" } } */
 /* { dg-final { scan-rtl-dump-not "strub_update" "expand" } } */
 /* { dg-final { scan-rtl-dump "strub_leave" "expand" } } */
-/* { dg-final { scan-rtl-dump "\n\[(\]call_insn\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
+/* { dg-final { scan-rtl-dump "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-O2fni.c b/gcc/testsuite/c-c++-common/strub-O2fni.c
index 51cae845d5f..85a8f76785e 100644
--- a/gcc/testsuite/c-c++-common/strub-O2fni.c
+++ b/gcc/testsuite/c-c++-common/strub-O2fni.c
@@ -1,7 +1,7 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fdump-rtl-expand -fno-inline-functions" } */
+/* { dg-options "-O2 -fstrub=default -fdump-rtl-expand -fno-inline" } */
 
-/* With -fno-inline-functions, none of the strub builtins are inlined.  */
+/* With -fno-inline, none of the strub builtins are inlined.  */
 
 int __attribute__ ((__strub__)) var;
 
@@ -12,4 +12,4 @@ int f() {
 /* { dg-final { scan-rtl-dump "strub_enter" "expand" } } */
 /* { dg-final { scan-rtl-dump "strub_update" "expand" } } */
 /* { dg-final { scan-rtl-dump "strub_leave" "expand" } } */
-/* { dg-final { scan-rtl-dump-not "\n\[(\]call_insn\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
+/* { dg-final { scan-rtl-dump-not "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-O3fni.c b/gcc/testsuite/c-c++-common/strub-O3fni.c
index 8f67b613be8..a2eedfd96b2 100644
--- a/gcc/testsuite/c-c++-common/strub-O3fni.c
+++ b/gcc/testsuite/c-c++-common/strub-O3fni.c
@@ -1,7 +1,7 @@
 /* { dg-do compile } */
 /* { dg-options "-O3 -fstrub=default -fdump-rtl-expand -fno-inline" } */
 
-/* With -fno-inline-functions, none of the strub builtins are inlined.  */
+/* With -fno-inline, none of the strub builtins are inlined.  */
 
 int __attribute__ ((__strub__)) var;
 
@@ -12,4 +12,4 @@ int f() {
 /* { dg-final { scan-rtl-dump "strub_enter" "expand" } } */
 /* { dg-final { scan-rtl-dump "strub_update" "expand" } } */
 /* { dg-final { scan-rtl-dump "strub_leave" "expand" } } */
-/* { dg-final { scan-rtl-dump-not "\n\[(\]call_insn\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
+/* { dg-final { scan-rtl-dump-not "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-Og.c b/gcc/testsuite/c-c++-common/strub-Og.c
index 6f60349573f..e5cb1f60541 100644
--- a/gcc/testsuite/c-c++-common/strub-Og.c
+++ b/gcc/testsuite/c-c++-common/strub-Og.c
@@ -13,4 +13,4 @@ int f() {
 /* { dg-final { scan-rtl-dump-not "strub_enter" "expand" } } */
 /* { dg-final { scan-rtl-dump "strub_update" "expand" } } */
 /* { dg-final { scan-rtl-dump "strub_leave" "expand" } } */
-/* { dg-final { scan-rtl-dump-not "\n\[(\]call_insn\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
+/* { dg-final { scan-rtl-dump-not "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-Os.c b/gcc/testsuite/c-c++-common/strub-Os.c
index 5d1c08a7528..194aacc2c05 100644
--- a/gcc/testsuite/c-c++-common/strub-Os.c
+++ b/gcc/testsuite/c-c++-common/strub-Os.c
@@ -15,4 +15,4 @@ int f() {
 /* { dg-final { scan-rtl-dump-not "strub_enter" "expand" } } */
 /* { dg-final { scan-rtl-dump-not "strub_update" "expand" } } */
 /* { dg-final { scan-rtl-dump "strub_leave" "expand" } } */
-/* { dg-final { scan-rtl-dump-not "\n\[(\]call_insn\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
+/* { dg-final { scan-rtl-dump-not "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-all1.c b/gcc/testsuite/c-c++-common/strub-all1.c
index 54daf84656c..46e84bf6560 100644
--- a/gcc/testsuite/c-c++-common/strub-all1.c
+++ b/gcc/testsuite/c-c++-common/strub-all1.c
@@ -3,9 +3,9 @@
 
 /* h becomes STRUB_CALLABLE, rather than STRUB_INLINABLE, because of the
    strub-enabling -fstrub flag, and gets inlined before pass_ipa_strub.  */
-static void
+static inline void
 __attribute__ ((__always_inline__))
-h() {  /* { dg-warning "might not be inlinable" } */
+h() {
 }
 
 /* g becomes STRUB_AT_CALLS, because of the flag.  */
diff --git a/gcc/testsuite/c-c++-common/strub-apply1.c b/gcc/testsuite/c-c++-common/strub-apply1.c
new file mode 100644
index 00000000000..f180b17f30e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply1.c
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default" } */
+
+void __attribute__ ((__strub__ (3)))
+apply_function (void *args)
+{
+  __builtin_apply (0, args, 0);
+}
+
+void __attribute__ ((__strub__ (2)))
+apply_args (int i, int j, double d)
+{
+  void *args = __builtin_apply_args ();
+  apply_function (args);
+}
diff --git a/gcc/testsuite/c-c++-common/strub-apply2.c b/gcc/testsuite/c-c++-common/strub-apply2.c
new file mode 100644
index 00000000000..379a54b73b7
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply2.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default" } */
+
+extern void __attribute__ ((__strub__))
+apply_function (void *args);
+
+void __attribute__ ((__strub__))
+apply_args (int i, int j, double d) /* { dg-error "selected" } */
+{
+  void *args = __builtin_apply_args (); /* { dg-message "does not support" } */
+  apply_function (args);
+}
diff --git a/gcc/testsuite/c-c++-common/strub-apply3.c b/gcc/testsuite/c-c++-common/strub-apply3.c
new file mode 100644
index 00000000000..9b4786be698
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply3.c
@@ -0,0 +1,8 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default" } */
+
+void __attribute__ ((__strub__))
+apply_function (void *args)
+{
+  __builtin_apply (0, args, 0); /* { dg-error "in .strub. context" } */
+}
diff --git a/gcc/testsuite/c-c++-common/strub-apply4.c b/gcc/testsuite/c-c++-common/strub-apply4.c
new file mode 100644
index 00000000000..409f747743e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply4.c
@@ -0,0 +1,21 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fstrub=default -fdump-ipa-strubm" } */
+
+/* Check that implicit enabling of strub mode selects internal strub when the
+   function uses __builtin_apply_args, that prevents the optimization to
+   at-calls mode.  */
+
+int __attribute__ ((__strub__)) var;
+
+static inline void
+apply_args (int i, int j, double d)
+{
+  var++;
+  __builtin_apply_args ();
+}
+
+void f() {
+  apply_args (1, 2, 3);
+}
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 1 "strubm" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-at-calls1.c b/gcc/testsuite/c-c++-common/strub-at-calls1.c
index 0d1b9fce833..d964b07ae5d 100644
--- a/gcc/testsuite/c-c++-common/strub-at-calls1.c
+++ b/gcc/testsuite/c-c++-common/strub-at-calls1.c
@@ -3,9 +3,9 @@
 
 /* h becomes STRUB_CALLABLE, rather than STRUB_INLINABLE, because of the
    strub-enabling -fstrub flag, and gets inlined before pass_ipa_strub.  */
-static void
+static inline void
 __attribute__ ((__always_inline__))
-h() {  /* { dg-warning "might not be inlinable" } */
+h() {
 }
 
 /* g becomes STRUB_AT_CALLS, because of the flag.  */
diff --git a/gcc/testsuite/c-c++-common/strub-default1.c b/gcc/testsuite/c-c++-common/strub-default1.c
index a1e1803aadc..d579ec62f55 100644
--- a/gcc/testsuite/c-c++-common/strub-default1.c
+++ b/gcc/testsuite/c-c++-common/strub-default1.c
@@ -3,12 +3,12 @@
 
 static int __attribute__ ((__strub__)) var;
 
-/* h becomes STRUB_STRUB_INLINABLE, because of the use of the strub variable,
+/* h becomes STRUB_INLINABLE, because of the use of the strub variable,
    and the always_inline flag.  It would get inlined before pass_ipa_strub, if
    it weren't for the error.  */
-static void
+static inline void
 __attribute__ ((__always_inline__))
-h() {  /* { dg-warning "might not be inlinable" } */
+h() {
   var++;
 }
 
@@ -34,7 +34,7 @@ f() {
 /* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 1 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-3\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]-3\[)\]" 1 "strub" } } */
 /* { dg-final { scan-ipa-dump-times "strub \[(\]-4\[)\]" 1 "strub" } } */
 /* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 1 "strub" } } */
 /* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O1.c b/gcc/testsuite/c-c++-common/strub-defer-O1.c
new file mode 100644
index 00000000000..7b04eea35d9
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-O1.c
@@ -0,0 +1,7 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default -O1" } */
+
+/* Check that a strub function called by another strub function does NOT defer
+   the strubbing to its caller at -O1.  */
+
+#include "strub-defer-O2.c"
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O2.c b/gcc/testsuite/c-c++-common/strub-defer-O2.c
new file mode 100644
index 00000000000..67d96419a5e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-O2.c
@@ -0,0 +1,8 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default -O2" } */
+
+/* Check that a strub function called by another strub function does NOT defer
+   the strubbing to its caller at -O2.  */
+
+#define EXPECT_DEFERRAL !
+#include "strub-defer-O3.c"
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O3.c b/gcc/testsuite/c-c++-common/strub-defer-O3.c
new file mode 100644
index 00000000000..34828d2711e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-O3.c
@@ -0,0 +1,93 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default -O3" } */
+
+/* Check that a strub function called by another strub function defers the
+   strubbing to its caller at -O3.  */
+
+#ifndef EXPECT_DEFERRAL
+# define EXPECT_DEFERRAL
+#endif
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+static inline __attribute__ ((__always_inline__, __strub__ (3)))
+char *
+leak_string (void)
+{
+  /* We use this variable to avoid any stack red zone.  Stack scrubbing covers
+     it, but __builtin_stack_address, that we take as a reference, doesn't, so
+     if e.g. callable() were to store the string in the red zone, we wouldn't
+     find it because it would be outside the range we searched.  */
+  typedef void __attribute__ ((__strub__ (3))) callable_t (char *);
+  callable_t *f = 0;
+
+  char s[sizeof (test_string)];
+  __builtin_strcpy (s, test_string);
+  asm ("" : "+m" (s), "+r" (f));
+
+  if (__builtin_expect (!f, 1))
+    return (char*)__builtin_stack_address ();
+
+  f (s);
+  return 0;
+}
+
+static inline __attribute__ ((__always_inline__, __strub__ (3)))
+int
+look_for_string (char *e)
+{
+  char *p = (char*)__builtin_stack_address ();
+
+  if (p == e)
+    __builtin_abort ();
+
+  if (p > e)
+    {
+      char *q = p;
+      p = e;
+      e = q;
+    }
+
+  for (char *re = e - sizeof (test_string); p < re; p++)
+    for (int i = 0; p[i] == test_string[i]; i++)
+      if (i == sizeof (test_string) - 1)
+	return i;
+
+  return 0;
+}
+
+static __attribute__ ((__strub__ (1), __noinline__, __noclone__))
+char *
+at_calls ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ (1)))
+char *
+deferred_at_calls ()
+{
+  char *ret = at_calls ();
+  if (EXPECT_DEFERRAL !look_for_string (ret))
+    __builtin_abort ();
+  return ret;
+}
+
+static __attribute__ ((__strub__ (2)))
+char *
+deferred_internal ()
+{
+  char *ret = at_calls ();
+  if (EXPECT_DEFERRAL !look_for_string (ret))
+    __builtin_abort ();
+  return ret;
+}
+
+int main ()
+{
+  if (look_for_string (deferred_at_calls ()))
+    __builtin_abort ();
+  if (look_for_string (deferred_internal ()))
+    __builtin_abort ();
+  __builtin_exit (0);
+}
diff --git a/gcc/testsuite/c-c++-common/strub-defer-Os.c b/gcc/testsuite/c-c++-common/strub-defer-Os.c
new file mode 100644
index 00000000000..b273660aea1
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-Os.c
@@ -0,0 +1,7 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default -Os" } */
+
+/* Check that a strub function called by another strub function defers the
+   strubbing to its caller at -Os.  */
+
+#include "strub-defer-O3.c"
diff --git a/gcc/testsuite/c-c++-common/strub-internal1.c b/gcc/testsuite/c-c++-common/strub-internal1.c
index b9bd787df0a..a74658c9ac9 100644
--- a/gcc/testsuite/c-c++-common/strub-internal1.c
+++ b/gcc/testsuite/c-c++-common/strub-internal1.c
@@ -3,9 +3,9 @@
 
 /* h becomes STRUB_CALLABLE, rather than STRUB_INLINABLE, because of the
    strub-enabling -fstrub flag, and gets inlined before pass_ipa_strub.  */
-static void
+static inline void
 __attribute__ ((__always_inline__))
-h() {  /* { dg-warning "might not be inlinable" } */
+h() {
 }
 
 /* g becomes STRUB_INTERNAL because of the flag, and gets split into
diff --git a/gcc/testsuite/c-c++-common/strub-parms1.c b/gcc/testsuite/c-c++-common/strub-parms1.c
new file mode 100644
index 00000000000..0422ccc7f7d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-parms1.c
@@ -0,0 +1,48 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+#include <stdarg.h>
+
+void __attribute__ ((__strub__ (2)))
+small_args (int i, long long l, void *p, void **q, double d, char c)
+{
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid small_args.strub.\[0-9\]* \[(\]int i, long long int l, void \\* p, void \\* \\* q, double d, char c, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " small_args.strub.\[0-9\]* \[(\]\[^&\]*&.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+
+
+struct large_arg {
+  int x[128];
+};
+
+void __attribute__ ((__strub__ (2)))
+large_byref_arg (struct large_arg la)
+{
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid large_byref_arg.strub.\[0-9\]* \[(\]struct large_arg & la, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " large_byref_arg.strub.\[0-9\]* \[(\]&\[^&\]*&.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+
+void __attribute__ ((__strub__ (2)))
+std_arg (int i, ...)
+{
+  va_list vl;
+  va_start (vl, i);
+  va_end (vl);
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid std_arg.strub.\[0-9\]* \[(\]int i, \[^&,\]* &\[^&,\]*.strub.va_list_ptr, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " std_arg.strub.\[0-9\]* \[(\]\[^&\]*&.strub.va_list.\[0-9\]*, &.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_start \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_copy \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_end \\(" 2 "strub" } } */
+
+void __attribute__ ((__strub__ (2)))
+apply_args (int i, int j, double d)
+{
+  __builtin_apply_args ();
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid apply_args.strub.\[0-9\]* \[(\]int i, int j, double d, void \\*\[^&,\]*.strub.apply_args, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " apply_args.strub.\[0-9\]* \[(\]\[^&\]*.strub.apply_args.\[0-9\]*_\[0-9\]*, &.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-parms2.c b/gcc/testsuite/c-c++-common/strub-parms2.c
new file mode 100644
index 00000000000..aaf02a2cd46
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-parms2.c
@@ -0,0 +1,36 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+#include <stdarg.h>
+
+void __attribute__ ((__strub__ (1)))
+small_args (int i, long long l, void *p, void **q, double d, char c)
+{
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid small_args \[(\]int i, long long int l, void \\* p, void \\* \\* q, double d, char c, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+
+
+struct large_arg {
+  int x[128];
+};
+
+void __attribute__ ((__strub__ (1)))
+large_byref_arg (struct large_arg la)
+{
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid large_byref_arg \[(\]struct large_arg la, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+
+void __attribute__ ((__strub__ (1)))
+std_arg (int i, ...)
+{
+  va_list vl;
+  va_start (vl, i);
+  va_end (vl);
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid std_arg \[(\]int i, void \\* &\[^&,\]*.strub.watermark_ptr\[, .]*\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_start \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-not "va_copy \\(" "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_end \\(" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-parms3.c b/gcc/testsuite/c-c++-common/strub-parms3.c
new file mode 100644
index 00000000000..2846098160d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-parms3.c
@@ -0,0 +1,58 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that uses of a strub variable implicitly enables internal strub for
+   publicly-visible functions, and causes the same transformations to their
+   signatures as those in strub-parms1.c.  */
+
+#include <stdarg.h>
+
+int __attribute__ ((__strub__)) var;
+
+void
+small_args (int i, long long l, void *p, void **q, double d, char c)
+{
+  var++;
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid small_args.strub.\[0-9\]* \[(\]int i, long long int l, void \\* p, void \\* \\* q, double d, char c, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " small_args.strub.\[0-9\]* \[(\]\[^&\]*&.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+
+
+struct large_arg {
+  int x[128];
+};
+
+void
+large_byref_arg (struct large_arg la)
+{
+  var++;
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid large_byref_arg.strub.\[0-9\]* \[(\]struct large_arg & la, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " large_byref_arg.strub.\[0-9\]* \[(\]&\[^&\]*&.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+
+void
+std_arg (int i, ...)
+{
+  va_list vl;
+  va_start (vl, i);
+  var++;
+  va_end (vl);
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid std_arg.strub.\[0-9\]* \[(\]int i, \[^&,\]* &\[^&,\]*.strub.va_list_ptr, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " std_arg.strub.\[0-9\]* \[(\]\[^&\]*&.strub.va_list.\[0-9\]*, &.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_start \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_copy \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_end \\(" 2 "strub" } } */
+
+void
+apply_args (int i, int j, double d)
+{
+  var++;
+  __builtin_apply_args ();
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid apply_args.strub.\[0-9\]* \[(\]int i, int j, double d, void \\*\[^&,\]*.strub.apply_args, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " apply_args.strub.\[0-9\]* \[(\]\[^&\]*.strub.apply_args.\[0-9\]*_\[0-9\]*, &.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable1.c b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
new file mode 100644
index 00000000000..45965f275c9
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub -fno-inline" } */
+
+/* Check that strub and non-strub functions can be called from non-strub
+   contexts, and that strub and callable functions can be called from strub
+   contexts.  */
+
+#define OMIT_IMPERMISSIBLE_CALLS 1
+#include "strub-callable2.c"
+
+/* { dg-final { scan-ipa-dump-times "strub_enter" 45 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_update" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_leave" 45 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable2.c b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
new file mode 100644
index 00000000000..38935e3270b
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
@@ -0,0 +1,264 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default" } */
+
+/* Check that impermissible (cross-strub-context) calls are reported.  */
+
+extern int __attribute__ ((__strub__ (3))) xcallable (void);
+extern int __attribute__ ((__strub__ (2))) xinternal (void);
+extern int __attribute__ ((__strub__ (1))) xat_calls (void);
+extern int __attribute__ ((__strub__ (0))) xdisabled (void);
+
+int __attribute__ ((__strub__ (3))) callable (void);
+int __attribute__ ((__strub__ (2))) internal (void);
+int __attribute__ ((__strub__ (1))) at_calls (void);
+int __attribute__ ((__strub__ (0))) disabled (void);
+
+int __attribute__ ((__strub__)) var;
+int var_user (void);
+
+static inline int __attribute__ ((__always_inline__, __strub__ (3)))
+icallable (void);
+static inline int __attribute__ ((__always_inline__, __strub__ (2)))
+iinternal (void);
+static inline int __attribute__ ((__always_inline__, __strub__ (1)))
+iat_calls (void);
+static inline int __attribute__ ((__always_inline__, __strub__ (0)))
+idisabled (void);
+static inline int __attribute__ ((__always_inline__))
+ivar_user (void);
+
+static inline int __attribute__ ((__always_inline__, __strub__ (3)))
+i_callable (void) { return 0; }
+static inline int __attribute__ ((__always_inline__, __strub__ (2)))
+i_internal (void) { return var; }
+static inline int __attribute__ ((__always_inline__, __strub__ (1)))
+i_at_calls (void) { return var; }
+static inline int __attribute__ ((__always_inline__, __strub__ (0)))
+i_disabled (void) { return 0; }
+static inline int __attribute__ ((__always_inline__))
+i_var_user (void) { return var; }
+
+#define CALLS_GOOD_FOR_STRUB_CONTEXT(ISEP)	\
+  do {						\
+    ret += i ## ISEP ## at_calls ();		\
+    ret += i ## ISEP ## internal ();		\
+    ret += i ## ISEP ## var_user ();		\
+  } while (0)
+
+#define CALLS_GOOD_FOR_NONSTRUB_CONTEXT(ISEP)	\
+  do {						\
+    ret += internal ();				\
+    ret += disabled ();				\
+    ret += var_user ();				\
+						\
+    ret += i ## ISEP ## disabled ();		\
+						\
+    ret += xinternal ();			\
+    ret += xdisabled ();			\
+  } while (0)
+
+#define CALLS_GOOD_FOR_EITHER_CONTEXT(ISEP)	\
+  do {						\
+    ret += i ## ISEP ## callable ();		\
+						\
+    ret += callable ();				\
+    ret += at_calls ();				\
+						\
+    ret += xat_calls ();			\
+    ret += xcallable ();			\
+  } while (0)
+
+/* Not a strub context, so it can call anything.
+   Explicitly declared as callable even from within strub contexts.  */
+int __attribute__ ((__strub__ (3)))
+callable (void) {
+  int ret = 0;
+
+  /* CALLS_GOOD_FOR_STRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += iat_calls (); /* { dg-error "in non-.strub. context" } */
+    ret += iinternal (); /* { dg-error "in non-.strub. context" } */
+    ret += ivar_user (); /* { dg-error "in non-.strub. context" } */
+#endif
+  CALLS_GOOD_FOR_EITHER_CONTEXT();
+  CALLS_GOOD_FOR_NONSTRUB_CONTEXT();
+
+  return ret;
+}
+
+/* Internal strubbing means the body is a strub context, so it can only call
+   strub functions, and it's not itself callable from strub functions.  */
+int __attribute__ ((__strub__ (2)))
+internal (void) {
+  int ret = var;
+
+  CALLS_GOOD_FOR_STRUB_CONTEXT();
+  CALLS_GOOD_FOR_EITHER_CONTEXT();
+  /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += internal (); /* { dg-error "in .strub. context" } */
+    ret += disabled (); /* { dg-error "in .strub. context" } */
+    ret += var_user (); /* { dg-error "in .strub. context" } */
+
+    ret += idisabled (); /* { dg-error "in .strub. context" } */
+
+    ret += xinternal (); /* { dg-error "in .strub. context" } */
+    ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+  return ret;
+}
+
+int __attribute__ ((__strub__ (1)))
+at_calls (void) {
+  int ret = var;
+
+  CALLS_GOOD_FOR_STRUB_CONTEXT();
+  CALLS_GOOD_FOR_EITHER_CONTEXT();
+  /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += internal (); /* { dg-error "in .strub. context" } */
+    ret += disabled (); /* { dg-error "in .strub. context" } */
+    ret += var_user (); /* { dg-error "in .strub. context" } */
+
+    ret += idisabled (); /* { dg-error "in .strub. context" } */
+
+    ret += xinternal (); /* { dg-error "in .strub. context" } */
+    ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+  return ret;
+}
+
+int __attribute__ ((__strub__ (0)))
+disabled () {
+  int ret = 0;
+
+  /* CALLS_GOOD_FOR_STRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += iat_calls (); /* { dg-error "in non-.strub. context" } */
+    ret += iinternal (); /* { dg-error "in non-.strub. context" } */
+    ret += ivar_user (); /* { dg-error "in non-.strub. context" } */
+#endif
+  CALLS_GOOD_FOR_EITHER_CONTEXT();
+  CALLS_GOOD_FOR_NONSTRUB_CONTEXT();
+
+  return ret;
+}  
+
+int
+var_user (void) {
+  int ret = var;
+
+  CALLS_GOOD_FOR_STRUB_CONTEXT();
+  CALLS_GOOD_FOR_EITHER_CONTEXT();
+  /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += internal (); /* { dg-error "in .strub. context" } */
+    ret += disabled (); /* { dg-error "in .strub. context" } */
+    ret += var_user (); /* { dg-error "in .strub. context" } */
+
+    ret += idisabled (); /* { dg-error "in .strub. context" } */
+
+    ret += xinternal (); /* { dg-error "in .strub. context" } */
+    ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+  return ret;
+}
+
+int
+icallable (void)
+{
+  int ret = 0;
+
+  /* CALLS_GOOD_FOR_STRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += i_at_calls (); /* { dg-error "in non-.strub. context" } */
+    ret += i_internal (); /* { dg-error "in non-.strub. context" } */
+    ret += i_var_user (); /* { dg-error "in non-.strub. context" } */
+#endif
+  CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+  CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_);
+
+  return ret;
+}
+
+int
+iinternal (void) {
+  int ret = var;
+
+  CALLS_GOOD_FOR_STRUB_CONTEXT(_);
+  CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+  /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += internal (); /* { dg-error "in .strub. context" } */
+    ret += disabled (); /* { dg-error "in .strub. context" } */
+    ret += var_user (); /* { dg-error "in .strub. context" } */
+
+    ret += i_disabled (); /* { dg-error "in .strub. context" } */
+
+    ret += xinternal (); /* { dg-error "in .strub. context" } */
+    ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+  return ret;
+}
+
+int
+iat_calls (void) {
+  int ret = var;
+
+  CALLS_GOOD_FOR_STRUB_CONTEXT(_);
+  CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+  /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += internal (); /* { dg-error "in .strub. context" } */
+    ret += disabled (); /* { dg-error "in .strub. context" } */
+    ret += var_user (); /* { dg-error "in .strub. context" } */
+
+    ret += i_disabled (); /* { dg-error "in .strub. context" } */
+
+    ret += xinternal (); /* { dg-error "in .strub. context" } */
+    ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+  return ret;
+}
+
+int
+idisabled () {
+  int ret = 0;
+
+  /* CALLS_GOOD_FOR_STRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += i_at_calls (); /* { dg-error "in non-.strub. context" } */
+    ret += i_internal (); /* { dg-error "in non-.strub. context" } */
+    ret += i_var_user (); /* { dg-error "in non-.strub. context" } */
+#endif
+  CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+  CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_);
+
+  return ret;
+}  
+
+int
+ivar_user (void) {
+  int ret = var;
+
+  CALLS_GOOD_FOR_STRUB_CONTEXT(_);
+  CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+  /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += internal (); /* { dg-error "in .strub. context" } */
+    ret += disabled (); /* { dg-error "in .strub. context" } */
+    ret += var_user (); /* { dg-error "in .strub. context" } */
+
+    ret += i_disabled (); /* { dg-error "in .strub. context" } */
+
+    ret += xinternal (); /* { dg-error "in .strub. context" } */
+    ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+  return ret;
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const1.c b/gcc/testsuite/c-c++-common/torture/strub-const1.c
new file mode 100644
index 00000000000..100fb0c59a9
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const1.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub const function call, we issue an asm statement
+   to make sure the watermark passed to it is held in memory before the call,
+   and another to make sure it is not assumed to be unchanged.  */
+
+int __attribute__ ((__strub__, __const__))
+f() {
+  return 0;
+}
+
+int
+g() {
+  return f();
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const2.c b/gcc/testsuite/c-c++-common/torture/strub-const2.c
new file mode 100644
index 00000000000..9e818ac9748
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const2.c
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-const function call, we issue an
+   asm statement to make sure the watermark passed to it is held in memory
+   before the call, and another to make sure it is not assumed to be
+   unchanged.  */
+
+int __attribute__ ((__strub__))
+#if ! __OPTIMIZE__
+__attribute__ ((__const__))
+#endif
+f() {
+  return 0;
+}
+
+int
+g() {
+  return f();
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const3.c b/gcc/testsuite/c-c++-common/torture/strub-const3.c
new file mode 100644
index 00000000000..d40e8aa45cb
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const3.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub const wrapping call, we issue an asm statement
+   to make sure the watermark passed to it is held in memory before the call,
+   and another to make sure it is not assumed to be unchanged.  */
+
+int __attribute__ ((__strub__ (2), __const__))
+f() {
+  return 0;
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const4.c b/gcc/testsuite/c-c++-common/torture/strub-const4.c
new file mode 100644
index 00000000000..d4cbdaf10f3
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const4.c
@@ -0,0 +1,17 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-const wrapping call, we issue an
+   asm statement to make sure the watermark passed to it is held in memory
+   before the call, and another to make sure it is not assumed to be
+   unchanged.  */
+
+int __attribute__ ((__strub__ (2)))
+#if ! __OPTIMIZE__
+__attribute__ ((__const__))
+#endif
+f() {
+  return 0;
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data5.c b/gcc/testsuite/c-c++-common/torture/strub-data5.c
index 251790d4bbb..07e25af9c53 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data5.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data5.c
@@ -1,15 +1,13 @@
 /* { dg-do compile } */
 /* { dg-options "-fstrub=default -Werror" } */
 
-/* The pointer itself is a strub variable, that would enable internal strubbing
-   if its value was used.  Here, it's only overwritten, so no strub.  */
 typedef int __attribute__ ((__strub__)) strub_int;
 strub_int *ptr;
 
 int *f () {
-  return ptr; /* { dg-warn "incompatible" } */
+  return ptr; /* { dg-warning "incompatible" } */
 }
 
 strub_int *g () {
-  return f (); /* { dg-warn "incompatible" } */
+  return f (); /* { dg-warning "incompatible" } */
 }
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure1.c b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
new file mode 100644
index 00000000000..cb223da6efc
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub pure function call, we issue an asm statement
+   to make sure the watermark passed to it is not assumed to be unchanged.  */
+
+int __attribute__ ((__strub__, __pure__))
+f() {
+  static int i; /* Stop it from being detected as const.  */
+  return i;
+}
+
+int
+g() {
+  return f();
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure2.c b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
new file mode 100644
index 00000000000..67d1434b1f8
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-pure function call, we issue an asm
+   statement to make sure the watermark passed to it is not assumed to be
+   unchanged.  */
+
+int __attribute__ ((__strub__))
+#if ! __OPTIMIZE__ /* At -O0, implicit pure detection doesn't run.  */
+__attribute__ ((__pure__))
+#endif
+f() {
+  static int i; /* Stop it from being detected as const.  */
+  return i;
+}
+
+int
+g() {
+  return f();
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure3.c b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
new file mode 100644
index 00000000000..59f02ea901f
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub pure wrapping call, we issue an asm statement
+   to make sure the watermark passed to it is not assumed to be unchanged.  */
+
+int __attribute__ ((__strub__ (2), __pure__))
+f() {
+  static int i; /* Stop it from being detected as const.  */
+  return i;
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure4.c b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
new file mode 100644
index 00000000000..973e909217d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
@@ -0,0 +1,17 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-pure wrapping call, we issue an asm
+   statement to make sure the watermark passed to it is not assumed to be
+   unchanged.  */
+
+int __attribute__ ((__strub__ (2)))
+#if ! __OPTIMIZE__ /* At -O0, implicit pure detection doesn't run.  */
+__attribute__ ((__pure__))
+#endif
+f() {
+  static int i; /* Stop it from being detected as const.  */
+  return i;
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run1.c b/gcc/testsuite/c-c++-common/torture/strub-run1.c
new file mode 100644
index 00000000000..828a4cc2998
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run1.c
@@ -0,0 +1,85 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default" } */
+
+/* Check that a non-strub function leaves a string behind in the stack, and that
+   equivalent strub functions don't.  */
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+static inline __attribute__ ((__always_inline__, __strub__ (3)))
+char *
+leak_string (void)
+{
+  /* We use this variable to avoid any stack red zone.  Stack scrubbing covers
+     it, but __builtin_stack_address, that we take as a reference, doesn't, so
+     if e.g. callable() were to store the string in the red zone, we wouldn't
+     find it because it would be outside the range we searched.  */
+  typedef void __attribute__ ((__strub__ (3))) callable_t (char *);
+  callable_t *f = 0;
+
+  char s[sizeof (test_string)];
+  __builtin_strcpy (s, test_string);
+  asm ("" : "+m" (s), "+r" (f));
+
+  if (__builtin_expect (!f, 1))
+    return __builtin_stack_address ();
+
+  f (s);
+  return 0;
+}
+
+static inline __attribute__ ((__always_inline__))
+int
+look_for_string (char *e)
+{
+  char *p = __builtin_stack_address ();
+
+  if (p == e)
+    __builtin_abort ();
+
+  if (p > e)
+    {
+      char *q = p;
+      p = e;
+      e = q;
+    }
+
+  for (char *re = e - sizeof (test_string); p < re; p++)
+    for (int i = 0; p[i] == test_string[i]; i++)
+      if (i == sizeof (test_string) - 1)
+	return i;
+
+  return 0;
+}
+
+static __attribute__ ((__noinline__, __noclone__))
+char *
+callable ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ (1)))
+char *
+at_calls ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ (2)))
+char *
+internal ()
+{
+  return leak_string ();
+}
+
+int main ()
+{
+  if (!look_for_string (callable ()))
+    __builtin_abort ();
+  if (look_for_string (at_calls ()))
+    __builtin_abort ();
+  if (look_for_string (internal ()))
+    __builtin_abort ();
+  __builtin_exit (0);
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run2.c b/gcc/testsuite/c-c++-common/torture/strub-run2.c
new file mode 100644
index 00000000000..5794b694b2d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run2.c
@@ -0,0 +1,75 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default" } */
+
+/* Check that a non-strub function leaves a string behind in the stack, and that
+   equivalent strub functions don't.  */
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+static inline __attribute__ ((__always_inline__, __strub__ (3)))
+char *
+leak_string (void)
+{
+  int len = sizeof (test_string);
+  asm ("" : "+rm" (len));
+  char s[len];
+  __builtin_strcpy (s, test_string);
+  asm ("" : "+m" (s));
+  return __builtin_stack_address ();
+}
+
+static inline __attribute__ ((__always_inline__))
+int
+look_for_string (char *e)
+{
+  char *p = __builtin_stack_address ();
+
+  if (p == e)
+    __builtin_abort ();
+
+  if (p > e)
+    {
+      char *q = p;
+      p = e;
+      e = q;
+    }
+
+  for (char *re = e - sizeof (test_string); p < re; p++)
+    for (int i = 0; p[i] == test_string[i]; i++)
+      if (i == sizeof (test_string) - 1)
+	return i;
+
+  return 0;
+}
+
+static __attribute__ ((__noinline__, __noclone__))
+char *
+callable ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ (1)))
+char *
+at_calls ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ (2)))
+char *
+internal ()
+{
+  return leak_string ();
+}
+
+int main ()
+{
+  if (!look_for_string (callable ()))
+    __builtin_abort ();
+  if (look_for_string (at_calls ()))
+    __builtin_abort ();
+  if (look_for_string (internal ()))
+    __builtin_abort ();
+  __builtin_exit (0);
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run3.c b/gcc/testsuite/c-c++-common/torture/strub-run3.c
new file mode 100644
index 00000000000..7da79055959
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run3.c
@@ -0,0 +1,75 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default" } */
+/* { dg-require-effective-target alloca } */
+
+/* Check that a non-strub function leaves a string behind in the stack, and that
+   equivalent strub functions don't.  */
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+static inline __attribute__ ((__always_inline__, __strub__ (3)))
+char *
+leak_string (void)
+{
+  int len = sizeof (test_string);
+  char *s = __builtin_alloca (len);
+  __builtin_strcpy (s, test_string);
+  asm ("" : "+m" (s));
+  return __builtin_stack_address ();
+}
+
+static inline __attribute__ ((__always_inline__))
+int
+look_for_string (char *e)
+{
+  char *p = __builtin_stack_address ();
+
+  if (p == e)
+    __builtin_abort ();
+
+  if (p > e)
+    {
+      char *q = p;
+      p = e;
+      e = q;
+    }
+
+  for (char *re = e - sizeof (test_string); p < re; p++)
+    for (int i = 0; p[i] == test_string[i]; i++)
+      if (i == sizeof (test_string) - 1)
+	return i;
+
+  return 0;
+}
+
+static __attribute__ ((__noinline__, __noclone__))
+char *
+callable ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ (1)))
+char *
+at_calls ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ (2)))
+char *
+internal ()
+{
+  return leak_string ();
+}
+
+int main ()
+{
+  if (!look_for_string (callable ()))
+    __builtin_abort ();
+  if (look_for_string (at_calls ()))
+    __builtin_abort ();
+  if (look_for_string (internal ()))
+    __builtin_abort ();
+  __builtin_exit (0);
+}
diff --git a/gcc/testsuite/g++.dg/wrappers/strub1.C b/gcc/testsuite/g++.dg/strub-run1.C
similarity index 90%
rename from gcc/testsuite/g++.dg/wrappers/strub1.C
rename to gcc/testsuite/g++.dg/strub-run1.C
index a474a929649..754291eaa01 100644
--- a/gcc/testsuite/g++.dg/wrappers/strub1.C
+++ b/gcc/testsuite/g++.dg/strub-run1.C
@@ -1,4 +1,5 @@
 // { dg-do run }
+// { dg-options "-fstrub=internal" }
 
 // Check that we don't get extra copies.
 
diff --git a/gcc/testsuite/g++.dg/wrappers/strub2.C b/gcc/testsuite/g++.dg/wrappers/strub2.C
deleted file mode 100644
index 25a62166448..00000000000
--- a/gcc/testsuite/g++.dg/wrappers/strub2.C
+++ /dev/null
@@ -1,22 +0,0 @@
-// { dg-do run }
-
-// This doesn't really test anything yet.  We should mark the
-// variables as requiring strubbing, and somehow check that the
-// wrapped functions take the parameter by reference.
-
-struct T {
-  char d[32 * sizeof(void*)];
-};
-
-T foo (T q) { asm ("" : : "m"(q)); return q; }
-T bar (T p) { return foo (p); }
-
-T tmp;
-T tmp2;
-
-int main () {
-  __builtin_memset (&tmp, 0x55, sizeof (tmp));
-  tmp2 = bar (tmp);
-  if (__builtin_memcmp (&tmp, &tmp2, sizeof (tmp)))
-    __builtin_abort ();
-}
diff --git a/gcc/testsuite/g++.dg/wrappers/strub3.C b/gcc/testsuite/g++.dg/wrappers/strub3.C
deleted file mode 100644
index e1b51cd0399..00000000000
--- a/gcc/testsuite/g++.dg/wrappers/strub3.C
+++ /dev/null
@@ -1,22 +0,0 @@
-// { dg-do run }
-
-// This doesn't really test anything yet.  We should mark the
-// variables as requiring strubbing, and somehow check that the
-// wrapped functions take the parameter by reference.
-
-struct T {
-  char d[32 * sizeof(void*)];
-};
-
-static T foo (T q) { asm ("" : : "m"(q)); return q; }
-static T bar (T p) { return foo (p); }
-
-T tmp;
-T tmp2;
-
-int main () {
-  __builtin_memset (&tmp, 0x55, sizeof (tmp));
-  tmp2 = bar (tmp);
-  if (__builtin_memcmp (&tmp, &tmp2, sizeof (tmp)))
-    __builtin_abort ();
-}
diff --git a/gcc/testsuite/g++.dg/wrappers/strub4.C b/gcc/testsuite/g++.dg/wrappers/strub4.C
deleted file mode 100644
index d021fca88e4..00000000000
--- a/gcc/testsuite/g++.dg/wrappers/strub4.C
+++ /dev/null
@@ -1,18 +0,0 @@
-// { dg-do run }
-
-namespace
-{
-  class foo
-  {
-  public:
-    foo();
-  };
-
-  foo::foo() {}
-
-  foo bar;
-}
-
-int main()
-{
-}


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [gcc(refs/users/aoliva/heads/strub)] more tests, red zones, and deferred strubbing
@ 2021-08-05 14:04 Alexandre Oliva
  0 siblings, 0 replies; 6+ messages in thread
From: Alexandre Oliva @ 2021-08-05 14:04 UTC (permalink / raw)
  To: gcc-cvs

https://gcc.gnu.org/g:bc3198f3f02085722a0ccbf0c4b43be5dee8b981

commit bc3198f3f02085722a0ccbf0c4b43be5dee8b981
Author: Alexandre Oliva <oliva@adacore.com>
Date:   Thu Aug 5 11:03:49 2021 -0300

    more tests, red zones, and deferred strubbing

Diff:
---
 gcc/builtins.c                                     |  86 +++++-
 gcc/ipa-strub.c                                    | 340 +++++++++++++--------
 gcc/ipa-strub.h                                    |   1 +
 gcc/testsuite/c-c++-common/strub-O2.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O2fni.c           |   6 +-
 gcc/testsuite/c-c++-common/strub-O3fni.c           |   4 +-
 gcc/testsuite/c-c++-common/strub-Og.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-Os.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-all1.c            |   4 +-
 gcc/testsuite/c-c++-common/strub-apply1.c          |  15 +
 gcc/testsuite/c-c++-common/strub-apply2.c          |  12 +
 gcc/testsuite/c-c++-common/strub-apply3.c          |   8 +
 gcc/testsuite/c-c++-common/strub-apply4.c          |  21 ++
 gcc/testsuite/c-c++-common/strub-at-calls1.c       |   4 +-
 gcc/testsuite/c-c++-common/strub-default1.c        |   8 +-
 gcc/testsuite/c-c++-common/strub-defer-O1.c        |   7 +
 gcc/testsuite/c-c++-common/strub-defer-O2.c        |   8 +
 gcc/testsuite/c-c++-common/strub-defer-O3.c        |  93 ++++++
 gcc/testsuite/c-c++-common/strub-defer-Os.c        |   7 +
 gcc/testsuite/c-c++-common/strub-internal1.c       |   4 +-
 gcc/testsuite/c-c++-common/strub-parms1.c          |  48 +++
 gcc/testsuite/c-c++-common/strub-parms2.c          |  36 +++
 gcc/testsuite/c-c++-common/strub-parms3.c          |  58 ++++
 .../c-c++-common/torture/strub-callable1.c         |  13 +
 .../c-c++-common/torture/strub-callable2.c         | 264 ++++++++++++++++
 gcc/testsuite/c-c++-common/torture/strub-const1.c  |  18 ++
 gcc/testsuite/c-c++-common/torture/strub-const2.c  |  22 ++
 gcc/testsuite/c-c++-common/torture/strub-const3.c  |  13 +
 gcc/testsuite/c-c++-common/torture/strub-const4.c  |  17 ++
 gcc/testsuite/c-c++-common/torture/strub-data5.c   |   6 +-
 gcc/testsuite/c-c++-common/torture/strub-pure1.c   |  18 ++
 gcc/testsuite/c-c++-common/torture/strub-pure2.c   |  22 ++
 gcc/testsuite/c-c++-common/torture/strub-pure3.c   |  13 +
 gcc/testsuite/c-c++-common/torture/strub-pure4.c   |  17 ++
 gcc/testsuite/c-c++-common/torture/strub-run1.c    |  85 ++++++
 gcc/testsuite/c-c++-common/torture/strub-run2.c    |  75 +++++
 gcc/testsuite/c-c++-common/torture/strub-run3.c    |  75 +++++
 .../g++.dg/{wrappers/strub1.C => strub-run1.C}     |   1 +
 gcc/testsuite/g++.dg/wrappers/strub2.C             |  22 --
 gcc/testsuite/g++.dg/wrappers/strub3.C             |  22 --
 gcc/testsuite/g++.dg/wrappers/strub4.C             |  18 --
 41 files changed, 1290 insertions(+), 207 deletions(-)

diff --git a/gcc/builtins.c b/gcc/builtins.c
index f387d93974f..8e5d6e57aef 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -80,6 +80,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "attr-fnspec.h"
 #include "demangle.h"
 #include "gimple-range.h"
+#include "ipa-strub.h"
 
 struct target_builtins default_target_builtins;
 #if SWITCHABLE_TARGET
@@ -7926,7 +7927,23 @@ expand_builtin_strub_enter (tree exp)
   if (optimize < 1 || flag_no_inline)
     return NULL_RTX;
 
-  rtx stktop = expand_builtin_stack_address ();
+  rtx stktop = NULL_RTX;
+
+#if 1 || defined RED_ZONE_SIZE
+  if (tree wmptr = (optimize
+		    ? find_watermark_parm (current_function_decl)
+		    : NULL_TREE))
+    {
+      tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+      tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+				 build_int_cst (TREE_TYPE (wmptr), 0));
+      rtx wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+      stktop = force_reg (ptr_mode, wmark);
+    }
+#endif
+
+  if (!stktop)
+    stktop = expand_builtin_stack_address ();
 
   tree wmptr = CALL_EXPR_ARG (exp, 0);
   tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
@@ -7952,6 +7969,52 @@ expand_builtin_strub_update (tree exp)
 
   rtx stktop = expand_builtin_stack_address ();
 
+#ifdef RED_ZONE_SIZE
+  /* Here's how the strub enter, update and leave functions deal with red zones.
+
+     If it weren't for red zones, update, called from within a strub context,
+     would bump the watermark to the top of the stack.  Enter and leave, running
+     in the caller, would use the caller's top of stack address both to
+     initialize the watermark passed to the callee, and to start strubbing the
+     stack afterwards.
+
+     Ideally, we'd update the watermark so as to cover the used amount of red
+     zone, and strub starting at the caller's other end of the (presumably
+     unused) red zone.  Normally, only leaf functions use the red zone, but at
+     this point we can't tell whether a function is a leaf, nor can we tell how
+     much of the red zone it uses.  Furthermore, some strub contexts may have
+     been inlined so that update and leave are called from the same stack frame,
+     and the strub builtins may all have been inlined, turning a strub function
+     into a leaf.
+
+     So cleaning the range from the caller's stack pointer (one end of the red
+     zone) to the (potentially inlined) callee's (other end of the) red zone
+     could scribble over the caller's own red zone.
+
+     We avoid this possibility by arranging for callers that are strub contexts
+     to use their own watermark as the strub starting point.  So, if A calls B,
+     and B calls C, B will tell A to strub up to the end of B's red zone, and
+     will strub itself only the part of C's stack frame and red zone that
+     doesn't overlap with B's.  With that, we don't need to know who's leaf and
+     who isn't: inlined calls will shrink their strub window to zero, each
+     remaining call will strub some portion of the stack, and eventually the
+     strub context will return to a caller that isn't a strub context itself,
+     that will therefore use its own stack pointer as the strub starting point.
+     It's not a leaf, because strub contexts can't be inlined into non-strub
+     contexts, so it doesn't use the red zone, and it will therefore correctly
+     strub up the callee's stack frame up to the end of the callee's red zone.
+     Neat!  */
+  if (true /* (flags_from_decl_or_type (current_function_decl) & ECF_LEAF) */)
+    {
+      poly_int64 red_zone_size = RED_ZONE_SIZE;
+#if STACK_GROWS_DOWNWARD
+      red_zone_size = -red_zone_size;
+#endif
+      stktop = plus_constant (ptr_mode, stktop, red_zone_size);
+      stktop = force_reg (ptr_mode, stktop);
+    }
+#endif
+
   tree wmptr = CALL_EXPR_ARG (exp, 0);
   tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
   tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
@@ -7982,7 +8045,23 @@ expand_builtin_strub_leave (tree exp)
   if (optimize < 2 || flag_no_inline)
     return NULL_RTX;
 
-  rtx stktop = expand_builtin_stack_address ();
+  rtx stktop = NULL_RTX;
+
+#if 1 || defined RED_ZONE_SIZE
+  if (tree wmptr = (optimize
+		    ? find_watermark_parm (current_function_decl)
+		    : NULL_TREE))
+    {
+      tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+      tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+				 build_int_cst (TREE_TYPE (wmptr), 0));
+      rtx wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+      stktop = force_reg (ptr_mode, wmark);
+    }
+#endif
+
+  if (!stktop)
+    stktop = expand_builtin_stack_address ();
 
   tree wmptr = CALL_EXPR_ARG (exp, 0);
   tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
@@ -8000,6 +8079,9 @@ expand_builtin_strub_leave (tree exp)
   rtx end = stktop;
 #endif
 
+  /* We're going to modify it, so make sure it's not e.g. the stack pointer.  */
+  base = copy_to_reg (base);
+
   rtx_code_label *done = gen_label_rtx ();
   do_compare_rtx_and_jump (base, end, LT, STACK_UNSIGNED,
 			   ptr_mode, NULL_RTX, done, NULL,
diff --git a/gcc/ipa-strub.c b/gcc/ipa-strub.c
index e302f0ec1c5..0364b7e1305 100644
--- a/gcc/ipa-strub.c
+++ b/gcc/ipa-strub.c
@@ -210,10 +210,16 @@ get_strub_mode_from_attr (tree strub_attr, bool var_p = false)
   return mode;
 }
 
+static enum strub_mode
+get_strub_mode_from_decl (tree fndecl)
+{
+  return get_strub_mode_from_attr (get_strub_attr_from_decl (fndecl));
+}
+
 static enum strub_mode
 get_strub_mode (cgraph_node *node)
 {
-  return get_strub_mode_from_attr (get_strub_attr_from_decl (node->decl));
+  return get_strub_mode_from_decl (node->decl);
 }
 
 static enum strub_mode
@@ -263,7 +269,7 @@ calls_builtin_apply_args_p (cgraph_node *node, bool report = false)
 	break;
 
       sorry_at (gimple_location (e->call_stmt),
-		"at-calls strub does not support call to %qD",
+		"at-calls %<strub%> does not support call to %qD",
 		cdecl);
     }
 
@@ -292,7 +298,8 @@ can_strub_p (cgraph_node *node, bool report = false)
 	return result;
 
       sorry_at (DECL_SOURCE_LOCATION (node->decl),
-		"%qD is not eligible for strub because of attribute %<noipa%>",
+		"%qD is not eligible for %<strub%>"
+		" because of attribute %<noipa%>",
 		node->decl);
     }
 
@@ -352,7 +359,7 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 	return result;
 
       sorry_at (DECL_SOURCE_LOCATION (node->decl),
-		"%qD is not eligible for internal strub"
+		"%qD is not eligible for internal %<strub%>"
 		" because of attribute %<noclone%>",
 		node->decl);
     }
@@ -372,7 +379,7 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 	return result;
 
       sorry_at (gimple_location (e->call_stmt),
-		"internal strub does not support call to %qD",
+		"internal %<strub%> does not support call to %qD",
 		cdecl);
     }
 
@@ -411,7 +418,7 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 	      return result;
 
 	    sorry_at (gimple_location (label_stmt),
-		      "internal strub does not support user labels");
+		      "internal %<strub%> does not support user labels");
 	  }
     }
 
@@ -425,16 +432,16 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 	return result;
 
       sorry_at (DECL_SOURCE_LOCATION (node->decl),
-		"%qD has too many arguments for internal strub",
+		"%qD has too many arguments for internal %<strub%>",
 		node->decl);
     }
 
-  if (result)
-    /* Since we're not changing the function identity proper, just
-       moving its full implementation, we *could* disable
-       fun->cannot_be_copied_reason and/or temporarily drop a noclone
-       attribute.  */
-    gcc_checking_assert (tree_versionable_function_p (node->decl));
+  /* Since we're not changing the function identity proper, just
+     moving its full implementation, we *could* disable
+     fun->cannot_be_copied_reason and/or temporarily drop a noclone
+     attribute.  */
+  gcc_checking_assert (!result || !node->has_gimple_body_p ()
+		       || tree_versionable_function_p (node->decl));
 
   return result;
 }
@@ -489,8 +496,22 @@ strub_callable_builtin_p (cgraph_node *node)
     case BUILT_IN_NONE:
       gcc_unreachable ();
 
-      /* ??? Make all builtins callable.  We wish to make any builtin call the
-	 compiler might introduce on its own callable.  Anything that is
+      /* This temporarily allocates stack for the call, and we can't reasonably
+	 update the watermark for that.  Besides, we don't check the actual call
+	 target, nor its signature, and it seems to be overkill to as much as
+	 try to do so.  */
+    case BUILT_IN_APPLY:
+      return false;
+
+      /* Conversely, this shouldn't be called from within strub contexts, since
+	 the caller may have had its signature modified.  STRUB_INTERNAL is ok,
+	 the call will remain in the STRUB_WRAPPER, and removed from the
+	 STRUB_WRAPPED clone.  */
+    case BUILT_IN_APPLY_ARGS:
+      return false;
+
+      /* ??? Make all other builtins callable.  We wish to make any builtin call
+	 the compiler might introduce on its own callable.  Anything that is
 	 predictable enough as to be known not to allow stack data that should
 	 be strubbed to unintentionally escape to non-strub contexts can be
 	 allowed, and pretty much every builtin appears to fit this description.
@@ -698,7 +719,8 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
 	{
 	  gcc_checking_assert (analyze_body);
 	  error_at (DECL_SOURCE_LOCATION (node->decl),
-		    "%qD requires strub, but no viable strub mode was found",
+		    "%qD requires %<strub%>,"
+		    " but no viable %<strub%> mode was found",
 		    node->decl);
 	  break;
 	}
@@ -751,7 +773,7 @@ set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
 	       && mode == STRUB_INLINABLE))
 	{
 	  error_at (DECL_SOURCE_LOCATION (node->decl),
-		    "strub mode %i selected for %qD, when %i was requested",
+		    "%<strub%> mode %i selected for %qD, when %i was requested",
 		    (int) mode, node->decl,
 		    (int) get_strub_mode_from_attr (attr));
 	  if (node->alias)
@@ -979,7 +1001,7 @@ verify_strub ()
 	if (callee_mode == STRUB_DISABLED
 	    || callee_mode == STRUB_INTERNAL)
 	  error_at (gimple_location (e->call_stmt),
-		    "indirect non-strub call in strub context %qD",
+		    "indirect non-%<strub%> call in %<strub%> context %qD",
 		    node->decl);
       }
 
@@ -987,9 +1009,22 @@ verify_strub ()
       {
 	gcc_checking_assert (!e->indirect_unknown_callee);
 	if (!strub_callable_from_p (e->callee, node))
-	  error_at (gimple_location (e->call_stmt),
-		    "calling non-strub %qD in strub context %qD",
-		    e->callee->decl, node->decl);
+	  {
+	    if (get_strub_mode (e->callee) == STRUB_INLINABLE)
+	      error_at (gimple_location (e->call_stmt),
+			"calling %<always_inline%> %<strub%> %qD"
+			" in non-%<strub%> context %qD",
+			e->callee->decl, node->decl);
+	    else if (fndecl_built_in_p (e->callee->decl, BUILT_IN_APPLY_ARGS)
+		     && get_strub_mode (node) == STRUB_INTERNAL)
+	      /* This is ok, it will be kept in the STRUB_WRAPPER, and removed
+		 from the STRUB_WRAPPED's strub context.  */
+	      continue;
+	    else
+	      error_at (gimple_location (e->call_stmt),
+			"calling non-%<strub%> %qD in %<strub%> context %qD",
+			e->callee->decl, node->decl);
+	  }
       }
   }
 
@@ -1067,27 +1102,25 @@ public:
 
   /* Use a distinct ptr_type_node to denote the watermark, so that we can
      recognize it in arg lists and avoid modifying types twice.  */
-  DEF_TYPE (wmt, build_distinct_type_copy (ptr_type_node))
+  DEF_TYPE (wmt, build_variant_type_copy (ptr_type_node))
 
-  DEF_TYPE (pwmt, build_pointer_type (get_wmt ()))
+  DEF_TYPE (pwmt, build_reference_type (get_wmt ()))
 
   DEF_TYPE (qpwmt,
 	    build_qualified_type (get_pwmt (),
 				  TYPE_QUAL_RESTRICT
-				  | TYPE_QUAL_CONST))
+				  /* | TYPE_QUAL_CONST */))
 
-  DEF_TYPE (pptr, build_pointer_type (ptr_type_node))
-
-  DEF_TYPE (qpptr,
-	    build_qualified_type (get_pptr (),
+  DEF_TYPE (qptr,
+	    build_qualified_type (ptr_type_node,
 				  TYPE_QUAL_RESTRICT
 				  | TYPE_QUAL_CONST))
 
   DEF_TYPE (qpvalst,
-	    build_qualified_type (build_pointer_type
+	    build_qualified_type (build_reference_type
 				  (va_list_type_node),
 				  TYPE_QUAL_RESTRICT
-				  | TYPE_QUAL_CONST))
+				  /* | TYPE_QUAL_CONST */))
 
 #undef DEF_TYPE
 
@@ -1159,12 +1192,13 @@ public:
 
   DEF_IDENT (watermark_ptr)
   DEF_IDENT (va_list_ptr)
-  DEF_IDENT (apply_args_ptr)
+  DEF_IDENT (apply_args)
 
 #undef DEF_IDENT
 
   static inline int adjust_at_calls_type (tree);
   static inline void adjust_at_calls_call (cgraph_edge *, int);
+  static inline void adjust_at_calls_calls (cgraph_node *);
 
   static inline gimple_seq
   call_update_watermark (tree wmptr, cgraph_node *node, profile_count count,
@@ -1493,6 +1527,39 @@ ipa_strub_set_mode_for_new_functions ()
   last_cgraph_order = symtab->order;
 }
 
+/* Return the PARM_DECL of the incoming watermark pointer, if there is one.  */
+tree
+find_watermark_parm (tree fndecl)
+{
+  switch (get_strub_mode_from_decl (fndecl))
+    {
+    case STRUB_WRAPPED:
+    case STRUB_AT_CALLS:
+    case STRUB_AT_CALLS_OPT:
+      break;
+
+    case STRUB_INTERNAL:
+    case STRUB_WRAPPER:
+    case STRUB_CALLABLE:
+    case STRUB_DISABLED:
+    case STRUB_INLINABLE:
+      return NULL_TREE;
+
+    default:
+      gcc_unreachable ();
+    }
+
+  for (tree parm = DECL_ARGUMENTS (fndecl); parm; parm = DECL_CHAIN (parm))
+    if (TREE_TYPE (parm) == pass_ipa_strub::get_qpwmt ())
+      {
+	gcc_checking_assert (DECL_NAME (parm)
+			     == pass_ipa_strub::get_watermark_ptr ());
+	return parm;
+      }
+
+  gcc_unreachable ();
+}
+
 /* Adjust a STRUB_AT_CALLS function TYPE, adding a watermark pointer if it
    hasn't been added yet.  Return the named argument count.  */
 int
@@ -1507,7 +1574,7 @@ pass_ipa_strub::adjust_at_calls_type (tree type)
   tree qpwmptrt = get_qpwmt ();
   while (*tlist && TREE_VALUE (*tlist) != void_type_node)
     {
-      /* The type has alreayd been adjusted.  */
+      /* The type has already been adjusted.  */
       if (TREE_VALUE (*tlist) == qpwmptrt)
 	return named_args;
       named_args++;
@@ -1581,22 +1648,37 @@ pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
 			 && (TREE_TYPE (gimple_call_arg (ocall, named_args))
 			     == get_pwmt ())));
 
-  /* ??? If it's a (tail?) call within a strub context, maybe pass on
-     the strub watermark instead of wrapping the call.  */
-
-  /* Initialize the watermark before the call.  */
-  tree swm = create_tmp_var (get_wmt (), ".strub.watermark");
-  TREE_ADDRESSABLE (swm) = true;
-  tree swmp = build1 (ADDR_EXPR, get_pwmt (), swm);
+  /* If we're already within a strub context, pass on the incoming watermark
+     pointer, and omit the enter and leave calls around the modified call.  */
+  tree swm, swmp = (false && /* This is disabled for now, to give the
+				red-zone-capable builtin expanders more thorough
+				testing.  */
+		    (optimize_size || optimize > 2)
+		    ? find_watermark_parm (e->caller->decl)
+		    : NULL_TREE);
+  bool omit_own_watermark = swmp;
+  if (omit_own_watermark)
+    swm = build2 (MEM_REF,
+		  TREE_TYPE (TREE_TYPE (swmp)),
+		  swmp,
+		  build_int_cst (TREE_TYPE (swmp), 0));
+  else
+    {
+      swm = create_tmp_var (get_wmt (), ".strub.watermark");
+      TREE_ADDRESSABLE (swm) = true;
+      swmp = build1 (ADDR_EXPR, get_pwmt (), swm);
 
-  tree enter = get_enter ();
-  gcall *stptr = gimple_build_call (enter, 1,
-				    unshare_expr (swmp));
-  gsi_insert_before (&gsi, stptr, GSI_SAME_STMT);
+      /* Initialize the watermark before the call.  */
+      tree enter = get_enter ();
+      gcall *stptr = gimple_build_call (enter, 1,
+					unshare_expr (swmp));
+      gsi_insert_before (&gsi, stptr, GSI_SAME_STMT);
 #if !IMPLICIT_CGRAPH_EDGES
-  e->caller->create_edge (cgraph_node::get_create (enter),
-			  stptr, gsi_bb (gsi)->count, false);
+      e->caller->create_edge (cgraph_node::get_create (enter),
+			      stptr, gsi_bb (gsi)->count, false);
 #endif
+    }
+
 
   /* Replace the call with one that passes the swmp argument first.  */
   gcall *wrcall;
@@ -1674,12 +1756,12 @@ pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
 		       build_tree_list
 		       (build_tree_list
 			(NULL_TREE, build_string (2, "=m")),
-			swm));
+			unshare_expr (swm)));
 	vec_safe_push (inputs,
 		       build_tree_list
 		       (build_tree_list
 			(NULL_TREE, build_string (1, "m")),
-			swm));
+			unshare_expr (swm)));
 	gasm *forcemod = gimple_build_asm_vec ("", inputs, outputs,
 					       NULL, NULL);
 	gimple_seq_add_stmt (&seq, forcemod);
@@ -1693,7 +1775,7 @@ pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
 			   build_tree_list
 			   (build_tree_list
 			    (NULL_TREE, build_string (1, "m")),
-			    swm));
+			    unshare_expr (swm)));
 	    gasm *force_store = gimple_build_asm_vec ("", inputs, NULL,
 						      NULL, NULL);
 	    gsi_insert_before (&gsi, force_store, GSI_SAME_STMT);
@@ -1701,17 +1783,69 @@ pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
       }
 #endif
 
-    gcall *sleave = gimple_build_call (get_leave (), 1,
-				       unshare_expr (swmp));
-    gimple_seq_add_stmt (&seq, sleave);
+    if (!omit_own_watermark)
+      {
+	gcall *sleave = gimple_build_call (get_leave (), 1,
+					   unshare_expr (swmp));
+	gimple_seq_add_stmt (&seq, sleave);
 
-    gassign *clobber = gimple_build_assign (swm,
-					    build_clobber
-					    (TREE_TYPE (swm)));
-    gimple_seq_add_stmt (&seq, clobber);
+	gassign *clobber = gimple_build_assign (swm,
+						build_clobber
+						(TREE_TYPE (swm)));
+	gimple_seq_add_stmt (&seq, clobber);
+      }
   }
 
-  gsi_insert_finally_seq_after_call (gsi, seq);
+  if (seq)
+    gsi_insert_finally_seq_after_call (gsi, seq);
+}
+
+void
+pass_ipa_strub::adjust_at_calls_calls (cgraph_node *node)
+{
+  /* Adjust unknown-callee indirect calls with STRUB_AT_CALLS types within
+     onode.  */
+  if (node->indirect_calls)
+    {
+      push_cfun (DECL_STRUCT_FUNCTION (node->decl));
+      for (cgraph_edge *e = node->indirect_calls; e; e = e->next_callee)
+	{
+	  gcc_checking_assert (e->indirect_unknown_callee);
+
+	  tree callee_fntype = gimple_call_fntype (e->call_stmt);
+	  enum strub_mode callee_mode
+	    = get_strub_mode_from_type (callee_fntype);
+
+	  if (callee_mode != STRUB_AT_CALLS
+	      && callee_mode != STRUB_AT_CALLS_OPT)
+	    continue;
+
+	  int named_args = adjust_at_calls_type (callee_fntype);
+
+	  adjust_at_calls_call (e, named_args);
+	}
+      pop_cfun ();
+    }
+
+  if (node->callees)
+    {
+      push_cfun (DECL_STRUCT_FUNCTION (node->decl));
+      for (cgraph_edge *e = node->callees; e; e = e->next_callee)
+	{
+	  gcc_checking_assert (!e->indirect_unknown_callee);
+
+	  enum strub_mode callee_mode = get_strub_mode (e->callee);
+
+	  if (callee_mode != STRUB_AT_CALLS
+	      && callee_mode != STRUB_AT_CALLS_OPT)
+	    continue;
+
+	  int named_args = adjust_at_calls_type (TREE_TYPE (e->callee->decl));
+
+	  adjust_at_calls_call (e, named_args);
+	}
+      pop_cfun ();
+    }
 }
 
 unsigned int
@@ -1849,54 +1983,13 @@ pass_ipa_strub::execute (function *)
     if (!onode->has_gimple_body_p ())
       continue;
 
-    /* Adjust unknown-callee indirect calls with STRUB_AT_CALLS types within
-       onode.  */
-    if (onode->indirect_calls)
-      {
-	push_cfun (DECL_STRUCT_FUNCTION (onode->decl));
-	for (cgraph_edge *e = onode->indirect_calls; e; e = e->next_callee)
-	  {
-	    gcc_checking_assert (e->indirect_unknown_callee);
-
-	    tree callee_fntype = gimple_call_fntype (e->call_stmt);
-	    enum strub_mode callee_mode
-	      = get_strub_mode_from_type (callee_fntype);
-
-	    if (callee_mode != STRUB_AT_CALLS
-		&& callee_mode != STRUB_AT_CALLS_OPT)
-	      continue;
-
-	    int named_args = adjust_at_calls_type (callee_fntype);
-
-	    adjust_at_calls_call (e, named_args);
-	  }
-	pop_cfun ();
-      }
-
-    if (onode->callees)
-      {
-	push_cfun (DECL_STRUCT_FUNCTION (onode->decl));
-	for (cgraph_edge *e = onode->callees; e; e = e->next_callee)
-	  {
-	    gcc_checking_assert (!e->indirect_unknown_callee);
-
-	    enum strub_mode callee_mode = get_strub_mode (e->callee);
-
-	    if (callee_mode != STRUB_AT_CALLS
-		&& callee_mode != STRUB_AT_CALLS_OPT)
-	      continue;
-
-	    int named_args = adjust_at_calls_type (TREE_TYPE (e->callee->decl));
-
-	    adjust_at_calls_call (e, named_args);
-	  }
-	pop_cfun ();
-      }
-
     enum strub_mode mode = get_strub_mode (onode);
 
     if (mode != STRUB_INTERNAL)
-      continue;
+      {
+	adjust_at_calls_calls (onode);
+	continue;
+      }
 
 #if 0
     /* Hmm, this is an i386-specific attribute.  Do we need machine-specific
@@ -1909,7 +2002,7 @@ pass_ipa_strub::execute (function *)
     if (!DECL_STRUCT_FUNCTION (onode->decl))
       {
 	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"not splitting struct-less function %qD for stack scrubbing",
+		"not splitting struct-less function %qD for %<strub%>",
 		onode->decl);
 	continue;
       }
@@ -1917,7 +2010,7 @@ pass_ipa_strub::execute (function *)
     if (!onode->lowered)
       {
 	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"not splitting non-lowered function %qD for stack scrubbing",
+		"not splitting non-lowered function %qD for %<strub%>",
 		onode->decl);
 	continue;
       }
@@ -1929,7 +2022,7 @@ pass_ipa_strub::execute (function *)
     if (!tree_versionable_function_p (onode->decl))
       {
 	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"%qD cannot be split for stack scrubbing",
+		"%qD cannot be split for %<strub%>",
 		onode->decl);
 	continue;
       }
@@ -1956,7 +2049,7 @@ pass_ipa_strub::execute (function *)
 	{
 	  ipa_adjusted_param aaadj = {};
 	  aaadj.op = IPA_PARAM_OP_NEW;
-	  aaadj.type = get_qpptr ();
+	  aaadj.type = get_qptr ();
 	  vec_safe_push (nparms, aaadj);
 	}
 
@@ -1982,7 +2075,7 @@ pass_ipa_strub::execute (function *)
     if (!nnode)
       {
 	error_at (DECL_SOURCE_LOCATION (onode->decl),
-		  "failed to split %qD for stack scrubbing",
+		  "failed to split %qD for %<strub%>",
 		  onode->decl);
 	continue;
       }
@@ -1996,6 +2089,8 @@ pass_ipa_strub::execute (function *)
     set_strub_mode_to (onode, STRUB_WRAPPER);
     set_strub_mode_to (nnode, STRUB_WRAPPED);
 
+    adjust_at_calls_calls (nnode);
+
     /* Decide which of the wrapped function's parms we want to turn into
        references to the argument passed to the wrapper.  In general, we want to
        copy small arguments, and avoid copying large ones.  Variable-sized array
@@ -2181,8 +2276,8 @@ pass_ipa_strub::execute (function *)
 	     only of reading because const/pure.  */
 	  if (apply_args)
 	    {
-	      nspec[curlen++] = (no_writes_p ? 'r' : '.');
-	      nspec[curlen++] = (no_writes_p ? 't' : ' ');
+	      nspec[curlen++] = 'r';
+	      nspec[curlen++] = ' ';
 	    }
 	  if (is_stdarg)
 	    {
@@ -2485,24 +2580,24 @@ pass_ipa_strub::execute (function *)
     }
 
     {
-      tree aaptr = NULL_TREE;
+      tree aaval = NULL_TREE;
       tree vaptr = NULL_TREE;
       tree wmptr = NULL_TREE;
       for (tree arg = DECL_ARGUMENTS (nnode->decl); arg; arg = DECL_CHAIN (arg))
 	{
-	  aaptr = vaptr;
+	  aaval = vaptr;
 	  vaptr = wmptr;
 	  wmptr = arg;
 	}
 
       if (!apply_args)
-	aaptr = NULL_TREE;
+	aaval = NULL_TREE;
       /* The trailing args are [apply_args], [va_list_ptr], and
 	 watermark.  If we don't have a va_list_ptr, the penultimate
 	 argument is apply_args.
        */
       else if (!is_stdarg)
-	aaptr = vaptr;
+	aaval = vaptr;
 
       if (!is_stdarg)
 	vaptr = NULL_TREE;
@@ -2522,10 +2617,10 @@ pass_ipa_strub::execute (function *)
 
       if (apply_args)
 	{
-	  DECL_NAME (aaptr) = get_apply_args_ptr ();
-	  DECL_ARTIFICIAL (aaptr) = 1;
-	  DECL_IGNORED_P (aaptr) = 1;
-	  TREE_USED (aaptr) = 1;
+	  DECL_NAME (aaval) = get_apply_args ();
+	  DECL_ARTIFICIAL (aaval) = 1;
+	  DECL_IGNORED_P (aaval) = 1;
+	  TREE_USED (aaval) = 1;
 	}
 
       push_cfun (DECL_STRUCT_FUNCTION (nnode->decl));
@@ -2582,9 +2677,10 @@ pass_ipa_strub::execute (function *)
 	    else if (fndecl && is_stdarg
 		     && fndecl_built_in_p (fndecl, BUILT_IN_VA_START))
 	      {
-		if (builtin_decl_explicit (BUILT_IN_VA_START) != fndecl)
-		  sorry_at (gimple_location (call),
-			    "nonstandard stdarg conventions");
+		/* Using a non-default stdarg ABI makes the function ineligible
+		   for internal strub.  */
+		gcc_checking_assert (builtin_decl_explicit (BUILT_IN_VA_START)
+				     == fndecl);
 		tree bvacopy = builtin_decl_explicit (BUILT_IN_VA_COPY);
 		gimple_call_set_fndecl (call, bvacopy);
 		tree arg = vaptr;
@@ -2607,7 +2703,9 @@ pass_ipa_strub::execute (function *)
 		     && fndecl_built_in_p (fndecl, BUILT_IN_APPLY_ARGS))
 	      {
 		tree lhs = gimple_call_lhs (call);
-		gassign *assign = gimple_build_assign (lhs, aaptr);
+		gimple *assign = (lhs
+				  ? gimple_build_assign (lhs, aaval)
+				  : gimple_build_nop ());
 		gsi_replace (&gsi, assign, true);
 		cgraph_edge::remove (e);
 	      }
@@ -2650,7 +2748,7 @@ pass_ipa_strub::execute (function *)
 
       if (apply_args)
 	{
-	  tree aalst = create_tmp_var (ptr_type_node, ".strub.appargs");
+	  tree aalst = create_tmp_var (ptr_type_node, ".strub.apply_args");
 	  tree bappargs = builtin_decl_explicit (BUILT_IN_APPLY_ARGS);
 	  gcall *appargs = gimple_build_call (bappargs, 0);
 	  gimple_call_set_lhs (appargs, aalst);
diff --git a/gcc/ipa-strub.h b/gcc/ipa-strub.h
index cadbca5002a..ea8ee857e8e 100644
--- a/gcc/ipa-strub.h
+++ b/gcc/ipa-strub.h
@@ -23,3 +23,4 @@ along with GCC; see the file COPYING3.  If not see
    doesn't have to be called directly by CALLER, but the returned
    value says nothing about intervening functions.  */
 extern bool strub_inlinable_p (cgraph_node *callee, cgraph_node *caller);
+extern tree find_watermark_parm (tree fndecl);
diff --git a/gcc/testsuite/c-c++-common/strub-O2.c b/gcc/testsuite/c-c++-common/strub-O2.c
index 9cc39e763b1..7848c46d179 100644
--- a/gcc/testsuite/c-c++-common/strub-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-O2.c
@@ -13,4 +13,4 @@ int f() {
 /* { dg-final { scan-rtl-dump-not "strub_enter" "expand" } } */
 /* { dg-final { scan-rtl-dump-not "strub_update" "expand" } } */
 /* { dg-final { scan-rtl-dump "strub_leave" "expand" } } */
-/* { dg-final { scan-rtl-dump "\n\[(\]call_insn\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
+/* { dg-final { scan-rtl-dump "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-O2fni.c b/gcc/testsuite/c-c++-common/strub-O2fni.c
index 51cae845d5f..85a8f76785e 100644
--- a/gcc/testsuite/c-c++-common/strub-O2fni.c
+++ b/gcc/testsuite/c-c++-common/strub-O2fni.c
@@ -1,7 +1,7 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fdump-rtl-expand -fno-inline-functions" } */
+/* { dg-options "-O2 -fstrub=default -fdump-rtl-expand -fno-inline" } */
 
-/* With -fno-inline-functions, none of the strub builtins are inlined.  */
+/* With -fno-inline, none of the strub builtins are inlined.  */
 
 int __attribute__ ((__strub__)) var;
 
@@ -12,4 +12,4 @@ int f() {
 /* { dg-final { scan-rtl-dump "strub_enter" "expand" } } */
 /* { dg-final { scan-rtl-dump "strub_update" "expand" } } */
 /* { dg-final { scan-rtl-dump "strub_leave" "expand" } } */
-/* { dg-final { scan-rtl-dump-not "\n\[(\]call_insn\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
+/* { dg-final { scan-rtl-dump-not "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-O3fni.c b/gcc/testsuite/c-c++-common/strub-O3fni.c
index 8f67b613be8..a2eedfd96b2 100644
--- a/gcc/testsuite/c-c++-common/strub-O3fni.c
+++ b/gcc/testsuite/c-c++-common/strub-O3fni.c
@@ -1,7 +1,7 @@
 /* { dg-do compile } */
 /* { dg-options "-O3 -fstrub=default -fdump-rtl-expand -fno-inline" } */
 
-/* With -fno-inline-functions, none of the strub builtins are inlined.  */
+/* With -fno-inline, none of the strub builtins are inlined.  */
 
 int __attribute__ ((__strub__)) var;
 
@@ -12,4 +12,4 @@ int f() {
 /* { dg-final { scan-rtl-dump "strub_enter" "expand" } } */
 /* { dg-final { scan-rtl-dump "strub_update" "expand" } } */
 /* { dg-final { scan-rtl-dump "strub_leave" "expand" } } */
-/* { dg-final { scan-rtl-dump-not "\n\[(\]call_insn\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
+/* { dg-final { scan-rtl-dump-not "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-Og.c b/gcc/testsuite/c-c++-common/strub-Og.c
index 6f60349573f..e5cb1f60541 100644
--- a/gcc/testsuite/c-c++-common/strub-Og.c
+++ b/gcc/testsuite/c-c++-common/strub-Og.c
@@ -13,4 +13,4 @@ int f() {
 /* { dg-final { scan-rtl-dump-not "strub_enter" "expand" } } */
 /* { dg-final { scan-rtl-dump "strub_update" "expand" } } */
 /* { dg-final { scan-rtl-dump "strub_leave" "expand" } } */
-/* { dg-final { scan-rtl-dump-not "\n\[(\]call_insn\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
+/* { dg-final { scan-rtl-dump-not "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-Os.c b/gcc/testsuite/c-c++-common/strub-Os.c
index 5d1c08a7528..194aacc2c05 100644
--- a/gcc/testsuite/c-c++-common/strub-Os.c
+++ b/gcc/testsuite/c-c++-common/strub-Os.c
@@ -15,4 +15,4 @@ int f() {
 /* { dg-final { scan-rtl-dump-not "strub_enter" "expand" } } */
 /* { dg-final { scan-rtl-dump-not "strub_update" "expand" } } */
 /* { dg-final { scan-rtl-dump "strub_leave" "expand" } } */
-/* { dg-final { scan-rtl-dump-not "\n\[(\]call_insn\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
+/* { dg-final { scan-rtl-dump-not "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-all1.c b/gcc/testsuite/c-c++-common/strub-all1.c
index 54daf84656c..46e84bf6560 100644
--- a/gcc/testsuite/c-c++-common/strub-all1.c
+++ b/gcc/testsuite/c-c++-common/strub-all1.c
@@ -3,9 +3,9 @@
 
 /* h becomes STRUB_CALLABLE, rather than STRUB_INLINABLE, because of the
    strub-enabling -fstrub flag, and gets inlined before pass_ipa_strub.  */
-static void
+static inline void
 __attribute__ ((__always_inline__))
-h() {  /* { dg-warning "might not be inlinable" } */
+h() {
 }
 
 /* g becomes STRUB_AT_CALLS, because of the flag.  */
diff --git a/gcc/testsuite/c-c++-common/strub-apply1.c b/gcc/testsuite/c-c++-common/strub-apply1.c
new file mode 100644
index 00000000000..f180b17f30e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply1.c
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default" } */
+
+void __attribute__ ((__strub__ (3)))
+apply_function (void *args)
+{
+  __builtin_apply (0, args, 0);
+}
+
+void __attribute__ ((__strub__ (2)))
+apply_args (int i, int j, double d)
+{
+  void *args = __builtin_apply_args ();
+  apply_function (args);
+}
diff --git a/gcc/testsuite/c-c++-common/strub-apply2.c b/gcc/testsuite/c-c++-common/strub-apply2.c
new file mode 100644
index 00000000000..379a54b73b7
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply2.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default" } */
+
+extern void __attribute__ ((__strub__))
+apply_function (void *args);
+
+void __attribute__ ((__strub__))
+apply_args (int i, int j, double d) /* { dg-error "selected" } */
+{
+  void *args = __builtin_apply_args (); /* { dg-message "does not support" } */
+  apply_function (args);
+}
diff --git a/gcc/testsuite/c-c++-common/strub-apply3.c b/gcc/testsuite/c-c++-common/strub-apply3.c
new file mode 100644
index 00000000000..9b4786be698
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply3.c
@@ -0,0 +1,8 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default" } */
+
+void __attribute__ ((__strub__))
+apply_function (void *args)
+{
+  __builtin_apply (0, args, 0); /* { dg-error "in .strub. context" } */
+}
diff --git a/gcc/testsuite/c-c++-common/strub-apply4.c b/gcc/testsuite/c-c++-common/strub-apply4.c
new file mode 100644
index 00000000000..409f747743e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply4.c
@@ -0,0 +1,21 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fstrub=default -fdump-ipa-strubm" } */
+
+/* Check that implicit enabling of strub mode selects internal strub when the
+   function uses __builtin_apply_args, that prevents the optimization to
+   at-calls mode.  */
+
+int __attribute__ ((__strub__)) var;
+
+static inline void
+apply_args (int i, int j, double d)
+{
+  var++;
+  __builtin_apply_args ();
+}
+
+void f() {
+  apply_args (1, 2, 3);
+}
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 1 "strubm" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-at-calls1.c b/gcc/testsuite/c-c++-common/strub-at-calls1.c
index 0d1b9fce833..d964b07ae5d 100644
--- a/gcc/testsuite/c-c++-common/strub-at-calls1.c
+++ b/gcc/testsuite/c-c++-common/strub-at-calls1.c
@@ -3,9 +3,9 @@
 
 /* h becomes STRUB_CALLABLE, rather than STRUB_INLINABLE, because of the
    strub-enabling -fstrub flag, and gets inlined before pass_ipa_strub.  */
-static void
+static inline void
 __attribute__ ((__always_inline__))
-h() {  /* { dg-warning "might not be inlinable" } */
+h() {
 }
 
 /* g becomes STRUB_AT_CALLS, because of the flag.  */
diff --git a/gcc/testsuite/c-c++-common/strub-default1.c b/gcc/testsuite/c-c++-common/strub-default1.c
index a1e1803aadc..d579ec62f55 100644
--- a/gcc/testsuite/c-c++-common/strub-default1.c
+++ b/gcc/testsuite/c-c++-common/strub-default1.c
@@ -3,12 +3,12 @@
 
 static int __attribute__ ((__strub__)) var;
 
-/* h becomes STRUB_STRUB_INLINABLE, because of the use of the strub variable,
+/* h becomes STRUB_INLINABLE, because of the use of the strub variable,
    and the always_inline flag.  It would get inlined before pass_ipa_strub, if
    it weren't for the error.  */
-static void
+static inline void
 __attribute__ ((__always_inline__))
-h() {  /* { dg-warning "might not be inlinable" } */
+h() {
   var++;
 }
 
@@ -34,7 +34,7 @@ f() {
 /* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 1 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-3\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]-3\[)\]" 1 "strub" } } */
 /* { dg-final { scan-ipa-dump-times "strub \[(\]-4\[)\]" 1 "strub" } } */
 /* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 1 "strub" } } */
 /* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O1.c b/gcc/testsuite/c-c++-common/strub-defer-O1.c
new file mode 100644
index 00000000000..7b04eea35d9
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-O1.c
@@ -0,0 +1,7 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default -O1" } */
+
+/* Check that a strub function called by another strub function does NOT defer
+   the strubbing to its caller at -O1.  */
+
+#include "strub-defer-O2.c"
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O2.c b/gcc/testsuite/c-c++-common/strub-defer-O2.c
new file mode 100644
index 00000000000..67d96419a5e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-O2.c
@@ -0,0 +1,8 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default -O2" } */
+
+/* Check that a strub function called by another strub function does NOT defer
+   the strubbing to its caller at -O2.  */
+
+#define EXPECT_DEFERRAL !
+#include "strub-defer-O3.c"
diff --git a/gcc/testsuite/c-c++-common/strub-defer-O3.c b/gcc/testsuite/c-c++-common/strub-defer-O3.c
new file mode 100644
index 00000000000..34828d2711e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-O3.c
@@ -0,0 +1,93 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default -O3" } */
+
+/* Check that a strub function called by another strub function defers the
+   strubbing to its caller at -O3.  */
+
+#ifndef EXPECT_DEFERRAL
+# define EXPECT_DEFERRAL
+#endif
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+static inline __attribute__ ((__always_inline__, __strub__ (3)))
+char *
+leak_string (void)
+{
+  /* We use this variable to avoid any stack red zone.  Stack scrubbing covers
+     it, but __builtin_stack_address, that we take as a reference, doesn't, so
+     if e.g. callable() were to store the string in the red zone, we wouldn't
+     find it because it would be outside the range we searched.  */
+  typedef void __attribute__ ((__strub__ (3))) callable_t (char *);
+  callable_t *f = 0;
+
+  char s[sizeof (test_string)];
+  __builtin_strcpy (s, test_string);
+  asm ("" : "+m" (s), "+r" (f));
+
+  if (__builtin_expect (!f, 1))
+    return (char*)__builtin_stack_address ();
+
+  f (s);
+  return 0;
+}
+
+static inline __attribute__ ((__always_inline__, __strub__ (3)))
+int
+look_for_string (char *e)
+{
+  char *p = (char*)__builtin_stack_address ();
+
+  if (p == e)
+    __builtin_abort ();
+
+  if (p > e)
+    {
+      char *q = p;
+      p = e;
+      e = q;
+    }
+
+  for (char *re = e - sizeof (test_string); p < re; p++)
+    for (int i = 0; p[i] == test_string[i]; i++)
+      if (i == sizeof (test_string) - 1)
+	return i;
+
+  return 0;
+}
+
+static __attribute__ ((__strub__ (1), __noinline__, __noclone__))
+char *
+at_calls ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ (1)))
+char *
+deferred_at_calls ()
+{
+  char *ret = at_calls ();
+  if (EXPECT_DEFERRAL !look_for_string (ret))
+    __builtin_abort ();
+  return ret;
+}
+
+static __attribute__ ((__strub__ (2)))
+char *
+deferred_internal ()
+{
+  char *ret = at_calls ();
+  if (EXPECT_DEFERRAL !look_for_string (ret))
+    __builtin_abort ();
+  return ret;
+}
+
+int main ()
+{
+  if (look_for_string (deferred_at_calls ()))
+    __builtin_abort ();
+  if (look_for_string (deferred_internal ()))
+    __builtin_abort ();
+  __builtin_exit (0);
+}
diff --git a/gcc/testsuite/c-c++-common/strub-defer-Os.c b/gcc/testsuite/c-c++-common/strub-defer-Os.c
new file mode 100644
index 00000000000..b273660aea1
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-defer-Os.c
@@ -0,0 +1,7 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default -Os" } */
+
+/* Check that a strub function called by another strub function defers the
+   strubbing to its caller at -Os.  */
+
+#include "strub-defer-O3.c"
diff --git a/gcc/testsuite/c-c++-common/strub-internal1.c b/gcc/testsuite/c-c++-common/strub-internal1.c
index b9bd787df0a..a74658c9ac9 100644
--- a/gcc/testsuite/c-c++-common/strub-internal1.c
+++ b/gcc/testsuite/c-c++-common/strub-internal1.c
@@ -3,9 +3,9 @@
 
 /* h becomes STRUB_CALLABLE, rather than STRUB_INLINABLE, because of the
    strub-enabling -fstrub flag, and gets inlined before pass_ipa_strub.  */
-static void
+static inline void
 __attribute__ ((__always_inline__))
-h() {  /* { dg-warning "might not be inlinable" } */
+h() {
 }
 
 /* g becomes STRUB_INTERNAL because of the flag, and gets split into
diff --git a/gcc/testsuite/c-c++-common/strub-parms1.c b/gcc/testsuite/c-c++-common/strub-parms1.c
new file mode 100644
index 00000000000..0422ccc7f7d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-parms1.c
@@ -0,0 +1,48 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+#include <stdarg.h>
+
+void __attribute__ ((__strub__ (2)))
+small_args (int i, long long l, void *p, void **q, double d, char c)
+{
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid small_args.strub.\[0-9\]* \[(\]int i, long long int l, void \\* p, void \\* \\* q, double d, char c, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " small_args.strub.\[0-9\]* \[(\]\[^&\]*&.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+
+
+struct large_arg {
+  int x[128];
+};
+
+void __attribute__ ((__strub__ (2)))
+large_byref_arg (struct large_arg la)
+{
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid large_byref_arg.strub.\[0-9\]* \[(\]struct large_arg & la, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " large_byref_arg.strub.\[0-9\]* \[(\]&\[^&\]*&.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+
+void __attribute__ ((__strub__ (2)))
+std_arg (int i, ...)
+{
+  va_list vl;
+  va_start (vl, i);
+  va_end (vl);
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid std_arg.strub.\[0-9\]* \[(\]int i, \[^&,\]* &\[^&,\]*.strub.va_list_ptr, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " std_arg.strub.\[0-9\]* \[(\]\[^&\]*&.strub.va_list.\[0-9\]*, &.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_start \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_copy \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_end \\(" 2 "strub" } } */
+
+void __attribute__ ((__strub__ (2)))
+apply_args (int i, int j, double d)
+{
+  __builtin_apply_args ();
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid apply_args.strub.\[0-9\]* \[(\]int i, int j, double d, void \\*\[^&,\]*.strub.apply_args, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " apply_args.strub.\[0-9\]* \[(\]\[^&\]*.strub.apply_args.\[0-9\]*_\[0-9\]*, &.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-parms2.c b/gcc/testsuite/c-c++-common/strub-parms2.c
new file mode 100644
index 00000000000..aaf02a2cd46
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-parms2.c
@@ -0,0 +1,36 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+#include <stdarg.h>
+
+void __attribute__ ((__strub__ (1)))
+small_args (int i, long long l, void *p, void **q, double d, char c)
+{
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid small_args \[(\]int i, long long int l, void \\* p, void \\* \\* q, double d, char c, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+
+
+struct large_arg {
+  int x[128];
+};
+
+void __attribute__ ((__strub__ (1)))
+large_byref_arg (struct large_arg la)
+{
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid large_byref_arg \[(\]struct large_arg la, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+
+void __attribute__ ((__strub__ (1)))
+std_arg (int i, ...)
+{
+  va_list vl;
+  va_start (vl, i);
+  va_end (vl);
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid std_arg \[(\]int i, void \\* &\[^&,\]*.strub.watermark_ptr\[, .]*\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_start \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-not "va_copy \\(" "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_end \\(" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-parms3.c b/gcc/testsuite/c-c++-common/strub-parms3.c
new file mode 100644
index 00000000000..2846098160d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-parms3.c
@@ -0,0 +1,58 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that uses of a strub variable implicitly enables internal strub for
+   publicly-visible functions, and causes the same transformations to their
+   signatures as those in strub-parms1.c.  */
+
+#include <stdarg.h>
+
+int __attribute__ ((__strub__)) var;
+
+void
+small_args (int i, long long l, void *p, void **q, double d, char c)
+{
+  var++;
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid small_args.strub.\[0-9\]* \[(\]int i, long long int l, void \\* p, void \\* \\* q, double d, char c, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " small_args.strub.\[0-9\]* \[(\]\[^&\]*&.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+
+
+struct large_arg {
+  int x[128];
+};
+
+void
+large_byref_arg (struct large_arg la)
+{
+  var++;
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid large_byref_arg.strub.\[0-9\]* \[(\]struct large_arg & la, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " large_byref_arg.strub.\[0-9\]* \[(\]&\[^&\]*&.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+
+void
+std_arg (int i, ...)
+{
+  va_list vl;
+  va_start (vl, i);
+  var++;
+  va_end (vl);
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid std_arg.strub.\[0-9\]* \[(\]int i, \[^&,\]* &\[^&,\]*.strub.va_list_ptr, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " std_arg.strub.\[0-9\]* \[(\]\[^&\]*&.strub.va_list.\[0-9\]*, &.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_start \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_copy \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_end \\(" 2 "strub" } } */
+
+void
+apply_args (int i, int j, double d)
+{
+  var++;
+  __builtin_apply_args ();
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid apply_args.strub.\[0-9\]* \[(\]int i, int j, double d, void \\*\[^&,\]*.strub.apply_args, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " apply_args.strub.\[0-9\]* \[(\]\[^&\]*.strub.apply_args.\[0-9\]*_\[0-9\]*, &.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable1.c b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
new file mode 100644
index 00000000000..45965f275c9
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub -fno-inline" } */
+
+/* Check that strub and non-strub functions can be called from non-strub
+   contexts, and that strub and callable functions can be called from strub
+   contexts.  */
+
+#define OMIT_IMPERMISSIBLE_CALLS 1
+#include "strub-callable2.c"
+
+/* { dg-final { scan-ipa-dump-times "strub_enter" 45 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_update" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_leave" 45 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable2.c b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
new file mode 100644
index 00000000000..38935e3270b
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
@@ -0,0 +1,264 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default" } */
+
+/* Check that impermissible (cross-strub-context) calls are reported.  */
+
+extern int __attribute__ ((__strub__ (3))) xcallable (void);
+extern int __attribute__ ((__strub__ (2))) xinternal (void);
+extern int __attribute__ ((__strub__ (1))) xat_calls (void);
+extern int __attribute__ ((__strub__ (0))) xdisabled (void);
+
+int __attribute__ ((__strub__ (3))) callable (void);
+int __attribute__ ((__strub__ (2))) internal (void);
+int __attribute__ ((__strub__ (1))) at_calls (void);
+int __attribute__ ((__strub__ (0))) disabled (void);
+
+int __attribute__ ((__strub__)) var;
+int var_user (void);
+
+static inline int __attribute__ ((__always_inline__, __strub__ (3)))
+icallable (void);
+static inline int __attribute__ ((__always_inline__, __strub__ (2)))
+iinternal (void);
+static inline int __attribute__ ((__always_inline__, __strub__ (1)))
+iat_calls (void);
+static inline int __attribute__ ((__always_inline__, __strub__ (0)))
+idisabled (void);
+static inline int __attribute__ ((__always_inline__))
+ivar_user (void);
+
+static inline int __attribute__ ((__always_inline__, __strub__ (3)))
+i_callable (void) { return 0; }
+static inline int __attribute__ ((__always_inline__, __strub__ (2)))
+i_internal (void) { return var; }
+static inline int __attribute__ ((__always_inline__, __strub__ (1)))
+i_at_calls (void) { return var; }
+static inline int __attribute__ ((__always_inline__, __strub__ (0)))
+i_disabled (void) { return 0; }
+static inline int __attribute__ ((__always_inline__))
+i_var_user (void) { return var; }
+
+#define CALLS_GOOD_FOR_STRUB_CONTEXT(ISEP)	\
+  do {						\
+    ret += i ## ISEP ## at_calls ();		\
+    ret += i ## ISEP ## internal ();		\
+    ret += i ## ISEP ## var_user ();		\
+  } while (0)
+
+#define CALLS_GOOD_FOR_NONSTRUB_CONTEXT(ISEP)	\
+  do {						\
+    ret += internal ();				\
+    ret += disabled ();				\
+    ret += var_user ();				\
+						\
+    ret += i ## ISEP ## disabled ();		\
+						\
+    ret += xinternal ();			\
+    ret += xdisabled ();			\
+  } while (0)
+
+#define CALLS_GOOD_FOR_EITHER_CONTEXT(ISEP)	\
+  do {						\
+    ret += i ## ISEP ## callable ();		\
+						\
+    ret += callable ();				\
+    ret += at_calls ();				\
+						\
+    ret += xat_calls ();			\
+    ret += xcallable ();			\
+  } while (0)
+
+/* Not a strub context, so it can call anything.
+   Explicitly declared as callable even from within strub contexts.  */
+int __attribute__ ((__strub__ (3)))
+callable (void) {
+  int ret = 0;
+
+  /* CALLS_GOOD_FOR_STRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += iat_calls (); /* { dg-error "in non-.strub. context" } */
+    ret += iinternal (); /* { dg-error "in non-.strub. context" } */
+    ret += ivar_user (); /* { dg-error "in non-.strub. context" } */
+#endif
+  CALLS_GOOD_FOR_EITHER_CONTEXT();
+  CALLS_GOOD_FOR_NONSTRUB_CONTEXT();
+
+  return ret;
+}
+
+/* Internal strubbing means the body is a strub context, so it can only call
+   strub functions, and it's not itself callable from strub functions.  */
+int __attribute__ ((__strub__ (2)))
+internal (void) {
+  int ret = var;
+
+  CALLS_GOOD_FOR_STRUB_CONTEXT();
+  CALLS_GOOD_FOR_EITHER_CONTEXT();
+  /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += internal (); /* { dg-error "in .strub. context" } */
+    ret += disabled (); /* { dg-error "in .strub. context" } */
+    ret += var_user (); /* { dg-error "in .strub. context" } */
+
+    ret += idisabled (); /* { dg-error "in .strub. context" } */
+
+    ret += xinternal (); /* { dg-error "in .strub. context" } */
+    ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+  return ret;
+}
+
+int __attribute__ ((__strub__ (1)))
+at_calls (void) {
+  int ret = var;
+
+  CALLS_GOOD_FOR_STRUB_CONTEXT();
+  CALLS_GOOD_FOR_EITHER_CONTEXT();
+  /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += internal (); /* { dg-error "in .strub. context" } */
+    ret += disabled (); /* { dg-error "in .strub. context" } */
+    ret += var_user (); /* { dg-error "in .strub. context" } */
+
+    ret += idisabled (); /* { dg-error "in .strub. context" } */
+
+    ret += xinternal (); /* { dg-error "in .strub. context" } */
+    ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+  return ret;
+}
+
+int __attribute__ ((__strub__ (0)))
+disabled () {
+  int ret = 0;
+
+  /* CALLS_GOOD_FOR_STRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += iat_calls (); /* { dg-error "in non-.strub. context" } */
+    ret += iinternal (); /* { dg-error "in non-.strub. context" } */
+    ret += ivar_user (); /* { dg-error "in non-.strub. context" } */
+#endif
+  CALLS_GOOD_FOR_EITHER_CONTEXT();
+  CALLS_GOOD_FOR_NONSTRUB_CONTEXT();
+
+  return ret;
+}  
+
+int
+var_user (void) {
+  int ret = var;
+
+  CALLS_GOOD_FOR_STRUB_CONTEXT();
+  CALLS_GOOD_FOR_EITHER_CONTEXT();
+  /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += internal (); /* { dg-error "in .strub. context" } */
+    ret += disabled (); /* { dg-error "in .strub. context" } */
+    ret += var_user (); /* { dg-error "in .strub. context" } */
+
+    ret += idisabled (); /* { dg-error "in .strub. context" } */
+
+    ret += xinternal (); /* { dg-error "in .strub. context" } */
+    ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+  return ret;
+}
+
+int
+icallable (void)
+{
+  int ret = 0;
+
+  /* CALLS_GOOD_FOR_STRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += i_at_calls (); /* { dg-error "in non-.strub. context" } */
+    ret += i_internal (); /* { dg-error "in non-.strub. context" } */
+    ret += i_var_user (); /* { dg-error "in non-.strub. context" } */
+#endif
+  CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+  CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_);
+
+  return ret;
+}
+
+int
+iinternal (void) {
+  int ret = var;
+
+  CALLS_GOOD_FOR_STRUB_CONTEXT(_);
+  CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+  /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += internal (); /* { dg-error "in .strub. context" } */
+    ret += disabled (); /* { dg-error "in .strub. context" } */
+    ret += var_user (); /* { dg-error "in .strub. context" } */
+
+    ret += i_disabled (); /* { dg-error "in .strub. context" } */
+
+    ret += xinternal (); /* { dg-error "in .strub. context" } */
+    ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+  return ret;
+}
+
+int
+iat_calls (void) {
+  int ret = var;
+
+  CALLS_GOOD_FOR_STRUB_CONTEXT(_);
+  CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+  /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += internal (); /* { dg-error "in .strub. context" } */
+    ret += disabled (); /* { dg-error "in .strub. context" } */
+    ret += var_user (); /* { dg-error "in .strub. context" } */
+
+    ret += i_disabled (); /* { dg-error "in .strub. context" } */
+
+    ret += xinternal (); /* { dg-error "in .strub. context" } */
+    ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+  return ret;
+}
+
+int
+idisabled () {
+  int ret = 0;
+
+  /* CALLS_GOOD_FOR_STRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += i_at_calls (); /* { dg-error "in non-.strub. context" } */
+    ret += i_internal (); /* { dg-error "in non-.strub. context" } */
+    ret += i_var_user (); /* { dg-error "in non-.strub. context" } */
+#endif
+  CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+  CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_);
+
+  return ret;
+}  
+
+int
+ivar_user (void) {
+  int ret = var;
+
+  CALLS_GOOD_FOR_STRUB_CONTEXT(_);
+  CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+  /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += internal (); /* { dg-error "in .strub. context" } */
+    ret += disabled (); /* { dg-error "in .strub. context" } */
+    ret += var_user (); /* { dg-error "in .strub. context" } */
+
+    ret += i_disabled (); /* { dg-error "in .strub. context" } */
+
+    ret += xinternal (); /* { dg-error "in .strub. context" } */
+    ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+  return ret;
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const1.c b/gcc/testsuite/c-c++-common/torture/strub-const1.c
new file mode 100644
index 00000000000..100fb0c59a9
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const1.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub const function call, we issue an asm statement
+   to make sure the watermark passed to it is held in memory before the call,
+   and another to make sure it is not assumed to be unchanged.  */
+
+int __attribute__ ((__strub__, __const__))
+f() {
+  return 0;
+}
+
+int
+g() {
+  return f();
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const2.c b/gcc/testsuite/c-c++-common/torture/strub-const2.c
new file mode 100644
index 00000000000..9e818ac9748
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const2.c
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-const function call, we issue an
+   asm statement to make sure the watermark passed to it is held in memory
+   before the call, and another to make sure it is not assumed to be
+   unchanged.  */
+
+int __attribute__ ((__strub__))
+#if ! __OPTIMIZE__
+__attribute__ ((__const__))
+#endif
+f() {
+  return 0;
+}
+
+int
+g() {
+  return f();
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const3.c b/gcc/testsuite/c-c++-common/torture/strub-const3.c
new file mode 100644
index 00000000000..d40e8aa45cb
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const3.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub const wrapping call, we issue an asm statement
+   to make sure the watermark passed to it is held in memory before the call,
+   and another to make sure it is not assumed to be unchanged.  */
+
+int __attribute__ ((__strub__ (2), __const__))
+f() {
+  return 0;
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const4.c b/gcc/testsuite/c-c++-common/torture/strub-const4.c
new file mode 100644
index 00000000000..d4cbdaf10f3
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const4.c
@@ -0,0 +1,17 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-const wrapping call, we issue an
+   asm statement to make sure the watermark passed to it is held in memory
+   before the call, and another to make sure it is not assumed to be
+   unchanged.  */
+
+int __attribute__ ((__strub__ (2)))
+#if ! __OPTIMIZE__
+__attribute__ ((__const__))
+#endif
+f() {
+  return 0;
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data5.c b/gcc/testsuite/c-c++-common/torture/strub-data5.c
index 251790d4bbb..07e25af9c53 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data5.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data5.c
@@ -1,15 +1,13 @@
 /* { dg-do compile } */
 /* { dg-options "-fstrub=default -Werror" } */
 
-/* The pointer itself is a strub variable, that would enable internal strubbing
-   if its value was used.  Here, it's only overwritten, so no strub.  */
 typedef int __attribute__ ((__strub__)) strub_int;
 strub_int *ptr;
 
 int *f () {
-  return ptr; /* { dg-warn "incompatible" } */
+  return ptr; /* { dg-warning "incompatible" } */
 }
 
 strub_int *g () {
-  return f (); /* { dg-warn "incompatible" } */
+  return f (); /* { dg-warning "incompatible" } */
 }
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure1.c b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
new file mode 100644
index 00000000000..cb223da6efc
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub pure function call, we issue an asm statement
+   to make sure the watermark passed to it is not assumed to be unchanged.  */
+
+int __attribute__ ((__strub__, __pure__))
+f() {
+  static int i; /* Stop it from being detected as const.  */
+  return i;
+}
+
+int
+g() {
+  return f();
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure2.c b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
new file mode 100644
index 00000000000..67d1434b1f8
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-pure function call, we issue an asm
+   statement to make sure the watermark passed to it is not assumed to be
+   unchanged.  */
+
+int __attribute__ ((__strub__))
+#if ! __OPTIMIZE__ /* At -O0, implicit pure detection doesn't run.  */
+__attribute__ ((__pure__))
+#endif
+f() {
+  static int i; /* Stop it from being detected as const.  */
+  return i;
+}
+
+int
+g() {
+  return f();
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure3.c b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
new file mode 100644
index 00000000000..59f02ea901f
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub pure wrapping call, we issue an asm statement
+   to make sure the watermark passed to it is not assumed to be unchanged.  */
+
+int __attribute__ ((__strub__ (2), __pure__))
+f() {
+  static int i; /* Stop it from being detected as const.  */
+  return i;
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure4.c b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
new file mode 100644
index 00000000000..973e909217d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
@@ -0,0 +1,17 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-pure wrapping call, we issue an asm
+   statement to make sure the watermark passed to it is not assumed to be
+   unchanged.  */
+
+int __attribute__ ((__strub__ (2)))
+#if ! __OPTIMIZE__ /* At -O0, implicit pure detection doesn't run.  */
+__attribute__ ((__pure__))
+#endif
+f() {
+  static int i; /* Stop it from being detected as const.  */
+  return i;
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run1.c b/gcc/testsuite/c-c++-common/torture/strub-run1.c
new file mode 100644
index 00000000000..828a4cc2998
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run1.c
@@ -0,0 +1,85 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default" } */
+
+/* Check that a non-strub function leaves a string behind in the stack, and that
+   equivalent strub functions don't.  */
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+static inline __attribute__ ((__always_inline__, __strub__ (3)))
+char *
+leak_string (void)
+{
+  /* We use this variable to avoid any stack red zone.  Stack scrubbing covers
+     it, but __builtin_stack_address, that we take as a reference, doesn't, so
+     if e.g. callable() were to store the string in the red zone, we wouldn't
+     find it because it would be outside the range we searched.  */
+  typedef void __attribute__ ((__strub__ (3))) callable_t (char *);
+  callable_t *f = 0;
+
+  char s[sizeof (test_string)];
+  __builtin_strcpy (s, test_string);
+  asm ("" : "+m" (s), "+r" (f));
+
+  if (__builtin_expect (!f, 1))
+    return __builtin_stack_address ();
+
+  f (s);
+  return 0;
+}
+
+static inline __attribute__ ((__always_inline__))
+int
+look_for_string (char *e)
+{
+  char *p = __builtin_stack_address ();
+
+  if (p == e)
+    __builtin_abort ();
+
+  if (p > e)
+    {
+      char *q = p;
+      p = e;
+      e = q;
+    }
+
+  for (char *re = e - sizeof (test_string); p < re; p++)
+    for (int i = 0; p[i] == test_string[i]; i++)
+      if (i == sizeof (test_string) - 1)
+	return i;
+
+  return 0;
+}
+
+static __attribute__ ((__noinline__, __noclone__))
+char *
+callable ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ (1)))
+char *
+at_calls ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ (2)))
+char *
+internal ()
+{
+  return leak_string ();
+}
+
+int main ()
+{
+  if (!look_for_string (callable ()))
+    __builtin_abort ();
+  if (look_for_string (at_calls ()))
+    __builtin_abort ();
+  if (look_for_string (internal ()))
+    __builtin_abort ();
+  __builtin_exit (0);
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run2.c b/gcc/testsuite/c-c++-common/torture/strub-run2.c
new file mode 100644
index 00000000000..5794b694b2d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run2.c
@@ -0,0 +1,75 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default" } */
+
+/* Check that a non-strub function leaves a string behind in the stack, and that
+   equivalent strub functions don't.  */
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+static inline __attribute__ ((__always_inline__, __strub__ (3)))
+char *
+leak_string (void)
+{
+  int len = sizeof (test_string);
+  asm ("" : "+rm" (len));
+  char s[len];
+  __builtin_strcpy (s, test_string);
+  asm ("" : "+m" (s));
+  return __builtin_stack_address ();
+}
+
+static inline __attribute__ ((__always_inline__))
+int
+look_for_string (char *e)
+{
+  char *p = __builtin_stack_address ();
+
+  if (p == e)
+    __builtin_abort ();
+
+  if (p > e)
+    {
+      char *q = p;
+      p = e;
+      e = q;
+    }
+
+  for (char *re = e - sizeof (test_string); p < re; p++)
+    for (int i = 0; p[i] == test_string[i]; i++)
+      if (i == sizeof (test_string) - 1)
+	return i;
+
+  return 0;
+}
+
+static __attribute__ ((__noinline__, __noclone__))
+char *
+callable ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ (1)))
+char *
+at_calls ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ (2)))
+char *
+internal ()
+{
+  return leak_string ();
+}
+
+int main ()
+{
+  if (!look_for_string (callable ()))
+    __builtin_abort ();
+  if (look_for_string (at_calls ()))
+    __builtin_abort ();
+  if (look_for_string (internal ()))
+    __builtin_abort ();
+  __builtin_exit (0);
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run3.c b/gcc/testsuite/c-c++-common/torture/strub-run3.c
new file mode 100644
index 00000000000..7da79055959
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run3.c
@@ -0,0 +1,75 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default" } */
+/* { dg-require-effective-target alloca } */
+
+/* Check that a non-strub function leaves a string behind in the stack, and that
+   equivalent strub functions don't.  */
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+static inline __attribute__ ((__always_inline__, __strub__ (3)))
+char *
+leak_string (void)
+{
+  int len = sizeof (test_string);
+  char *s = __builtin_alloca (len);
+  __builtin_strcpy (s, test_string);
+  asm ("" : "+m" (s));
+  return __builtin_stack_address ();
+}
+
+static inline __attribute__ ((__always_inline__))
+int
+look_for_string (char *e)
+{
+  char *p = __builtin_stack_address ();
+
+  if (p == e)
+    __builtin_abort ();
+
+  if (p > e)
+    {
+      char *q = p;
+      p = e;
+      e = q;
+    }
+
+  for (char *re = e - sizeof (test_string); p < re; p++)
+    for (int i = 0; p[i] == test_string[i]; i++)
+      if (i == sizeof (test_string) - 1)
+	return i;
+
+  return 0;
+}
+
+static __attribute__ ((__noinline__, __noclone__))
+char *
+callable ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ (1)))
+char *
+at_calls ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ (2)))
+char *
+internal ()
+{
+  return leak_string ();
+}
+
+int main ()
+{
+  if (!look_for_string (callable ()))
+    __builtin_abort ();
+  if (look_for_string (at_calls ()))
+    __builtin_abort ();
+  if (look_for_string (internal ()))
+    __builtin_abort ();
+  __builtin_exit (0);
+}
diff --git a/gcc/testsuite/g++.dg/wrappers/strub1.C b/gcc/testsuite/g++.dg/strub-run1.C
similarity index 90%
rename from gcc/testsuite/g++.dg/wrappers/strub1.C
rename to gcc/testsuite/g++.dg/strub-run1.C
index a474a929649..754291eaa01 100644
--- a/gcc/testsuite/g++.dg/wrappers/strub1.C
+++ b/gcc/testsuite/g++.dg/strub-run1.C
@@ -1,4 +1,5 @@
 // { dg-do run }
+// { dg-options "-fstrub=internal" }
 
 // Check that we don't get extra copies.
 
diff --git a/gcc/testsuite/g++.dg/wrappers/strub2.C b/gcc/testsuite/g++.dg/wrappers/strub2.C
deleted file mode 100644
index 25a62166448..00000000000
--- a/gcc/testsuite/g++.dg/wrappers/strub2.C
+++ /dev/null
@@ -1,22 +0,0 @@
-// { dg-do run }
-
-// This doesn't really test anything yet.  We should mark the
-// variables as requiring strubbing, and somehow check that the
-// wrapped functions take the parameter by reference.
-
-struct T {
-  char d[32 * sizeof(void*)];
-};
-
-T foo (T q) { asm ("" : : "m"(q)); return q; }
-T bar (T p) { return foo (p); }
-
-T tmp;
-T tmp2;
-
-int main () {
-  __builtin_memset (&tmp, 0x55, sizeof (tmp));
-  tmp2 = bar (tmp);
-  if (__builtin_memcmp (&tmp, &tmp2, sizeof (tmp)))
-    __builtin_abort ();
-}
diff --git a/gcc/testsuite/g++.dg/wrappers/strub3.C b/gcc/testsuite/g++.dg/wrappers/strub3.C
deleted file mode 100644
index e1b51cd0399..00000000000
--- a/gcc/testsuite/g++.dg/wrappers/strub3.C
+++ /dev/null
@@ -1,22 +0,0 @@
-// { dg-do run }
-
-// This doesn't really test anything yet.  We should mark the
-// variables as requiring strubbing, and somehow check that the
-// wrapped functions take the parameter by reference.
-
-struct T {
-  char d[32 * sizeof(void*)];
-};
-
-static T foo (T q) { asm ("" : : "m"(q)); return q; }
-static T bar (T p) { return foo (p); }
-
-T tmp;
-T tmp2;
-
-int main () {
-  __builtin_memset (&tmp, 0x55, sizeof (tmp));
-  tmp2 = bar (tmp);
-  if (__builtin_memcmp (&tmp, &tmp2, sizeof (tmp)))
-    __builtin_abort ();
-}
diff --git a/gcc/testsuite/g++.dg/wrappers/strub4.C b/gcc/testsuite/g++.dg/wrappers/strub4.C
deleted file mode 100644
index d021fca88e4..00000000000
--- a/gcc/testsuite/g++.dg/wrappers/strub4.C
+++ /dev/null
@@ -1,18 +0,0 @@
-// { dg-do run }
-
-namespace
-{
-  class foo
-  {
-  public:
-    foo();
-  };
-
-  foo::foo() {}
-
-  foo bar;
-}
-
-int main()
-{
-}


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [gcc(refs/users/aoliva/heads/strub)] more tests, red zones, and deferred strubbing
@ 2021-08-05 13:42 Alexandre Oliva
  0 siblings, 0 replies; 6+ messages in thread
From: Alexandre Oliva @ 2021-08-05 13:42 UTC (permalink / raw)
  To: gcc-cvs

https://gcc.gnu.org/g:89318d8d4e34012c29aba2f6c72fda6a79450de2

commit 89318d8d4e34012c29aba2f6c72fda6a79450de2
Author: Alexandre Oliva <oliva@adacore.com>
Date:   Thu Aug 5 08:43:23 2021 -0300

    more tests, red zones, and deferred strubbing

Diff:
---
 gcc/builtins.c                                     | 971 ++++++++-------------
 gcc/ipa-strub.c                                    | 340 +++++---
 gcc/ipa-strub.h                                    |   1 +
 gcc/testsuite/c-c++-common/strub-O2.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O2fni.c           |   6 +-
 gcc/testsuite/c-c++-common/strub-O3fni.c           |   4 +-
 gcc/testsuite/c-c++-common/strub-Og.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-Os.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-all1.c            |   4 +-
 gcc/testsuite/c-c++-common/strub-apply1.c          |  15 +
 gcc/testsuite/c-c++-common/strub-apply2.c          |  12 +
 gcc/testsuite/c-c++-common/strub-apply3.c          |   8 +
 gcc/testsuite/c-c++-common/strub-apply4.c          |  21 +
 gcc/testsuite/c-c++-common/strub-at-calls1.c       |   4 +-
 gcc/testsuite/c-c++-common/strub-default1.c        |   8 +-
 gcc/testsuite/c-c++-common/strub-internal1.c       |   4 +-
 gcc/testsuite/c-c++-common/strub-parms1.c          |  48 +
 gcc/testsuite/c-c++-common/strub-parms2.c          |  36 +
 gcc/testsuite/c-c++-common/strub-parms3.c          |  58 ++
 .../c-c++-common/torture/strub-callable1.c         |  13 +
 .../c-c++-common/torture/strub-callable2.c         | 264 ++++++
 gcc/testsuite/c-c++-common/torture/strub-const1.c  |  18 +
 gcc/testsuite/c-c++-common/torture/strub-const2.c  |  22 +
 gcc/testsuite/c-c++-common/torture/strub-const3.c  |  13 +
 gcc/testsuite/c-c++-common/torture/strub-const4.c  |  17 +
 gcc/testsuite/c-c++-common/torture/strub-data5.c   |   6 +-
 gcc/testsuite/c-c++-common/torture/strub-pure1.c   |  18 +
 gcc/testsuite/c-c++-common/torture/strub-pure2.c   |  22 +
 gcc/testsuite/c-c++-common/torture/strub-pure3.c   |  13 +
 gcc/testsuite/c-c++-common/torture/strub-pure4.c   |  17 +
 gcc/testsuite/c-c++-common/torture/strub-run1.c    |  85 ++
 gcc/testsuite/c-c++-common/torture/strub-run2.c    |  75 ++
 gcc/testsuite/c-c++-common/torture/strub-run3.c    |  75 ++
 .../g++.dg/{wrappers/strub1.C => strub-run1.C}     |   1 +
 gcc/testsuite/g++.dg/wrappers/strub2.C             |  22 -
 gcc/testsuite/g++.dg/wrappers/strub3.C             |  22 -
 gcc/testsuite/g++.dg/wrappers/strub4.C             |  18 -
 37 files changed, 1454 insertions(+), 813 deletions(-)

diff --git a/gcc/builtins.c b/gcc/builtins.c
index f387d93974f..090001cd970 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -80,6 +80,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "attr-fnspec.h"
 #include "demangle.h"
 #include "gimple-range.h"
+#include "ipa-strub.h"
 
 struct target_builtins default_target_builtins;
 #if SWITCHABLE_TARGET
@@ -129,6 +130,7 @@ static rtx expand_builtin_va_copy (tree);
 static rtx inline_expand_builtin_bytecmp (tree, rtx);
 static rtx expand_builtin_strcmp (tree, rtx);
 static rtx expand_builtin_strncmp (tree, rtx, machine_mode);
+static rtx builtin_memcpy_read_str (void *, HOST_WIDE_INT, scalar_int_mode);
 static rtx expand_builtin_memchr (tree, rtx);
 static rtx expand_builtin_memcpy (tree, rtx);
 static rtx expand_builtin_memory_copy_args (tree dest, tree src, tree len,
@@ -145,6 +147,7 @@ static rtx expand_builtin_stpcpy (tree, rtx, machine_mode);
 static rtx expand_builtin_stpncpy (tree, rtx);
 static rtx expand_builtin_strncat (tree, rtx);
 static rtx expand_builtin_strncpy (tree, rtx);
+static rtx builtin_memset_gen_str (void *, HOST_WIDE_INT, scalar_int_mode);
 static rtx expand_builtin_memset (tree, rtx, machine_mode);
 static rtx expand_builtin_memset_args (tree, tree, tree, rtx, machine_mode, tree);
 static rtx expand_builtin_bzero (tree);
@@ -207,7 +210,6 @@ access_ref::access_ref (tree bound /* = NULL_TREE */,
 {
   /* Set to valid.  */
   offrng[0] = offrng[1] = 0;
-  offmax[0] = offmax[1] = 0;
   /* Invalidate.   */
   sizrng[0] = sizrng[1] = -1;
 
@@ -459,21 +461,6 @@ access_ref::size_remaining (offset_int *pmin /* = NULL */) const
   return sizrng[1] - or0;
 }
 
-/* Return true if the offset and object size are in range for SIZE.  */
-
-bool
-access_ref::offset_in_range (const offset_int &size) const
-{
-  if (size_remaining () < size)
-    return false;
-
-  if (base0)
-    return offmax[0] >= 0 && offmax[1] <= sizrng[1];
-
-  offset_int maxoff = wi::to_offset (TYPE_MAX_VALUE (ptrdiff_type_node));
-  return offmax[0] > -maxoff && offmax[1] < maxoff;
-}
-
 /* Add the range [MIN, MAX] to the offset range.  For known objects (with
    zero-based offsets) at least one of whose offset's bounds is in range,
    constrain the other (or both) to the bounds of the object (i.e., zero
@@ -510,8 +497,6 @@ void access_ref::add_offset (const offset_int &min, const offset_int &max)
       if (max >= 0)
 	{
 	  offrng[0] = 0;
-	  if (offmax[0] > 0)
-	    offmax[0] = 0;
 	  return;
 	}
 
@@ -528,12 +513,6 @@ void access_ref::add_offset (const offset_int &min, const offset_int &max)
 	offrng[0] = 0;
     }
 
-  /* Set the minimum and maximmum computed so far. */
-  if (offrng[1] < 0 && offrng[1] < offmax[0])
-    offmax[0] = offrng[1];
-  if (offrng[0] > 0 && offrng[0] > offmax[1])
-    offmax[1] = offrng[0];
-
   if (!base0)
     return;
 
@@ -764,9 +743,13 @@ pointer_query::flush_cache ()
 static bool
 is_builtin_name (const char *name)
 {
-  return (startswith (name, "__builtin_")
-	  || startswith (name, "__sync_")
-	  || startswith (name, "__atomic_"));
+  if (strncmp (name, "__builtin_", 10) == 0)
+    return true;
+  if (strncmp (name, "__sync_", 7) == 0)
+    return true;
+  if (strncmp (name, "__atomic_", 9) == 0)
+    return true;
+  return false;
 }
 
 /* Return true if NODE should be considered for inline expansion regardless
@@ -950,10 +933,6 @@ bool
 get_object_alignment_1 (tree exp, unsigned int *alignp,
 			unsigned HOST_WIDE_INT *bitposp)
 {
-  /* Strip a WITH_SIZE_EXPR, get_inner_reference doesn't know how to deal
-     with it.  */
-  if (TREE_CODE (exp) == WITH_SIZE_EXPR)
-    exp = TREE_OPERAND (exp, 0);
   return get_object_alignment_2 (exp, alignp, bitposp, false);
 }
 
@@ -1120,9 +1099,7 @@ warn_string_no_nul (location_t loc, tree expr, const char *fname,
 		    bool exact /* = false */,
 		    const wide_int bndrng[2] /* = NULL */)
 {
-  const opt_code opt = OPT_Wstringop_overread;
-  if ((expr && warning_suppressed_p (expr, opt))
-      || warning_suppressed_p (arg, opt))
+  if ((expr && TREE_NO_WARNING (expr)) || TREE_NO_WARNING (arg))
     return;
 
   loc = expansion_point_location_if_in_system_header (loc);
@@ -1150,45 +1127,45 @@ warn_string_no_nul (location_t loc, tree expr, const char *fname,
       if (bndrng)
 	{
 	  if (wi::ltu_p (maxsiz, bndrng[0]))
-	    warned = warning_at (loc, opt,
-				 "%qD specified bound %s exceeds "
+	    warned = warning_at (loc, OPT_Wstringop_overread,
+				 "%K%qD specified bound %s exceeds "
 				 "maximum object size %E",
-				 func, bndstr, maxobjsize);
+				 expr, func, bndstr, maxobjsize);
 	  else
 	    {
 	      bool maybe = wi::to_wide (size) == bndrng[0];
-	      warned = warning_at (loc, opt,
+	      warned = warning_at (loc, OPT_Wstringop_overread,
 				   exact
-				   ? G_("%qD specified bound %s exceeds "
+				   ? G_("%K%qD specified bound %s exceeds "
 					"the size %E of unterminated array")
 				   : (maybe
-				      ? G_("%qD specified bound %s may "
+				      ? G_("%K%qD specified bound %s may "
 					   "exceed the size of at most %E "
 					   "of unterminated array")
-				      : G_("%qD specified bound %s exceeds "
+				      : G_("%K%qD specified bound %s exceeds "
 					   "the size of at most %E "
 					   "of unterminated array")),
-				   func, bndstr, size);
+				   expr, func, bndstr, size);
 	    }
 	}
       else
-	warned = warning_at (loc, opt,
-			     "%qD argument missing terminating nul",
-			     func);
+	warned = warning_at (loc, OPT_Wstringop_overread,
+			     "%K%qD argument missing terminating nul",
+			     expr, func);
     }
   else
     {
       if (bndrng)
 	{
 	  if (wi::ltu_p (maxsiz, bndrng[0]))
-	    warned = warning_at (loc, opt,
+	    warned = warning_at (loc, OPT_Wstringop_overread,
 				 "%qs specified bound %s exceeds "
 				 "maximum object size %E",
 				 fname, bndstr, maxobjsize);
 	  else
 	    {
 	      bool maybe = wi::to_wide (size) == bndrng[0];
-	      warned = warning_at (loc, opt,
+	      warned = warning_at (loc, OPT_Wstringop_overread,
 				   exact
 				   ? G_("%qs specified bound %s exceeds "
 					"the size %E of unterminated array")
@@ -1203,7 +1180,7 @@ warn_string_no_nul (location_t loc, tree expr, const char *fname,
 	    }
 	}
       else
-	warned = warning_at (loc, opt,
+	warned = warning_at (loc, OPT_Wstringop_overread,
 			     "%qs argument missing terminating nul",
 			     fname);
     }
@@ -1212,9 +1189,9 @@ warn_string_no_nul (location_t loc, tree expr, const char *fname,
     {
       inform (DECL_SOURCE_LOCATION (decl),
 	      "referenced argument declared here");
-      suppress_warning (arg, opt);
+      TREE_NO_WARNING (arg) = 1;
       if (expr)
-	suppress_warning (expr, opt);
+	TREE_NO_WARNING (expr) = 1;
     }
 }
 
@@ -1246,15 +1223,14 @@ check_nul_terminated_array (tree expr, tree src,
   wide_int bndrng[2];
   if (bound)
     {
-      value_range r;
-
-      get_global_range_query ()->range_of_expr (r, bound);
-
-      if (r.kind () != VR_RANGE)
-	return true;
-
-      bndrng[0] = r.lower_bound ();
-      bndrng[1] = r.upper_bound ();
+      if (TREE_CODE (bound) == INTEGER_CST)
+	bndrng[0] = bndrng[1] = wi::to_wide (bound);
+      else
+	{
+	  value_range_kind rng = get_range_info (bound, bndrng, bndrng + 1);
+	  if (rng != VR_RANGE)
+	    return true;
+	}
 
       if (exact)
 	{
@@ -1472,14 +1448,14 @@ c_strlen (tree arg, int only_value, c_strlen_data *data, unsigned eltsize)
     {
       /* Suppress multiple warnings for propagated constant strings.  */
       if (only_value != 2
-	  && !warning_suppressed_p (arg, OPT_Warray_bounds)
+	  && !TREE_NO_WARNING (arg)
 	  && warning_at (loc, OPT_Warray_bounds,
 			 "offset %qwi outside bounds of constant string",
 			 eltoff))
 	{
 	  if (decl)
 	    inform (DECL_SOURCE_LOCATION (decl), "%qE declared here", decl);
-	  suppress_warning (arg, OPT_Warray_bounds);
+	  TREE_NO_WARNING (arg) = 1;
 	}
       return NULL_TREE;
     }
@@ -3860,12 +3836,9 @@ expand_builtin_strnlen (tree exp, rtx target, machine_mode target_mode)
     return NULL_RTX;
 
   wide_int min, max;
-  value_range r;
-  get_global_range_query ()->range_of_expr (r, bound);
-  if (r.kind () != VR_RANGE)
+  enum value_range_kind rng = get_range_info (bound, &min, &max);
+  if (rng != VR_RANGE)
     return NULL_RTX;
-  min = r.lower_bound ();
-  max = r.upper_bound ();
 
   if (!len || TREE_CODE (len) != INTEGER_CST)
     {
@@ -3890,7 +3863,7 @@ expand_builtin_strnlen (tree exp, rtx target, machine_mode target_mode)
    a target constant.  */
 
 static rtx
-builtin_memcpy_read_str (void *data, void *, HOST_WIDE_INT offset,
+builtin_memcpy_read_str (void *data, HOST_WIDE_INT offset,
 			 scalar_int_mode mode)
 {
   /* The REPresentation pointed to by DATA need not be a nul-terminated
@@ -3933,16 +3906,7 @@ determine_block_size (tree len, rtx len_rtx,
 	*probable_max_size = *max_size = GET_MODE_MASK (GET_MODE (len_rtx));
 
       if (TREE_CODE (len) == SSA_NAME)
-	{
-	  value_range r;
-	  get_global_range_query ()->range_of_expr (r, len);
-	  range_type = r.kind ();
-	  if (range_type != VR_UNDEFINED)
-	    {
-	      min = wi::to_wide (r.min ());
-	      max = wi::to_wide (r.max ());
-	    }
-	}
+	range_type = get_range_info (len, &min, &max);
       if (range_type == VR_RANGE)
 	{
 	  if (wi::fits_uhwi_p (min) && *min_size < min.to_uhwi ())
@@ -3974,10 +3938,10 @@ determine_block_size (tree len, rtx len_rtx,
    accessing an object with SIZE.  */
 
 static bool
-maybe_warn_for_bound (opt_code opt, location_t loc, tree exp, tree func,
+maybe_warn_for_bound (int opt, location_t loc, tree exp, tree func,
 		      tree bndrng[2], tree size, const access_data *pad = NULL)
 {
-  if (!bndrng[0] || warning_suppressed_p (exp, opt))
+  if (!bndrng[0] || TREE_NO_WARNING (exp))
     return false;
 
   tree maxobjsize = max_object_size ();
@@ -3994,34 +3958,35 @@ maybe_warn_for_bound (opt_code opt, location_t loc, tree exp, tree func,
 	    warned = (func
 		      ? warning_at (loc, opt,
 				    (maybe
-				     ? G_("%qD specified bound %E may "
+				     ? G_("%K%qD specified bound %E may "
 					  "exceed maximum object size %E")
-				     : G_("%qD specified bound %E "
+				     : G_("%K%qD specified bound %E "
 					  "exceeds maximum object size %E")),
-				    func, bndrng[0], maxobjsize)
+				    exp, func, bndrng[0], maxobjsize)
 		      : warning_at (loc, opt,
 				    (maybe
-				     ? G_("specified bound %E may "
+				     ? G_("%Kspecified bound %E may "
 					  "exceed maximum object size %E")
-				     : G_("specified bound %E "
+				     : G_("%Kspecified bound %E "
 					  "exceeds maximum object size %E")),
-				    bndrng[0], maxobjsize));
+				    exp, bndrng[0], maxobjsize));
 	  else
 	    warned = (func
 		      ? warning_at (loc, opt,
 				    (maybe
-				     ? G_("%qD specified bound [%E, %E] may "
+				     ? G_("%K%qD specified bound [%E, %E] may "
 					  "exceed maximum object size %E")
-				     : G_("%qD specified bound [%E, %E] "
+				     : G_("%K%qD specified bound [%E, %E] "
 					  "exceeds maximum object size %E")),
-				    func, bndrng[0], bndrng[1], maxobjsize)
+				    exp, func,
+				    bndrng[0], bndrng[1], maxobjsize)
 		      : warning_at (loc, opt,
 				    (maybe
-				     ? G_("specified bound [%E, %E] may "
+				     ? G_("%Kspecified bound [%E, %E] may "
 					  "exceed maximum object size %E")
-				     : G_("specified bound [%E, %E] "
+				     : G_("%Kspecified bound [%E, %E] "
 					  "exceeds maximum object size %E")),
-				    bndrng[0], bndrng[1], maxobjsize));
+				    exp, bndrng[0], bndrng[1], maxobjsize));
 	}
       else if (!size || tree_int_cst_le (bndrng[0], size))
 	return false;
@@ -4029,34 +3994,34 @@ maybe_warn_for_bound (opt_code opt, location_t loc, tree exp, tree func,
 	warned = (func
 		  ? warning_at (loc, opt,
 				(maybe
-				 ? G_("%qD specified bound %E may exceed "
+				 ? G_("%K%qD specified bound %E may exceed "
 				      "source size %E")
-				 : G_("%qD specified bound %E exceeds "
+				 : G_("%K%qD specified bound %E exceeds "
 				      "source size %E")),
-				func, bndrng[0], size)
+				exp, func, bndrng[0], size)
 		  : warning_at (loc, opt,
 				(maybe
-				 ? G_("specified bound %E may exceed "
+				 ? G_("%Kspecified bound %E may exceed "
 				      "source size %E")
-				 : G_("specified bound %E exceeds "
+				 : G_("%Kspecified bound %E exceeds "
 				      "source size %E")),
-				bndrng[0], size));
+				exp, bndrng[0], size));
       else
 	warned = (func
 		  ? warning_at (loc, opt,
 				(maybe
-				 ? G_("%qD specified bound [%E, %E] may "
+				 ? G_("%K%qD specified bound [%E, %E] may "
 				      "exceed source size %E")
-				 : G_("%qD specified bound [%E, %E] exceeds "
+				 : G_("%K%qD specified bound [%E, %E] exceeds "
 				      "source size %E")),
-				func, bndrng[0], bndrng[1], size)
+				exp, func, bndrng[0], bndrng[1], size)
 		  : warning_at (loc, opt,
 				(maybe
-				 ? G_("specified bound [%E, %E] may exceed "
+				 ? G_("%Kspecified bound [%E, %E] may exceed "
 				      "source size %E")
-				 : G_("specified bound [%E, %E] exceeds "
+				 : G_("%Kspecified bound [%E, %E] exceeds "
 				      "source size %E")),
-				bndrng[0], bndrng[1], size));
+				exp, bndrng[0], bndrng[1], size));
       if (warned)
 	{
 	  if (pad && pad->src.ref)
@@ -4068,7 +4033,7 @@ maybe_warn_for_bound (opt_code opt, location_t loc, tree exp, tree func,
 		inform (EXPR_LOCATION (pad->src.ref),
 			"source object allocated here");
 	    }
-	  suppress_warning (exp, opt);
+	  TREE_NO_WARNING (exp) = true;
 	}
 
       return warned;
@@ -4081,69 +4046,70 @@ maybe_warn_for_bound (opt_code opt, location_t loc, tree exp, tree func,
 	warned = (func
 		  ? warning_at (loc, opt,
 				(maybe
-				 ? G_("%qD specified size %E may "
+				 ? G_("%K%qD specified size %E may "
 				      "exceed maximum object size %E")
-				 : G_("%qD specified size %E "
+				 : G_("%K%qD specified size %E "
 				      "exceeds maximum object size %E")),
-				func, bndrng[0], maxobjsize)
+				exp, func, bndrng[0], maxobjsize)
 		  : warning_at (loc, opt,
 				(maybe
-				 ? G_("specified size %E may exceed "
+				 ? G_("%Kspecified size %E may exceed "
 				      "maximum object size %E")
-				 : G_("specified size %E exceeds "
+				 : G_("%Kspecified size %E exceeds "
 				      "maximum object size %E")),
-				bndrng[0], maxobjsize));
+				exp, bndrng[0], maxobjsize));
       else
 	warned = (func
 		  ? warning_at (loc, opt,
 				(maybe
-				 ? G_("%qD specified size between %E and %E "
+				 ? G_("%K%qD specified size between %E and %E "
 				      "may exceed maximum object size %E")
-				 : G_("%qD specified size between %E and %E "
+				 : G_("%K%qD specified size between %E and %E "
 				      "exceeds maximum object size %E")),
-				func, bndrng[0], bndrng[1], maxobjsize)
+				exp, func,
+				bndrng[0], bndrng[1], maxobjsize)
 		  : warning_at (loc, opt,
 				(maybe
-				 ? G_("specified size between %E and %E "
+				 ? G_("%Kspecified size between %E and %E "
 				      "may exceed maximum object size %E")
-				 : G_("specified size between %E and %E "
+				 : G_("%Kspecified size between %E and %E "
 				      "exceeds maximum object size %E")),
-				bndrng[0], bndrng[1], maxobjsize));
+				exp, bndrng[0], bndrng[1], maxobjsize));
     }
   else if (!size || tree_int_cst_le (bndrng[0], size))
     return false;
   else if (tree_int_cst_equal (bndrng[0], bndrng[1]))
     warned = (func
-	      ? warning_at (loc, opt,
+	      ? warning_at (loc, OPT_Wstringop_overflow_,
 			    (maybe
-			     ? G_("%qD specified bound %E may exceed "
+			     ? G_("%K%qD specified bound %E may exceed "
 				  "destination size %E")
-			     : G_("%qD specified bound %E exceeds "
+			     : G_("%K%qD specified bound %E exceeds "
 				  "destination size %E")),
-			    func, bndrng[0], size)
-	      : warning_at (loc, opt,
+			    exp, func, bndrng[0], size)
+	      : warning_at (loc, OPT_Wstringop_overflow_,
 			    (maybe
-			     ? G_("specified bound %E may exceed "
+			     ? G_("%Kspecified bound %E may exceed "
 				  "destination size %E")
-			     : G_("specified bound %E exceeds "
+			     : G_("%Kspecified bound %E exceeds "
 				  "destination size %E")),
-			    bndrng[0], size));
+			    exp, bndrng[0], size));
   else
     warned = (func
-	      ? warning_at (loc, opt,
+	      ? warning_at (loc, OPT_Wstringop_overflow_,
 			    (maybe
-			     ? G_("%qD specified bound [%E, %E] may exceed "
+			     ? G_("%K%qD specified bound [%E, %E] may exceed "
 				  "destination size %E")
-			     : G_("%qD specified bound [%E, %E] exceeds "
+			     : G_("%K%qD specified bound [%E, %E] exceeds "
 				  "destination size %E")),
-			    func, bndrng[0], bndrng[1], size)
-	      : warning_at (loc, opt,
+			    exp, func, bndrng[0], bndrng[1], size)
+	      : warning_at (loc, OPT_Wstringop_overflow_,
 			    (maybe
-			     ? G_("specified bound [%E, %E] exceeds "
+			     ? G_("%Kspecified bound [%E, %E] exceeds "
 				  "destination size %E")
-			     : G_("specified bound [%E, %E] exceeds "
+			     : G_("%Kspecified bound [%E, %E] exceeds "
 				  "destination size %E")),
-			    bndrng[0], bndrng[1], size));
+			    exp, bndrng[0], bndrng[1], size));
 
   if (warned)
     {
@@ -4156,7 +4122,7 @@ maybe_warn_for_bound (opt_code opt, location_t loc, tree exp, tree func,
 	    inform (EXPR_LOCATION (pad->dst.ref),
 		    "destination object allocated here");
 	}
-      suppress_warning (exp, opt);
+      TREE_NO_WARNING (exp) = true;
     }
 
   return warned;
@@ -4181,63 +4147,65 @@ warn_for_access (location_t loc, tree func, tree exp, int opt, tree range[2],
 	warned = (func
 		  ? warning_n (loc, opt, tree_to_uhwi (range[0]),
 			       (maybe
-				? G_("%qD may access %E byte in a region "
+				? G_("%K%qD may access %E byte in a region "
 				     "of size %E")
-				: G_("%qD accessing %E byte in a region "
+				: G_("%K%qD accessing %E byte in a region "
 				     "of size %E")),
 				(maybe
-				 ? G_ ("%qD may access %E bytes in a region "
+				 ? G_ ("%K%qD may access %E bytes in a region "
 				       "of size %E")
-				 : G_ ("%qD accessing %E bytes in a region "
+				 : G_ ("%K%qD accessing %E bytes in a region "
 				       "of size %E")),
-			       func, range[0], size)
+			       exp, func, range[0], size)
 		  : warning_n (loc, opt, tree_to_uhwi (range[0]),
 			       (maybe
-				? G_("may access %E byte in a region "
+				? G_("%Kmay access %E byte in a region "
 				     "of size %E")
-				: G_("accessing %E byte in a region "
+				: G_("%Kaccessing %E byte in a region "
 				     "of size %E")),
 			       (maybe
-				? G_("may access %E bytes in a region "
+				? G_("%Kmay access %E bytes in a region "
 				     "of size %E")
-				: G_("accessing %E bytes in a region "
+				: G_("%Kaccessing %E bytes in a region "
 				     "of size %E")),
-			       range[0], size));
+			       exp, range[0], size));
       else if (tree_int_cst_sign_bit (range[1]))
 	{
 	  /* Avoid printing the upper bound if it's invalid.  */
 	  warned = (func
 		    ? warning_at (loc, opt,
 				  (maybe
-				   ? G_("%qD may access %E or more bytes "
+				   ? G_("%K%qD may access %E or more bytes "
 					"in a region of size %E")
-				   : G_("%qD accessing %E or more bytes "
+				   : G_("%K%qD accessing %E or more bytes "
 					"in a region of size %E")),
-				  func, range[0], size)
+				  exp, func, range[0], size)
 		    : warning_at (loc, opt,
 				  (maybe
-				   ? G_("may access %E or more bytes "
+				   ? G_("%Kmay access %E or more bytes "
 					"in a region of size %E")
-				   : G_("accessing %E or more bytes "
+				   : G_("%Kaccessing %E or more bytes "
 					"in a region of size %E")),
-				  range[0], size));
+				  exp, range[0], size));
 	}
       else
 	warned = (func
 		  ? warning_at (loc, opt,
 				(maybe
-				 ? G_("%qD may access between %E and %E "
+				 ? G_("%K%qD may access between %E and %E "
 				      "bytes in a region of size %E")
-				 : G_("%qD accessing between %E and %E "
+				 : G_("%K%qD accessing between %E and %E "
 				      "bytes in a region of size %E")),
-				func, range[0], range[1], size)
+				exp, func, range[0], range[1],
+				size)
 		  : warning_at (loc, opt,
 				(maybe
-				 ? G_("may access between %E and %E bytes "
+				 ? G_("%Kmay access between %E and %E bytes "
 				      "in a region of size %E")
-				 : G_("accessing between %E and %E bytes "
+				 : G_("%Kaccessing between %E and %E bytes "
 				      "in a region of size %E")),
-				range[0], range[1], size));
+				exp, range[0], range[1],
+				size));
       return warned;
     }
 
@@ -4247,67 +4215,69 @@ warn_for_access (location_t loc, tree func, tree exp, int opt, tree range[2],
 	warned = (func
 		  ? warning_n (loc, opt, tree_to_uhwi (range[0]),
 			       (maybe
-				? G_("%qD may write %E byte into a region "
+				? G_("%K%qD may write %E byte into a region "
 				     "of size %E")
-				: G_("%qD writing %E byte into a region "
+				: G_("%K%qD writing %E byte into a region "
 				     "of size %E overflows the destination")),
 			       (maybe
-				? G_("%qD may write %E bytes into a region "
+				? G_("%K%qD may write %E bytes into a region "
 				     "of size %E")
-				: G_("%qD writing %E bytes into a region "
+				: G_("%K%qD writing %E bytes into a region "
 				     "of size %E overflows the destination")),
-			       func, range[0], size)
+			       exp, func, range[0], size)
 		  : warning_n (loc, opt, tree_to_uhwi (range[0]),
 			       (maybe
-				? G_("may write %E byte into a region "
+				? G_("%Kmay write %E byte into a region "
 				     "of size %E")
-				: G_("writing %E byte into a region "
+				: G_("%Kwriting %E byte into a region "
 				     "of size %E overflows the destination")),
 			       (maybe
-				? G_("may write %E bytes into a region "
+				? G_("%Kmay write %E bytes into a region "
 				     "of size %E")
-				: G_("writing %E bytes into a region "
+				: G_("%Kwriting %E bytes into a region "
 				     "of size %E overflows the destination")),
-			       range[0], size));
+			       exp, range[0], size));
       else if (tree_int_cst_sign_bit (range[1]))
 	{
 	  /* Avoid printing the upper bound if it's invalid.  */
 	  warned = (func
 		    ? warning_at (loc, opt,
 				  (maybe
-				   ? G_("%qD may write %E or more bytes "
+				   ? G_("%K%qD may write %E or more bytes "
 					"into a region of size %E")
-				   : G_("%qD writing %E or more bytes "
+				   : G_("%K%qD writing %E or more bytes "
 					"into a region of size %E overflows "
 					"the destination")),
-				  func, range[0], size)
+				  exp, func, range[0], size)
 		    : warning_at (loc, opt,
 				  (maybe
-				   ? G_("may write %E or more bytes into "
+				   ? G_("%Kmay write %E or more bytes into "
 					"a region of size %E")
-				   : G_("writing %E or more bytes into "
+				   : G_("%Kwriting %E or more bytes into "
 					"a region of size %E overflows "
 					"the destination")),
-				  range[0], size));
+				  exp, range[0], size));
 	}
       else
 	warned = (func
 		  ? warning_at (loc, opt,
 				(maybe
-				 ? G_("%qD may write between %E and %E bytes "
+				 ? G_("%K%qD may write between %E and %E bytes "
 				      "into a region of size %E")
-				 : G_("%qD writing between %E and %E bytes "
+				 : G_("%K%qD writing between %E and %E bytes "
 				      "into a region of size %E overflows "
 				      "the destination")),
-				func, range[0], range[1], size)
+				exp, func, range[0], range[1],
+				size)
 		  : warning_at (loc, opt,
 				(maybe
-				 ? G_("may write between %E and %E bytes "
+				 ? G_("%Kmay write between %E and %E bytes "
 				      "into a region of size %E")
-				 : G_("writing between %E and %E bytes "
+				 : G_("%Kwriting between %E and %E bytes "
 				      "into a region of size %E overflows "
 				      "the destination")),
-				range[0], range[1], size));
+				exp, range[0], range[1],
+				size));
       return warned;
     }
 
@@ -4318,67 +4288,67 @@ warn_for_access (location_t loc, tree func, tree exp, int opt, tree range[2],
 		  ? warning_n (loc, OPT_Wstringop_overread,
 			       tree_to_uhwi (range[0]),
 			       (maybe
-				? G_("%qD may read %E byte from a region "
+				? G_("%K%qD may read %E byte from a region "
 				     "of size %E")
-				: G_("%qD reading %E byte from a region "
+				: G_("%K%qD reading %E byte from a region "
 				     "of size %E")),
 			       (maybe
-				? G_("%qD may read %E bytes from a region "
+				? G_("%K%qD may read %E bytes from a region "
 				     "of size %E")
-				: G_("%qD reading %E bytes from a region "
+				: G_("%K%qD reading %E bytes from a region "
 				     "of size %E")),
-			       func, range[0], size)
+			       exp, func, range[0], size)
 		  : warning_n (loc, OPT_Wstringop_overread,
 			       tree_to_uhwi (range[0]),
 			       (maybe
-				? G_("may read %E byte from a region "
+				? G_("%Kmay read %E byte from a region "
 				     "of size %E")
-				: G_("reading %E byte from a region "
+				: G_("%Kreading %E byte from a region "
 				     "of size %E")),
 			       (maybe
-				? G_("may read %E bytes from a region "
+				? G_("%Kmay read %E bytes from a region "
 				     "of size %E")
-				: G_("reading %E bytes from a region "
+				: G_("%Kreading %E bytes from a region "
 				     "of size %E")),
-			       range[0], size));
+			       exp, range[0], size));
       else if (tree_int_cst_sign_bit (range[1]))
 	{
 	  /* Avoid printing the upper bound if it's invalid.  */
 	  warned = (func
 		    ? warning_at (loc, OPT_Wstringop_overread,
 				  (maybe
-				   ? G_("%qD may read %E or more bytes "
+				   ? G_("%K%qD may read %E or more bytes "
 					"from a region of size %E")
-				   : G_("%qD reading %E or more bytes "
+				   : G_("%K%qD reading %E or more bytes "
 					"from a region of size %E")),
-				  func, range[0], size)
+				  exp, func, range[0], size)
 		    : warning_at (loc, OPT_Wstringop_overread,
 				  (maybe
-				   ? G_("may read %E or more bytes "
+				   ? G_("%Kmay read %E or more bytes "
 					"from a region of size %E")
-				   : G_("reading %E or more bytes "
+				   : G_("%Kreading %E or more bytes "
 					"from a region of size %E")),
-				  range[0], size));
+				  exp, range[0], size));
 	}
       else
 	warned = (func
 		  ? warning_at (loc, OPT_Wstringop_overread,
 				(maybe
-				 ? G_("%qD may read between %E and %E bytes "
+				 ? G_("%K%qD may read between %E and %E bytes "
 				      "from a region of size %E")
-				 : G_("%qD reading between %E and %E bytes "
+				 : G_("%K%qD reading between %E and %E bytes "
 				      "from a region of size %E")),
-				func, range[0], range[1], size)
+				exp, func, range[0], range[1], size)
 		  : warning_at (loc, opt,
 				(maybe
-				 ? G_("may read between %E and %E bytes "
+				 ? G_("%Kmay read between %E and %E bytes "
 				      "from a region of size %E")
-				 : G_("reading between %E and %E bytes "
+				 : G_("%Kreading between %E and %E bytes "
 				      "from a region of size %E")),
-				range[0], range[1], size));
+				exp, range[0], range[1], size));
 
       if (warned)
-	suppress_warning (exp, OPT_Wstringop_overread);
+	TREE_NO_WARNING (exp) = true;
 
       return warned;
     }
@@ -4388,40 +4358,40 @@ warn_for_access (location_t loc, tree func, tree exp, int opt, tree range[2],
     warned = (func
 	      ? warning_n (loc, OPT_Wstringop_overread,
 			   tree_to_uhwi (range[0]),
-			   "%qD expecting %E byte in a region of size %E",
-			   "%qD expecting %E bytes in a region of size %E",
-			   func, range[0], size)
+			   "%K%qD expecting %E byte in a region of size %E",
+			   "%K%qD expecting %E bytes in a region of size %E",
+			   exp, func, range[0], size)
 	      : warning_n (loc, OPT_Wstringop_overread,
 			   tree_to_uhwi (range[0]),
-			   "expecting %E byte in a region of size %E",
-			   "expecting %E bytes in a region of size %E",
-			   range[0], size));
+			   "%Kexpecting %E byte in a region of size %E",
+			   "%Kexpecting %E bytes in a region of size %E",
+			   exp, range[0], size));
   else if (tree_int_cst_sign_bit (range[1]))
     {
       /* Avoid printing the upper bound if it's invalid.  */
       warned = (func
 		? warning_at (loc, OPT_Wstringop_overread,
-			      "%qD expecting %E or more bytes in a region "
+			      "%K%qD expecting %E or more bytes in a region "
 			      "of size %E",
-			      func, range[0], size)
+			      exp, func, range[0], size)
 		: warning_at (loc, OPT_Wstringop_overread,
-			      "expecting %E or more bytes in a region "
+			      "%Kexpecting %E or more bytes in a region "
 			      "of size %E",
-			      range[0], size));
+			      exp, range[0], size));
     }
   else
     warned = (func
 	      ? warning_at (loc, OPT_Wstringop_overread,
-			    "%qD expecting between %E and %E bytes in "
+			    "%K%qD expecting between %E and %E bytes in "
 			    "a region of size %E",
-			    func, range[0], range[1], size)
+			    exp, func, range[0], range[1], size)
 	      : warning_at (loc, OPT_Wstringop_overread,
-			    "expecting between %E and %E bytes in "
+			    "%Kexpecting between %E and %E bytes in "
 			    "a region of size %E",
-			    range[0], range[1], size));
+			    exp, range[0], range[1], size));
 
   if (warned)
-    suppress_warning (exp, OPT_Wstringop_overread);
+    TREE_NO_WARNING (exp) = true;
 
   return warned;
 }
@@ -4596,46 +4566,23 @@ access_ref::inform_access (access_mode mode) const
       return;
     }
 
-  if (mode == access_read_only)
-    {
-      if (allocfn == NULL_TREE)
-	{
-	  if (*offstr)
-	    inform (loc, "at offset %s into source object %qE of size %s",
-		    offstr, ref, sizestr);
-	  else
-	    inform (loc, "source object %qE of size %s", ref, sizestr);
-
-	  return;
-	}
-
-      if (*offstr)
-	inform (loc,
-		"at offset %s into source object of size %s allocated by %qE",
-		offstr, sizestr, allocfn);
-      else
-	inform (loc, "source object of size %s allocated by %qE",
-		sizestr, allocfn);
-      return;
-    }
-
   if (allocfn == NULL_TREE)
     {
       if (*offstr)
-	inform (loc, "at offset %s into object %qE of size %s",
+	inform (loc, "at offset %s into source object %qE of size %s",
 		offstr, ref, sizestr);
       else
-	inform (loc, "object %qE of size %s", ref, sizestr);
+	inform (loc, "source object %qE of size %s", ref, sizestr);
 
       return;
     }
 
   if (*offstr)
     inform (loc,
-	    "at offset %s into object of size %s allocated by %qE",
+	    "at offset %s into source object of size %s allocated by %qE",
 	    offstr, sizestr, allocfn);
   else
-    inform (loc, "object of size %s allocated by %qE",
+    inform (loc, "source object of size %s allocated by %qE",
 	    sizestr, allocfn);
 }
 
@@ -4801,7 +4748,7 @@ check_access (tree exp, tree dstwrite,
       && TREE_CODE (range[0]) == INTEGER_CST
       && tree_int_cst_lt (maxobjsize, range[0]))
     {
-      location_t loc = EXPR_LOCATION (exp);
+      location_t loc = tree_inlined_location (exp);
       maybe_warn_for_bound (OPT_Wstringop_overflow_, loc, exp, func, range,
 			    NULL_TREE, pad);
       return false;
@@ -4823,13 +4770,11 @@ check_access (tree exp, tree dstwrite,
 		  && tree_fits_uhwi_p (dstwrite)
 		  && tree_int_cst_lt (dstwrite, range[0]))))
 	{
-	  const opt_code opt = OPT_Wstringop_overflow_;
-	  if (warning_suppressed_p (exp, opt)
-	      || (pad && pad->dst.ref
-		  && warning_suppressed_p (pad->dst.ref, opt)))
+	  if (TREE_NO_WARNING (exp)
+	      || (pad && pad->dst.ref && TREE_NO_WARNING (pad->dst.ref)))
 	    return false;
 
-	  location_t loc = EXPR_LOCATION (exp);
+	  location_t loc = tree_inlined_location (exp);
 	  bool warned = false;
 	  if (dstwrite == slen && at_least_one)
 	    {
@@ -4837,16 +4782,16 @@ check_access (tree exp, tree dstwrite,
 		 and a source of unknown length.  The call will write
 		 at least one byte past the end of the destination.  */
 	      warned = (func
-			? warning_at (loc, opt,
-				      "%qD writing %E or more bytes into "
+			? warning_at (loc, OPT_Wstringop_overflow_,
+				      "%K%qD writing %E or more bytes into "
 				      "a region of size %E overflows "
 				      "the destination",
-				      func, range[0], dstsize)
-			: warning_at (loc, opt,
-				      "writing %E or more bytes into "
+				      exp, func, range[0], dstsize)
+			: warning_at (loc, OPT_Wstringop_overflow_,
+				      "%Kwriting %E or more bytes into "
 				      "a region of size %E overflows "
 				      "the destination",
-				      range[0], dstsize));
+				      exp, range[0], dstsize));
 	    }
 	  else
 	    {
@@ -4863,7 +4808,7 @@ check_access (tree exp, tree dstwrite,
 
 	  if (warned)
 	    {
-	      suppress_warning (exp, OPT_Wstringop_overflow_);
+	      TREE_NO_WARNING (exp) = true;
 	      if (pad)
 		pad->dst.inform_access (pad->mode);
 	    }
@@ -4882,7 +4827,7 @@ check_access (tree exp, tree dstwrite,
 	 PAD is nonnull and BNDRNG is valid.  */
       get_size_range (maxread, range, pad ? pad->src.bndrng : NULL);
 
-      location_t loc = EXPR_LOCATION (exp);
+      location_t loc = tree_inlined_location (exp);
       tree size = dstsize;
       if (pad && pad->mode == access_read_only)
 	size = wide_int_to_tree (sizetype, pad->src.sizrng[1]);
@@ -4898,9 +4843,9 @@ check_access (tree exp, tree dstwrite,
 
 	  if (size != maxobjsize && tree_int_cst_lt (size, range[0]))
 	    {
-	      opt_code opt = (dstwrite || mode != access_read_only
-			      ? OPT_Wstringop_overflow_
-			      : OPT_Wstringop_overread);
+	      int opt = (dstwrite || mode != access_read_only
+			 ? OPT_Wstringop_overflow_
+			 : OPT_Wstringop_overread);
 	      maybe_warn_for_bound (opt, loc, exp, func, range, size, pad);
 	      return false;
 	    }
@@ -4936,21 +4881,19 @@ check_access (tree exp, tree dstwrite,
 
   if (overread)
     {
-      const opt_code opt = OPT_Wstringop_overread;
-      if (warning_suppressed_p (exp, opt)
-	  || (srcstr && warning_suppressed_p (srcstr, opt))
-	  || (pad && pad->src.ref
-	      && warning_suppressed_p (pad->src.ref, opt)))
+      if (TREE_NO_WARNING (exp)
+	  || (srcstr && TREE_NO_WARNING (srcstr))
+	  || (pad && pad->src.ref && TREE_NO_WARNING (pad->src.ref)))
 	return false;
 
-      location_t loc = EXPR_LOCATION (exp);
+      location_t loc = tree_inlined_location (exp);
       const bool read
 	= mode == access_read_only || mode == access_read_write;
       const bool maybe = pad && pad->dst.parmarray;
-      if (warn_for_access (loc, func, exp, opt, range, slen, false, read,
-			   maybe))
+      if (warn_for_access (loc, func, exp, OPT_Wstringop_overread, range,
+			   slen, false, read, maybe))
 	{
-	  suppress_warning (exp, opt);
+	  TREE_NO_WARNING (exp) = true;
 	  if (pad)
 	    pad->src.inform_access (access_read_only);
 	}
@@ -4982,8 +4925,8 @@ check_read_access (tree exp, tree src, tree bound /* = NULL_TREE */,
 /* If STMT is a call to an allocation function, returns the constant
    maximum size of the object allocated by the call represented as
    sizetype.  If nonnull, sets RNG1[] to the range of the size.
-   When nonnull, uses RVALS for range information, otherwise gets global
-   range info.
+   When nonnull, uses RVALS for range information, otherwise calls
+   get_range_info to get it.
    Returns null when STMT is not a call to a valid allocation function.  */
 
 tree
@@ -5201,19 +5144,12 @@ get_offset_range (tree x, gimple *stmt, offset_int r[2], range_query *rvals)
 /* Return the argument that the call STMT to a built-in function returns
    or null if it doesn't.  On success, set OFFRNG[] to the range of offsets
    from the argument reflected in the value returned by the built-in if it
-   can be determined, otherwise to 0 and HWI_M1U respectively.  Set
-   *PAST_END for functions like mempcpy that might return a past the end
-   pointer (most functions return a dereferenceable pointer to an existing
-   element of an array).  */
+   can be determined, otherwise to 0 and HWI_M1U respectively.  */
 
 static tree
-gimple_call_return_array (gimple *stmt, offset_int offrng[2], bool *past_end,
+gimple_call_return_array (gimple *stmt, offset_int offrng[2],
 			  range_query *rvals)
 {
-  /* Clear and set below for the rare function(s) that might return
-     a past-the-end pointer.  */
-  *past_end = false;
-
   {
     /* Check for attribute fn spec to see if the function returns one
        of its arguments.  */
@@ -5221,7 +5157,6 @@ gimple_call_return_array (gimple *stmt, offset_int offrng[2], bool *past_end,
     unsigned int argno;
     if (fnspec.returns_arg (&argno))
       {
-	/* Functions return the first argument (not a range).  */
 	offrng[0] = offrng[1] = 0;
 	return gimple_call_arg (stmt, argno);
       }
@@ -5251,7 +5186,6 @@ gimple_call_return_array (gimple *stmt, offset_int offrng[2], bool *past_end,
       if (gimple_call_num_args (stmt) != 2)
 	return NULL_TREE;
 
-      /* Allocation functions return a pointer to the beginning.  */
       offrng[0] = offrng[1] = 0;
       return gimple_call_arg (stmt, 1);
     }
@@ -5263,6 +5197,10 @@ gimple_call_return_array (gimple *stmt, offset_int offrng[2], bool *past_end,
     case BUILT_IN_MEMMOVE:
     case BUILT_IN_MEMMOVE_CHK:
     case BUILT_IN_MEMSET:
+    case BUILT_IN_STPCPY:
+    case BUILT_IN_STPCPY_CHK:
+    case BUILT_IN_STPNCPY:
+    case BUILT_IN_STPNCPY_CHK:
     case BUILT_IN_STRCAT:
     case BUILT_IN_STRCAT_CHK:
     case BUILT_IN_STRCPY:
@@ -5271,34 +5209,18 @@ gimple_call_return_array (gimple *stmt, offset_int offrng[2], bool *past_end,
     case BUILT_IN_STRNCAT_CHK:
     case BUILT_IN_STRNCPY:
     case BUILT_IN_STRNCPY_CHK:
-      /* Functions return the first argument (not a range).  */
       offrng[0] = offrng[1] = 0;
       return gimple_call_arg (stmt, 0);
 
     case BUILT_IN_MEMPCPY:
     case BUILT_IN_MEMPCPY_CHK:
       {
-	/* The returned pointer is in a range constrained by the smaller
-	   of the upper bound of the size argument and the source object
-	   size.  */
-	offrng[0] = 0;
-	offrng[1] = HOST_WIDE_INT_M1U;
 	tree off = gimple_call_arg (stmt, 2);
-	bool off_valid = get_offset_range (off, stmt, offrng, rvals);
-	if (!off_valid || offrng[0] != offrng[1])
+	if (!get_offset_range (off, stmt, offrng, rvals))
 	  {
-	    /* If the offset is either indeterminate or in some range,
-	       try to constrain its upper bound to at most the size
-	       of the source object.  */
-	    access_ref aref;
-	    tree src = gimple_call_arg (stmt, 1);
-	    if (compute_objsize (src, 1, &aref, rvals)
-		&& aref.sizrng[1] < offrng[1])
-	      offrng[1] = aref.sizrng[1];
+	    offrng[0] = 0;
+	    offrng[1] = HOST_WIDE_INT_M1U;
 	  }
-
-	/* Mempcpy may return a past-the-end pointer.  */
-	*past_end = true;
 	return gimple_call_arg (stmt, 0);
       }
 
@@ -5306,63 +5228,23 @@ gimple_call_return_array (gimple *stmt, offset_int offrng[2], bool *past_end,
       {
 	tree off = gimple_call_arg (stmt, 2);
 	if (get_offset_range (off, stmt, offrng, rvals))
-	  offrng[1] -= 1;
+	  offrng[0] = 0;
 	else
-	  offrng[1] = HOST_WIDE_INT_M1U;
-
-	offrng[0] = 0;
+	  {
+	    offrng[0] = 0;
+	    offrng[1] = HOST_WIDE_INT_M1U;
+	  }
 	return gimple_call_arg (stmt, 0);
       }
 
     case BUILT_IN_STRCHR:
     case BUILT_IN_STRRCHR:
     case BUILT_IN_STRSTR:
-      offrng[0] = 0;
-      offrng[1] = HOST_WIDE_INT_M1U;
-      return gimple_call_arg (stmt, 0);
-
-    case BUILT_IN_STPCPY:
-    case BUILT_IN_STPCPY_CHK:
       {
-	access_ref aref;
-	tree src = gimple_call_arg (stmt, 1);
-	if (compute_objsize (src, 1, &aref, rvals))
-	  offrng[1] = aref.sizrng[1] - 1;
-	else
-	  offrng[1] = HOST_WIDE_INT_M1U;
-	
 	offrng[0] = 0;
-	return gimple_call_arg (stmt, 0);
-      }
-
-    case BUILT_IN_STPNCPY:
-    case BUILT_IN_STPNCPY_CHK:
-      {
-	/* The returned pointer is in a range between the first argument
-	   and it plus the smaller of the upper bound of the size argument
-	   and the source object size.  */
 	offrng[1] = HOST_WIDE_INT_M1U;
-	tree off = gimple_call_arg (stmt, 2);
-	if (!get_offset_range (off, stmt, offrng, rvals)
-	    || offrng[0] != offrng[1])
-	  {
-	    /* If the offset is either indeterminate or in some range,
-	       try to constrain its upper bound to at most the size
-	       of the source object.  */
-	    access_ref aref;
-	    tree src = gimple_call_arg (stmt, 1);
-	    if (compute_objsize (src, 1, &aref, rvals)
-		&& aref.sizrng[1] < offrng[1])
-	      offrng[1] = aref.sizrng[1];
-	  }
-
-	/* When the source is the empty string the returned pointer is
-	   a copy of the argument.  Otherwise stpcpy can also return
-	   a past-the-end pointer.  */
-	offrng[0] = 0;
-	*past_end = true;
-	return gimple_call_arg (stmt, 0);
       }
+      return gimple_call_arg (stmt, 0);
 
     default:
       break;
@@ -5542,16 +5424,16 @@ handle_mem_ref (tree mref, int ostype, access_ref *pref,
 
   if (VECTOR_TYPE_P (TREE_TYPE (mref)))
     {
-      /* Hack: Handle MEM_REFs of vector types as those to complete
-	 objects; those may be synthesized from multiple assignments
-	 to consecutive data members (see PR 93200 and 96963).
+      /* Hack: Give up for MEM_REFs of vector types; those may be
+	 synthesized from multiple assignments to consecutive data
+	 members (see PR 93200 and 96963).
 	 FIXME: Vectorized assignments should only be present after
 	 vectorization so this hack is only necessary after it has
 	 run and could be avoided in calls from prior passes (e.g.,
 	 tree-ssa-strlen.c).
 	 FIXME: Deal with this more generally, e.g., by marking up
 	 such MEM_REFs at the time they're created.  */
-      ostype = 0;
+      return false;
     }
 
   tree mrefop = TREE_OPERAND (mref, 0);
@@ -5815,12 +5697,9 @@ compute_objsize_r (tree ptr, int ostype, access_ref *pref,
 	      /* For functions known to return one of their pointer arguments
 		 try to determine what the returned pointer points to, and on
 		 success add OFFRNG which was set to the offset added by
-		 the function (e.g., memchr or stpcpy) to the overall offset.
-	      */
-	      bool past_end;
+		 the function (e.g., memchr) to the overall offset.  */
 	      offset_int offrng[2];
-	      if (tree ret = gimple_call_return_array (stmt, offrng,
-						       &past_end, rvals))
+	      if (tree ret = gimple_call_return_array (stmt, offrng, rvals))
 		{
 		  if (!compute_objsize_r (ret, ostype, pref, snlim, qry))
 		    return false;
@@ -5829,11 +5708,6 @@ compute_objsize_r (tree ptr, int ostype, access_ref *pref,
 		     the object.  */
 		  offset_int remrng[2];
 		  remrng[1] = pref->size_remaining (remrng);
-		  if (remrng[1] != 0 && !past_end)
-		    /* Decrement the size for functions that never return
-		       a past-the-end pointer.  */
-		    remrng[1] -= 1;
-
 		  if (remrng[1] < offrng[1])
 		    offrng[1] = remrng[1];
 		  pref->add_offset (offrng[0], offrng[1]);
@@ -5913,12 +5787,6 @@ compute_objsize_r (tree ptr, int ostype, access_ref *pref,
 
       tree rhs = gimple_assign_rhs1 (stmt);
 
-      if (code == ASSERT_EXPR)
-	{
-	  rhs = TREE_OPERAND (rhs, 0);
-	  return compute_objsize_r (rhs, ostype, pref, snlim, qry);
-	}
-
       if (code == POINTER_PLUS_EXPR
 	  && TREE_CODE (TREE_TYPE (rhs)) == POINTER_TYPE)
 	{
@@ -6547,7 +6415,7 @@ expand_builtin_stpncpy (tree exp, rtx)
    constant.  */
 
 rtx
-builtin_strncpy_read_str (void *data, void *, HOST_WIDE_INT offset,
+builtin_strncpy_read_str (void *data, HOST_WIDE_INT offset,
 			  scalar_int_mode mode)
 {
   const char *str = (const char *) data;
@@ -6598,10 +6466,10 @@ check_strncat_sizes (tree exp, tree objsize)
   if (tree_fits_uhwi_p (maxread) && tree_fits_uhwi_p (objsize)
       && tree_int_cst_equal (objsize, maxread))
     {
-      location_t loc = EXPR_LOCATION (exp);
+      location_t loc = tree_inlined_location (exp);
       warning_at (loc, OPT_Wstringop_overflow_,
-		  "%qD specified bound %E equals destination size",
-		  get_callee_fndecl (exp), maxread);
+		  "%K%qD specified bound %E equals destination size",
+		  exp, get_callee_fndecl (exp), maxread);
 
       return false;
     }
@@ -6671,10 +6539,10 @@ expand_builtin_strncat (tree exp, rtx)
   if (tree_fits_uhwi_p (maxread) && tree_fits_uhwi_p (destsize)
       && tree_int_cst_equal (destsize, maxread))
     {
-      location_t loc = EXPR_LOCATION (exp);
+      location_t loc = tree_inlined_location (exp);
       warning_at (loc, OPT_Wstringop_overflow_,
-		  "%qD specified bound %E equals destination size",
-		  get_callee_fndecl (exp), maxread);
+		  "%K%qD specified bound %E equals destination size",
+		  exp, get_callee_fndecl (exp), maxread);
 
       return NULL_RTX;
     }
@@ -6758,22 +6626,12 @@ expand_builtin_strncpy (tree exp, rtx target)
 
 /* Callback routine for store_by_pieces.  Read GET_MODE_BITSIZE (MODE)
    bytes from constant string DATA + OFFSET and return it as target
-   constant.  If PREV isn't nullptr, it has the RTL info from the
-   previous iteration.  */
+   constant.  */
 
 rtx
-builtin_memset_read_str (void *data, void *prevp,
-			 HOST_WIDE_INT offset ATTRIBUTE_UNUSED,
+builtin_memset_read_str (void *data, HOST_WIDE_INT offset ATTRIBUTE_UNUSED,
 			 scalar_int_mode mode)
 {
-  by_pieces_prev *prev = (by_pieces_prev *) prevp;
-  if (prev != nullptr && prev->data != nullptr)
-    {
-      /* Use the previous data in the same mode.  */
-      if (prev->mode == mode)
-	return prev->data;
-    }
-
   const char *c = (const char *) data;
   char *p = XALLOCAVEC (char, GET_MODE_SIZE (mode));
 
@@ -6785,30 +6643,16 @@ builtin_memset_read_str (void *data, void *prevp,
 /* Callback routine for store_by_pieces.  Return the RTL of a register
    containing GET_MODE_SIZE (MODE) consecutive copies of the unsigned
    char value given in the RTL register data.  For example, if mode is
-   4 bytes wide, return the RTL for 0x01010101*data.  If PREV isn't
-   nullptr, it has the RTL info from the previous iteration.  */
+   4 bytes wide, return the RTL for 0x01010101*data.  */
 
 static rtx
-builtin_memset_gen_str (void *data, void *prevp,
-			HOST_WIDE_INT offset ATTRIBUTE_UNUSED,
+builtin_memset_gen_str (void *data, HOST_WIDE_INT offset ATTRIBUTE_UNUSED,
 			scalar_int_mode mode)
 {
   rtx target, coeff;
   size_t size;
   char *p;
 
-  by_pieces_prev *prev = (by_pieces_prev *) prevp;
-  if (prev != nullptr && prev->data != nullptr)
-    {
-      /* Use the previous data in the same mode.  */
-      if (prev->mode == mode)
-	return prev->data;
-
-      target = simplify_gen_subreg (mode, prev->data, prev->mode, 0);
-      if (target != nullptr)
-	return target;
-    }
-
   size = GET_MODE_SIZE (mode);
   if (size == 1)
     return (rtx) data;
@@ -6843,168 +6687,6 @@ expand_builtin_memset (tree exp, rtx target, machine_mode mode)
   return expand_builtin_memset_args (dest, val, len, target, mode, exp);
 }
 
-/* Try to store VAL (or, if NULL_RTX, VALC) in LEN bytes starting at TO.
-   Return TRUE if successful, FALSE otherwise.  TO is assumed to be
-   aligned at an ALIGN-bits boundary.  LEN must be a multiple of
-   1<<CTZ_LEN between MIN_LEN and MAX_LEN.
-
-   The strategy is to issue one store_by_pieces for each power of two,
-   from most to least significant, guarded by a test on whether there
-   are at least that many bytes left to copy in LEN.
-
-   ??? Should we skip some powers of two in favor of loops?  Maybe start
-   at the max of TO/LEN/word alignment, at least when optimizing for
-   size, instead of ensuring O(log len) dynamic compares?  */
-
-bool
-try_store_by_multiple_pieces (rtx to, rtx len, unsigned int ctz_len,
-			      unsigned HOST_WIDE_INT min_len,
-			      unsigned HOST_WIDE_INT max_len,
-			      rtx val, char valc, unsigned int align)
-{
-  int max_bits = floor_log2 (max_len);
-  int min_bits = floor_log2 (min_len);
-  int sctz_len = ctz_len;
-
-  gcc_checking_assert (sctz_len >= 0);
-
-  if (val)
-    valc = 1;
-
-  /* Bits more significant than TST_BITS are part of the shared prefix
-     in the binary representation of both min_len and max_len.  Since
-     they're identical, we don't need to test them in the loop.  */
-  int tst_bits = (max_bits != min_bits ? max_bits
-		  : floor_log2 (max_len ^ min_len));
-
-  /* Check whether it's profitable to start by storing a fixed BLKSIZE
-     bytes, to lower max_bits.  In the unlikely case of a constant LEN
-     (implied by identical MAX_LEN and MIN_LEN), we want to issue a
-     single store_by_pieces, but otherwise, select the minimum multiple
-     of the ALIGN (in bytes) and of the MCD of the possible LENs, that
-     brings MAX_LEN below TST_BITS, if that's lower than min_len.  */
-  unsigned HOST_WIDE_INT blksize;
-  if (max_len > min_len)
-    {
-      unsigned HOST_WIDE_INT alrng = MAX (HOST_WIDE_INT_1U << ctz_len,
-					  align / BITS_PER_UNIT);
-      blksize = max_len - (HOST_WIDE_INT_1U << tst_bits) + alrng;
-      blksize &= ~(alrng - 1);
-    }
-  else if (max_len == min_len)
-    blksize = max_len;
-  else
-    gcc_unreachable ();
-  if (min_len >= blksize)
-    {
-      min_len -= blksize;
-      min_bits = floor_log2 (min_len);
-      max_len -= blksize;
-      max_bits = floor_log2 (max_len);
-
-      tst_bits = (max_bits != min_bits ? max_bits
-		 : floor_log2 (max_len ^ min_len));
-    }
-  else
-    blksize = 0;
-
-  /* Check that we can use store by pieces for the maximum store count
-     we may issue (initial fixed-size block, plus conditional
-     power-of-two-sized from max_bits to ctz_len.  */
-  unsigned HOST_WIDE_INT xlenest = blksize;
-  if (max_bits >= 0)
-    xlenest += ((HOST_WIDE_INT_1U << max_bits) * 2
-		- (HOST_WIDE_INT_1U << ctz_len));
-  if (!can_store_by_pieces (xlenest, builtin_memset_read_str,
-			    &valc, align, true))
-    return false;
-
-  rtx (*constfun) (void *, void *, HOST_WIDE_INT, scalar_int_mode);
-  void *constfundata;
-  if (val)
-    {
-      constfun = builtin_memset_gen_str;
-      constfundata = val = force_reg (TYPE_MODE (unsigned_char_type_node),
-				      val);
-    }
-  else
-    {
-      constfun = builtin_memset_read_str;
-      constfundata = &valc;
-    }
-
-  rtx ptr = copy_addr_to_reg (convert_to_mode (ptr_mode, XEXP (to, 0), 0));
-  rtx rem = copy_to_mode_reg (ptr_mode, convert_to_mode (ptr_mode, len, 0));
-  to = replace_equiv_address (to, ptr);
-  set_mem_align (to, align);
-
-  if (blksize)
-    {
-      to = store_by_pieces (to, blksize,
-			    constfun, constfundata,
-			    align, true,
-			    max_len != 0 ? RETURN_END : RETURN_BEGIN);
-      if (max_len == 0)
-	return true;
-
-      /* Adjust PTR, TO and REM.  Since TO's address is likely
-	 PTR+offset, we have to replace it.  */
-      emit_move_insn (ptr, force_operand (XEXP (to, 0), NULL_RTX));
-      to = replace_equiv_address (to, ptr);
-      rtx rem_minus_blksize = plus_constant (ptr_mode, rem, -blksize);
-      emit_move_insn (rem, force_operand (rem_minus_blksize, NULL_RTX));
-    }
-
-  /* Iterate over power-of-two block sizes from the maximum length to
-     the least significant bit possibly set in the length.  */
-  for (int i = max_bits; i >= sctz_len; i--)
-    {
-      rtx_code_label *label = NULL;
-      blksize = HOST_WIDE_INT_1U << i;
-
-      /* If we're past the bits shared between min_ and max_len, expand
-	 a test on the dynamic length, comparing it with the
-	 BLKSIZE.  */
-      if (i <= tst_bits)
-	{
-	  label = gen_label_rtx ();
-	  emit_cmp_and_jump_insns (rem, GEN_INT (blksize), LT, NULL,
-				   ptr_mode, 1, label,
-				   profile_probability::even ());
-	}
-      /* If we are at a bit that is in the prefix shared by min_ and
-	 max_len, skip this BLKSIZE if the bit is clear.  */
-      else if ((max_len & blksize) == 0)
-	continue;
-
-      /* Issue a store of BLKSIZE bytes.  */
-      to = store_by_pieces (to, blksize,
-			    constfun, constfundata,
-			    align, true,
-			    i != sctz_len ? RETURN_END : RETURN_BEGIN);
-
-      /* Adjust REM and PTR, unless this is the last iteration.  */
-      if (i != sctz_len)
-	{
-	  emit_move_insn (ptr, force_operand (XEXP (to, 0), NULL_RTX));
-	  to = replace_equiv_address (to, ptr);
-	  rtx rem_minus_blksize = plus_constant (ptr_mode, rem, -blksize);
-	  emit_move_insn (rem, force_operand (rem_minus_blksize, NULL_RTX));
-	}
-
-      if (label)
-	{
-	  emit_label (label);
-
-	  /* Given conditional stores, the offset can no longer be
-	     known, so clear it.  */
-	  clear_mem_offset (to);
-	}
-    }
-
-  return true;
-}
-
 /* Helper function to do the actual work for expand_builtin_memset.  The
    arguments to the builtin_memset call DEST, VAL, and LEN are broken out
    so that this can also be called without constructing an actual CALL_EXPR.
@@ -7059,8 +6741,7 @@ expand_builtin_memset_args (tree dest, tree val, tree len,
   dest_mem = get_memory_rtx (dest, len);
   val_mode = TYPE_MODE (unsigned_char_type_node);
 
-  if (TREE_CODE (val) != INTEGER_CST
-      || target_char_cast (val, &c))
+  if (TREE_CODE (val) != INTEGER_CST)
     {
       rtx val_rtx;
 
@@ -7084,12 +6765,7 @@ expand_builtin_memset_args (tree dest, tree val, tree len,
       else if (!set_storage_via_setmem (dest_mem, len_rtx, val_rtx,
 					dest_align, expected_align,
 					expected_size, min_size, max_size,
-					probable_max_size)
-	       && !try_store_by_multiple_pieces (dest_mem, len_rtx,
-						 tree_ctz (len),
-						 min_size, max_size,
-						 val_rtx, 0,
-						 dest_align))
+					probable_max_size))
 	goto do_libcall;
 
       dest_mem = force_operand (XEXP (dest_mem, 0), NULL_RTX);
@@ -7097,6 +6773,9 @@ expand_builtin_memset_args (tree dest, tree val, tree len,
       return dest_mem;
     }
 
+  if (target_char_cast (val, &c))
+    goto do_libcall;
+
   if (c)
     {
       if (tree_fits_uhwi_p (len)
@@ -7110,12 +6789,7 @@ expand_builtin_memset_args (tree dest, tree val, tree len,
 					gen_int_mode (c, val_mode),
 					dest_align, expected_align,
 					expected_size, min_size, max_size,
-					probable_max_size)
-	       && !try_store_by_multiple_pieces (dest_mem, len_rtx,
-						 tree_ctz (len),
-						 min_size, max_size,
-						 NULL_RTX, c,
-						 dest_align))
+					probable_max_size))
 	goto do_libcall;
 
       dest_mem = force_operand (XEXP (dest_mem, 0), NULL_RTX);
@@ -7129,7 +6803,7 @@ expand_builtin_memset_args (tree dest, tree val, tree len,
 				   ? BLOCK_OP_TAILCALL : BLOCK_OP_NORMAL,
 				   expected_align, expected_size,
 				   min_size, max_size,
-				   probable_max_size, tree_ctz (len));
+				   probable_max_size);
 
   if (dest_addr == 0)
     {
@@ -7447,7 +7121,7 @@ expand_builtin_strncmp (tree exp, ATTRIBUTE_UNUSED rtx target,
       || !check_nul_terminated_array (exp, arg2, arg3))
     return NULL_RTX;
 
-  location_t loc = EXPR_LOCATION (exp);
+  location_t loc = tree_inlined_location (exp);
   tree len1 = c_strlen (arg1, 1);
   tree len2 = c_strlen (arg2, 1);
 
@@ -7585,7 +7259,8 @@ expand_builtin_strncmp (tree exp, ATTRIBUTE_UNUSED rtx target,
   /* Expand the library call ourselves using a stabilized argument
      list to avoid re-evaluating the function's arguments twice.  */
   tree call = build_call_nofold_loc (loc, fndecl, 3, arg1, arg2, len);
-  copy_warning (call, exp);
+  if (TREE_NO_WARNING (exp))
+    TREE_NO_WARNING (call) = true;
   gcc_assert (TREE_CODE (call) == CALL_EXPR);
   CALL_EXPR_TAILCALL (call) = CALL_EXPR_TAILCALL (exp);
   return expand_call (call, target, target == const0_rtx);
@@ -7926,7 +7601,23 @@ expand_builtin_strub_enter (tree exp)
   if (optimize < 1 || flag_no_inline)
     return NULL_RTX;
 
-  rtx stktop = expand_builtin_stack_address ();
+  rtx stktop = NULL_RTX;
+
+#if 1 || defined RED_ZONE_SIZE
+  if (tree wmptr = (optimize
+		    ? find_watermark_parm (current_function_decl)
+		    : NULL_TREE))
+    {
+      tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+      tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+				 build_int_cst (TREE_TYPE (wmptr), 0));
+      rtx wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+      stktop = force_reg (ptr_mode, wmark);
+    }
+#endif
+
+  if (!stktop)
+    stktop = expand_builtin_stack_address ();
 
   tree wmptr = CALL_EXPR_ARG (exp, 0);
   tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
@@ -7947,11 +7638,57 @@ expand_builtin_strub_update (tree exp)
   if (!validate_arglist (exp, POINTER_TYPE, VOID_TYPE))
     return NULL_RTX;
 
-  if (optimize < 2 || optimize_size || flag_no_inline)
+  if (optimize < 2 || flag_no_inline)
     return NULL_RTX;
 
   rtx stktop = expand_builtin_stack_address ();
 
+#ifdef RED_ZONE_SIZE
+  /* Here's how the strub enter, update and leave functions deal with red zones.
+
+     If it weren't for red zones, update, called from within a strub context,
+     would bump the watermark to the top of the stack.  Enter and leave, running
+     in the caller, would use the caller's top of stack address both to
+     initialize the watermark passed to the callee, and to start strubbing the
+     stack afterwards.
+
+     Ideally, we'd update the watermark so as to cover the used amount of red
+     zone, and strub starting at the caller's other end of the (presumably
+     unused) red zone.  Normally, only leaf functions use the red zone, but at
+     this point we can't tell whether a function is a leaf, nor can we tell how
+     much of the red zone it uses.  Furthermore, some strub contexts may have
+     been inlined so that update and leave are called from the same stack frame,
+     and the strub builtins may all have been inlined, turning a strub function
+     into a leaf.
+
+     So cleaning the range from the caller's stack pointer (one end of the red
+     zone) to the (potentially inlined) callee's (other end of the) red zone
+     could scribble over the caller's own red zone.
+
+     We avoid this possibility by arranging for callers that are strub contexts
+     to use their own watermark as the strub starting point.  So, if A calls B,
+     and B calls C, B will tell A to strub up to the end of B's red zone, and
+     will strub itself only the part of C's stack frame and red zone that
+     doesn't overlap with B's.  With that, we don't need to know who's leaf and
+     who isn't: inlined calls will shrink their strub window to zero, each
+     remaining call will strub some portion of the stack, and eventually the
+     strub context will return to a caller that isn't a strub context itself,
+     that will therefore use its own stack pointer as the strub starting point.
+     It's not a leaf, because strub contexts can't be inlined into non-strub
+     contexts, so it doesn't use the red zone, and it will therefore correctly
+     strub up the callee's stack frame up to the end of the callee's red zone.
+     Neat!  */
+  if (true /* (flags_from_decl_or_type (current_function_decl) & ECF_LEAF) */)
+    {
+      poly_int64 red_zone_size = RED_ZONE_SIZE;
+#if STACK_GROWS_DOWNWARD
+      red_zone_size = -red_zone_size;
+#endif
+      stktop = plus_constant (ptr_mode, stktop, red_zone_size);
+      stktop = force_reg (ptr_mode, stktop);
+    }
+#endif
+
   tree wmptr = CALL_EXPR_ARG (exp, 0);
   tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
   tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
@@ -7979,10 +7716,26 @@ expand_builtin_strub_leave (tree exp)
   if (!validate_arglist (exp, POINTER_TYPE, VOID_TYPE))
     return NULL_RTX;
 
-  if (optimize < 2 || flag_no_inline)
+  if (optimize < 2 || optimize_size || flag_no_inline)
     return NULL_RTX;
 
-  rtx stktop = expand_builtin_stack_address ();
+  rtx stktop = NULL_RTX;
+
+#if 1 || defined RED_ZONE_SIZE
+  if (tree wmptr = (optimize
+		    ? find_watermark_parm (current_function_decl)
+		    : NULL_TREE))
+    {
+      tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+      tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+				 build_int_cst (TREE_TYPE (wmptr), 0));
+      rtx wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+      stktop = force_reg (ptr_mode, wmark);
+    }
+#endif
+
+  if (!stktop)
+    stktop = expand_builtin_stack_address ();
 
   tree wmptr = CALL_EXPR_ARG (exp, 0);
   tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
@@ -8000,6 +7753,9 @@ expand_builtin_strub_leave (tree exp)
   rtx end = stktop;
 #endif
 
+  /* We're going to modify it, so make sure it's not e.g. the stack pointer.  */
+  base = copy_to_reg (base);
+
   rtx_code_label *done = gen_label_rtx ();
   do_compare_rtx_and_jump (base, end, LT, STACK_UNSIGNED,
 			   ptr_mode, NULL_RTX, done, NULL,
@@ -10264,13 +10020,13 @@ expand_builtin (tree exp, rtx target, rtx subtarget, machine_mode mode,
     case BUILT_IN_VA_ARG_PACK:
       /* All valid uses of __builtin_va_arg_pack () are removed during
 	 inlining.  */
-      error ("invalid use of %<__builtin_va_arg_pack ()%>");
+      error ("%Kinvalid use of %<__builtin_va_arg_pack ()%>", exp);
       return const0_rtx;
 
     case BUILT_IN_VA_ARG_PACK_LEN:
       /* All valid uses of __builtin_va_arg_pack_len () are removed during
 	 inlining.  */
-      error ("invalid use of %<__builtin_va_arg_pack_len ()%>");
+      error ("%Kinvalid use of %<__builtin_va_arg_pack_len ()%>", exp);
       return const0_rtx;
 
       /* Return the address of the first anonymous stack arg.  */
@@ -10515,7 +10271,7 @@ expand_builtin (tree exp, rtx target, rtx subtarget, machine_mode mode,
       break;
 
     /* Expand it as BUILT_IN_MEMCMP_EQ first. If not successful, change it
-       back to a BUILT_IN_STRCMP. Remember to delete the 3rd parameter
+       back to a BUILT_IN_STRCMP. Remember to delete the 3rd paramater
        when changing it to a strcmp call.  */
     case BUILT_IN_STRCMP_EQ:
       target = expand_builtin_memcmp (exp, target, true);
@@ -13240,8 +12996,8 @@ expand_builtin_object_size (tree exp)
 
   if (!validate_arglist (exp, POINTER_TYPE, INTEGER_TYPE, VOID_TYPE))
     {
-      error ("first argument of %qD must be a pointer, second integer constant",
-	     fndecl);
+      error ("%Kfirst argument of %qD must be a pointer, second integer constant",
+	     exp, fndecl);
       expand_builtin_trap ();
       return const0_rtx;
     }
@@ -13253,8 +13009,8 @@ expand_builtin_object_size (tree exp)
       || tree_int_cst_sgn (ost) < 0
       || compare_tree_int (ost, 3) > 0)
     {
-      error ("last argument of %qD is not integer constant between 0 and 3",
-	      fndecl);
+      error ("%Klast argument of %qD is not integer constant between 0 and 3",
+	     exp, fndecl);
       expand_builtin_trap ();
       return const0_rtx;
     }
@@ -14066,8 +13822,8 @@ warn_dealloc_offset (location_t loc, tree exp, const access_ref &aref)
     }
 
   if (!warning_at (loc, OPT_Wfree_nonheap_object,
-		   "%qD called on pointer %qE with nonzero offset%s",
-		   dealloc_decl, aref.ref, offstr))
+		   "%K%qD called on pointer %qE with nonzero offset%s",
+		   exp, dealloc_decl, aref.ref, offstr))
     return false;
 
   if (DECL_P (aref.ref))
@@ -14122,15 +13878,15 @@ maybe_emit_free_warning (tree exp)
     return;
 
   tree dealloc_decl = get_callee_fndecl (exp);
-  location_t loc = EXPR_LOCATION (exp);
+  location_t loc = tree_inlined_location (exp);
 
   if (DECL_P (ref) || EXPR_P (ref))
     {
       /* Diagnose freeing a declared object.  */
       if (aref.ref_declared ()
 	  && warning_at (loc, OPT_Wfree_nonheap_object,
-			 "%qD called on unallocated object %qD",
-			 dealloc_decl, ref))
+			 "%K%qD called on unallocated object %qD",
+			 exp, dealloc_decl, ref))
 	{
 	  loc = (DECL_P (ref)
 		 ? DECL_SOURCE_LOCATION (ref)
@@ -14149,8 +13905,8 @@ maybe_emit_free_warning (tree exp)
   else if (CONSTANT_CLASS_P (ref))
     {
       if (warning_at (loc, OPT_Wfree_nonheap_object,
-		      "%qD called on a pointer to an unallocated "
-		      "object %qE", dealloc_decl, ref))
+		      "%K%qD called on a pointer to an unallocated "
+		      "object %qE", exp, dealloc_decl, ref))
 	{
 	  if (TREE_CODE (ptr) == SSA_NAME)
 	    {
@@ -14182,24 +13938,23 @@ maybe_emit_free_warning (tree exp)
 	      else
 		{
 		  tree alloc_decl = gimple_call_fndecl (def_stmt);
-		  const opt_code opt =
-		    (DECL_IS_OPERATOR_NEW_P (alloc_decl)
-		     || DECL_IS_OPERATOR_DELETE_P (dealloc_decl)
-		     ? OPT_Wmismatched_new_delete
-		     : OPT_Wmismatched_dealloc);
+		  int opt = (DECL_IS_OPERATOR_NEW_P (alloc_decl)
+			     || DECL_IS_OPERATOR_DELETE_P (dealloc_decl)
+			     ? OPT_Wmismatched_new_delete
+			     : OPT_Wmismatched_dealloc);
 		  warned = warning_at (loc, opt,
-				       "%qD called on pointer returned "
+				       "%K%qD called on pointer returned "
 				       "from a mismatched allocation "
-				       "function", dealloc_decl);
+				       "function", exp, dealloc_decl);
 		}
 	    }
 	  else if (gimple_call_builtin_p (def_stmt, BUILT_IN_ALLOCA)
 	    	   || gimple_call_builtin_p (def_stmt,
 	    				     BUILT_IN_ALLOCA_WITH_ALIGN))
 	    warned = warning_at (loc, OPT_Wfree_nonheap_object,
-				 "%qD called on pointer to "
+				 "%K%qD called on pointer to "
 				 "an unallocated object",
-				 dealloc_decl);
+				 exp, dealloc_decl);
 	  else if (warn_dealloc_offset (loc, exp, aref))
 	    return;
 
@@ -14297,7 +14052,7 @@ fold_builtin_varargs (location_t loc, tree fndecl, tree *args, int nargs)
     {
       ret = build1 (NOP_EXPR, TREE_TYPE (ret), ret);
       SET_EXPR_LOCATION (ret, loc);
-      suppress_warning (ret);
+      TREE_NO_WARNING (ret) = 1;
       return ret;
     }
   return NULL_TREE;
diff --git a/gcc/ipa-strub.c b/gcc/ipa-strub.c
index e302f0ec1c5..0364b7e1305 100644
--- a/gcc/ipa-strub.c
+++ b/gcc/ipa-strub.c
@@ -210,10 +210,16 @@ get_strub_mode_from_attr (tree strub_attr, bool var_p = false)
   return mode;
 }
 
+static enum strub_mode
+get_strub_mode_from_decl (tree fndecl)
+{
+  return get_strub_mode_from_attr (get_strub_attr_from_decl (fndecl));
+}
+
 static enum strub_mode
 get_strub_mode (cgraph_node *node)
 {
-  return get_strub_mode_from_attr (get_strub_attr_from_decl (node->decl));
+  return get_strub_mode_from_decl (node->decl);
 }
 
 static enum strub_mode
@@ -263,7 +269,7 @@ calls_builtin_apply_args_p (cgraph_node *node, bool report = false)
 	break;
 
       sorry_at (gimple_location (e->call_stmt),
-		"at-calls strub does not support call to %qD",
+		"at-calls %<strub%> does not support call to %qD",
 		cdecl);
     }
 
@@ -292,7 +298,8 @@ can_strub_p (cgraph_node *node, bool report = false)
 	return result;
 
       sorry_at (DECL_SOURCE_LOCATION (node->decl),
-		"%qD is not eligible for strub because of attribute %<noipa%>",
+		"%qD is not eligible for %<strub%>"
+		" because of attribute %<noipa%>",
 		node->decl);
     }
 
@@ -352,7 +359,7 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 	return result;
 
       sorry_at (DECL_SOURCE_LOCATION (node->decl),
-		"%qD is not eligible for internal strub"
+		"%qD is not eligible for internal %<strub%>"
 		" because of attribute %<noclone%>",
 		node->decl);
     }
@@ -372,7 +379,7 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 	return result;
 
       sorry_at (gimple_location (e->call_stmt),
-		"internal strub does not support call to %qD",
+		"internal %<strub%> does not support call to %qD",
 		cdecl);
     }
 
@@ -411,7 +418,7 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 	      return result;
 
 	    sorry_at (gimple_location (label_stmt),
-		      "internal strub does not support user labels");
+		      "internal %<strub%> does not support user labels");
 	  }
     }
 
@@ -425,16 +432,16 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 	return result;
 
       sorry_at (DECL_SOURCE_LOCATION (node->decl),
-		"%qD has too many arguments for internal strub",
+		"%qD has too many arguments for internal %<strub%>",
 		node->decl);
     }
 
-  if (result)
-    /* Since we're not changing the function identity proper, just
-       moving its full implementation, we *could* disable
-       fun->cannot_be_copied_reason and/or temporarily drop a noclone
-       attribute.  */
-    gcc_checking_assert (tree_versionable_function_p (node->decl));
+  /* Since we're not changing the function identity proper, just
+     moving its full implementation, we *could* disable
+     fun->cannot_be_copied_reason and/or temporarily drop a noclone
+     attribute.  */
+  gcc_checking_assert (!result || !node->has_gimple_body_p ()
+		       || tree_versionable_function_p (node->decl));
 
   return result;
 }
@@ -489,8 +496,22 @@ strub_callable_builtin_p (cgraph_node *node)
     case BUILT_IN_NONE:
       gcc_unreachable ();
 
-      /* ??? Make all builtins callable.  We wish to make any builtin call the
-	 compiler might introduce on its own callable.  Anything that is
+      /* This temporarily allocates stack for the call, and we can't reasonably
+	 update the watermark for that.  Besides, we don't check the actual call
+	 target, nor its signature, and it seems to be overkill to as much as
+	 try to do so.  */
+    case BUILT_IN_APPLY:
+      return false;
+
+      /* Conversely, this shouldn't be called from within strub contexts, since
+	 the caller may have had its signature modified.  STRUB_INTERNAL is ok,
+	 the call will remain in the STRUB_WRAPPER, and removed from the
+	 STRUB_WRAPPED clone.  */
+    case BUILT_IN_APPLY_ARGS:
+      return false;
+
+      /* ??? Make all other builtins callable.  We wish to make any builtin call
+	 the compiler might introduce on its own callable.  Anything that is
 	 predictable enough as to be known not to allow stack data that should
 	 be strubbed to unintentionally escape to non-strub contexts can be
 	 allowed, and pretty much every builtin appears to fit this description.
@@ -698,7 +719,8 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
 	{
 	  gcc_checking_assert (analyze_body);
 	  error_at (DECL_SOURCE_LOCATION (node->decl),
-		    "%qD requires strub, but no viable strub mode was found",
+		    "%qD requires %<strub%>,"
+		    " but no viable %<strub%> mode was found",
 		    node->decl);
 	  break;
 	}
@@ -751,7 +773,7 @@ set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
 	       && mode == STRUB_INLINABLE))
 	{
 	  error_at (DECL_SOURCE_LOCATION (node->decl),
-		    "strub mode %i selected for %qD, when %i was requested",
+		    "%<strub%> mode %i selected for %qD, when %i was requested",
 		    (int) mode, node->decl,
 		    (int) get_strub_mode_from_attr (attr));
 	  if (node->alias)
@@ -979,7 +1001,7 @@ verify_strub ()
 	if (callee_mode == STRUB_DISABLED
 	    || callee_mode == STRUB_INTERNAL)
 	  error_at (gimple_location (e->call_stmt),
-		    "indirect non-strub call in strub context %qD",
+		    "indirect non-%<strub%> call in %<strub%> context %qD",
 		    node->decl);
       }
 
@@ -987,9 +1009,22 @@ verify_strub ()
       {
 	gcc_checking_assert (!e->indirect_unknown_callee);
 	if (!strub_callable_from_p (e->callee, node))
-	  error_at (gimple_location (e->call_stmt),
-		    "calling non-strub %qD in strub context %qD",
-		    e->callee->decl, node->decl);
+	  {
+	    if (get_strub_mode (e->callee) == STRUB_INLINABLE)
+	      error_at (gimple_location (e->call_stmt),
+			"calling %<always_inline%> %<strub%> %qD"
+			" in non-%<strub%> context %qD",
+			e->callee->decl, node->decl);
+	    else if (fndecl_built_in_p (e->callee->decl, BUILT_IN_APPLY_ARGS)
+		     && get_strub_mode (node) == STRUB_INTERNAL)
+	      /* This is ok, it will be kept in the STRUB_WRAPPER, and removed
+		 from the STRUB_WRAPPED's strub context.  */
+	      continue;
+	    else
+	      error_at (gimple_location (e->call_stmt),
+			"calling non-%<strub%> %qD in %<strub%> context %qD",
+			e->callee->decl, node->decl);
+	  }
       }
   }
 
@@ -1067,27 +1102,25 @@ public:
 
   /* Use a distinct ptr_type_node to denote the watermark, so that we can
      recognize it in arg lists and avoid modifying types twice.  */
-  DEF_TYPE (wmt, build_distinct_type_copy (ptr_type_node))
+  DEF_TYPE (wmt, build_variant_type_copy (ptr_type_node))
 
-  DEF_TYPE (pwmt, build_pointer_type (get_wmt ()))
+  DEF_TYPE (pwmt, build_reference_type (get_wmt ()))
 
   DEF_TYPE (qpwmt,
 	    build_qualified_type (get_pwmt (),
 				  TYPE_QUAL_RESTRICT
-				  | TYPE_QUAL_CONST))
+				  /* | TYPE_QUAL_CONST */))
 
-  DEF_TYPE (pptr, build_pointer_type (ptr_type_node))
-
-  DEF_TYPE (qpptr,
-	    build_qualified_type (get_pptr (),
+  DEF_TYPE (qptr,
+	    build_qualified_type (ptr_type_node,
 				  TYPE_QUAL_RESTRICT
 				  | TYPE_QUAL_CONST))
 
   DEF_TYPE (qpvalst,
-	    build_qualified_type (build_pointer_type
+	    build_qualified_type (build_reference_type
 				  (va_list_type_node),
 				  TYPE_QUAL_RESTRICT
-				  | TYPE_QUAL_CONST))
+				  /* | TYPE_QUAL_CONST */))
 
 #undef DEF_TYPE
 
@@ -1159,12 +1192,13 @@ public:
 
   DEF_IDENT (watermark_ptr)
   DEF_IDENT (va_list_ptr)
-  DEF_IDENT (apply_args_ptr)
+  DEF_IDENT (apply_args)
 
 #undef DEF_IDENT
 
   static inline int adjust_at_calls_type (tree);
   static inline void adjust_at_calls_call (cgraph_edge *, int);
+  static inline void adjust_at_calls_calls (cgraph_node *);
 
   static inline gimple_seq
   call_update_watermark (tree wmptr, cgraph_node *node, profile_count count,
@@ -1493,6 +1527,39 @@ ipa_strub_set_mode_for_new_functions ()
   last_cgraph_order = symtab->order;
 }
 
+/* Return the PARM_DECL of the incoming watermark pointer, if there is one.  */
+tree
+find_watermark_parm (tree fndecl)
+{
+  switch (get_strub_mode_from_decl (fndecl))
+    {
+    case STRUB_WRAPPED:
+    case STRUB_AT_CALLS:
+    case STRUB_AT_CALLS_OPT:
+      break;
+
+    case STRUB_INTERNAL:
+    case STRUB_WRAPPER:
+    case STRUB_CALLABLE:
+    case STRUB_DISABLED:
+    case STRUB_INLINABLE:
+      return NULL_TREE;
+
+    default:
+      gcc_unreachable ();
+    }
+
+  for (tree parm = DECL_ARGUMENTS (fndecl); parm; parm = DECL_CHAIN (parm))
+    if (TREE_TYPE (parm) == pass_ipa_strub::get_qpwmt ())
+      {
+	gcc_checking_assert (DECL_NAME (parm)
+			     == pass_ipa_strub::get_watermark_ptr ());
+	return parm;
+      }
+
+  gcc_unreachable ();
+}
+
 /* Adjust a STRUB_AT_CALLS function TYPE, adding a watermark pointer if it
    hasn't been added yet.  Return the named argument count.  */
 int
@@ -1507,7 +1574,7 @@ pass_ipa_strub::adjust_at_calls_type (tree type)
   tree qpwmptrt = get_qpwmt ();
   while (*tlist && TREE_VALUE (*tlist) != void_type_node)
     {
-      /* The type has alreayd been adjusted.  */
+      /* The type has already been adjusted.  */
       if (TREE_VALUE (*tlist) == qpwmptrt)
 	return named_args;
       named_args++;
@@ -1581,22 +1648,37 @@ pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
 			 && (TREE_TYPE (gimple_call_arg (ocall, named_args))
 			     == get_pwmt ())));
 
-  /* ??? If it's a (tail?) call within a strub context, maybe pass on
-     the strub watermark instead of wrapping the call.  */
-
-  /* Initialize the watermark before the call.  */
-  tree swm = create_tmp_var (get_wmt (), ".strub.watermark");
-  TREE_ADDRESSABLE (swm) = true;
-  tree swmp = build1 (ADDR_EXPR, get_pwmt (), swm);
+  /* If we're already within a strub context, pass on the incoming watermark
+     pointer, and omit the enter and leave calls around the modified call.  */
+  tree swm, swmp = (false && /* This is disabled for now, to give the
+				red-zone-capable builtin expanders more thorough
+				testing.  */
+		    (optimize_size || optimize > 2)
+		    ? find_watermark_parm (e->caller->decl)
+		    : NULL_TREE);
+  bool omit_own_watermark = swmp;
+  if (omit_own_watermark)
+    swm = build2 (MEM_REF,
+		  TREE_TYPE (TREE_TYPE (swmp)),
+		  swmp,
+		  build_int_cst (TREE_TYPE (swmp), 0));
+  else
+    {
+      swm = create_tmp_var (get_wmt (), ".strub.watermark");
+      TREE_ADDRESSABLE (swm) = true;
+      swmp = build1 (ADDR_EXPR, get_pwmt (), swm);
 
-  tree enter = get_enter ();
-  gcall *stptr = gimple_build_call (enter, 1,
-				    unshare_expr (swmp));
-  gsi_insert_before (&gsi, stptr, GSI_SAME_STMT);
+      /* Initialize the watermark before the call.  */
+      tree enter = get_enter ();
+      gcall *stptr = gimple_build_call (enter, 1,
+					unshare_expr (swmp));
+      gsi_insert_before (&gsi, stptr, GSI_SAME_STMT);
 #if !IMPLICIT_CGRAPH_EDGES
-  e->caller->create_edge (cgraph_node::get_create (enter),
-			  stptr, gsi_bb (gsi)->count, false);
+      e->caller->create_edge (cgraph_node::get_create (enter),
+			      stptr, gsi_bb (gsi)->count, false);
 #endif
+    }
+
 
   /* Replace the call with one that passes the swmp argument first.  */
   gcall *wrcall;
@@ -1674,12 +1756,12 @@ pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
 		       build_tree_list
 		       (build_tree_list
 			(NULL_TREE, build_string (2, "=m")),
-			swm));
+			unshare_expr (swm)));
 	vec_safe_push (inputs,
 		       build_tree_list
 		       (build_tree_list
 			(NULL_TREE, build_string (1, "m")),
-			swm));
+			unshare_expr (swm)));
 	gasm *forcemod = gimple_build_asm_vec ("", inputs, outputs,
 					       NULL, NULL);
 	gimple_seq_add_stmt (&seq, forcemod);
@@ -1693,7 +1775,7 @@ pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
 			   build_tree_list
 			   (build_tree_list
 			    (NULL_TREE, build_string (1, "m")),
-			    swm));
+			    unshare_expr (swm)));
 	    gasm *force_store = gimple_build_asm_vec ("", inputs, NULL,
 						      NULL, NULL);
 	    gsi_insert_before (&gsi, force_store, GSI_SAME_STMT);
@@ -1701,17 +1783,69 @@ pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
       }
 #endif
 
-    gcall *sleave = gimple_build_call (get_leave (), 1,
-				       unshare_expr (swmp));
-    gimple_seq_add_stmt (&seq, sleave);
+    if (!omit_own_watermark)
+      {
+	gcall *sleave = gimple_build_call (get_leave (), 1,
+					   unshare_expr (swmp));
+	gimple_seq_add_stmt (&seq, sleave);
 
-    gassign *clobber = gimple_build_assign (swm,
-					    build_clobber
-					    (TREE_TYPE (swm)));
-    gimple_seq_add_stmt (&seq, clobber);
+	gassign *clobber = gimple_build_assign (swm,
+						build_clobber
+						(TREE_TYPE (swm)));
+	gimple_seq_add_stmt (&seq, clobber);
+      }
   }
 
-  gsi_insert_finally_seq_after_call (gsi, seq);
+  if (seq)
+    gsi_insert_finally_seq_after_call (gsi, seq);
+}
+
+void
+pass_ipa_strub::adjust_at_calls_calls (cgraph_node *node)
+{
+  /* Adjust unknown-callee indirect calls with STRUB_AT_CALLS types within
+     onode.  */
+  if (node->indirect_calls)
+    {
+      push_cfun (DECL_STRUCT_FUNCTION (node->decl));
+      for (cgraph_edge *e = node->indirect_calls; e; e = e->next_callee)
+	{
+	  gcc_checking_assert (e->indirect_unknown_callee);
+
+	  tree callee_fntype = gimple_call_fntype (e->call_stmt);
+	  enum strub_mode callee_mode
+	    = get_strub_mode_from_type (callee_fntype);
+
+	  if (callee_mode != STRUB_AT_CALLS
+	      && callee_mode != STRUB_AT_CALLS_OPT)
+	    continue;
+
+	  int named_args = adjust_at_calls_type (callee_fntype);
+
+	  adjust_at_calls_call (e, named_args);
+	}
+      pop_cfun ();
+    }
+
+  if (node->callees)
+    {
+      push_cfun (DECL_STRUCT_FUNCTION (node->decl));
+      for (cgraph_edge *e = node->callees; e; e = e->next_callee)
+	{
+	  gcc_checking_assert (!e->indirect_unknown_callee);
+
+	  enum strub_mode callee_mode = get_strub_mode (e->callee);
+
+	  if (callee_mode != STRUB_AT_CALLS
+	      && callee_mode != STRUB_AT_CALLS_OPT)
+	    continue;
+
+	  int named_args = adjust_at_calls_type (TREE_TYPE (e->callee->decl));
+
+	  adjust_at_calls_call (e, named_args);
+	}
+      pop_cfun ();
+    }
 }
 
 unsigned int
@@ -1849,54 +1983,13 @@ pass_ipa_strub::execute (function *)
     if (!onode->has_gimple_body_p ())
       continue;
 
-    /* Adjust unknown-callee indirect calls with STRUB_AT_CALLS types within
-       onode.  */
-    if (onode->indirect_calls)
-      {
-	push_cfun (DECL_STRUCT_FUNCTION (onode->decl));
-	for (cgraph_edge *e = onode->indirect_calls; e; e = e->next_callee)
-	  {
-	    gcc_checking_assert (e->indirect_unknown_callee);
-
-	    tree callee_fntype = gimple_call_fntype (e->call_stmt);
-	    enum strub_mode callee_mode
-	      = get_strub_mode_from_type (callee_fntype);
-
-	    if (callee_mode != STRUB_AT_CALLS
-		&& callee_mode != STRUB_AT_CALLS_OPT)
-	      continue;
-
-	    int named_args = adjust_at_calls_type (callee_fntype);
-
-	    adjust_at_calls_call (e, named_args);
-	  }
-	pop_cfun ();
-      }
-
-    if (onode->callees)
-      {
-	push_cfun (DECL_STRUCT_FUNCTION (onode->decl));
-	for (cgraph_edge *e = onode->callees; e; e = e->next_callee)
-	  {
-	    gcc_checking_assert (!e->indirect_unknown_callee);
-
-	    enum strub_mode callee_mode = get_strub_mode (e->callee);
-
-	    if (callee_mode != STRUB_AT_CALLS
-		&& callee_mode != STRUB_AT_CALLS_OPT)
-	      continue;
-
-	    int named_args = adjust_at_calls_type (TREE_TYPE (e->callee->decl));
-
-	    adjust_at_calls_call (e, named_args);
-	  }
-	pop_cfun ();
-      }
-
     enum strub_mode mode = get_strub_mode (onode);
 
     if (mode != STRUB_INTERNAL)
-      continue;
+      {
+	adjust_at_calls_calls (onode);
+	continue;
+      }
 
 #if 0
     /* Hmm, this is an i386-specific attribute.  Do we need machine-specific
@@ -1909,7 +2002,7 @@ pass_ipa_strub::execute (function *)
     if (!DECL_STRUCT_FUNCTION (onode->decl))
       {
 	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"not splitting struct-less function %qD for stack scrubbing",
+		"not splitting struct-less function %qD for %<strub%>",
 		onode->decl);
 	continue;
       }
@@ -1917,7 +2010,7 @@ pass_ipa_strub::execute (function *)
     if (!onode->lowered)
       {
 	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"not splitting non-lowered function %qD for stack scrubbing",
+		"not splitting non-lowered function %qD for %<strub%>",
 		onode->decl);
 	continue;
       }
@@ -1929,7 +2022,7 @@ pass_ipa_strub::execute (function *)
     if (!tree_versionable_function_p (onode->decl))
       {
 	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"%qD cannot be split for stack scrubbing",
+		"%qD cannot be split for %<strub%>",
 		onode->decl);
 	continue;
       }
@@ -1956,7 +2049,7 @@ pass_ipa_strub::execute (function *)
 	{
 	  ipa_adjusted_param aaadj = {};
 	  aaadj.op = IPA_PARAM_OP_NEW;
-	  aaadj.type = get_qpptr ();
+	  aaadj.type = get_qptr ();
 	  vec_safe_push (nparms, aaadj);
 	}
 
@@ -1982,7 +2075,7 @@ pass_ipa_strub::execute (function *)
     if (!nnode)
       {
 	error_at (DECL_SOURCE_LOCATION (onode->decl),
-		  "failed to split %qD for stack scrubbing",
+		  "failed to split %qD for %<strub%>",
 		  onode->decl);
 	continue;
       }
@@ -1996,6 +2089,8 @@ pass_ipa_strub::execute (function *)
     set_strub_mode_to (onode, STRUB_WRAPPER);
     set_strub_mode_to (nnode, STRUB_WRAPPED);
 
+    adjust_at_calls_calls (nnode);
+
     /* Decide which of the wrapped function's parms we want to turn into
        references to the argument passed to the wrapper.  In general, we want to
        copy small arguments, and avoid copying large ones.  Variable-sized array
@@ -2181,8 +2276,8 @@ pass_ipa_strub::execute (function *)
 	     only of reading because const/pure.  */
 	  if (apply_args)
 	    {
-	      nspec[curlen++] = (no_writes_p ? 'r' : '.');
-	      nspec[curlen++] = (no_writes_p ? 't' : ' ');
+	      nspec[curlen++] = 'r';
+	      nspec[curlen++] = ' ';
 	    }
 	  if (is_stdarg)
 	    {
@@ -2485,24 +2580,24 @@ pass_ipa_strub::execute (function *)
     }
 
     {
-      tree aaptr = NULL_TREE;
+      tree aaval = NULL_TREE;
       tree vaptr = NULL_TREE;
       tree wmptr = NULL_TREE;
       for (tree arg = DECL_ARGUMENTS (nnode->decl); arg; arg = DECL_CHAIN (arg))
 	{
-	  aaptr = vaptr;
+	  aaval = vaptr;
 	  vaptr = wmptr;
 	  wmptr = arg;
 	}
 
       if (!apply_args)
-	aaptr = NULL_TREE;
+	aaval = NULL_TREE;
       /* The trailing args are [apply_args], [va_list_ptr], and
 	 watermark.  If we don't have a va_list_ptr, the penultimate
 	 argument is apply_args.
        */
       else if (!is_stdarg)
-	aaptr = vaptr;
+	aaval = vaptr;
 
       if (!is_stdarg)
 	vaptr = NULL_TREE;
@@ -2522,10 +2617,10 @@ pass_ipa_strub::execute (function *)
 
       if (apply_args)
 	{
-	  DECL_NAME (aaptr) = get_apply_args_ptr ();
-	  DECL_ARTIFICIAL (aaptr) = 1;
-	  DECL_IGNORED_P (aaptr) = 1;
-	  TREE_USED (aaptr) = 1;
+	  DECL_NAME (aaval) = get_apply_args ();
+	  DECL_ARTIFICIAL (aaval) = 1;
+	  DECL_IGNORED_P (aaval) = 1;
+	  TREE_USED (aaval) = 1;
 	}
 
       push_cfun (DECL_STRUCT_FUNCTION (nnode->decl));
@@ -2582,9 +2677,10 @@ pass_ipa_strub::execute (function *)
 	    else if (fndecl && is_stdarg
 		     && fndecl_built_in_p (fndecl, BUILT_IN_VA_START))
 	      {
-		if (builtin_decl_explicit (BUILT_IN_VA_START) != fndecl)
-		  sorry_at (gimple_location (call),
-			    "nonstandard stdarg conventions");
+		/* Using a non-default stdarg ABI makes the function ineligible
+		   for internal strub.  */
+		gcc_checking_assert (builtin_decl_explicit (BUILT_IN_VA_START)
+				     == fndecl);
 		tree bvacopy = builtin_decl_explicit (BUILT_IN_VA_COPY);
 		gimple_call_set_fndecl (call, bvacopy);
 		tree arg = vaptr;
@@ -2607,7 +2703,9 @@ pass_ipa_strub::execute (function *)
 		     && fndecl_built_in_p (fndecl, BUILT_IN_APPLY_ARGS))
 	      {
 		tree lhs = gimple_call_lhs (call);
-		gassign *assign = gimple_build_assign (lhs, aaptr);
+		gimple *assign = (lhs
+				  ? gimple_build_assign (lhs, aaval)
+				  : gimple_build_nop ());
 		gsi_replace (&gsi, assign, true);
 		cgraph_edge::remove (e);
 	      }
@@ -2650,7 +2748,7 @@ pass_ipa_strub::execute (function *)
 
       if (apply_args)
 	{
-	  tree aalst = create_tmp_var (ptr_type_node, ".strub.appargs");
+	  tree aalst = create_tmp_var (ptr_type_node, ".strub.apply_args");
 	  tree bappargs = builtin_decl_explicit (BUILT_IN_APPLY_ARGS);
 	  gcall *appargs = gimple_build_call (bappargs, 0);
 	  gimple_call_set_lhs (appargs, aalst);
diff --git a/gcc/ipa-strub.h b/gcc/ipa-strub.h
index cadbca5002a..ea8ee857e8e 100644
--- a/gcc/ipa-strub.h
+++ b/gcc/ipa-strub.h
@@ -23,3 +23,4 @@ along with GCC; see the file COPYING3.  If not see
    doesn't have to be called directly by CALLER, but the returned
    value says nothing about intervening functions.  */
 extern bool strub_inlinable_p (cgraph_node *callee, cgraph_node *caller);
+extern tree find_watermark_parm (tree fndecl);
diff --git a/gcc/testsuite/c-c++-common/strub-O2.c b/gcc/testsuite/c-c++-common/strub-O2.c
index 9cc39e763b1..7848c46d179 100644
--- a/gcc/testsuite/c-c++-common/strub-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-O2.c
@@ -13,4 +13,4 @@ int f() {
 /* { dg-final { scan-rtl-dump-not "strub_enter" "expand" } } */
 /* { dg-final { scan-rtl-dump-not "strub_update" "expand" } } */
 /* { dg-final { scan-rtl-dump "strub_leave" "expand" } } */
-/* { dg-final { scan-rtl-dump "\n\[(\]call_insn\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
+/* { dg-final { scan-rtl-dump "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-O2fni.c b/gcc/testsuite/c-c++-common/strub-O2fni.c
index 51cae845d5f..85a8f76785e 100644
--- a/gcc/testsuite/c-c++-common/strub-O2fni.c
+++ b/gcc/testsuite/c-c++-common/strub-O2fni.c
@@ -1,7 +1,7 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fdump-rtl-expand -fno-inline-functions" } */
+/* { dg-options "-O2 -fstrub=default -fdump-rtl-expand -fno-inline" } */
 
-/* With -fno-inline-functions, none of the strub builtins are inlined.  */
+/* With -fno-inline, none of the strub builtins are inlined.  */
 
 int __attribute__ ((__strub__)) var;
 
@@ -12,4 +12,4 @@ int f() {
 /* { dg-final { scan-rtl-dump "strub_enter" "expand" } } */
 /* { dg-final { scan-rtl-dump "strub_update" "expand" } } */
 /* { dg-final { scan-rtl-dump "strub_leave" "expand" } } */
-/* { dg-final { scan-rtl-dump-not "\n\[(\]call_insn\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
+/* { dg-final { scan-rtl-dump-not "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-O3fni.c b/gcc/testsuite/c-c++-common/strub-O3fni.c
index 8f67b613be8..a2eedfd96b2 100644
--- a/gcc/testsuite/c-c++-common/strub-O3fni.c
+++ b/gcc/testsuite/c-c++-common/strub-O3fni.c
@@ -1,7 +1,7 @@
 /* { dg-do compile } */
 /* { dg-options "-O3 -fstrub=default -fdump-rtl-expand -fno-inline" } */
 
-/* With -fno-inline-functions, none of the strub builtins are inlined.  */
+/* With -fno-inline, none of the strub builtins are inlined.  */
 
 int __attribute__ ((__strub__)) var;
 
@@ -12,4 +12,4 @@ int f() {
 /* { dg-final { scan-rtl-dump "strub_enter" "expand" } } */
 /* { dg-final { scan-rtl-dump "strub_update" "expand" } } */
 /* { dg-final { scan-rtl-dump "strub_leave" "expand" } } */
-/* { dg-final { scan-rtl-dump-not "\n\[(\]call_insn\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
+/* { dg-final { scan-rtl-dump-not "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-Og.c b/gcc/testsuite/c-c++-common/strub-Og.c
index 6f60349573f..e5cb1f60541 100644
--- a/gcc/testsuite/c-c++-common/strub-Og.c
+++ b/gcc/testsuite/c-c++-common/strub-Og.c
@@ -13,4 +13,4 @@ int f() {
 /* { dg-final { scan-rtl-dump-not "strub_enter" "expand" } } */
 /* { dg-final { scan-rtl-dump "strub_update" "expand" } } */
 /* { dg-final { scan-rtl-dump "strub_leave" "expand" } } */
-/* { dg-final { scan-rtl-dump-not "\n\[(\]call_insn\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
+/* { dg-final { scan-rtl-dump-not "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-Os.c b/gcc/testsuite/c-c++-common/strub-Os.c
index 5d1c08a7528..194aacc2c05 100644
--- a/gcc/testsuite/c-c++-common/strub-Os.c
+++ b/gcc/testsuite/c-c++-common/strub-Os.c
@@ -15,4 +15,4 @@ int f() {
 /* { dg-final { scan-rtl-dump-not "strub_enter" "expand" } } */
 /* { dg-final { scan-rtl-dump-not "strub_update" "expand" } } */
 /* { dg-final { scan-rtl-dump "strub_leave" "expand" } } */
-/* { dg-final { scan-rtl-dump-not "\n\[(\]call_insn\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
+/* { dg-final { scan-rtl-dump-not "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-all1.c b/gcc/testsuite/c-c++-common/strub-all1.c
index 54daf84656c..46e84bf6560 100644
--- a/gcc/testsuite/c-c++-common/strub-all1.c
+++ b/gcc/testsuite/c-c++-common/strub-all1.c
@@ -3,9 +3,9 @@
 
 /* h becomes STRUB_CALLABLE, rather than STRUB_INLINABLE, because of the
    strub-enabling -fstrub flag, and gets inlined before pass_ipa_strub.  */
-static void
+static inline void
 __attribute__ ((__always_inline__))
-h() {  /* { dg-warning "might not be inlinable" } */
+h() {
 }
 
 /* g becomes STRUB_AT_CALLS, because of the flag.  */
diff --git a/gcc/testsuite/c-c++-common/strub-apply1.c b/gcc/testsuite/c-c++-common/strub-apply1.c
new file mode 100644
index 00000000000..f180b17f30e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply1.c
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default" } */
+
+void __attribute__ ((__strub__ (3)))
+apply_function (void *args)
+{
+  __builtin_apply (0, args, 0);
+}
+
+void __attribute__ ((__strub__ (2)))
+apply_args (int i, int j, double d)
+{
+  void *args = __builtin_apply_args ();
+  apply_function (args);
+}
diff --git a/gcc/testsuite/c-c++-common/strub-apply2.c b/gcc/testsuite/c-c++-common/strub-apply2.c
new file mode 100644
index 00000000000..379a54b73b7
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply2.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default" } */
+
+extern void __attribute__ ((__strub__))
+apply_function (void *args);
+
+void __attribute__ ((__strub__))
+apply_args (int i, int j, double d) /* { dg-error "selected" } */
+{
+  void *args = __builtin_apply_args (); /* { dg-message "does not support" } */
+  apply_function (args);
+}
diff --git a/gcc/testsuite/c-c++-common/strub-apply3.c b/gcc/testsuite/c-c++-common/strub-apply3.c
new file mode 100644
index 00000000000..9b4786be698
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply3.c
@@ -0,0 +1,8 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default" } */
+
+void __attribute__ ((__strub__))
+apply_function (void *args)
+{
+  __builtin_apply (0, args, 0); /* { dg-error "in .strub. context" } */
+}
diff --git a/gcc/testsuite/c-c++-common/strub-apply4.c b/gcc/testsuite/c-c++-common/strub-apply4.c
new file mode 100644
index 00000000000..409f747743e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply4.c
@@ -0,0 +1,21 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fstrub=default -fdump-ipa-strubm" } */
+
+/* Check that implicit enabling of strub mode selects internal strub when the
+   function uses __builtin_apply_args, that prevents the optimization to
+   at-calls mode.  */
+
+int __attribute__ ((__strub__)) var;
+
+static inline void
+apply_args (int i, int j, double d)
+{
+  var++;
+  __builtin_apply_args ();
+}
+
+void f() {
+  apply_args (1, 2, 3);
+}
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 1 "strubm" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-at-calls1.c b/gcc/testsuite/c-c++-common/strub-at-calls1.c
index 0d1b9fce833..d964b07ae5d 100644
--- a/gcc/testsuite/c-c++-common/strub-at-calls1.c
+++ b/gcc/testsuite/c-c++-common/strub-at-calls1.c
@@ -3,9 +3,9 @@
 
 /* h becomes STRUB_CALLABLE, rather than STRUB_INLINABLE, because of the
    strub-enabling -fstrub flag, and gets inlined before pass_ipa_strub.  */
-static void
+static inline void
 __attribute__ ((__always_inline__))
-h() {  /* { dg-warning "might not be inlinable" } */
+h() {
 }
 
 /* g becomes STRUB_AT_CALLS, because of the flag.  */
diff --git a/gcc/testsuite/c-c++-common/strub-default1.c b/gcc/testsuite/c-c++-common/strub-default1.c
index a1e1803aadc..d579ec62f55 100644
--- a/gcc/testsuite/c-c++-common/strub-default1.c
+++ b/gcc/testsuite/c-c++-common/strub-default1.c
@@ -3,12 +3,12 @@
 
 static int __attribute__ ((__strub__)) var;
 
-/* h becomes STRUB_STRUB_INLINABLE, because of the use of the strub variable,
+/* h becomes STRUB_INLINABLE, because of the use of the strub variable,
    and the always_inline flag.  It would get inlined before pass_ipa_strub, if
    it weren't for the error.  */
-static void
+static inline void
 __attribute__ ((__always_inline__))
-h() {  /* { dg-warning "might not be inlinable" } */
+h() {
   var++;
 }
 
@@ -34,7 +34,7 @@ f() {
 /* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 1 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-3\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]-3\[)\]" 1 "strub" } } */
 /* { dg-final { scan-ipa-dump-times "strub \[(\]-4\[)\]" 1 "strub" } } */
 /* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 1 "strub" } } */
 /* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-internal1.c b/gcc/testsuite/c-c++-common/strub-internal1.c
index b9bd787df0a..a74658c9ac9 100644
--- a/gcc/testsuite/c-c++-common/strub-internal1.c
+++ b/gcc/testsuite/c-c++-common/strub-internal1.c
@@ -3,9 +3,9 @@
 
 /* h becomes STRUB_CALLABLE, rather than STRUB_INLINABLE, because of the
    strub-enabling -fstrub flag, and gets inlined before pass_ipa_strub.  */
-static void
+static inline void
 __attribute__ ((__always_inline__))
-h() {  /* { dg-warning "might not be inlinable" } */
+h() {
 }
 
 /* g becomes STRUB_INTERNAL because of the flag, and gets split into
diff --git a/gcc/testsuite/c-c++-common/strub-parms1.c b/gcc/testsuite/c-c++-common/strub-parms1.c
new file mode 100644
index 00000000000..0422ccc7f7d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-parms1.c
@@ -0,0 +1,48 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+#include <stdarg.h>
+
+void __attribute__ ((__strub__ (2)))
+small_args (int i, long long l, void *p, void **q, double d, char c)
+{
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid small_args.strub.\[0-9\]* \[(\]int i, long long int l, void \\* p, void \\* \\* q, double d, char c, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " small_args.strub.\[0-9\]* \[(\]\[^&\]*&.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+
+
+struct large_arg {
+  int x[128];
+};
+
+void __attribute__ ((__strub__ (2)))
+large_byref_arg (struct large_arg la)
+{
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid large_byref_arg.strub.\[0-9\]* \[(\]struct large_arg & la, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " large_byref_arg.strub.\[0-9\]* \[(\]&\[^&\]*&.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+
+void __attribute__ ((__strub__ (2)))
+std_arg (int i, ...)
+{
+  va_list vl;
+  va_start (vl, i);
+  va_end (vl);
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid std_arg.strub.\[0-9\]* \[(\]int i, \[^&,\]* &\[^&,\]*.strub.va_list_ptr, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " std_arg.strub.\[0-9\]* \[(\]\[^&\]*&.strub.va_list.\[0-9\]*, &.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_start \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_copy \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_end \\(" 2 "strub" } } */
+
+void __attribute__ ((__strub__ (2)))
+apply_args (int i, int j, double d)
+{
+  __builtin_apply_args ();
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid apply_args.strub.\[0-9\]* \[(\]int i, int j, double d, void \\*\[^&,\]*.strub.apply_args, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " apply_args.strub.\[0-9\]* \[(\]\[^&\]*.strub.apply_args.\[0-9\]*_\[0-9\]*, &.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-parms2.c b/gcc/testsuite/c-c++-common/strub-parms2.c
new file mode 100644
index 00000000000..aaf02a2cd46
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-parms2.c
@@ -0,0 +1,36 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+#include <stdarg.h>
+
+void __attribute__ ((__strub__ (1)))
+small_args (int i, long long l, void *p, void **q, double d, char c)
+{
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid small_args \[(\]int i, long long int l, void \\* p, void \\* \\* q, double d, char c, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+
+
+struct large_arg {
+  int x[128];
+};
+
+void __attribute__ ((__strub__ (1)))
+large_byref_arg (struct large_arg la)
+{
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid large_byref_arg \[(\]struct large_arg la, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+
+void __attribute__ ((__strub__ (1)))
+std_arg (int i, ...)
+{
+  va_list vl;
+  va_start (vl, i);
+  va_end (vl);
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid std_arg \[(\]int i, void \\* &\[^&,\]*.strub.watermark_ptr\[, .]*\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_start \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-not "va_copy \\(" "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_end \\(" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-parms3.c b/gcc/testsuite/c-c++-common/strub-parms3.c
new file mode 100644
index 00000000000..2846098160d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-parms3.c
@@ -0,0 +1,58 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that uses of a strub variable implicitly enables internal strub for
+   publicly-visible functions, and causes the same transformations to their
+   signatures as those in strub-parms1.c.  */
+
+#include <stdarg.h>
+
+int __attribute__ ((__strub__)) var;
+
+void
+small_args (int i, long long l, void *p, void **q, double d, char c)
+{
+  var++;
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid small_args.strub.\[0-9\]* \[(\]int i, long long int l, void \\* p, void \\* \\* q, double d, char c, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " small_args.strub.\[0-9\]* \[(\]\[^&\]*&.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+
+
+struct large_arg {
+  int x[128];
+};
+
+void
+large_byref_arg (struct large_arg la)
+{
+  var++;
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid large_byref_arg.strub.\[0-9\]* \[(\]struct large_arg & la, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " large_byref_arg.strub.\[0-9\]* \[(\]&\[^&\]*&.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+
+void
+std_arg (int i, ...)
+{
+  va_list vl;
+  va_start (vl, i);
+  var++;
+  va_end (vl);
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid std_arg.strub.\[0-9\]* \[(\]int i, \[^&,\]* &\[^&,\]*.strub.va_list_ptr, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " std_arg.strub.\[0-9\]* \[(\]\[^&\]*&.strub.va_list.\[0-9\]*, &.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_start \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_copy \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_end \\(" 2 "strub" } } */
+
+void
+apply_args (int i, int j, double d)
+{
+  var++;
+  __builtin_apply_args ();
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid apply_args.strub.\[0-9\]* \[(\]int i, int j, double d, void \\*\[^&,\]*.strub.apply_args, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " apply_args.strub.\[0-9\]* \[(\]\[^&\]*.strub.apply_args.\[0-9\]*_\[0-9\]*, &.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable1.c b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
new file mode 100644
index 00000000000..45965f275c9
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub -fno-inline" } */
+
+/* Check that strub and non-strub functions can be called from non-strub
+   contexts, and that strub and callable functions can be called from strub
+   contexts.  */
+
+#define OMIT_IMPERMISSIBLE_CALLS 1
+#include "strub-callable2.c"
+
+/* { dg-final { scan-ipa-dump-times "strub_enter" 45 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_update" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_leave" 45 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable2.c b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
new file mode 100644
index 00000000000..38935e3270b
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
@@ -0,0 +1,264 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default" } */
+
+/* Check that impermissible (cross-strub-context) calls are reported.  */
+
+extern int __attribute__ ((__strub__ (3))) xcallable (void);
+extern int __attribute__ ((__strub__ (2))) xinternal (void);
+extern int __attribute__ ((__strub__ (1))) xat_calls (void);
+extern int __attribute__ ((__strub__ (0))) xdisabled (void);
+
+int __attribute__ ((__strub__ (3))) callable (void);
+int __attribute__ ((__strub__ (2))) internal (void);
+int __attribute__ ((__strub__ (1))) at_calls (void);
+int __attribute__ ((__strub__ (0))) disabled (void);
+
+int __attribute__ ((__strub__)) var;
+int var_user (void);
+
+static inline int __attribute__ ((__always_inline__, __strub__ (3)))
+icallable (void);
+static inline int __attribute__ ((__always_inline__, __strub__ (2)))
+iinternal (void);
+static inline int __attribute__ ((__always_inline__, __strub__ (1)))
+iat_calls (void);
+static inline int __attribute__ ((__always_inline__, __strub__ (0)))
+idisabled (void);
+static inline int __attribute__ ((__always_inline__))
+ivar_user (void);
+
+static inline int __attribute__ ((__always_inline__, __strub__ (3)))
+i_callable (void) { return 0; }
+static inline int __attribute__ ((__always_inline__, __strub__ (2)))
+i_internal (void) { return var; }
+static inline int __attribute__ ((__always_inline__, __strub__ (1)))
+i_at_calls (void) { return var; }
+static inline int __attribute__ ((__always_inline__, __strub__ (0)))
+i_disabled (void) { return 0; }
+static inline int __attribute__ ((__always_inline__))
+i_var_user (void) { return var; }
+
+#define CALLS_GOOD_FOR_STRUB_CONTEXT(ISEP)	\
+  do {						\
+    ret += i ## ISEP ## at_calls ();		\
+    ret += i ## ISEP ## internal ();		\
+    ret += i ## ISEP ## var_user ();		\
+  } while (0)
+
+#define CALLS_GOOD_FOR_NONSTRUB_CONTEXT(ISEP)	\
+  do {						\
+    ret += internal ();				\
+    ret += disabled ();				\
+    ret += var_user ();				\
+						\
+    ret += i ## ISEP ## disabled ();		\
+						\
+    ret += xinternal ();			\
+    ret += xdisabled ();			\
+  } while (0)
+
+#define CALLS_GOOD_FOR_EITHER_CONTEXT(ISEP)	\
+  do {						\
+    ret += i ## ISEP ## callable ();		\
+						\
+    ret += callable ();				\
+    ret += at_calls ();				\
+						\
+    ret += xat_calls ();			\
+    ret += xcallable ();			\
+  } while (0)
+
+/* Not a strub context, so it can call anything.
+   Explicitly declared as callable even from within strub contexts.  */
+int __attribute__ ((__strub__ (3)))
+callable (void) {
+  int ret = 0;
+
+  /* CALLS_GOOD_FOR_STRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += iat_calls (); /* { dg-error "in non-.strub. context" } */
+    ret += iinternal (); /* { dg-error "in non-.strub. context" } */
+    ret += ivar_user (); /* { dg-error "in non-.strub. context" } */
+#endif
+  CALLS_GOOD_FOR_EITHER_CONTEXT();
+  CALLS_GOOD_FOR_NONSTRUB_CONTEXT();
+
+  return ret;
+}
+
+/* Internal strubbing means the body is a strub context, so it can only call
+   strub functions, and it's not itself callable from strub functions.  */
+int __attribute__ ((__strub__ (2)))
+internal (void) {
+  int ret = var;
+
+  CALLS_GOOD_FOR_STRUB_CONTEXT();
+  CALLS_GOOD_FOR_EITHER_CONTEXT();
+  /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += internal (); /* { dg-error "in .strub. context" } */
+    ret += disabled (); /* { dg-error "in .strub. context" } */
+    ret += var_user (); /* { dg-error "in .strub. context" } */
+
+    ret += idisabled (); /* { dg-error "in .strub. context" } */
+
+    ret += xinternal (); /* { dg-error "in .strub. context" } */
+    ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+  return ret;
+}
+
+int __attribute__ ((__strub__ (1)))
+at_calls (void) {
+  int ret = var;
+
+  CALLS_GOOD_FOR_STRUB_CONTEXT();
+  CALLS_GOOD_FOR_EITHER_CONTEXT();
+  /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += internal (); /* { dg-error "in .strub. context" } */
+    ret += disabled (); /* { dg-error "in .strub. context" } */
+    ret += var_user (); /* { dg-error "in .strub. context" } */
+
+    ret += idisabled (); /* { dg-error "in .strub. context" } */
+
+    ret += xinternal (); /* { dg-error "in .strub. context" } */
+    ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+  return ret;
+}
+
+int __attribute__ ((__strub__ (0)))
+disabled () {
+  int ret = 0;
+
+  /* CALLS_GOOD_FOR_STRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += iat_calls (); /* { dg-error "in non-.strub. context" } */
+    ret += iinternal (); /* { dg-error "in non-.strub. context" } */
+    ret += ivar_user (); /* { dg-error "in non-.strub. context" } */
+#endif
+  CALLS_GOOD_FOR_EITHER_CONTEXT();
+  CALLS_GOOD_FOR_NONSTRUB_CONTEXT();
+
+  return ret;
+}  
+
+int
+var_user (void) {
+  int ret = var;
+
+  CALLS_GOOD_FOR_STRUB_CONTEXT();
+  CALLS_GOOD_FOR_EITHER_CONTEXT();
+  /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += internal (); /* { dg-error "in .strub. context" } */
+    ret += disabled (); /* { dg-error "in .strub. context" } */
+    ret += var_user (); /* { dg-error "in .strub. context" } */
+
+    ret += idisabled (); /* { dg-error "in .strub. context" } */
+
+    ret += xinternal (); /* { dg-error "in .strub. context" } */
+    ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+  return ret;
+}
+
+int
+icallable (void)
+{
+  int ret = 0;
+
+  /* CALLS_GOOD_FOR_STRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += i_at_calls (); /* { dg-error "in non-.strub. context" } */
+    ret += i_internal (); /* { dg-error "in non-.strub. context" } */
+    ret += i_var_user (); /* { dg-error "in non-.strub. context" } */
+#endif
+  CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+  CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_);
+
+  return ret;
+}
+
+int
+iinternal (void) {
+  int ret = var;
+
+  CALLS_GOOD_FOR_STRUB_CONTEXT(_);
+  CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+  /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += internal (); /* { dg-error "in .strub. context" } */
+    ret += disabled (); /* { dg-error "in .strub. context" } */
+    ret += var_user (); /* { dg-error "in .strub. context" } */
+
+    ret += i_disabled (); /* { dg-error "in .strub. context" } */
+
+    ret += xinternal (); /* { dg-error "in .strub. context" } */
+    ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+  return ret;
+}
+
+int
+iat_calls (void) {
+  int ret = var;
+
+  CALLS_GOOD_FOR_STRUB_CONTEXT(_);
+  CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+  /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += internal (); /* { dg-error "in .strub. context" } */
+    ret += disabled (); /* { dg-error "in .strub. context" } */
+    ret += var_user (); /* { dg-error "in .strub. context" } */
+
+    ret += i_disabled (); /* { dg-error "in .strub. context" } */
+
+    ret += xinternal (); /* { dg-error "in .strub. context" } */
+    ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+  return ret;
+}
+
+int
+idisabled () {
+  int ret = 0;
+
+  /* CALLS_GOOD_FOR_STRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += i_at_calls (); /* { dg-error "in non-.strub. context" } */
+    ret += i_internal (); /* { dg-error "in non-.strub. context" } */
+    ret += i_var_user (); /* { dg-error "in non-.strub. context" } */
+#endif
+  CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+  CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_);
+
+  return ret;
+}  
+
+int
+ivar_user (void) {
+  int ret = var;
+
+  CALLS_GOOD_FOR_STRUB_CONTEXT(_);
+  CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+  /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += internal (); /* { dg-error "in .strub. context" } */
+    ret += disabled (); /* { dg-error "in .strub. context" } */
+    ret += var_user (); /* { dg-error "in .strub. context" } */
+
+    ret += i_disabled (); /* { dg-error "in .strub. context" } */
+
+    ret += xinternal (); /* { dg-error "in .strub. context" } */
+    ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+  return ret;
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const1.c b/gcc/testsuite/c-c++-common/torture/strub-const1.c
new file mode 100644
index 00000000000..100fb0c59a9
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const1.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub const function call, we issue an asm statement
+   to make sure the watermark passed to it is held in memory before the call,
+   and another to make sure it is not assumed to be unchanged.  */
+
+int __attribute__ ((__strub__, __const__))
+f() {
+  return 0;
+}
+
+int
+g() {
+  return f();
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const2.c b/gcc/testsuite/c-c++-common/torture/strub-const2.c
new file mode 100644
index 00000000000..9e818ac9748
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const2.c
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-const function call, we issue an
+   asm statement to make sure the watermark passed to it is held in memory
+   before the call, and another to make sure it is not assumed to be
+   unchanged.  */
+
+int __attribute__ ((__strub__))
+#if ! __OPTIMIZE__
+__attribute__ ((__const__))
+#endif
+f() {
+  return 0;
+}
+
+int
+g() {
+  return f();
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const3.c b/gcc/testsuite/c-c++-common/torture/strub-const3.c
new file mode 100644
index 00000000000..d40e8aa45cb
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const3.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub const wrapping call, we issue an asm statement
+   to make sure the watermark passed to it is held in memory before the call,
+   and another to make sure it is not assumed to be unchanged.  */
+
+int __attribute__ ((__strub__ (2), __const__))
+f() {
+  return 0;
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const4.c b/gcc/testsuite/c-c++-common/torture/strub-const4.c
new file mode 100644
index 00000000000..d4cbdaf10f3
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const4.c
@@ -0,0 +1,17 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-const wrapping call, we issue an
+   asm statement to make sure the watermark passed to it is held in memory
+   before the call, and another to make sure it is not assumed to be
+   unchanged.  */
+
+int __attribute__ ((__strub__ (2)))
+#if ! __OPTIMIZE__
+__attribute__ ((__const__))
+#endif
+f() {
+  return 0;
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data5.c b/gcc/testsuite/c-c++-common/torture/strub-data5.c
index 251790d4bbb..07e25af9c53 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data5.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data5.c
@@ -1,15 +1,13 @@
 /* { dg-do compile } */
 /* { dg-options "-fstrub=default -Werror" } */
 
-/* The pointer itself is a strub variable, that would enable internal strubbing
-   if its value was used.  Here, it's only overwritten, so no strub.  */
 typedef int __attribute__ ((__strub__)) strub_int;
 strub_int *ptr;
 
 int *f () {
-  return ptr; /* { dg-warn "incompatible" } */
+  return ptr; /* { dg-warning "incompatible" } */
 }
 
 strub_int *g () {
-  return f (); /* { dg-warn "incompatible" } */
+  return f (); /* { dg-warning "incompatible" } */
 }
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure1.c b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
new file mode 100644
index 00000000000..cb223da6efc
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub pure function call, we issue an asm statement
+   to make sure the watermark passed to it is not assumed to be unchanged.  */
+
+int __attribute__ ((__strub__, __pure__))
+f() {
+  static int i; /* Stop it from being detected as const.  */
+  return i;
+}
+
+int
+g() {
+  return f();
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure2.c b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
new file mode 100644
index 00000000000..67d1434b1f8
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-pure function call, we issue an asm
+   statement to make sure the watermark passed to it is not assumed to be
+   unchanged.  */
+
+int __attribute__ ((__strub__))
+#if ! __OPTIMIZE__ /* At -O0, implicit pure detection doesn't run.  */
+__attribute__ ((__pure__))
+#endif
+f() {
+  static int i; /* Stop it from being detected as const.  */
+  return i;
+}
+
+int
+g() {
+  return f();
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure3.c b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
new file mode 100644
index 00000000000..59f02ea901f
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub pure wrapping call, we issue an asm statement
+   to make sure the watermark passed to it is not assumed to be unchanged.  */
+
+int __attribute__ ((__strub__ (2), __pure__))
+f() {
+  static int i; /* Stop it from being detected as const.  */
+  return i;
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure4.c b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
new file mode 100644
index 00000000000..973e909217d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
@@ -0,0 +1,17 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-pure wrapping call, we issue an asm
+   statement to make sure the watermark passed to it is not assumed to be
+   unchanged.  */
+
+int __attribute__ ((__strub__ (2)))
+#if ! __OPTIMIZE__ /* At -O0, implicit pure detection doesn't run.  */
+__attribute__ ((__pure__))
+#endif
+f() {
+  static int i; /* Stop it from being detected as const.  */
+  return i;
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run1.c b/gcc/testsuite/c-c++-common/torture/strub-run1.c
new file mode 100644
index 00000000000..828a4cc2998
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run1.c
@@ -0,0 +1,85 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default" } */
+
+/* Check that a non-strub function leaves a string behind in the stack, and that
+   equivalent strub functions don't.  */
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+static inline __attribute__ ((__always_inline__, __strub__ (3)))
+char *
+leak_string (void)
+{
+  /* We use this variable to avoid any stack red zone.  Stack scrubbing covers
+     it, but __builtin_stack_address, that we take as a reference, doesn't, so
+     if e.g. callable() were to store the string in the red zone, we wouldn't
+     find it because it would be outside the range we searched.  */
+  typedef void __attribute__ ((__strub__ (3))) callable_t (char *);
+  callable_t *f = 0;
+
+  char s[sizeof (test_string)];
+  __builtin_strcpy (s, test_string);
+  asm ("" : "+m" (s), "+r" (f));
+
+  if (__builtin_expect (!f, 1))
+    return __builtin_stack_address ();
+
+  f (s);
+  return 0;
+}
+
+static inline __attribute__ ((__always_inline__))
+int
+look_for_string (char *e)
+{
+  char *p = __builtin_stack_address ();
+
+  if (p == e)
+    __builtin_abort ();
+
+  if (p > e)
+    {
+      char *q = p;
+      p = e;
+      e = q;
+    }
+
+  for (char *re = e - sizeof (test_string); p < re; p++)
+    for (int i = 0; p[i] == test_string[i]; i++)
+      if (i == sizeof (test_string) - 1)
+	return i;
+
+  return 0;
+}
+
+static __attribute__ ((__noinline__, __noclone__))
+char *
+callable ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ (1)))
+char *
+at_calls ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ (2)))
+char *
+internal ()
+{
+  return leak_string ();
+}
+
+int main ()
+{
+  if (!look_for_string (callable ()))
+    __builtin_abort ();
+  if (look_for_string (at_calls ()))
+    __builtin_abort ();
+  if (look_for_string (internal ()))
+    __builtin_abort ();
+  __builtin_exit (0);
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run2.c b/gcc/testsuite/c-c++-common/torture/strub-run2.c
new file mode 100644
index 00000000000..5794b694b2d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run2.c
@@ -0,0 +1,75 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default" } */
+
+/* Check that a non-strub function leaves a string behind in the stack, and that
+   equivalent strub functions don't.  */
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+static inline __attribute__ ((__always_inline__, __strub__ (3)))
+char *
+leak_string (void)
+{
+  int len = sizeof (test_string);
+  asm ("" : "+rm" (len));
+  char s[len];
+  __builtin_strcpy (s, test_string);
+  asm ("" : "+m" (s));
+  return __builtin_stack_address ();
+}
+
+static inline __attribute__ ((__always_inline__))
+int
+look_for_string (char *e)
+{
+  char *p = __builtin_stack_address ();
+
+  if (p == e)
+    __builtin_abort ();
+
+  if (p > e)
+    {
+      char *q = p;
+      p = e;
+      e = q;
+    }
+
+  for (char *re = e - sizeof (test_string); p < re; p++)
+    for (int i = 0; p[i] == test_string[i]; i++)
+      if (i == sizeof (test_string) - 1)
+	return i;
+
+  return 0;
+}
+
+static __attribute__ ((__noinline__, __noclone__))
+char *
+callable ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ (1)))
+char *
+at_calls ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ (2)))
+char *
+internal ()
+{
+  return leak_string ();
+}
+
+int main ()
+{
+  if (!look_for_string (callable ()))
+    __builtin_abort ();
+  if (look_for_string (at_calls ()))
+    __builtin_abort ();
+  if (look_for_string (internal ()))
+    __builtin_abort ();
+  __builtin_exit (0);
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run3.c b/gcc/testsuite/c-c++-common/torture/strub-run3.c
new file mode 100644
index 00000000000..7da79055959
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run3.c
@@ -0,0 +1,75 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default" } */
+/* { dg-require-effective-target alloca } */
+
+/* Check that a non-strub function leaves a string behind in the stack, and that
+   equivalent strub functions don't.  */
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+static inline __attribute__ ((__always_inline__, __strub__ (3)))
+char *
+leak_string (void)
+{
+  int len = sizeof (test_string);
+  char *s = __builtin_alloca (len);
+  __builtin_strcpy (s, test_string);
+  asm ("" : "+m" (s));
+  return __builtin_stack_address ();
+}
+
+static inline __attribute__ ((__always_inline__))
+int
+look_for_string (char *e)
+{
+  char *p = __builtin_stack_address ();
+
+  if (p == e)
+    __builtin_abort ();
+
+  if (p > e)
+    {
+      char *q = p;
+      p = e;
+      e = q;
+    }
+
+  for (char *re = e - sizeof (test_string); p < re; p++)
+    for (int i = 0; p[i] == test_string[i]; i++)
+      if (i == sizeof (test_string) - 1)
+	return i;
+
+  return 0;
+}
+
+static __attribute__ ((__noinline__, __noclone__))
+char *
+callable ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ (1)))
+char *
+at_calls ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ (2)))
+char *
+internal ()
+{
+  return leak_string ();
+}
+
+int main ()
+{
+  if (!look_for_string (callable ()))
+    __builtin_abort ();
+  if (look_for_string (at_calls ()))
+    __builtin_abort ();
+  if (look_for_string (internal ()))
+    __builtin_abort ();
+  __builtin_exit (0);
+}
diff --git a/gcc/testsuite/g++.dg/wrappers/strub1.C b/gcc/testsuite/g++.dg/strub-run1.C
similarity index 90%
rename from gcc/testsuite/g++.dg/wrappers/strub1.C
rename to gcc/testsuite/g++.dg/strub-run1.C
index a474a929649..754291eaa01 100644
--- a/gcc/testsuite/g++.dg/wrappers/strub1.C
+++ b/gcc/testsuite/g++.dg/strub-run1.C
@@ -1,4 +1,5 @@
 // { dg-do run }
+// { dg-options "-fstrub=internal" }
 
 // Check that we don't get extra copies.
 
diff --git a/gcc/testsuite/g++.dg/wrappers/strub2.C b/gcc/testsuite/g++.dg/wrappers/strub2.C
deleted file mode 100644
index 25a62166448..00000000000
--- a/gcc/testsuite/g++.dg/wrappers/strub2.C
+++ /dev/null
@@ -1,22 +0,0 @@
-// { dg-do run }
-
-// This doesn't really test anything yet.  We should mark the
-// variables as requiring strubbing, and somehow check that the
-// wrapped functions take the parameter by reference.
-
-struct T {
-  char d[32 * sizeof(void*)];
-};
-
-T foo (T q) { asm ("" : : "m"(q)); return q; }
-T bar (T p) { return foo (p); }
-
-T tmp;
-T tmp2;
-
-int main () {
-  __builtin_memset (&tmp, 0x55, sizeof (tmp));
-  tmp2 = bar (tmp);
-  if (__builtin_memcmp (&tmp, &tmp2, sizeof (tmp)))
-    __builtin_abort ();
-}
diff --git a/gcc/testsuite/g++.dg/wrappers/strub3.C b/gcc/testsuite/g++.dg/wrappers/strub3.C
deleted file mode 100644
index e1b51cd0399..00000000000
--- a/gcc/testsuite/g++.dg/wrappers/strub3.C
+++ /dev/null
@@ -1,22 +0,0 @@
-// { dg-do run }
-
-// This doesn't really test anything yet.  We should mark the
-// variables as requiring strubbing, and somehow check that the
-// wrapped functions take the parameter by reference.
-
-struct T {
-  char d[32 * sizeof(void*)];
-};
-
-static T foo (T q) { asm ("" : : "m"(q)); return q; }
-static T bar (T p) { return foo (p); }
-
-T tmp;
-T tmp2;
-
-int main () {
-  __builtin_memset (&tmp, 0x55, sizeof (tmp));
-  tmp2 = bar (tmp);
-  if (__builtin_memcmp (&tmp, &tmp2, sizeof (tmp)))
-    __builtin_abort ();
-}
diff --git a/gcc/testsuite/g++.dg/wrappers/strub4.C b/gcc/testsuite/g++.dg/wrappers/strub4.C
deleted file mode 100644
index d021fca88e4..00000000000
--- a/gcc/testsuite/g++.dg/wrappers/strub4.C
+++ /dev/null
@@ -1,18 +0,0 @@
-// { dg-do run }
-
-namespace
-{
-  class foo
-  {
-  public:
-    foo();
-  };
-
-  foo::foo() {}
-
-  foo bar;
-}
-
-int main()
-{
-}


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [gcc(refs/users/aoliva/heads/strub)] more tests, red zones, and deferred strubbing
@ 2021-08-05 13:33 Alexandre Oliva
  0 siblings, 0 replies; 6+ messages in thread
From: Alexandre Oliva @ 2021-08-05 13:33 UTC (permalink / raw)
  To: gcc-cvs

https://gcc.gnu.org/g:e9ba882becd6e1db1a2bc8b5df3a9d2190a2ef17

commit e9ba882becd6e1db1a2bc8b5df3a9d2190a2ef17
Author: Alexandre Oliva <oliva@adacore.com>
Date:   Thu Aug 5 08:43:23 2021 -0300

    more tests, red zones, and deferred strubbing

Diff:
---
 gcc/builtins.c                                     | 972 ++++++++-------------
 gcc/ipa-strub.c                                    | 340 ++++---
 gcc/ipa-strub.h                                    |   1 +
 gcc/testsuite/c-c++-common/strub-O2.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O2fni.c           |   6 +-
 gcc/testsuite/c-c++-common/strub-O3fni.c           |   4 +-
 gcc/testsuite/c-c++-common/strub-Og.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-Os.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-all1.c            |   4 +-
 gcc/testsuite/c-c++-common/strub-apply1.c          |  15 +
 gcc/testsuite/c-c++-common/strub-apply2.c          |  12 +
 gcc/testsuite/c-c++-common/strub-apply3.c          |   8 +
 gcc/testsuite/c-c++-common/strub-apply4.c          |  21 +
 gcc/testsuite/c-c++-common/strub-at-calls1.c       |   4 +-
 gcc/testsuite/c-c++-common/strub-default1.c        |   8 +-
 gcc/testsuite/c-c++-common/strub-internal1.c       |   4 +-
 gcc/testsuite/c-c++-common/strub-parms1.c          |  48 +
 gcc/testsuite/c-c++-common/strub-parms2.c          |  36 +
 gcc/testsuite/c-c++-common/strub-parms3.c          |  58 ++
 .../c-c++-common/torture/strub-callable1.c         |  13 +
 .../c-c++-common/torture/strub-callable2.c         | 264 ++++++
 gcc/testsuite/c-c++-common/torture/strub-const1.c  |  18 +
 gcc/testsuite/c-c++-common/torture/strub-const2.c  |  22 +
 gcc/testsuite/c-c++-common/torture/strub-const3.c  |  13 +
 gcc/testsuite/c-c++-common/torture/strub-const4.c  |  17 +
 gcc/testsuite/c-c++-common/torture/strub-data5.c   |   6 +-
 gcc/testsuite/c-c++-common/torture/strub-pure1.c   |  18 +
 gcc/testsuite/c-c++-common/torture/strub-pure2.c   |  22 +
 gcc/testsuite/c-c++-common/torture/strub-pure3.c   |  13 +
 gcc/testsuite/c-c++-common/torture/strub-pure4.c   |  17 +
 gcc/testsuite/c-c++-common/torture/strub-run1.c    |  85 ++
 gcc/testsuite/c-c++-common/torture/strub-run2.c    |  75 ++
 gcc/testsuite/c-c++-common/torture/strub-run3.c    |  75 ++
 .../g++.dg/{wrappers/strub1.C => strub-run1.C}     |   1 +
 gcc/testsuite/g++.dg/wrappers/strub2.C             |  22 -
 gcc/testsuite/g++.dg/wrappers/strub3.C             |  22 -
 gcc/testsuite/g++.dg/wrappers/strub4.C             |  18 -
 37 files changed, 1454 insertions(+), 814 deletions(-)

diff --git a/gcc/builtins.c b/gcc/builtins.c
index f387d93974f..4a9e9d7e2fd 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -79,7 +79,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "tree-outof-ssa.h"
 #include "attr-fnspec.h"
 #include "demangle.h"
-#include "gimple-range.h"
+#include "ipa-strub.h"
 
 struct target_builtins default_target_builtins;
 #if SWITCHABLE_TARGET
@@ -129,6 +129,7 @@ static rtx expand_builtin_va_copy (tree);
 static rtx inline_expand_builtin_bytecmp (tree, rtx);
 static rtx expand_builtin_strcmp (tree, rtx);
 static rtx expand_builtin_strncmp (tree, rtx, machine_mode);
+static rtx builtin_memcpy_read_str (void *, HOST_WIDE_INT, scalar_int_mode);
 static rtx expand_builtin_memchr (tree, rtx);
 static rtx expand_builtin_memcpy (tree, rtx);
 static rtx expand_builtin_memory_copy_args (tree dest, tree src, tree len,
@@ -145,6 +146,7 @@ static rtx expand_builtin_stpcpy (tree, rtx, machine_mode);
 static rtx expand_builtin_stpncpy (tree, rtx);
 static rtx expand_builtin_strncat (tree, rtx);
 static rtx expand_builtin_strncpy (tree, rtx);
+static rtx builtin_memset_gen_str (void *, HOST_WIDE_INT, scalar_int_mode);
 static rtx expand_builtin_memset (tree, rtx, machine_mode);
 static rtx expand_builtin_memset_args (tree, tree, tree, rtx, machine_mode, tree);
 static rtx expand_builtin_bzero (tree);
@@ -207,7 +209,6 @@ access_ref::access_ref (tree bound /* = NULL_TREE */,
 {
   /* Set to valid.  */
   offrng[0] = offrng[1] = 0;
-  offmax[0] = offmax[1] = 0;
   /* Invalidate.   */
   sizrng[0] = sizrng[1] = -1;
 
@@ -459,21 +460,6 @@ access_ref::size_remaining (offset_int *pmin /* = NULL */) const
   return sizrng[1] - or0;
 }
 
-/* Return true if the offset and object size are in range for SIZE.  */
-
-bool
-access_ref::offset_in_range (const offset_int &size) const
-{
-  if (size_remaining () < size)
-    return false;
-
-  if (base0)
-    return offmax[0] >= 0 && offmax[1] <= sizrng[1];
-
-  offset_int maxoff = wi::to_offset (TYPE_MAX_VALUE (ptrdiff_type_node));
-  return offmax[0] > -maxoff && offmax[1] < maxoff;
-}
-
 /* Add the range [MIN, MAX] to the offset range.  For known objects (with
    zero-based offsets) at least one of whose offset's bounds is in range,
    constrain the other (or both) to the bounds of the object (i.e., zero
@@ -510,8 +496,6 @@ void access_ref::add_offset (const offset_int &min, const offset_int &max)
       if (max >= 0)
 	{
 	  offrng[0] = 0;
-	  if (offmax[0] > 0)
-	    offmax[0] = 0;
 	  return;
 	}
 
@@ -528,12 +512,6 @@ void access_ref::add_offset (const offset_int &min, const offset_int &max)
 	offrng[0] = 0;
     }
 
-  /* Set the minimum and maximmum computed so far. */
-  if (offrng[1] < 0 && offrng[1] < offmax[0])
-    offmax[0] = offrng[1];
-  if (offrng[0] > 0 && offrng[0] > offmax[1])
-    offmax[1] = offrng[0];
-
   if (!base0)
     return;
 
@@ -764,9 +742,13 @@ pointer_query::flush_cache ()
 static bool
 is_builtin_name (const char *name)
 {
-  return (startswith (name, "__builtin_")
-	  || startswith (name, "__sync_")
-	  || startswith (name, "__atomic_"));
+  if (strncmp (name, "__builtin_", 10) == 0)
+    return true;
+  if (strncmp (name, "__sync_", 7) == 0)
+    return true;
+  if (strncmp (name, "__atomic_", 9) == 0)
+    return true;
+  return false;
 }
 
 /* Return true if NODE should be considered for inline expansion regardless
@@ -950,10 +932,6 @@ bool
 get_object_alignment_1 (tree exp, unsigned int *alignp,
 			unsigned HOST_WIDE_INT *bitposp)
 {
-  /* Strip a WITH_SIZE_EXPR, get_inner_reference doesn't know how to deal
-     with it.  */
-  if (TREE_CODE (exp) == WITH_SIZE_EXPR)
-    exp = TREE_OPERAND (exp, 0);
   return get_object_alignment_2 (exp, alignp, bitposp, false);
 }
 
@@ -1120,9 +1098,7 @@ warn_string_no_nul (location_t loc, tree expr, const char *fname,
 		    bool exact /* = false */,
 		    const wide_int bndrng[2] /* = NULL */)
 {
-  const opt_code opt = OPT_Wstringop_overread;
-  if ((expr && warning_suppressed_p (expr, opt))
-      || warning_suppressed_p (arg, opt))
+  if ((expr && TREE_NO_WARNING (expr)) || TREE_NO_WARNING (arg))
     return;
 
   loc = expansion_point_location_if_in_system_header (loc);
@@ -1150,45 +1126,45 @@ warn_string_no_nul (location_t loc, tree expr, const char *fname,
       if (bndrng)
 	{
 	  if (wi::ltu_p (maxsiz, bndrng[0]))
-	    warned = warning_at (loc, opt,
-				 "%qD specified bound %s exceeds "
+	    warned = warning_at (loc, OPT_Wstringop_overread,
+				 "%K%qD specified bound %s exceeds "
 				 "maximum object size %E",
-				 func, bndstr, maxobjsize);
+				 expr, func, bndstr, maxobjsize);
 	  else
 	    {
 	      bool maybe = wi::to_wide (size) == bndrng[0];
-	      warned = warning_at (loc, opt,
+	      warned = warning_at (loc, OPT_Wstringop_overread,
 				   exact
-				   ? G_("%qD specified bound %s exceeds "
+				   ? G_("%K%qD specified bound %s exceeds "
 					"the size %E of unterminated array")
 				   : (maybe
-				      ? G_("%qD specified bound %s may "
+				      ? G_("%K%qD specified bound %s may "
 					   "exceed the size of at most %E "
 					   "of unterminated array")
-				      : G_("%qD specified bound %s exceeds "
+				      : G_("%K%qD specified bound %s exceeds "
 					   "the size of at most %E "
 					   "of unterminated array")),
-				   func, bndstr, size);
+				   expr, func, bndstr, size);
 	    }
 	}
       else
-	warned = warning_at (loc, opt,
-			     "%qD argument missing terminating nul",
-			     func);
+	warned = warning_at (loc, OPT_Wstringop_overread,
+			     "%K%qD argument missing terminating nul",
+			     expr, func);
     }
   else
     {
       if (bndrng)
 	{
 	  if (wi::ltu_p (maxsiz, bndrng[0]))
-	    warned = warning_at (loc, opt,
+	    warned = warning_at (loc, OPT_Wstringop_overread,
 				 "%qs specified bound %s exceeds "
 				 "maximum object size %E",
 				 fname, bndstr, maxobjsize);
 	  else
 	    {
 	      bool maybe = wi::to_wide (size) == bndrng[0];
-	      warned = warning_at (loc, opt,
+	      warned = warning_at (loc, OPT_Wstringop_overread,
 				   exact
 				   ? G_("%qs specified bound %s exceeds "
 					"the size %E of unterminated array")
@@ -1203,7 +1179,7 @@ warn_string_no_nul (location_t loc, tree expr, const char *fname,
 	    }
 	}
       else
-	warned = warning_at (loc, opt,
+	warned = warning_at (loc, OPT_Wstringop_overread,
 			     "%qs argument missing terminating nul",
 			     fname);
     }
@@ -1212,9 +1188,9 @@ warn_string_no_nul (location_t loc, tree expr, const char *fname,
     {
       inform (DECL_SOURCE_LOCATION (decl),
 	      "referenced argument declared here");
-      suppress_warning (arg, opt);
+      TREE_NO_WARNING (arg) = 1;
       if (expr)
-	suppress_warning (expr, opt);
+	TREE_NO_WARNING (expr) = 1;
     }
 }
 
@@ -1246,15 +1222,14 @@ check_nul_terminated_array (tree expr, tree src,
   wide_int bndrng[2];
   if (bound)
     {
-      value_range r;
-
-      get_global_range_query ()->range_of_expr (r, bound);
-
-      if (r.kind () != VR_RANGE)
-	return true;
-
-      bndrng[0] = r.lower_bound ();
-      bndrng[1] = r.upper_bound ();
+      if (TREE_CODE (bound) == INTEGER_CST)
+	bndrng[0] = bndrng[1] = wi::to_wide (bound);
+      else
+	{
+	  value_range_kind rng = get_range_info (bound, bndrng, bndrng + 1);
+	  if (rng != VR_RANGE)
+	    return true;
+	}
 
       if (exact)
 	{
@@ -1472,14 +1447,14 @@ c_strlen (tree arg, int only_value, c_strlen_data *data, unsigned eltsize)
     {
       /* Suppress multiple warnings for propagated constant strings.  */
       if (only_value != 2
-	  && !warning_suppressed_p (arg, OPT_Warray_bounds)
+	  && !TREE_NO_WARNING (arg)
 	  && warning_at (loc, OPT_Warray_bounds,
 			 "offset %qwi outside bounds of constant string",
 			 eltoff))
 	{
 	  if (decl)
 	    inform (DECL_SOURCE_LOCATION (decl), "%qE declared here", decl);
-	  suppress_warning (arg, OPT_Warray_bounds);
+	  TREE_NO_WARNING (arg) = 1;
 	}
       return NULL_TREE;
     }
@@ -3860,12 +3835,9 @@ expand_builtin_strnlen (tree exp, rtx target, machine_mode target_mode)
     return NULL_RTX;
 
   wide_int min, max;
-  value_range r;
-  get_global_range_query ()->range_of_expr (r, bound);
-  if (r.kind () != VR_RANGE)
+  enum value_range_kind rng = get_range_info (bound, &min, &max);
+  if (rng != VR_RANGE)
     return NULL_RTX;
-  min = r.lower_bound ();
-  max = r.upper_bound ();
 
   if (!len || TREE_CODE (len) != INTEGER_CST)
     {
@@ -3890,7 +3862,7 @@ expand_builtin_strnlen (tree exp, rtx target, machine_mode target_mode)
    a target constant.  */
 
 static rtx
-builtin_memcpy_read_str (void *data, void *, HOST_WIDE_INT offset,
+builtin_memcpy_read_str (void *data, HOST_WIDE_INT offset,
 			 scalar_int_mode mode)
 {
   /* The REPresentation pointed to by DATA need not be a nul-terminated
@@ -3933,16 +3905,7 @@ determine_block_size (tree len, rtx len_rtx,
 	*probable_max_size = *max_size = GET_MODE_MASK (GET_MODE (len_rtx));
 
       if (TREE_CODE (len) == SSA_NAME)
-	{
-	  value_range r;
-	  get_global_range_query ()->range_of_expr (r, len);
-	  range_type = r.kind ();
-	  if (range_type != VR_UNDEFINED)
-	    {
-	      min = wi::to_wide (r.min ());
-	      max = wi::to_wide (r.max ());
-	    }
-	}
+	range_type = get_range_info (len, &min, &max);
       if (range_type == VR_RANGE)
 	{
 	  if (wi::fits_uhwi_p (min) && *min_size < min.to_uhwi ())
@@ -3974,10 +3937,10 @@ determine_block_size (tree len, rtx len_rtx,
    accessing an object with SIZE.  */
 
 static bool
-maybe_warn_for_bound (opt_code opt, location_t loc, tree exp, tree func,
+maybe_warn_for_bound (int opt, location_t loc, tree exp, tree func,
 		      tree bndrng[2], tree size, const access_data *pad = NULL)
 {
-  if (!bndrng[0] || warning_suppressed_p (exp, opt))
+  if (!bndrng[0] || TREE_NO_WARNING (exp))
     return false;
 
   tree maxobjsize = max_object_size ();
@@ -3994,34 +3957,35 @@ maybe_warn_for_bound (opt_code opt, location_t loc, tree exp, tree func,
 	    warned = (func
 		      ? warning_at (loc, opt,
 				    (maybe
-				     ? G_("%qD specified bound %E may "
+				     ? G_("%K%qD specified bound %E may "
 					  "exceed maximum object size %E")
-				     : G_("%qD specified bound %E "
+				     : G_("%K%qD specified bound %E "
 					  "exceeds maximum object size %E")),
-				    func, bndrng[0], maxobjsize)
+				    exp, func, bndrng[0], maxobjsize)
 		      : warning_at (loc, opt,
 				    (maybe
-				     ? G_("specified bound %E may "
+				     ? G_("%Kspecified bound %E may "
 					  "exceed maximum object size %E")
-				     : G_("specified bound %E "
+				     : G_("%Kspecified bound %E "
 					  "exceeds maximum object size %E")),
-				    bndrng[0], maxobjsize));
+				    exp, bndrng[0], maxobjsize));
 	  else
 	    warned = (func
 		      ? warning_at (loc, opt,
 				    (maybe
-				     ? G_("%qD specified bound [%E, %E] may "
+				     ? G_("%K%qD specified bound [%E, %E] may "
 					  "exceed maximum object size %E")
-				     : G_("%qD specified bound [%E, %E] "
+				     : G_("%K%qD specified bound [%E, %E] "
 					  "exceeds maximum object size %E")),
-				    func, bndrng[0], bndrng[1], maxobjsize)
+				    exp, func,
+				    bndrng[0], bndrng[1], maxobjsize)
 		      : warning_at (loc, opt,
 				    (maybe
-				     ? G_("specified bound [%E, %E] may "
+				     ? G_("%Kspecified bound [%E, %E] may "
 					  "exceed maximum object size %E")
-				     : G_("specified bound [%E, %E] "
+				     : G_("%Kspecified bound [%E, %E] "
 					  "exceeds maximum object size %E")),
-				    bndrng[0], bndrng[1], maxobjsize));
+				    exp, bndrng[0], bndrng[1], maxobjsize));
 	}
       else if (!size || tree_int_cst_le (bndrng[0], size))
 	return false;
@@ -4029,34 +3993,34 @@ maybe_warn_for_bound (opt_code opt, location_t loc, tree exp, tree func,
 	warned = (func
 		  ? warning_at (loc, opt,
 				(maybe
-				 ? G_("%qD specified bound %E may exceed "
+				 ? G_("%K%qD specified bound %E may exceed "
 				      "source size %E")
-				 : G_("%qD specified bound %E exceeds "
+				 : G_("%K%qD specified bound %E exceeds "
 				      "source size %E")),
-				func, bndrng[0], size)
+				exp, func, bndrng[0], size)
 		  : warning_at (loc, opt,
 				(maybe
-				 ? G_("specified bound %E may exceed "
+				 ? G_("%Kspecified bound %E may exceed "
 				      "source size %E")
-				 : G_("specified bound %E exceeds "
+				 : G_("%Kspecified bound %E exceeds "
 				      "source size %E")),
-				bndrng[0], size));
+				exp, bndrng[0], size));
       else
 	warned = (func
 		  ? warning_at (loc, opt,
 				(maybe
-				 ? G_("%qD specified bound [%E, %E] may "
+				 ? G_("%K%qD specified bound [%E, %E] may "
 				      "exceed source size %E")
-				 : G_("%qD specified bound [%E, %E] exceeds "
+				 : G_("%K%qD specified bound [%E, %E] exceeds "
 				      "source size %E")),
-				func, bndrng[0], bndrng[1], size)
+				exp, func, bndrng[0], bndrng[1], size)
 		  : warning_at (loc, opt,
 				(maybe
-				 ? G_("specified bound [%E, %E] may exceed "
+				 ? G_("%Kspecified bound [%E, %E] may exceed "
 				      "source size %E")
-				 : G_("specified bound [%E, %E] exceeds "
+				 : G_("%Kspecified bound [%E, %E] exceeds "
 				      "source size %E")),
-				bndrng[0], bndrng[1], size));
+				exp, bndrng[0], bndrng[1], size));
       if (warned)
 	{
 	  if (pad && pad->src.ref)
@@ -4068,7 +4032,7 @@ maybe_warn_for_bound (opt_code opt, location_t loc, tree exp, tree func,
 		inform (EXPR_LOCATION (pad->src.ref),
 			"source object allocated here");
 	    }
-	  suppress_warning (exp, opt);
+	  TREE_NO_WARNING (exp) = true;
 	}
 
       return warned;
@@ -4081,69 +4045,70 @@ maybe_warn_for_bound (opt_code opt, location_t loc, tree exp, tree func,
 	warned = (func
 		  ? warning_at (loc, opt,
 				(maybe
-				 ? G_("%qD specified size %E may "
+				 ? G_("%K%qD specified size %E may "
 				      "exceed maximum object size %E")
-				 : G_("%qD specified size %E "
+				 : G_("%K%qD specified size %E "
 				      "exceeds maximum object size %E")),
-				func, bndrng[0], maxobjsize)
+				exp, func, bndrng[0], maxobjsize)
 		  : warning_at (loc, opt,
 				(maybe
-				 ? G_("specified size %E may exceed "
+				 ? G_("%Kspecified size %E may exceed "
 				      "maximum object size %E")
-				 : G_("specified size %E exceeds "
+				 : G_("%Kspecified size %E exceeds "
 				      "maximum object size %E")),
-				bndrng[0], maxobjsize));
+				exp, bndrng[0], maxobjsize));
       else
 	warned = (func
 		  ? warning_at (loc, opt,
 				(maybe
-				 ? G_("%qD specified size between %E and %E "
+				 ? G_("%K%qD specified size between %E and %E "
 				      "may exceed maximum object size %E")
-				 : G_("%qD specified size between %E and %E "
+				 : G_("%K%qD specified size between %E and %E "
 				      "exceeds maximum object size %E")),
-				func, bndrng[0], bndrng[1], maxobjsize)
+				exp, func,
+				bndrng[0], bndrng[1], maxobjsize)
 		  : warning_at (loc, opt,
 				(maybe
-				 ? G_("specified size between %E and %E "
+				 ? G_("%Kspecified size between %E and %E "
 				      "may exceed maximum object size %E")
-				 : G_("specified size between %E and %E "
+				 : G_("%Kspecified size between %E and %E "
 				      "exceeds maximum object size %E")),
-				bndrng[0], bndrng[1], maxobjsize));
+				exp, bndrng[0], bndrng[1], maxobjsize));
     }
   else if (!size || tree_int_cst_le (bndrng[0], size))
     return false;
   else if (tree_int_cst_equal (bndrng[0], bndrng[1]))
     warned = (func
-	      ? warning_at (loc, opt,
+	      ? warning_at (loc, OPT_Wstringop_overflow_,
 			    (maybe
-			     ? G_("%qD specified bound %E may exceed "
+			     ? G_("%K%qD specified bound %E may exceed "
 				  "destination size %E")
-			     : G_("%qD specified bound %E exceeds "
+			     : G_("%K%qD specified bound %E exceeds "
 				  "destination size %E")),
-			    func, bndrng[0], size)
-	      : warning_at (loc, opt,
+			    exp, func, bndrng[0], size)
+	      : warning_at (loc, OPT_Wstringop_overflow_,
 			    (maybe
-			     ? G_("specified bound %E may exceed "
+			     ? G_("%Kspecified bound %E may exceed "
 				  "destination size %E")
-			     : G_("specified bound %E exceeds "
+			     : G_("%Kspecified bound %E exceeds "
 				  "destination size %E")),
-			    bndrng[0], size));
+			    exp, bndrng[0], size));
   else
     warned = (func
-	      ? warning_at (loc, opt,
+	      ? warning_at (loc, OPT_Wstringop_overflow_,
 			    (maybe
-			     ? G_("%qD specified bound [%E, %E] may exceed "
+			     ? G_("%K%qD specified bound [%E, %E] may exceed "
 				  "destination size %E")
-			     : G_("%qD specified bound [%E, %E] exceeds "
+			     : G_("%K%qD specified bound [%E, %E] exceeds "
 				  "destination size %E")),
-			    func, bndrng[0], bndrng[1], size)
-	      : warning_at (loc, opt,
+			    exp, func, bndrng[0], bndrng[1], size)
+	      : warning_at (loc, OPT_Wstringop_overflow_,
 			    (maybe
-			     ? G_("specified bound [%E, %E] exceeds "
+			     ? G_("%Kspecified bound [%E, %E] exceeds "
 				  "destination size %E")
-			     : G_("specified bound [%E, %E] exceeds "
+			     : G_("%Kspecified bound [%E, %E] exceeds "
 				  "destination size %E")),
-			    bndrng[0], bndrng[1], size));
+			    exp, bndrng[0], bndrng[1], size));
 
   if (warned)
     {
@@ -4156,7 +4121,7 @@ maybe_warn_for_bound (opt_code opt, location_t loc, tree exp, tree func,
 	    inform (EXPR_LOCATION (pad->dst.ref),
 		    "destination object allocated here");
 	}
-      suppress_warning (exp, opt);
+      TREE_NO_WARNING (exp) = true;
     }
 
   return warned;
@@ -4181,63 +4146,65 @@ warn_for_access (location_t loc, tree func, tree exp, int opt, tree range[2],
 	warned = (func
 		  ? warning_n (loc, opt, tree_to_uhwi (range[0]),
 			       (maybe
-				? G_("%qD may access %E byte in a region "
+				? G_("%K%qD may access %E byte in a region "
 				     "of size %E")
-				: G_("%qD accessing %E byte in a region "
+				: G_("%K%qD accessing %E byte in a region "
 				     "of size %E")),
 				(maybe
-				 ? G_ ("%qD may access %E bytes in a region "
+				 ? G_ ("%K%qD may access %E bytes in a region "
 				       "of size %E")
-				 : G_ ("%qD accessing %E bytes in a region "
+				 : G_ ("%K%qD accessing %E bytes in a region "
 				       "of size %E")),
-			       func, range[0], size)
+			       exp, func, range[0], size)
 		  : warning_n (loc, opt, tree_to_uhwi (range[0]),
 			       (maybe
-				? G_("may access %E byte in a region "
+				? G_("%Kmay access %E byte in a region "
 				     "of size %E")
-				: G_("accessing %E byte in a region "
+				: G_("%Kaccessing %E byte in a region "
 				     "of size %E")),
 			       (maybe
-				? G_("may access %E bytes in a region "
+				? G_("%Kmay access %E bytes in a region "
 				     "of size %E")
-				: G_("accessing %E bytes in a region "
+				: G_("%Kaccessing %E bytes in a region "
 				     "of size %E")),
-			       range[0], size));
+			       exp, range[0], size));
       else if (tree_int_cst_sign_bit (range[1]))
 	{
 	  /* Avoid printing the upper bound if it's invalid.  */
 	  warned = (func
 		    ? warning_at (loc, opt,
 				  (maybe
-				   ? G_("%qD may access %E or more bytes "
+				   ? G_("%K%qD may access %E or more bytes "
 					"in a region of size %E")
-				   : G_("%qD accessing %E or more bytes "
+				   : G_("%K%qD accessing %E or more bytes "
 					"in a region of size %E")),
-				  func, range[0], size)
+				  exp, func, range[0], size)
 		    : warning_at (loc, opt,
 				  (maybe
-				   ? G_("may access %E or more bytes "
+				   ? G_("%Kmay access %E or more bytes "
 					"in a region of size %E")
-				   : G_("accessing %E or more bytes "
+				   : G_("%Kaccessing %E or more bytes "
 					"in a region of size %E")),
-				  range[0], size));
+				  exp, range[0], size));
 	}
       else
 	warned = (func
 		  ? warning_at (loc, opt,
 				(maybe
-				 ? G_("%qD may access between %E and %E "
+				 ? G_("%K%qD may access between %E and %E "
 				      "bytes in a region of size %E")
-				 : G_("%qD accessing between %E and %E "
+				 : G_("%K%qD accessing between %E and %E "
 				      "bytes in a region of size %E")),
-				func, range[0], range[1], size)
+				exp, func, range[0], range[1],
+				size)
 		  : warning_at (loc, opt,
 				(maybe
-				 ? G_("may access between %E and %E bytes "
+				 ? G_("%Kmay access between %E and %E bytes "
 				      "in a region of size %E")
-				 : G_("accessing between %E and %E bytes "
+				 : G_("%Kaccessing between %E and %E bytes "
 				      "in a region of size %E")),
-				range[0], range[1], size));
+				exp, range[0], range[1],
+				size));
       return warned;
     }
 
@@ -4247,67 +4214,69 @@ warn_for_access (location_t loc, tree func, tree exp, int opt, tree range[2],
 	warned = (func
 		  ? warning_n (loc, opt, tree_to_uhwi (range[0]),
 			       (maybe
-				? G_("%qD may write %E byte into a region "
+				? G_("%K%qD may write %E byte into a region "
 				     "of size %E")
-				: G_("%qD writing %E byte into a region "
+				: G_("%K%qD writing %E byte into a region "
 				     "of size %E overflows the destination")),
 			       (maybe
-				? G_("%qD may write %E bytes into a region "
+				? G_("%K%qD may write %E bytes into a region "
 				     "of size %E")
-				: G_("%qD writing %E bytes into a region "
+				: G_("%K%qD writing %E bytes into a region "
 				     "of size %E overflows the destination")),
-			       func, range[0], size)
+			       exp, func, range[0], size)
 		  : warning_n (loc, opt, tree_to_uhwi (range[0]),
 			       (maybe
-				? G_("may write %E byte into a region "
+				? G_("%Kmay write %E byte into a region "
 				     "of size %E")
-				: G_("writing %E byte into a region "
+				: G_("%Kwriting %E byte into a region "
 				     "of size %E overflows the destination")),
 			       (maybe
-				? G_("may write %E bytes into a region "
+				? G_("%Kmay write %E bytes into a region "
 				     "of size %E")
-				: G_("writing %E bytes into a region "
+				: G_("%Kwriting %E bytes into a region "
 				     "of size %E overflows the destination")),
-			       range[0], size));
+			       exp, range[0], size));
       else if (tree_int_cst_sign_bit (range[1]))
 	{
 	  /* Avoid printing the upper bound if it's invalid.  */
 	  warned = (func
 		    ? warning_at (loc, opt,
 				  (maybe
-				   ? G_("%qD may write %E or more bytes "
+				   ? G_("%K%qD may write %E or more bytes "
 					"into a region of size %E")
-				   : G_("%qD writing %E or more bytes "
+				   : G_("%K%qD writing %E or more bytes "
 					"into a region of size %E overflows "
 					"the destination")),
-				  func, range[0], size)
+				  exp, func, range[0], size)
 		    : warning_at (loc, opt,
 				  (maybe
-				   ? G_("may write %E or more bytes into "
+				   ? G_("%Kmay write %E or more bytes into "
 					"a region of size %E")
-				   : G_("writing %E or more bytes into "
+				   : G_("%Kwriting %E or more bytes into "
 					"a region of size %E overflows "
 					"the destination")),
-				  range[0], size));
+				  exp, range[0], size));
 	}
       else
 	warned = (func
 		  ? warning_at (loc, opt,
 				(maybe
-				 ? G_("%qD may write between %E and %E bytes "
+				 ? G_("%K%qD may write between %E and %E bytes "
 				      "into a region of size %E")
-				 : G_("%qD writing between %E and %E bytes "
+				 : G_("%K%qD writing between %E and %E bytes "
 				      "into a region of size %E overflows "
 				      "the destination")),
-				func, range[0], range[1], size)
+				exp, func, range[0], range[1],
+				size)
 		  : warning_at (loc, opt,
 				(maybe
-				 ? G_("may write between %E and %E bytes "
+				 ? G_("%Kmay write between %E and %E bytes "
 				      "into a region of size %E")
-				 : G_("writing between %E and %E bytes "
+				 : G_("%Kwriting between %E and %E bytes "
 				      "into a region of size %E overflows "
 				      "the destination")),
-				range[0], range[1], size));
+				exp, range[0], range[1],
+				size));
       return warned;
     }
 
@@ -4318,67 +4287,67 @@ warn_for_access (location_t loc, tree func, tree exp, int opt, tree range[2],
 		  ? warning_n (loc, OPT_Wstringop_overread,
 			       tree_to_uhwi (range[0]),
 			       (maybe
-				? G_("%qD may read %E byte from a region "
+				? G_("%K%qD may read %E byte from a region "
 				     "of size %E")
-				: G_("%qD reading %E byte from a region "
+				: G_("%K%qD reading %E byte from a region "
 				     "of size %E")),
 			       (maybe
-				? G_("%qD may read %E bytes from a region "
+				? G_("%K%qD may read %E bytes from a region "
 				     "of size %E")
-				: G_("%qD reading %E bytes from a region "
+				: G_("%K%qD reading %E bytes from a region "
 				     "of size %E")),
-			       func, range[0], size)
+			       exp, func, range[0], size)
 		  : warning_n (loc, OPT_Wstringop_overread,
 			       tree_to_uhwi (range[0]),
 			       (maybe
-				? G_("may read %E byte from a region "
+				? G_("%Kmay read %E byte from a region "
 				     "of size %E")
-				: G_("reading %E byte from a region "
+				: G_("%Kreading %E byte from a region "
 				     "of size %E")),
 			       (maybe
-				? G_("may read %E bytes from a region "
+				? G_("%Kmay read %E bytes from a region "
 				     "of size %E")
-				: G_("reading %E bytes from a region "
+				: G_("%Kreading %E bytes from a region "
 				     "of size %E")),
-			       range[0], size));
+			       exp, range[0], size));
       else if (tree_int_cst_sign_bit (range[1]))
 	{
 	  /* Avoid printing the upper bound if it's invalid.  */
 	  warned = (func
 		    ? warning_at (loc, OPT_Wstringop_overread,
 				  (maybe
-				   ? G_("%qD may read %E or more bytes "
+				   ? G_("%K%qD may read %E or more bytes "
 					"from a region of size %E")
-				   : G_("%qD reading %E or more bytes "
+				   : G_("%K%qD reading %E or more bytes "
 					"from a region of size %E")),
-				  func, range[0], size)
+				  exp, func, range[0], size)
 		    : warning_at (loc, OPT_Wstringop_overread,
 				  (maybe
-				   ? G_("may read %E or more bytes "
+				   ? G_("%Kmay read %E or more bytes "
 					"from a region of size %E")
-				   : G_("reading %E or more bytes "
+				   : G_("%Kreading %E or more bytes "
 					"from a region of size %E")),
-				  range[0], size));
+				  exp, range[0], size));
 	}
       else
 	warned = (func
 		  ? warning_at (loc, OPT_Wstringop_overread,
 				(maybe
-				 ? G_("%qD may read between %E and %E bytes "
+				 ? G_("%K%qD may read between %E and %E bytes "
 				      "from a region of size %E")
-				 : G_("%qD reading between %E and %E bytes "
+				 : G_("%K%qD reading between %E and %E bytes "
 				      "from a region of size %E")),
-				func, range[0], range[1], size)
+				exp, func, range[0], range[1], size)
 		  : warning_at (loc, opt,
 				(maybe
-				 ? G_("may read between %E and %E bytes "
+				 ? G_("%Kmay read between %E and %E bytes "
 				      "from a region of size %E")
-				 : G_("reading between %E and %E bytes "
+				 : G_("%Kreading between %E and %E bytes "
 				      "from a region of size %E")),
-				range[0], range[1], size));
+				exp, range[0], range[1], size));
 
       if (warned)
-	suppress_warning (exp, OPT_Wstringop_overread);
+	TREE_NO_WARNING (exp) = true;
 
       return warned;
     }
@@ -4388,40 +4357,40 @@ warn_for_access (location_t loc, tree func, tree exp, int opt, tree range[2],
     warned = (func
 	      ? warning_n (loc, OPT_Wstringop_overread,
 			   tree_to_uhwi (range[0]),
-			   "%qD expecting %E byte in a region of size %E",
-			   "%qD expecting %E bytes in a region of size %E",
-			   func, range[0], size)
+			   "%K%qD expecting %E byte in a region of size %E",
+			   "%K%qD expecting %E bytes in a region of size %E",
+			   exp, func, range[0], size)
 	      : warning_n (loc, OPT_Wstringop_overread,
 			   tree_to_uhwi (range[0]),
-			   "expecting %E byte in a region of size %E",
-			   "expecting %E bytes in a region of size %E",
-			   range[0], size));
+			   "%Kexpecting %E byte in a region of size %E",
+			   "%Kexpecting %E bytes in a region of size %E",
+			   exp, range[0], size));
   else if (tree_int_cst_sign_bit (range[1]))
     {
       /* Avoid printing the upper bound if it's invalid.  */
       warned = (func
 		? warning_at (loc, OPT_Wstringop_overread,
-			      "%qD expecting %E or more bytes in a region "
+			      "%K%qD expecting %E or more bytes in a region "
 			      "of size %E",
-			      func, range[0], size)
+			      exp, func, range[0], size)
 		: warning_at (loc, OPT_Wstringop_overread,
-			      "expecting %E or more bytes in a region "
+			      "%Kexpecting %E or more bytes in a region "
 			      "of size %E",
-			      range[0], size));
+			      exp, range[0], size));
     }
   else
     warned = (func
 	      ? warning_at (loc, OPT_Wstringop_overread,
-			    "%qD expecting between %E and %E bytes in "
+			    "%K%qD expecting between %E and %E bytes in "
 			    "a region of size %E",
-			    func, range[0], range[1], size)
+			    exp, func, range[0], range[1], size)
 	      : warning_at (loc, OPT_Wstringop_overread,
-			    "expecting between %E and %E bytes in "
+			    "%Kexpecting between %E and %E bytes in "
 			    "a region of size %E",
-			    range[0], range[1], size));
+			    exp, range[0], range[1], size));
 
   if (warned)
-    suppress_warning (exp, OPT_Wstringop_overread);
+    TREE_NO_WARNING (exp) = true;
 
   return warned;
 }
@@ -4596,46 +4565,23 @@ access_ref::inform_access (access_mode mode) const
       return;
     }
 
-  if (mode == access_read_only)
-    {
-      if (allocfn == NULL_TREE)
-	{
-	  if (*offstr)
-	    inform (loc, "at offset %s into source object %qE of size %s",
-		    offstr, ref, sizestr);
-	  else
-	    inform (loc, "source object %qE of size %s", ref, sizestr);
-
-	  return;
-	}
-
-      if (*offstr)
-	inform (loc,
-		"at offset %s into source object of size %s allocated by %qE",
-		offstr, sizestr, allocfn);
-      else
-	inform (loc, "source object of size %s allocated by %qE",
-		sizestr, allocfn);
-      return;
-    }
-
   if (allocfn == NULL_TREE)
     {
       if (*offstr)
-	inform (loc, "at offset %s into object %qE of size %s",
+	inform (loc, "at offset %s into source object %qE of size %s",
 		offstr, ref, sizestr);
       else
-	inform (loc, "object %qE of size %s", ref, sizestr);
+	inform (loc, "source object %qE of size %s", ref, sizestr);
 
       return;
     }
 
   if (*offstr)
     inform (loc,
-	    "at offset %s into object of size %s allocated by %qE",
+	    "at offset %s into source object of size %s allocated by %qE",
 	    offstr, sizestr, allocfn);
   else
-    inform (loc, "object of size %s allocated by %qE",
+    inform (loc, "source object of size %s allocated by %qE",
 	    sizestr, allocfn);
 }
 
@@ -4801,7 +4747,7 @@ check_access (tree exp, tree dstwrite,
       && TREE_CODE (range[0]) == INTEGER_CST
       && tree_int_cst_lt (maxobjsize, range[0]))
     {
-      location_t loc = EXPR_LOCATION (exp);
+      location_t loc = tree_inlined_location (exp);
       maybe_warn_for_bound (OPT_Wstringop_overflow_, loc, exp, func, range,
 			    NULL_TREE, pad);
       return false;
@@ -4823,13 +4769,11 @@ check_access (tree exp, tree dstwrite,
 		  && tree_fits_uhwi_p (dstwrite)
 		  && tree_int_cst_lt (dstwrite, range[0]))))
 	{
-	  const opt_code opt = OPT_Wstringop_overflow_;
-	  if (warning_suppressed_p (exp, opt)
-	      || (pad && pad->dst.ref
-		  && warning_suppressed_p (pad->dst.ref, opt)))
+	  if (TREE_NO_WARNING (exp)
+	      || (pad && pad->dst.ref && TREE_NO_WARNING (pad->dst.ref)))
 	    return false;
 
-	  location_t loc = EXPR_LOCATION (exp);
+	  location_t loc = tree_inlined_location (exp);
 	  bool warned = false;
 	  if (dstwrite == slen && at_least_one)
 	    {
@@ -4837,16 +4781,16 @@ check_access (tree exp, tree dstwrite,
 		 and a source of unknown length.  The call will write
 		 at least one byte past the end of the destination.  */
 	      warned = (func
-			? warning_at (loc, opt,
-				      "%qD writing %E or more bytes into "
+			? warning_at (loc, OPT_Wstringop_overflow_,
+				      "%K%qD writing %E or more bytes into "
 				      "a region of size %E overflows "
 				      "the destination",
-				      func, range[0], dstsize)
-			: warning_at (loc, opt,
-				      "writing %E or more bytes into "
+				      exp, func, range[0], dstsize)
+			: warning_at (loc, OPT_Wstringop_overflow_,
+				      "%Kwriting %E or more bytes into "
 				      "a region of size %E overflows "
 				      "the destination",
-				      range[0], dstsize));
+				      exp, range[0], dstsize));
 	    }
 	  else
 	    {
@@ -4863,7 +4807,7 @@ check_access (tree exp, tree dstwrite,
 
 	  if (warned)
 	    {
-	      suppress_warning (exp, OPT_Wstringop_overflow_);
+	      TREE_NO_WARNING (exp) = true;
 	      if (pad)
 		pad->dst.inform_access (pad->mode);
 	    }
@@ -4882,7 +4826,7 @@ check_access (tree exp, tree dstwrite,
 	 PAD is nonnull and BNDRNG is valid.  */
       get_size_range (maxread, range, pad ? pad->src.bndrng : NULL);
 
-      location_t loc = EXPR_LOCATION (exp);
+      location_t loc = tree_inlined_location (exp);
       tree size = dstsize;
       if (pad && pad->mode == access_read_only)
 	size = wide_int_to_tree (sizetype, pad->src.sizrng[1]);
@@ -4898,9 +4842,9 @@ check_access (tree exp, tree dstwrite,
 
 	  if (size != maxobjsize && tree_int_cst_lt (size, range[0]))
 	    {
-	      opt_code opt = (dstwrite || mode != access_read_only
-			      ? OPT_Wstringop_overflow_
-			      : OPT_Wstringop_overread);
+	      int opt = (dstwrite || mode != access_read_only
+			 ? OPT_Wstringop_overflow_
+			 : OPT_Wstringop_overread);
 	      maybe_warn_for_bound (opt, loc, exp, func, range, size, pad);
 	      return false;
 	    }
@@ -4936,21 +4880,19 @@ check_access (tree exp, tree dstwrite,
 
   if (overread)
     {
-      const opt_code opt = OPT_Wstringop_overread;
-      if (warning_suppressed_p (exp, opt)
-	  || (srcstr && warning_suppressed_p (srcstr, opt))
-	  || (pad && pad->src.ref
-	      && warning_suppressed_p (pad->src.ref, opt)))
+      if (TREE_NO_WARNING (exp)
+	  || (srcstr && TREE_NO_WARNING (srcstr))
+	  || (pad && pad->src.ref && TREE_NO_WARNING (pad->src.ref)))
 	return false;
 
-      location_t loc = EXPR_LOCATION (exp);
+      location_t loc = tree_inlined_location (exp);
       const bool read
 	= mode == access_read_only || mode == access_read_write;
       const bool maybe = pad && pad->dst.parmarray;
-      if (warn_for_access (loc, func, exp, opt, range, slen, false, read,
-			   maybe))
+      if (warn_for_access (loc, func, exp, OPT_Wstringop_overread, range,
+			   slen, false, read, maybe))
 	{
-	  suppress_warning (exp, opt);
+	  TREE_NO_WARNING (exp) = true;
 	  if (pad)
 	    pad->src.inform_access (access_read_only);
 	}
@@ -4982,8 +4924,8 @@ check_read_access (tree exp, tree src, tree bound /* = NULL_TREE */,
 /* If STMT is a call to an allocation function, returns the constant
    maximum size of the object allocated by the call represented as
    sizetype.  If nonnull, sets RNG1[] to the range of the size.
-   When nonnull, uses RVALS for range information, otherwise gets global
-   range info.
+   When nonnull, uses RVALS for range information, otherwise calls
+   get_range_info to get it.
    Returns null when STMT is not a call to a valid allocation function.  */
 
 tree
@@ -5201,19 +5143,12 @@ get_offset_range (tree x, gimple *stmt, offset_int r[2], range_query *rvals)
 /* Return the argument that the call STMT to a built-in function returns
    or null if it doesn't.  On success, set OFFRNG[] to the range of offsets
    from the argument reflected in the value returned by the built-in if it
-   can be determined, otherwise to 0 and HWI_M1U respectively.  Set
-   *PAST_END for functions like mempcpy that might return a past the end
-   pointer (most functions return a dereferenceable pointer to an existing
-   element of an array).  */
+   can be determined, otherwise to 0 and HWI_M1U respectively.  */
 
 static tree
-gimple_call_return_array (gimple *stmt, offset_int offrng[2], bool *past_end,
+gimple_call_return_array (gimple *stmt, offset_int offrng[2],
 			  range_query *rvals)
 {
-  /* Clear and set below for the rare function(s) that might return
-     a past-the-end pointer.  */
-  *past_end = false;
-
   {
     /* Check for attribute fn spec to see if the function returns one
        of its arguments.  */
@@ -5221,7 +5156,6 @@ gimple_call_return_array (gimple *stmt, offset_int offrng[2], bool *past_end,
     unsigned int argno;
     if (fnspec.returns_arg (&argno))
       {
-	/* Functions return the first argument (not a range).  */
 	offrng[0] = offrng[1] = 0;
 	return gimple_call_arg (stmt, argno);
       }
@@ -5251,7 +5185,6 @@ gimple_call_return_array (gimple *stmt, offset_int offrng[2], bool *past_end,
       if (gimple_call_num_args (stmt) != 2)
 	return NULL_TREE;
 
-      /* Allocation functions return a pointer to the beginning.  */
       offrng[0] = offrng[1] = 0;
       return gimple_call_arg (stmt, 1);
     }
@@ -5263,6 +5196,10 @@ gimple_call_return_array (gimple *stmt, offset_int offrng[2], bool *past_end,
     case BUILT_IN_MEMMOVE:
     case BUILT_IN_MEMMOVE_CHK:
     case BUILT_IN_MEMSET:
+    case BUILT_IN_STPCPY:
+    case BUILT_IN_STPCPY_CHK:
+    case BUILT_IN_STPNCPY:
+    case BUILT_IN_STPNCPY_CHK:
     case BUILT_IN_STRCAT:
     case BUILT_IN_STRCAT_CHK:
     case BUILT_IN_STRCPY:
@@ -5271,34 +5208,18 @@ gimple_call_return_array (gimple *stmt, offset_int offrng[2], bool *past_end,
     case BUILT_IN_STRNCAT_CHK:
     case BUILT_IN_STRNCPY:
     case BUILT_IN_STRNCPY_CHK:
-      /* Functions return the first argument (not a range).  */
       offrng[0] = offrng[1] = 0;
       return gimple_call_arg (stmt, 0);
 
     case BUILT_IN_MEMPCPY:
     case BUILT_IN_MEMPCPY_CHK:
       {
-	/* The returned pointer is in a range constrained by the smaller
-	   of the upper bound of the size argument and the source object
-	   size.  */
-	offrng[0] = 0;
-	offrng[1] = HOST_WIDE_INT_M1U;
 	tree off = gimple_call_arg (stmt, 2);
-	bool off_valid = get_offset_range (off, stmt, offrng, rvals);
-	if (!off_valid || offrng[0] != offrng[1])
+	if (!get_offset_range (off, stmt, offrng, rvals))
 	  {
-	    /* If the offset is either indeterminate or in some range,
-	       try to constrain its upper bound to at most the size
-	       of the source object.  */
-	    access_ref aref;
-	    tree src = gimple_call_arg (stmt, 1);
-	    if (compute_objsize (src, 1, &aref, rvals)
-		&& aref.sizrng[1] < offrng[1])
-	      offrng[1] = aref.sizrng[1];
+	    offrng[0] = 0;
+	    offrng[1] = HOST_WIDE_INT_M1U;
 	  }
-
-	/* Mempcpy may return a past-the-end pointer.  */
-	*past_end = true;
 	return gimple_call_arg (stmt, 0);
       }
 
@@ -5306,63 +5227,23 @@ gimple_call_return_array (gimple *stmt, offset_int offrng[2], bool *past_end,
       {
 	tree off = gimple_call_arg (stmt, 2);
 	if (get_offset_range (off, stmt, offrng, rvals))
-	  offrng[1] -= 1;
+	  offrng[0] = 0;
 	else
-	  offrng[1] = HOST_WIDE_INT_M1U;
-
-	offrng[0] = 0;
+	  {
+	    offrng[0] = 0;
+	    offrng[1] = HOST_WIDE_INT_M1U;
+	  }
 	return gimple_call_arg (stmt, 0);
       }
 
     case BUILT_IN_STRCHR:
     case BUILT_IN_STRRCHR:
     case BUILT_IN_STRSTR:
-      offrng[0] = 0;
-      offrng[1] = HOST_WIDE_INT_M1U;
-      return gimple_call_arg (stmt, 0);
-
-    case BUILT_IN_STPCPY:
-    case BUILT_IN_STPCPY_CHK:
       {
-	access_ref aref;
-	tree src = gimple_call_arg (stmt, 1);
-	if (compute_objsize (src, 1, &aref, rvals))
-	  offrng[1] = aref.sizrng[1] - 1;
-	else
-	  offrng[1] = HOST_WIDE_INT_M1U;
-	
 	offrng[0] = 0;
-	return gimple_call_arg (stmt, 0);
-      }
-
-    case BUILT_IN_STPNCPY:
-    case BUILT_IN_STPNCPY_CHK:
-      {
-	/* The returned pointer is in a range between the first argument
-	   and it plus the smaller of the upper bound of the size argument
-	   and the source object size.  */
 	offrng[1] = HOST_WIDE_INT_M1U;
-	tree off = gimple_call_arg (stmt, 2);
-	if (!get_offset_range (off, stmt, offrng, rvals)
-	    || offrng[0] != offrng[1])
-	  {
-	    /* If the offset is either indeterminate or in some range,
-	       try to constrain its upper bound to at most the size
-	       of the source object.  */
-	    access_ref aref;
-	    tree src = gimple_call_arg (stmt, 1);
-	    if (compute_objsize (src, 1, &aref, rvals)
-		&& aref.sizrng[1] < offrng[1])
-	      offrng[1] = aref.sizrng[1];
-	  }
-
-	/* When the source is the empty string the returned pointer is
-	   a copy of the argument.  Otherwise stpcpy can also return
-	   a past-the-end pointer.  */
-	offrng[0] = 0;
-	*past_end = true;
-	return gimple_call_arg (stmt, 0);
       }
+      return gimple_call_arg (stmt, 0);
 
     default:
       break;
@@ -5542,16 +5423,16 @@ handle_mem_ref (tree mref, int ostype, access_ref *pref,
 
   if (VECTOR_TYPE_P (TREE_TYPE (mref)))
     {
-      /* Hack: Handle MEM_REFs of vector types as those to complete
-	 objects; those may be synthesized from multiple assignments
-	 to consecutive data members (see PR 93200 and 96963).
+      /* Hack: Give up for MEM_REFs of vector types; those may be
+	 synthesized from multiple assignments to consecutive data
+	 members (see PR 93200 and 96963).
 	 FIXME: Vectorized assignments should only be present after
 	 vectorization so this hack is only necessary after it has
 	 run and could be avoided in calls from prior passes (e.g.,
 	 tree-ssa-strlen.c).
 	 FIXME: Deal with this more generally, e.g., by marking up
 	 such MEM_REFs at the time they're created.  */
-      ostype = 0;
+      return false;
     }
 
   tree mrefop = TREE_OPERAND (mref, 0);
@@ -5815,12 +5696,9 @@ compute_objsize_r (tree ptr, int ostype, access_ref *pref,
 	      /* For functions known to return one of their pointer arguments
 		 try to determine what the returned pointer points to, and on
 		 success add OFFRNG which was set to the offset added by
-		 the function (e.g., memchr or stpcpy) to the overall offset.
-	      */
-	      bool past_end;
+		 the function (e.g., memchr) to the overall offset.  */
 	      offset_int offrng[2];
-	      if (tree ret = gimple_call_return_array (stmt, offrng,
-						       &past_end, rvals))
+	      if (tree ret = gimple_call_return_array (stmt, offrng, rvals))
 		{
 		  if (!compute_objsize_r (ret, ostype, pref, snlim, qry))
 		    return false;
@@ -5829,11 +5707,6 @@ compute_objsize_r (tree ptr, int ostype, access_ref *pref,
 		     the object.  */
 		  offset_int remrng[2];
 		  remrng[1] = pref->size_remaining (remrng);
-		  if (remrng[1] != 0 && !past_end)
-		    /* Decrement the size for functions that never return
-		       a past-the-end pointer.  */
-		    remrng[1] -= 1;
-
 		  if (remrng[1] < offrng[1])
 		    offrng[1] = remrng[1];
 		  pref->add_offset (offrng[0], offrng[1]);
@@ -5913,12 +5786,6 @@ compute_objsize_r (tree ptr, int ostype, access_ref *pref,
 
       tree rhs = gimple_assign_rhs1 (stmt);
 
-      if (code == ASSERT_EXPR)
-	{
-	  rhs = TREE_OPERAND (rhs, 0);
-	  return compute_objsize_r (rhs, ostype, pref, snlim, qry);
-	}
-
       if (code == POINTER_PLUS_EXPR
 	  && TREE_CODE (TREE_TYPE (rhs)) == POINTER_TYPE)
 	{
@@ -6547,7 +6414,7 @@ expand_builtin_stpncpy (tree exp, rtx)
    constant.  */
 
 rtx
-builtin_strncpy_read_str (void *data, void *, HOST_WIDE_INT offset,
+builtin_strncpy_read_str (void *data, HOST_WIDE_INT offset,
 			  scalar_int_mode mode)
 {
   const char *str = (const char *) data;
@@ -6598,10 +6465,10 @@ check_strncat_sizes (tree exp, tree objsize)
   if (tree_fits_uhwi_p (maxread) && tree_fits_uhwi_p (objsize)
       && tree_int_cst_equal (objsize, maxread))
     {
-      location_t loc = EXPR_LOCATION (exp);
+      location_t loc = tree_inlined_location (exp);
       warning_at (loc, OPT_Wstringop_overflow_,
-		  "%qD specified bound %E equals destination size",
-		  get_callee_fndecl (exp), maxread);
+		  "%K%qD specified bound %E equals destination size",
+		  exp, get_callee_fndecl (exp), maxread);
 
       return false;
     }
@@ -6671,10 +6538,10 @@ expand_builtin_strncat (tree exp, rtx)
   if (tree_fits_uhwi_p (maxread) && tree_fits_uhwi_p (destsize)
       && tree_int_cst_equal (destsize, maxread))
     {
-      location_t loc = EXPR_LOCATION (exp);
+      location_t loc = tree_inlined_location (exp);
       warning_at (loc, OPT_Wstringop_overflow_,
-		  "%qD specified bound %E equals destination size",
-		  get_callee_fndecl (exp), maxread);
+		  "%K%qD specified bound %E equals destination size",
+		  exp, get_callee_fndecl (exp), maxread);
 
       return NULL_RTX;
     }
@@ -6758,22 +6625,12 @@ expand_builtin_strncpy (tree exp, rtx target)
 
 /* Callback routine for store_by_pieces.  Read GET_MODE_BITSIZE (MODE)
    bytes from constant string DATA + OFFSET and return it as target
-   constant.  If PREV isn't nullptr, it has the RTL info from the
-   previous iteration.  */
+   constant.  */
 
 rtx
-builtin_memset_read_str (void *data, void *prevp,
-			 HOST_WIDE_INT offset ATTRIBUTE_UNUSED,
+builtin_memset_read_str (void *data, HOST_WIDE_INT offset ATTRIBUTE_UNUSED,
 			 scalar_int_mode mode)
 {
-  by_pieces_prev *prev = (by_pieces_prev *) prevp;
-  if (prev != nullptr && prev->data != nullptr)
-    {
-      /* Use the previous data in the same mode.  */
-      if (prev->mode == mode)
-	return prev->data;
-    }
-
   const char *c = (const char *) data;
   char *p = XALLOCAVEC (char, GET_MODE_SIZE (mode));
 
@@ -6785,30 +6642,16 @@ builtin_memset_read_str (void *data, void *prevp,
 /* Callback routine for store_by_pieces.  Return the RTL of a register
    containing GET_MODE_SIZE (MODE) consecutive copies of the unsigned
    char value given in the RTL register data.  For example, if mode is
-   4 bytes wide, return the RTL for 0x01010101*data.  If PREV isn't
-   nullptr, it has the RTL info from the previous iteration.  */
+   4 bytes wide, return the RTL for 0x01010101*data.  */
 
 static rtx
-builtin_memset_gen_str (void *data, void *prevp,
-			HOST_WIDE_INT offset ATTRIBUTE_UNUSED,
+builtin_memset_gen_str (void *data, HOST_WIDE_INT offset ATTRIBUTE_UNUSED,
 			scalar_int_mode mode)
 {
   rtx target, coeff;
   size_t size;
   char *p;
 
-  by_pieces_prev *prev = (by_pieces_prev *) prevp;
-  if (prev != nullptr && prev->data != nullptr)
-    {
-      /* Use the previous data in the same mode.  */
-      if (prev->mode == mode)
-	return prev->data;
-
-      target = simplify_gen_subreg (mode, prev->data, prev->mode, 0);
-      if (target != nullptr)
-	return target;
-    }
-
   size = GET_MODE_SIZE (mode);
   if (size == 1)
     return (rtx) data;
@@ -6843,168 +6686,6 @@ expand_builtin_memset (tree exp, rtx target, machine_mode mode)
   return expand_builtin_memset_args (dest, val, len, target, mode, exp);
 }
 
-/* Try to store VAL (or, if NULL_RTX, VALC) in LEN bytes starting at TO.
-   Return TRUE if successful, FALSE otherwise.  TO is assumed to be
-   aligned at an ALIGN-bits boundary.  LEN must be a multiple of
-   1<<CTZ_LEN between MIN_LEN and MAX_LEN.
-
-   The strategy is to issue one store_by_pieces for each power of two,
-   from most to least significant, guarded by a test on whether there
-   are at least that many bytes left to copy in LEN.
-
-   ??? Should we skip some powers of two in favor of loops?  Maybe start
-   at the max of TO/LEN/word alignment, at least when optimizing for
-   size, instead of ensuring O(log len) dynamic compares?  */
-
-bool
-try_store_by_multiple_pieces (rtx to, rtx len, unsigned int ctz_len,
-			      unsigned HOST_WIDE_INT min_len,
-			      unsigned HOST_WIDE_INT max_len,
-			      rtx val, char valc, unsigned int align)
-{
-  int max_bits = floor_log2 (max_len);
-  int min_bits = floor_log2 (min_len);
-  int sctz_len = ctz_len;
-
-  gcc_checking_assert (sctz_len >= 0);
-
-  if (val)
-    valc = 1;
-
-  /* Bits more significant than TST_BITS are part of the shared prefix
-     in the binary representation of both min_len and max_len.  Since
-     they're identical, we don't need to test them in the loop.  */
-  int tst_bits = (max_bits != min_bits ? max_bits
-		  : floor_log2 (max_len ^ min_len));
-
-  /* Check whether it's profitable to start by storing a fixed BLKSIZE
-     bytes, to lower max_bits.  In the unlikely case of a constant LEN
-     (implied by identical MAX_LEN and MIN_LEN), we want to issue a
-     single store_by_pieces, but otherwise, select the minimum multiple
-     of the ALIGN (in bytes) and of the MCD of the possible LENs, that
-     brings MAX_LEN below TST_BITS, if that's lower than min_len.  */
-  unsigned HOST_WIDE_INT blksize;
-  if (max_len > min_len)
-    {
-      unsigned HOST_WIDE_INT alrng = MAX (HOST_WIDE_INT_1U << ctz_len,
-					  align / BITS_PER_UNIT);
-      blksize = max_len - (HOST_WIDE_INT_1U << tst_bits) + alrng;
-      blksize &= ~(alrng - 1);
-    }
-  else if (max_len == min_len)
-    blksize = max_len;
-  else
-    gcc_unreachable ();
-  if (min_len >= blksize)
-    {
-      min_len -= blksize;
-      min_bits = floor_log2 (min_len);
-      max_len -= blksize;
-      max_bits = floor_log2 (max_len);
-
-      tst_bits = (max_bits != min_bits ? max_bits
-		 : floor_log2 (max_len ^ min_len));
-    }
-  else
-    blksize = 0;
-
-  /* Check that we can use store by pieces for the maximum store count
-     we may issue (initial fixed-size block, plus conditional
-     power-of-two-sized from max_bits to ctz_len.  */
-  unsigned HOST_WIDE_INT xlenest = blksize;
-  if (max_bits >= 0)
-    xlenest += ((HOST_WIDE_INT_1U << max_bits) * 2
-		- (HOST_WIDE_INT_1U << ctz_len));
-  if (!can_store_by_pieces (xlenest, builtin_memset_read_str,
-			    &valc, align, true))
-    return false;
-
-  rtx (*constfun) (void *, void *, HOST_WIDE_INT, scalar_int_mode);
-  void *constfundata;
-  if (val)
-    {
-      constfun = builtin_memset_gen_str;
-      constfundata = val = force_reg (TYPE_MODE (unsigned_char_type_node),
-				      val);
-    }
-  else
-    {
-      constfun = builtin_memset_read_str;
-      constfundata = &valc;
-    }
-
-  rtx ptr = copy_addr_to_reg (convert_to_mode (ptr_mode, XEXP (to, 0), 0));
-  rtx rem = copy_to_mode_reg (ptr_mode, convert_to_mode (ptr_mode, len, 0));
-  to = replace_equiv_address (to, ptr);
-  set_mem_align (to, align);
-
-  if (blksize)
-    {
-      to = store_by_pieces (to, blksize,
-			    constfun, constfundata,
-			    align, true,
-			    max_len != 0 ? RETURN_END : RETURN_BEGIN);
-      if (max_len == 0)
-	return true;
-
-      /* Adjust PTR, TO and REM.  Since TO's address is likely
-	 PTR+offset, we have to replace it.  */
-      emit_move_insn (ptr, force_operand (XEXP (to, 0), NULL_RTX));
-      to = replace_equiv_address (to, ptr);
-      rtx rem_minus_blksize = plus_constant (ptr_mode, rem, -blksize);
-      emit_move_insn (rem, force_operand (rem_minus_blksize, NULL_RTX));
-    }
-
-  /* Iterate over power-of-two block sizes from the maximum length to
-     the least significant bit possibly set in the length.  */
-  for (int i = max_bits; i >= sctz_len; i--)
-    {
-      rtx_code_label *label = NULL;
-      blksize = HOST_WIDE_INT_1U << i;
-
-      /* If we're past the bits shared between min_ and max_len, expand
-	 a test on the dynamic length, comparing it with the
-	 BLKSIZE.  */
-      if (i <= tst_bits)
-	{
-	  label = gen_label_rtx ();
-	  emit_cmp_and_jump_insns (rem, GEN_INT (blksize), LT, NULL,
-				   ptr_mode, 1, label,
-				   profile_probability::even ());
-	}
-      /* If we are at a bit that is in the prefix shared by min_ and
-	 max_len, skip this BLKSIZE if the bit is clear.  */
-      else if ((max_len & blksize) == 0)
-	continue;
-
-      /* Issue a store of BLKSIZE bytes.  */
-      to = store_by_pieces (to, blksize,
-			    constfun, constfundata,
-			    align, true,
-			    i != sctz_len ? RETURN_END : RETURN_BEGIN);
-
-      /* Adjust REM and PTR, unless this is the last iteration.  */
-      if (i != sctz_len)
-	{
-	  emit_move_insn (ptr, force_operand (XEXP (to, 0), NULL_RTX));
-	  to = replace_equiv_address (to, ptr);
-	  rtx rem_minus_blksize = plus_constant (ptr_mode, rem, -blksize);
-	  emit_move_insn (rem, force_operand (rem_minus_blksize, NULL_RTX));
-	}
-
-      if (label)
-	{
-	  emit_label (label);
-
-	  /* Given conditional stores, the offset can no longer be
-	     known, so clear it.  */
-	  clear_mem_offset (to);
-	}
-    }
-
-  return true;
-}
-
 /* Helper function to do the actual work for expand_builtin_memset.  The
    arguments to the builtin_memset call DEST, VAL, and LEN are broken out
    so that this can also be called without constructing an actual CALL_EXPR.
@@ -7059,8 +6740,7 @@ expand_builtin_memset_args (tree dest, tree val, tree len,
   dest_mem = get_memory_rtx (dest, len);
   val_mode = TYPE_MODE (unsigned_char_type_node);
 
-  if (TREE_CODE (val) != INTEGER_CST
-      || target_char_cast (val, &c))
+  if (TREE_CODE (val) != INTEGER_CST)
     {
       rtx val_rtx;
 
@@ -7084,12 +6764,7 @@ expand_builtin_memset_args (tree dest, tree val, tree len,
       else if (!set_storage_via_setmem (dest_mem, len_rtx, val_rtx,
 					dest_align, expected_align,
 					expected_size, min_size, max_size,
-					probable_max_size)
-	       && !try_store_by_multiple_pieces (dest_mem, len_rtx,
-						 tree_ctz (len),
-						 min_size, max_size,
-						 val_rtx, 0,
-						 dest_align))
+					probable_max_size))
 	goto do_libcall;
 
       dest_mem = force_operand (XEXP (dest_mem, 0), NULL_RTX);
@@ -7097,6 +6772,9 @@ expand_builtin_memset_args (tree dest, tree val, tree len,
       return dest_mem;
     }
 
+  if (target_char_cast (val, &c))
+    goto do_libcall;
+
   if (c)
     {
       if (tree_fits_uhwi_p (len)
@@ -7110,12 +6788,7 @@ expand_builtin_memset_args (tree dest, tree val, tree len,
 					gen_int_mode (c, val_mode),
 					dest_align, expected_align,
 					expected_size, min_size, max_size,
-					probable_max_size)
-	       && !try_store_by_multiple_pieces (dest_mem, len_rtx,
-						 tree_ctz (len),
-						 min_size, max_size,
-						 NULL_RTX, c,
-						 dest_align))
+					probable_max_size))
 	goto do_libcall;
 
       dest_mem = force_operand (XEXP (dest_mem, 0), NULL_RTX);
@@ -7129,7 +6802,7 @@ expand_builtin_memset_args (tree dest, tree val, tree len,
 				   ? BLOCK_OP_TAILCALL : BLOCK_OP_NORMAL,
 				   expected_align, expected_size,
 				   min_size, max_size,
-				   probable_max_size, tree_ctz (len));
+				   probable_max_size);
 
   if (dest_addr == 0)
     {
@@ -7447,7 +7120,7 @@ expand_builtin_strncmp (tree exp, ATTRIBUTE_UNUSED rtx target,
       || !check_nul_terminated_array (exp, arg2, arg3))
     return NULL_RTX;
 
-  location_t loc = EXPR_LOCATION (exp);
+  location_t loc = tree_inlined_location (exp);
   tree len1 = c_strlen (arg1, 1);
   tree len2 = c_strlen (arg2, 1);
 
@@ -7585,7 +7258,8 @@ expand_builtin_strncmp (tree exp, ATTRIBUTE_UNUSED rtx target,
   /* Expand the library call ourselves using a stabilized argument
      list to avoid re-evaluating the function's arguments twice.  */
   tree call = build_call_nofold_loc (loc, fndecl, 3, arg1, arg2, len);
-  copy_warning (call, exp);
+  if (TREE_NO_WARNING (exp))
+    TREE_NO_WARNING (call) = true;
   gcc_assert (TREE_CODE (call) == CALL_EXPR);
   CALL_EXPR_TAILCALL (call) = CALL_EXPR_TAILCALL (exp);
   return expand_call (call, target, target == const0_rtx);
@@ -7926,7 +7600,23 @@ expand_builtin_strub_enter (tree exp)
   if (optimize < 1 || flag_no_inline)
     return NULL_RTX;
 
-  rtx stktop = expand_builtin_stack_address ();
+  rtx stktop = NULL_RTX;
+
+#if 1 || defined RED_ZONE_SIZE
+  if (tree wmptr = (optimize
+		    ? find_watermark_parm (current_function_decl)
+		    : NULL_TREE))
+    {
+      tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+      tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+				 build_int_cst (TREE_TYPE (wmptr), 0));
+      rtx wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+      stktop = force_reg (ptr_mode, wmark);
+    }
+#endif
+
+  if (!stktop)
+    stktop = expand_builtin_stack_address ();
 
   tree wmptr = CALL_EXPR_ARG (exp, 0);
   tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
@@ -7947,11 +7637,57 @@ expand_builtin_strub_update (tree exp)
   if (!validate_arglist (exp, POINTER_TYPE, VOID_TYPE))
     return NULL_RTX;
 
-  if (optimize < 2 || optimize_size || flag_no_inline)
+  if (optimize < 2 || flag_no_inline)
     return NULL_RTX;
 
   rtx stktop = expand_builtin_stack_address ();
 
+#ifdef RED_ZONE_SIZE
+  /* Here's how the strub enter, update and leave functions deal with red zones.
+
+     If it weren't for red zones, update, called from within a strub context,
+     would bump the watermark to the top of the stack.  Enter and leave, running
+     in the caller, would use the caller's top of stack address both to
+     initialize the watermark passed to the callee, and to start strubbing the
+     stack afterwards.
+
+     Ideally, we'd update the watermark so as to cover the used amount of red
+     zone, and strub starting at the caller's other end of the (presumably
+     unused) red zone.  Normally, only leaf functions use the red zone, but at
+     this point we can't tell whether a function is a leaf, nor can we tell how
+     much of the red zone it uses.  Furthermore, some strub contexts may have
+     been inlined so that update and leave are called from the same stack frame,
+     and the strub builtins may all have been inlined, turning a strub function
+     into a leaf.
+
+     So cleaning the range from the caller's stack pointer (one end of the red
+     zone) to the (potentially inlined) callee's (other end of the) red zone
+     could scribble over the caller's own red zone.
+
+     We avoid this possibility by arranging for callers that are strub contexts
+     to use their own watermark as the strub starting point.  So, if A calls B,
+     and B calls C, B will tell A to strub up to the end of B's red zone, and
+     will strub itself only the part of C's stack frame and red zone that
+     doesn't overlap with B's.  With that, we don't need to know who's leaf and
+     who isn't: inlined calls will shrink their strub window to zero, each
+     remaining call will strub some portion of the stack, and eventually the
+     strub context will return to a caller that isn't a strub context itself,
+     that will therefore use its own stack pointer as the strub starting point.
+     It's not a leaf, because strub contexts can't be inlined into non-strub
+     contexts, so it doesn't use the red zone, and it will therefore correctly
+     strub up the callee's stack frame up to the end of the callee's red zone.
+     Neat!  */
+  if (true /* (flags_from_decl_or_type (current_function_decl) & ECF_LEAF) */)
+    {
+      poly_int64 red_zone_size = RED_ZONE_SIZE;
+#if STACK_GROWS_DOWNWARD
+      red_zone_size = -red_zone_size;
+#endif
+      stktop = plus_constant (ptr_mode, stktop, red_zone_size);
+      stktop = force_reg (ptr_mode, stktop);
+    }
+#endif
+
   tree wmptr = CALL_EXPR_ARG (exp, 0);
   tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
   tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
@@ -7979,10 +7715,26 @@ expand_builtin_strub_leave (tree exp)
   if (!validate_arglist (exp, POINTER_TYPE, VOID_TYPE))
     return NULL_RTX;
 
-  if (optimize < 2 || flag_no_inline)
+  if (optimize < 2 || optimize_size || flag_no_inline)
     return NULL_RTX;
 
-  rtx stktop = expand_builtin_stack_address ();
+  rtx stktop = NULL_RTX;
+
+#if 1 || defined RED_ZONE_SIZE
+  if (tree wmptr = (optimize
+		    ? find_watermark_parm (current_function_decl)
+		    : NULL_TREE))
+    {
+      tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+      tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+				 build_int_cst (TREE_TYPE (wmptr), 0));
+      rtx wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+      stktop = force_reg (ptr_mode, wmark);
+    }
+#endif
+
+  if (!stktop)
+    stktop = expand_builtin_stack_address ();
 
   tree wmptr = CALL_EXPR_ARG (exp, 0);
   tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
@@ -8000,6 +7752,9 @@ expand_builtin_strub_leave (tree exp)
   rtx end = stktop;
 #endif
 
+  /* We're going to modify it, so make sure it's not e.g. the stack pointer.  */
+  base = copy_to_reg (base);
+
   rtx_code_label *done = gen_label_rtx ();
   do_compare_rtx_and_jump (base, end, LT, STACK_UNSIGNED,
 			   ptr_mode, NULL_RTX, done, NULL,
@@ -10264,13 +10019,13 @@ expand_builtin (tree exp, rtx target, rtx subtarget, machine_mode mode,
     case BUILT_IN_VA_ARG_PACK:
       /* All valid uses of __builtin_va_arg_pack () are removed during
 	 inlining.  */
-      error ("invalid use of %<__builtin_va_arg_pack ()%>");
+      error ("%Kinvalid use of %<__builtin_va_arg_pack ()%>", exp);
       return const0_rtx;
 
     case BUILT_IN_VA_ARG_PACK_LEN:
       /* All valid uses of __builtin_va_arg_pack_len () are removed during
 	 inlining.  */
-      error ("invalid use of %<__builtin_va_arg_pack_len ()%>");
+      error ("%Kinvalid use of %<__builtin_va_arg_pack_len ()%>", exp);
       return const0_rtx;
 
       /* Return the address of the first anonymous stack arg.  */
@@ -10515,7 +10270,7 @@ expand_builtin (tree exp, rtx target, rtx subtarget, machine_mode mode,
       break;
 
     /* Expand it as BUILT_IN_MEMCMP_EQ first. If not successful, change it
-       back to a BUILT_IN_STRCMP. Remember to delete the 3rd parameter
+       back to a BUILT_IN_STRCMP. Remember to delete the 3rd paramater
        when changing it to a strcmp call.  */
     case BUILT_IN_STRCMP_EQ:
       target = expand_builtin_memcmp (exp, target, true);
@@ -13240,8 +12995,8 @@ expand_builtin_object_size (tree exp)
 
   if (!validate_arglist (exp, POINTER_TYPE, INTEGER_TYPE, VOID_TYPE))
     {
-      error ("first argument of %qD must be a pointer, second integer constant",
-	     fndecl);
+      error ("%Kfirst argument of %qD must be a pointer, second integer constant",
+	     exp, fndecl);
       expand_builtin_trap ();
       return const0_rtx;
     }
@@ -13253,8 +13008,8 @@ expand_builtin_object_size (tree exp)
       || tree_int_cst_sgn (ost) < 0
       || compare_tree_int (ost, 3) > 0)
     {
-      error ("last argument of %qD is not integer constant between 0 and 3",
-	      fndecl);
+      error ("%Klast argument of %qD is not integer constant between 0 and 3",
+	     exp, fndecl);
       expand_builtin_trap ();
       return const0_rtx;
     }
@@ -14066,8 +13821,8 @@ warn_dealloc_offset (location_t loc, tree exp, const access_ref &aref)
     }
 
   if (!warning_at (loc, OPT_Wfree_nonheap_object,
-		   "%qD called on pointer %qE with nonzero offset%s",
-		   dealloc_decl, aref.ref, offstr))
+		   "%K%qD called on pointer %qE with nonzero offset%s",
+		   exp, dealloc_decl, aref.ref, offstr))
     return false;
 
   if (DECL_P (aref.ref))
@@ -14122,15 +13877,15 @@ maybe_emit_free_warning (tree exp)
     return;
 
   tree dealloc_decl = get_callee_fndecl (exp);
-  location_t loc = EXPR_LOCATION (exp);
+  location_t loc = tree_inlined_location (exp);
 
   if (DECL_P (ref) || EXPR_P (ref))
     {
       /* Diagnose freeing a declared object.  */
       if (aref.ref_declared ()
 	  && warning_at (loc, OPT_Wfree_nonheap_object,
-			 "%qD called on unallocated object %qD",
-			 dealloc_decl, ref))
+			 "%K%qD called on unallocated object %qD",
+			 exp, dealloc_decl, ref))
 	{
 	  loc = (DECL_P (ref)
 		 ? DECL_SOURCE_LOCATION (ref)
@@ -14149,8 +13904,8 @@ maybe_emit_free_warning (tree exp)
   else if (CONSTANT_CLASS_P (ref))
     {
       if (warning_at (loc, OPT_Wfree_nonheap_object,
-		      "%qD called on a pointer to an unallocated "
-		      "object %qE", dealloc_decl, ref))
+		      "%K%qD called on a pointer to an unallocated "
+		      "object %qE", exp, dealloc_decl, ref))
 	{
 	  if (TREE_CODE (ptr) == SSA_NAME)
 	    {
@@ -14182,24 +13937,23 @@ maybe_emit_free_warning (tree exp)
 	      else
 		{
 		  tree alloc_decl = gimple_call_fndecl (def_stmt);
-		  const opt_code opt =
-		    (DECL_IS_OPERATOR_NEW_P (alloc_decl)
-		     || DECL_IS_OPERATOR_DELETE_P (dealloc_decl)
-		     ? OPT_Wmismatched_new_delete
-		     : OPT_Wmismatched_dealloc);
+		  int opt = (DECL_IS_OPERATOR_NEW_P (alloc_decl)
+			     || DECL_IS_OPERATOR_DELETE_P (dealloc_decl)
+			     ? OPT_Wmismatched_new_delete
+			     : OPT_Wmismatched_dealloc);
 		  warned = warning_at (loc, opt,
-				       "%qD called on pointer returned "
+				       "%K%qD called on pointer returned "
 				       "from a mismatched allocation "
-				       "function", dealloc_decl);
+				       "function", exp, dealloc_decl);
 		}
 	    }
 	  else if (gimple_call_builtin_p (def_stmt, BUILT_IN_ALLOCA)
 	    	   || gimple_call_builtin_p (def_stmt,
 	    				     BUILT_IN_ALLOCA_WITH_ALIGN))
 	    warned = warning_at (loc, OPT_Wfree_nonheap_object,
-				 "%qD called on pointer to "
+				 "%K%qD called on pointer to "
 				 "an unallocated object",
-				 dealloc_decl);
+				 exp, dealloc_decl);
 	  else if (warn_dealloc_offset (loc, exp, aref))
 	    return;
 
@@ -14297,7 +14051,7 @@ fold_builtin_varargs (location_t loc, tree fndecl, tree *args, int nargs)
     {
       ret = build1 (NOP_EXPR, TREE_TYPE (ret), ret);
       SET_EXPR_LOCATION (ret, loc);
-      suppress_warning (ret);
+      TREE_NO_WARNING (ret) = 1;
       return ret;
     }
   return NULL_TREE;
diff --git a/gcc/ipa-strub.c b/gcc/ipa-strub.c
index e302f0ec1c5..0364b7e1305 100644
--- a/gcc/ipa-strub.c
+++ b/gcc/ipa-strub.c
@@ -210,10 +210,16 @@ get_strub_mode_from_attr (tree strub_attr, bool var_p = false)
   return mode;
 }
 
+static enum strub_mode
+get_strub_mode_from_decl (tree fndecl)
+{
+  return get_strub_mode_from_attr (get_strub_attr_from_decl (fndecl));
+}
+
 static enum strub_mode
 get_strub_mode (cgraph_node *node)
 {
-  return get_strub_mode_from_attr (get_strub_attr_from_decl (node->decl));
+  return get_strub_mode_from_decl (node->decl);
 }
 
 static enum strub_mode
@@ -263,7 +269,7 @@ calls_builtin_apply_args_p (cgraph_node *node, bool report = false)
 	break;
 
       sorry_at (gimple_location (e->call_stmt),
-		"at-calls strub does not support call to %qD",
+		"at-calls %<strub%> does not support call to %qD",
 		cdecl);
     }
 
@@ -292,7 +298,8 @@ can_strub_p (cgraph_node *node, bool report = false)
 	return result;
 
       sorry_at (DECL_SOURCE_LOCATION (node->decl),
-		"%qD is not eligible for strub because of attribute %<noipa%>",
+		"%qD is not eligible for %<strub%>"
+		" because of attribute %<noipa%>",
 		node->decl);
     }
 
@@ -352,7 +359,7 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 	return result;
 
       sorry_at (DECL_SOURCE_LOCATION (node->decl),
-		"%qD is not eligible for internal strub"
+		"%qD is not eligible for internal %<strub%>"
 		" because of attribute %<noclone%>",
 		node->decl);
     }
@@ -372,7 +379,7 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 	return result;
 
       sorry_at (gimple_location (e->call_stmt),
-		"internal strub does not support call to %qD",
+		"internal %<strub%> does not support call to %qD",
 		cdecl);
     }
 
@@ -411,7 +418,7 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 	      return result;
 
 	    sorry_at (gimple_location (label_stmt),
-		      "internal strub does not support user labels");
+		      "internal %<strub%> does not support user labels");
 	  }
     }
 
@@ -425,16 +432,16 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 	return result;
 
       sorry_at (DECL_SOURCE_LOCATION (node->decl),
-		"%qD has too many arguments for internal strub",
+		"%qD has too many arguments for internal %<strub%>",
 		node->decl);
     }
 
-  if (result)
-    /* Since we're not changing the function identity proper, just
-       moving its full implementation, we *could* disable
-       fun->cannot_be_copied_reason and/or temporarily drop a noclone
-       attribute.  */
-    gcc_checking_assert (tree_versionable_function_p (node->decl));
+  /* Since we're not changing the function identity proper, just
+     moving its full implementation, we *could* disable
+     fun->cannot_be_copied_reason and/or temporarily drop a noclone
+     attribute.  */
+  gcc_checking_assert (!result || !node->has_gimple_body_p ()
+		       || tree_versionable_function_p (node->decl));
 
   return result;
 }
@@ -489,8 +496,22 @@ strub_callable_builtin_p (cgraph_node *node)
     case BUILT_IN_NONE:
       gcc_unreachable ();
 
-      /* ??? Make all builtins callable.  We wish to make any builtin call the
-	 compiler might introduce on its own callable.  Anything that is
+      /* This temporarily allocates stack for the call, and we can't reasonably
+	 update the watermark for that.  Besides, we don't check the actual call
+	 target, nor its signature, and it seems to be overkill to as much as
+	 try to do so.  */
+    case BUILT_IN_APPLY:
+      return false;
+
+      /* Conversely, this shouldn't be called from within strub contexts, since
+	 the caller may have had its signature modified.  STRUB_INTERNAL is ok,
+	 the call will remain in the STRUB_WRAPPER, and removed from the
+	 STRUB_WRAPPED clone.  */
+    case BUILT_IN_APPLY_ARGS:
+      return false;
+
+      /* ??? Make all other builtins callable.  We wish to make any builtin call
+	 the compiler might introduce on its own callable.  Anything that is
 	 predictable enough as to be known not to allow stack data that should
 	 be strubbed to unintentionally escape to non-strub contexts can be
 	 allowed, and pretty much every builtin appears to fit this description.
@@ -698,7 +719,8 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
 	{
 	  gcc_checking_assert (analyze_body);
 	  error_at (DECL_SOURCE_LOCATION (node->decl),
-		    "%qD requires strub, but no viable strub mode was found",
+		    "%qD requires %<strub%>,"
+		    " but no viable %<strub%> mode was found",
 		    node->decl);
 	  break;
 	}
@@ -751,7 +773,7 @@ set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
 	       && mode == STRUB_INLINABLE))
 	{
 	  error_at (DECL_SOURCE_LOCATION (node->decl),
-		    "strub mode %i selected for %qD, when %i was requested",
+		    "%<strub%> mode %i selected for %qD, when %i was requested",
 		    (int) mode, node->decl,
 		    (int) get_strub_mode_from_attr (attr));
 	  if (node->alias)
@@ -979,7 +1001,7 @@ verify_strub ()
 	if (callee_mode == STRUB_DISABLED
 	    || callee_mode == STRUB_INTERNAL)
 	  error_at (gimple_location (e->call_stmt),
-		    "indirect non-strub call in strub context %qD",
+		    "indirect non-%<strub%> call in %<strub%> context %qD",
 		    node->decl);
       }
 
@@ -987,9 +1009,22 @@ verify_strub ()
       {
 	gcc_checking_assert (!e->indirect_unknown_callee);
 	if (!strub_callable_from_p (e->callee, node))
-	  error_at (gimple_location (e->call_stmt),
-		    "calling non-strub %qD in strub context %qD",
-		    e->callee->decl, node->decl);
+	  {
+	    if (get_strub_mode (e->callee) == STRUB_INLINABLE)
+	      error_at (gimple_location (e->call_stmt),
+			"calling %<always_inline%> %<strub%> %qD"
+			" in non-%<strub%> context %qD",
+			e->callee->decl, node->decl);
+	    else if (fndecl_built_in_p (e->callee->decl, BUILT_IN_APPLY_ARGS)
+		     && get_strub_mode (node) == STRUB_INTERNAL)
+	      /* This is ok, it will be kept in the STRUB_WRAPPER, and removed
+		 from the STRUB_WRAPPED's strub context.  */
+	      continue;
+	    else
+	      error_at (gimple_location (e->call_stmt),
+			"calling non-%<strub%> %qD in %<strub%> context %qD",
+			e->callee->decl, node->decl);
+	  }
       }
   }
 
@@ -1067,27 +1102,25 @@ public:
 
   /* Use a distinct ptr_type_node to denote the watermark, so that we can
      recognize it in arg lists and avoid modifying types twice.  */
-  DEF_TYPE (wmt, build_distinct_type_copy (ptr_type_node))
+  DEF_TYPE (wmt, build_variant_type_copy (ptr_type_node))
 
-  DEF_TYPE (pwmt, build_pointer_type (get_wmt ()))
+  DEF_TYPE (pwmt, build_reference_type (get_wmt ()))
 
   DEF_TYPE (qpwmt,
 	    build_qualified_type (get_pwmt (),
 				  TYPE_QUAL_RESTRICT
-				  | TYPE_QUAL_CONST))
+				  /* | TYPE_QUAL_CONST */))
 
-  DEF_TYPE (pptr, build_pointer_type (ptr_type_node))
-
-  DEF_TYPE (qpptr,
-	    build_qualified_type (get_pptr (),
+  DEF_TYPE (qptr,
+	    build_qualified_type (ptr_type_node,
 				  TYPE_QUAL_RESTRICT
 				  | TYPE_QUAL_CONST))
 
   DEF_TYPE (qpvalst,
-	    build_qualified_type (build_pointer_type
+	    build_qualified_type (build_reference_type
 				  (va_list_type_node),
 				  TYPE_QUAL_RESTRICT
-				  | TYPE_QUAL_CONST))
+				  /* | TYPE_QUAL_CONST */))
 
 #undef DEF_TYPE
 
@@ -1159,12 +1192,13 @@ public:
 
   DEF_IDENT (watermark_ptr)
   DEF_IDENT (va_list_ptr)
-  DEF_IDENT (apply_args_ptr)
+  DEF_IDENT (apply_args)
 
 #undef DEF_IDENT
 
   static inline int adjust_at_calls_type (tree);
   static inline void adjust_at_calls_call (cgraph_edge *, int);
+  static inline void adjust_at_calls_calls (cgraph_node *);
 
   static inline gimple_seq
   call_update_watermark (tree wmptr, cgraph_node *node, profile_count count,
@@ -1493,6 +1527,39 @@ ipa_strub_set_mode_for_new_functions ()
   last_cgraph_order = symtab->order;
 }
 
+/* Return the PARM_DECL of the incoming watermark pointer, if there is one.  */
+tree
+find_watermark_parm (tree fndecl)
+{
+  switch (get_strub_mode_from_decl (fndecl))
+    {
+    case STRUB_WRAPPED:
+    case STRUB_AT_CALLS:
+    case STRUB_AT_CALLS_OPT:
+      break;
+
+    case STRUB_INTERNAL:
+    case STRUB_WRAPPER:
+    case STRUB_CALLABLE:
+    case STRUB_DISABLED:
+    case STRUB_INLINABLE:
+      return NULL_TREE;
+
+    default:
+      gcc_unreachable ();
+    }
+
+  for (tree parm = DECL_ARGUMENTS (fndecl); parm; parm = DECL_CHAIN (parm))
+    if (TREE_TYPE (parm) == pass_ipa_strub::get_qpwmt ())
+      {
+	gcc_checking_assert (DECL_NAME (parm)
+			     == pass_ipa_strub::get_watermark_ptr ());
+	return parm;
+      }
+
+  gcc_unreachable ();
+}
+
 /* Adjust a STRUB_AT_CALLS function TYPE, adding a watermark pointer if it
    hasn't been added yet.  Return the named argument count.  */
 int
@@ -1507,7 +1574,7 @@ pass_ipa_strub::adjust_at_calls_type (tree type)
   tree qpwmptrt = get_qpwmt ();
   while (*tlist && TREE_VALUE (*tlist) != void_type_node)
     {
-      /* The type has alreayd been adjusted.  */
+      /* The type has already been adjusted.  */
       if (TREE_VALUE (*tlist) == qpwmptrt)
 	return named_args;
       named_args++;
@@ -1581,22 +1648,37 @@ pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
 			 && (TREE_TYPE (gimple_call_arg (ocall, named_args))
 			     == get_pwmt ())));
 
-  /* ??? If it's a (tail?) call within a strub context, maybe pass on
-     the strub watermark instead of wrapping the call.  */
-
-  /* Initialize the watermark before the call.  */
-  tree swm = create_tmp_var (get_wmt (), ".strub.watermark");
-  TREE_ADDRESSABLE (swm) = true;
-  tree swmp = build1 (ADDR_EXPR, get_pwmt (), swm);
+  /* If we're already within a strub context, pass on the incoming watermark
+     pointer, and omit the enter and leave calls around the modified call.  */
+  tree swm, swmp = (false && /* This is disabled for now, to give the
+				red-zone-capable builtin expanders more thorough
+				testing.  */
+		    (optimize_size || optimize > 2)
+		    ? find_watermark_parm (e->caller->decl)
+		    : NULL_TREE);
+  bool omit_own_watermark = swmp;
+  if (omit_own_watermark)
+    swm = build2 (MEM_REF,
+		  TREE_TYPE (TREE_TYPE (swmp)),
+		  swmp,
+		  build_int_cst (TREE_TYPE (swmp), 0));
+  else
+    {
+      swm = create_tmp_var (get_wmt (), ".strub.watermark");
+      TREE_ADDRESSABLE (swm) = true;
+      swmp = build1 (ADDR_EXPR, get_pwmt (), swm);
 
-  tree enter = get_enter ();
-  gcall *stptr = gimple_build_call (enter, 1,
-				    unshare_expr (swmp));
-  gsi_insert_before (&gsi, stptr, GSI_SAME_STMT);
+      /* Initialize the watermark before the call.  */
+      tree enter = get_enter ();
+      gcall *stptr = gimple_build_call (enter, 1,
+					unshare_expr (swmp));
+      gsi_insert_before (&gsi, stptr, GSI_SAME_STMT);
 #if !IMPLICIT_CGRAPH_EDGES
-  e->caller->create_edge (cgraph_node::get_create (enter),
-			  stptr, gsi_bb (gsi)->count, false);
+      e->caller->create_edge (cgraph_node::get_create (enter),
+			      stptr, gsi_bb (gsi)->count, false);
 #endif
+    }
+
 
   /* Replace the call with one that passes the swmp argument first.  */
   gcall *wrcall;
@@ -1674,12 +1756,12 @@ pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
 		       build_tree_list
 		       (build_tree_list
 			(NULL_TREE, build_string (2, "=m")),
-			swm));
+			unshare_expr (swm)));
 	vec_safe_push (inputs,
 		       build_tree_list
 		       (build_tree_list
 			(NULL_TREE, build_string (1, "m")),
-			swm));
+			unshare_expr (swm)));
 	gasm *forcemod = gimple_build_asm_vec ("", inputs, outputs,
 					       NULL, NULL);
 	gimple_seq_add_stmt (&seq, forcemod);
@@ -1693,7 +1775,7 @@ pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
 			   build_tree_list
 			   (build_tree_list
 			    (NULL_TREE, build_string (1, "m")),
-			    swm));
+			    unshare_expr (swm)));
 	    gasm *force_store = gimple_build_asm_vec ("", inputs, NULL,
 						      NULL, NULL);
 	    gsi_insert_before (&gsi, force_store, GSI_SAME_STMT);
@@ -1701,17 +1783,69 @@ pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
       }
 #endif
 
-    gcall *sleave = gimple_build_call (get_leave (), 1,
-				       unshare_expr (swmp));
-    gimple_seq_add_stmt (&seq, sleave);
+    if (!omit_own_watermark)
+      {
+	gcall *sleave = gimple_build_call (get_leave (), 1,
+					   unshare_expr (swmp));
+	gimple_seq_add_stmt (&seq, sleave);
 
-    gassign *clobber = gimple_build_assign (swm,
-					    build_clobber
-					    (TREE_TYPE (swm)));
-    gimple_seq_add_stmt (&seq, clobber);
+	gassign *clobber = gimple_build_assign (swm,
+						build_clobber
+						(TREE_TYPE (swm)));
+	gimple_seq_add_stmt (&seq, clobber);
+      }
   }
 
-  gsi_insert_finally_seq_after_call (gsi, seq);
+  if (seq)
+    gsi_insert_finally_seq_after_call (gsi, seq);
+}
+
+void
+pass_ipa_strub::adjust_at_calls_calls (cgraph_node *node)
+{
+  /* Adjust unknown-callee indirect calls with STRUB_AT_CALLS types within
+     onode.  */
+  if (node->indirect_calls)
+    {
+      push_cfun (DECL_STRUCT_FUNCTION (node->decl));
+      for (cgraph_edge *e = node->indirect_calls; e; e = e->next_callee)
+	{
+	  gcc_checking_assert (e->indirect_unknown_callee);
+
+	  tree callee_fntype = gimple_call_fntype (e->call_stmt);
+	  enum strub_mode callee_mode
+	    = get_strub_mode_from_type (callee_fntype);
+
+	  if (callee_mode != STRUB_AT_CALLS
+	      && callee_mode != STRUB_AT_CALLS_OPT)
+	    continue;
+
+	  int named_args = adjust_at_calls_type (callee_fntype);
+
+	  adjust_at_calls_call (e, named_args);
+	}
+      pop_cfun ();
+    }
+
+  if (node->callees)
+    {
+      push_cfun (DECL_STRUCT_FUNCTION (node->decl));
+      for (cgraph_edge *e = node->callees; e; e = e->next_callee)
+	{
+	  gcc_checking_assert (!e->indirect_unknown_callee);
+
+	  enum strub_mode callee_mode = get_strub_mode (e->callee);
+
+	  if (callee_mode != STRUB_AT_CALLS
+	      && callee_mode != STRUB_AT_CALLS_OPT)
+	    continue;
+
+	  int named_args = adjust_at_calls_type (TREE_TYPE (e->callee->decl));
+
+	  adjust_at_calls_call (e, named_args);
+	}
+      pop_cfun ();
+    }
 }
 
 unsigned int
@@ -1849,54 +1983,13 @@ pass_ipa_strub::execute (function *)
     if (!onode->has_gimple_body_p ())
       continue;
 
-    /* Adjust unknown-callee indirect calls with STRUB_AT_CALLS types within
-       onode.  */
-    if (onode->indirect_calls)
-      {
-	push_cfun (DECL_STRUCT_FUNCTION (onode->decl));
-	for (cgraph_edge *e = onode->indirect_calls; e; e = e->next_callee)
-	  {
-	    gcc_checking_assert (e->indirect_unknown_callee);
-
-	    tree callee_fntype = gimple_call_fntype (e->call_stmt);
-	    enum strub_mode callee_mode
-	      = get_strub_mode_from_type (callee_fntype);
-
-	    if (callee_mode != STRUB_AT_CALLS
-		&& callee_mode != STRUB_AT_CALLS_OPT)
-	      continue;
-
-	    int named_args = adjust_at_calls_type (callee_fntype);
-
-	    adjust_at_calls_call (e, named_args);
-	  }
-	pop_cfun ();
-      }
-
-    if (onode->callees)
-      {
-	push_cfun (DECL_STRUCT_FUNCTION (onode->decl));
-	for (cgraph_edge *e = onode->callees; e; e = e->next_callee)
-	  {
-	    gcc_checking_assert (!e->indirect_unknown_callee);
-
-	    enum strub_mode callee_mode = get_strub_mode (e->callee);
-
-	    if (callee_mode != STRUB_AT_CALLS
-		&& callee_mode != STRUB_AT_CALLS_OPT)
-	      continue;
-
-	    int named_args = adjust_at_calls_type (TREE_TYPE (e->callee->decl));
-
-	    adjust_at_calls_call (e, named_args);
-	  }
-	pop_cfun ();
-      }
-
     enum strub_mode mode = get_strub_mode (onode);
 
     if (mode != STRUB_INTERNAL)
-      continue;
+      {
+	adjust_at_calls_calls (onode);
+	continue;
+      }
 
 #if 0
     /* Hmm, this is an i386-specific attribute.  Do we need machine-specific
@@ -1909,7 +2002,7 @@ pass_ipa_strub::execute (function *)
     if (!DECL_STRUCT_FUNCTION (onode->decl))
       {
 	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"not splitting struct-less function %qD for stack scrubbing",
+		"not splitting struct-less function %qD for %<strub%>",
 		onode->decl);
 	continue;
       }
@@ -1917,7 +2010,7 @@ pass_ipa_strub::execute (function *)
     if (!onode->lowered)
       {
 	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"not splitting non-lowered function %qD for stack scrubbing",
+		"not splitting non-lowered function %qD for %<strub%>",
 		onode->decl);
 	continue;
       }
@@ -1929,7 +2022,7 @@ pass_ipa_strub::execute (function *)
     if (!tree_versionable_function_p (onode->decl))
       {
 	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"%qD cannot be split for stack scrubbing",
+		"%qD cannot be split for %<strub%>",
 		onode->decl);
 	continue;
       }
@@ -1956,7 +2049,7 @@ pass_ipa_strub::execute (function *)
 	{
 	  ipa_adjusted_param aaadj = {};
 	  aaadj.op = IPA_PARAM_OP_NEW;
-	  aaadj.type = get_qpptr ();
+	  aaadj.type = get_qptr ();
 	  vec_safe_push (nparms, aaadj);
 	}
 
@@ -1982,7 +2075,7 @@ pass_ipa_strub::execute (function *)
     if (!nnode)
       {
 	error_at (DECL_SOURCE_LOCATION (onode->decl),
-		  "failed to split %qD for stack scrubbing",
+		  "failed to split %qD for %<strub%>",
 		  onode->decl);
 	continue;
       }
@@ -1996,6 +2089,8 @@ pass_ipa_strub::execute (function *)
     set_strub_mode_to (onode, STRUB_WRAPPER);
     set_strub_mode_to (nnode, STRUB_WRAPPED);
 
+    adjust_at_calls_calls (nnode);
+
     /* Decide which of the wrapped function's parms we want to turn into
        references to the argument passed to the wrapper.  In general, we want to
        copy small arguments, and avoid copying large ones.  Variable-sized array
@@ -2181,8 +2276,8 @@ pass_ipa_strub::execute (function *)
 	     only of reading because const/pure.  */
 	  if (apply_args)
 	    {
-	      nspec[curlen++] = (no_writes_p ? 'r' : '.');
-	      nspec[curlen++] = (no_writes_p ? 't' : ' ');
+	      nspec[curlen++] = 'r';
+	      nspec[curlen++] = ' ';
 	    }
 	  if (is_stdarg)
 	    {
@@ -2485,24 +2580,24 @@ pass_ipa_strub::execute (function *)
     }
 
     {
-      tree aaptr = NULL_TREE;
+      tree aaval = NULL_TREE;
       tree vaptr = NULL_TREE;
       tree wmptr = NULL_TREE;
       for (tree arg = DECL_ARGUMENTS (nnode->decl); arg; arg = DECL_CHAIN (arg))
 	{
-	  aaptr = vaptr;
+	  aaval = vaptr;
 	  vaptr = wmptr;
 	  wmptr = arg;
 	}
 
       if (!apply_args)
-	aaptr = NULL_TREE;
+	aaval = NULL_TREE;
       /* The trailing args are [apply_args], [va_list_ptr], and
 	 watermark.  If we don't have a va_list_ptr, the penultimate
 	 argument is apply_args.
        */
       else if (!is_stdarg)
-	aaptr = vaptr;
+	aaval = vaptr;
 
       if (!is_stdarg)
 	vaptr = NULL_TREE;
@@ -2522,10 +2617,10 @@ pass_ipa_strub::execute (function *)
 
       if (apply_args)
 	{
-	  DECL_NAME (aaptr) = get_apply_args_ptr ();
-	  DECL_ARTIFICIAL (aaptr) = 1;
-	  DECL_IGNORED_P (aaptr) = 1;
-	  TREE_USED (aaptr) = 1;
+	  DECL_NAME (aaval) = get_apply_args ();
+	  DECL_ARTIFICIAL (aaval) = 1;
+	  DECL_IGNORED_P (aaval) = 1;
+	  TREE_USED (aaval) = 1;
 	}
 
       push_cfun (DECL_STRUCT_FUNCTION (nnode->decl));
@@ -2582,9 +2677,10 @@ pass_ipa_strub::execute (function *)
 	    else if (fndecl && is_stdarg
 		     && fndecl_built_in_p (fndecl, BUILT_IN_VA_START))
 	      {
-		if (builtin_decl_explicit (BUILT_IN_VA_START) != fndecl)
-		  sorry_at (gimple_location (call),
-			    "nonstandard stdarg conventions");
+		/* Using a non-default stdarg ABI makes the function ineligible
+		   for internal strub.  */
+		gcc_checking_assert (builtin_decl_explicit (BUILT_IN_VA_START)
+				     == fndecl);
 		tree bvacopy = builtin_decl_explicit (BUILT_IN_VA_COPY);
 		gimple_call_set_fndecl (call, bvacopy);
 		tree arg = vaptr;
@@ -2607,7 +2703,9 @@ pass_ipa_strub::execute (function *)
 		     && fndecl_built_in_p (fndecl, BUILT_IN_APPLY_ARGS))
 	      {
 		tree lhs = gimple_call_lhs (call);
-		gassign *assign = gimple_build_assign (lhs, aaptr);
+		gimple *assign = (lhs
+				  ? gimple_build_assign (lhs, aaval)
+				  : gimple_build_nop ());
 		gsi_replace (&gsi, assign, true);
 		cgraph_edge::remove (e);
 	      }
@@ -2650,7 +2748,7 @@ pass_ipa_strub::execute (function *)
 
       if (apply_args)
 	{
-	  tree aalst = create_tmp_var (ptr_type_node, ".strub.appargs");
+	  tree aalst = create_tmp_var (ptr_type_node, ".strub.apply_args");
 	  tree bappargs = builtin_decl_explicit (BUILT_IN_APPLY_ARGS);
 	  gcall *appargs = gimple_build_call (bappargs, 0);
 	  gimple_call_set_lhs (appargs, aalst);
diff --git a/gcc/ipa-strub.h b/gcc/ipa-strub.h
index cadbca5002a..ea8ee857e8e 100644
--- a/gcc/ipa-strub.h
+++ b/gcc/ipa-strub.h
@@ -23,3 +23,4 @@ along with GCC; see the file COPYING3.  If not see
    doesn't have to be called directly by CALLER, but the returned
    value says nothing about intervening functions.  */
 extern bool strub_inlinable_p (cgraph_node *callee, cgraph_node *caller);
+extern tree find_watermark_parm (tree fndecl);
diff --git a/gcc/testsuite/c-c++-common/strub-O2.c b/gcc/testsuite/c-c++-common/strub-O2.c
index 9cc39e763b1..7848c46d179 100644
--- a/gcc/testsuite/c-c++-common/strub-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-O2.c
@@ -13,4 +13,4 @@ int f() {
 /* { dg-final { scan-rtl-dump-not "strub_enter" "expand" } } */
 /* { dg-final { scan-rtl-dump-not "strub_update" "expand" } } */
 /* { dg-final { scan-rtl-dump "strub_leave" "expand" } } */
-/* { dg-final { scan-rtl-dump "\n\[(\]call_insn\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
+/* { dg-final { scan-rtl-dump "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-O2fni.c b/gcc/testsuite/c-c++-common/strub-O2fni.c
index 51cae845d5f..85a8f76785e 100644
--- a/gcc/testsuite/c-c++-common/strub-O2fni.c
+++ b/gcc/testsuite/c-c++-common/strub-O2fni.c
@@ -1,7 +1,7 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fdump-rtl-expand -fno-inline-functions" } */
+/* { dg-options "-O2 -fstrub=default -fdump-rtl-expand -fno-inline" } */
 
-/* With -fno-inline-functions, none of the strub builtins are inlined.  */
+/* With -fno-inline, none of the strub builtins are inlined.  */
 
 int __attribute__ ((__strub__)) var;
 
@@ -12,4 +12,4 @@ int f() {
 /* { dg-final { scan-rtl-dump "strub_enter" "expand" } } */
 /* { dg-final { scan-rtl-dump "strub_update" "expand" } } */
 /* { dg-final { scan-rtl-dump "strub_leave" "expand" } } */
-/* { dg-final { scan-rtl-dump-not "\n\[(\]call_insn\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
+/* { dg-final { scan-rtl-dump-not "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-O3fni.c b/gcc/testsuite/c-c++-common/strub-O3fni.c
index 8f67b613be8..a2eedfd96b2 100644
--- a/gcc/testsuite/c-c++-common/strub-O3fni.c
+++ b/gcc/testsuite/c-c++-common/strub-O3fni.c
@@ -1,7 +1,7 @@
 /* { dg-do compile } */
 /* { dg-options "-O3 -fstrub=default -fdump-rtl-expand -fno-inline" } */
 
-/* With -fno-inline-functions, none of the strub builtins are inlined.  */
+/* With -fno-inline, none of the strub builtins are inlined.  */
 
 int __attribute__ ((__strub__)) var;
 
@@ -12,4 +12,4 @@ int f() {
 /* { dg-final { scan-rtl-dump "strub_enter" "expand" } } */
 /* { dg-final { scan-rtl-dump "strub_update" "expand" } } */
 /* { dg-final { scan-rtl-dump "strub_leave" "expand" } } */
-/* { dg-final { scan-rtl-dump-not "\n\[(\]call_insn\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
+/* { dg-final { scan-rtl-dump-not "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-Og.c b/gcc/testsuite/c-c++-common/strub-Og.c
index 6f60349573f..e5cb1f60541 100644
--- a/gcc/testsuite/c-c++-common/strub-Og.c
+++ b/gcc/testsuite/c-c++-common/strub-Og.c
@@ -13,4 +13,4 @@ int f() {
 /* { dg-final { scan-rtl-dump-not "strub_enter" "expand" } } */
 /* { dg-final { scan-rtl-dump "strub_update" "expand" } } */
 /* { dg-final { scan-rtl-dump "strub_leave" "expand" } } */
-/* { dg-final { scan-rtl-dump-not "\n\[(\]call_insn\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
+/* { dg-final { scan-rtl-dump-not "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-Os.c b/gcc/testsuite/c-c++-common/strub-Os.c
index 5d1c08a7528..194aacc2c05 100644
--- a/gcc/testsuite/c-c++-common/strub-Os.c
+++ b/gcc/testsuite/c-c++-common/strub-Os.c
@@ -15,4 +15,4 @@ int f() {
 /* { dg-final { scan-rtl-dump-not "strub_enter" "expand" } } */
 /* { dg-final { scan-rtl-dump-not "strub_update" "expand" } } */
 /* { dg-final { scan-rtl-dump "strub_leave" "expand" } } */
-/* { dg-final { scan-rtl-dump-not "\n\[(\]call_insn\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
+/* { dg-final { scan-rtl-dump-not "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-all1.c b/gcc/testsuite/c-c++-common/strub-all1.c
index 54daf84656c..46e84bf6560 100644
--- a/gcc/testsuite/c-c++-common/strub-all1.c
+++ b/gcc/testsuite/c-c++-common/strub-all1.c
@@ -3,9 +3,9 @@
 
 /* h becomes STRUB_CALLABLE, rather than STRUB_INLINABLE, because of the
    strub-enabling -fstrub flag, and gets inlined before pass_ipa_strub.  */
-static void
+static inline void
 __attribute__ ((__always_inline__))
-h() {  /* { dg-warning "might not be inlinable" } */
+h() {
 }
 
 /* g becomes STRUB_AT_CALLS, because of the flag.  */
diff --git a/gcc/testsuite/c-c++-common/strub-apply1.c b/gcc/testsuite/c-c++-common/strub-apply1.c
new file mode 100644
index 00000000000..f180b17f30e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply1.c
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default" } */
+
+void __attribute__ ((__strub__ (3)))
+apply_function (void *args)
+{
+  __builtin_apply (0, args, 0);
+}
+
+void __attribute__ ((__strub__ (2)))
+apply_args (int i, int j, double d)
+{
+  void *args = __builtin_apply_args ();
+  apply_function (args);
+}
diff --git a/gcc/testsuite/c-c++-common/strub-apply2.c b/gcc/testsuite/c-c++-common/strub-apply2.c
new file mode 100644
index 00000000000..379a54b73b7
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply2.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default" } */
+
+extern void __attribute__ ((__strub__))
+apply_function (void *args);
+
+void __attribute__ ((__strub__))
+apply_args (int i, int j, double d) /* { dg-error "selected" } */
+{
+  void *args = __builtin_apply_args (); /* { dg-message "does not support" } */
+  apply_function (args);
+}
diff --git a/gcc/testsuite/c-c++-common/strub-apply3.c b/gcc/testsuite/c-c++-common/strub-apply3.c
new file mode 100644
index 00000000000..9b4786be698
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply3.c
@@ -0,0 +1,8 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default" } */
+
+void __attribute__ ((__strub__))
+apply_function (void *args)
+{
+  __builtin_apply (0, args, 0); /* { dg-error "in .strub. context" } */
+}
diff --git a/gcc/testsuite/c-c++-common/strub-apply4.c b/gcc/testsuite/c-c++-common/strub-apply4.c
new file mode 100644
index 00000000000..409f747743e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply4.c
@@ -0,0 +1,21 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fstrub=default -fdump-ipa-strubm" } */
+
+/* Check that implicit enabling of strub mode selects internal strub when the
+   function uses __builtin_apply_args, that prevents the optimization to
+   at-calls mode.  */
+
+int __attribute__ ((__strub__)) var;
+
+static inline void
+apply_args (int i, int j, double d)
+{
+  var++;
+  __builtin_apply_args ();
+}
+
+void f() {
+  apply_args (1, 2, 3);
+}
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 1 "strubm" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-at-calls1.c b/gcc/testsuite/c-c++-common/strub-at-calls1.c
index 0d1b9fce833..d964b07ae5d 100644
--- a/gcc/testsuite/c-c++-common/strub-at-calls1.c
+++ b/gcc/testsuite/c-c++-common/strub-at-calls1.c
@@ -3,9 +3,9 @@
 
 /* h becomes STRUB_CALLABLE, rather than STRUB_INLINABLE, because of the
    strub-enabling -fstrub flag, and gets inlined before pass_ipa_strub.  */
-static void
+static inline void
 __attribute__ ((__always_inline__))
-h() {  /* { dg-warning "might not be inlinable" } */
+h() {
 }
 
 /* g becomes STRUB_AT_CALLS, because of the flag.  */
diff --git a/gcc/testsuite/c-c++-common/strub-default1.c b/gcc/testsuite/c-c++-common/strub-default1.c
index a1e1803aadc..d579ec62f55 100644
--- a/gcc/testsuite/c-c++-common/strub-default1.c
+++ b/gcc/testsuite/c-c++-common/strub-default1.c
@@ -3,12 +3,12 @@
 
 static int __attribute__ ((__strub__)) var;
 
-/* h becomes STRUB_STRUB_INLINABLE, because of the use of the strub variable,
+/* h becomes STRUB_INLINABLE, because of the use of the strub variable,
    and the always_inline flag.  It would get inlined before pass_ipa_strub, if
    it weren't for the error.  */
-static void
+static inline void
 __attribute__ ((__always_inline__))
-h() {  /* { dg-warning "might not be inlinable" } */
+h() {
   var++;
 }
 
@@ -34,7 +34,7 @@ f() {
 /* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 1 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-3\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]-3\[)\]" 1 "strub" } } */
 /* { dg-final { scan-ipa-dump-times "strub \[(\]-4\[)\]" 1 "strub" } } */
 /* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 1 "strub" } } */
 /* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-internal1.c b/gcc/testsuite/c-c++-common/strub-internal1.c
index b9bd787df0a..a74658c9ac9 100644
--- a/gcc/testsuite/c-c++-common/strub-internal1.c
+++ b/gcc/testsuite/c-c++-common/strub-internal1.c
@@ -3,9 +3,9 @@
 
 /* h becomes STRUB_CALLABLE, rather than STRUB_INLINABLE, because of the
    strub-enabling -fstrub flag, and gets inlined before pass_ipa_strub.  */
-static void
+static inline void
 __attribute__ ((__always_inline__))
-h() {  /* { dg-warning "might not be inlinable" } */
+h() {
 }
 
 /* g becomes STRUB_INTERNAL because of the flag, and gets split into
diff --git a/gcc/testsuite/c-c++-common/strub-parms1.c b/gcc/testsuite/c-c++-common/strub-parms1.c
new file mode 100644
index 00000000000..0422ccc7f7d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-parms1.c
@@ -0,0 +1,48 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+#include <stdarg.h>
+
+void __attribute__ ((__strub__ (2)))
+small_args (int i, long long l, void *p, void **q, double d, char c)
+{
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid small_args.strub.\[0-9\]* \[(\]int i, long long int l, void \\* p, void \\* \\* q, double d, char c, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " small_args.strub.\[0-9\]* \[(\]\[^&\]*&.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+
+
+struct large_arg {
+  int x[128];
+};
+
+void __attribute__ ((__strub__ (2)))
+large_byref_arg (struct large_arg la)
+{
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid large_byref_arg.strub.\[0-9\]* \[(\]struct large_arg & la, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " large_byref_arg.strub.\[0-9\]* \[(\]&\[^&\]*&.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+
+void __attribute__ ((__strub__ (2)))
+std_arg (int i, ...)
+{
+  va_list vl;
+  va_start (vl, i);
+  va_end (vl);
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid std_arg.strub.\[0-9\]* \[(\]int i, \[^&,\]* &\[^&,\]*.strub.va_list_ptr, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " std_arg.strub.\[0-9\]* \[(\]\[^&\]*&.strub.va_list.\[0-9\]*, &.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_start \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_copy \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_end \\(" 2 "strub" } } */
+
+void __attribute__ ((__strub__ (2)))
+apply_args (int i, int j, double d)
+{
+  __builtin_apply_args ();
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid apply_args.strub.\[0-9\]* \[(\]int i, int j, double d, void \\*\[^&,\]*.strub.apply_args, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " apply_args.strub.\[0-9\]* \[(\]\[^&\]*.strub.apply_args.\[0-9\]*_\[0-9\]*, &.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-parms2.c b/gcc/testsuite/c-c++-common/strub-parms2.c
new file mode 100644
index 00000000000..aaf02a2cd46
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-parms2.c
@@ -0,0 +1,36 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+#include <stdarg.h>
+
+void __attribute__ ((__strub__ (1)))
+small_args (int i, long long l, void *p, void **q, double d, char c)
+{
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid small_args \[(\]int i, long long int l, void \\* p, void \\* \\* q, double d, char c, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+
+
+struct large_arg {
+  int x[128];
+};
+
+void __attribute__ ((__strub__ (1)))
+large_byref_arg (struct large_arg la)
+{
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid large_byref_arg \[(\]struct large_arg la, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+
+void __attribute__ ((__strub__ (1)))
+std_arg (int i, ...)
+{
+  va_list vl;
+  va_start (vl, i);
+  va_end (vl);
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid std_arg \[(\]int i, void \\* &\[^&,\]*.strub.watermark_ptr\[, .]*\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_start \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-not "va_copy \\(" "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_end \\(" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-parms3.c b/gcc/testsuite/c-c++-common/strub-parms3.c
new file mode 100644
index 00000000000..2846098160d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-parms3.c
@@ -0,0 +1,58 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that uses of a strub variable implicitly enables internal strub for
+   publicly-visible functions, and causes the same transformations to their
+   signatures as those in strub-parms1.c.  */
+
+#include <stdarg.h>
+
+int __attribute__ ((__strub__)) var;
+
+void
+small_args (int i, long long l, void *p, void **q, double d, char c)
+{
+  var++;
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid small_args.strub.\[0-9\]* \[(\]int i, long long int l, void \\* p, void \\* \\* q, double d, char c, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " small_args.strub.\[0-9\]* \[(\]\[^&\]*&.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+
+
+struct large_arg {
+  int x[128];
+};
+
+void
+large_byref_arg (struct large_arg la)
+{
+  var++;
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid large_byref_arg.strub.\[0-9\]* \[(\]struct large_arg & la, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " large_byref_arg.strub.\[0-9\]* \[(\]&\[^&\]*&.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+
+void
+std_arg (int i, ...)
+{
+  va_list vl;
+  va_start (vl, i);
+  var++;
+  va_end (vl);
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid std_arg.strub.\[0-9\]* \[(\]int i, \[^&,\]* &\[^&,\]*.strub.va_list_ptr, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " std_arg.strub.\[0-9\]* \[(\]\[^&\]*&.strub.va_list.\[0-9\]*, &.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_start \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_copy \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_end \\(" 2 "strub" } } */
+
+void
+apply_args (int i, int j, double d)
+{
+  var++;
+  __builtin_apply_args ();
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid apply_args.strub.\[0-9\]* \[(\]int i, int j, double d, void \\*\[^&,\]*.strub.apply_args, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " apply_args.strub.\[0-9\]* \[(\]\[^&\]*.strub.apply_args.\[0-9\]*_\[0-9\]*, &.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable1.c b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
new file mode 100644
index 00000000000..45965f275c9
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub -fno-inline" } */
+
+/* Check that strub and non-strub functions can be called from non-strub
+   contexts, and that strub and callable functions can be called from strub
+   contexts.  */
+
+#define OMIT_IMPERMISSIBLE_CALLS 1
+#include "strub-callable2.c"
+
+/* { dg-final { scan-ipa-dump-times "strub_enter" 45 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_update" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_leave" 45 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable2.c b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
new file mode 100644
index 00000000000..38935e3270b
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
@@ -0,0 +1,264 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default" } */
+
+/* Check that impermissible (cross-strub-context) calls are reported.  */
+
+extern int __attribute__ ((__strub__ (3))) xcallable (void);
+extern int __attribute__ ((__strub__ (2))) xinternal (void);
+extern int __attribute__ ((__strub__ (1))) xat_calls (void);
+extern int __attribute__ ((__strub__ (0))) xdisabled (void);
+
+int __attribute__ ((__strub__ (3))) callable (void);
+int __attribute__ ((__strub__ (2))) internal (void);
+int __attribute__ ((__strub__ (1))) at_calls (void);
+int __attribute__ ((__strub__ (0))) disabled (void);
+
+int __attribute__ ((__strub__)) var;
+int var_user (void);
+
+static inline int __attribute__ ((__always_inline__, __strub__ (3)))
+icallable (void);
+static inline int __attribute__ ((__always_inline__, __strub__ (2)))
+iinternal (void);
+static inline int __attribute__ ((__always_inline__, __strub__ (1)))
+iat_calls (void);
+static inline int __attribute__ ((__always_inline__, __strub__ (0)))
+idisabled (void);
+static inline int __attribute__ ((__always_inline__))
+ivar_user (void);
+
+static inline int __attribute__ ((__always_inline__, __strub__ (3)))
+i_callable (void) { return 0; }
+static inline int __attribute__ ((__always_inline__, __strub__ (2)))
+i_internal (void) { return var; }
+static inline int __attribute__ ((__always_inline__, __strub__ (1)))
+i_at_calls (void) { return var; }
+static inline int __attribute__ ((__always_inline__, __strub__ (0)))
+i_disabled (void) { return 0; }
+static inline int __attribute__ ((__always_inline__))
+i_var_user (void) { return var; }
+
+#define CALLS_GOOD_FOR_STRUB_CONTEXT(ISEP)	\
+  do {						\
+    ret += i ## ISEP ## at_calls ();		\
+    ret += i ## ISEP ## internal ();		\
+    ret += i ## ISEP ## var_user ();		\
+  } while (0)
+
+#define CALLS_GOOD_FOR_NONSTRUB_CONTEXT(ISEP)	\
+  do {						\
+    ret += internal ();				\
+    ret += disabled ();				\
+    ret += var_user ();				\
+						\
+    ret += i ## ISEP ## disabled ();		\
+						\
+    ret += xinternal ();			\
+    ret += xdisabled ();			\
+  } while (0)
+
+#define CALLS_GOOD_FOR_EITHER_CONTEXT(ISEP)	\
+  do {						\
+    ret += i ## ISEP ## callable ();		\
+						\
+    ret += callable ();				\
+    ret += at_calls ();				\
+						\
+    ret += xat_calls ();			\
+    ret += xcallable ();			\
+  } while (0)
+
+/* Not a strub context, so it can call anything.
+   Explicitly declared as callable even from within strub contexts.  */
+int __attribute__ ((__strub__ (3)))
+callable (void) {
+  int ret = 0;
+
+  /* CALLS_GOOD_FOR_STRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += iat_calls (); /* { dg-error "in non-.strub. context" } */
+    ret += iinternal (); /* { dg-error "in non-.strub. context" } */
+    ret += ivar_user (); /* { dg-error "in non-.strub. context" } */
+#endif
+  CALLS_GOOD_FOR_EITHER_CONTEXT();
+  CALLS_GOOD_FOR_NONSTRUB_CONTEXT();
+
+  return ret;
+}
+
+/* Internal strubbing means the body is a strub context, so it can only call
+   strub functions, and it's not itself callable from strub functions.  */
+int __attribute__ ((__strub__ (2)))
+internal (void) {
+  int ret = var;
+
+  CALLS_GOOD_FOR_STRUB_CONTEXT();
+  CALLS_GOOD_FOR_EITHER_CONTEXT();
+  /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += internal (); /* { dg-error "in .strub. context" } */
+    ret += disabled (); /* { dg-error "in .strub. context" } */
+    ret += var_user (); /* { dg-error "in .strub. context" } */
+
+    ret += idisabled (); /* { dg-error "in .strub. context" } */
+
+    ret += xinternal (); /* { dg-error "in .strub. context" } */
+    ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+  return ret;
+}
+
+int __attribute__ ((__strub__ (1)))
+at_calls (void) {
+  int ret = var;
+
+  CALLS_GOOD_FOR_STRUB_CONTEXT();
+  CALLS_GOOD_FOR_EITHER_CONTEXT();
+  /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += internal (); /* { dg-error "in .strub. context" } */
+    ret += disabled (); /* { dg-error "in .strub. context" } */
+    ret += var_user (); /* { dg-error "in .strub. context" } */
+
+    ret += idisabled (); /* { dg-error "in .strub. context" } */
+
+    ret += xinternal (); /* { dg-error "in .strub. context" } */
+    ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+  return ret;
+}
+
+int __attribute__ ((__strub__ (0)))
+disabled () {
+  int ret = 0;
+
+  /* CALLS_GOOD_FOR_STRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += iat_calls (); /* { dg-error "in non-.strub. context" } */
+    ret += iinternal (); /* { dg-error "in non-.strub. context" } */
+    ret += ivar_user (); /* { dg-error "in non-.strub. context" } */
+#endif
+  CALLS_GOOD_FOR_EITHER_CONTEXT();
+  CALLS_GOOD_FOR_NONSTRUB_CONTEXT();
+
+  return ret;
+}  
+
+int
+var_user (void) {
+  int ret = var;
+
+  CALLS_GOOD_FOR_STRUB_CONTEXT();
+  CALLS_GOOD_FOR_EITHER_CONTEXT();
+  /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += internal (); /* { dg-error "in .strub. context" } */
+    ret += disabled (); /* { dg-error "in .strub. context" } */
+    ret += var_user (); /* { dg-error "in .strub. context" } */
+
+    ret += idisabled (); /* { dg-error "in .strub. context" } */
+
+    ret += xinternal (); /* { dg-error "in .strub. context" } */
+    ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+  return ret;
+}
+
+int
+icallable (void)
+{
+  int ret = 0;
+
+  /* CALLS_GOOD_FOR_STRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += i_at_calls (); /* { dg-error "in non-.strub. context" } */
+    ret += i_internal (); /* { dg-error "in non-.strub. context" } */
+    ret += i_var_user (); /* { dg-error "in non-.strub. context" } */
+#endif
+  CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+  CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_);
+
+  return ret;
+}
+
+int
+iinternal (void) {
+  int ret = var;
+
+  CALLS_GOOD_FOR_STRUB_CONTEXT(_);
+  CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+  /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += internal (); /* { dg-error "in .strub. context" } */
+    ret += disabled (); /* { dg-error "in .strub. context" } */
+    ret += var_user (); /* { dg-error "in .strub. context" } */
+
+    ret += i_disabled (); /* { dg-error "in .strub. context" } */
+
+    ret += xinternal (); /* { dg-error "in .strub. context" } */
+    ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+  return ret;
+}
+
+int
+iat_calls (void) {
+  int ret = var;
+
+  CALLS_GOOD_FOR_STRUB_CONTEXT(_);
+  CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+  /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += internal (); /* { dg-error "in .strub. context" } */
+    ret += disabled (); /* { dg-error "in .strub. context" } */
+    ret += var_user (); /* { dg-error "in .strub. context" } */
+
+    ret += i_disabled (); /* { dg-error "in .strub. context" } */
+
+    ret += xinternal (); /* { dg-error "in .strub. context" } */
+    ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+  return ret;
+}
+
+int
+idisabled () {
+  int ret = 0;
+
+  /* CALLS_GOOD_FOR_STRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += i_at_calls (); /* { dg-error "in non-.strub. context" } */
+    ret += i_internal (); /* { dg-error "in non-.strub. context" } */
+    ret += i_var_user (); /* { dg-error "in non-.strub. context" } */
+#endif
+  CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+  CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_);
+
+  return ret;
+}  
+
+int
+ivar_user (void) {
+  int ret = var;
+
+  CALLS_GOOD_FOR_STRUB_CONTEXT(_);
+  CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+  /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += internal (); /* { dg-error "in .strub. context" } */
+    ret += disabled (); /* { dg-error "in .strub. context" } */
+    ret += var_user (); /* { dg-error "in .strub. context" } */
+
+    ret += i_disabled (); /* { dg-error "in .strub. context" } */
+
+    ret += xinternal (); /* { dg-error "in .strub. context" } */
+    ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+  return ret;
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const1.c b/gcc/testsuite/c-c++-common/torture/strub-const1.c
new file mode 100644
index 00000000000..100fb0c59a9
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const1.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub const function call, we issue an asm statement
+   to make sure the watermark passed to it is held in memory before the call,
+   and another to make sure it is not assumed to be unchanged.  */
+
+int __attribute__ ((__strub__, __const__))
+f() {
+  return 0;
+}
+
+int
+g() {
+  return f();
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const2.c b/gcc/testsuite/c-c++-common/torture/strub-const2.c
new file mode 100644
index 00000000000..9e818ac9748
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const2.c
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-const function call, we issue an
+   asm statement to make sure the watermark passed to it is held in memory
+   before the call, and another to make sure it is not assumed to be
+   unchanged.  */
+
+int __attribute__ ((__strub__))
+#if ! __OPTIMIZE__
+__attribute__ ((__const__))
+#endif
+f() {
+  return 0;
+}
+
+int
+g() {
+  return f();
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const3.c b/gcc/testsuite/c-c++-common/torture/strub-const3.c
new file mode 100644
index 00000000000..d40e8aa45cb
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const3.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub const wrapping call, we issue an asm statement
+   to make sure the watermark passed to it is held in memory before the call,
+   and another to make sure it is not assumed to be unchanged.  */
+
+int __attribute__ ((__strub__ (2), __const__))
+f() {
+  return 0;
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const4.c b/gcc/testsuite/c-c++-common/torture/strub-const4.c
new file mode 100644
index 00000000000..d4cbdaf10f3
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const4.c
@@ -0,0 +1,17 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-const wrapping call, we issue an
+   asm statement to make sure the watermark passed to it is held in memory
+   before the call, and another to make sure it is not assumed to be
+   unchanged.  */
+
+int __attribute__ ((__strub__ (2)))
+#if ! __OPTIMIZE__
+__attribute__ ((__const__))
+#endif
+f() {
+  return 0;
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data5.c b/gcc/testsuite/c-c++-common/torture/strub-data5.c
index 251790d4bbb..07e25af9c53 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data5.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data5.c
@@ -1,15 +1,13 @@
 /* { dg-do compile } */
 /* { dg-options "-fstrub=default -Werror" } */
 
-/* The pointer itself is a strub variable, that would enable internal strubbing
-   if its value was used.  Here, it's only overwritten, so no strub.  */
 typedef int __attribute__ ((__strub__)) strub_int;
 strub_int *ptr;
 
 int *f () {
-  return ptr; /* { dg-warn "incompatible" } */
+  return ptr; /* { dg-warning "incompatible" } */
 }
 
 strub_int *g () {
-  return f (); /* { dg-warn "incompatible" } */
+  return f (); /* { dg-warning "incompatible" } */
 }
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure1.c b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
new file mode 100644
index 00000000000..cb223da6efc
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub pure function call, we issue an asm statement
+   to make sure the watermark passed to it is not assumed to be unchanged.  */
+
+int __attribute__ ((__strub__, __pure__))
+f() {
+  static int i; /* Stop it from being detected as const.  */
+  return i;
+}
+
+int
+g() {
+  return f();
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure2.c b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
new file mode 100644
index 00000000000..67d1434b1f8
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-pure function call, we issue an asm
+   statement to make sure the watermark passed to it is not assumed to be
+   unchanged.  */
+
+int __attribute__ ((__strub__))
+#if ! __OPTIMIZE__ /* At -O0, implicit pure detection doesn't run.  */
+__attribute__ ((__pure__))
+#endif
+f() {
+  static int i; /* Stop it from being detected as const.  */
+  return i;
+}
+
+int
+g() {
+  return f();
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure3.c b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
new file mode 100644
index 00000000000..59f02ea901f
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub pure wrapping call, we issue an asm statement
+   to make sure the watermark passed to it is not assumed to be unchanged.  */
+
+int __attribute__ ((__strub__ (2), __pure__))
+f() {
+  static int i; /* Stop it from being detected as const.  */
+  return i;
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure4.c b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
new file mode 100644
index 00000000000..973e909217d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
@@ -0,0 +1,17 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-pure wrapping call, we issue an asm
+   statement to make sure the watermark passed to it is not assumed to be
+   unchanged.  */
+
+int __attribute__ ((__strub__ (2)))
+#if ! __OPTIMIZE__ /* At -O0, implicit pure detection doesn't run.  */
+__attribute__ ((__pure__))
+#endif
+f() {
+  static int i; /* Stop it from being detected as const.  */
+  return i;
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run1.c b/gcc/testsuite/c-c++-common/torture/strub-run1.c
new file mode 100644
index 00000000000..828a4cc2998
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run1.c
@@ -0,0 +1,85 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default" } */
+
+/* Check that a non-strub function leaves a string behind in the stack, and that
+   equivalent strub functions don't.  */
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+static inline __attribute__ ((__always_inline__, __strub__ (3)))
+char *
+leak_string (void)
+{
+  /* We use this variable to avoid any stack red zone.  Stack scrubbing covers
+     it, but __builtin_stack_address, that we take as a reference, doesn't, so
+     if e.g. callable() were to store the string in the red zone, we wouldn't
+     find it because it would be outside the range we searched.  */
+  typedef void __attribute__ ((__strub__ (3))) callable_t (char *);
+  callable_t *f = 0;
+
+  char s[sizeof (test_string)];
+  __builtin_strcpy (s, test_string);
+  asm ("" : "+m" (s), "+r" (f));
+
+  if (__builtin_expect (!f, 1))
+    return __builtin_stack_address ();
+
+  f (s);
+  return 0;
+}
+
+static inline __attribute__ ((__always_inline__))
+int
+look_for_string (char *e)
+{
+  char *p = __builtin_stack_address ();
+
+  if (p == e)
+    __builtin_abort ();
+
+  if (p > e)
+    {
+      char *q = p;
+      p = e;
+      e = q;
+    }
+
+  for (char *re = e - sizeof (test_string); p < re; p++)
+    for (int i = 0; p[i] == test_string[i]; i++)
+      if (i == sizeof (test_string) - 1)
+	return i;
+
+  return 0;
+}
+
+static __attribute__ ((__noinline__, __noclone__))
+char *
+callable ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ (1)))
+char *
+at_calls ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ (2)))
+char *
+internal ()
+{
+  return leak_string ();
+}
+
+int main ()
+{
+  if (!look_for_string (callable ()))
+    __builtin_abort ();
+  if (look_for_string (at_calls ()))
+    __builtin_abort ();
+  if (look_for_string (internal ()))
+    __builtin_abort ();
+  __builtin_exit (0);
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run2.c b/gcc/testsuite/c-c++-common/torture/strub-run2.c
new file mode 100644
index 00000000000..5794b694b2d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run2.c
@@ -0,0 +1,75 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default" } */
+
+/* Check that a non-strub function leaves a string behind in the stack, and that
+   equivalent strub functions don't.  */
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+static inline __attribute__ ((__always_inline__, __strub__ (3)))
+char *
+leak_string (void)
+{
+  int len = sizeof (test_string);
+  asm ("" : "+rm" (len));
+  char s[len];
+  __builtin_strcpy (s, test_string);
+  asm ("" : "+m" (s));
+  return __builtin_stack_address ();
+}
+
+static inline __attribute__ ((__always_inline__))
+int
+look_for_string (char *e)
+{
+  char *p = __builtin_stack_address ();
+
+  if (p == e)
+    __builtin_abort ();
+
+  if (p > e)
+    {
+      char *q = p;
+      p = e;
+      e = q;
+    }
+
+  for (char *re = e - sizeof (test_string); p < re; p++)
+    for (int i = 0; p[i] == test_string[i]; i++)
+      if (i == sizeof (test_string) - 1)
+	return i;
+
+  return 0;
+}
+
+static __attribute__ ((__noinline__, __noclone__))
+char *
+callable ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ (1)))
+char *
+at_calls ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ (2)))
+char *
+internal ()
+{
+  return leak_string ();
+}
+
+int main ()
+{
+  if (!look_for_string (callable ()))
+    __builtin_abort ();
+  if (look_for_string (at_calls ()))
+    __builtin_abort ();
+  if (look_for_string (internal ()))
+    __builtin_abort ();
+  __builtin_exit (0);
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run3.c b/gcc/testsuite/c-c++-common/torture/strub-run3.c
new file mode 100644
index 00000000000..7da79055959
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run3.c
@@ -0,0 +1,75 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default" } */
+/* { dg-require-effective-target alloca } */
+
+/* Check that a non-strub function leaves a string behind in the stack, and that
+   equivalent strub functions don't.  */
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+static inline __attribute__ ((__always_inline__, __strub__ (3)))
+char *
+leak_string (void)
+{
+  int len = sizeof (test_string);
+  char *s = __builtin_alloca (len);
+  __builtin_strcpy (s, test_string);
+  asm ("" : "+m" (s));
+  return __builtin_stack_address ();
+}
+
+static inline __attribute__ ((__always_inline__))
+int
+look_for_string (char *e)
+{
+  char *p = __builtin_stack_address ();
+
+  if (p == e)
+    __builtin_abort ();
+
+  if (p > e)
+    {
+      char *q = p;
+      p = e;
+      e = q;
+    }
+
+  for (char *re = e - sizeof (test_string); p < re; p++)
+    for (int i = 0; p[i] == test_string[i]; i++)
+      if (i == sizeof (test_string) - 1)
+	return i;
+
+  return 0;
+}
+
+static __attribute__ ((__noinline__, __noclone__))
+char *
+callable ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ (1)))
+char *
+at_calls ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ (2)))
+char *
+internal ()
+{
+  return leak_string ();
+}
+
+int main ()
+{
+  if (!look_for_string (callable ()))
+    __builtin_abort ();
+  if (look_for_string (at_calls ()))
+    __builtin_abort ();
+  if (look_for_string (internal ()))
+    __builtin_abort ();
+  __builtin_exit (0);
+}
diff --git a/gcc/testsuite/g++.dg/wrappers/strub1.C b/gcc/testsuite/g++.dg/strub-run1.C
similarity index 90%
rename from gcc/testsuite/g++.dg/wrappers/strub1.C
rename to gcc/testsuite/g++.dg/strub-run1.C
index a474a929649..754291eaa01 100644
--- a/gcc/testsuite/g++.dg/wrappers/strub1.C
+++ b/gcc/testsuite/g++.dg/strub-run1.C
@@ -1,4 +1,5 @@
 // { dg-do run }
+// { dg-options "-fstrub=internal" }
 
 // Check that we don't get extra copies.
 
diff --git a/gcc/testsuite/g++.dg/wrappers/strub2.C b/gcc/testsuite/g++.dg/wrappers/strub2.C
deleted file mode 100644
index 25a62166448..00000000000
--- a/gcc/testsuite/g++.dg/wrappers/strub2.C
+++ /dev/null
@@ -1,22 +0,0 @@
-// { dg-do run }
-
-// This doesn't really test anything yet.  We should mark the
-// variables as requiring strubbing, and somehow check that the
-// wrapped functions take the parameter by reference.
-
-struct T {
-  char d[32 * sizeof(void*)];
-};
-
-T foo (T q) { asm ("" : : "m"(q)); return q; }
-T bar (T p) { return foo (p); }
-
-T tmp;
-T tmp2;
-
-int main () {
-  __builtin_memset (&tmp, 0x55, sizeof (tmp));
-  tmp2 = bar (tmp);
-  if (__builtin_memcmp (&tmp, &tmp2, sizeof (tmp)))
-    __builtin_abort ();
-}
diff --git a/gcc/testsuite/g++.dg/wrappers/strub3.C b/gcc/testsuite/g++.dg/wrappers/strub3.C
deleted file mode 100644
index e1b51cd0399..00000000000
--- a/gcc/testsuite/g++.dg/wrappers/strub3.C
+++ /dev/null
@@ -1,22 +0,0 @@
-// { dg-do run }
-
-// This doesn't really test anything yet.  We should mark the
-// variables as requiring strubbing, and somehow check that the
-// wrapped functions take the parameter by reference.
-
-struct T {
-  char d[32 * sizeof(void*)];
-};
-
-static T foo (T q) { asm ("" : : "m"(q)); return q; }
-static T bar (T p) { return foo (p); }
-
-T tmp;
-T tmp2;
-
-int main () {
-  __builtin_memset (&tmp, 0x55, sizeof (tmp));
-  tmp2 = bar (tmp);
-  if (__builtin_memcmp (&tmp, &tmp2, sizeof (tmp)))
-    __builtin_abort ();
-}
diff --git a/gcc/testsuite/g++.dg/wrappers/strub4.C b/gcc/testsuite/g++.dg/wrappers/strub4.C
deleted file mode 100644
index d021fca88e4..00000000000
--- a/gcc/testsuite/g++.dg/wrappers/strub4.C
+++ /dev/null
@@ -1,18 +0,0 @@
-// { dg-do run }
-
-namespace
-{
-  class foo
-  {
-  public:
-    foo();
-  };
-
-  foo::foo() {}
-
-  foo bar;
-}
-
-int main()
-{
-}


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [gcc(refs/users/aoliva/heads/strub)] more tests, red zones, and deferred strubbing
@ 2021-08-05 12:07 Alexandre Oliva
  0 siblings, 0 replies; 6+ messages in thread
From: Alexandre Oliva @ 2021-08-05 12:07 UTC (permalink / raw)
  To: gcc-cvs

https://gcc.gnu.org/g:243fea0d9e30acef7fb3673ebf82fca1815577fe

commit 243fea0d9e30acef7fb3673ebf82fca1815577fe
Author: Alexandre Oliva <oliva@adacore.com>
Date:   Thu Aug 5 08:43:23 2021 -0300

    more tests, red zones, and deferred strubbing

Diff:
---
 gcc/builtins.c                                     |  83 ++++++-
 gcc/ipa-strub.c                                    | 240 +++++++++++++------
 gcc/ipa-strub.h                                    |   1 +
 gcc/testsuite/c-c++-common/strub-O2.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-O2fni.c           |   6 +-
 gcc/testsuite/c-c++-common/strub-O3fni.c           |   4 +-
 gcc/testsuite/c-c++-common/strub-Og.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-Os.c              |   2 +-
 gcc/testsuite/c-c++-common/strub-all1.c            |   4 +-
 gcc/testsuite/c-c++-common/strub-apply1.c          |  15 ++
 gcc/testsuite/c-c++-common/strub-apply2.c          |  12 +
 gcc/testsuite/c-c++-common/strub-apply3.c          |   8 +
 gcc/testsuite/c-c++-common/strub-apply4.c          |  21 ++
 gcc/testsuite/c-c++-common/strub-at-calls1.c       |   4 +-
 gcc/testsuite/c-c++-common/strub-default1.c        |   8 +-
 gcc/testsuite/c-c++-common/strub-internal1.c       |   4 +-
 gcc/testsuite/c-c++-common/strub-parms1.c          |  48 ++++
 gcc/testsuite/c-c++-common/strub-parms2.c          |  36 +++
 gcc/testsuite/c-c++-common/strub-parms3.c          |  58 +++++
 .../c-c++-common/torture/strub-callable1.c         |  13 +
 .../c-c++-common/torture/strub-callable2.c         | 264 +++++++++++++++++++++
 gcc/testsuite/c-c++-common/torture/strub-const1.c  |  18 ++
 gcc/testsuite/c-c++-common/torture/strub-const2.c  |  22 ++
 gcc/testsuite/c-c++-common/torture/strub-const3.c  |  13 +
 gcc/testsuite/c-c++-common/torture/strub-const4.c  |  17 ++
 gcc/testsuite/c-c++-common/torture/strub-data5.c   |   6 +-
 gcc/testsuite/c-c++-common/torture/strub-pure1.c   |  18 ++
 gcc/testsuite/c-c++-common/torture/strub-pure2.c   |  22 ++
 gcc/testsuite/c-c++-common/torture/strub-pure3.c   |  13 +
 gcc/testsuite/c-c++-common/torture/strub-pure4.c   |  17 ++
 gcc/testsuite/c-c++-common/torture/strub-run1.c    |  85 +++++++
 gcc/testsuite/c-c++-common/torture/strub-run2.c    |  75 ++++++
 gcc/testsuite/c-c++-common/torture/strub-run3.c    |  75 ++++++
 .../g++.dg/{wrappers/strub1.C => strub-run1.C}     |   1 +
 gcc/testsuite/g++.dg/wrappers/strub2.C             |  22 --
 gcc/testsuite/g++.dg/wrappers/strub3.C             |  22 --
 gcc/testsuite/g++.dg/wrappers/strub4.C             |  18 --
 37 files changed, 1117 insertions(+), 162 deletions(-)

diff --git a/gcc/builtins.c b/gcc/builtins.c
index f387d93974f..813d02a67d2 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -80,6 +80,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "attr-fnspec.h"
 #include "demangle.h"
 #include "gimple-range.h"
+#include "ipa-strub.h"
 
 struct target_builtins default_target_builtins;
 #if SWITCHABLE_TARGET
@@ -7926,7 +7927,23 @@ expand_builtin_strub_enter (tree exp)
   if (optimize < 1 || flag_no_inline)
     return NULL_RTX;
 
-  rtx stktop = expand_builtin_stack_address ();
+  rtx stktop = NULL_RTX;
+
+#ifdef RED_ZONE_SIZE || 1
+  if (tree wmptr = (optimize
+		    ? find_watermark_parm (current_function_decl)
+		    : NULL_TREE))
+    {
+      tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+      tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+				 build_int_cst (TREE_TYPE (wmptr), 0));
+      rtx wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+      stktop = force_reg (ptr_mode, wmark);
+    }
+#endif
+
+  if (!stktop)
+    stktop = expand_builtin_stack_address ();
 
   tree wmptr = CALL_EXPR_ARG (exp, 0);
   tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
@@ -7952,6 +7969,52 @@ expand_builtin_strub_update (tree exp)
 
   rtx stktop = expand_builtin_stack_address ();
 
+#ifdef RED_ZONE_SIZE
+  /* Here's how the strub enter, update and leave functions deal with red zones.
+
+     If it weren't for red zones, update, called from within a strub context,
+     would bump the watermark to the top of the stack.  Enter and leave, running
+     in the caller, would use the caller's top of stack address both to
+     initialize the watermark passed to the callee, and to start strubbing the
+     stack afterwards.
+
+     Ideally, we'd update the watermark so as to cover the used amount of red
+     zone, and strub starting at the caller's other end of the (presumably
+     unused) red zone.  Normally, only leaf functions use the red zone, but at
+     this point we can't tell whether a function is a leaf, nor can we tell how
+     much of the red zone it uses.  Furthermore, some strub contexts may have
+     been inlined so that update and leave are called from the same stack frame,
+     and the strub builtins may all have been inlined, turning a strub function
+     into a leaf.
+
+     So cleaning the range from the caller's stack pointer (one end of the red
+     zone) to the (potentially inlined) callee's (other end of the) red zone
+     could scribble over the caller's own red zone.
+
+     We avoid this possibility by arranging for callers that are strub contexts
+     to use their own watermark as the strub starting point.  So, if A calls B,
+     and B calls C, B will tell A to strub up to the end of B's red zone, and
+     will strub itself only the part of C's stack frame and red zone that
+     doesn't overlap with B's.  With that, we don't need to know who's leaf and
+     who isn't: inlined calls will shrink their strub window to zero, each
+     remaining call will strub some portion of the stack, and eventually the
+     strub context will return to a caller that isn't a strub context itself,
+     that will therefore use its own stack pointer as the strub starting point.
+     It's not a leaf, because strub contexts can't be inlined into non-strub
+     contexts, so it doesn't use the red zone, and it will therefore correctly
+     strub up the callee's stack frame up to the end of the callee's red zone.
+     Neat!  */
+  if (true /* (flags_from_decl_or_type (current_function_decl) & ECF_LEAF) */)
+    {
+      poly_int64 red_zone_size = RED_ZONE_SIZE;
+#if STACK_GROWS_DOWNWARD
+      red_zone_size = -red_zone_size;
+#endif
+      stktop = plus_constant (ptr_mode, stktop, red_zone_size);
+      stktop = force_reg (ptr_mode, stktop);
+    }
+#endif
+
   tree wmptr = CALL_EXPR_ARG (exp, 0);
   tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
   tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
@@ -7982,7 +8045,23 @@ expand_builtin_strub_leave (tree exp)
   if (optimize < 2 || flag_no_inline)
     return NULL_RTX;
 
-  rtx stktop = expand_builtin_stack_address ();
+  rtx stktop = NULL_RTX;
+
+#ifdef RED_ZONE_SIZE || 1
+  if (tree wmptr = (optimize
+		    ? find_watermark_parm (current_function_decl)
+		    : NULL_TREE))
+    {
+      tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
+      tree wmtree = fold_build2 (MEM_REF, wmtype, wmptr,
+				 build_int_cst (TREE_TYPE (wmptr), 0));
+      rtx wmark = expand_expr (wmtree, NULL_RTX, ptr_mode, EXPAND_MEMORY);
+      stktop = force_reg (ptr_mode, wmark);
+    }
+#endif
+
+  if (!stktop)
+    stktop = expand_builtin_stack_address ();
 
   tree wmptr = CALL_EXPR_ARG (exp, 0);
   tree wmtype = TREE_TYPE (TREE_TYPE (wmptr));
diff --git a/gcc/ipa-strub.c b/gcc/ipa-strub.c
index e302f0ec1c5..ee9cab3318c 100644
--- a/gcc/ipa-strub.c
+++ b/gcc/ipa-strub.c
@@ -210,10 +210,16 @@ get_strub_mode_from_attr (tree strub_attr, bool var_p = false)
   return mode;
 }
 
+static enum strub_mode
+get_strub_mode_from_decl (tree fndecl)
+{
+  return get_strub_mode_from_attr (get_strub_attr_from_decl (fndecl));
+}
+
 static enum strub_mode
 get_strub_mode (cgraph_node *node)
 {
-  return get_strub_mode_from_attr (get_strub_attr_from_decl (node->decl));
+  return get_strub_mode_from_decl (node->decl);
 }
 
 static enum strub_mode
@@ -263,7 +269,7 @@ calls_builtin_apply_args_p (cgraph_node *node, bool report = false)
 	break;
 
       sorry_at (gimple_location (e->call_stmt),
-		"at-calls strub does not support call to %qD",
+		"at-calls %<strub%> does not support call to %qD",
 		cdecl);
     }
 
@@ -292,7 +298,8 @@ can_strub_p (cgraph_node *node, bool report = false)
 	return result;
 
       sorry_at (DECL_SOURCE_LOCATION (node->decl),
-		"%qD is not eligible for strub because of attribute %<noipa%>",
+		"%qD is not eligible for %<strub%>"
+		" because of attribute %<noipa%>",
 		node->decl);
     }
 
@@ -352,7 +359,7 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 	return result;
 
       sorry_at (DECL_SOURCE_LOCATION (node->decl),
-		"%qD is not eligible for internal strub"
+		"%qD is not eligible for internal %<strub%>"
 		" because of attribute %<noclone%>",
 		node->decl);
     }
@@ -372,7 +379,7 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 	return result;
 
       sorry_at (gimple_location (e->call_stmt),
-		"internal strub does not support call to %qD",
+		"internal %<strub%> does not support call to %qD",
 		cdecl);
     }
 
@@ -411,7 +418,7 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 	      return result;
 
 	    sorry_at (gimple_location (label_stmt),
-		      "internal strub does not support user labels");
+		      "internal %<strub%> does not support user labels");
 	  }
     }
 
@@ -425,16 +432,16 @@ can_strub_internally_p (cgraph_node *node, bool report = false)
 	return result;
 
       sorry_at (DECL_SOURCE_LOCATION (node->decl),
-		"%qD has too many arguments for internal strub",
+		"%qD has too many arguments for internal %<strub%>",
 		node->decl);
     }
 
-  if (result)
-    /* Since we're not changing the function identity proper, just
-       moving its full implementation, we *could* disable
-       fun->cannot_be_copied_reason and/or temporarily drop a noclone
-       attribute.  */
-    gcc_checking_assert (tree_versionable_function_p (node->decl));
+  /* Since we're not changing the function identity proper, just
+     moving its full implementation, we *could* disable
+     fun->cannot_be_copied_reason and/or temporarily drop a noclone
+     attribute.  */
+  gcc_checking_assert (!result || !node->has_gimple_body_p ()
+		       || tree_versionable_function_p (node->decl));
 
   return result;
 }
@@ -489,8 +496,22 @@ strub_callable_builtin_p (cgraph_node *node)
     case BUILT_IN_NONE:
       gcc_unreachable ();
 
-      /* ??? Make all builtins callable.  We wish to make any builtin call the
-	 compiler might introduce on its own callable.  Anything that is
+      /* This temporarily allocates stack for the call, and we can't reasonably
+	 update the watermark for that.  Besides, we don't check the actual call
+	 target, nor its signature, and it seems to be overkill to as much as
+	 try to do so.  */
+    case BUILT_IN_APPLY:
+      return false;
+
+      /* Conversely, this shouldn't be called from within strub contexts, since
+	 the caller may have had its signature modified.  STRUB_INTERNAL is ok,
+	 the call will remain in the STRUB_WRAPPER, and removed from the
+	 STRUB_WRAPPED clone.  */
+    case BUILT_IN_APPLY_ARGS:
+      return false;
+
+      /* ??? Make all other builtins callable.  We wish to make any builtin call
+	 the compiler might introduce on its own callable.  Anything that is
 	 predictable enough as to be known not to allow stack data that should
 	 be strubbed to unintentionally escape to non-strub contexts can be
 	 allowed, and pretty much every builtin appears to fit this description.
@@ -698,7 +719,8 @@ compute_strub_mode (cgraph_node *node, tree strub_attr)
 	{
 	  gcc_checking_assert (analyze_body);
 	  error_at (DECL_SOURCE_LOCATION (node->decl),
-		    "%qD requires strub, but no viable strub mode was found",
+		    "%qD requires %<strub%>,"
+		    " but no viable %<strub%> mode was found",
 		    node->decl);
 	  break;
 	}
@@ -751,7 +773,7 @@ set_strub_mode_to (cgraph_node *node, enum strub_mode mode)
 	       && mode == STRUB_INLINABLE))
 	{
 	  error_at (DECL_SOURCE_LOCATION (node->decl),
-		    "strub mode %i selected for %qD, when %i was requested",
+		    "%<strub%> mode %i selected for %qD, when %i was requested",
 		    (int) mode, node->decl,
 		    (int) get_strub_mode_from_attr (attr));
 	  if (node->alias)
@@ -979,7 +1001,7 @@ verify_strub ()
 	if (callee_mode == STRUB_DISABLED
 	    || callee_mode == STRUB_INTERNAL)
 	  error_at (gimple_location (e->call_stmt),
-		    "indirect non-strub call in strub context %qD",
+		    "indirect non-%<strub%> call in %<strub%> context %qD",
 		    node->decl);
       }
 
@@ -987,9 +1009,22 @@ verify_strub ()
       {
 	gcc_checking_assert (!e->indirect_unknown_callee);
 	if (!strub_callable_from_p (e->callee, node))
-	  error_at (gimple_location (e->call_stmt),
-		    "calling non-strub %qD in strub context %qD",
-		    e->callee->decl, node->decl);
+	  {
+	    if (get_strub_mode (e->callee) == STRUB_INLINABLE)
+	      error_at (gimple_location (e->call_stmt),
+			"calling %<always_inline%> %<strub%> %qD"
+			" in non-%<strub%> context %qD",
+			e->callee->decl, node->decl);
+	    else if (fndecl_built_in_p (e->callee->decl, BUILT_IN_APPLY_ARGS)
+		     && get_strub_mode (node) == STRUB_INTERNAL)
+	      /* This is ok, it will be kept in the STRUB_WRAPPER, and removed
+		 from the STRUB_WRAPPED's strub context.  */
+	      continue;
+	    else
+	      error_at (gimple_location (e->call_stmt),
+			"calling non-%<strub%> %qD in %<strub%> context %qD",
+			e->callee->decl, node->decl);
+	  }
       }
   }
 
@@ -1067,27 +1102,25 @@ public:
 
   /* Use a distinct ptr_type_node to denote the watermark, so that we can
      recognize it in arg lists and avoid modifying types twice.  */
-  DEF_TYPE (wmt, build_distinct_type_copy (ptr_type_node))
+  DEF_TYPE (wmt, build_variant_type_copy (ptr_type_node))
 
-  DEF_TYPE (pwmt, build_pointer_type (get_wmt ()))
+  DEF_TYPE (pwmt, build_reference_type (get_wmt ()))
 
   DEF_TYPE (qpwmt,
 	    build_qualified_type (get_pwmt (),
 				  TYPE_QUAL_RESTRICT
-				  | TYPE_QUAL_CONST))
-
-  DEF_TYPE (pptr, build_pointer_type (ptr_type_node))
+				  /* | TYPE_QUAL_CONST */))
 
-  DEF_TYPE (qpptr,
-	    build_qualified_type (get_pptr (),
+  DEF_TYPE (qptr,
+	    build_qualified_type (ptr_type_node,
 				  TYPE_QUAL_RESTRICT
 				  | TYPE_QUAL_CONST))
 
   DEF_TYPE (qpvalst,
-	    build_qualified_type (build_pointer_type
+	    build_qualified_type (build_reference_type
 				  (va_list_type_node),
 				  TYPE_QUAL_RESTRICT
-				  | TYPE_QUAL_CONST))
+				  /* | TYPE_QUAL_CONST */))
 
 #undef DEF_TYPE
 
@@ -1159,7 +1192,7 @@ public:
 
   DEF_IDENT (watermark_ptr)
   DEF_IDENT (va_list_ptr)
-  DEF_IDENT (apply_args_ptr)
+  DEF_IDENT (apply_args)
 
 #undef DEF_IDENT
 
@@ -1493,6 +1526,39 @@ ipa_strub_set_mode_for_new_functions ()
   last_cgraph_order = symtab->order;
 }
 
+/* Return the PARM_DECL of the incoming watermark pointer, if there is one.  */
+tree
+find_watermark_parm (tree fndecl)
+{
+  switch (get_strub_mode_from_decl (fndecl))
+    {
+    case STRUB_WRAPPED:
+    case STRUB_AT_CALLS:
+    case STRUB_AT_CALLS_OPT:
+      break;
+
+    case STRUB_INTERNAL:
+    case STRUB_WRAPPER:
+    case STRUB_CALLABLE:
+    case STRUB_DISABLED:
+    case STRUB_INLINABLE:
+      return NULL_TREE;
+
+    default:
+      gcc_unreachable ();
+    }
+
+  for (tree parm = DECL_ARGUMENTS (fndecl); parm; parm = DECL_CHAIN (parm))
+    if (TREE_TYPE (parm) == pass_ipa_strub::get_qpwmt ())
+      {
+	gcc_checking_assert (DECL_NAME (parm)
+			     == pass_ipa_strub::get_watermark_ptr ());
+	return parm;
+      }
+
+  gcc_unreachable ();
+}
+
 /* Adjust a STRUB_AT_CALLS function TYPE, adding a watermark pointer if it
    hasn't been added yet.  Return the named argument count.  */
 int
@@ -1507,7 +1573,7 @@ pass_ipa_strub::adjust_at_calls_type (tree type)
   tree qpwmptrt = get_qpwmt ();
   while (*tlist && TREE_VALUE (*tlist) != void_type_node)
     {
-      /* The type has alreayd been adjusted.  */
+      /* The type has already been adjusted.  */
       if (TREE_VALUE (*tlist) == qpwmptrt)
 	return named_args;
       named_args++;
@@ -1581,22 +1647,37 @@ pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
 			 && (TREE_TYPE (gimple_call_arg (ocall, named_args))
 			     == get_pwmt ())));
 
-  /* ??? If it's a (tail?) call within a strub context, maybe pass on
-     the strub watermark instead of wrapping the call.  */
-
-  /* Initialize the watermark before the call.  */
-  tree swm = create_tmp_var (get_wmt (), ".strub.watermark");
-  TREE_ADDRESSABLE (swm) = true;
-  tree swmp = build1 (ADDR_EXPR, get_pwmt (), swm);
+  /* If we're already within a strub context, pass on the incoming watermark
+     pointer, and omit the enter and leave calls around the modified call.  */
+  tree swm, swmp = (false && /* This is disabled for now, to give the
+				red-zone-capable builtin expanders more thorough
+				testing.  */
+		    (optimize_size || optimize > 2)
+		    ? find_watermark_parm (e->caller->decl)
+		    : NULL_TREE);
+  bool omit_own_watermark = swmp;
+  if (omit_own_watermark)
+    swm = build2 (MEM_REF,
+		  TREE_TYPE (TREE_TYPE (swmp)),
+		  swmp,
+		  build_int_cst (TREE_TYPE (swmp), 0));
+  else
+    {
+      swm = create_tmp_var (get_wmt (), ".strub.watermark");
+      TREE_ADDRESSABLE (swm) = true;
+      swmp = build1 (ADDR_EXPR, get_pwmt (), swm);
 
-  tree enter = get_enter ();
-  gcall *stptr = gimple_build_call (enter, 1,
-				    unshare_expr (swmp));
-  gsi_insert_before (&gsi, stptr, GSI_SAME_STMT);
+      /* Initialize the watermark before the call.  */
+      tree enter = get_enter ();
+      gcall *stptr = gimple_build_call (enter, 1,
+					unshare_expr (swmp));
+      gsi_insert_before (&gsi, stptr, GSI_SAME_STMT);
 #if !IMPLICIT_CGRAPH_EDGES
-  e->caller->create_edge (cgraph_node::get_create (enter),
-			  stptr, gsi_bb (gsi)->count, false);
+      e->caller->create_edge (cgraph_node::get_create (enter),
+			      stptr, gsi_bb (gsi)->count, false);
 #endif
+    }
+
 
   /* Replace the call with one that passes the swmp argument first.  */
   gcall *wrcall;
@@ -1674,12 +1755,12 @@ pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
 		       build_tree_list
 		       (build_tree_list
 			(NULL_TREE, build_string (2, "=m")),
-			swm));
+			unshare_expr (swm)));
 	vec_safe_push (inputs,
 		       build_tree_list
 		       (build_tree_list
 			(NULL_TREE, build_string (1, "m")),
-			swm));
+			unshare_expr (swm)));
 	gasm *forcemod = gimple_build_asm_vec ("", inputs, outputs,
 					       NULL, NULL);
 	gimple_seq_add_stmt (&seq, forcemod);
@@ -1693,7 +1774,7 @@ pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
 			   build_tree_list
 			   (build_tree_list
 			    (NULL_TREE, build_string (1, "m")),
-			    swm));
+			    unshare_expr (swm)));
 	    gasm *force_store = gimple_build_asm_vec ("", inputs, NULL,
 						      NULL, NULL);
 	    gsi_insert_before (&gsi, force_store, GSI_SAME_STMT);
@@ -1701,17 +1782,21 @@ pass_ipa_strub::adjust_at_calls_call (cgraph_edge *e, int named_args)
       }
 #endif
 
-    gcall *sleave = gimple_build_call (get_leave (), 1,
-				       unshare_expr (swmp));
-    gimple_seq_add_stmt (&seq, sleave);
+    if (!omit_own_watermark)
+      {
+	gcall *sleave = gimple_build_call (get_leave (), 1,
+					   unshare_expr (swmp));
+	gimple_seq_add_stmt (&seq, sleave);
 
-    gassign *clobber = gimple_build_assign (swm,
-					    build_clobber
-					    (TREE_TYPE (swm)));
-    gimple_seq_add_stmt (&seq, clobber);
+	gassign *clobber = gimple_build_assign (swm,
+						build_clobber
+						(TREE_TYPE (swm)));
+	gimple_seq_add_stmt (&seq, clobber);
+      }
   }
 
-  gsi_insert_finally_seq_after_call (gsi, seq);
+  if (seq)
+    gsi_insert_finally_seq_after_call (gsi, seq);
 }
 
 unsigned int
@@ -1909,7 +1994,7 @@ pass_ipa_strub::execute (function *)
     if (!DECL_STRUCT_FUNCTION (onode->decl))
       {
 	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"not splitting struct-less function %qD for stack scrubbing",
+		"not splitting struct-less function %qD for %<strub%>",
 		onode->decl);
 	continue;
       }
@@ -1917,7 +2002,7 @@ pass_ipa_strub::execute (function *)
     if (!onode->lowered)
       {
 	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"not splitting non-lowered function %qD for stack scrubbing",
+		"not splitting non-lowered function %qD for %<strub%>",
 		onode->decl);
 	continue;
       }
@@ -1929,7 +2014,7 @@ pass_ipa_strub::execute (function *)
     if (!tree_versionable_function_p (onode->decl))
       {
 	inform (DECL_SOURCE_LOCATION (onode->decl),
-		"%qD cannot be split for stack scrubbing",
+		"%qD cannot be split for %<strub%>",
 		onode->decl);
 	continue;
       }
@@ -1956,7 +2041,7 @@ pass_ipa_strub::execute (function *)
 	{
 	  ipa_adjusted_param aaadj = {};
 	  aaadj.op = IPA_PARAM_OP_NEW;
-	  aaadj.type = get_qpptr ();
+	  aaadj.type = get_qptr ();
 	  vec_safe_push (nparms, aaadj);
 	}
 
@@ -1982,7 +2067,7 @@ pass_ipa_strub::execute (function *)
     if (!nnode)
       {
 	error_at (DECL_SOURCE_LOCATION (onode->decl),
-		  "failed to split %qD for stack scrubbing",
+		  "failed to split %qD for %<strub%>",
 		  onode->decl);
 	continue;
       }
@@ -2181,8 +2266,8 @@ pass_ipa_strub::execute (function *)
 	     only of reading because const/pure.  */
 	  if (apply_args)
 	    {
-	      nspec[curlen++] = (no_writes_p ? 'r' : '.');
-	      nspec[curlen++] = (no_writes_p ? 't' : ' ');
+	      nspec[curlen++] = 'r';
+	      nspec[curlen++] = ' ';
 	    }
 	  if (is_stdarg)
 	    {
@@ -2485,24 +2570,24 @@ pass_ipa_strub::execute (function *)
     }
 
     {
-      tree aaptr = NULL_TREE;
+      tree aaval = NULL_TREE;
       tree vaptr = NULL_TREE;
       tree wmptr = NULL_TREE;
       for (tree arg = DECL_ARGUMENTS (nnode->decl); arg; arg = DECL_CHAIN (arg))
 	{
-	  aaptr = vaptr;
+	  aaval = vaptr;
 	  vaptr = wmptr;
 	  wmptr = arg;
 	}
 
       if (!apply_args)
-	aaptr = NULL_TREE;
+	aaval = NULL_TREE;
       /* The trailing args are [apply_args], [va_list_ptr], and
 	 watermark.  If we don't have a va_list_ptr, the penultimate
 	 argument is apply_args.
        */
       else if (!is_stdarg)
-	aaptr = vaptr;
+	aaval = vaptr;
 
       if (!is_stdarg)
 	vaptr = NULL_TREE;
@@ -2522,10 +2607,10 @@ pass_ipa_strub::execute (function *)
 
       if (apply_args)
 	{
-	  DECL_NAME (aaptr) = get_apply_args_ptr ();
-	  DECL_ARTIFICIAL (aaptr) = 1;
-	  DECL_IGNORED_P (aaptr) = 1;
-	  TREE_USED (aaptr) = 1;
+	  DECL_NAME (aaval) = get_apply_args ();
+	  DECL_ARTIFICIAL (aaval) = 1;
+	  DECL_IGNORED_P (aaval) = 1;
+	  TREE_USED (aaval) = 1;
 	}
 
       push_cfun (DECL_STRUCT_FUNCTION (nnode->decl));
@@ -2582,9 +2667,10 @@ pass_ipa_strub::execute (function *)
 	    else if (fndecl && is_stdarg
 		     && fndecl_built_in_p (fndecl, BUILT_IN_VA_START))
 	      {
-		if (builtin_decl_explicit (BUILT_IN_VA_START) != fndecl)
-		  sorry_at (gimple_location (call),
-			    "nonstandard stdarg conventions");
+		/* Using a non-default stdarg ABI makes the function ineligible
+		   for internal strub.  */
+		gcc_checking_assert (builtin_decl_explicit (BUILT_IN_VA_START)
+				     == fndecl);
 		tree bvacopy = builtin_decl_explicit (BUILT_IN_VA_COPY);
 		gimple_call_set_fndecl (call, bvacopy);
 		tree arg = vaptr;
@@ -2607,7 +2693,9 @@ pass_ipa_strub::execute (function *)
 		     && fndecl_built_in_p (fndecl, BUILT_IN_APPLY_ARGS))
 	      {
 		tree lhs = gimple_call_lhs (call);
-		gassign *assign = gimple_build_assign (lhs, aaptr);
+		gimple *assign = (lhs
+				  ? gimple_build_assign (lhs, aaval)
+				  : gimple_build_nop ());
 		gsi_replace (&gsi, assign, true);
 		cgraph_edge::remove (e);
 	      }
@@ -2650,7 +2738,7 @@ pass_ipa_strub::execute (function *)
 
       if (apply_args)
 	{
-	  tree aalst = create_tmp_var (ptr_type_node, ".strub.appargs");
+	  tree aalst = create_tmp_var (ptr_type_node, ".strub.apply_args");
 	  tree bappargs = builtin_decl_explicit (BUILT_IN_APPLY_ARGS);
 	  gcall *appargs = gimple_build_call (bappargs, 0);
 	  gimple_call_set_lhs (appargs, aalst);
diff --git a/gcc/ipa-strub.h b/gcc/ipa-strub.h
index cadbca5002a..ea8ee857e8e 100644
--- a/gcc/ipa-strub.h
+++ b/gcc/ipa-strub.h
@@ -23,3 +23,4 @@ along with GCC; see the file COPYING3.  If not see
    doesn't have to be called directly by CALLER, but the returned
    value says nothing about intervening functions.  */
 extern bool strub_inlinable_p (cgraph_node *callee, cgraph_node *caller);
+extern tree find_watermark_parm (tree fndecl);
diff --git a/gcc/testsuite/c-c++-common/strub-O2.c b/gcc/testsuite/c-c++-common/strub-O2.c
index 9cc39e763b1..7848c46d179 100644
--- a/gcc/testsuite/c-c++-common/strub-O2.c
+++ b/gcc/testsuite/c-c++-common/strub-O2.c
@@ -13,4 +13,4 @@ int f() {
 /* { dg-final { scan-rtl-dump-not "strub_enter" "expand" } } */
 /* { dg-final { scan-rtl-dump-not "strub_update" "expand" } } */
 /* { dg-final { scan-rtl-dump "strub_leave" "expand" } } */
-/* { dg-final { scan-rtl-dump "\n\[(\]call_insn\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
+/* { dg-final { scan-rtl-dump "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-O2fni.c b/gcc/testsuite/c-c++-common/strub-O2fni.c
index 51cae845d5f..85a8f76785e 100644
--- a/gcc/testsuite/c-c++-common/strub-O2fni.c
+++ b/gcc/testsuite/c-c++-common/strub-O2fni.c
@@ -1,7 +1,7 @@
 /* { dg-do compile } */
-/* { dg-options "-O2 -fstrub=default -fdump-rtl-expand -fno-inline-functions" } */
+/* { dg-options "-O2 -fstrub=default -fdump-rtl-expand -fno-inline" } */
 
-/* With -fno-inline-functions, none of the strub builtins are inlined.  */
+/* With -fno-inline, none of the strub builtins are inlined.  */
 
 int __attribute__ ((__strub__)) var;
 
@@ -12,4 +12,4 @@ int f() {
 /* { dg-final { scan-rtl-dump "strub_enter" "expand" } } */
 /* { dg-final { scan-rtl-dump "strub_update" "expand" } } */
 /* { dg-final { scan-rtl-dump "strub_leave" "expand" } } */
-/* { dg-final { scan-rtl-dump-not "\n\[(\]call_insn\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
+/* { dg-final { scan-rtl-dump-not "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-O3fni.c b/gcc/testsuite/c-c++-common/strub-O3fni.c
index 8f67b613be8..a2eedfd96b2 100644
--- a/gcc/testsuite/c-c++-common/strub-O3fni.c
+++ b/gcc/testsuite/c-c++-common/strub-O3fni.c
@@ -1,7 +1,7 @@
 /* { dg-do compile } */
 /* { dg-options "-O3 -fstrub=default -fdump-rtl-expand -fno-inline" } */
 
-/* With -fno-inline-functions, none of the strub builtins are inlined.  */
+/* With -fno-inline, none of the strub builtins are inlined.  */
 
 int __attribute__ ((__strub__)) var;
 
@@ -12,4 +12,4 @@ int f() {
 /* { dg-final { scan-rtl-dump "strub_enter" "expand" } } */
 /* { dg-final { scan-rtl-dump "strub_update" "expand" } } */
 /* { dg-final { scan-rtl-dump "strub_leave" "expand" } } */
-/* { dg-final { scan-rtl-dump-not "\n\[(\]call_insn\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
+/* { dg-final { scan-rtl-dump-not "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-Og.c b/gcc/testsuite/c-c++-common/strub-Og.c
index 6f60349573f..e5cb1f60541 100644
--- a/gcc/testsuite/c-c++-common/strub-Og.c
+++ b/gcc/testsuite/c-c++-common/strub-Og.c
@@ -13,4 +13,4 @@ int f() {
 /* { dg-final { scan-rtl-dump-not "strub_enter" "expand" } } */
 /* { dg-final { scan-rtl-dump "strub_update" "expand" } } */
 /* { dg-final { scan-rtl-dump "strub_leave" "expand" } } */
-/* { dg-final { scan-rtl-dump-not "\n\[(\]call_insn\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
+/* { dg-final { scan-rtl-dump-not "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-Os.c b/gcc/testsuite/c-c++-common/strub-Os.c
index 5d1c08a7528..194aacc2c05 100644
--- a/gcc/testsuite/c-c++-common/strub-Os.c
+++ b/gcc/testsuite/c-c++-common/strub-Os.c
@@ -15,4 +15,4 @@ int f() {
 /* { dg-final { scan-rtl-dump-not "strub_enter" "expand" } } */
 /* { dg-final { scan-rtl-dump-not "strub_update" "expand" } } */
 /* { dg-final { scan-rtl-dump "strub_leave" "expand" } } */
-/* { dg-final { scan-rtl-dump-not "\n\[(\]call_insn\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
+/* { dg-final { scan-rtl-dump-not "\[(\]call\[^\n\]*strub_leave.*\n\[(\]code_label" "expand" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-all1.c b/gcc/testsuite/c-c++-common/strub-all1.c
index 54daf84656c..46e84bf6560 100644
--- a/gcc/testsuite/c-c++-common/strub-all1.c
+++ b/gcc/testsuite/c-c++-common/strub-all1.c
@@ -3,9 +3,9 @@
 
 /* h becomes STRUB_CALLABLE, rather than STRUB_INLINABLE, because of the
    strub-enabling -fstrub flag, and gets inlined before pass_ipa_strub.  */
-static void
+static inline void
 __attribute__ ((__always_inline__))
-h() {  /* { dg-warning "might not be inlinable" } */
+h() {
 }
 
 /* g becomes STRUB_AT_CALLS, because of the flag.  */
diff --git a/gcc/testsuite/c-c++-common/strub-apply1.c b/gcc/testsuite/c-c++-common/strub-apply1.c
new file mode 100644
index 00000000000..f180b17f30e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply1.c
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default" } */
+
+void __attribute__ ((__strub__ (3)))
+apply_function (void *args)
+{
+  __builtin_apply (0, args, 0);
+}
+
+void __attribute__ ((__strub__ (2)))
+apply_args (int i, int j, double d)
+{
+  void *args = __builtin_apply_args ();
+  apply_function (args);
+}
diff --git a/gcc/testsuite/c-c++-common/strub-apply2.c b/gcc/testsuite/c-c++-common/strub-apply2.c
new file mode 100644
index 00000000000..379a54b73b7
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply2.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default" } */
+
+extern void __attribute__ ((__strub__))
+apply_function (void *args);
+
+void __attribute__ ((__strub__))
+apply_args (int i, int j, double d) /* { dg-error "selected" } */
+{
+  void *args = __builtin_apply_args (); /* { dg-message "does not support" } */
+  apply_function (args);
+}
diff --git a/gcc/testsuite/c-c++-common/strub-apply3.c b/gcc/testsuite/c-c++-common/strub-apply3.c
new file mode 100644
index 00000000000..9b4786be698
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply3.c
@@ -0,0 +1,8 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default" } */
+
+void __attribute__ ((__strub__))
+apply_function (void *args)
+{
+  __builtin_apply (0, args, 0); /* { dg-error "in .strub. context" } */
+}
diff --git a/gcc/testsuite/c-c++-common/strub-apply4.c b/gcc/testsuite/c-c++-common/strub-apply4.c
new file mode 100644
index 00000000000..409f747743e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-apply4.c
@@ -0,0 +1,21 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fstrub=default -fdump-ipa-strubm" } */
+
+/* Check that implicit enabling of strub mode selects internal strub when the
+   function uses __builtin_apply_args, that prevents the optimization to
+   at-calls mode.  */
+
+int __attribute__ ((__strub__)) var;
+
+static inline void
+apply_args (int i, int j, double d)
+{
+  var++;
+  __builtin_apply_args ();
+}
+
+void f() {
+  apply_args (1, 2, 3);
+}
+
+/* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 1 "strubm" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-at-calls1.c b/gcc/testsuite/c-c++-common/strub-at-calls1.c
index 0d1b9fce833..d964b07ae5d 100644
--- a/gcc/testsuite/c-c++-common/strub-at-calls1.c
+++ b/gcc/testsuite/c-c++-common/strub-at-calls1.c
@@ -3,9 +3,9 @@
 
 /* h becomes STRUB_CALLABLE, rather than STRUB_INLINABLE, because of the
    strub-enabling -fstrub flag, and gets inlined before pass_ipa_strub.  */
-static void
+static inline void
 __attribute__ ((__always_inline__))
-h() {  /* { dg-warning "might not be inlinable" } */
+h() {
 }
 
 /* g becomes STRUB_AT_CALLS, because of the flag.  */
diff --git a/gcc/testsuite/c-c++-common/strub-default1.c b/gcc/testsuite/c-c++-common/strub-default1.c
index a1e1803aadc..d579ec62f55 100644
--- a/gcc/testsuite/c-c++-common/strub-default1.c
+++ b/gcc/testsuite/c-c++-common/strub-default1.c
@@ -3,12 +3,12 @@
 
 static int __attribute__ ((__strub__)) var;
 
-/* h becomes STRUB_STRUB_INLINABLE, because of the use of the strub variable,
+/* h becomes STRUB_INLINABLE, because of the use of the strub variable,
    and the always_inline flag.  It would get inlined before pass_ipa_strub, if
    it weren't for the error.  */
-static void
+static inline void
 __attribute__ ((__always_inline__))
-h() {  /* { dg-warning "might not be inlinable" } */
+h() {
   var++;
 }
 
@@ -34,7 +34,7 @@ f() {
 /* { dg-final { scan-ipa-dump-times "strub \[(\]2\[)\]" 1 "strubm" } } */
 
 /* { dg-final { scan-ipa-dump-times "strub \[(\]" 4 "strub" } } */
-/* { dg-final { scan-ipa-dump-times "strub \[(\]-3\[)\]" 1 "strubm" } } */
+/* { dg-final { scan-ipa-dump-times "strub \[(\]-3\[)\]" 1 "strub" } } */
 /* { dg-final { scan-ipa-dump-times "strub \[(\]-4\[)\]" 1 "strub" } } */
 /* { dg-final { scan-ipa-dump-times "strub \[(\]-1\[)\]" 1 "strub" } } */
 /* { dg-final { scan-ipa-dump-times "strub \[(\]-2\[)\]" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-internal1.c b/gcc/testsuite/c-c++-common/strub-internal1.c
index b9bd787df0a..a74658c9ac9 100644
--- a/gcc/testsuite/c-c++-common/strub-internal1.c
+++ b/gcc/testsuite/c-c++-common/strub-internal1.c
@@ -3,9 +3,9 @@
 
 /* h becomes STRUB_CALLABLE, rather than STRUB_INLINABLE, because of the
    strub-enabling -fstrub flag, and gets inlined before pass_ipa_strub.  */
-static void
+static inline void
 __attribute__ ((__always_inline__))
-h() {  /* { dg-warning "might not be inlinable" } */
+h() {
 }
 
 /* g becomes STRUB_INTERNAL because of the flag, and gets split into
diff --git a/gcc/testsuite/c-c++-common/strub-parms1.c b/gcc/testsuite/c-c++-common/strub-parms1.c
new file mode 100644
index 00000000000..0422ccc7f7d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-parms1.c
@@ -0,0 +1,48 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+#include <stdarg.h>
+
+void __attribute__ ((__strub__ (2)))
+small_args (int i, long long l, void *p, void **q, double d, char c)
+{
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid small_args.strub.\[0-9\]* \[(\]int i, long long int l, void \\* p, void \\* \\* q, double d, char c, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " small_args.strub.\[0-9\]* \[(\]\[^&\]*&.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+
+
+struct large_arg {
+  int x[128];
+};
+
+void __attribute__ ((__strub__ (2)))
+large_byref_arg (struct large_arg la)
+{
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid large_byref_arg.strub.\[0-9\]* \[(\]struct large_arg & la, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " large_byref_arg.strub.\[0-9\]* \[(\]&\[^&\]*&.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+
+void __attribute__ ((__strub__ (2)))
+std_arg (int i, ...)
+{
+  va_list vl;
+  va_start (vl, i);
+  va_end (vl);
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid std_arg.strub.\[0-9\]* \[(\]int i, \[^&,\]* &\[^&,\]*.strub.va_list_ptr, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " std_arg.strub.\[0-9\]* \[(\]\[^&\]*&.strub.va_list.\[0-9\]*, &.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_start \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_copy \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_end \\(" 2 "strub" } } */
+
+void __attribute__ ((__strub__ (2)))
+apply_args (int i, int j, double d)
+{
+  __builtin_apply_args ();
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid apply_args.strub.\[0-9\]* \[(\]int i, int j, double d, void \\*\[^&,\]*.strub.apply_args, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " apply_args.strub.\[0-9\]* \[(\]\[^&\]*.strub.apply_args.\[0-9\]*_\[0-9\]*, &.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-parms2.c b/gcc/testsuite/c-c++-common/strub-parms2.c
new file mode 100644
index 00000000000..aaf02a2cd46
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-parms2.c
@@ -0,0 +1,36 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+#include <stdarg.h>
+
+void __attribute__ ((__strub__ (1)))
+small_args (int i, long long l, void *p, void **q, double d, char c)
+{
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid small_args \[(\]int i, long long int l, void \\* p, void \\* \\* q, double d, char c, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+
+
+struct large_arg {
+  int x[128];
+};
+
+void __attribute__ ((__strub__ (1)))
+large_byref_arg (struct large_arg la)
+{
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid large_byref_arg \[(\]struct large_arg la, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+
+void __attribute__ ((__strub__ (1)))
+std_arg (int i, ...)
+{
+  va_list vl;
+  va_start (vl, i);
+  va_end (vl);
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid std_arg \[(\]int i, void \\* &\[^&,\]*.strub.watermark_ptr\[, .]*\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_start \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-not "va_copy \\(" "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_end \\(" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/strub-parms3.c b/gcc/testsuite/c-c++-common/strub-parms3.c
new file mode 100644
index 00000000000..2846098160d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/strub-parms3.c
@@ -0,0 +1,58 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that uses of a strub variable implicitly enables internal strub for
+   publicly-visible functions, and causes the same transformations to their
+   signatures as those in strub-parms1.c.  */
+
+#include <stdarg.h>
+
+int __attribute__ ((__strub__)) var;
+
+void
+small_args (int i, long long l, void *p, void **q, double d, char c)
+{
+  var++;
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid small_args.strub.\[0-9\]* \[(\]int i, long long int l, void \\* p, void \\* \\* q, double d, char c, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " small_args.strub.\[0-9\]* \[(\]\[^&\]*&.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+
+
+struct large_arg {
+  int x[128];
+};
+
+void
+large_byref_arg (struct large_arg la)
+{
+  var++;
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid large_byref_arg.strub.\[0-9\]* \[(\]struct large_arg & la, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " large_byref_arg.strub.\[0-9\]* \[(\]&\[^&\]*&.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+
+void
+std_arg (int i, ...)
+{
+  va_list vl;
+  va_start (vl, i);
+  var++;
+  va_end (vl);
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid std_arg.strub.\[0-9\]* \[(\]int i, \[^&,\]* &\[^&,\]*.strub.va_list_ptr, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " std_arg.strub.\[0-9\]* \[(\]\[^&\]*&.strub.va_list.\[0-9\]*, &.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_start \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_copy \\(" 1 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "va_end \\(" 2 "strub" } } */
+
+void
+apply_args (int i, int j, double d)
+{
+  var++;
+  __builtin_apply_args ();
+}
+
+/* { dg-final { scan-ipa-dump "\nvoid apply_args.strub.\[0-9\]* \[(\]int i, int j, double d, void \\*\[^&,\]*.strub.apply_args, void \\* &\[^&,\]*.strub.watermark_ptr\[)\]" "strub" } } */
+/* { dg-final { scan-ipa-dump " apply_args.strub.\[0-9\]* \[(\]\[^&\]*.strub.apply_args.\[0-9\]*_\[0-9\]*, &.strub.watermark.\[0-9\]*\[)\]" "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable1.c b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
new file mode 100644
index 00000000000..45965f275c9
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable1.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub -fno-inline" } */
+
+/* Check that strub and non-strub functions can be called from non-strub
+   contexts, and that strub and callable functions can be called from strub
+   contexts.  */
+
+#define OMIT_IMPERMISSIBLE_CALLS 1
+#include "strub-callable2.c"
+
+/* { dg-final { scan-ipa-dump-times "strub_enter" 45 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_update" 4 "strub" } } */
+/* { dg-final { scan-ipa-dump-times "strub_leave" 45 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-callable2.c b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
new file mode 100644
index 00000000000..38935e3270b
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-callable2.c
@@ -0,0 +1,264 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default" } */
+
+/* Check that impermissible (cross-strub-context) calls are reported.  */
+
+extern int __attribute__ ((__strub__ (3))) xcallable (void);
+extern int __attribute__ ((__strub__ (2))) xinternal (void);
+extern int __attribute__ ((__strub__ (1))) xat_calls (void);
+extern int __attribute__ ((__strub__ (0))) xdisabled (void);
+
+int __attribute__ ((__strub__ (3))) callable (void);
+int __attribute__ ((__strub__ (2))) internal (void);
+int __attribute__ ((__strub__ (1))) at_calls (void);
+int __attribute__ ((__strub__ (0))) disabled (void);
+
+int __attribute__ ((__strub__)) var;
+int var_user (void);
+
+static inline int __attribute__ ((__always_inline__, __strub__ (3)))
+icallable (void);
+static inline int __attribute__ ((__always_inline__, __strub__ (2)))
+iinternal (void);
+static inline int __attribute__ ((__always_inline__, __strub__ (1)))
+iat_calls (void);
+static inline int __attribute__ ((__always_inline__, __strub__ (0)))
+idisabled (void);
+static inline int __attribute__ ((__always_inline__))
+ivar_user (void);
+
+static inline int __attribute__ ((__always_inline__, __strub__ (3)))
+i_callable (void) { return 0; }
+static inline int __attribute__ ((__always_inline__, __strub__ (2)))
+i_internal (void) { return var; }
+static inline int __attribute__ ((__always_inline__, __strub__ (1)))
+i_at_calls (void) { return var; }
+static inline int __attribute__ ((__always_inline__, __strub__ (0)))
+i_disabled (void) { return 0; }
+static inline int __attribute__ ((__always_inline__))
+i_var_user (void) { return var; }
+
+#define CALLS_GOOD_FOR_STRUB_CONTEXT(ISEP)	\
+  do {						\
+    ret += i ## ISEP ## at_calls ();		\
+    ret += i ## ISEP ## internal ();		\
+    ret += i ## ISEP ## var_user ();		\
+  } while (0)
+
+#define CALLS_GOOD_FOR_NONSTRUB_CONTEXT(ISEP)	\
+  do {						\
+    ret += internal ();				\
+    ret += disabled ();				\
+    ret += var_user ();				\
+						\
+    ret += i ## ISEP ## disabled ();		\
+						\
+    ret += xinternal ();			\
+    ret += xdisabled ();			\
+  } while (0)
+
+#define CALLS_GOOD_FOR_EITHER_CONTEXT(ISEP)	\
+  do {						\
+    ret += i ## ISEP ## callable ();		\
+						\
+    ret += callable ();				\
+    ret += at_calls ();				\
+						\
+    ret += xat_calls ();			\
+    ret += xcallable ();			\
+  } while (0)
+
+/* Not a strub context, so it can call anything.
+   Explicitly declared as callable even from within strub contexts.  */
+int __attribute__ ((__strub__ (3)))
+callable (void) {
+  int ret = 0;
+
+  /* CALLS_GOOD_FOR_STRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += iat_calls (); /* { dg-error "in non-.strub. context" } */
+    ret += iinternal (); /* { dg-error "in non-.strub. context" } */
+    ret += ivar_user (); /* { dg-error "in non-.strub. context" } */
+#endif
+  CALLS_GOOD_FOR_EITHER_CONTEXT();
+  CALLS_GOOD_FOR_NONSTRUB_CONTEXT();
+
+  return ret;
+}
+
+/* Internal strubbing means the body is a strub context, so it can only call
+   strub functions, and it's not itself callable from strub functions.  */
+int __attribute__ ((__strub__ (2)))
+internal (void) {
+  int ret = var;
+
+  CALLS_GOOD_FOR_STRUB_CONTEXT();
+  CALLS_GOOD_FOR_EITHER_CONTEXT();
+  /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += internal (); /* { dg-error "in .strub. context" } */
+    ret += disabled (); /* { dg-error "in .strub. context" } */
+    ret += var_user (); /* { dg-error "in .strub. context" } */
+
+    ret += idisabled (); /* { dg-error "in .strub. context" } */
+
+    ret += xinternal (); /* { dg-error "in .strub. context" } */
+    ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+  return ret;
+}
+
+int __attribute__ ((__strub__ (1)))
+at_calls (void) {
+  int ret = var;
+
+  CALLS_GOOD_FOR_STRUB_CONTEXT();
+  CALLS_GOOD_FOR_EITHER_CONTEXT();
+  /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += internal (); /* { dg-error "in .strub. context" } */
+    ret += disabled (); /* { dg-error "in .strub. context" } */
+    ret += var_user (); /* { dg-error "in .strub. context" } */
+
+    ret += idisabled (); /* { dg-error "in .strub. context" } */
+
+    ret += xinternal (); /* { dg-error "in .strub. context" } */
+    ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+  return ret;
+}
+
+int __attribute__ ((__strub__ (0)))
+disabled () {
+  int ret = 0;
+
+  /* CALLS_GOOD_FOR_STRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += iat_calls (); /* { dg-error "in non-.strub. context" } */
+    ret += iinternal (); /* { dg-error "in non-.strub. context" } */
+    ret += ivar_user (); /* { dg-error "in non-.strub. context" } */
+#endif
+  CALLS_GOOD_FOR_EITHER_CONTEXT();
+  CALLS_GOOD_FOR_NONSTRUB_CONTEXT();
+
+  return ret;
+}  
+
+int
+var_user (void) {
+  int ret = var;
+
+  CALLS_GOOD_FOR_STRUB_CONTEXT();
+  CALLS_GOOD_FOR_EITHER_CONTEXT();
+  /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += internal (); /* { dg-error "in .strub. context" } */
+    ret += disabled (); /* { dg-error "in .strub. context" } */
+    ret += var_user (); /* { dg-error "in .strub. context" } */
+
+    ret += idisabled (); /* { dg-error "in .strub. context" } */
+
+    ret += xinternal (); /* { dg-error "in .strub. context" } */
+    ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+  return ret;
+}
+
+int
+icallable (void)
+{
+  int ret = 0;
+
+  /* CALLS_GOOD_FOR_STRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += i_at_calls (); /* { dg-error "in non-.strub. context" } */
+    ret += i_internal (); /* { dg-error "in non-.strub. context" } */
+    ret += i_var_user (); /* { dg-error "in non-.strub. context" } */
+#endif
+  CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+  CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_);
+
+  return ret;
+}
+
+int
+iinternal (void) {
+  int ret = var;
+
+  CALLS_GOOD_FOR_STRUB_CONTEXT(_);
+  CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+  /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += internal (); /* { dg-error "in .strub. context" } */
+    ret += disabled (); /* { dg-error "in .strub. context" } */
+    ret += var_user (); /* { dg-error "in .strub. context" } */
+
+    ret += i_disabled (); /* { dg-error "in .strub. context" } */
+
+    ret += xinternal (); /* { dg-error "in .strub. context" } */
+    ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+  return ret;
+}
+
+int
+iat_calls (void) {
+  int ret = var;
+
+  CALLS_GOOD_FOR_STRUB_CONTEXT(_);
+  CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+  /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += internal (); /* { dg-error "in .strub. context" } */
+    ret += disabled (); /* { dg-error "in .strub. context" } */
+    ret += var_user (); /* { dg-error "in .strub. context" } */
+
+    ret += i_disabled (); /* { dg-error "in .strub. context" } */
+
+    ret += xinternal (); /* { dg-error "in .strub. context" } */
+    ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+  return ret;
+}
+
+int
+idisabled () {
+  int ret = 0;
+
+  /* CALLS_GOOD_FOR_STRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += i_at_calls (); /* { dg-error "in non-.strub. context" } */
+    ret += i_internal (); /* { dg-error "in non-.strub. context" } */
+    ret += i_var_user (); /* { dg-error "in non-.strub. context" } */
+#endif
+  CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+  CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_);
+
+  return ret;
+}  
+
+int
+ivar_user (void) {
+  int ret = var;
+
+  CALLS_GOOD_FOR_STRUB_CONTEXT(_);
+  CALLS_GOOD_FOR_EITHER_CONTEXT(_);
+  /* CALLS_GOOD_FOR_NONSTRUB_CONTEXT(_); */
+#if !OMIT_IMPERMISSIBLE_CALLS
+    ret += internal (); /* { dg-error "in .strub. context" } */
+    ret += disabled (); /* { dg-error "in .strub. context" } */
+    ret += var_user (); /* { dg-error "in .strub. context" } */
+
+    ret += i_disabled (); /* { dg-error "in .strub. context" } */
+
+    ret += xinternal (); /* { dg-error "in .strub. context" } */
+    ret += xdisabled (); /* { dg-error "in .strub. context" } */
+#endif
+
+  return ret;
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const1.c b/gcc/testsuite/c-c++-common/torture/strub-const1.c
new file mode 100644
index 00000000000..100fb0c59a9
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const1.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub const function call, we issue an asm statement
+   to make sure the watermark passed to it is held in memory before the call,
+   and another to make sure it is not assumed to be unchanged.  */
+
+int __attribute__ ((__strub__, __const__))
+f() {
+  return 0;
+}
+
+int
+g() {
+  return f();
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const2.c b/gcc/testsuite/c-c++-common/torture/strub-const2.c
new file mode 100644
index 00000000000..9e818ac9748
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const2.c
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-const function call, we issue an
+   asm statement to make sure the watermark passed to it is held in memory
+   before the call, and another to make sure it is not assumed to be
+   unchanged.  */
+
+int __attribute__ ((__strub__))
+#if ! __OPTIMIZE__
+__attribute__ ((__const__))
+#endif
+f() {
+  return 0;
+}
+
+int
+g() {
+  return f();
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const3.c b/gcc/testsuite/c-c++-common/torture/strub-const3.c
new file mode 100644
index 00000000000..d40e8aa45cb
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const3.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub const wrapping call, we issue an asm statement
+   to make sure the watermark passed to it is held in memory before the call,
+   and another to make sure it is not assumed to be unchanged.  */
+
+int __attribute__ ((__strub__ (2), __const__))
+f() {
+  return 0;
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-const4.c b/gcc/testsuite/c-c++-common/torture/strub-const4.c
new file mode 100644
index 00000000000..d4cbdaf10f3
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-const4.c
@@ -0,0 +1,17 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-const wrapping call, we issue an
+   asm statement to make sure the watermark passed to it is held in memory
+   before the call, and another to make sure it is not assumed to be
+   unchanged.  */
+
+int __attribute__ ((__strub__ (2)))
+#if ! __OPTIMIZE__
+__attribute__ ((__const__))
+#endif
+f() {
+  return 0;
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 2 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-data5.c b/gcc/testsuite/c-c++-common/torture/strub-data5.c
index 251790d4bbb..07e25af9c53 100644
--- a/gcc/testsuite/c-c++-common/torture/strub-data5.c
+++ b/gcc/testsuite/c-c++-common/torture/strub-data5.c
@@ -1,15 +1,13 @@
 /* { dg-do compile } */
 /* { dg-options "-fstrub=default -Werror" } */
 
-/* The pointer itself is a strub variable, that would enable internal strubbing
-   if its value was used.  Here, it's only overwritten, so no strub.  */
 typedef int __attribute__ ((__strub__)) strub_int;
 strub_int *ptr;
 
 int *f () {
-  return ptr; /* { dg-warn "incompatible" } */
+  return ptr; /* { dg-warning "incompatible" } */
 }
 
 strub_int *g () {
-  return f (); /* { dg-warn "incompatible" } */
+  return f (); /* { dg-warning "incompatible" } */
 }
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure1.c b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
new file mode 100644
index 00000000000..cb223da6efc
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure1.c
@@ -0,0 +1,18 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub pure function call, we issue an asm statement
+   to make sure the watermark passed to it is not assumed to be unchanged.  */
+
+int __attribute__ ((__strub__, __pure__))
+f() {
+  static int i; /* Stop it from being detected as const.  */
+  return i;
+}
+
+int
+g() {
+  return f();
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure2.c b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
new file mode 100644
index 00000000000..67d1434b1f8
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure2.c
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-pure function call, we issue an asm
+   statement to make sure the watermark passed to it is not assumed to be
+   unchanged.  */
+
+int __attribute__ ((__strub__))
+#if ! __OPTIMIZE__ /* At -O0, implicit pure detection doesn't run.  */
+__attribute__ ((__pure__))
+#endif
+f() {
+  static int i; /* Stop it from being detected as const.  */
+  return i;
+}
+
+int
+g() {
+  return f();
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure3.c b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
new file mode 100644
index 00000000000..59f02ea901f
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure3.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub pure wrapping call, we issue an asm statement
+   to make sure the watermark passed to it is not assumed to be unchanged.  */
+
+int __attribute__ ((__strub__ (2), __pure__))
+f() {
+  static int i; /* Stop it from being detected as const.  */
+  return i;
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-pure4.c b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
new file mode 100644
index 00000000000..973e909217d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-pure4.c
@@ -0,0 +1,17 @@
+/* { dg-do compile } */
+/* { dg-options "-fstrub=default -fdump-ipa-strub" } */
+
+/* Check that, along with a strub implicitly-pure wrapping call, we issue an asm
+   statement to make sure the watermark passed to it is not assumed to be
+   unchanged.  */
+
+int __attribute__ ((__strub__ (2)))
+#if ! __OPTIMIZE__ /* At -O0, implicit pure detection doesn't run.  */
+__attribute__ ((__pure__))
+#endif
+f() {
+  static int i; /* Stop it from being detected as const.  */
+  return i;
+}
+
+/* { dg-final { scan-ipa-dump-times "__asm__" 1 "strub" } } */
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run1.c b/gcc/testsuite/c-c++-common/torture/strub-run1.c
new file mode 100644
index 00000000000..828a4cc2998
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run1.c
@@ -0,0 +1,85 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default" } */
+
+/* Check that a non-strub function leaves a string behind in the stack, and that
+   equivalent strub functions don't.  */
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+static inline __attribute__ ((__always_inline__, __strub__ (3)))
+char *
+leak_string (void)
+{
+  /* We use this variable to avoid any stack red zone.  Stack scrubbing covers
+     it, but __builtin_stack_address, that we take as a reference, doesn't, so
+     if e.g. callable() were to store the string in the red zone, we wouldn't
+     find it because it would be outside the range we searched.  */
+  typedef void __attribute__ ((__strub__ (3))) callable_t (char *);
+  callable_t *f = 0;
+
+  char s[sizeof (test_string)];
+  __builtin_strcpy (s, test_string);
+  asm ("" : "+m" (s), "+r" (f));
+
+  if (__builtin_expect (!f, 1))
+    return __builtin_stack_address ();
+
+  f (s);
+  return 0;
+}
+
+static inline __attribute__ ((__always_inline__))
+int
+look_for_string (char *e)
+{
+  char *p = __builtin_stack_address ();
+
+  if (p == e)
+    __builtin_abort ();
+
+  if (p > e)
+    {
+      char *q = p;
+      p = e;
+      e = q;
+    }
+
+  for (char *re = e - sizeof (test_string); p < re; p++)
+    for (int i = 0; p[i] == test_string[i]; i++)
+      if (i == sizeof (test_string) - 1)
+	return i;
+
+  return 0;
+}
+
+static __attribute__ ((__noinline__, __noclone__))
+char *
+callable ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ (1)))
+char *
+at_calls ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ (2)))
+char *
+internal ()
+{
+  return leak_string ();
+}
+
+int main ()
+{
+  if (!look_for_string (callable ()))
+    __builtin_abort ();
+  if (look_for_string (at_calls ()))
+    __builtin_abort ();
+  if (look_for_string (internal ()))
+    __builtin_abort ();
+  __builtin_exit (0);
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run2.c b/gcc/testsuite/c-c++-common/torture/strub-run2.c
new file mode 100644
index 00000000000..5794b694b2d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run2.c
@@ -0,0 +1,75 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default" } */
+
+/* Check that a non-strub function leaves a string behind in the stack, and that
+   equivalent strub functions don't.  */
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+static inline __attribute__ ((__always_inline__, __strub__ (3)))
+char *
+leak_string (void)
+{
+  int len = sizeof (test_string);
+  asm ("" : "+rm" (len));
+  char s[len];
+  __builtin_strcpy (s, test_string);
+  asm ("" : "+m" (s));
+  return __builtin_stack_address ();
+}
+
+static inline __attribute__ ((__always_inline__))
+int
+look_for_string (char *e)
+{
+  char *p = __builtin_stack_address ();
+
+  if (p == e)
+    __builtin_abort ();
+
+  if (p > e)
+    {
+      char *q = p;
+      p = e;
+      e = q;
+    }
+
+  for (char *re = e - sizeof (test_string); p < re; p++)
+    for (int i = 0; p[i] == test_string[i]; i++)
+      if (i == sizeof (test_string) - 1)
+	return i;
+
+  return 0;
+}
+
+static __attribute__ ((__noinline__, __noclone__))
+char *
+callable ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ (1)))
+char *
+at_calls ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ (2)))
+char *
+internal ()
+{
+  return leak_string ();
+}
+
+int main ()
+{
+  if (!look_for_string (callable ()))
+    __builtin_abort ();
+  if (look_for_string (at_calls ()))
+    __builtin_abort ();
+  if (look_for_string (internal ()))
+    __builtin_abort ();
+  __builtin_exit (0);
+}
diff --git a/gcc/testsuite/c-c++-common/torture/strub-run3.c b/gcc/testsuite/c-c++-common/torture/strub-run3.c
new file mode 100644
index 00000000000..7da79055959
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/torture/strub-run3.c
@@ -0,0 +1,75 @@
+/* { dg-do run } */
+/* { dg-options "-fstrub=default" } */
+/* { dg-require-effective-target alloca } */
+
+/* Check that a non-strub function leaves a string behind in the stack, and that
+   equivalent strub functions don't.  */
+
+const char test_string[] = "\x55\xde\xad\xbe\xef\xc0\x1d\xca\xfe\x55\xaa";
+
+static inline __attribute__ ((__always_inline__, __strub__ (3)))
+char *
+leak_string (void)
+{
+  int len = sizeof (test_string);
+  char *s = __builtin_alloca (len);
+  __builtin_strcpy (s, test_string);
+  asm ("" : "+m" (s));
+  return __builtin_stack_address ();
+}
+
+static inline __attribute__ ((__always_inline__))
+int
+look_for_string (char *e)
+{
+  char *p = __builtin_stack_address ();
+
+  if (p == e)
+    __builtin_abort ();
+
+  if (p > e)
+    {
+      char *q = p;
+      p = e;
+      e = q;
+    }
+
+  for (char *re = e - sizeof (test_string); p < re; p++)
+    for (int i = 0; p[i] == test_string[i]; i++)
+      if (i == sizeof (test_string) - 1)
+	return i;
+
+  return 0;
+}
+
+static __attribute__ ((__noinline__, __noclone__))
+char *
+callable ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ (1)))
+char *
+at_calls ()
+{
+  return leak_string ();
+}
+
+static __attribute__ ((__strub__ (2)))
+char *
+internal ()
+{
+  return leak_string ();
+}
+
+int main ()
+{
+  if (!look_for_string (callable ()))
+    __builtin_abort ();
+  if (look_for_string (at_calls ()))
+    __builtin_abort ();
+  if (look_for_string (internal ()))
+    __builtin_abort ();
+  __builtin_exit (0);
+}
diff --git a/gcc/testsuite/g++.dg/wrappers/strub1.C b/gcc/testsuite/g++.dg/strub-run1.C
similarity index 90%
rename from gcc/testsuite/g++.dg/wrappers/strub1.C
rename to gcc/testsuite/g++.dg/strub-run1.C
index a474a929649..754291eaa01 100644
--- a/gcc/testsuite/g++.dg/wrappers/strub1.C
+++ b/gcc/testsuite/g++.dg/strub-run1.C
@@ -1,4 +1,5 @@
 // { dg-do run }
+// { dg-options "-fstrub=internal" }
 
 // Check that we don't get extra copies.
 
diff --git a/gcc/testsuite/g++.dg/wrappers/strub2.C b/gcc/testsuite/g++.dg/wrappers/strub2.C
deleted file mode 100644
index 25a62166448..00000000000
--- a/gcc/testsuite/g++.dg/wrappers/strub2.C
+++ /dev/null
@@ -1,22 +0,0 @@
-// { dg-do run }
-
-// This doesn't really test anything yet.  We should mark the
-// variables as requiring strubbing, and somehow check that the
-// wrapped functions take the parameter by reference.
-
-struct T {
-  char d[32 * sizeof(void*)];
-};
-
-T foo (T q) { asm ("" : : "m"(q)); return q; }
-T bar (T p) { return foo (p); }
-
-T tmp;
-T tmp2;
-
-int main () {
-  __builtin_memset (&tmp, 0x55, sizeof (tmp));
-  tmp2 = bar (tmp);
-  if (__builtin_memcmp (&tmp, &tmp2, sizeof (tmp)))
-    __builtin_abort ();
-}
diff --git a/gcc/testsuite/g++.dg/wrappers/strub3.C b/gcc/testsuite/g++.dg/wrappers/strub3.C
deleted file mode 100644
index e1b51cd0399..00000000000
--- a/gcc/testsuite/g++.dg/wrappers/strub3.C
+++ /dev/null
@@ -1,22 +0,0 @@
-// { dg-do run }
-
-// This doesn't really test anything yet.  We should mark the
-// variables as requiring strubbing, and somehow check that the
-// wrapped functions take the parameter by reference.
-
-struct T {
-  char d[32 * sizeof(void*)];
-};
-
-static T foo (T q) { asm ("" : : "m"(q)); return q; }
-static T bar (T p) { return foo (p); }
-
-T tmp;
-T tmp2;
-
-int main () {
-  __builtin_memset (&tmp, 0x55, sizeof (tmp));
-  tmp2 = bar (tmp);
-  if (__builtin_memcmp (&tmp, &tmp2, sizeof (tmp)))
-    __builtin_abort ();
-}
diff --git a/gcc/testsuite/g++.dg/wrappers/strub4.C b/gcc/testsuite/g++.dg/wrappers/strub4.C
deleted file mode 100644
index d021fca88e4..00000000000
--- a/gcc/testsuite/g++.dg/wrappers/strub4.C
+++ /dev/null
@@ -1,18 +0,0 @@
-// { dg-do run }
-
-namespace
-{
-  class foo
-  {
-  public:
-    foo();
-  };
-
-  foo::foo() {}
-
-  foo bar;
-}
-
-int main()
-{
-}


^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2021-08-05 21:32 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-08-05 21:32 [gcc(refs/users/aoliva/heads/strub)] more tests, red zones, and deferred strubbing Alexandre Oliva
  -- strict thread matches above, loose matches on Subject: below --
2021-08-05 18:05 Alexandre Oliva
2021-08-05 14:04 Alexandre Oliva
2021-08-05 13:42 Alexandre Oliva
2021-08-05 13:33 Alexandre Oliva
2021-08-05 12:07 Alexandre Oliva

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).