public inbox for gcc-cvs@sourceware.org
help / color / mirror / Atom feed
From: Alexandre Oliva <aoliva@gcc.gnu.org>
To: gcc-cvs@gcc.gnu.org
Subject: [gcc(refs/users/aoliva/heads/strub)] more tests, red zones, and deferred strubbing
Date: Thu,  5 Aug 2021 18:05:56 +0000 (GMT)	[thread overview]
Message-ID: <20210805180556.E02FA3999008@sourceware.org> (raw)

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()
-{
-}


             reply	other threads:[~2021-08-05 18:05 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-08-05 18:05 Alexandre Oliva [this message]
  -- strict thread matches above, loose matches on Subject: below --
2021-08-05 21:32 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

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20210805180556.E02FA3999008@sourceware.org \
    --to=aoliva@gcc.gnu.org \
    --cc=gcc-cvs@gcc.gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).